I am trying to write a good jQuery Plugin structure. I am trying to follow "best practices" from jQuery.com and others.
But I am little bit confused about prototype.
Should I use it or not? And Is the actual structure looks good or terrible?
Thanks !
(function( $ ){
var defaults = { /* ... */ },
publicMethods = {
add: function(options){
var $this = $(this);
// ...
return $this;
}
},
privateMethods = {
init: function(options) {
var $this = $(this);
return $this;
},
click: function() {
//...
}
};
$.fn.tooltip = function(method) {
var args = arguments;
$(this).each(function() {
if ( publicMethods[method] ) {
return publicMethods[ method ].apply( this, Array.prototype.slice.call( args, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return privateMethods.init.apply( this, args );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
});
};
})( jQuery );
With respect to using the .prototype of the tooltip function, that would be useful only if the tooltip function is going to be invoked as a constructor.
Typically as a jQuery plugin, you're only interested in using the object created from the jQuery constructor, rather than creating your own objects from the plugin function.
There may be occasion to use a constructor somewhere internally in your plugin code, but it typically wouldn't be the actual plugin function itself.
Related
Let’s say I have following simple plugin:
(function ( $ ) {
$.fn.greenify = function() {
this.css( "color", "green" );
return this;
};
}( jQuery ));
I don’t want to make any changes to the plugin itself, but I would like to wrap with another function and extend its namespace as follow:
(function($) {
$.fn.myFunction = function () {
(function ($) {
$.fn.greenify = function () {
this.css("color", "green");
return this;
};
}(jQuery));
};
})(jQuery);
So I can call the plugin function like this:
$(selector). myFunction().greenify();
Basically I want to disable calling ‘greenify’ function directly.
Is it possible?
It's not clear from the question, but I'm assuming the simple plugin "greenify" is a third-party or other "forced to use" plugin that you, for whatever reason, can't change. Let's also assume that it's actually quite a complicated plugin and simplified for the sake of the question.
This means
you can't change it
you can't duplicate the entire plugin inside your wrapper
The usual method for overwriting something is to take a copy, then make the new version do what you want, possibly calling the old version, eg:
var oldfoo = foo;
foo = function() {
alert("foo called");
oldfoo(); // or oldfoo.apply(this) to be clearer
}
The same principle can be applied here, but instead make 'foo' (in the example above) null - to get rid of it, eg:
var oldfoo = foo;
newfoo = function() {
alert("foo called");
oldfoo(); // or oldfoo.apply(this) to be clearer
}
foo = null;
The complication is with jquery and wanting to keep the method chaining, which can be achieved by storing 'this' and applying it as desired.
Here's the full code with explanation comments:
// The original plugin to be wrapped
(function ( $ ) {
$.fn.greenify = function() {
// changed to background-color for more impact (demo purposes)
this.css( "background-color", "lightgreen" );
return this;
};
}( jQuery ));
(function($) {
// allow this to be referred to later
// inside another loop where 'this' is something else
var me = this;
// take a copy of the original
// this stays inside the scope of (function($)) so can't be seen outside
me.original_greeny = $.fn.greenify;
// provide a wrapper
$.fn.myFunction = function () {
// the jquery elements for applying later
var $this = $(this)
// exported function
return {
greenify: function() {
// Call the original function with the jquery elements
// and return them for chaining
return me.original_greeny.apply($this)
}
};
};
})(jQuery);
// Now remove the original completely
(function ( $ ) {
$.fn.greenify = null;
}(jQuery));
// As desired, also demonstrating chaining still works
$("#a").myFunction().greenify().css("font-style", "italic")
// Confirm that the original has been removed
// gives error $(...).greenify is not a function
try {
$("#a").greenify()
} catch(e) {
$("#a").text("error on $().greenify: " + e)
}
and a jsfiddle
If you want to create your own context, one way is to return an object with a set of functions:
(function($) {
$.fn.myFunction = function () {
// Cache jQuery object
var $this = this;
// Return interface that acts on jQuery object
return {
greenify: function () {
$this.css("color", "green");
return $this;
}
};
};
})(jQuery);
Then you could call it using:
$(selector).myFunction().greenify();
Fiddle
Edit: As a warning though, when you do this you are leaving the jQuery chaining context after calling .myFunction, which can be very confusing in code.
You definitely don't want to take the current shown approach, because every time myFunction is called, it will also assign greenify. Simply take your myFunction plugin, assign a flag to the jQuery object that was constructed, and then check for that flag in greenify.
(function($) {
$.fn.myFunction = function () {
this.greenMarker = true;
return this;
};
$.fn.greenify = function () {
if(!this.greenMarker) return this;
this.css("color", "green");
return this;
};
})(jQuery);
$('.f').greenify();
$('.g').myFunction().greenify();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="f">No Green</div>
<div class="g">Green Me</div>
I think you're talking about method chaining which is the beauty of jQuery methods. Define your new method as follows, but leave greenify() unchanged:
(function($) {
$.fn.myFunction = function () {
return this.each(function() {
//Do stuff
});
};
})(jQuery);
(function ( $ ) {
$.fn.greenify = function() {
this.css( "color", "green" );
return this;
};
}( jQuery ));
(function($) {
$.fn.myFunction = function () {
return this.each(function() {
$(this).css({border:'1px solid black',textAlign:'center'});
});
};
})(jQuery);
$('.box').myFunction().greenify();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="box">COLOR CHANGE</div>
Pretty sure so long as your myFunction returns this, that you should be able to chain whatever you want to it.
(function ( $ ) {
$.fn.greenify = function() {
this.css( "color", "green" );
return this;
};
}( jQuery ));
(function($) {
$.fn.myFunction = function () {
// Do some stuff
return this;
};
})(jQuery);
$(selector).myFunction().greenify();
EDIT: If the whole point is to "disable calling greenify directly", why would you extend jQuery in the first place? 'greenify could be nested and triggered by passing an argument of some kind. Something like:
(function($) {
$.fn.myFunction = function (options) {
// Do some stuff
if (options.greenify) {
this.css("color", "green");
}
return this;
};
})(jQuery);
$(selector).myFunction({greenify: true});
... or you could just define greenify as a function (instead of a plugin) and call it. But the point of defining plugins is so they can then be called globally.
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'm building a Jquery Plugin. My skeleton code is as below:
(function (window, document, $, undefined) {
var methods = {
init : function(options){
},
func_1: function(){
},
func_2: function(){
}
};
$.fn.myplugin = function(args){
if ( methods[args] )
{
return methods[ args ].apply( this, Array.prototype.slice.call( arguments, 1 ));
}
else if ( typeof args === 'object' || ! args )
{
var opts = $.extend({}, $.fn.myplugin.defaults, args);
var new_args = new Array(opts);
return methods.init.apply( this, new_args );
}
else
{
$.error( 'Method ' + args + ' does not exist' );
}
};
$.fn.myplugin.defaults = {
func_1: function(){},
func_2: function(){}
};
}(window, document, jQuery));
I'm looking to extend this plugin so that I may add additional functions to JQuery. So, I want to have these functions called as below:
$.myplugin.new_func();
How do I do this? I know I probably have to use $.extend but not sure how to go about it.
Thanks in advance.
The difference between $.fn.myplugin and $.myplugin is that the latter doesn't have any context. So you can define the latter using the following code. In order to use chaining on the result of $.myplugin you just need to return an object that you would like to use the .new_func() method on, for example a certain jQuery object.
$.myplugin = function () {
...
return $('body');
};
Ok, after going through some older JQuery plugins (FancyBox 2 especially), I managed to figure out a way to do this. Below is the entire skeleton code:
(function (window, document, $, undefined) {
var methods = {
init : function(options){
},
func_1: function(){
},
func_2: function(){
}
};
$.fn.myplugin = function(args){
if ( methods[args] )
{
return methods[ args ].apply( this, Array.prototype.slice.call( arguments, 1 ));
}
else if ( typeof args === 'object' || ! args )
{
var opts = $.extend({}, $.fn.myplugin.defaults, args);
var new_args = new Array(opts);
return methods.init.apply( this, new_args );
}
else
{
$.error( 'Method ' + args + ' does not exist' );
}
};
$.fn.myplugin.defaults = {
func_1: function(){},
func_2: function(){}
};
//below is the code I added to get the desired functionality
var D = $.myplugin = function(){};
$.extend(D, {
new_func: function(){
//add functionality here
}
});
}(window, document, jQuery));
It is also possible to define a function (called new_func or whatever) inside methods object, and then call it down below from your new function using methods.new_func().
Cheers.
I am trying to create a Jquery plugin that maintains chainability and has public methods as specified in Jquery Plugins/Authoring . The complexity is that it is trying to maintain certain vars that I want the public methods to use.
This is my jsfiddle : http://jsfiddle.net/badmash69/9cqcj/2/
javascript code :
(function($){
var methods = {
init : function( options ) {
this.options = options;
}
, add_that: function (elem) {
$(this).append(elem);
return (this);
}
, show_parent: function(){
// this is a simple test to see if the plugin vars are accessible
alert("parent id=" + $(this).parentId)
}
, add_this: function (elem) {
return methods.add_that.apply(this,elem);
}
};
$.fn.test = function (method) {
var args = method;
var argss = Array.prototype.slice.call(args, 1);
return this.each(function(){
var $this = $(this);
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.test' );
}
var element = $(this);
var parentId= element.parent().attr("id")
});
};
})(jQuery);
$('#test').test('add_this',$('<div>Hello World d</div>'));
$('#test').test('show_parent');
Html Code
<div id="holder">
<div id="test"></div>
</div>
I cant figure out what I am doping wrong here .
How can I make it work ? I would deeply appreciate any help .
the way that I do this is using the $.data, you can have specific object local vars, "public"/"private" methods, etc. here goes an small example in how I will do it
(function($){
var myTestMethods = function() {
// local variables
var last_added;
// local "private" methods
var init=function(options) {
this.options = options;
last_added = null;
return this;
};
var add_that=function(elem) {
last_added = elem;
this.append(elem);
return this;
};
var show_parent=function() {
alert("parent id=" + this.parent().attr('id'));
}
return { // this are your obj "public" methods
// notice we are not listing add_that method, therefore this method will be a "private" method
init : init,
show_parent: show_parent, // you can publish a private method
get_last_added: function(){
return last_added; // you can access local variables
},
add_this: function (elem) {
return add_that.apply(this, elem); // you can also run local methods
}
}
};
$.fn.test = function (method) {
var obj_data = this.data('myTestData');
if (typeof(obj_data) != "undefined") {
if ( obj_data[method] ) {
return obj_data[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
}else {
$.error( 'Method ' + method + ' does not exist on jQuery.test' );
}
} else {
if (typeof(method) === 'object' || ! method) {
obj_data = myTestMethods();
this.data('myTestData', obj_data);
return obj_data.init.apply(this, arguments);
}
}
};
})(jQuery);
$('#test').test(); //init
$('#test').test('add_this',$('<div>Hello World d</div>'));
$('#test').test('show_parent');
this code has small tests so there may be small bugs, but this will show you the basic idea in how to do what you want.
Take a look at this demo: http://jsfiddle.net/9cqcj/11/
As they suggest, to keep data you should better use .data:
return this.each(function(){
var $this = $(this);
$this.data("parentId",$this.parent().attr("id"));
....
(assuming that you need parentId of each element in set)
Also, you have a problem with calling your methods:
return this.each(function(){
var $this = $(this);
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
Last line, arguments - arguments of function passed to .each is used. In order to get original arguments save them into variable before calling a method:
$.fn.test = function (method) {
var args = arguments;
return this.each(function(){
var $this = $(this);
$this.data("parentId",$this.parent().attr("id"));
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( args , 1 ));
See arguments replaced with args in last line.
Also, when you are using .apply, second parameter should be an array:
return methods.add_that.apply(this, [elem]);
In case like this:
return methods.add_that.apply(this, elem);
You can get unexpected problems. For instance, try to replace elem with simple string "test" and see what you will get in console. Or if you will pass jQuery object, you will get DOM object in called method