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
Related
I am attempting to move away from spaghetti code in my jQuery plugins and work in a more structured manner.
I am doing so by using the basic boilerplate template found here: https://github.com/jquery-boilerplate/jquery-patterns/blob/master/patterns/jquery.basic.plugin-boilerplate.js
I also found the ToDo MVC plain jQuery code a source of inspiration because it doesn't chain many functions and keeps things nicely separated (I am not allowed to post more than two links due to my lack of status, so you will have to Google that yourself).
All in all I find a combination of the two above quite good to work with, but there seems to be no way to work like this without referencing this (the root plugin object) quite often. This in itself is not an issue, but sometimes this becomes out of scope. I haven't really found an elegant way around it, especially when binding event handlers via jQuery on.(). Please see my JSFiddle for an illustration of what I mean: http://jsfiddle.net/hendrikdegraaf/wzp3ok4h/18/
I find that passing the plugin object in the event.data object as event.data.base a bit awkward (it's just very verbose and harms readability of my code). Is there not a better way to reference the main plugin object?
I've had issues with variable scope before when using this way of structuring my plugin, so any general advice on how to structure my plugins in a sensible way would be appreciated as well.
Thanks,
Hendrik
EDIT: Please see my code below
;(function ( $, window, document, undefined ) {
// Create the defaults once
var pluginName = "myplugin",
defaults = {
settingA : 'someValue',
settingB : 'someValue'
};
// The actual plugin constructor
function Plugin(params) {
params.options = $.extend( {}, defaults, params.options);
this.params = params;
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function () {
var base = this; // cache the context so we can still reference it when entering new context
this.cacheElements();
this.bindEvents(base);
},
cacheElements : function (base) { //cache elements
this.$el = $('div.doSomething');
},
bindEvents : function (base) { // bind some touch functions to touch events, and modal window hidden event
this.$el.on('click', {base: base}, this.myFunction);
},
myFunction : function () {
console.log(this); //this now refers to $el
}
};
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[pluginName] = function ( params ) {
return this.each(function () {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName,
new Plugin(params));
}
});
};
})( jQuery, window, document );
I recently ran into this issue, and I found a few work-arounds.
JavaScript's bind works in modern browsers:
bindEvents : function (base) {
this.$el.on('click', {base: base}, this.myFunction.bind(this));
},
myFunction : function () {
console.log(this); // Plugin context
}
jQuery's proxy function works in older browsers:
bindEvents : function (base) {
this.$el.on('click', {base: base}, $.proxy(this.myFunction, this));
},
myFunction : function () {
console.log(this); // Plugin context
}
You can also create the event handler function inside your bindEvents function, and give yourself access to both contexts:
bindEvents : function (base) {
var instance = this;
var myFunction = function() {
console.log(this); // element context
console.log(instance); // Plugin context
};
this.$el.on('click', {base: base}, myFunction);
}
I'm creating a jQuery plugin using the following boilerplate:
https://github.com/jquery-boilerplate/jquery-boilerplate/blob/master/src/jquery.boilerplate.js
My question is, if I have additional public methods in my plugin, how do I access the plugin's settings (and other variables) from within that public method?
;(function ( $, window, document, undefined ) {
// Create the defaults once
var pluginName = "myplugin",
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();
}
var body = $('body');
Plugin.prototype = {
init: function () {
// Init code here
},
yourOtherFunction: function () {
// This is a private method
}
};
// Toggle menu opening
$.fn.doSomething = function(){
// How do I access the plugin's settings here? <<<<<<< Here is the issue
};
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[ pluginName ] = function ( options ) {
this.each(function() {
if ( !$.data( this, "plugin_" + pluginName ) ) {
$.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
}
});
// chain jQuery functions
return this;
};
})( jQuery, window, document );
So, after instaniating my plugin using $('.myelement').myplugin(); later I can do $('.myelement').doSomething(); and in that method, I need to be able to access the plugin's settings. How do I do that? this.settings didn't appear to work.
Is there a better alternative plugin boilerplate or is this one pretty standard?
That's a bad design. You are creating a completely new plugin. And there is no connection between myplugin and doSomething.
I would suggest you to do this:
$.fn[ pluginName ] = function ( options ) {
if(options == 'doSomething'){
// do something here or call some predefined function here
}
else{
this.each(function() {
if ( !$.data( this, "plugin_" + pluginName ) ) {
$.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
}
});
}
// chain jQuery functions
return this;
};
So, if you use the above construct, after calling $('.myelement').myplugin();, you can do:
$('.myelement').myplugin('doSomething'); //this is cool!
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);
}
I hope that you understand my english.
I try use Jquery pattern "Lighrweight" (http://coding.smashingmagazine.com/2011/10/11/essential-jquery-plugin-patterns/)
But I have a problem for call method and the scope of this.
I bind plugin on table :
$("#tableSurface0").flexTab();
$("#tableSurface1").flexTab();
My Jquery plugin :
;(function ( $, window, document, undefined ) {
var pluginName = 'flexTab',
defaults = {
wrapOverflowHeight : true
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
this.options = $.extend( {}, defaults, options) ;
this.init();
}
Plugin.prototype.init = function () {
$("th:not([noflex])", this.element).on("mousedown", menuContextuel);
};
menuContextuel = function(event)
{
//BUG
console.log( ?? ); // show this.element of constructor
}
// 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 );
So I can not call this.element in menuContextuel function without add data in event handler :
Plugin.prototype.init = function () {
$("th:not([noflex])", this.element).on("mousedown", { table:this.element }, menuContextuel);
};
...
menuContextuel = function(event)
{
console.log( event.data.table );
}
There-he has another solution ?
Thank you
It is because the function is being called and the "this" is the element that is clicked on. Use can use jQuery's proxy so maintain the scope of "this" that you are after.
$("th:not([noflex])", this.element).on("mousedown", $.proxy(menuContextuel,this));
I've been using the below boilerplate for jQuery Plugin development which has been working great. But I'm not sure of the best way to make a "method" public.
;(function ( $, window, undefined ) {
var pluginName = 'defaultPluginName',
document = window.document,
defaults = {
propertyName: "value"
};
function Plugin( element, options ) {
this.element = element;
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype.init = function () {
};
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
}
});
}
}(jQuery, window));
Thanks
Only functions can be private / local, not a jQuery Plugin.
If you are using jQuery and trying to add your own (custom) function in jQuery, there are a best way jQuery.fn.yourFunctionname = function(){}, since jQuery is already in public scope so jQuery.fn.yourFunctionname is a public scope (no matter where you define your Plugin , will available for global use).
window.jQuery = jQuery;
$.fn.pluginName = function ( options ) {
return this.each(function () {
// code goes here
});
}