Why does using "once" instead of "on" break things here? - javascript

So I built an app where you enter the name of your favorite book and author in a form, and it generates a card with that information.
There's a checkbox on the form to mark if you own it and a "remove" button.
It works with an addtoLibrary() function that takes the info from the form, puts it into an object and that object into firebase.
Within that function, I have another function called render() which takes the info from firebase and puts it into the DOM.
Next I added another function called retrievefromDatabase() which also incorporates the render() function, and loops through the list of objects to draw cards for the items that exist in the database.
The problem I'm facing now is that whenever the checkbox is checked or unchecked, the retrievefromDatabase() function activates and draws another card.
I think if I could change the on in the second database call to once here, that would solve my problem. But when I do that, all the fields in my card render to "undefined."
I'm not sure why it's even doing that, because the initial database call is a once that loops over every object key, and there are no new object keys when the checkbox changes. It's just a change of state for the object.
function retrievefromDatabase() {
firebase.database().ref("Book").once("value", gotData);
function gotData(Book) {
var books = Book.val();
var keys = Object.keys(books);
for (var i = 0; i < keys.length; i++) {
firebase.database().ref("Book/" + keys[i]).on("value", function(snapshot) {
newPostKey = snapshot.key;
function oldBook(title, fname, lname, pubDate, contrib, own) {
this.title = title;
this.fname = fname;
this.lname = lname;
this.pubDate = pubDate;
this.contrib = contrib;
this.own = own;
};
var archiveBook = new oldBook(snapshot.val().title,
snapshot.val().fname,
snapshot.val().lname,
snapshot.val().pubDate,
snapshot.val().contrib,
snapshot.val().own);
render();
})}}};

