ReSharper warning about ClosureOnModifiedVariable - why? - javascript

So I came upon this code during a review:
var permissions = $("#" + me.map.permissionsGridHtmlId).data("kendoGrid").dataSource.data();
var data = form.serializeArray();
for (var i = 0; i < permissions.length; i++) {
var record = permissions[i].toJSON();
$.each(record, function (key, value) {
data.push({
// ReSharper disable once ClosureOnModifiedVariable
name: "Permissions[" + i + "]." + key,
value: value
});
});
}
and that '// ReSharper disable' comment gave me pause.
I tried to look into it, and found this - https://www.jetbrains.com/help/resharper/AccessToForEachVariableInClosure.html
However, I tried to remove the comment and then do as that link said, create a variable inside of the scope to store the value, but the warning didn't go away.
Furthermore, despite the warning, it seems to behave as designed - the value of 'i' changes properly, and at the end the 'data' variable stores the proper/expected values.
So, my question is... why is ReSharper warning about this? Is there an actual problem in the code, or a bug in ReSharper? If the former, how should I fix the code? If the latter, is this warning ever right (and so we should leave the disable comment) or should I change the inspection severity to never show this warning?
Update
The following change to the code made the warning go away:
$.each(permissions, function (i, permission) {
$.each(permission.toJSON(), function (key, value) {
data.push({
name: "Permissions[" + i + "]." + key,
value: value
});
});
});
I'd still like to know why the warning exists, though, when the two code snippets appear to act identically.

Access to modified closure is only a problem if a lambda (function parameter in your case) would be executed after variable modification. In your case, $.each should execute lambda instantly, so it shouldn't be a problem. But ReSharper doesn't know if a called function would execute a passed lambda instantly or would store it for later execution, especially in JavaScript with its dynamic typing. So it just gives the warning always.
Please note that the article you found refers to C#, and the fix it suggests is only valid for C#. You cannot fix a problem in JS by using var i1 = i, because variable i1 declared by var would have a function scope, and you need it to have a block scope. So, if you can use ECMAScript 2015, then you can use let or const to declare a variable with a block scope, for example:
var permissions = $("#" + me.map.permissionsGridHtmlId).data("kendoGrid").dataSource.data();
var data = form.serializeArray();
for (var i = 0; i < permissions.length; i++) {
var record = permissions[i].toJSON();
let i1 = i;
$.each(record, function (key, value) {
data.push({
name: "Permissions[" + i1 + "]." + key,
value: value
});
});
}

Related

Storing a cumulative value to a variable from within a function that's in a for loop, then recalling that variable elsewhere

I'm having trouble understanding a particular concept.
The general problem is storing a cumulative value to a variable from within a function that's in a for loop, then recalling the variable elsewhere.
I'll explain all of the parts first:
I call to an API to get the value of a key. The call looks something like this.
keyName = API.key('keyName');
keyName.get(function(err, value) {
console.log(value);
});
However, I need to get a whole bunch of these keys, so I put the call into a for loop. I also want to store the cumulative value in the variable fullString
var fullString = [];
for (var i = 0; i < numberOfKeys; i++) {
keyName = API.key('keyName' + i);
keyName.get(function(err, value) {
fullString += value;
console.log(fullString);
});
}
Let's say:
keyName0 : 'a'
keyName1 : 'b'
keyName2 : 'c'
When the loop runs, I'll get this (which I understand):
a
ab
abc
The thing that I don't understand is, if I reference fullString outside of the function it returns null i.e.
var fullString = [];
for (var i = 0; i < numberOfKeys; i++) {
keyName = API.key('keyName' + i);
keyName.get(function(err, value) {
fullString += value;
});
console.log(fullString);
}
console.log(fullString);
Both of the above console.log's will return null, shouldn't they log the full value i.e. abc
The problem is not with scoping but with asynchronicity. You are logging the value of fullString before this is resolved.
If you put the log call just after the fullString += value statement you will see that the variable is in fact equal to abc (on the last iteration).
However because get() is async fullString will be update after you called console.log.
update: to get the final value i suggest you read on javascript promises.
AngularJS has a very nice implementation of it derived by Kris Kowal's Q, read here.
in your case it would work something like (just an idea):
var fullString = keyname.getAll().done(function (data) { return data; });

Using eval to assign a variable to a method. How else should I do it?

