JSHint warning about closures inside loops using outer variables - javascript

My code does work but I don't want the jshint errors anymore:
Functions declared within loop referencing an outer scoped variable may lead to confusing semantics
I've tried using let from ES6 to get around the error because I thought that would solve the problem. I configured my gruntfile to use ES6 as well.
I tried using two loops, the outer loop with variable 'i' and the inner loop with variable 'j'
Neither worked.
Full code provided here: https://jsfiddle.net/rwschmitz/zz7ot3uu/
var hobbies = document.getElementsByClassName("hobbies");
var active = false;
// For mouse input
for (var i = 0; i < 5; i++) {
hobbies[i].onmouseover = function() {
hobbies[0].classList.add('hobbies-slide-left');
hobbies[1].classList.add('hobbies-slide-right');
hobbies[2].classList.add('hobbies-slide-left');
hobbies[3].classList.add('hobbies-slide-right');
hobbies[4].classList.add('hobbies-slide-left');
};
}
// For click input
for (var i = 0; i < 5; i++) {
hobbies[i].onclick = function() {
hobbies[0].classList.add('hobbies-slide-left');
hobbies[1].classList.add('hobbies-slide-right');
hobbies[2].classList.add('hobbies-slide-left');
hobbies[3].classList.add('hobbies-slide-right');
hobbies[4].classList.add('hobbies-slide-left');
};
}

You could change your loops to something like this, using Array#forEach():
var hobbies = Array.from(document.getElementsByClassName('hobbies'));
var classes = ['hobbies-slide-left', 'hobbies-slide-right'];
var events = ['mouseover', 'click'];
function addHobbyClass (hobby, index) {
hobby.classList.add(this[index % this.length]);
}
function hobbyEventListener () {
hobbies.forEach(addHobbyClass, classes);
}
hobbies.forEach(function (hobby) {
this.forEach(function (event) {
this.addEventListener(event, hobbyEventListener);
}, hobby);
}, events);

Two additional examples of how to fix the problem.
var hobbies = document.querySelectorAll('.hobbies');
var eventHooks = ['mouseover', 'click'];
hobbies.forEach(function(hobby) {
eventHooks.forEach(function(hook) {
hobby.addEventListener(hook, function() {
hobbies[0].classList.add('hobbies-slide-left');
hobbies[1].classList.add('hobbies-slide-right');
hobbies[2].classList.add('hobbies-slide-left');
hobbies[3].classList.add('hobbies-slide-right');
hobbies[4].classList.add('hobbies-slide-left');
});
});
});
var hobbies = document.getElementsByClassName('hobbies');
var eventHooks = ['mouseover', 'click'];
// Attach events
var attachEvents = function(key) {
eventHooks.forEach(function(hook) {
hobbies[key].addEventListener(hook, function() {
hobbies[0].classList.add('hobbies-slide-left');
hobbies[1].classList.add('hobbies-slide-right');
hobbies[2].classList.add('hobbies-slide-left');
hobbies[3].classList.add('hobbies-slide-right');
hobbies[4].classList.add('hobbies-slide-left');
});
});
};
// Init
var init = function() {
// Loop through hobbies
for (var i = 0; i < hobbies.length; i++) {
attachEvents(i);
}
}
init();

Related

Value being undefined? [duplicate]

I'm appending onclick events to elements that I'm creating dynamically. I'm using the code below, this is the important part only.
Test.prototype.Show= function (contents) {
for (i = 0; i <= contents.length - 1; i++) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function () { return that.ClickContent.apply(that, [contents[i]]); };
}
}
First it says that it's undefined. Then I changed and added:
var content = content[i];
menulink.onclick = function () { return that.ClickContent.apply(that, [content]); };
What is happening now is that it always append the last element to all onclick events( aka elements). What I'm doing wrong here?
It's a classical problem. When the callback is called, the loop is finished so the value of i is content.length.
Use this for example :
Test.prototype.Show= function (contents) {
for (var i = 0; i < contents.length; i++) { // no need to have <= and -1
(function(i){ // creates a new variable i
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function () { return that.ClickContent.apply(that, [contents[i]]); };
})(i);
}
}
This immediately called function creates a scope for a new variable i, whose value is thus protected.
Better still, separate the code making the handler into a function, both for clarity and to avoid creating and throwing away builder functions unnecessarily:
Test.prototype.Show = function (contents) {
for (var i = 0; i <= contents.length - 1; i++) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = makeHandler(i);
}
function makeHandler(index) {
return function () {
return that.ClickContent.apply(that, [contents[index]]);
};
}
};
A way to avoid this problem altogether, if you don't need compatibility with IE8, is to introduce a scope with forEach, instead of using a for loop:
Test.prototype.Show = function (contents) {
contents.forEach(function(content) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function() {
return that.ClickContent.call(that, content);
};
});
}

