jquery not triggered in loop - javascript

I'm building an internal Javascript app and it has a function for creating editable lists of objects. It loops through an array of objects, and displays a summary for each one along with an edit button. It then displays some more buttons at the bottom of the list for creating new items and going to the previous screen.
All of the buttons work, expect ones created within the loop. The click event never fires for the buttons added to the DOM within the loop. The really strange thing is that if I take the edit variable and append to the DOM outside of the loop the click event does fire.
var displayArray = function (where, list, summarise, display, newItem, completion, back) {
var listing = $("<div>");
listing.addClass("Listing");
for (var i = 0; i < list.length; i++) {
var index = i;
var edit = createButton("Edit", function () {
display(list[index]);
});
var anchor = $("<p>");
anchor.text(summarise(list[index]));
anchor.append(edit); //this will create an edit button, but the click event is never fired
listing.append(anchor);
}
where.append(listing);
var create = createButton("New", newItem);
where.empty();
where.append(listing);
where.append(create);
//where.append(edit); // uncomment this and it will create a button that will display the last item in the list
if (back) { where.append(createButton("Back", back)); }
if (completion) {
completion();
}
}
var createButton = function(name, action){
var button = $("<input>");
button.attr("type", "button");
button.attr("value", name);
button.on("click", action);
return button;
}
The variables being passed into the function are as follows:
where = the html element that will display the items
list = the array of objects to display
summarise = a function that generates summary text for an object passed to it that will be displayed for this object listing
display = a function that generates the edit screen for an object
newItem = a function that generates a new object and displays the edit screen for it
completion = a function to call after the screen has been built that will generate any additional UI as needed.
back = a function to display the previous screen
The behaviour is consistent across IE9, Firefox 20.0.1, and Chrome 29.0.1547.66

