Problems accessing this.element inside jQuery plugin extension - javascript

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.

Related

Wrap static function (IIFE) into prototype

I wrote a function that I thought I'd only need to process 1 object but it turns out I need more than 1. I will use a simple example:
var Helper = (function () {
return {
el: null
init: function(el) {
this.el = el;
}
doStuff: function(){
// Modify this.el in someway
}
};
}());
So then I'd just do Helper.init(el) on page load and then run Helper.doStuff() when I needed it.
Well now I have three elements who need this functionality.
My first idea was to just make it do Helper.init([el1,el2,el3]) and have it work on an array of elements but I may want to treat each element separately.
I am thinking the best approach is to probably turn the Helper IIFE into a "class" with prototype, but I am a bit crunch on time so I was looking for a way to make a wrapper to accomplish what I need.
I was thinking I can just take the function and not immediately execute it, and then somehow store that function into a prototyped function and utilize it that way.
Looking for ideas on how to best do this with minimal code change.
I am thinking the best approach is to probably turn the Helper IIFE into a "class" with prototype, but I am a bit crunch on time...
I wouldn't expect it to take very long.
I was thinking I can just take the function and not immediately execute it, and then somehow store that function into a prototyped function and utilize it that way.
Looking for ideas on how to best do this with minimal code change.
The class pattern is just one pattern in JavaScript, you can use that Helper just like it is as the prototype of other objects, which matches your "minimal changes" requirement. Just use Object.create:
var helper1 = Object.create(Helper);
helper1.init(el);
var helper2 = Object.create(Helper);
helper2.init(el2);
var helper3 = Object.create(Helper);
helper3.init(el3);
// ...
helper1.doStuff();
helper2.doStuff();
helper3.doStuff();
If you add return this; to the end of init, that can be more concise:
var helper1 = Object.create(Helper).init(el);
var helper2 = Object.create(Helper).init(el2);
var helper3 = Object.create(Helper).init(el3);
// ...
helper1.doStuff();
helper2.doStuff();
helper3.doStuff();
Live Example:
var Helper = (function () {
return {
el: null,
init: function(el) {
this.el = el;
return this;
},
doStuff: function(){
this.el.style.color = "green";
this.el.style.fontWeight = "bold";
}
};
}());
var helper1 = Object.create(Helper).init(document.getElementById("el1"));
var helper2 = Object.create(Helper).init(document.getElementById("el2"));
var helper3 = Object.create(Helper).init(document.getElementById("el3"));
// ...
setTimeout(function() {
helper1.doStuff();
}, 400);
setTimeout(function() {
helper2.doStuff();
}, 800);
setTimeout(function() {
helper3.doStuff();
}, 1200);
<div id="el1">el1</div>
<div id="el2">el2</div>
<div id="el3">el3</div>
You could even keep using Helper directly on the first el, further reducing code changes, though I wouldn't recommend it.
Alternately, wrap it in a function that returns it (and here I've also included that change to init):
function getHelper() {
var Helper = (function () {
return {
el: null,
init: function(el) {
this.el = el;
return this; // <============== Added
},
doStuff: function(){
// Modify this.el in someway
}
};
}());
return Helper;
}
Then for your three places you need it:
var helper1 = getHelper().init(el);
var helper2 = getHelper().init(el2);
var helper3 = getHelper().init(el2);
// ...
helper1.doStuff();
helper2.doStuff();
helper3.doStuff();
Side note: You don't need the IIFE there anyway unless you have things in it that aren't shown beyond the object initializer...
Just rewritten code:
function Helper (el) {
this.el = el;
}
Helper.prototype = {
doStuff: function(){
// Modify this.el in someway
}
};
var helper1 = new Helper(el1);
var helper2 = new Helper(el2);
var helper3 = new Helper(el3);
helper1.doStaff();
helper2.doStaff();
helper3.doStaff();
Another way, retrieves arguments from arguments object:
var Helper = (function () {
return {
el: null
init: function() {
this.el = Array.from(arguments)
}
doStuff: function(){
this.el.forEach(el => {
// Modify el in someway
});
}
};
}());

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
}
}

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

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.

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.

Categories

Resources