How to make a variable which is inside a function global?

Following is my javascript function, I want to use variable selected outside function, but I am getting selected not defined error in console of inspect element. window.yourGlobalVariable is not solving my problem.
function showMe(pause_btn) {
var selected = [];
for (var i = 0; i < chboxs.length; i++) {
if (chboxs[i].checked) {
selected.push(chboxs[i].value);
}
}
}
If you really want it to be global, you have two options:
Declare it globally and then leave the var off in the function:
var selected;
function showMe(pause_btn) {
selected = [];
for (var i = 0; i < chboxs.length; i++) {
if (chboxs[i].checked) {
selected.push(chboxs[i].value);
}
}
}
Assign to a window property
function showMe(pause_btn) {
window.selected = [];
for (var i = 0; i < chboxs.length; i++) {
if (chboxs[i].checked) {
selected.push(chboxs[i].value); // Don't need `window.` here, could use it for clarity though
}
}
}
A properties of window are global variables (you can access them either with or without window. in front of them).
But, I would avoid making it global. Either have showMe return the information:
function showMe(pause_btn) {
var selected = [];
for (var i = 0; i < chboxs.length; i++) {
if (chboxs[i].checked) {
selected.push(chboxs[i].value);
}
}
return selected;
}
...and then where you need it:
var selected = showMe();
...or declare it in the scope containing showMe, but not globally. Without context, that looks exactly like #1 above; here's a bit of context:
(function() {
var selected;
function showMe(pause_btn) {
selected = [];
for (var i = 0; i < chboxs.length; i++) {
if (chboxs[i].checked) {
selected.push(chboxs[i].value);
}
}
return selected;
}
// ...other stuff that needs `selected` goes here...
})();
The outer anonymous function is a "scoping function" which means that selected isn't global, it's just common to anything in that function.
Instead of assigning it to the window object or declaring it outside of the function, I would recommend creating your own object outside of the function, then assigning variables from there. This avoids cluttering the window object and puts all of your global variables in one place, making them easy to keep track of.
For example,
var globalObject {}
function MyFunction {
globalObject.yourVariableName=what your variable is
}
Do this:
var selected;
function showMe(pause_btn) {
selected = [];
for (var i = 0; i < chboxs.length; i++) {
if (chboxs[i].checked) {
selected.push(chboxs[i].value);
}
}
}
You can actually skip the var selected; line but I prefer declaring my variables.
Dont use this;
selected = [];
it is a bug of javascript
window.selected = [];
inside your function.
You could define the array called selected in the scope that the function called showMe is defined.
In terms of code:
var selected = [];
function showMe(pause_btn) {
for (var i = 0; i < chboxs.length; i++) {
if (chboxs[i].checked) {
selected.push(chboxs[i].value);
}
}
}
var selected = [];
function showMe(pause_btn) {
for (var i = 0; i < chboxs.length; i++) {
if (chboxs[i].checked) {
selected.push(chboxs[i].value);
}
}
}
If you declare selected as a property on the window object, you will be able to access it from anywhere else.
function showMe(pause_btn) {
window.selected = [];
for (var i = 0; i < chboxs.length; i++) {
if (chboxs[i].checked) {
selected.push(chboxs[i].value);
}
}
}

FileReader: reading multiple items; how to set a callback upon reading the last one?

I am creating a 'drag and drop' field which can accept files and folders recursively. This is the code:
function traverseFileTree(item) {
if (item.isFile) {
item.file(function(file) {
var reader = new FileReader();
reader.onload = function(evt) {
// do something...
};
reader.readAsText(file);
});
} else if (item.isDirectory) {
var dirReader = item.createReader();
dirReader.readEntries(function(entries) {
for (var i = 0; i < entries.length; i++) {
traverseFileTree(entries[i]);
}
});
}
}
var dropZone = document.getElementById("drop_zone");
dropZone.addEventListener("drop", function(evt) {
var items = event.dataTransfer.items;
for (var i = 0; i < items.length; i++) {
var item = items[i].webkitGetAsEntry();
if (item) {
traverseFileTree(item);
}
}
}, false);
My question: what is the best or perhaps the most elegant way of having a callback upon reading the last file. Since the read is asynchronous, I can not just rely on scope rules. So, is it by use of a counter, or am I missing some cool method here? Thanks!
You could make some sort of counter and check it on the onload functions (you may want to have it in the onerror, too).
function CountDown() {
this.remain = 0;
}
CountDown.prototype.inc = function (x) {
if (undefined === x) x = 1;
this.remain += +x;
return this;
}
CountDown.prototype.done = function () {
if (0 >= --this.remain) {
this.remain = 0;
return true;
}
return false;
}
var count = new CountDown().inc(3); // 3 files
// in the onload for each, test
count.done(); // false
count.done(); // false
count.done(); // true
count.done(); // true
count.done(); // true, etc
Of course, this could also be done in your code without the whole object wrapping if you scope the variable properly.

