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

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.

Related

Using Prototype with "Namespace" for existing object

I am looking to achieve something along the following.
HTMLSpanElement.prototype.testNS = {
_this: this,
testFunc: function() {
console.log(this.innerHTML) //undefined as expected as this is the testFunc object
},
testFunc2: function() {
console.log(this._this) //Window object
}
}
My goal is to add some helper functions directly to a span element in this case.
So, if I had the following:
<span>test</span>
I could find the span and call this code to return "test"
spanElement.testNS.testFunc()
I know that a function retains scope of it's parent when I do it like so...
HTMLSpanElement.prototype.testFunc = function() {
console.log(this.innerHTML)
}
But I am attempting to organize the code a bit and make it more obvious where the functions are coming from when I add them, and I can't seem to find a way to retain scope, when I do a normal JSON object grab the this scope into _this: this it just returns the global scope of "window".
Disclaimer: You shouldn't be trying to modify the prototypes on built-in types, especially host objects. It's a bad idea.
The reason your approach isn't working for you is that the functions are being called with the testNS object as the this.
You can get this to work if you define testNS as a property with a getter function, using Object.defineProperty. The reason this works is that the get function runs in the context of the object on which the property is being accessed (which would be the span):
Object.defineProperty(HTMLSpanElement.prototype, 'testNS', {
get: function() {
var _this = this;
return {
testFunc: function() {
console.log(_this.innerHTML)
},
testFunc2: function() {
console.log(_this)
}
}
}
});
var span = document.getElementById('mySpan');
span.testNS.testFunc();
span.testNS.testFunc2();
<span id="mySpan">Wah-hoo!</span>
A more "vanilla" approach is to just have testNS be a plain function and call it like one. This works because testNS is called in the context of the object on which it is being called (again, the span):
HTMLSpanElement.prototype.testNS = function() {
var _this = this;
return {
testFunc: function() {
console.log(_this.innerHTML)
},
testFunc2: function() {
console.log(_this)
}
}
}
var span = document.getElementById('mySpan');
span.testNS().testFunc();
span.testNS().testFunc2();
<span id="mySpan">Wah-hoo!</span>
When you call a function as foo.bar() then this inside bar refers to foo. Hence if you call the function as spanElement.testNS.testFunc(), this refers to spanElement.testNS.
_this: this, cannot work because this cannot refer to a <span> element.
To get access to spanElement from testFunc you could implement testNS as a getter:
Object.defineProperty(HTMLSpanElement.prototype, 'testNS', {
get: function() {
var element = this;
return {
testFunc: function() {
console.log(element.innerHTML);
},
};
},
});
document.querySelector('span').testNS.testFunc();
<span>foo</span>
Because it's a strange requirement I wrote a an equivalent strange solution :-)
Basically the createElement has been overriden in order to add a namespace object literal and then define a new function testFunc on top of the namespace using the instance of the element binded to the function
!function(){
var defaultNamespace = "testNS";
var createElement = document.createElement;
document.createElement = function(tag, namespace) {
var element = createElement.apply(document, arguments);
element[namespace || defaultNamespace] = {
testFunc : function() {
console.log(this.innerHTML);
}.bind(element)
};
return element;
}
}();
var span = document.createElement("span");

jQuery plugin boiler plate - private method with bound scope?

I've been looking at the plugin boiler plate for jQuery plugins, I find it ok but there is one major flaw in the design or maybe just something I can't figure out.
When I author plugins at the moment, it is easy for me to define publicly exposed methods and private methods that only the plugin has access to.
When I tried to do something similar in the boiler plate I was thwarted.
;(function ( $, window, document, undefined ) {
// Create the defaults once
var
pluginName = "defaultPluginName",
defaults = {
propertyName: "value"
};
// The actual plugin constructor
function Plugin ( element, options ) {
this.element = element;
this.settings = $.extend( {}, defaults, options );
this.defaults = defaults;
this.name = pluginName;
this.init();
}
Plugin.prototype.init = function() {
console.log('init')
console.log(this)
this.yourOtherFunction();
}
Plugin.prototype.yourOtherFunction = function () {
console.log('yourOtherFunction')
console.log(this)
this.yourOtherFunction2();
}
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction().bind(this)
}
var privateFunction = function() {
console.log('private')
console.log(this)
}
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[ pluginName ] = function ( options ) {
return this.each(function() {
if ( !$.data( this, "plugin_" + pluginName ) ) {
$.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
}
});
};
})( jQuery, window, document );
$(document).defaultPluginName()
Anyway you can see the function 'privateFunction' it's scope is to the window object, but what I want to be able to do is scope it to the Plugin instance, or basically 'this' from the prototype methods.
What I don't want to do, is pass the scope into each private function as a function argument!
So how can I bind the scope?
Console output
init
Plugin { element=document, settings={...}, defaults={...}, more...}
yourOtherFunction
Plugin { element=document, settings={...}, defaults={...}, more...}
private
Window index.html <-- I want Plugin, not window
You are calling privateFunction and then binding this as scope for its result. So use (as said by #Khanh_TO):
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction.apply(this,arguments);
}
Instead of:
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction().bind(this)
}
More details:
bind returns a copy of the function on which is called (the result of privateFunction in your case) after applying the scope you've passed in (this in your case). What bind does is something like:
Function.prototype.bind = function(scope) {
var _function = this;
var _args = [];
for (var i = 0, len = arguments.length-1; i < len; i++){ _args[i] = arguments[i+1]; }
return function() {
// returns the same function on which is called (not the same Function object, but
// another with same properties) with 'this' equal to the first parameter and
// the remaining specified parameters as parameters of the function returned
return _function.apply(scope, _args);
}
}
eg. myFunction.bind(newScope, param1, param2, ...) -> returns an anonymous function which in turns returns the function myFunction(param1, param2,....) with set this = newScope.
So, as a proof of concept, also this code would have worked:
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction.bind(this)();
}
but you should use the first one since the last one does the same thing with extra passages.
Replace:
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction().bind(this)
}
With
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction.apply(this,arguments);
}

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.

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.

