Create jQuery plugin with configuration settings - javascript

I've been using the following design patter to create jQuery plugins. I'm pretty sure I got the concept from the jQuery homepage, however, it appears to no longer be published there.
I recently attempted to access the settings variable within a method (i.e. someOtherMethod ()) other than the init() method, and experienced an error as settings was not defined. I see the cause as settings is isolated to the init() method.
If I move settings outside of this method, I could then access it from different methods, however, each instance when the plugin is applied will not have its unique settings variable which is unacceptable. For instance, $('#id1, #id2').myPlugin({x:111}); should have a common settings variable, however $('#id1').myPlugin({x:111}); $('#id2').myPlugin({x:222}); should each have their unique settings variable.
Given the below design pattern as a starting point, how can I access the settings variable from all methods associated with the plugin, yet have a unique settings variable each time the plugin is applied?
(function( $ ){
var defaults={
x : 123,
y : 321
};
// var settings={}; //Should settings be defined here???
var methods = {
init : function( options ) {
var settings = $.extend(defaults, options || {});
//settings = $.extend(defaults, options || {}); //Should settings just be updated and not defined here?
return this.each(function(){
//whatever
});
},
someOtherMethod : function() {
return $(this).each(function(){
//do whatever and use settings variable as applicable
})
},
};
$.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 ));
$('#id1, #id2').myPlugin({x:111}); //Sets x=111 in settings for both
$('#id3').myPlugin({x:333}); //Sets x=333 in settings.
$('#id3').myPlugin('someOtherMethod'); //Will have access to x=333 in settings.

You're going to want to save the settings object per-element so that the settings will persist across different selectors. The best way to do this is to use jQuery.data to attach a settings object to the element. This way, the settings will persist each time the element is selected, regardless of how it is selected.
Then, in the .each call of someOtherMethod, you can access this data using jQuery.data on the element.
Also, each individual element is going to need a separate settings object to avoid overwriting shared settings, so this:
var settings = $.extend(defaults, options || {});
Will need to be replaced with this:
var settings = $.extend({}, defaults, options || {});
Otherwise the defaults object will be overwritten each time with new settings properties, and shared among all the elements.
In this example, I have created a variable name internalPrefix with the value of '_myPlugin' for the key under which to save the data using jQuery.data. I've added some tests at the bottom to show how it can be initialized on different ways, but the method can be called and still be aware of the settings used to initialize on the element.
#Working Example:
(function( $ ){
var defaults={
x : 123,
y : 321
};
//A variable to save the setting data under.
var internalPrefix = '_myPlugin';
var methods = {
init : function( options ) {
return this.each(function() {
//Setup the settings object.
var settings = $.extend({}, defaults, options || {});
//Save the settings to the element.
$(this).data(internalPrefix, settings);
});
},
someOtherMethod : function() {
return this.each(function() {
//Get the existing settings.
var settings = $(this).data(internalPrefix);
//Example:
$('<code></code>').text(JSON.stringify(settings)).appendTo(this);
})
},
};
$.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 ));
//Initialize the plugin different ways.
$('.group-1').myPlugin();
$('.group-2').myPlugin({
x : 42,
y : 1337
});
//Cal the methods on those different ways.
$('p').myPlugin('someOtherMethod');
<p class="group-1">group 1</p>
<p class="group-1">group 1</p>
<p class="group-2">group 2</p>
<p class="group-2">group 2</p>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Updated, Added {} as first argument to $.extend()
If, however, you want to preserve both of the original objects, you
can do so by passing an empty object as the target:
var object = $.extend({}, object1, object2);
Try setting defaults as property of methods , defining settings within init
var settings = $.extend({}, methods.defaults, options || {});
setting this element .data() with settings before call to .each()
return this.data(settings).each(function() {})
settings would then be unique for each element ; accessible utilizing $(this).data() , returning defaults if options not passed as argument to $.fn.myPlugin, updating defaults if options passed as argument to $.fn.myPlugin
(function($) {
var defaults = {
x: 123,
y: 321
};
var methods = {
init: function(options) {
// extend `methods.defaults` with `options`
var settings = $.extend({}, methods.defaults, options || {});
return this.data(settings).each(function() {
//whatever
console.log("init", this.id, $(this).data());
});
},
someOtherMethod: function() {
return $(this).each(function() {
//do whatever and use settings variable as applicable
console.log("someOtherMethod", this.id, $(this).data());
})
},
};
// set `defaults` as property of `methods`
methods.defaults = defaults;
$.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));
$('#id1, #id2').myPlugin({
x: 111
});
$("#id1").myPlugin({
y: 222
});
$("#id2").myPlugin({
y: 333
});
$("#id2").myPlugin("someOtherMethod");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<div id="id1"></div>
<div id="id2"></div>
jsfiddle http://jsfiddle.net/z28wwnLh/1/

