I am building a small app which is part of a sales-enquiry process. It has 'pages' which the visitor progresses through. I have laid out these pages as part of a large object literal. In the following code, branch-select is one of those pages. As you can see, the init() function sets a sibling value by using this to refer to the parent branch-select. However, the save() function is called from a click event, so instead of using this, it seems I have to laboriously write out the full object reference each time to set values? Please see the code & comments below to illustrate the problem:
// This is part of a larger object called "stepData"
"previous page": {
// ...
}
"branch-select": {
ref: "Select Type",
visited: false,
init: function(){
this.visited = true; // Here I can use "this" to set other values in the parent object
// ....
},
next: "",
save: function(){
branchKey = $(this).attr('data-value'); // this function is invoked from a click event, so "this" refers to the DOM element that was clicked. Therefore throughout the rest of the function if I want to set values on the parent object, I have to write out the full object reference each time...
switch(branchKey){
case "Lodges":
stepData['branch-select'].ref = "Lodges";
stepData['branch-select'].values[0].a = "Lodges";
stepData['branch-select'].next = "lodge-2"; // Do I really have to write out stepData['branch-select'] each time?
break;
case "Caravans":
stepData['branch-select'].ref = "Caravans";
stepData['branch-select'].values[0].a = "Caravans";
stepData['branch-select'].next = "van-2";
break;
}
stepData[stepData['branch-select'].next].init();
}
},
"next page": {
// ...
}
In the interests of DRY (Don't repeat yourself) code, I was wondering if there is any neat solution to this?
EDIT:
Webkit's answer presents a new problem; the clicked DOM element (.branch-select) is dynamically introduced, so to bind the click event, I have to use:
$("#template-holder").on('click', ".branch-select", stepData['branch-select'].save);
(template-holder is the parent element which is always present). How would I integrate the call() method into the above code?
Another way to have "this" reference your object when handling an event is using 'call'.
for example:
var setData = {
save: function(){
// 'this' shall be setData!
var _bs = this['branch-select'];
_bs.ref = "Lodges"...
}
}
then:
$(".someElement").on('click', function() {
setData.save.call(setData)
});
**updated (I'm pretty sure this should work just the same):
$("#template-holder").on('click', ".branch-select", function() {
stepData['branch-select'].save.call(setData)
});
Related
I'm working on a jQuery plugin, and using the anonymous closure pattern to wrap the works. Internally I define an object prototype with several methods. My previous practice was to define a closure-global var self and set self = this in the constructor. I could then invoke any method on the instance by invoking it on self. Works fine for one-off single instance plugins, but not in the case of suporting multiple instances of the same plugin in a single page. In that case I was running into state being overwritten, I think because I used this single reference to the class code in all places.
Fast forward, I am trying to follow an example I found by Alex Sexton https://alexsexton.com/blog/2010/02/using-inheritance-patterns-to-organize-large-jquery-applications/
His code shows a use of $.data(this,'myPlugin',MyClassObj) for caching an instance of the class on the parent element. I clearly don't understand how to reuse that cached object. QUESTION 1: how does one reload the specific instance? An example of doing so in a closure context would be great.
Furthermore, within my design I have a couple of keydown handlers which ought to invoke a common redisplay function when they are complete. However, this is tied to the target of the event, and I can't see how to invoke another method from within this function. As I said before I used to use a closure-global to access other methods, but that seems ill-advised for multiple instance plugins. QUESTION 2: How do I regain the closure scope this from within a function which has overridden the value of this ?
(function($){
var MyClass = {
init: function(opt,elem) {
this.options = $.extend({},this.options,options);
this.elem = elem;
this.$elem = $(elem);
this._build();
$('.selector',this.elem).on('keydown', this._move );
// other setup things
return this;
},
_build: function() {
// build out the html object
},
_move: function(e) {
if(which == 38) {
e.preventDefault();
// move some things, store some state
// QUESTION 1: how should I store this state?
}
// QUESTION 2: how should I invoke the redisplay fn?
this._redisplay(); // does not work: this == e.target
},
_redisplay: function() {
console.log( 'redisplaying state data' );
// QUESTION 3: how should I be retrieving this state ?
}
}; // end of MyClass
// Start up the plugin
$.fn.myClass = function(options) {
if ( this.length ) {
return this.each(function(){
if ( ! $.data(this, 'myClass') ) {
$.data(this, 'myClass', Object.create(MyClass).init(options, this));
}
});
}
};
})(jQuery);
Any insights really appreciated.
I am trying to use enquire.js to trigger a reload of my bxslider when the screen size is small, to show fewer images.
I have registered a screen width as below.
enquire.register("screen and (max-width:900px)", {
match: this.ChangeSliderDown,
unmatch:this.ChangeSliderUp
});
Now as part of the transition i need to do a calculation based on a variable that is associated with the Prototype of the current class.
ChildCarousel.prototype = {
...
ChangeSliderUp: function()
{
var maxSlides = (this.ourCarouselCollection.length < 3) ? 1 : 3;
...
}
}
in all my other functions referring to this allows me to access variables such as the ourCarouselCollection in the instance of enguire js i get the object that is a result of the register call.
why is this happening and is it possible to change it?
adding the bind(this method solved the problem
enquire.register("screen and (max-width:900px)", {
match: this.ChangeSliderDown.bind(this),
unmatch:this.ChangeSliderUp.bind(this)
});
The value of this has nothing to do with scope, it is resolved within an execution context and is set by the call or with bind. Also, it is convention that only functions that are intended to be called as constructors have names that start with a capital letter (so ChangeSliderUp should be changeSliderUp).
The ChangeSliderUp method is expecting to be called with this referencing an instance of ChildCarousel as its this. When you assign a reference to the function like:
match: this.ChangeSliderDown
then the function will be called without this being set to the instance and will default to the global object or be undefined in strict mode.
You can use bind per Bluephlame's answer, or use a closure something like:
// Assuming that this referenes an instance of ChildCarousel
// where this code is running
var carousel = this;
enquire.register("screen and (max-width:900px)", {
match: function() {carousel.ChangeSliderDown();},
unmatch: function() {carousel.ChangeSliderUp();}
});
but I can't test that. It should ensure that the function is called as a method of an instance, hence setting this to the instance.
I am trying to write a script that does a slide show. I can do it with functions, but I want to use the prototype method. What I am having a hard time figuring out is the procedure. Here is what I have tried to do
var displayVars = {
slide: '',
thumb: ''
}
//setup display
display = function(slide,thumb) {
displayVars.slide = $(slide);
displayVars.thumb = $(thumb);
// set slider width
}
display.prototype.play = function() {
// move slide to this location
display.hightlight();
}
display.prototype.hightlight = function() {
// add border to element
}
$(function() {
newdis = new display('.show-slide','.window-thumbs');
displayVars.timer = setTimeout(newdis.play,500);
});
If you notice in the play function I want to call the highlight method. What I really want is to run the highlight function every time the play function is called. I can't get my head to see how this can be done because "display" or "this" will not let me access the highlight function.
The problem is not with the innards of your prototype functions, but rather with the way you set up the timeout handler.
displayVars.timer = setTimeout(function() { newdis.play(); }, 500);
Then you'll be able to use this in the "play" function:
display.prototype.play = function() {
// move slide to this location
this.hightlight();
}
There's no intrinsic "membership" relationship between a function and an object of any sort. Object properties can refer to functions, but the only time that means anything is when a function call is made via the object property reference. Since you weren't calling the function, but just grabbing a reference to it to pass to "setTimeout()", there was nothing to set the value of this. By wrapping it in an anonymous function that explicitly calls "play" via the object reference, you set up this correctly.
Another way to do this is with the "bind()" function available in newer browsers:
displayVars.tinmer = setTimeout(newdis.play.bind(newdis), 500);
That will have more-or-less the same effect as the anonymous function (with some extra subtleties that don't make much difference most of the time).
This may be a bit abstract but I'm trying to get my head round JavaScript closures etc. Take the following code:
function MyObj() {
var me = this;
this.foo = function(bar) {
// Do something with 'bar'
}
// Set up lots of local variables etc.
// ....
$(window).load(function() {
// Add a delegated click handler to specific <input> elements
$(document).on('click.myobj', 'input.special', function() {
// Do something with the <input> that triggered the click event
me.foo(this);
});
});
}
var myObj = new MyObj();
The anonymous function passed to that is bound to the click event creates a closure that references me. What I want to know is whether it's better to do something like this instead (to avoid the closure):
$(window).load(function() {
// Add a delegated click handler to specific <input> elements
(function(localMe) {
$(document).on('click.myobj', 'input.special', function() {
// Do something with the <input> that triggered the click event
localMe.foo(this);
});
})(me);
});
Is this a better approach, or am I being overly paranoid about creating a closure? Alternatively, is there a "third way"?
EDIT
Additionally, would it be better to do something like this:
$(window).load(function() {
// Add a delegated click handler to specific <input> elements
$(document).on('click.myobj', 'input.special', {localMe : me}, function(event) {
// Do something with the <input> that triggered the click event
event.data.localMe.foo(this);
});
});
The latter is (AFAIK) more efficient, but probably not measurably so unless used in a tight loop.
The reason is that all variable dereferencing must follow the scope chain. In the latter case, the variable localMe can be found in the anonymous function's parameter list.
In the former case, the variable isn't found there, but in the outer scope. This traversal up the scope chain takes extra time.
Anonymous functions are massively used in javascript now (as arguments and as an immediate function for scopes/closures). There's no performance problem with that.
But you can have a problem of code reading maybe. Because when you see a variable, you must check where the variable is from. But no big deal here.
And in your second example, you still have a closure "break". Because in your anonymous function in the click, you use the localMe variable. And the localMe is an argument of a fonction outside of your fonction.
// Here, 'me' is a direct local variable.
$(window).load(function() {
// Here, we are in an anonymous fonction, so 'me' is not a direct variable anymore. But you still can access it.
// Add a delegated click handler to specific <input> elements
(function(localMe) {
// Here, 'localMe' is a direct local variable.
$(document).on('click.myobj', 'input.special', function() {
// We are in an anonymous function, so 'localMe' is not a direct variable anymore.
// Do something with the <input> that triggered the click event
localMe.foo(this);
});
})(me);
});
If you really want to avoid a closure "break", you should bind your function to your object. But note that not every browser support the bind method on functions.
You will always create a closure if you bind the event from the constructor. In fact, you even need the closure to preserve the reference to your instance. However, you might do something like this:
function MyObj() {
this.foo = function(bar) {
// Do something with 'bar'
}
// Set up lots of local variables etc.
// ....
}
var myObj = new MyObj();
$(function() {
$(document).on('click.myobj', 'input.special', function() {
myObj.foo(this);
});
});
If you do only create a singleton instance of your constructor, it won't matter anyway.
I would probably do it this way:
var bind = function( fn, me ) { return function() { return fn.apply(me, arguments); }; },
Object = (function() {
function Object() {
this.handler = bind(this.handler, this);
// Add a delegated click handler to specific <input> elements.
$(document).on("click.myobj", "input.special", this.handler);
}
Object.prototype.foo = function( bar ) {
// Do something with "bar".
};
Object.prototype.handler = function( event ) {
// Do something with the <input> that triggered the click even.
return this.foo(event.currentTarget);
};
return Object;
})();
var obj = new Object();
This skips the uses of closures and iifes, using .apply instead. Not sure if it is more efficient or not, but it is another option.
I am developing an add-on for Firefox (3.6.*). in the following code notify called from inside init works fine, but I get an error saying this.notify is not a function when it is called from within onPageLoad. Why is that?
Also when I change the call to myextobj.notify('title', 'msg'), it works. The same is true for accessing variables. So, what is the difference between this and the object name as a prefix?
var myextobj = {
init: function() {
this.notify('init', 'We are inside init');
...
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
},
onPageLoad: function(aEvent) {
this.notify('onPageLoad', 'We are inside onPageLoad');
...
},
notify: function (title, text) {
Components.classes['#mozilla.org/alerts-service;1'].
getService(Components.interfaces.nsIAlertsService).
showAlertNotification(null, title, text, false, '', null);
}
};
window.addEventListener("load", function() { myextobj.init(); }, false);
When you do this:
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
you just add the function that is hold in onPageLoad as event handler. The connection to the object is lost and this will refer to the global object when executed.
Just create an anonymous function as you do for the load event:
var that = this; // capture reference to object
appcontent.addEventListener("DOMContentLoaded", function(event) {
that.onPageLoad(event);
// myextobj.onPageLoad(event); should also work in this case
}, true);
Remember that functions are first class objects in JavaScript, they can be passed around like any other value. Functions have no reference to an object they are defined on, because they don't belong to that object. They are just another kind of data.
To which object this refers to in a function is decided upon execution and depends on the context the function is executed in. If you call obj.func() then the context is obj, but if you assign the function to another variable before like var a = obj.func (that is wat you do with adding the event handler (in a way)) and then call a(), this will refer to the global object (which is window most of the time).
When onPageLoad is called for the event, 'this' would not be referring to your myextobj. Because it wasn't called in the context of your object myextobj.
The way I deal with this is, by having all member functions of an object using the following convention.
var myObj = {
.....
counter: 0,
.....
myFunction: function () {
var t = myObj;
t.myOtherFunc();
},
....
myOtherFunc: function() {
var t = myObj;
t.counter++;
}
};
See how, I'm aliasing myObj as t, to save on typing and making my intent of using this clear.
Now you can call your methods safely from any context without worrying about what this would be referring to. Unless you really want the standard behavior; in that case, you may like to look at the call and apply methods. This link might help: Function.apply and Function.call in JavaScript
You may also want to look at a recent addition to JavaScript (would be available in FireFox 4): the bind method: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
Another link, which directly addresses your problem: https://developer.mozilla.org/en/DOM/element.addEventListener#The_value_of_this_within_the_handler
The other way to add an event listener without losing track of this is to pass this itself as the event listener. However you are limited in that the function is always called handleEvent, so it's less useful if you have many listeners (unless they are all for different events, in which case you can switch on the event's type).