When you add a listener to a location in the database, you get a snapshot with all data under that location. This means that there is no need to attach a listener to nodes under that location in the callback.
In addition, it is much easier to loop over the child nodes of a snapshot with snapshot.forEach(), instead of extracting the keys and then using those.
So your code can be simplified to:
function retrievefromDatabase() {
firebase.database().ref("Book").once("value", (book) => {
book.forEach((snapshot) => {
newPostKey = snapshot.key;
function oldBook(title, fname, lname, pubDate, contrib, own) {
this.title = title;
this.fname = fname;
this.lname = lname;
this.pubDate = pubDate;
this.contrib = contrib;
this.own = own;
};
var archiveBook = new oldBook(snapshot.val().title,
snapshot.val().fname,
snapshot.val().lname,
snapshot.val().pubDate,
snapshot.val().contrib,
snapshot.val().own);
})
render();
}
};
On on vs once:
if you only want to get the data once, you should use once. This also means that you'll have to call retrievefromDatabase each time you want to load the data.
if you want to continue to monitor the database for changes after retrieving the data, you should use on. In this case, be sure to only call retrievefromDatabase once, as you don't want to attach multiple permanent listeners.

This is what I attempted, which worked, but is much uglier than Frank's solution:
function retrievefromDatabase() {
firebase.database().ref("Book").once("value", gotData);
function gotData(Book) {
var books = Book.val();
var keys = Object.keys(books);
for (var i = 0; i < keys.length; i++) {
firebase.database().ref("Book/" + keys[i]).once("value", function(snapshot) {
titlesnap = snapshot.val().title;
fnamesnap = snapshot.val().fname;
lnamesnap = snapshot.val().lname;
pubsnap = snapshot.val().pubDate;
contribsnap = snapshot.val().contrib;
newPostKey = snapshot.key;
const bookContainer = document.createElement("div");
bookContainer.classList.add("book-container");
const newVolume = document.createElement("div");
newVolume.classList.add("volume");
bookContainer.appendChild(newVolume);
bookContainer.setAttribute('id', `${newPostKey}`);
const frontCover = document.createElement("div");
newVolume.appendChild(frontCover);
frontCover.style.setProperty("background-color", getRandomColor());
frontCover.innerHTML = "<br /><br />"
+ "<b>" + titlesnap + "</b>" + "<br /><br />"
+ fnamesnap + " "
+ lnamesnap + "<br /><br />"
+ "Published: " + pubsnap + "<br />"
+ "Added by: <br />" + contribsnap + "<br />";
const checkbox = document.createElement('input');
checkbox.type = "checkbox";
checkbox.id = "checkbox";
if (snapshot.val().own == true) {
checkbox.checked = true;
}
else {
checkbox.checked = false;
};
checkbox.addEventListener("change", function() {
if (checkbox.checked === false) {
firebase.database().ref('Book/' + bookContainer.id + '/own').set(false);
}
else if (checkbox.checked === true) {
firebase.database().ref('Book/' + bookContainer.id + '/own').set(true);
}});
const label = document.createElement("label");
label.appendChild(document.createTextNode(" I own a copy"));
const newgraf = document.createElement("p")
frontCover.appendChild(checkbox);
frontCover.appendChild(label);
frontCover.appendChild(newgraf);
const removeButton = document.createElement('button')
frontCover.appendChild(removeButton);
removeButton.textContent = 'Remove';
removeButton.addEventListener("click", function(event){
firebase.database().ref('Book/').child(bookContainer.id).remove()
bookContainer.remove();
})
libraryContainer.insertAdjacentElement('afterbegin',bookContainer);
});
};
};};

Related

How do i display the genres of a particular movie from tmdb api

I am trying to get the details of the the movie using tmdb api so fall everything was good until i tried to display all the genres of that movie
const tmdb_api_url = "https://api.themoviedb.org/3/tv/" + 127332 + "?api_key=API-KEY";
async function getDetails() {
const response = await fetch (tmdb_api_url);
const data = await response.json();
const { name, first_air_date, vote_average, number_of_seasons, episode_run_time, genres, overview, origin_country} = data;
document.getElementById('title').textContent = name;
document.getElementById('first_air_date').textContent = first_air_date;
document.getElementById('vote_average').textContent = vote_average;
document.getElementById('number_of_seasons').textContent = number_of_seasons + " Season" + (number_of_seasons == 1 ? "" : "s ");
document.getElementById('run_time').textContent = episode_run_time;
document.getElementById('overview').textContent = overview;
document.getElementById('origin_country').textContent = origin_country;
var g = "";
for (i in genres) {
g += genres[i].name;
}
document.getElementById('genres').textContent = genres[i].name;
}
getDetails();
This is what i tried but its only showing one genre
And can anyone help me simplify the code what i tried
You are getting only one genre because of this line document.getElementById('genres').textContent = genres[i].name;
This will always display the last genre.
Modify it as follows
var g = "";
for (i in genres) {
g += genres[i].name + ", ";
}
document.getElementById('genres').textContent = g.substr(0, g.length - 1); // to remove last , added
Update : Adding each genre in there respective anchor tag
let genreTags = "";
for(i in genres){
genreTags+= `${genres[i].name}`; // Add genre link in the href
}
document.getElementById("genres").innerHTML = genreTags;
This is the jist of adding genres as anchor, but there are much better ways of doing it.

Adding a listener to a button created by .innerHTML

I am trying to add a listener to a button created by .innerHTML after receiving a listener. This is the code:
let inputConfirm = document.getElementById('inputConfirm');
let inputText = document.getElementById('inputText');
let displayList = document.getElementById('displayList');
inputConfirm.addEventListener('click', e=>listElement())
const listElement = () => {
let inputValue = inputText.value //takes input from textbox
let newValue = "<li class='each-item'><div class='listbox'>" + inputValue + "<input type='checkbox' class='checkbox'><button class='delete-button'>X</button></div></li>"; //EACH list element that I want to add to the ordered list.
if(inputValue !== ""){
displayList.innerHTML += newValue;
inputText.value = "";//clears the text box
};
};
I need the button with class "delete-button" created via newValue variable to be clickable and send a console.log
It's unclear what you want to output in the console but the simple way is to add onclick event into .innerHTML and add a function.
let inputConfirm = document.getElementById('inputConfirm');
let inputText = document.getElementById('inputText');
let displayList = document.getElementById('displayList');
inputConfirm.addEventListener('click', e=>listElement())
const listElement = () => {
let inputValue = inputText.value //takes input from textbox
let newValue = "<li class='each-item'><div class='listbox'>" + inputValue + "<input type='checkbox' class='checkbox'><button class='delete-button' onclick='toConsole(event)'>X</button></div></li>"; //EACH list element that I want to add to the ordered list.
if(inputValue !== ""){
displayList.innerHTML += newValue;
inputText.value = "";//clears the text box
};
};
function toConsole(e){
console.log(e.target);
}

Why can an event not be attached in the script but in the console?

I want to dynamically create, populate and clear a list with html and javascript. The creation and population of the list work just fine, but when I want to add the delete-button to the list item I can't attach the onclick event to the newly created element. Here is my complete function, it is called every time some changes happen to the printlist array:
var printlist = [];
var awesome = document.createElement("i");
awesome.className = "fa fa-minus";
function addToList(stationid, stationname)
{
var object = {id: stationid, name: stationname};
printlist.push(object);
drawList();
}
function removeFromList(id)
{
printlist.splice(id, 1);
drawList();
}
function drawList()
{
if (printlist.length > 0)
{
document.getElementById("printListDialog").style.visibility = 'visible';
var dlg = document.getElementById("DlgContent");
dlg.innerHTML = "";
for (var i = 0; i < printlist.length; i++)
{
var item = document.createElement("li");
item.className = "list-group-item";
var link = document.createElement("a");
link.href = "#";
link.dataset.listnumber = i;
link.style.color = "red";
link.style.float = "right";
link.appendChild(awesome);
link.onclick = function(){onRemove();};
item.innerHTML = printlist[i].name + " " + link.outerHTML;
dlg.appendChild(item);
}
}
else
{
document.getElementById("printListDialog").style.visibility = 'hidden';
}
}
function onRemove(e)
{
if (!e)
e = window.event;
var sender = e.srcElement || e.target;
removeFromList(sender.dataset.listnumber);
}
I tried:
link.onclick = function(){onRemove();};
as well as
link.addEventListener("click", onRemove);
Neither of those lines successfully adds the event from the script. However when I call any of the 2 lines above from the console it works and the event is attached.
Why does it work from the console but not from the script?
link.onclick = function(){onRemove();};
doesn't work because you're not passing through the event argument. link.onclick = onRemove should work just as your addEventListener call.
However, both of them don't work because of the line
item.innerHTML = printlist[i].name + " " + link.outerHTML;
which destroys the link element with all its dynamic data like .dataset or .onclick, and forms a raw html string that doesn't contain them. They're lost.
Do not use HTML strings!
Replace the line with
item.appendChild(document.createTextNode(printlist[i].name + " "));
item.appendChild(link); // keeps the element with the installed listener

button onclick not working on first click

my button.onclick doesn't work on the first click, but it works on the second.
I used an alert to check and even the alert doesnt work on the first click, but works on the second click.
here's the link to the app in case you need it -
http://silentarrowz.imad.hasura-app.io/news
could you tell me what's wrong??
here's the code
window.onclick = function () {
var displayNews = document.getElementById('currentNews');
var newsButton = document.getElementById('getnews');
newsButton.onclick = function () {
alert('the button is clicked');
var newsxr = new XMLHttpRequest();
newsxr.onreadystatechange = function () {
if (newsxr.readyState === XMLHttpRequest.DONE && newsxr.status === 200) {
var currentNews = JSON.parse(newsxr.responseText);
var currentArticles = currentNews['articles'];
var numberArticles = currentNews['articles'].length;
var newsDisplay = '';
var author;
var title;
var description;
var urlToImage;
for (var i = 0; i < numberArticles; i++) {
author = currentArticles[i]['author'];
title = currentArticles[i]['title'];
description = currentArticles[i]['description'];
urlToImage = currentArticles[i]['urlToImage'];
newsDisplay = newsDisplay + "<p>" + "<span class='title'>" + title + "</span>" + "<br>" + description + "<br>" + "<img src='" + urlToImage +
"'</img>" + "</p>";
}
alert('displaying the news now');
console.log('current news is : ', currentNews);
displayNews.innerHTML = newsDisplay;
}
}; //on state change
newsxr.open('GET', 'https://newsapi.org/v1/articles?source=national-geographic&sortBy=top&apiKey=1af110441a8e4f72925f78344e58c2a4', true);
newsxr.send(null);
}; //button onclick function ends
}; // window onclick function ends
The truth about your code is the following..
You are assigning an onclick event to the window. when you click the window it then gets the buttons id which then assigns an onclick event to your button.
Your button only works when you click anywhere in the window (your browser User interface). You can try it and see
SOLUTION
remove the on window.onclick event stuff.
this should be the only code you should be seeing in your editor to make things work.
var displayNews = document.getElementById('currentNews');
var newsButton = document.getElementById('getnews');
newsButton.onclick = function(){
alert('the button is clicked');
var newsxr = new XMLHttpRequest();
newsxr.onreadystatechange = function(){
if(newsxr.readyState ===XMLHttpRequest.DONE && newsxr.status ===200){
var currentNews = JSON.parse(newsxr.responseText);
var currentArticles = currentNews['articles'];
var numberArticles = currentNews['articles'].length;
var newsDisplay ='';
var author;
var title;
var description;
var urlToImage;
for(var i=0;i<numberArticles;i++){
author = currentArticles[i]['author'];
title = currentArticles[i]['title'];
description = currentArticles[i]['description'];
urlToImage = currentArticles[i]['urlToImage'];
newsDisplay = newsDisplay + "<p>"+"<span class='title'>"+ title+"</span>"+ "<br>"+description+"<br>"+"<img src='"+urlToImage+"'</img>"+"</p>";
}
alert('displaying the news now');
console.log('current news is : ',currentNews);
displayNews.innerHTML = newsDisplay;
}
};//on state change
newsxr.open('GET','https://newsapi.org/v1/articles?source=national-geographic&sortBy=top&apiKey=1af110441a8e4f72925f78344e58c2a4',true);
newsxr.send(null);
};
//button onclick function ends
I hope this was explanatory
Because the first click is window.onclick = function() part which tells the window to define another click event only, and then the real click event will work when you click the second time.
Deleting the window click event already suffices.
P.S. I don't see why having window click event is meaningful in your code.

set multiple sliders onchange functions with changin variables

I have a loop with changing parameters wich I'd like to use in a oninput function of the sliders I'm creating within that loop, but I can't get it to work. Here's a simplified version of my script:
for (var con in dict) {
var div = document.getElementById("content");
var conDiv = document.createElement("div");
conDiv.innerHTML = "<b>" + con + ":</b><br>";
var effectID = dict[con].effect_id;
for (var param in dict[con].params) {
var inp,span = document.createElement("span");
span.innerHTML = " " + param + " ";
conDiv.appendChild(span);
inp = document.createElement("input");
inp.type = "range";
inp.min = vars[effectID][param].min;
inp.max = vars[effectID][param].max;
inp.value = dict[con].params[param];
inp.oninput = function(con,param,val) {
setParam(con,param,val);
}(con,param,this.value);
conDiv.appendChild(inp);
}
div.appendChild(conDiv);
}
What's wrong with my code?
edit: My goal: I have a set of audio effects that I want to change. Every container (con) controls an effect node via multiple parameters. All those parameters have different min- and max-values and an actual value they have right now. Via the sliders I want to be able to call a function that changes the parameters of an container. Therefore, every slider should control one effect parameter.
That's because the callback is called after the loop finish, so your variables have changed.
A usual trick is to use an immediately called function whose scope can store your variable values :
for (var con in dict) {
(function(con) { // <= creates a scope and a new con variable
var div = document.getElementById("content");
var conDiv = document.createElement("div");
conDiv.innerHTML = "<b>" + con + ":</b><br>";
var effectID = dict[con].effect_id;
for (var param in dict[con].params) {
(function(param){
var inp,span = document.createElement("span");
span.innerHTML = " " + param + " ";
conDiv.appendChild(span);
inp = document.createElement("input");
inp.type = "range";
inp.min = vars[effectID][param].min;
inp.max = vars[effectID][param].max;
inp.value = dict[con].params[param];
inp.oninput = function() {
setParam(con,param,inp.value);
};
conDiv.appendChild(inp);
})(param);
}
div.appendChild(conDiv);
})(con);
}

Categories

Resources