Mootools class variable scope - javascript

Take this class:
var MyClass = new Class({
Implements: [Events, Options],
initialize: function() {
this.a = 1;
},
myMethod: function() {
var mc = new differentClass({
events: {
onClick: function() {
console.log(this.a); // undefined (should be 1, 2, 3 etc)
this.a ++;
}
}
});
}
});
How do I keep the value of this.a? I am basically trying to draw a line (using canvas) from the last point to the co-ordinates just clicked.
[EDIT]
I dont want to bind this as it's bad apparently and it will over-ride the differentClass options.

several patterns are available.
decorator via .bind()
var mc = new differentClass({
events: {
click: function() {
console.log(this.a);
this.a ++;
}.bind(this) // binds the scope of the function to the upper scope (myclass)
}
});
keeping a reference.
var self = this; // reference parent instance of myClass
var mc = new differentClass({
events: {
click: function() {
console.log(self.a);
self.a ++;
}
}
});
pointing to a myClass method that can deal with it:
handleClick: function() {
this.a++;
},
myMethod: function() {
var mc = new differentClass({
events: {
click: this.handleClick.bind(this)
}
});
}
the 2-nd one - by storing a reference is preferred due to the smaller footprint and universal support whereas .bind is not available in every browser and needs to be shimmed as well as the extra time to curry the function on execution.
self is what you will find in mootools-core itself when possible.
if performance is not at risk, method 3 can probably offer the best readability and code structure. the arguments to the method will remain what the click handler passes, i.e. event and event.target will be the handler.
in pattern #2 with self, this will point to the click handler within the anonymous function (or to the other class, for example), which may be useful as well - rebinding context can be a pain in the neck

You can refer proper context like this:
...
myMethod: function() {
var _this = this;
var mc = new differentClass({
events: {
onClick: function() {
console.log(_this.a);
_this.a ++;
}
}
});
}
...

Related

object oriented javascript - this [function] is not a function

I am moving some jquery functions into a javascript object to clean up some code. My problem is, when I put methods on my object's constructor, calling this.functionName() returns the error this.functionName is not a function but if my functions are helper methods and are outside of the object's constructor, they work just fine.
Here is my code that does not work
function MyConstructor() {
this.init();
this.selectAllHandler();
}
MyConstructor.prototype = {
init: function() {
var self = this;
$(document).on('click', '#my_element', function() {
self.selectAllHandler.call(this);
});
},
selectAllHandler: function() {
// handler works fine
var ids_array = this.idsArray(checkboxes); // error happening here
},
// helpers
idsArray: function(checkboxes) {
// trying to call
}
}
But, having my object w/ a constructor and then calling the "helper" outside of the object works fine. For example, this works fine.
function MyConstructor() {
this.init();
}
MyConstructor.prototype = {
init: function() {
var self = this;
$(document).on('click', '#my_element', function() {
self.selectAllHandler.call(this);
});
},
selectAllHandler: function() {
// handler works fine
var ids_array = idsArray(checkboxes);
}
}
function idsArray() {
// code that works fine
}
One thing to note as well, is that in this scenario, by running console.log this refers to the element being clicked on, and not the constructor.
I have tried using call, apply, and bind, but have not had success, though I think it's been syntax related.
How can I build this so I can call my "helper" functions inside my object?
Not sure how you were using bind, since you said it didn't work for you.
If you want, you can use bind like below. Also, in your code snippet checkboxes was not defined. This way you don't need to use self.
function MyConstructor() {
this.init();
this.selectAllHandler();
}
MyConstructor.prototype = {
init: function() {
//var self = this;
$(document).on('click', '#my_element', function() {
//self.selectAllHandler.call(self);
this.selectAllHandler();
}.bind(this));
},
selectAllHandler: function() {
// handler works fine
var checkboxes;
var ids_array = this.idsArray(checkboxes); // error happening here
},
// helpers
idsArray: function(checkboxes) {
// trying to call
console.log('test');
}
}
var o = new MyConstructor();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I was able to figure it out. I thought I could call another function in the constructor just using this.functionName(). however, $(this) was referring to the element I was clicking on.
I remembered I defined self (this) in my init function which refers to the window object. Well, inside the window object is my object, and my function is on that object. So i was able to successfully call my object by doing
function MyConstructor() {
this.init();
}
MyConstructor.prototype = {
init: function() {
var self = this;
$(document).on('click', '#my_element', function() {
self.selectAllHandler.call(this);
});
},
selectAllHandler: function() {
// RIGHT HERE
var ids_array = self.MyConstructor.prototype.idsArray(checkboxes);
},
// helpers
idsArray: function(checkboxes) {
// some codes
}
}

