jQuery plugin boiler plate - private method with bound scope? - javascript

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

Related

Store instance for usage in nested map/object

I have a really annoying scope issue for my JQuery Widget. Essentially I need to access the widget instance (this) inside of my map/object options.
Is it possible to do this? Any advice how I can achieve this?
$.widget( "my.myWidget", {
// Below 'this.defCallback' will be undefined
// How can I store 'this' (the widget instance) in a variable??
options: {
callback: this.defCallback // allow user to overwrite/provide custom callback
},
....
defCallback: function() {
console.log('defCallback');
}
});
If I had a nested function I know I can easily solve this but I have a nested object/map which makes things difficult.
function foo {
var _this = this;
...
var bar = function() {
// easily access this
_this.defCallback();
...
}
}
Usage:
$('<div></div>')
.myWidget(); // use defCallback
$('<div></div>')
.myWidget({
callback: function() {
...
}
}); // use custom callback
Edit: How the callback function is 'bound' and called:
_create: function() {
this.element.click( this.options.callback );
}
.click(value.callback(_this)
In javascript you could dynamically change the context of a function with the apply() and with call() methods.
On es5 you could use bind().
So your code:
_create: function() {
this.element.click( this.options.callback );
}
Became with apply():
_create: function() {
var el = this.element;
var callback = this.options.callback;
el.click(function() {
callback.apply(el);
// If you have parameters:
// callback.apply(el, arguments || array);
});
}
With call():
_create: function() {
var el = this.element;
var callback = this.options.callback;
el.click(function() {
callback.call(el);
// If you have parameters:
// callback.call(el, arg0, arg1, ...);
});
}
With bind():
_create: function() {
this.element.click(this.options.callback.bind(this));
}
UPDATE
As your issue is to have the this reference binded inside the object definition you need to change your code.
The quick way is is to emend it like this (from your fiddle):
var mw = {
defCallback: function () {
alert("abc");
},
_create: function () {
//this.populateOptions();
alert("Is undefined: " + this.options.isUndefined); // outputs 'true'
this.element.click(this.options.callback.bind(this));
},
populateOptions: function() {
if (this.options.callback === undefined)
this.options.callback = this.defCallback;
}
};
So you first define your object with the parent attributes and functions.
mw.options = {
//accessObjectParent: this.instantiator,
isUndefined: (mw.defCallback === undefined), // this refers to the map/object
// Can I access the maps 'parent'/instantiator?
// this.instantiator.defCallback ???
callback: mw.defCallback
};
Than you attach the options object and you could refer on the parent object instead of using this.
$.widget( "my.myWidget", mw );
And now you pass the object on your widget declaration.

How to use JQuery Proxy with the JQuery Boilerplate

I am currently in the process of writing a plugin using the JQuery Boilerplate. However I am having issues calling functions that are not called within the init() function. The boilerplate comments state that functions can be called via this.functionname() from within the init function but I'm unsure how to do this if they're called from elsewhere.
From looking at other questions on here it looks like I can use JQuery Proxy for this but can see no example of how to apply this to the boilerplate. Could someone show me, for instance how I could call this.createCarousel() or this.settings from within onEnterMobile please:
;(function ( $, window, document, undefined ) {
// Create the defaults once
var pluginName = "dcResponsiveCarousel",
defaults = {
itemsPerPageOnMobile: 1,
itemsPerPageOnTablet: 2,
itemsPerPageOnDesktop: 3
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
$element = $(element);
this.settings = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function() {
enquire
.register("screen and (max-width:767px)", this.onEnterMobile)
.register("screen and (min-width:768px) and (max-width:991px)", this.onEnterTablet)
.register("screen and (min-width:992px)", this.onEnterDesktop)
},
onEnterMobile: function (){
var deviceType = "mobile";
console.log(deviceType);
//this.createCarousel($element, itemsPerPage, deviceType);
}
}
};
// 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 );
OK after seeking some offline help on this issue I thought I'd post up an example of how JQuery Proxy can be used within JQuery Boilerplate:
init: function() {
var that = this;
enquire
.register("screen and (max-width:767px)", $.proxy(this.onEnterMobile,this))
.register("screen and (min-width:768px) and (max-width:991px)", $.proxy(this.onEnterTablet,this))
.register("screen and (min-width:992px)", $.proxy(this.onEnterDesktop,this))
},
So rather than just calling my callback function this.onEnterMobile I'm calling that function from within another "proxy" function which then allows me to pass through the value of the "this" keyword. This allows me to access my settings from any function in the plugin using this.settings or call any other function using this.functionName

Invoke javascript function from string

I have the following code in my javascript module, however this requires me to make the functions visible to the outside world.
var mymodule = function() {
var self = null,
init = function () {
self = this;
$('.actionButton').click(function () {
var worklistId = $(this).data('worklistid'),
action = $(this).data('action');
self[action] && self[action](worklistId); //watchout methods marked as not used are used by this invocation
})
},
send = function () {
// some logic
},
finish = function () {
// some logic
},
delete = function () {
// some logic
};
return {
init: init,
send: send,
finish: finish,
delete: delete
};
}();
mymodule.init();
So the only thing I want to return in my module is the init function. However when I do this I cant invoke the functions, because the object (self) only contains the init function visible on the outside.
return {
init: init
};
Is there any solution to invoke my functions like this without making them visible to the outside world? Please no if else statements, because my workflow is bigger then the 3 actions in this example. I want to make my module as closed as possible because this reduces the dependencies.
Update
Here is a updated jsfiddle with one of the proposed solutions, however this is giving me another issue. http://jsfiddle.net/marcofranssen/bU2Ke/
Something like this would work:
var mymodule = function() {
var self = this;
init = function () {
$('.actionButton').click(function () {
var worklistId = $(this).data('worklistid'), action = $(this).data('action');
self[action] && self[action](worklistId); //watchout methods marked as not used are used by this invocation
})
}
self.send = function () {
console.log('send');
}
self.finish = function () {
console.log('finish');
}
self.delete = function (item) {
console.log('delete');
};
return {
init: init,
};
}();
mymodule.init();​
Here's the fiddle:
http://jsfiddle.net/yngvebn/SRqN3/
By setting the self-variable to this, outside the init-function, and attaching the send, finish and delete functions to self, you can use the self[action] syntax from within the init-function
Yes, there is an easy (but perhaps slightly messy) way you can do this without making the functions visible to the global object:
var privateFunctions = { deleter: deleter, send: send};
Then, instead of self[action]();, just do privateFunctions[action](); and you're good to go.
Note that I changed delete to deleter, because delete is a reserved keyword...
var mymodule = function() {
var self = {},
init = function () {
$('.actionButton').click(function () {
var worklistId = $(this).data('worklistid'),
action = $(this).data('action');
self[action] && self[action](worklistId); //watchout methods marked as not used are used by this invocation
})
};
self.send = function () {
// some logic
};
self.finish = function () {
// some logic
};
self.delete = function () {
// some logic
};
return{
init:init
}
}();
mymodule.init();
This should Work!!
Even if you return an object just with the init property and you populate the rest dynamically such that your module uses them, you would still be making them visible to the outside at runtime. Anyone who wants to debug your module would easily get to them.
You can still create anonymous methods at runtime and they would also be visible together with their implementation.
In your code example, it is vague what "self" really is. You should keep it simple, use encapsulated functions as "private" methods and return a "public" (or "privileged" as Crockford calls it) function that have access to them.
This is the YUI way of doing singletons with private functions and variables. Example pattern:
var mymodule = (function() {
var internal = {
'send': function() {},
'finish': function() {},
'delete': function() {}
};
return {
'init': function(action) {
// access to internals, f.ex:
if ( internal.hasOwnProperty(action) ) {
internal[action].call(this); // bring the caller context
}
}
};
}());
mymodule.init('send');

Pass javascript function as parameter and evaluate its content as class content

Is it possible to pass a function as parameter to a method and evaluate it's content as if it was part of the class?
function Class()
{
this.Method = function( param )
{
// Call "CallThis"
};
this.CallThis = function()
{
};
}
var c = new Class();
c.Method(
{
evalThisContent: function()
{
this.CallThis();
}
}
);
If I follow your intention:
function Class()
{
this.Method = function( param, name )
{
this[name] = param;
param.call(this);
};
this.CallThis = function()
{
};
}
var c = new Class();
c.Method(function() {
this.CallThis();
}, 'evalThisContent');
It's certainly possible to invoke the function, in this case with
param()
as for "as if it was part of the class", if you mean would it have access to its members through this, no it wouldn't. But you could pass in a reference to this (object of type Class) to the function and it could access its members through this reference.
I've modified the class in the way I think you may have intended, though Zirak seems to have already demonstrated the main idea.
function Class() {
}
Class.prototype.method = function(param) {
if (typeof param === 'object' && param.evalThisContent) {
param.evalThisContent.call(this);
}
};
Class.prototype.callThis = function() {
alert("I'm getting called indirectly!");
};
var c = new Class();
c.method(
{
evalThisContent: function()
{
this.callThis();
}
}
);
If you wish, you could instead add or alter "evalThisContent" dynamically on the prototype, making it available to all objects which may henceforth wish to call it:
Class.prototype.method = function(param) {
if (typeof param === 'object' && param.evalThisContent && !Class.prototype.evalThisContent) {
Class.prototype.evalContent = param.evalThisContent;
}
this.evalContent();
};
This has the advantage of not creating a function into memory each time, nor invoking it in a less than optimal way with call, while call (or apply), as in the first example, has the more commonly useful advantage of allowing each instance of the Class to use its own functions or implementations (or you could use inheritance).

Is it possible to append functions to a JS class that have access to the class's private variables?

I have an existing class I need to convert so I can append functions like my_class.prototype.my_funcs.afucntion = function(){ alert(private_var);} after the main object definition. What's the best/easiest method for converting an existing class to use this method? Currently I have a JavaScript object constructed like this:
var my_class = function (){
var private_var = '';
var private_int = 0
var private_var2 = '';
[...]
var private_func1 = function(id) {
return document.getElementById(id);
};
var private_func2 = function(id) {
alert(id);
};
return{
public_func1: function(){
},
my_funcs: {
do_this: function{
},
do_that: function(){
}
}
}
}();
Unfortunately, currently, I need to dynamically add functions and methods to this object with PHP based on user selected settings, there could be no functions added or 50. This is making adding features very complicated because to add a my_class.my_funcs.afunction(); function, I have to add a PHP call inside the JS file so it can access the private variables, and it just makes everything so messy.
I want to be able to use the prototype method so I can clean out all of the PHP calls inside the main JS file.
Try declaring your "Class" like this:
var MyClass = function () {
// Private variables and functions
var privateVar = '',
privateNum = 0,
privateVar2 = '',
privateFn = function (arg) {
return arg + privateNum;
};
// Public variables and functions
this.publicVar = '';
this.publicNum = 0;
this.publicVar2 = '';
this.publicFn = function () {
return 'foo';
};
this.publicObject = {
'property': 'value',
'fn': function () {
return 'bar';
}
};
};
You can augment this object by adding properties to its prototype (but they won't be accessible unless you create an instance of this class)
MyClass.prototype.aFunction = function (arg1, arg2) {
return arg1 + arg2 + this.publicNum;
// Has access to public members of the current instance
};
Helpful?
Edit: Make sure you create an instance of MyClass or nothing will work properly.
// Correct
var instance = new MyClass();
instance.publicFn(); //-> 'foo'
// Incorrect
MyClass.publicFn(); //-> TypeError
Okay, so the way you're constructing a class is different than what I usually do, but I was able to get the below working:
var my_class = function() {
var fn = function() {
this.do_this = function() { alert("do this"); }
this.do_that = function() { alert("do that"); }
}
return {
public_func1: function() { alert("public func1"); },
fn: fn,
my_funcs: new fn()
}
}
var instance = new my_class();
instance.fn.prototype.do_something_else = function() {
alert("doing something else");
}
instance.my_funcs.do_something_else();
As to what's happening [Edited]:
I changed your my_funcs object to a private method 'fn'
I passed a reference to it to a similar name 'fn' in the return object instance so that you can prototype it.
I made my_funcs an instance of the private member fn so that it will be able to execute all of the fn methods
Hope it helps, - Kevin
Maybe I'm missing what it is you're trying to do, but can't you just assign the prototype to the instance once you create it? So, first create your prototype object:
proto = function(){
var proto_func = function() {
return 'new proto func';
};
return {proto_func: proto_func};
}();
Then use it:
instance = new my_class();
instance.prototype = proto;
alert(instance.prototype.proto_func());

Categories

Resources