I am using a method to accept a portion of an element's ID and use that to determine which url path I need to pass to a method. In my case this is used by a click event on a button that could be attached to any one of a number of grids so I need to know which grid I am on in order to know what data to reload:
jQuery('.reset').click(function(e) {
e.preventDefault();
jQuery('#' + grid_id + '_mnf').toggle();
jQuery('#' + grid_id + '_mnfall > .instance').each(function () {
jQuery(this).removeAttr("checked");
});
var url = eval(grid_id + '_url');
jQuery('#foo').setGridParam({url:url}).trigger('reloadGrid');
});
The possible urls are defined as variables so I have something like:
var foo_url = url_path + '?input=moduleone&param=1';
var bar_url = url_path + '?input=moduleone&param=2';
This means that in the setGridParam method above the url value will be one of these vars. If I simply do:
var url = grid_id + '_url'; //note I'm not using eval here
The value passed to setGridParam will not work.
If I don't use eval like I do above the url will not work. Why? What is going on here? What else should I do since "eval is evil"?
In my mind, without using eval, I am passing the url like:
jQuery('#foo').setGridParam({url:foo_url}).trigger('reloadGrid');
but this fails so apparently eval is required here?
Because I am sure this is too rigid and clunky of a way to do this and in case anyone wants to see it (and perhaps suggest how I am doing this wrong?), here is how I am getting that grid_id:
// ex. id='foo_filter'
var source = $(this).parent().parent().attr('id').split('_');
var parsed_source = source.splice(1, source.length + 1);
grid_id_portion = '';
for (var i = 0; i < parsed_source.length; i++) {
grid_id += parsed_source[i];
if(i < parsed_source.length - 1){
grid_id += '_';
}
}
Some issues I see... I have to define the urls as variables for each grid. This is quite inflexible, yes? Those urls are pretty similar as well so defining a bunch of them seems inefficient. Also, what if someone crafts a special chunk of code to insert as the ID that I am breaking apart to pass to eval. That could be bad news, yes? Any suggestions on how to improve on this would be greatly appreciated.
Use a map to store URLs, not local variables.
var URLs = {
foo: url_path + '?input=moduleone&param=1',
bar: url_path + '?input=moduleone&param=2'
};
jQuery('.reset').click(function() {
//do stuff...
var url = URLs[grid_id];
jQuery('#foo').setGridParam({url:url}).trigger('reloadGrid');
});

Mootools 1.3.2 & IE8 Only Error, Object doesn't support property/method