jQuery options object reference

When dealing with jQuery options object, should I reference the "global" ResponsiveMenu every time or create a "local" copy of the option I need in each module?
Have a look at the code and let me know which you think is best and why, or if it even matters at all. The way I've been doing it is: if I use the reference more than once, I make a "local" copy. If I only use it once, I'll reference the "global" one.
ResponsiveMenu = {
init: function(options, elem) {
this.options = $.extend( {}, this.options, options );
this.elem = $(elem);
this.bindEvents();
return this;
},
options: {
trigger: null,
activeClass: 'active',
submenuTrigger: $('.sub-toggle')
},
bindEvents: function() {
var self = this;
this.options.trigger.on('click', triggerMain(evt, self));
},
triggerMain: function(evt, self) {
evt.preventDefault();
var activeClass = self.options.activeClass;
self.elem.toggleClass(activeClass);
self.options.trigger.toggleClass(activeClass); //"Global" reference
},
}
OR this:
bindEvents: function() {
var self = this,
trigger = this.options.trigger; //"Local" copy
trigger.on('click', triggerMain(evt, self, trigger));
},
triggerMain: function(evt, self, trigger) {
evt.preventDefault();
var activeClass = self.options.activeClass;
self.elem.toggleClass(activeClass);
trigger.toggleClass(activeClass);
},
This looks primarily like a style question. Generally I only define a variable if I need to access that same value more than once. When referencing a function you will also run into scope issues. For example in this case:
var obj = {
num: 2,
trigger: function() {
console.log(this.num);
}
}
obj.trigger();
It will log 2 to the console because the function is bound to obj as the scope. If you do
var t = obj.trigger;
t();
however, you will get undefined because the default scope for a function is the window object. In ECMAScript 5 you can tell what to bind the function to like this:
var t = obj.trigger.bind(obj);
t();
Which will now log 2.

Backbone: Easiest way to maintain reference to 'this' for a Model inside callbacks

var JavascriptHelper = Backbone.Model.extend("JavascriptHelper",
{}, // never initialized as an instance
{
myFn: function() {
$('.selector').live('click', function() {
this.anotherFn(); // FAIL!
});
},
anotherFn: function() {
alert('This is never called from myFn()');
}
}
);
The usual _.bindAll(this, ...) approach won't work here because I am never initializing this model as an instance. Any ideas? Thanks.
You could do it by hand:
myFn: function() {
$('.selector').live('click', function() {
JavascriptHelper.anotherFn();
});
}
Or, if anotherFn doesn't care what this is when it is called (or if it wants this to be what live uses):
myFn: function() {
$('.selector').live('click', JavascriptHelper.anotherFn);
}
As an aside, live has been deprecated in favor of on. Also, if you're not instantiating your JavascriptHelper, then why is it a Backbone.Model at all? Why not use a simple object literal:
var JavascriptHelper = {
myFn: function() {
//...
},
anotherFn: function() {
//...
}
};
And what are you expecting this construct:
var JavascriptHelper = Backbone.Model.extend(string, {}, {...})
to leave you in JavascriptHelper? Extending a string is strange but passing three arguments to Backbone.Model.extend is pointless, it only cares about two arguments. If you want static properties then you should be passing them as the second argument:
var JavascriptHelper = Backbone.Model.extend({}, { myFn: ... });

How do I find the instance to which a method belongs?

