I have the following code:
'use strict';
// Data
class cat {
constructor(name, picture, clicks) {
this.name = name;
this.picture = picture;
this.clicks = clicks;
}
};
var cat1 = new cat('Mercury', 'cat1.jpg', 0);
var cat2 = new cat('Venus', 'cat2.jpg', 0);
var cat3 = new cat('Mars', 'cat3.jpg', 0);
var cat4 = new cat('Jupiter', 'cat4.jpg', 0);
var cat5 = new cat('Saturn', 'cat5.jpg', 0);
var cat6 = new cat('Neptune', 'cat6.jpg', 0);
var cats = [cat1, cat2, cat3, cat4, cat5, cat6];
// Program
for (var i = 0; i < cats.length; i++) {
// Current cat
var icat = cats[i];
$('#cat_list').append('<li id="cat' + (i + 1) + '">' + icat.name + '</li>');
$('#cat' + (i + 1)).on('click', (function(iSaved, icatSaved) {
return function() {
console.log('You clicked on cat' + (iSaved + 1) + ' from the list!');
$('#cat_title').text(icatSaved.name);
$('#cat_image').attr('src', 'img/' + icatSaved.picture);
$('#catClicks').text(icatSaved.clicks);
$('#cat_image').on('click', function() {
icatSaved.clicks++;
$('#catClicks').text(icatSaved.clicks);
});
};
})(i, icat));
};
It works great except for the embedded click handler that increments the click count for the cat object in question. It seems that the click incrementing ends up impacting more than one of the cat objects and also causes some of them to increment by 2, 3, or 4 when clicking on the picture. I must be doing something dumb to get this behavior but I'm not sure what. Is this because I’ve embedded a click handler within the list click handler?
–Jim
You are setting an event listener inside another event listener. Each time #cat{N} get clicked, you add another event listener for #cat_image. Thus clicking it over and over again setup more and more event listeners for the same element. When you click #cat_image, all those event listeners get invoked, and since they are all incrementing the clicks property, the latter get incremented a lot of times by just one click.
The best way at approaching this is to use event delegation:
for (var i = 0; i < cats.length; i++) {
var icat = cats[i];
// add a class of .cat and a data-index attribute to all cats
$('#cat_list').append('<li class="cat" data-index="' + i + '">' + icat.name + '</li>');
// ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
};
var catSaved = null; // move this outside the event listener to be visible for both event listeners
$('#cat_list').on('click', '.cat', function(e) { // whenever a .cat element get clicked from within #cat_list element
var index = $(this).data('index'); // get the index of that cat from cats array (previously stored in data-index)
catSaved = cats[index]; // get the cat itself
$('#cat_title').text(catSaved.name);
$('#cat_image').attr('src', 'img/' + catSaved.picture);
$('#catClicks').text(catSaved.clicks);
});
$('#cat_image').on('click', function() { // move this event listener outside, to set it only once
if(catSaved) { // if we have a catSaved element
catSaved.clicks++; // ...
$('#catClicks').text(catSaved.clicks);
}
});
Related
So I was in the presumption that this function
button.onclick = exampleFunk;
would give me a handler on each button when I click them, but it doesn't. When replacing it with:
button.onclick = alert("bananas");
I'm getting alerts at page onload. The problem is already solved with this:
button.setAttribute("onclick", "removeIssue(this)");
Out of curiousity... What's going on?
edited layout of post
EDIT
var issues = [];
window.onload = function () {
//alert("venster geladen");
issuesToList()
}
function issuesToList(data) {
/*alert(
"array length is " + data.issues.length + "\n" +
"total_count is " + data.total_count + "\n" +
"limit is " + data.limit + "\n" +
"offset is " + data.offset + "\n" + ""
);*/
for (i = 0; i < data.issues.length; i++) {
issue = data.issues[i];
createIssue(issue);
}
}
function createIssue(issue){
var id = issue.id;
var tracker = issue.tracker;
var status = issue.status;
var priority = issue.priority;
var subject = issue.subject;
var description = issue.description;
var assignee = issue.assignee;
var watchers = issue.watchers;
var ticket = new Issue(id, tracker, status, priority, subject, description, assignee, watchers);
issues.push(ticket);
var button = document.createElement("button");
button.innerHTML = "-";
button.onclick = function (){ alert("bananas")};
//button.setAttribute("onclick", "removeIssue(this)");
var item = document.createElement("div");
item.setAttribute("id", id);
item.appendChild(button);
item.innerHTML += " " + subject;
var container = document.getElementById("container");
container.appendChild(item);
}
function removeIssue(e){
var key = e.parentNode.getAttribute("id");
var count = issues.length;
if(confirm("Confirm to delete")){
for(i=0; i<count; i++){
if (issues[i].id == key ){
issues.splice(i,1);
var element = document.getElementById(key);
element.parentNode.removeChild(element);
}
}
}
}
function Issue(id, tracker, status, priority, subject, description, assignee, watchers){
this.id = id;
this.tracker = tracker;
this.status = status;
this.priority = priority;
this.subject = subject;
this.description = description;
this.assignee = assignee;
this.watchers = watchers;
}
EDIT
<body>
<h1>List of Issues</h1>
<div id="container"></div>
<script src="http://www.redmine.org/issues.json?limit=10&callback=issuesToList"></script>
</body>
You need to mask the alert in a function:
button.onclick = function (){ alert("bananas")};
As such:
var btn = document.createElement("BUTTON");
var t = document.createTextNode("CLICK ME");
btn.appendChild(t);
btn.onclick = function() {alert("bananas")};
document.body.appendChild(btn);
Whats going on?
You alert() is executed on page load because its a function call. When the execution of your script reaches that line your assignment
button.onclick = alert("bananas");
is actually executing the alert statement and not assigning it to button.onclick
You can bind arguments to the function so that it returns with the function you want it to call using your arguments (with additional arguments passed later added on to the end). This way doesn't require writing extraneous code (when all you want to do is call a single function) and looks a lot sleeker. See the following example:
button.onclick = alert.bind(window, "bananas");
An unrelated example of how it works in your own code is like this:
var alert2 = alert.bind(window, 'Predefined arg');
alert2(); // 'Predefined arg'
alert2('Unused'); // 'Predefined arg'
For IE, this requires IE9 as a minimum. See MDN for more information.
EDIT: I've looked closer at your code and there was one significant change that was needed for it to work... You cannot add onto the innerHTML when you've added JavaScript properties to a child element. Changing the innerHTML of the parent element will convert your element into HTML, which won't have the onclick property you made before. Use element.appendChild(document.createTextNode('My text')) to add text dynamically.
See a functioning example here: http://jsfiddle.net/2ftmh0gh/2/
I have a huge array called newCombs when I call store.put(game) the tab turns white and is gone in chrome task manager. Basically the process just stops and it fails to store the array
var trans = this.db.transaction('Games', 'readwrite');
var store = trans.objectStore('Games');
store.get(new Date().toLocaleDateString() + ' ' + prize.value).onsuccess = function() {
var game = this.result;
game.combs = game.combs.concat(newCombs); //I'm appending to the game.combs array which is empty when I first run it (when it also crashes)
store.put(game);
}
trans.oncomplete = function(evt) {
arrLength = 0;
newCombs = [];
}
This is what game is equal to:
game = {
name: new Date().toLocaleDateString() + ' ' + prize.value,
date: new Date().toLocaleDateString(),
prize: prize.value,
playerData: [...],
points: {...}
}
The above part is a method of an object so this is not the window object everything works fine until the code hits the line with: store.put(game); the page just crashes.
As #paldepind suggests you are changing scope in the onsuccess event handler and losing sight of your custom object.
One way around this would be to take advantage of a closure and assign this to a local variable, before you define the anonymous callback function:
var trans = this.db.transaction('Games', 'readwrite');
var store = trans.objectStore('Games');
var that = this;
store.get(new Date().toLocaleDateString() + ' ' + prize.value).onsuccess = function() {
var game = that.result;
game.combs = game.combs.concat(newCombs);
store.put(game);
}
trans.oncomplete = function(evt) {
arrLength = 0;
newCombs = [];
}
I am trying to dynamically make divs that are clickable. I have inserted a test function. The test function runs even though the div has not been clicked.
function displayResults(responseTxt)
{
var results = document.getElementById("results");
jsonObj = eval ("(" + responseTxt + ")");
var length = jsonObj.response.artists.length;
results.innerHTML = "Please click on an artist for more details: "
for ( var i = 0; i < length; i++)
{
var entry = document.createElement("div");
var field = document.createElement("fieldset");
entry.id = i;
entry.innerHTML = i + 1 + ". " + jsonObj.response.artists[i].name;
field.appendChild(entry);
results.appendChild(field);
//entry.addEventListener("click", idSearch(jsonObj.response.artists[i].id), false);
entry.addEventListener("click", test(), false);
}
} // end function displayResults
function test()
{
document.getElementById("results").innerHTML = "tested";
}
You are calling the test() function and passing its return value to .addEventListener(). Remove the parentheses:
entry.addEventListener("click", test, false);
So that you pass the function itself to .addEventListener().
That answers the question as asked, but to anticipate your next problem, for the line you've got commented out you'd do this:
entry.addEventListener("click",
function() {
idSearch(jsonObj.response.artists[i].id);
}, false);
That is, create an anonymous function to pass to .addEventListener() where the anonymous function knows how to call your idSearch() function with parameters. Except that won't work because when the event is actually triggered i will have the value from the end of the loop. You need to add an extra function/closure so that the individual values of i are accessible:
for ( var i = 0; i < length; i++)
{
var entry = document.createElement("div");
var field = document.createElement("fieldset");
entry.id = i;
entry.innerHTML = i + 1 + ". " + jsonObj.response.artists[i].name;
field.appendChild(entry);
results.appendChild(field);
// add immediately-invoked anonymous function here:
(function(i) {
entry.addEventListener("click",
function() {
idSearch(jsonObj.response.artists[i].id);
}, false);
})(i);
}
That way the i in jsonObj.response.artists[i].id is actually going to be the parameter i from the anonymous function which is the individual value of i from the loop at the time each iteration ran.
I have an array of jQuery objects that I built and which have events attached to them.
var obs = [];
for(var i=0;i<100;i++) {
var j = jQuery('<div></div>');
j.click(function() { console.log('Clicked ' + i); });
obs.push(j);
}
My HTML is:
<div id="replaceme"></div>
How do I replace the div on the page with all the jQuery objects in my array? I don't want to concatenate their HTML because I want to preserve the event handlers.
You can replace an element with an array of jQuery elements by doing this:
$(theElement).replaceWith(theArryaOfjQueryElements);
In your case, this is:
$('#replaceme').replaceWith(objs);
See the jQuery.replaceWith() method.
BTW you have an error here:
j.click(function() { console.log('Clicked ' + i); });
This will always print Clicked 100. The reason is that your function closes a reference to i, not to it's value. So if i is modified before this function is called, the function will see the new value of i.
You should do this instead:
(function(i) {
j.click(function() { console.log('Clicked ' + i); });
}(i));
Use replaceWith jQuery method which replaces each element in the set of matched elements with the provided new content.
var $obj = jQuery();//This will create an empty jQuery object
for(var i=0;i<100;i++) {
var j = jQuery('<div></div>');
j.click(function() { console.log('Clicked ' + i); });
$obj.add(j);//Using add method to add the jQuery object into $obj
}
$("#replaceme").replaceWith($obj);
var obs = [];
for(var i=0;i<100;i++) {
var j = jQuery('<div></div>');
j.click(function() { console.log('Clicked ' + i); });
obs.push(j);
}
$('#replaceme').html(objs);
/* or */
$('#replaceme').replaceWith(objs);
I read, that creating all elements at once is much faster then creating one by one. With the click function, you could count all previous siblings to get the index:
var str = ''
for (var i = 0; i < 100; i++) {
str += '<div></div>';
}
var elem = jQuery(str);
elem.click(function() { console.log('clicked '+jQuery(this).prevAll().length); });
jQuery('#replaceme').replaceWith(elem);
var ButtonFarmAtivada = new Array();
function X() {
var tableCol = dom.cn("td"); //cell 0
//create start checkbox button
ButtonFarmAtivada[index] = createInputButton("checkbox", index);
ButtonFarmAtivada[index].name = "buttonFarmAtivada_"+index;
ButtonFarmAtivada[index].checked = GM_getValue("farmAtivada_"+index, true);
FM_log(3,"checkboxFarm "+(index)+" = "+GM_getValue("farmAtivada_"+index));
ButtonFarmAtivada[index].addEventListener("click", function() {
rp_farmAtivada(index);
}, false);
tableCol.appendChild(ButtonFarmAtivada[i]);
tableRow.appendChild(tableCol); // add the cell
}
1) is it possible to create the button inside an array as I'm trying to do in that example? like an array of buttons?
2) I ask that because I will have to change this button later from another function, and I'm trying to do that like this (not working):
function rp_marcadesmarcaFarm(valor) {
var vListID = getAllVillageId().toString();
FM_log(4,"MarcaDesmarcaFarm + vListID="+vListID);
var attackList = vListID.split(",");
for (i = 0; i <= attackList.length; i++) {
FM_log(3, "Marca/desmarca = "+i+" "+buttonFarmAtivada[i].Checked);
ButtonFarmAtivada[i].Checked = valor;
};
};
For number 1) yes, you can.
function createInputButton(type, index) { // um, why the 'index' param?
// also, why is this function called 'createInputButton'
// if sometimes it returns a checkbox as opposed to a button?
var inputButton = document.createElement("input");
inputButton.type = type; // alternately you could use setAttribute like so:
// inputButton.setAttribute("type", type);
// it would be more XHTML-ish, ♪ if that's what you're into ♫
return inputButton;
}
I don't really understand part 2, sorry.