I have two elements and will get strings inside. (and i use .each` function)
The problem is that the second array (after got string by .each), is replace the first one.
Sorry, if you don't understand, but try to look below...
$('div').each(function () {
var data = [];
$('li', this).each(function () {
data.push($(this).text());
});
var data_length = data.length;
$(this).children("code").html(data + "");
$("code").click(function () {
data.move(data_length - 1, 0);
$(this).html(data + "");
});
});
Array.prototype.move = function (old_index, new_index) {
if (new_index >= this.length) {
var k = new_index - this.length;
while ((k--) + 1) {
this.push(undefined);
}
}
this.splice(new_index, 0, this.splice(old_index, 1)[0]);
return this; // for testing purposes
};
Demo: http://jsfiddle.net/kdpN7/
What did I do wrong?
For the same reason you do $(this).children('code') you should also bind your click event with a scope.
The problem is, you're iterating over 2 divs (your each) which means you're binding $('code') twice. The first time code is bound to click, it binds with the first data array (the 1's) and then it is bound a second time with (the 2's). So it IS first doing your click code for the 1s and then immediately running it for the 2s, thus overwriting. Change to $(this).find("code") (or children) and it works as expected.
http://jsfiddle.net/kdpN7/1/
On this line:
$("code").click(function () { ...
This is telling to update all code with that information. You need to change it so it's specific to each div:
$(this).find("code").click(function () { ...
Updated fiddle: http://jsfiddle.net/kdpN7/2/
Related
I have checked another questions like this one on stackoverflow, but it doesn't solved my problem.
My problem is that whenever I add events to dynamic added elements. but it doesn't work in case I click on that element. It means that it doesn't work properly.
Here is what I have implemented:
function init() {
let promptMessage = 'Enter the number of rows and columns'
let promptDefault = '1 1';
let prompt = this.prompt(promptMessage, promptDefault);
if (prompt) {
prompt = prompt.split(" ")
console.log(prompt);
prompt[0] = Number(prompt[0]);
prompt[1] = Number(prompt[1]);
return prompt;
}
return init()
}
function selected(i, j) {
return console.log(`You clicked on row ${i} and column ${j}`)
}
var gameSize = init(),
rows = gameSize[0],
cols = gameSize[1],
boardGame = document.querySelector('.board');
for (var i = 0; i < rows; i++) {
for (var j = 0; j < cols; j++) {
let div = document.createElement('div');
div.className = `block cell-${i}-${j}`;
div.addEventListener("click", selected(i, j));
boardGame.appendChild(div);
}
}
Problem: I expect that after entering numbers in prompt whenever I inspect the document see onclick="selected(i, j)" for each of elements. But it doesn't work so. Since the browser render the html file, it console.log all the elements, in case I didn't click on them. Where is the problem?
From what I see, div.addEventListener("click", selected(i, j)); is the problematic line.
You're invoking the function selected here (this is why you see the logging on load) and assigning its value as the handler (which in this case is undefined). You're actually intending to assign the function.
Change
function selected(i, j) {
return console.log(`You clicked on row ${i} and column ${j}`)
}
to
function selected(i, j) {
return function(){
console.log(`You clicked on row ${i} and column ${j}`);
}
}
This use of higher order functions lets you close over i and j to build a function that, once invoked, logs the appropriate values
You did bind the function wrong, it is executed the instantly, and the return value (null) is now your event listener. You need to bind it like a function.
ES5
div.addEventListener("click", function(){ selected(i, j) });
ES6
div.addEventListener("click", _ => selected(i, j));
You have another bug as well if you use the variables in the bind function as I showed, when you use var in your loop, the last value will the executed value when the event listener is executed, that bug can be solved if let is used instead
As sunrize920 and Benjaco wrote, you have you eventListener wrong. What you probably need is a closure, like this:
div.addEventListener("click", function() {
return selected(i, j)
});
I just wrote some code for practicing my jQuery. When I wrote this code, I found out it works fine with only using append() and without removing any original tr elements in the table. How does it work — could someone explain it to me? here is the complete code. Thanks!
Here is my jQuery code:
$(document).ready(function () {
var list = a();
var last = $('#table').find("tr").length;
$('#table').find("tr").each(function (index, element) {
$(this).prepend($("<button/>").text("↑").bind('click', function () {
up($(this).parent(), last);
}));
$(this).prepend($("<button/>").text("↓").bind('click', function () {
down($(this).parent(), last);
}));
});
$('#table').before($('<button />').text("reset").on('click', function () {
reset(list);
}));
});
function up(tr, last) {
if (0 != tr.index()) {
var prevTr = tr.prev();
tr.after(prevTr);
}
}
function down(tr, last) {
if (last - 1 != tr.index()) {
var nextTr = tr.next();
tr.before(nextTr);
}
}
var reset = function (list) {
for (var i = 0; i < list.length; i++) {
$("#table").append(list[i]);
}
};
var a = function () {
var list = [];
$('#table tr').each(function () {
list.push($(this));
});
return list;
};
Be aware, appending already existing element just move it. I guess maybe you want to clone it:
jsFiddle
var reset = function (list) {
for (var i = 0; i < list.length; i++) {
$("#table").append(list[i].clone(true));
}
};
But then, reset function is misnamed...
$(document).ready(function () {
waits for the page and all elements to be loaded
var list = a();
var last = $('#table').find("tr").length;
sets up specific variables, in this case list is the function a() defined later in the page and last gets the length of the last tr in the table.
$('#table').find("tr").each(function (index, element) {
sets up a loop through each tr element on in the table with id #table
$(this).prepend($("<button/>").text("↑").bind('click', function () {
up($(this).parent(), last);
}));
Because you are inside the loop, $(this) represents the tr that the loop is currently on. It then prepends a button and adds a click listener on this button. When the button is pressed, it will call the function up, which is defined later on, with the buttons parent as the first parameter and last (defined earlier) as the second
$(this).prepend($("<button/>").text("↓").bind('click', function () {
down($(this).parent(), last);
}));
This adds another button, but calls down() instead of up()
});
End of the loop.
$('#table').before($('<button />').text("reset").on('click', function () {
reset(list);
}));
This adds a button before the table that when clicked calls the reset function with list as the only parameter, list is set to a().
});
function up(tr, last) {
if (0 != tr.index()) {
var prevTr = tr.prev();
tr.after(prevTr);
}
}
This function is called when moving an item up, it first checks to see if the index is not 0 (so not the first element as this couldn't be moved up) if it is not then it puts the previous tr after the variable tr. Which in this case is the parent to the button (or the current tr)
function down(tr, last) {
if (last - 1 != tr.index()) {
var nextTr = tr.next();
tr.before(nextTr);
}
}
Works exactly the same as the function above, but in the opposite direction.
var reset = function (list) {
for (var i = 0; i < list.length; i++) {
$("#table").append(list[i]);
}
};
This function is saved in the variable reset, it loops through each tr (defined in a()) and appends it to the table,
var a = function () {
var list = [];
$('#table tr').each(function () {
list.push($(this));
});
return list;
};
This function creates and returns an array which loops through each tr and adds to that array. So we know the original state and can return to it.
var myElements = document.getElementsByName('bb1');
for (var i = 0; i < myElements.length; i++) {
var curValue = myElements[i].getAttribute('innerId')
myElements[i].addEventListener('mouseover', function () {
alert('Hello i am : ' + curValue);
}, false);
}
when mouse over, every element, instead of showing a different value for curValue, a constant value (the last iteration value) is displayed.
what am i doing wrong here?
There is no different scope inside blocks like for in JavaScript, so when your mouseover event is triggered, it will alert the current variable value which was set in the last iteration.
You can just use this inside your callback function to get the attribute of the object which the event was triggered.
var myElements = document.getElementsByName('bb1');
for (var i = 0; i < myElements.length; i++) {
myElements[i].addEventListener('mouseover', function () {
alert('Hello i am : ' + this.getAttribute('innerId'));
}, false);
}
The general issue here is the closure in Javascript. This happens when using variable (in this case curValue) not defined within the callback function.
I recommend reading answers about JS closures here.
I have an array called RotatorNames. It contains random things but let's just say that it contains ["rotatorA","rotatorB","rotatorC"].
I want to loop through the array, and for each item i want to trigger a click event. I have got some of this working, except that the everything get's triggered instantly. How can i force the loop to wait a few seconds before it continues looping.
Here's what i have.
function Rotator() {
var RotatorNames = ["rotatorA","rotatorB","rotatorC"];
RotatorNames.forEach(function(entry){
window.setTimeout(function() {
//Trigger that elements button.
var elemntBtn = $('#btn_' + entry);
elemntBtn.trigger('click');
}, 5000);
});
}
You can run this to see what my issue is. http://jsfiddle.net/BxDtp/
Also, sometimes the alerts do A,C,B instead of A,B,C.
While I'm sure the other answers work, I would prefer using this setup:
function rotator(arr) {
var iterator = function (index) {
if (index >= arr.length) {
index = 0;
}
console.log(arr[index]);
setTimeout(function () {
iterator(++index);
}, 1500);
};
iterator(0);
};
rotator(["rotatorA", "rotatorB", "rotatorC"]);
DEMO: http://jsfiddle.net/BxDtp/4/
It just seems more logical to me than trying to get the iterations to line up correctly by passing the "correct" value to setTimeout.
This allows for the array to be continually iterated over, in order. If you want it to stop after going through it once, change index = 0; to return;.
You can increase the timeout based on the current index:
RotatorNames.forEach(function(entry, i) {
window.setTimeout(function() {
//Trigger that elements button.
var elemntBtn = $('#btn_' + entry);
elemntBtn.trigger('click');
}, 5000 + (i * 1000)); // wait for 5 seconds + 1 more per element
});
Try:
var idx = 0;
function Rotator() {
var RotatorNames = ["rotatorA", "rotatorB", "rotatorC"];
setTimeout(function () {
console.log(RotatorNames[idx]);
idx = (idx<RotatorNames.length-1) ? idx+1:idx=0;
Rotator();
}, 5000);
}
Rotator();
jsFiddle example
(note that I used console.log instead of alert)
Something like this should do what you're after:
function Rotator(index){
var RotatorNames = ["rotatorA","rotatorB","rotatorC"];
index = (index === undefined ? 0 : index);
var $btn = $("#btn_"+RotatorNames[index]);
$btn.click();
if(RotatorNames[index+1]!==undefined){
window.setTimeout(function(){
Rotator(index+1);
}, 500);
}
}
Here what I'm trying to do.
I'm having an array like the following
var my_array = ['1', '2', '3' ... ,'1000000000000000'];
What I want to do is create a bunch of HTML elements for every element of that array, and since the array can contain a huge number of elements I attempted to do the following so the browser won't freeze.
for(var i in my_array)
{
if(my_array.hasOwnProperty(i))
{
setTimeout(function(){
do_something_with_data(my_array[i]);
});
}
}
What happens though is that the my_array[i] within the setTimeout doesn't have the value it should.
To be more accurate, when I try to console.log(my_array[i]) what I get is something like this:
"getUnique" function (){
var u = {}, a = [];
for(var i = 0, l = this.length; i < l; ++i){
if(u.hasOwnProperty(this[i])) {
continue;
}
a.push(this[i]);
u[this[i]] = 1;
}
return a;
}
getUnique is a function I've added to the Array prototype just like this:
Array.prototype.getUnique = function(){
var u = {}, a = [];
for(var i = 0, l = this.length; i < l; ++i){
if(u.hasOwnProperty(this[i])) {
continue;
}
a.push(this[i]);
u[this[i]] = 1;
}
return a;
};
Can please somebody help me with this issue?
the setTimeout is executed after the loop is done, and i is the last key or some garbage value at that point. You can capture the i like so:
for (var i in my_array) {
if (my_array.hasOwnProperty(i)) {
(function(capturedI) {
setTimeout(function() {
do_something_with_data(my_array[capturedI]);
});
})(i);
}
}
You should also not use for..in loops for arrays because it's an order of magnitude slower (especially so with the .hasOwnProperty check) than a for loop and the iteration order is not defined
If you have jQuery or willing to add some extra code for older browsers, you can do:
my_array.forEach( function( item ) {
setTimeout( function() {
do_something_with_data( item );
}, 1000);
});
With jQuery:
$.each( my_array, function( index, item ) {
setTimeout( function() {
do_something_with_data( item );
}, 1000);
});
See docs for [].forEach
The problem is that the functions you're creating have a reference to the i variable, not a copy of its value, and so when they run they see i as it is at that point in time (past the end of the array, presumably). (More: Closures are not complicated)
I'd recommend a completely different approach (below), but first, let's look at how to make your existing approach work.
To do what you were trying to do, with the for loop, you have to have the functions close over something that won't change. The usual way to do that is to use a factory function that creates the timeout functions such that they close over the argument to the factory. Or actually, you can pass in the array element's value rather than the index variable.
for(var i in my_array)
{
if(my_array.hasOwnProperty(i))
{
setTimeout(makeFunction(my_array[i]));
}
}
function makeFunction(entry) {
return function(){
do_something_with_data(entry);
};
}
But, I would probably restructure the code so you're not creating masses and masses of function objects unnecessarily. Instead, use one function, and have it close over an index that it increments:
// Assumes `my_array` exists at this point, and that it
// has at least one entry
var i = 0;
setTimeout(tick, 0);
function tick() {
// Process this entry
if (my_array.hasOwnProperty(i)) {
do_something_with_data(my_array[i]);
}
// Move to next
++i;
// If there are any left, schedule the next tick
if (i < my_array.length) {
setTimeout(tick, 0);
}
}
Its just a guess. Try it like:
for(var i in my_array)
{
if(my_array.hasOwnProperty(i))
setTimeout("do_something_with_data('"+my_array[i]+"')", 500);
}