// minified base class code (if the uncompressed code is needed, I'll post it)
function Class(){}Class.prototype.construct=function(){};Class.extend=function(c){var a=function(){arguments[0]!==Class&&this.construct.apply(this,arguments)},d=new this(Class),f=this.prototype;for(var e in c){var b=c[e];if(b instanceof Function)b.$=f;d[e]=b}a.prototype=d;a.extend=this.extend;return a};
// custom event class
var Event = Class.extend({
handlers: [],
// stores the event handler
subscribe: function(handler, thisObj) {
this.handlers.push([handler, thisObj]);
},
// calls the event handlers
fire: function() {
for(i in this.handlers) {
var handler = this.handlers[i];
handler[0].apply(handler[1]);
}
}
});
var Class2 = Class.extend({
myEvent: new Event(), // the event
test: function() { // fires the event
this.myEvent.fire(this);
}
});
var Class3 = Class.extend({
construct: function(model) {
this.name = "abc";
model.myEvent.subscribe(this.handler, this); // subscribe to the event
},
handler: function() {
alert(this.name); // alerts 'abc'
}
});
var instance1 = new Class2();
var instance2 = new Class3(instance1);
instance1.test();
The only way to make the event handler code to work with the good 'this' is by adding a new argument ('thisObj') to the 'subscribe' method? Is there a better way to do this?
The behaviour you're getting is due to the fact that when you pass a "method" to a function, the receiving function has no idea it's a method. It's just a block of javascript that needs to get executed.
Prototype gets around this issue with the bind method
http://api.prototypejs.org/language/function/prototype/bind/
You can get similar behaviour (I didn't look at the implementation details of bind) by using a closure.
var Class3 = Class.extend({
construct: function(model) {
this.name = "abc";
//model.myEvent.subscribe(this.handler, this); // subscribe to the event
var self = this;
model.myEvent.subscribe(function() {self.handler()});
},
handler: function() {
alert(this.name); // alerts 'abc'
}
});
Or apply some similar functionality to the subscribe method of your custom event class.
EDITED To reflect CMS's observations. Thanks!

Class.create and this keyword

I'm trying to use the Class methods of prototype.js to manage my object hierarchy on my current project, but I have some trouble with the this keyword in conjonction with Class.create.
Here is a piece of old-fashioned plain js code to create inheritance:
var Super1 = function () {
this.fu = "bar";
}
var Sub1 = function () {
this.baz = "bat";
this.f = function (e) {
alert("Sub1:"+this.fu+this.baz);
}.bindAsEventListener(this);
document.observe("click", this.f);
};
Sub1.prototype = new Super1();
new Sub1();
And here is my first attempt at mimicking this with Class.create:
var Super2 = Class.create({
fu: "bar"
});
var Sub2 = Class.create(Super2, {
baz: "bat",
f: function (e) {
alert("Sub2:"+this.fu+this.baz);
}.bindAsEventListener(this),
initialize: function () {
document.observe("click", this.f);
}
});
new Sub2();
So far so good... But of course it doesn't work: f is bound to window, not to the object created with new. The only way I found is:
var Super3 = Class.create({
fu: "bar"
});
var Sub3 = Class.create(Super3, {
baz: "bat",
f: function (e) {
alert("Sub3:"+this.fu+this.baz);
},
initialize: function () {
this.f = this.f.bindAsEventListener(this);
document.observe("click", this.f);
}
});
new Sub3();
But it's really inelegant. How am I supposed to handle this?
edit (to reply to Colin):
I need to bind f itself, so that I can call stopObserving on f (see http://prototypejs.org/api/event/stopObserving)
I still need bindAsEventListener every time I need this inside the listener, since by default this is the element that fires the event (see http://prototypejs.org/api/event/observe)
I'm still waiting for an anwser on the googlegroup :) I kind of posted here to see whether I can get a faster answer via S.O.
I could (and probably should) use bind instead of bindAsEventListener. I used the latter to make it clear that I'm getting a listener. It doesn't change the fact that the binding procedure is inelegant.
You almost got it, just no need to redeclare this.f:
var Sub3 = Class.create(Super3, {
baz: "bat",
f: function () {
alert("Sub3:"+this.fu+this.baz);
},
initialize: function () {
document.observe("click", this.f.bindAsEventListener(this));
// You could also use .bind(), since you don't pass any
// other arguments to f
}
});
Edit: In reply to your comment, you could do something like this (although it is arguably as 'ugly' as your example):
var Sub3 = Class.create(Super3, {
baz: "bat",
initialize: function () {
this.f = function () {
alert("Sub3:"+this.fu+this.baz);
}.bind(this);
document.observe("click", this.f);
}
});
Now you can use stopObserving with this.f. When you need to re-register the listener, you can simply use this.f again, since it'll still be bound to the same instance as was used when initialize was called.
I'm not too clear about this area, but I think the answer is (untested):
var Sub3 = Class.create(Super3, {
baz: "bat",
f: function() {
alert("Sub3:"+this.fu+this.baz);
},
initialize: function () {
document.observe("click", this.f.bind(this));
}
});
Class arranges that initialize is called as an object method of the new object, so 'this' refers to the new object inside 'initialize', so you just need to bind this.f to this.
Incidentally, bindAsEventListener is almost never needed: Event.observe (and Element.observe) call the handler with the event as the first argument anyway.
Incidentally 2, the Prototype & Scriptaculous google group is a very helpful forum.

Categories

Resources