Closures affecting variables in a closed function within a loop - javascript

this.config = {
source: psource,
_events: [
'value1',
'value2',
'value3'
]
};
// Add callbacks to source
var that = this;
for (var i = this.config._events.length - 1; i >= 0; i--) {
var name = this.config._events[i];
console.log(name); // correct
$(this.config.source).on(name, function() {
console.log(name); // value1
console.log(that.config._events[i]); // undefined
});
}
I can't see what is wrong here. I removed all the complicated versions and put in the simplest, it just doesn't want to work at all. The first console.log correctly outputs all the correct names, but it acts like the loop happens all at once, then does it again for the inner console.log's.
Can anyone see what's wrong?

In that block
console.log(that.config._events[i]); // undefined
i would end up being -1 every time your closure is called.
You would have to do something of the sort in order to create a closure around i
$(this.config.source).on(name, function(i) { return function() {
console.log(name); // value1
console.log(that.config._events[i]); // undefined
};
}(i) );

In Javascript, it is not recommended to define a function inside a for loop.
Instead, You should use a javascript lib that provides each e.g. underscore. Then your code will look like this:
_.each(this.config._events, function(e) {
$(this.config.source).on(name, function() {
console.log(e);
});
You might want to reverse the array before.
Here is the doc for _.each
You can also use jQuery's $.each which provides a similar interface.

Related

dynamic call to a function jQuery

I am trying to build a lib and I need to call functions dynamically depending on the variables I have in parameter like this
strategies = min
function dispatchRuleToStrategy(strategies)
{
$.each(strategies, function(index, value) {
strategy = "strategy_" + value;
});
}
function strategy_min()
{
// do something
}
How can I call the function strategy_min() from dispatchRuleToStrategy()?
I've been trying a couple of things none of which are working.
Thanks for your help
Use an Object to create a dictionary of your functions e.g. lib
var lib = {
'strategy_min': strategy_min
};
then you can invoke via the key in this dictionary Object
lib['strategy_min']();
If you've named all your functions and you don't want to re-type the names over and over, you could
var lib = {};
function addToLib(fn) {
lib[fn.name] = fn;
}
// then
addToLib(strategy_min);
// or
[strategy_min].forEach(addToLib);
Put them in an object and use the property name:
var strategy_table = {
min: function() {
// do something
},
max: function() {
// do something else
},
...
};
Then you can access them as strategy_table[value]:
$.each(strategies, function(index, value) {
strategy_table[value]();
});
Others have already suggested to create a wrapper object for the functions, however, if your strategy_min() function is in the global scope, you can access it directly:
window['strategy_' + value]();
window in browsers refers to the global object. The bracket notation is used to access properties whose keys are dynamically generated. This way you are accessing the function, which is a property of the global object, i.e. window, and calling it using the parentheses.
Finally I found the real problem. I was in a jquery document ready which is a closure. I did not knew what closures were before today.
Thanks all for your help
You can use eval() function in the following manner
$.each(strategies, function(index, value) {
strategy = "strategy_" + value;
eval(strategy+"()");
});

Is it a good practice to store jquery plugin configuration in data?

I want to create jQuery plugin with config (for example plugin myplugin).
Than call $(elem).myplugin(config); After that I want to call methods from this plugin like $(elem).myplugin().method() with already stored config.
My offer is something like that:
(function($) {
$.fn.myplugin = function(options) {
var $this = $(this);
var getOptions = function() {
return $this.data('myplugin');
};
var initOptions = function(opt) {
$this.data('myplugin', opt);
};
var setOption = function(key, value) {
$this.data('myplugin')[key] = value;
}
var updateBorderWidth = function() {
$this.css('border-width',
getOptions().borderWidth * getOptions().coeficient);
};
var init = function(opt) {
initOptions(opt);
updateBorderWidth();
}
function changeBorder(width) {
setOption('borderWidth', width)
updateBorderWidth();
}
if(options) {
init(options);
}
return {
changeBorder : changeBorder
};
}
})(jQuery);
And usage:
$(function() {
var item1 = $('#test1').myplugin({ coeficient: 1, borderWidth: 1 });
var item1 = $('#test2').myplugin({ coeficient: 2, borderWidth: 1 });
$('#btn').click(updateBorder);
});
function updateBorder() {
$('#test1').myplugin().changeBorder($('#inpt').val());
$('#test2').myplugin().changeBorder($('#inpt').val());
}
Example: http://jsfiddle.net/inser/zQumX/4/
My question: is it a good practice to do that?
May be it's incorrect approach. Can you offer better solution?
Edit:
After searching for threads on jQuery plugin template I found these Boilerplate templates (updated) which are more versatile and extensive designs than what I've offered below. Ultimately what you choose all depends on what your needs are. The Boilerplate templates cover more use cases than my offering, but each has its own benefits and caveats depending on the requirements.
Typically jQuery plugins either return a jQuery object when a value is passed to them as in:
.wrap(html) // returns a jQuery object
or they return a value when no parameter is passed in
.width() // returns a value
.height() // also returns a value
To read your example calling convention:
$('#test1').myplugin().changeBorder($('#inpt').val());
it would appear, to any developer who uses jQuery, as though two separate plugins are being utilized in tandem, first .myplugin() which one would assume will return a jQuery object with some default DOM maniplulation performed on #test1, then followed by .changeBorder($('#inpt').val()) which may also return a jQuery object but in the case of your example the whole line is not assigned to a variable so any return value is not used - again it looks like a DOM manipulation. But your design does not follow the standard calling convention that I've described, so there may be some confusion to anyone looking at your code as to what it actually does if they are not familiar with your plugin.
I have, in the past, considered a similar problem and use case to the one you are describing and I like the idea of having a convenient convention for calling separate functions associated with a plugin. The choice is totally up to you - it is your plugin and you will need to decide based on who will be using it, but the way that I have settled on is to simply pass the name of the function and it's parameters either as a separate .myplugin(name, parameters) or in an object as .myplugin(object).
I typically do it like so:
(function($) {
$.fn.myplugin = function(fn, o) { // both fn and o are [optional]
return this.each(function(){ // each() allows you to keep internal data separate for each DOM object that's being manipulated in case the jQuery object (from the original selector that generated this jQuery) is being referenced for later use
var $this = $(this); // in case $this is referenced in the short cuts
// short cut methods
if(fn==="method1") {
if ($this.data("method1")) // if not initialized method invocation fails
$this.data("method1")() // the () invokes the method passing user options
} else if(fn==="method2") {
if ($this.data("method2"))
$this.data("method2")()
} else if(fn==="method3") {
if ($this.data("method3"))
$this.data("method3")(o) // passing the user options to the method
} else if(fn==="destroy") {
if ($this.data("destroy"))
$this.data("destroy")()
}
// continue with initial configuration
var _data1,
_data2,
_default = { // contains all default parameters for any functions that may be called
param1: "value #1",
param2: "value #2",
},
_options = {
param1: (o===undefined) ? _default.param1 : (o.param1===undefined) ? _default.param1 : o.param1,
param2: (o===undefined) ? _default.param2 : (o.param2===undefined) ? _default.param2 : o.param2,
}
method1 = function(){
// do something that requires no parameters
return;
},
method2 = function(){
// do some other thing that requires no parameters
return;
},
method3 = function(){
// does something with param1
// _options can be reset from the user options parameter - (o) - from within any of these methods as is done above
return;
},
initialize = function(){
// may or may not use data1, data2, param1 and param2
$this
.data("method1", method1)
.data("method2", method2)
.data("method3", method3)
.data("destroy", destroy);
},
destroy = function(){
// be sure to unbind any events that were bound in initialize(), then:
$this
.removeData("method1", method1)
.removeData("method2", method2)
.removeData("method3", method3)
.removeData("destroy", destroy);
}
initialize();
}) // end of each()
} // end of function
})(jQuery);
And the usage:
var $test = $('#test').myplugin(false, {param1: 'first value', param2: 'second value'}); // initializes the object
$test.myplugin('method3', {param1: 'some new value', param2: 'second new value'}); // change some values (method invocation with params)
or you could just say:
$('#test').myplugin(); // assume defaults and initialize the selector
Passing parameters to javascript via data attributes is a great pattern, as it effectively decouples the Javascript code and the server-side code. It also does not have a negative effect on the testability of the Javascript code, which is a side-effect of a lot of other approaches to the problem.
I'd go as far as to say it is the best way for server-side code to communicate with client-side code in a web application.

jQuery.bind() events on plain Javascript objects

Is it ok to bind jQuery events to plain, non-DOM Javascript objects:
var myobject = {};
$(myobject).bind("foobar", function() { alert("daa"); });
$(myobject).trigger("foobar");
What are the implications for
Garbage collection (no new references created preventing object to GC'ed)
Object attributes (new attributes assigned to the object)?
Performance
Some things I have noted
Event name must not conflict with a function name on the object, e.g. you cannot have function init and event named init and trigger it correclty
Instead of using the jquery event system, I would implement one that mimics it using the jQuery.Callbacks method.
var myClass = function(){
this._callbacks = {};
};
myClass.prototype = {
addEvent: function(evname,callback) {
if (!this._callbacks[evname]) {
this._callbacks[evname] = $.Callbacks();
}
this._callbacks[evname].add(callback);
},
removeEvent: function(evname) {
if (!this._callbacks[evname]) {
return;
}
this._callbacks[evname].remove();
//Might need this too:
//this._callbacks[evname] = null;
},
triggerEvent: function(evname) {
if (this._callbacks[evname]) {
this._callbacks[evname].fire();
}
}
};
var foo = new myClass();
foo.addEvent("foo",function(){
console.log('foo');
});
foo.triggerEvent("foo");
foo.removeEvent("foo");
// event was removed, the below line won't do anything.
foo.triggerEvent("foo");
http://jsfiddle.net/kEuAP/
However, to answer your question, I don't see any immediate problems with what you are doing other than it isn't documented and may change functionality from version to version (although it works in all currently available versions 1.2.6+).
Seeing as jQuery support alteration of object properties via animate also, this is definitely fine.
var obj = {'test':0};
var interval = setInterval(function(){console.log(obj);}, 250);
$(obj).on("fun", function(){this.test++});
$(obj).animate(
{'test':100},
3000,
function (){
console.log(obj);
clearInterval(interval);
$(obj).trigger("fun")
console.log("increment",obj);
}
);
//will console log {test: 1.5}, {test: 6.4} etc then {test: 100}
//and finally "interval" {test: 101}
Quickredfox's backup comment is a pretty good source too:
http://forum.jquery.com/topic/triggering-custom-events-on-js-objects

Need some help understanding basics about JS module pattern

I am trying to organize my code using the revealing module pattern.
I have a very basic question about how to set up a setter method.
$(document).ready(function() {
var designs = (function() {
var curRow,
setCurRow = function(val) {
curRow = val;
},
initTable = function() {
setCurRow(0);
};
return {
curRow : curRow,
setCurRow : setCurRow,
initTable : initTable
}
}) ();
designs.initTable();
designs.setCurRow(someNewVal);
console.log(designs.curRow);
});
The problem is that i dont get the someNewVal in the console output, I get undefined instead! I have a feeling I am doing something pretty silly here.
You can also solve this in another way by understanding the scopes of the variables and functions involved.
When you return your object constructor { curRow: curRow ... }, that just initializes the object member named curRow to the value of the variable curRow in the scope of the anonymous function; it doesn't create any persistent connection between them.
Once the anonymous function returns, calling designs.setCurRow is updating the curRow variable in that scope exactly as you expect, but that variable is now totally inaccessible to the outside world -- there is no connection between it and the curRow member of designs.
You can solve this by making the setCurRow method operate on this.curRow, as in the other solutions. In that case you don't need to make curRow a variable in the original scope, since it's entirely unused. The other solution is to add a 'getter' method to your current one:
var designs = (function() {
var curRow,
setCurRow = function(val) {
curRow = val;
},
getCurRow = function() {
return curRow;
},
initTable = function() {
setCurRow(0);
};
return {
getCurRow : getCurRow,
setCurRow : setCurRow,
initTable : initTable
};
}) ();
designs.initTable();
designs.setCurRow(someNewVal);
console.log(designs.getCurRow());
Because getCurRow and setCurRow are functions that are closed in the scope containing the variable varRow, they can reach back into that scope and access and change variables that are only accessible within it.
In this case making curRow a member of the object you return is probably simpler, but the other way is useful too since you can use it to create effectively private members and methods.
Looks like you want an object, not a module:
$(document).ready(function() {
var designs = {
setCurRow: function(val) {
this.curRow = val;
},
initTable: function() {
this.setCurRow(0);
},
curRow: 0
};
designs.initTable();
designs.setCurRow(someNewVal);
console.log(designs.curRow);
});
The problem is that setCurRow sets the value of the variable curRow after designs.curRow has already been set. Consider something like this:
var a = 1;
b = a; // sets b = a = 1
b = 2; // sets b = 2; leaves a = 1
Your code is doing the same thing, but with object-properties and setter methods to make it look complicated. :-)
As ruakh pointed out, you never re-assign curRow on the returned object, so it is always the default value. Change it to:
setCurRow = function(val) {
this.curRow = curRow = val;
},
And everything should work*.
* At least mostly - you won't be able to use call and apply on setCurRow (or pass it to setTimeout or setInterval without binding it first to your object (designs), since this is bound at call time in JavaScript.

Jquery click bindings are not working correctly when binding multiple copies

I seem to have an issue when creating copies of a template and tying the .click() method to them properly. Take the following javascript for example:
function TestMethod() {
var test = Array();
test[0] = 0;
test[1] = 1;
test[2] = 2;
// Insert link into the page
$("#test_div").html("<br>");
var list;
for (x = 0; x < test.length; x++) {
var temp = $("#test_div").clone();
temp.find('a').html("Item #" + test[x]);
temp.click(function () { alert(x); });
if (list == undefined)
list = temp;
else
list = list.append(temp.contents());
}
$("#test_div2").append(list);
}
The problem I am seeing with this is that no matter which item the user clicks on, it always runs alert(2), even when you click on the first few items.
How can I get this to work?
Edit: I have made a very simple example that should show the problem much clearer. No matter what item you click on, it always shows an alert box with the number 2 on it.
Correct me if I'm wrong, .valueOf() in JS returns the primitive value of a Boolean object.....
this would not happen ShowObject(5,'T');... ShowObject(objectVal.valueOf(), 'T');
why not use objects[x].Value directly? ShowObject(objects[x].Value, 'T');
WOOOOOSSSHHHH!
after searching deeply... I found a solution...
because it's a closure, it won't really work that way...
here's a solution,
temp.find('a').bind('click', {testVal: x},function (e) {
alert(e.data.testVal);
return false;
});
for best explanation, please read this... in the middle part of the page where it says Passing Event Data a quick demo of above code
I think your issue arises from a misunderstanding of scopes in JavaScript. (My apologies if I'm wrong.)
function () {
for (...) {
var foo = ...;
$('<div>').click(function () { alert(foo); }).appendTo(...);
}
}
In JavaScript, only functions create a new scope (commonly referred to as a closure).
So, every round of the for loop will know the same foo, since its scope is the function, not the for. This also applies to the events being defined. By the end of looping, every click will know the same foo and know it to be the last value it was assigned.
To get around this, either create an inner closure with an immediately-executing, anonymous function:
function () {
for (...) {
(function (foo) {
$('<div>').click(function () { alert(foo); }).appendTo(...);
})(...);
}
}
Or, using a callback-based function, such as jQuery.each:
function () {
$.each(..., function (i, foo) {
$('<div>').click(function () { alert(foo); }).appendTo(...);
});
}
For your issue, I'd go with the latter (note the changes of objects[x] to just object):
var list;
jQuery.each(data.objects, function (x, object) {
// Clone the object list item template
var item = $("#object_item_list_template").clone();
// Setup the click action and inner text for the link tag in the template
var objectVal = object.Value;
item.find('a').click(function () { ShowObject(objectVal.valueOf(), 'T'); }).html(object.Text);
// add the html to the list
if (list == undefined)
list = item;
else
list.append(item.contents());
});

Categories