jQuery options object reference - javascript

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.

Related

How to call javascript class function from closure

I'm working on a jQuery plugin that does not have a selector. When initializing it, I instanciate an object that has functions. In these functions, I need to use closures. In these closures, I would like to call my initial object functions.
To make it more clear, here is a simplified version of the code.
HTML
<script src="/path/to/jquery/2.1.1/jquery.min.js"></script>
<script src="/path/to/my/script/myeditor.js"></script>
<div class="editable">Div1</div>
<div class="editable">Div2</div>
<script>
$.myeditor({
option1: 'a',
option2: 'b'
});
</script>
myeditor.js
function ($) {
var MyEditor = function (options)
{
this.$options = $.extend(true, {}, $.myeditor.defaults, options);
this.init();
};
$.myeditor = function (options = {})
{
return new MyEditor(options);
};
$.flyeditor.defaults = {
option1: '1',
option2: '2'
};
MyEditor.prototype.notify = function(message = '')
{
console.log(message);
};
MyEditor.prototype.init = function()
{
// Do stuff
$('.editables').each(function() {
$this = $(this);
// Here comes the error
notify($this.html());
});
};
}(jQuery);
The problem is that notify(this.html()); raises an error ReferenceError: notify is not defined
How can I reach this notify method?
You can assign this to a separate local variable in a closure. You need to do that because this will no longer point to your MyEditor object inside the each, it will point to each of the .editables
Also, you probably meant to call this.notify(), since the function is attached to the prototype of MyEditor
MyEditor.prototype.init = function()
{
// Do stuff
var that = this; // <-- now we can reach this inside function.
$('.editables').each(function() {
$this = $(this);
// Here comes the error
// You can't use notify, that function is not defined
// You can't use this.notify because this points to something else (the node)
// inside the function in each
that.notify($this.html());
});
};
MyEditor.prototype.init = function()
{
// Do stuff
var self = this;
$('.editables').each(function() {
$this = $(this);
// Here comes the error
self.notify($this.html());
});
};

OOP jQuery Plugin elem undefined in one place and not in others

How is my element undefined if I clearly define it here. It works in all the other methods but with exception of setUpCalendar(). I've included the "path" the code goes through before reaching the problematic part.
var Calendar = {
init: function(options, elem) {
this.options = $.extend( {}, this.options, options );
this.elem = $(elem); //Clearly defined here and works elsewhere
this.getFeed();
return this;
},
getFeed: function() {
var self = Calendar;
$.jGFeed(this.options.feedUrl, function (feeds) {
if (!feeds) {
return false;
}
$.extend(self.entries, feeds.entries);
self.parseEntries();
}, 10);
},
parseEntries: function() {
//Rename to fit plugin requirements
for (var i = 0; i < Calendar.entries.length; i++) {
var entry = Calendar.entries[i];
entry["allDay"] = false;
//Rename
entry["url"] = entry["link"];
delete entry["link"];
};
this.setUpCalendar();
},
setUpCalendar: function() {
Calendar.elem.fullCalendar({ //It's telling me Calendar.elem is undefined here
editable: false,
weekends: false,
events: Calendar.entries //funny story, here the reference works
});
}
};
UPDATE:
$.fn.ksdCalendar = function( options ) {
if (this.length) {
return this.each(function() {
var myCalendar = Object.create(Calendar);
myCalendar.init(options, this);
$.data(this, 'ksdCalendar', myCalendar);
});
}
};
$("#calendar").ksdCalendar({
feedUrl: "http://www.kent.k12.wa.us/site/RSS.aspx?DomainID=275&ModuleInstanceID=4937&PageID=4334"
});
So the problem here is that you're mixing your this references and Calendar references. When you call this.getFeed() you're operating on a newly created Calendar object. Within getFeed though, however, you're setting var self = Calendar and subsequently using self to set the values for entries and call parseEntries.
What you've done here is create a reference (with self) to the global Calendar object literal. That object is not the object that you've initialized via Object.create (and it's not the one you called init on).
What this all boils down to is, you've initialized myCalendar and set up the elem that it's attached to, but in your subsequent method calls you set entries on the global Calendar object literal. Inside your parseEntries method, all of this becomes apparent because the current scope is inside that of the global Calendar object literal and not the instance of the Calendar object created and returned by Object.create.
To fix it, do var self = this; instead of var self = Calendar;. Within parseEntries reference this.entries instead of Calendar.entries, and in setUpCalendar reference this.entries and this.elem instead of Calendar.entries and Calendar.elem repsectively.
In init() you're adding elem to this, that is myCalendar. In setupCalendar() you're trying to retrieve it from Calendar which doesn't seem to have the elem property (unless it's in a different portion of the code) even tho' it's the constructor function prototype for myCalendar.

Problems accessing this.element inside jQuery plugin extension

I'm trying to extend upon an existing jQuery plugin and I've run into some problem, that has been discussed before, unfortunately not perfectly related to my case. Basically, my code does something like this
(function($) {
var self = $.ech.multiselect.prototype,
orig_create = self._create,
orig_init = self._init;
var extensionMethods = {
elemental: null,
tooltip: function(e){
var id = self.elemental.attr("id");
//code
},
_create: function() {
var ret = orig_create.apply(this, arguments);
//code
return ret;
},
_init: function() {
var ret = orig_init.apply(this, arguments);
this.elemental = this.element;
return ret;
},
_bindEvents: function() {
self.tooltip(e);
}
};
$.extend(true, self.options, {tooltip: true});
$.extend(true, self, extensionMethods);
})(jQuery);
My problem is I'm trying to store a reference to this.element inside the "elemental" object, so that I can use it to get the id of the control inside tooltip(). It doesn't work, however and I've almost given up.
You're using self, which is the prototype object itself. You want this, which refers to the instance and has the correct property:
tooltip: function(e){
var id = this.elemental.attr("id");
},
and:
_bindEvents: function() {
this.tooltip(e);
}
The elemental in the prototype should never be set, because the elements differ per instance.

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: ... });

Mootools class variable scope

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 ++;
}
}
});
}
...

Categories

Resources