Just a quick stab but won't this work? or did i miss the point entirely?
(function ($) {
var defaults = {
x: 123,
y: 321
};
$.fn.myPlugin = {
init: function (opts) {
var settings = $.extend(defaults, opts || {});
$.each(settings, function (a, b) {
console.log(a, ':', b);
});
},
someOtherMethod: function (opts) {
var settings = $.extend(defaults, opts || {});
$.each(settings, function (a,b) {
console.log(a,':', b);
});
}
};
}(jQuery));
$('#id1, #id2').myPlugin.init({ x: 111 }); //Sets x=111 in settings for both
$('#id3').myPlugin.init({ x: 333 }); //Sets x=333 in settings.
$('#id3').myPlugin.someOtherMethod({ x: 223 });
$('#id1').myPlugin.someOtherMethod({ a: 223 });

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.

Passing a configuration object to a jQuery plugin

I often use the below design pattern to create jQuery plugins. I specify default values in the plugin, and allow the user to change them when they initialize the plugin. Normally, my default object just contains string properties, not objects, but this time, I wish to make one of the default properties an object (default_obj_1 in my example). At the bottom of the script, you see me attempt to change one of the properties of by passing the plugin {default_value_2: 666, default_obj_1.default_prop_3: 434}, but it invalid JavaScript and errors out. I also tried passing it {default_value_2: 666, default_obj_1: {default_prop_3: 434} but it appears to overwrite the other values which should remain.
How do I update just one of the default values in the jQuery plugin?
(function($){
var defaults = {
default_value_1 : 123,
default_value_2 : 321,
default_value_3 : 111,
default_obj_1 : {
default_prop_1 : 444,
default_prop_2 : 534,
default_prop_3 : 231
}
};
var methods = {
init : function (options) {
var settings = $.extend({}, defaults, options);
return this.each(function () {
//Do something using settings
});
}
};
$.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));
$(function(){
var myPlugin=$('#elem').myPlugin({default_value_2:666,default_obj_1.default_prop_3:434});
var myPlugin=$('#elem').myPlugin({default_value_2:666,default_obj_1:{default_prop_3:434} });
});
Change:
var settings = $.extend({}, defaults, options);
to
var settings = $.extend(true, {}, defaults, options); // notice the first param TRUE
to do a deep (recursive) merge.

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 to create a Jquery Plugin with Plugin Methods and Maintaining Chainability?

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

jQuery - plugin options default extend()

Following the good jQuery Plugins/Authoring instructions I have a little question
(function($){
// Default Settings
var settings = {
var1: 50
, var2: 100
};
var methods = {
init : function (options) {
console.log(settings);
settings = $.extend(options, settings); // Overwrite settings
console.log(settings);
return this;
}
, other_func: function () {
return this;
}
};
$.fn.my_plugin = 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.my_plugin');
}
};
})(jQuery);
If I do
>>> $('my_element').my_plugin({var3: 60})
Before Object { var2=100, var1=50}
After Object { var3=60, var2=100, var1=50}
[ my_element ]
>>> $('my_element').my_plugin({var1: 60})
Before Object { var1=50, var2=100}
After Object { var1=50, var2=100}
[ my_element ]
Why is my var1 not overridden ?
You mixed up the order of the arguments in your $.extend (target should be first), it should be:
settings = $.extend(settings, options);
See this fiddle and the docs for $.extend()
To avoid confusion you can also extend your settings with your defaults like this:
methods.init = function(options){
var settings = $.extend({
key1: 'default value for key 1',
key2: 'default value for key 2'
}, options); // <- if no / undefined options are passed extend will simply return the defaults
//here goes the rest
};
You are overwriting your defaults. Try creating a new variable to store the settings within the init method.
var defaults = {
var1: 50
, var2: 100
};
var methods = {
init : function (options) {
console.log(defaults);
var settings = $.extend({},defaults,options || {});
console.log(settings);
$(this).data("myPluginSettings",settings);
return this;
}
, other_func: function () {
console.log(this.data("myPluginSettings"));
return this;
}
};

Categories

Resources