My script is throwing an error only in IE8. I'm using Mootools 1.3.
The error thrown is:
Object doesn't support this property/method.
The IE8 debugger is telling me that the error is this (line with **):
append: function(original){
for (var i = 1, l = arguments.length; i < l; i++){
var extended = arguments[i] || {};
**for (var key in extended) original[key] = extended[key];**
}
return original;
}
The above code is around line 380 in the uncompressed version. It's part of Object.extend.
Now I am suspecting this is happening when I do a each on an array like object. This is the code I'm suspecting is triggering this:
var self = this,
c = certManager.addedCerts,
e = window.expManager.workExp,
cA = this.cA = [],
eA = this.eA = [];
c.each(function(x){
cA.push(x.value);
});
e.each(function(x){
eA.push(x.retrieve('info'));
});
The first array (c) is populated with numbers only. The second one (e) is populated with objects that have a store/retrieve going on.
The array like objects are declared like so:
addedCerts = this.addedCerts = []
workExp = this.workExp = []
in their respective modules (certManager & expManager).
Does anyone has any idea why this is happening?
EDIT:
As requested, here's how workExp is populated:
var r = $('resumeContent'),
h = "<div class=\"contentRowRight\"><a href=\"#\" class=\"xHover remove\" > </a><br/>" + yV + " " + mV + "</div><strong>" + pV + "</strong><br />" + cV,
n = new Element('div', {html: h, 'class': 'contentRow'}).inject(r, 'top');
n.getElement('.remove').addEvents({
'click': function (e) {
e.preventDefault();
self.removeExp(this);
},
'mouseover': function(){
var el = this;
this.store('mO', true);
(function() {
if (el.retrieve('mO')){
el.fieldToolTip('Remove Experience',false,false);
el.store('mO', false);
}
}).delay(500);
},
'mouseout': function(){
this.store('mO', false);
this.removeToolTip();
}
});
n.store('info', {'p': pV, 'c': cV, 'y': yV.replace(' year', '').replace('s', '').replace(' and', ''), 'm': mV.replace('month', '').replace('s', '')});
this.workExp[this.workExp.length] = n;
What I have going on is part of a form. I have a few fields inside a form, you fill them in and then click the Add button and it creates a new 'row'. Those rows contain information about a users work experience. Once the user as added all his work experience, he can keep filling the form. Once the form is all filled out, I iterate over the array object and transform the values into a JSON object which I then put in a hidden value and pass it to the back end.
If you guys want to see the script in action, you can visit http://www.oilforce.com/user-signup.php. You will be forced to fill the first page which is personal info (you can type in garbage, it's not like you're gonna submit it anyways) and then press next 3 times. On the fourth page, the work experience form is there. That's what the code above is doing.
The error was stemming from a field I had in my form. The id of the field was 'position' which apparently clashed with something in ie8. Renamed my field to 'pos' and everything seems to work now.
I'm getting the exact same error.
My code is very simple:
alert(document.id('YouTubeFlashPlayer').get('id'));
However it is run via Element.addEvent() inside a MooTools Class object. The offending method above is
.get()
It's as if IE8 is completely ignoring the MooTools extended methods, since
document.id('YouTubeFlashPlayer')
will in fact alert an HTML object.
I'm almost at the end of my rope with IE8. I'm considering banishing it via
if(Browser.ie8) document.getElement('body').dispose();
ok i was debugging the code a little bit.. and i think your problem is when you try to display the tooltip for the validation message. My guess is that there's a problem with the sound that the tooltip has to play.
My advice would be to change this line:
validateStep: function(x) {
...
// line 6230
this.fields[i].fieldToolTip('Your ' + names[i] + ' should be a minimum of ' + (lengths[i]+1) + ' characters.');
to a simple alert() (or maybe just comment this line) just to see if this is the problem. If this is the problem, you can disable the sound and hopefully you wont have this problem
Good Luck!
If you use compilation for your project you may consider to try at the top of your entry point (file)
import 'core-js'
At the moment core-js polyfill library is the easiest way to make Cross Browser Support

basic jquery looping question around 'click'

Basic question, but I have been pounding my head for a bit so thought id bring it here.
html looks like this (edit, fixed the closing quotes)
<span class='deleteimage-119'>delete</span>
<span class='deleteimage-120'>delete</span>
<span class='deleteimage-121'>delete</span>
<span class='deleteimage-122'>delete</span>
<span class='deleteimage-123'>delete</span>
javascript/jquery looks like this
iids = ['119','120','121','122','123'];
for (i=0; i<iids.length; i++) {
place = iids[i];
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
The click functionality gets attached to each individual span, but the alert after clicking just shows the last item in the array.
You have a scoping issue. By the time the callback fires, place has the last value from the loop.
You need to create a new variable for the closure; one variable per iteration, each of which will then be "caught" by the closure and used in the callback.
It would be nice if the solution were this:
var iids = ['119','120','121','122','123']; // don't forget `var` please
for (var i=0; i<iids.length; i++) {
var place = iids[i]; // local variable?
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
Alas, Javascript has no block scope so there's still only one variable here called place, and it keeps getting updated as the loop runs.
So, you have to use a function instead:
var iids = ['119','120','121','122','123'];
function f(place) {
// NOW `place` is a local variable.
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
for (var i=0; i<iids.length; i++) {
f(iids[i]);
}
There are neater ways to employ this function approach using closures and bound variables, and the other answers cover those neater ways quite well. My answer has focused on explaining the issue.
The issue is with scopes and closure.
In JS the scope is # function level and not block level.
Try this:
var iids = ['119','120','121','122','123'];
for (i=0; i<iids.length; i++) {
place = iids[i];
var clickFn = function(a){
return function(){
alert(a);
}
}(place);
$(".deleteimage-" + place).click(clickFn );
}
This is because the click is occurring after the loop is completed, so you're alerting place = iids[iids.length - 1]. In order to achieve the result you're looking for you need to create a function closure and pass place in as a parameter:
iids = ['119', '120', '121', '122', '123'];
for (i = 0; i < iids.length; i++) {
place = iids[i];
(function(_place) {
$(".deleteimage-" + _place).click(function() {
alert(_place);
});
} (place));
}
Inside the loop, you are binding the click event to those span, but the events are fired only after the loop is complete. So, it will always show the last value.
As others have mentioned, you have a scoping issue. Unless all of those classes have a unique meaning, I'd move the place value to a data attribute and leave deleteimage as the class name. Something like:
<span class='deleteimage' data-place='119'>delete</span>
<span class='deleteimage' data-place='120'>delete</span>
<span class='deleteimage' data-place='121'>delete</span>
<span class='deleteimage' data-place='122'>delete</span>
<span class='deleteimage' data-place='123'>delete</span>
$(".deleteimage").click(function() {
var place = $(this).data("place");
alert(place);
});
If the place values aren't unique values, then this answer doesn't apply.

Javascript reference by value/by reference problem

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) );

Categories