Javascript function (type) to store & use data

I really never used a javascript function type or class before, I understand Java and Python, but not javascript. So, I build a class like this:
function FormStore (type) {
this.setup = () =>{
this.store = {};
this.ERR_LINE_PREFIX = '#err_';
this.NO_DISPLAY_CLASS = 'no-display';
this.settings = {
'myID':{'hide':false},
}
}
this.checkVal= () => {
var geoArr = ['id_xx','myID', (...)];
var id;
$.each( geoArr, function(val) {
id = geoArr[val];
console.log(this.store) //-> returns undefined, below line is error
if (!(this.store[id])) {
return false;
}
});
};
var FS = new FormStore();
FS.setup();
The store is filled by components on document.ready. There is a function that looks up if the aligned components (glyph, label, input) have some classes or values and for the specific component fills a dict: {label:false,glyph:false, input:false}. However, for some reason it doesn't matter. Even if I enter some values in to the store right away (in setup) or create them on the fly, in checkVal the store doesn't exist, it's undefined.
Please, anybody, what am I not understanding about javascript type and classes here? I am googling this a lot and trying to find good resources but, "javascipt variable class" (or type) just yields a lot of DOM manipulation.
edit
There is a context problem in checkVal, you are using a non-arrow (and not explicitly bound) callback function and trying to access this inside of it. Change that to an arrow function as well, and the parent context (this) will be preserved:
$.each( geoArr, (val) => {
id = geoArr[val];
console.log(this.store)
if (!(this.store[id])) {
return false;
}
});
And while you are at changing that section, it's not going to work. You will not get access to $.each's return value. You should rely on native array APIs for this task and use Array.every to determine if all geoArr items are in the store (assuming that's your goal):
// returns false if not all geoArr items are in the store
geoArr.every(id => this.store[id])
original
I don't see you calling checkVal() anywhere, but based on the error you are getting it is called prior to setup() (since setup initializes the store). You could solve that problem straight away by moving this.store = {} out of setup (right at the top), e.g.:
function FormStore(type) {
this.store = {};
...
Having said that, I would suggest either defining your methods on the prototype, or utilizing ES6 classes. Here is a simplified version of both:
ES5 class
function FormStore(type) {
// make sure user didn't forget new keyword
if (this === window) {
throw new Error('FormStore must be called with "new" keyword')
}
// initialize state, this is the constructor
this.type = type;
this.store = {};
// any other state the class manages
}
FormStore.prototype = {
setup: function() {
// do setup stuff
// "this" points to instance
console.log('setup', this.type)
},
checkVal: function() {
}
}
var formStore = new FormStore('foo')
console.log(formStore.store) // <-- not undefined
formStore.setup()
ES6 Class
class FormStore {
constructor(type) {
this.type = type;
this.store = {};
}
setup() {
console.log('setup', this.type)
}
checkVal() {
}
}
const formStore = new FormStore('bar')
console.log(formStore.store) // <-- not undefined
formStore.setup()
It has to do with scoping. Your $.each in checkVal has a normal function. Inside the function the scope if this is different. If you want to keep the original scope you could use a fat arrow function like you do when defining the methods.
this.checkVal= () => {
var geoArr = ['id_xx','myID', (...)];
var id;
$.each( geoArr, val => {
id = geoArr[val];
console.log(this.store) //-> returns undefined, below line is error
if (!(this.store[id])) {
return false;
}
});
}
When you run your original code and place a breakpoint on the line with console.log you can see in the inspector that this is set to the Window object and no longer points to your FormStore.
function FormStore () {
this.setup = function(){
this.store = {};
this.ERR_LINE_PREFIX = '#err_';
this.NO_DISPLAY_CLASS = 'no-display';
this.settings = {
'myID':{'hide':false},
}
}
this.checkVal= function(){
var geoArr = ['id_xx','myID'];
var id;
$.each( geoArr, function(val) {
id = geoArr[val];
console.log(this.store) //-> returns undefined, below line is error
if (!(this.store[id])) {
return false;
}
});
}
};
var FS = new FormStore();
FS.setup();
Works absolutely fine, the code you provided had a missing bracket and you were using some broken es6 syntax

Categories

Resources