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);
}
Related
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);
});
};
};};
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
I'm trying to determine how to append a large amount of HTML to an existing element in the DOM. Due to certain constraints, I can't use innerHTML. Supposedly it's bad to use innerHTML as it doesn't treat things like an object and reloads the DOM or something. I know jquery's .append() is an option, as it supposedly does things properly, but I want to use pure javascript; I've read a few things saying jQuery shouldn't be used anymore. If there are any other libraries, or if jQuery is a valid option, then I'm fine with using it. I'm just trying to do/learn things the "right way".
Here's how I've been doing it. The function takes some info and creates a table row. This seems like a bit much to do something so simple...
function flyoutAddTicket(caseID, ticketNumber, accountName, subject, tktStatus, priority, createdDate){
//Create table row that will be inserted into the flyout table
var newtr = document.createElement("tr");
newtr.id = "sfoFlyout_Ticket_" + caseID;
newtr.className = "sfoFlyout_Ticket";
// Create elements that will be inserted into the list item
var tdOwn = document.createElement("td");
tdOwn.id = "sfoFlyout_Ticket_" + caseID + "_OwnButton";
var btnOwn = document.createElement("button");
btnOwn.className = "sfoFlyout_own sfo_button";
btnOwn.value = caseID;
btnOwn.textContent = (easterEggs.pwnButton) ? "Pwn" : "Own";
var tdTicketNumber = document.createElement("td");
tdTicketNumber.id = "sfoFlyout_Ticket_" + caseID + "_TicketNumber";
var aTicketNumber = document.createElement("a");
aTicketNumber.textContent = ticketNumber;
aTicketNumber.href = "/" + caseID;
var tdAccountName = document.createElement("td");
tdAccountName.id = "sfoFlyout_Ticket_" + caseID + "_Client";
tdAccountName.textContent = accountName;
var tdSubject = document.createElement("td");
tdSubject.id = "sfoFlyout_Ticket_" + caseID + "_Subject";
var aSubject = document.createElement("a");
aSubject.textContent = subject;
aSubject.href = "/" + caseID;
var tdStatus = document.createElement("td");
tdStatus.id = "sfoFlyout_Ticket_" + caseID + "_Status";
tdStatus.textContent = tktStatus;
var tdPriority = document.createElement("td");
tdPriority.id = "sfoFlyout_Ticket_" + caseID + "_Priority";
tdPriority.className = "sfoFlyout_Ticket_Priority";
tdPriority.textContent = priority;
// Append elements to table row
if (sfoOptions.ownButton){ newtr.appendChild(tdOwn); }
tdOwn.appendChild(btnOwn);
newtr.appendChild(tdTicketNumber);
tdTicketNumber.appendChild(aTicketNumber);
newtr.appendChild(tdAccountName);
newtr.appendChild(tdSubject);
tdSubject.appendChild(aSubject);
newtr.appendChild(tdStatus);
newtr.appendChild(tdPriority);
// Assign user preferred colors/borders
for (var pref in preferences.clients){
// Set border thickness/colors
if (preferences.clients[pref].name == "border"){
newtr.style.borderBottomWidth = sfoOptions.borderThickness + "px";
newtr.style.borderColor = preferences.clients[pref].color;
}
// Set row colors
if (preferences.clients[pref].name == accountName){
newtr.style.backgroundColor = preferences.clients[pref].color;
}
}
//Add list item to the flyout
flyoutTable.appendChild(newtr);
}
you can push the created elements into a list them loop through the list and then inside the for loop
document.getElementById('yourElement').appendChild(elementYouCreated)
or use appendChild() on your created elements
elementYouCreated.appendChild(itsChildYouCreated)
The function I'm struggling with is part of a script that creates folders and copies a certain spreadsheet into each folder 15 times and names them.
I have a loop to create the files and inside of it I have an if..then..else statement to use a certain name for the file if j>10 and another name (else) if it's not. It always names the file one name and doesn't seem to recognize the if/else statement to change the name if j is over 10.
Basically what I want is to name the file
var namedFileCopy = fileCopy.setName(newFileName);
if j is less than 10 and...
var _namedFileCopy = fileCopy.setName("_" + newFileName);
if j isn't less than 10.
I'm wondering if I need to add a break in there somewhere, but I'm not really familiar with how to use breaks.
This is the function that is giving me a problem, and you'll find all my code in the snippet following it.
function putFilesIntoFolders(arrayOfFolderNames,theFolderNames,cell) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var theSheet = ss.getSheetByName('Sheet1');
var folderType = theSheet.getRange(2,1);
var cell = folderType.getValue();
var file = DriveApp.getFileById("ID");
var dest_folder = "";
var baseFileName = "",
newfile,
newFileName = "",
i=0,
j=0;
for (i=0;i<arrayOfFolderNames.length;i+=1) {
var source_folder = DriveApp.getFolderById("ID");
dest_folder = DriveApp.getFolderById(folderIds[i]);
Logger.log('dest_folder' + dest_folder);
baseFileName = arrayOfFolderNames[i];
for (j=1; j<16; j+=1) {
var newFileName = baseFileName + " " + cell + " " + j.toString();
var fileCopy = file.makeCopy();
var namedFileCopy = fileCopy.setName(newFileName);
var _namedFileCopy = fileCopy.setName("_" + newFileName);
if (j<10) {
dest_folder.addFile(namedFileCopy);
source_folder.removeFile(fileCopy);
} else {
dest_folder.addFile(_namedFileCopy);
source_folder.removeFile(fileCopy);
};
};
};
};
var folderIds = [];
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('Data System Tool')
.addItem('Create Data System Folders', 'copyAndRenameTemplate')
.addToUi();
}
function copyAndRenameTemplate() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var theSheet = ss.getSheetByName('Sheet1');
var rangeOfFileNames = ss.getRange("B4:B");
var twoD_Array = rangeOfFileNames.getValues();
var arrayOfFileNames = twoD_Array.join().split(",");
var folderType = theSheet.getRange(2,1);
var cell = folderType.getValue();
Logger.log(folderType);
Logger.log(cell);
// throw new Error('This is not an error. This is just to abort javascript');
var fldrNamesRng = theSheet.getRange(4,1,theSheet.getLastRow()-3,1);
Logger.log('fldrNamesRng: ' + fldrNamesRng);
var folderNames = fldrNamesRng.getValues();
Logger.log('folderNames: ' + folderNames);
var oneD_FolderNames = folderNames.join().split(",");
Logger.log('oneD_FolderNames: ' + oneD_FolderNames);
makeTheFolders(oneD_FolderNames);
putFilesIntoFolders(oneD_FolderNames);
};
function makeTheFolders(theFolderNames,cell) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var theSheet = ss.getSheetByName('Sheet1');
var folderType = theSheet.getRange(2,1);
var cell = folderType.getValue();
var i=0,
folderObj;
for (i=0;i<theFolderNames.length;i+=1) {
folderObj = DriveApp.createFolder(theFolderNames[i] + " " + cell);
folderIds.push(folderObj.getId())
};
};
function putFilesIntoFolders(arrayOfFolderNames,theFolderNames,cell) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var theSheet = ss.getSheetByName('Sheet1');
var folderType = theSheet.getRange(2,1);
var cell = folderType.getValue();
var file = DriveApp.getFileById("ID");
var dest_folder = "";
var baseFileName = "",
newfile,
newFileName = "",
i=0,
j=0;
for (i=0;i<arrayOfFolderNames.length;i+=1) {
var source_folder = DriveApp.getFolderById("ID");
dest_folder = DriveApp.getFolderById(folderIds[i]);
Logger.log('dest_folder' + dest_folder);
baseFileName = arrayOfFolderNames[i];
for (j=1; j<16; j+=1) {
var newFileName = baseFileName + " " + cell + " " + j.toString();
var fileCopy = file.makeCopy();
var namedFileCopy = fileCopy.setName(newFileName);
var _namedFileCopy = fileCopy.setName("_" + newFileName);
if (j<10) {
dest_folder.addFile(namedFileCopy);
source_folder.removeFile(fileCopy);
} else {
dest_folder.addFile(_namedFileCopy);
source_folder.removeFile(fileCopy);
};
};
};
};
There's no issue about your loops, no need to break; out of them.
Your problem occurs in these lines of code:
var fileCopy = file.makeCopy();
var namedFileCopy = fileCopy.setName(newFileName);
var _namedFileCopy = fileCopy.setName("_" + newFileName);
The makeCopy() method will create a new file object, representing a new file in your Google Drive.
After that, you call the setName() method on the fileCopy object, which changes the name of the associated file. That's fine, except that you've assigned the return value of the method call to a new variable, namedFileCopy. You've now got two variables that reference the same object, because this method simply returns the original object for chaining. (Chaining is when we call multiple methods on on object in the same statement; it's only possible if each method returns the original object reference for the next method.)
Next, you call setName() on fileCopy again, assigning the return value to _namedFileCopy; that's the third reference to the exact same object. Oh, and that file - the file that all three object references point to - is now named with an underscore.
After this, your if..then..else is doomed to name the resulting file with an underscore. Actually... the file is already named with an underscore, from the third operation above.
Separate concerns some more; you've got to make a decision about a file name, so do that in isolation. Once you've completed that concern, you are ready to manipulate the file, which is a separate concern.
The resulting loop should look something like this:
for (i=0; i<arrayOfFolderNames.length; i++) {
var source_folder = DriveApp.getFolderById("ID");
dest_folder = DriveApp.getFolderById(folderIds[i]);
Logger.log('dest_folder' + dest_folder);
baseFileName = arrayOfFolderNames[i];
for (j=1; j<16; j++) {
// Determine the name for the new file
var newFileName = baseFileName + " " + cell + " " + j.toString();
// Prepend file name with underscore if j >= 10
if (j >= 10) newFileName = "_" + newFileName;
// Copy "file" and apply our newFileName, then move to new destination.
var fileCopy = file.makeCopy()
.setName(newFileName);
dest_folder.addFile(fileCopy);
source_folder.removeFile(fileCopy);
}
}
(I suspect this is what being said in Alvaz' answer.)
fileCopy.setName() changes the filename property of the file object. You should move your calls to fileCopy.setName() inside of your conditional branches.
I have been looking at this code for a long time trying to figure this out, but I am having no luck. This issue is that I want to assign a value to the parameter boxId. When I click on a box in the webpage an alert will come up displaying that id. I have tried many things, but nothing seems to work. I'm a beginner, so I feel at this point there just must be something that I don't know how to do.
constructor function:
function Box (boxId, name, color, number, coordinates) {
this.boxId = boxId;
this.name = name;
this.color = color;
this.number = number;
this.coordinates = coordinates;
}
global variables:
var boxes = [];
var counter = 0;
var boxId = 0;
init function:
window.onload = init;
function init() {
var generateButton = document.getElementById("generateButton");
generateButton.onclick = getBoxValues;
var clearButton = document.getElementById("clearButton");
clearButton.onclick = clear;
}
function to get values and create new boxes:
function getBoxValues() {
var nameInput = document.getElementById("name");
var name = nameInput.value;
var numbersArray = dataForm.elements.amount;
for (var i = 0; i < numbersArray.length; i++) {
if (numbersArray[i].checked) {
number = numbersArray[i].value;
}
}
var colorSelect = document.getElementById("color");
var colorOption = colorSelect.options[colorSelect.selectedIndex];
var color = colorOption.value;
if (name == null || name == "") {
alert("Please enter a name for your box");
return;
}
else {
var newbox = new Box(boxId, name, color, number, "coordinates");
boxes.push(newbox);
counter++;
var boxId = counter;
}
addBox(newbox);
var data = document.getElementById("dataForm");
data.reset();
}
function that adds boxes to the page:
function addBox(newbox) {
for (var i = 0; i < newbox.number; i++) {
var scene = document.getElementById("scene");
var div = document.createElement("div");
div.className += " " + "box";
div.innerHTML += newbox.name;
div.style.backgroundColor = newbox.color;
var x = Math.floor(Math.random() * (scene.offsetWidth-101));
var y = Math.floor(Math.random() * (scene.offsetHeight-101));
div.style.left = x + "px";
div.style.top = y + "px";
scene.appendChild(div);
div.onclick = display;
}
}
function to display alert when box is clicked:
function display(e) {
var a = e.target;
alert(a.counter);
}
function to clear boxes:
function clear() {
var elems = document.getElementsByClassName("box");
for ( k = elems.length - 1; k >= 0; k--) {
var parent = elems[k].parentNode;
parent.removeChild(elems[k]);
}
}
All of the other functions work just fine. I keep running into the id showing up as "undefined" when I click it, or the counter displaying "0" in the console log, for everything I've tried.
You can do it like this.
First, in addBox() embed boxId as an tag's attribute like this:
div.setAttribute('data-boxId', newbox.boxId);
Then in display() you can retrieve it back:
alert(e.target.getAttribute('data-boxId'));
Please tell if you do not prefer this approach and I will post an alternative (closure things).
Edit: Add jsfiddle example http://jsfiddle.net/runtarm/8FJpU/
One more try. Perhaps if you change:
var boxId = counter;
to
boxId = counter;
It will then use the boxId from the outer scope instead of the one defined in the function getBoxValues()