I am generating an HTML table dynamically using JavaScript. One of the table's columns contains an onmouseover event, and when the mouse cursor is over a particular cell, I wish to display cell specific information.
In my case, I wish to display the value of nPos via an alert() call as it was during the table construction.
Simplified example of my table creation:
for (var iLoop = 0 ; iLoop < nHitsPerPage ; iLoop++) {
var nPos = (nCurrentPageParam * SearchResults.nMaximumHitsPage) + iLoop;
//...
var cellInfo = tableResultsRow.insertCell(6);
//...
cellInfo.className = "noWrapCell";
cellInfo.innerHTML = "?";
cellInfo.style.cursor = "pointer";
// The line below is important - I need to store nPos value during the table consturction
cellInfo.onmouseover = function(){alert("nPos = " + nPos)};
cellInfo.onmousemove = function(){FileResort.LightboxShortInfo.update(event)};
cellInfo.onmouseout = function(){FileResort.LightboxShortInfo.hide(event)};
}
As you can see from the example above, I iteratively created a table within my for() loop. I want to store the value of the nPos within every row (record) which is different for each row (record).
My problem is that once I mouseover that particular cell, I get the same nPos value for every row (record), and that nPos is the current value of nPos at the particular application state.
I cannot find a way to record the nPos value as it was during the for() execution, which is important for me in identifying what record is stored in the particular row.
Is there a way to capture, or store the value of nPos for every row (record) during the table's initial construction?
You are yet another victim of the closure monster :)
Look at this:
cellInfo.onmouseover = function(){alert("nPos = " + nPos)};
the function here - let's call it lambda - will have to find the value of the variable nPos. Since nPos is not declared inside lambda, it will look for it in the upper level function, that is the function where nPos is created.
When the mouseover event will fire, the code that declares and sets nPos will already have run to completion, so nPos will have the value nHitsPerPage.
That is exactly what lambda will display. Unfortunately, that's not what you want :).
To get over that, you need to create a (lexical) closure, i.e. provide lambda with a value of nPos that suits your needs.
The way of doing it in Javascript is as follows:
cellInfo.onmouseover = function(p){
return function () { alert("nPos = " + p)};
} (nPos);
let's call nu the new function (the one that takes p as a parameter).
We changed lambda so that it now refers to p instead of nPos.
It means that when the mouseover event will fire, lambda will look for p in its upper function, which is now nu. And it will indeed find p there, as the parameter of nu.
p is a function parameter, so it recieves a copy of nPos at the time it is called.
It means lambda will refer to a different instance of nu's calling context for each instance of cellInfo.
Each instance of the mouseover event handler will now hold a copy of p that is set to the desired value of nPos.
Quick Psuedo Code:
var nPosArray = [];
var itemsArray = [];
for (var iLoop = 0 ; iLoop < nHitsPerPage ; iLoop++)
{
var nPos = (nCurrentPageParam * SearchResults.nMaximumHitsPage) + iLoop;
//...
var cellInfo = tableResultsRow.insertCell(6);
//...
cellInfo.className = "noWrapCell";
cellInfo.innerHTML = "?";
cellInfo.style.cursor = "pointer";
// The line below is important - I need to store nPos value during the table consturction
nPosArray.push(nPos);
cellInfo.addEventListener('mouseout', cellMouseOut(event));
cellInfo.onmousemove = function(){FileResort.LightboxShortInfo.update(event)};
cellInfo.onmouseout = function(){FileResort.LightboxShortInfo.hide(event)};
itemsArray.push(cellInfo);
}
function cellMouseOut(e)
{
for(iLoop = 0 ; iLoop < cellInfo.length; iLoop++)
{
if(e.target.id == cellInfo[iLoop].id)
{
alert('NPos: ' + nPosArray[iLoop]);
}
}
}
Related
The problem is I don't understand why this code works. It works, but I just can't wrap my mind around it. Here's a function that deletes a node from a singly linked list. I feel like it shouldn't work because it's not actually changing any of the elements in the list, I'm just changing the value of a variable I have set equal to something in the list. In other words, when I create a "runner" variable to iterate through the list, why are the changes I make to "runner" actually changing the list itself. Analogously, if I do
var x = 1
var y = x
y = 2
Obviously, x is still going to equal 1. Why isn't the same true for my Linked List "runner". In the deleteNode function below, why does changing the runner.next value actually change anything in the node or list that exists outside of the function?
function deleteNode(head, position) {
var runner = head
var counter = 0
while (runner) {
if (counter == position - 1) {
runner.next = runner.next.next
return head;
}
runner = runner.next
counter++
}
}
Its because runner is an object, and so the runner variable is a reference to that object.
for example
const x = {a:1}
const y = x;
x.a = 3
console.log(y.a) // this will print 3 also
I have a problem - I want to add an .onClick event to each cell of a dynamically generated table while I am creating it.
cell.onclick = function(){
mouseClick(row_number,i, cell);
};
However, it appears that the event is just added to the last cell in each row. How is it possible? Is it somehow overriding the previous ones? And how to make it work?
function drawRow(rowData, length, keys, row_number){
//rowData - object where the row data is stored, length-row length, keys - object with table keys stored, row_number - number of the row
var rowData=rowData;
var row = document.createElement("tr");
for(i=0; i<length; i++){
var cell = document.createElement("td");
console.log("Creating event for "+i+" .");
cell.onclick = function(){
mouseClick(row_number,i, cell);
};
var key = keys[i];
var cell_text = document.createTextNode(rowData[key]);
cell.appendChild(cell_text);
row.appendChild(cell);
}
return row;
}
You are seeing one of the most common problems in JavaScript-land.
Your onclick functions are not called until after that for-loop is done. When the for-loop is done, the value of i is that of length. Only then do you try to read it. In other words, every single one of your callbacks say
function () {mouseClick(row_number, i, cell}
By the time you finally execute the callback, the value of i is its last value.
If you are using ES6, instead write
for (let i=0; i<length; i++)
The let gives each invocation of the block its own variable i. In your code you only have one i shared among all the cells. That is why all cells are appearing to do the same thing!
If you are still in the ES5 game, replace the inner portion of your code with
cell.onclick = (function (j) {
return function () {
mouseClick(row_number, j, cell);
}
})(i);
This gives to each of your callback functions a copy of i at each point of the loop. The first time through the loop, i has a value of 0, and the callback you get is
function () {mouseClick(row_number, 0, cell}
The second iteration of the loop produces
function () {mouseClick(row_number, 1, cell}
and so on!
Pretty cool...but the ES6 way with let does the same thing, a bit more nicely.
(Alternatively, there are ways to do this thing with the forEach method on arrays. These also ensure the loop iterations do not share the index variable.)
Bear with me on this one cause this one is a bit tricky for me to explain.
So I have multiple observables assigned, say:-
var self = this;
self.amount = ko.observableArray();
self.data0 = ko.observable([10,11,12]);
self.data1 = ko.observable([1,2,3]);
self.data2 = ko.observable([3,4,5]);
self.data3 = ko.observable([6,7,8]);
self.data4 = ko.observable([9,10,11]);
And there is some button that changes the value on each of them with the following function (what this functions does isn't really important, it's merely to show that there is some change going on in the observables)
self.bindOneByOne = function(){
var self = this;
var i = 0;
while(self['data' + i]){
for(var j = 0, len = self['data'+i]().length; j < len; j++){
self['data'+i]()[j] *= 2;
}
self.amount.push(i);
i++;
}
};
Now what I'm wanting to do is to display the changes as it happens in the UI side, one at a time (first self.data0 and then data1 and so on..) when I call a function (click on a button in this case)
My attempt for that behavior so far:-
self.changeValues = function(){
var i = 0;
while(self['data' + i]){
setTimeout(self['data' +i].valueHasMutated,1000);
i++;
}
}
Shouldn't my code first bind self.data0 first and shouldn't it immediately reflect on my UI? Currently, I'm only seeing changes all at once which is not the behavior I wanted.
Here's the fiddle for what I'm trying to do. (Click on Populate/Change to populate the data and change it after it's been populated...and then Mutate to see the changes on the UI side. You can also see that the data is indeed changing when you press Populate/Change button if you check your console prior to clicking on Mutate button)
The key with the timeout is to capture the loop values (i and J) in a closure with the use of an IEFE(immediately invoked function execution)
for(var j = 0, len = self['data'+i]().length; j < len; j++){
(function(){
//this captures the item to set using the current value of i and J
var itemToSet = self['data'+i]()[j];
setTimeout(function(){
console.log('itemToSet',itemToSet());
itemToSet(itemToSet() * 2);
},1000*(i+1));
})() //the () brackets immediately invoke this function that is also in brackets
}
self.amount.push(i);
I've created a fiddle to show it working, you only need the 1 button to show it really, I have just made the second button update each item individually, rather than all values in the array that the first button does.
Fiddle here
Hope it helps.
I'm using shortcut.js to handle keyboard input and I'm wondering if there is a more efficient way to achieve my goal (currently most of the same code is copied and pasted).
For example, i have:
shortcut.add("0",function() {
points = -1;
sec = 0;
});
shortcut.add("1",function() {
points = 1;
sec = 0;
});
shortcut.add("2",function() {
points = 2;
sec = 0;
});
shortcut.add("3",function() {
points = 3;
sec = 0;
});
Ideally, I can generalize the function so that whatever key is entered is actually assigned to the points variable, except in the case where the user enters 0. In that case, the points variable is set to -1.
Any ideas on how to make this happen? Thank you!
A loop with a closure should do the trick:
for (var i = 0; i <= 9; ++i) {
(function(i) { // Capture current value of 'i' in this scope.
shortcut.add(i.toString(), function() {
points = i || -1; // 'i' if 'i' is not 0, else -1.
sec = 0;
});
})(i);
}
Update following comment: So why do we need a closure here? And what does the final (i); mean?
Basically, we need a closure because the anonymous functions passed to shortcut.add() will not be called right away, but some time in the future, after the loop has terminated. The functions capture i by reference, not by value, which means they will see the value of i that is current at the time they run, not at the time they're defined.
So, if we call shortcut.add() directly from the loop body, all the anonymous functions we pass will end up seeing the value of i that is current after the loop has terminated, which will always be the same (10).
Creating a new variable in each iteration looks like it could work, but doesn't:
for (var i = 0; i <= 9; ++i) {
var _i = i; // Create new variable containing current value of 'i'.
shortcut.add(i.toString(), function() {
points = _i || -1; // Won't work, '_i' is always 9.
sec = 0;
});
}
Since for loop bodies do not have their own scope in Javascript, _i ends up in function scope, the same as i, and will be captured the same way (its final value will be 9 instead of 10 because ++i does not apply to it).
So, what we really need here is a new scope in each iteration. To achieve this, we can define a function inside the loop, and call it immediately, passing it the current value of i:
var newScope = function(i) {
// Here, the value of 'i' will be the one current when 'newScope' is called
// and will not change, even if 'i' is captured by other functions.
};
newScope(i); // Call function with current value of 'i'.
Finally, we can do that without introducing the newScope name, by directly applying the call operator () to the function definition:
(function(i) {
// Here, the value of 'i' will be the one current when this function is
// called and will not change, even if 'i' is captured by other functions.
})(i); // Call function with current value of 'i'.
I hope this appropriately answers your questions, feel free to leave further comments if it does not. For more information about closures, see Closures on MDN.
I'm creating a jQuery plugin to do paging and encountered the following problem.
When I click on a page link created by the plugin below, it will always give we the value of the last index passed into the value i at the last iterator of the code below. If there are 4 pages, I will always get 4 if I press link 1, 2, 3 or 4. It seems that the reference to the delegate onclick also keeps a reference to the value of i instead of just the value.
Any Ideas? It's the options.onclick(i) that's acting strange.
$.fn.pager = function(options) {
var defaults = {
resultSet: undefined,
onclick: function(page) { alert(page); return false; },
};
return this.each(function () {
var rnd = Math.floor(Math.random()*9999)
var result = '';
for(var i = 1; i <= options.resultSet.PageCount; i++)
{
if(i == options.resultSet.PageCount)
result += '' + i + '';
else
result += '' + i + '' + options.separator;
}
$(this).html(result);
for(var i = 1; i <= options.resultSet.PageCount; i++)
{
$('#' + rnd + '_pagerPage_' + i).click(function() { options.onclick(i) });
}
});
}
I reduced the above code to just the problem case. So some checks re missing ;)
It seems that the reference to the delegate onclick also keeps a reference to the value of i instead of just the value.
What you are experiencing is your first (unexpected) encounter with closures. It's not even a reference that is being passed, it's weirder than that. To understand what's going on (and it's critical that you do if you program in javascript, this is considered basic stuff these days) read my answers to the following related questions:
Please explain the use of JavaScript closures in loops
Hidden Features of JavaScript?
This is a classic problem: the value of i that gets used inside the option click event handler function is whatever value i has at the point at which the event fires (which will be 4, the final value it has in the loop), not the value it had at the point at which you assigned the event handler. The way round it is to create an extra function each time through the loop that has its own variable containing a copy of the value of i at the point it was called:
var createClickHandler = function(n) {
return function() { options.onclick(n); };
};
$('#' + rnd + '_pagerPage_' + i).click( createClickHandler(i) );