Javascript dynamic onChange event on select

I have this javascript snippet:
var selectName["id1","id2","id3"];
setOnClickSelect = function (prefix, selectName) {
for(var i=0; i<selectName.length; i++) {
var selId = selectName[i];
alert(selId);
$(selId).onchange = function() {
$(selId).value = $(selId).options[$(selId).selectedIndex].text;
}
}
}
But when I change value to my id1 element, the alert wrote me always "id3".
Can I fix it?
EDIT:
I've changed my snippet with these statements:
setOnChangeSelect = function (prefix, selectName) {
for(var i=0; i<selectName.length; i++) {
var selId = selectName[i];
$(selId).onchange = (function (thisId) {
return function() {
$(selId).value = $(thisId).options[$(thisId).selectedIndex].text;
}
})(selId);
}
}
But selId is always the last element.
This is caused by the behavior of javaScript Closure, selId has been set to the selectName[2] at the end of the loop and that's why you get 'id3' back.
An fix is as following, the key is wrap the callback function inside another function to create another closure.
var selectName = ["id1","id2","id3"];
var setOnClickSelect = function (prefix, selectName) {
for(var i = 0; i < selectName.length; i++) {
var selId = selectName[i];
$(selId).onchange = (function (thisId) {
return function() {
$(thisId).value = $(thisId).options[$(thisId).selectedIndex].text;
}
})(selId);
}
};
Ps: there is synyax error for var selectName["id1","id2","id3"], you should use var selectName = ["id1","id2","id3"];

javascript object, self reference problem

I just started using oop in javascript and I ran across some problems trying to acces a method from inside another method.
here's the code I had:
var Game = {
initialize: function () {
if (canvas.isSupported()) {
sprites[0] = new Player();
this.update();
}
},
update: function() {
for (var i = 0; i < sprites.length; i++) {
sprites[i].update();
}
this.draw();
},
draw: function() {
this.clear();
for (var i = 0; i < sprites.length; i++) {
sprites[i].draw();
}
setTimeout(this.update, 10);
},
clear: function() {
canvas.context.clearRect(0, 0, canvas.element.width, canvas.element.height);
}
}
but calling the Game.update() gives an error that the draw method isn't defined.
I couldn't find a real solution for this.
eventually I found this How to call a method inside a javascript object
where the answer seems to be that I need to safe the this reference like:
var _this = this;
but I couldn't get that to work in literal notation, so I changed the code to object constructor (I guess that's how it's called) and added the variable.
I then changed
this.draw();
to
_this.draw();
and it worked.
though the
this.clear();
and the this.update() are still the same, they never seemed to give errors in the first place.
Can anyone explain why this is? and maybe point me to a better solution?
thanks in advance.
update
Here's what it should be:
var Game = function () {
var _this = this;
this.initialize = function () {
if (canvas.isSupported()) {
sprites[0] = new Player();
this.update();
}
}
this.update = function () {
for (var i = 0; i < sprites.length; i++) {
sprites[i].update();
}
this.draw();
}
this.draw = function () {
this.clear();
for (var i = 0; i < sprites.length; i++) {
sprites[i].draw();
}
setTimeout(function () { _this.update(); }, 10);
}
this.clear = function () {
canvas.context.clearRect(0, 0, canvas.element.width, canvas.element.height);
}
}
When you do this:
setTimeout(this.update, 10);
that does correctly pass the reference to your "update" function to the system, but when the browser actually calls the function later, it will have no idea what this is supposed to be. What you can do instead is the following:
var me = this;
setTimeout(function() { me.update(); }, 10);
That will ensure that when "update" is called, it will be called with this set correctly as a reference to your object.
Unlike some other languages, the fact that a function is defined initially as a property of an object does not intrinsically bind the function to that object. In the same way that if you had an object with a propertly that's a simple number:
maxLength: 25,
well the value "25" won't have anything in particular to do with the object; it's just a value. In JavaScript, functions are just values too. Thus it's incumbent upon the programmer to make sure that this will be set to something appropriate whenever a function is called in some "special" way.
You problem is that you use an object literal instead of an instantiated object
Try to do it this way instead:
var Game = function() {
this.initialize = function () {
if (canvas.isSupported()) {
sprites[0] = new Player();
this.update();
}
};
this.update = function() {
for (var i = 0; i < sprites.length; i++) {
sprites[i].update();
}
this.draw();
};
this.draw = function() {
this.clear();
for (var i = 0; i < sprites.length; i++) {
sprites[i].draw();
}
setTimeout(this.update, 10);
};
this.clear = function() {
canvas.context.clearRect(0, 0, canvas.element.width, canvas.element.height);
};
}
now use:
var myGame = new Game();

Categories