How to create a Jquery Plugin with Plugin Methods and Maintaining Chainability? - javascript

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

Related

Sharing data between jQuery methods

How could a variable be shared between two different jQuery methods (i.e. init and someMethod)? I am doing so below using jQuery data(), however, expect there is a more efficient non-jQuery way of doing so.
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
$(this).data('myData',123);
});
},
someMethod : function() {
return $(this).each(function(){
console.log($(this).data('myData'))
})
},
};
$.fn.myPlugin = function(method) {
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.myPlugin' );
}
};
}( jQuery ));
You can create an object that will stores everything and set this object to the element data, so you won't need to create it twice for the same element.
;(function($, undefined){
"use strict"; // Put this instruction to avoid some javascript quirks mode...
var defaults = {
myData: undefined,
init: _init,
someMethod: _someMethod
}
$.fn.myPlugin = function(method) {
var response = this;
this.each(function() {
var $this = $(this);
var mp = $this.data('plugin_myplugin');
if (!mp)
$this.data('plugin_myplugin', new MyPlugin($this, method));
else if (typeof method === 'string' && method.length > 0)
response = mp[method].apply(mp, Array.prototype.slice.call(arguments, 1));
else
$.error( 'Method ' + method + ' does not exist on jQuery.myPlugin' );
});
return response;
};
$.fn.MyPlugin = function() {
return $(this).data('plugin_myplugin');
};
function MyPlugin($element, options) {
this.$element = $element;
$.extend(true, this, $.fn.MyPlugin.defaults, $element.data(), options);
this.init();
}
$.fn.MyPlugin.defaults = defaults;
function _init() {
this.myData = 123;
}
function _someMethod() {
console.log(this.myData);
}
}(jQuery));
There are a couple important things here:
To override the defaults, you can simply do $.extend($.fn.MyPlugin.defaults, {someMethod: function() {alert(123);}});
Any instance can override some methods, e.g.:
<div id="div-test" data-some-value="123456">
$('#div-test').myPlugin({
someOtherValue: "asd",
someMethod: function() {alert(123); return "asdfgh"; }
});
var mp = $('#div-test').MyPlugin();
console.log(mp.someValue); // prints 123456
console.log(mp.someOtherValue); // prints "asd"
console.log(mp.someMethod); // prints that function up there.
I put the undefined as one of the parameter, but I didn't define any arguments at the bottom, that is because some old browser allows the undefined to be changed, so, we are forcing the undefined.

Extending a JQuery Plugin and JQuery at the same time

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.

How can I make a function available throughout a jQuery plugin and all its internal methods?

If I have a jQuery plugin using the normal standard of:
(function( $ ){
var methods = {
init : function( options ) {
var defaults = {
}
var options = $.extend(defaults, options);
return this.each(function(){
var returnValue = myUniversalFunction();
});
},
test : function( options ) {
var defaults = {
}
var options = $.extend(defaults, options);
return this.each(function(){
var returnValue = myUniversalFunction();
});
}
};
$.fn.jPlugin = function( method ) {
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.tooltip' );
}
};
})( jQuery );
Where would I put a function that could be accessed within both the init and test methods but not making it available to outside of the plugin itself?
Put it on line 2, just after (function( $ ){, like this:
(function( $ ){
var inner_function = function() {
// ...
};
var methods = {
// ...
};
$.fn.jPlugin = function( method ) {
// ...
};
})( jQuery );
The function inner_function will be available anywhere within (function($){ ... })(jQuery); but not outside it.
Right at the top. The function will be available to everything within that scope.
(function( $ ){
function myFunc() {
// ...
}

jQuery - plugin methods call

I'm trying to create a jQuery plugin following some official best practices
(function($){
var methods = {
init : function( options ) {
this.options = options;
}
, add_that: function (elem) {
this.append(elem);
return (this);
}
, add_this: function (elem) {
return (methods.add_that(elem));
}
};
$.fn.test = function (method) {
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' );
}
};
})(jQuery);
I'd like the method add_that to be able to append things to the matched element(s).
Now this method is called from add_this.
$('#test').test('add_this', $('<div />'));
TypeError: this.append is not a function
Why can't I access to the plugin (this) from add_that ?
Because the scope has changed when you've called it from add_this. Notice that the original call used Function.apply to scope the call to this.
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
}
So you can presumably get around the issue by using apply again in your method:
add_this: function (elem) {
return methods.add_that.apply(this,[elem]);
}
Live example: http://jsfiddle.net/XaUHV/
In the context you're using it "this" refers to methods.add_that
Since methods.add_that is a function there is no method on it called "append" by default.

Best jQuery Plugin Structure?

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.

Categories

Resources