I know exactly what the problem is here:
Try this.
for (var i = 0; i < list.length; i++) {
var index = i;
var edit = createButton("Edit", (function (item) {
return function () {
display(item);
};
})(list[index]));

Related

Passing object variable to onchange event JS

I've been browsing around for an answer to this question for a little while now, but haven't found a solution. I need to pass an object to a function which is being fired "onChange" when a select option is chosen by a user. The current code is:
selecter.onchange = function(){
var runScript = $("#actionSel option:selected").attr('script');
console.log(runScript);
eval("("+runScript+")();");
}
The intention here is to store a function within the "script" attr of the options, which will then be run when that option is selected. However, for one of my functions I need to pass a variable from the parent scope on in order to interact with the server via websockets.
The function stored in the "Script" attribute is :
function(){ popConfirm("Restore User","Do you really want to restore the selected users? This will un-delete the selected deleted users.",function(r){ if(r)restoreUser(r,io); });
Essentially this verifies with the user that they want to do what they've selected, then passes the result to my restore user function. It also needs to pass the "io" object on. However, I'm getting an error which states that io is undefined.
Any ideas would be extremely helpful. Thanks!
As requested, here are some additional relative pieces of code showing where IO is introduced.
AdminIO = new io(servPath);
AdminIO.on('send_users',function(rows){
toggleLoad();
appendUsers(rows,AdminIO);
});
Within appendUsers there's another function which compiles the select list and its options, dropActions(), wherein the selector.onchange and other piece I posted before are introduced. The selector.onchange is part of the function that creates the dropdown list. The function(){ popConfirm() } is added as the function to run on selection of that item. The function to build the list is :
dropActions = function(bActions, lActions, options){
// bActions = {id: myID, text: "this is my action", elem: document.getElementById('getDiv'), action: function(){ /*mycode here */}}
// lActions = {text: "select me to run an action", action: function(){ /*mycode here */}}
bActions = bActions || null;
lActions = lActions || null;
options = options || {elem: document.body, id: null};
if(!bActions && !lActions){ console.error("No actions added or available."); return; }
var
selID = options.id,
starter = (selID) ? document.getElementById(selID) : options.elem,
optsBar = document.createElement("header"),
selecter = document.createElement("select");
starterSel = document.createElement("option");
optsBar.id = "actionSelH";
starterSel.innerText = "More Actions";
starterSel.setAttribute('script','javascript:void(0)');
selecter.id = "actionSel";
selecter.appendChild(starterSel);
for(var i= 0; bActions.length > i; i++){
var
buttonText = bActions[i].text,
buttonID = bActions[i].id || 'ACT'+i,
buttonAction = bActions[i].action,
button = document.createElement('div');
button.id = buttonID;
button.classList.add("actionButton");
button.innerText = buttonText;
button.onclick = buttonAction;
optsBar.appendChild(button);
}
for(var i= 0; lActions.length > i; i++){
var selText = lActions[i].text,
selScript = lActions[i].action,
option = document.createElement('option');
option.innerText = selText;
option.setAttribute('script',selScript);
selecter.appendChild(option);
}
selecter.onchange = function(){
var runScript = $("#actionSel option:selected").attr('script');
console.log(runScript);
eval("("+runScript+")();");
}
optsBar.appendChild(selecter);
$(optsBar).insertBefore('#user_list_table');
//$('#user_list_table').after(optsBar);
//$(starter).prepend(optsBar);
},
Hopefully more context helps!
Instead of using eval, you could use the function constructor and pass your parameters directly to that function:
var scriptFunction = new Function(r, io, runScript);
scriptFunction(r, io);
Of course, this code assume that r and io will always be the variable you are looking for. If this ain't the case, you'll have to write your own logic to take the variable from the parent scope and then pass them to the scriptFunction.
Update
If you are able ton change the provided script, you could also try to implicitly declare the variables r and io:
function(r, io){ popConfirm("Restore User","Do you really want to restore the selected users? This will un-delete the selected deleted users.",function(r, io){ if(r)restoreUser(r,io); });

Plain OOP Javascript: Treating localStorage as an Array doesn't work?

I am trying to implement localStorage with my simple OOP todo list.
The fiddle is here: https://jsfiddle.net/b81t2789/
I thought I could just treat the local storage like an array and copy the logic I used with my actual array but that doesn't work.
Here, right after pushing the task into the array, I added a line that stores the task in the local storage and stringifies it:
// function that adds new task to the array
function pushArray(){
var newtask = new Task(toDo.value, "No note yet");
taskItems.push(newtask);
var storedTask = localStorage.setItem(newtask, JSON.stringify(newtask));
displayStorage(result2, storedTask);
displayArray(result, newtask.Name);
appendNote(result, newtask);
}
Then right below the function that displays the new array element I added one that retrieves the item from local storage, parses it, then creates a DOM element with the new task and appends it to another container.
//function that displays array elements
function displayArray(parent,obj){
var task = make("div","class","taskitem",obj);
parent.appendChild(task);
fadeIn(task);
}
//function that displays storage elements
function displayStorage(parent,obj){
var retrieveObject = localStorage.getItem(obj);
var parseTask = JSON.parse(retrieveObject);
var newDiv = make("div", "class", "newdiv", parseTask);
parent.appendChild(newDiv);
fadeIn(newDiv);
}
This doesn't work at all, not sure why, and then if I were to be able to get this to work how would I continue to go about storing and updating notes like I did in the array with local Storage? I thought this would be easy as I figured out how to make a todo with objects and arrays pretty quickly (when I thought it would be super difficult, but it's been a week now and I've made no progress!)
I guess these are the pitfalls of learning to code by yourself, any help would be much appreciated thank you!
Here is the full javascript code:
//getElementById shortcut
function grab(id) {
return document.getElementById(id);
}
// add eventlistener shortcut
var when = function() {
return function(obj, event, func) {
obj.addEventListener(event, func, false);
};
}();
//Custom function to create DOM elements and set their contents
function make(el,type,name,content){
var theElement = document.createElement(el);
theElement.setAttribute(type, name);
theElement.innerHTML = content;
return theElement;
}
//compute style shortcut
function setStyle(theElement){
return window.getComputedStyle(theElement);
}
//fade in shortcut.
function fadeIn(theElement){
var compute = setStyle(theElement).opacity;
theElement.style.opacity = 1;
}
/*****************************************************/
var toDo = grab("todo");
var result = grab("demo");
var demolist = grab("demolist");
var button = grab("btn");
// submit input on enter which fires function that pushes task into the array.
when(toDo, "keypress", function(event){
if (event.key == "Enter" || event.keyCode == 13) {
pushArray();
toDo.value = "";
}
});
// "SHOW ARRAY" FUNCTION to verify that the array is being updated (I like this better than using the console);
when(button, "click", function(event){
demolist.innerHTML = "";
for(i=0; i< taskItems.length; i++){
demolist.innerHTML += taskItems[i].Name + " " + taskItems[i].Note + "<br>";
}
});
function showNotes(theNote){
var defaultNote = "No note yet";
if(theNote){
}
}
var taskItems = [];
/*********************************************************/
//create Task object
function Task(name, note){
this.Name = name;
this.Note = note;
this.completed = false;
}
// function that adds new task to the array
function pushArray(){
var newtask = new Task(toDo.value, "No note yet");
taskItems.push(newtask);
displayArray(result, newtask.Name);
appendNote(result, newtask);
}
//function that displays array elements
function displayArray(parent,obj){
var task = make("div","class","taskitem",obj);
parent.appendChild(task);
fadeIn(task);
}
//function that displays notes
function appendNote(theElement,obj){
var newClassItem = make("input","class","tasknote");
theElement.appendChild(newClassItem);
when(newClassItem, "keypress", submitNote.bind(null, obj, newClassItem));
}
//function for submitting notes
function submitNote(task,noteInput){
if (event.key == "Enter" || event.keyCode == 13) {
task.Note = noteInput.value;
var newNote = make("div", "class", "hasNote", task.Note);
noteInput.parentNode.replaceChild(newNote, noteInput);
fadeIn(newNote);
when(newNote,"dblclick", function(){
newNote.parentNode.replaceChild(noteInput, newNote);
});
}
}
Being localStorage a key-value storage, depending on your needs, you are better off serializing (stringifying, whatever) the array and saving in a single index.
var tasks = [
'post the question on SO',
'describe it carefully',
'get a nice reply',
'implement the suggested solution'
];
If you really need to split it for performance reasons, you have to index them by a arbitrary index. If you have reordering it gets tricky and you can either reflush the whole set of tasks every time someone adds/edits/deletes/reorder the tasks (memory-efficient, but very CPU intensive) or save the indexes in a different key so you can reconstruct the order later, like:
var tasks = {
'task1': 'implement the suggested solution',
'task2': 'describe it carefully',
'task4': 'get a nice reply',
'task9': 'post the question on SO'
};
var tasksOrder = [9, 2, 4, 1];
The first idea is very simple to implement, but will give you problems with arbitrarily long lists, the second one is much more easy on the CPU but much harder to implement (and uses more memory). It depends on the specifics of your case.

How to Autorun an onclick event

sorry am still learning JavaScript, read W3Cschools javascript and Jquery but there is a lot they don't teach.
I am studying animation at the moment, how do I auto start this rather then wait for someone to click (event listener), I've attempted turning it into a function but I must be doing it wrong, also 1 more what does (Idx) mean, I understand (id) is Html ID element but not sure Idx, not easy to find on google. to read, event listener starts at 5th line from the bottom, and the shuffle cards is 6th line from top (not sure if that helps), original code is located here http://www.the-art-of-web.com/javascript/css-animation/ thanks for any help.
Regards. William.
var cardClick = function(id)
{
if(started) {
showCard(id);
} else {
// shuffle and deal cards
card_value.sort(function() { return Math.round(Math.random()) - 0.5; });
for(i=0; i < 16; i++) {
(function(idx) {
setTimeout(function() { moveToPlace(idx); }, idx * 100);
})(i);
}
started = true;
}
};
// initialise
var stage = document.getElementById(targetId);
var felt = document.createElement("div");
felt.id = "felt";
stage.appendChild(felt);
// template for card
var card = document.createElement("div");
card.innerHTML = "<img src=\"/images/cards/back.png\">";
for(var i=0; i < 16; i++) {
var newCard = card.cloneNode(true);
newCard.fromtop = 15 + 120 * Math.floor(i/4);
newCard.fromleft = 70 + 100 * (i%4);
(function(idx) {
newCard.addEventListener("click", function() { cardClick(idx); }, false);
})(i);
felt.appendChild(newCard);
cards.push(newCard);
I've gone through your code and added comments to try and help explain what is going on in this file:
//Declare card click function. Takes one parameter (id)
var cardClick = function(id){
if(started) {
showCard(id);
} else {
// shuffle and deal cards
card_value.sort(function() {
return Math.round(Math.random()) - 0.5;
});
for(i=0; i < 16; i++) {
(function(idx) {
setTimeout(function() {
moveToPlace(idx);
}, idx * 100);
})(i);
}
started = true;
}
};
// initialise
//set stage as reference to some element
var stage = document.getElementById(targetId);
//append a div with ID "felt" to the stage element
var felt = document.createElement("div");
felt.id = "felt";
stage.appendChild(felt);
// template for card
//declare card variable as a div with some html content
var card = document.createElement("div");
card.innerHTML = "<img src=\"/images/cards/back.png\">";
//Loop from 0 to 16, where i = current value
for(var i=0; i < 16; i++) {
//create a copy of the card made earlier
var newCard = card.cloneNode(true);
//apply some attributes to the new card
newCard.fromtop = 15 + 120 * Math.floor(i/4);
newCard.fromleft = 70 + 100 * (i%4);
//Create and run an anonymous function.
//The function takes one parameter (idx)
//The function is called using (i) as (idx)
(function(idx) {
//add click handler to the card element that triggers the card click
//function with parameter (idx)
newCard.addEventListener("click", function() { cardClick(idx); }, false);
})(i);
//add new card to the stage
felt.appendChild(newCard);
//add new card to an array of cards
cards.push(newCard);
} //end for loop (I added this. It should be here)
how do I auto start this rather then wait for someone to click
The way I would do it, is add a manual click event after the for loop that targets the first card that has the event handler. Because there is no ID set on the cards, I would try using the array that the cards are added to. Assuming that the cards array was empty when we started:
cards[0].click();
If that doesn't work, I would try targeting the item in the DOM. We know that each card is added to the end of div#felt. So, if we can target the first div inside felt, we should be able to trigger the click event on it.
document.getElementByID("felt").firstChild.click();
what does (Idx) mean
I'm hoping the comments help explain this. It looks like the variable idx is just used as an extended reference of i. Inside a for loop, the writer creates a function that takes one parameter (idx). The for loop has a variable (i) that increases by one for each instance of the loop. Each time the loop happens, i is passed into function as idx.
I hope that helps to get you an understanding of how this code works.

Can we create a dialog box with a checkbox and a combobox using google closure

I am trying to create a dialog box with a checkbox, a user name and a combobox for roles which is enabled only when the checkbox is checked. I have the basic code running on a jsp page but how do i get it to work in a dialog box? All the components are rendered using google closure.
my js file
function combox ()
{
goog.events.listen((goog.dom.getElement('switch')), goog.events.EventType.CLICK,
function(e) {
var request = new goog.net.XhrIo();
var cb = new Array();
goog.events.listen(request, "complete", function(e){
var xhr = /** #type {goog.net.XhrIo} */ (e.target);
res = xhr.getResponseJson();
var mycount = count(res.myrole);
var content = new Array();
var userlist = new Array();
for(var k=0;k<mycount;k++)
{
content[k] = res.myrole[k].role;
}
var mycount1 = count(res.myusers);
for(var l=0;l<mycount;l++)
{
userlist[l] = res.myusers[l].user;
}
var child = new Array();
var container = goog.dom.getElement('c');
for(var m=0;m<userlist.length;m++)
{
child[m] = goog.dom.createDom('div',{'id':'user'+(m+1)},userlist[m]);
cb[m] = new goog.ui.ComboBox();
cb[m].setUseDropdownArrow(true);
for(var n=0;n<content.length;n++)
{
cb[m].addItem(new goog.ui.ComboBoxItem(content[n]));;
}
cb[m].render();
goog.dom.append(container, child[m]);
});
});
}
function count(obj) {
var count=0;
for(var user in obj) {
if (obj.hasOwnProperty(user)) {
++count;
}
}
return count;
}
I'm getting proper response from my servlet, but I want these components in a dialog box(i.e a checkbox,username and a combobox for each user retrieved from my servlet.)
You should look into using a goog.ui.Dialog - http://docs.closure-library.googlecode.com/git/class_goog_ui_Dialog.html
Here is a demo of how to use one : http://closure-library.googlecode.com/git/closure/goog/demos/dialog.html
After instantiating one, you would use the setContent method to place your form as the content html of the dialog.
You can also extend the goog.ui.editor.AbstractDialog class ( http://docs.closure-library.googlecode.com/git/class_goog_ui_editor_AbstractDialog.html ), which helps manage an internal reference to a goog.ui.Dialog rather than directly creating one and gives convenient hide and show methods.
__
As a sidenote, it's typically viewed as an anti-pattern in Javascript to use the "new Array()" syntax rather than var userList = []; for reasons specified here and elsewhere.

JavaScript closures and variable scope

I am having trouble with JS closures:
// arg: an array of strings. each string is a mentioned user.
// fills in the list of mentioned users. Click on a mentioned user's name causes the page to load that user's info.
function fillInMentioned(mentions) {
var mentionList = document.getElementById("mention-list");
mentionList.innerHTML = "";
for (var i = 0; i < mentions.length; i++) {
var newAnchor = document.createElement("a");
// cause the page to load info for this screen name
newAnchor.onclick = function () { loadUsernameInfo(mentions[i]) };
// give this anchor the necessary content
newAnchor.innerHTML = mentions[i];
var newListItem = document.createElement("li");
newListItem.appendChild(newAnchor);
mentionList.appendChild(newListItem);
}
document.getElementById("mentions").setAttribute("class", ""); // unhide. hacky hack hack.
}
Unfortunately, clicking on one of these anchor tags results in a call like this:
loadUserNameInfo(undefined);
Why is this? My goal is an anchor like this:
<a onclick="loadUserNameInfo(someguy)">someguy</a>
How can I produce this?
Update This works:
newAnchor.onclick = function () { loadUsernameInfo(this.innerHTML) };
newAnchor.innerHTML = mentions[i];
The "i" reference inside the closure for the onclick handlers is trapping a live reference to "i". It gets updated for every loop, which affects all the closures created so far as well. When your while loop ends, "i" is just past the end of the mentions array, so mentions[i] == undefined for all of them.
Do this:
newAnchor.onclick = (function(idx) {
return function () { loadUsernameInfo(mentions[idx]) };
})(i);
to force the "i" to lock into a value idx inside the closure.
Your iterator i is stored as a reference, not as a value and so, as it is changed outside the closure, all the references to it are changing.
try this
function fillInMentioned(mentions) {
var mentionList = document.getElementById("mention-list");
mentionList.innerHTML = "";
for (var i = 0; i < mentions.length; i++) {
var newAnchor = document.createElement("a");
// Set the index as a property of the object
newAnchor.idx = i;
newAnchor.onclick = function () {
// Now use the property of the current object
loadUsernameInfo(mentions[this.idx])
};
// give this anchor the necessary content
newAnchor.innerHTML = mentions[i];
var newListItem = document.createElement("li");
newListItem.appendChild(newAnchor);
mentionList.appendChild(newListItem);
}
document.getElementById("mentions").setAttribute("class", "");
}

Categories