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;
}
};
Related
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 });
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.
How can I extend a plugin's public methods its prototype?
For instance, I have method1 in my plugin, and I want to add another and more through its .prototype. Is it possible?
var extensionMethods = {
method2: function(){
return this;
}
};
$.fn.MyPlugin.prototype = extensionMethods;
console.log($(".element").MyPlugin());
result,
Object { Element={...}, Options={...}, method1=function()}
Ideally,
Object { Element={...}, Options={...}, method1=function(), method2=function(), method2function()}
my plugin boilerplate,
(function ($) {
// Create the plugin name and defaults once
var pluginName = 'MyPlugin';
// Attach the plugin to jQuery namespace.
$.fn[pluginName] = function(PublicOptions) {
// Set private defaults.
var Defaults = {
param1: 'param1',
param2: 'param2',
onSuccess: function(){}
};
// Do a deep copy of the options.
var Options = $.extend(true, {}, Defaults, PublicOptions);
// Define a functional object to hold the api.
var PluginApi = function(Element, Options) {
this.Element = Element;
this.Options = Options;
};
// Define the public api and its public methods.
PluginApi.prototype = {
method1: function(PublicOptions) {
// Process the options.
var Options = $.extend(true, {}, this.Options, PublicOptions);
return this.Options;
}
};
//Create a new object of api.
return new PluginApi(this, Options);
};
})(jQuery);
Any ideas?
I think the best structure you can do in this case would not involve prototypes at all. Check this plugin base:
(function($) {
// Set private defaults.
var Defaults = {
param1: 'param1',
param2: 'param2',
onSuccess: function() {}
};
// Define the public api and its public methods.
var PluginApi = {
extend: function(name, method) {
PluginApi[name] = method;
return this;
},
init: function(PublicOptions) {
// Do a deep copy of the options.
var Options = $.extend(true, {}, Defaults, PublicOptions);
return this.each(function() {
console.log('set up plugin logic', this.tagName);
});
},
method1: function() {
console.log('called: method1');
return this;
}
};
// Create the plugin name and defaults once
var pluginName = 'MyPlugin';
// Attach the plugin to jQuery namespace.
$.fn[pluginName] = function(method) {
if (PluginApi[method]) {
return PluginApi[method].apply(this, Array.prototype.slice.call(arguments, 1));
}
else if (typeof method === 'object' || !method) {
return PluginApi.init.apply(this, arguments);
}
else {
$.error('Method ' + method + 'does not exist');
}
};
})(jQuery);
This plugin structure allows you to chain methods as expected:
$('h1').MyPlugin('method1').css('color', 'red');
In case of the need to use non-existent method you could do this:
// Extend plugin "prototype" with method2 and use it
$('h1, h2').MyPlugin('extend', 'method2', function(prop, value) {
return this.css(prop, value);
}).MyPlugin('method2', 'color', 'green');
Check usage example in the demo below.
(function($) {
// Set private defaults.
var Defaults = {
param1: 'param1',
param2: 'param2',
onSuccess: function() {}
};
// Define the public api and its public methods.
var PluginApi = {
extend: function(name, method) {
PluginApi[name] = method;
return this;
},
init: function(PublicOptions) {
// Do a deep copy of the options.
var Options = $.extend(true, {}, Defaults, PublicOptions);
return this.each(function() {
console.log('set up plugin logic', this.tagName);
});
},
method1: function() {
console.log('called: method1');
return this;
}
};
// Create the plugin name and defaults once
var pluginName = 'MyPlugin';
// Attach the plugin to jQuery namespace.
$.fn[pluginName] = function(method) {
if (PluginApi[method]) {
return PluginApi[method].apply(this, Array.prototype.slice.call(arguments, 1));
}
else if (typeof method === 'object' || !method) {
return PluginApi.init.apply(this, arguments);
}
else {
$.error('Method ' + method + 'does not exist');
}
};
})(jQuery);
// Call existen method1: should make h1 and h2 red
$('h1, h2').MyPlugin('method1').css('color', 'red');
// Call non-existent method2: should throw error in console
try {
$('h1, h2').MyPlugin('method2').css('color', 'green');
}
catch (e) {
// Extend "plugin" prototype with method2
$('h1, h2').MyPlugin('extend', 'method2', function(prop, value) {
return this.css(prop, value);
}).MyPlugin('method2', 'color', 'green');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>H1</h1>
<h2>H2</h2>
Or it may be more optimal to define a static method extend within $[pluginName] namespace:
// Attach the plugin to jQuery namespace.
$.fn[pluginName] = function(method) {
if (PluginApi[method]) {
return PluginApi[method].apply(this, Array.prototype.slice.call(arguments, 1));
}
else if (typeof method === 'object' || !method) {
return PluginApi.init.apply(this, arguments);
}
else {
$.error('Method ' + method + 'does not exist');
}
};
$[pluginName] = {};
$[pluginName].extend = function(name, method) {
PluginApi[name] = method;
};
and then use it like this when necessary to add additional methods:
$.MyPlugin.extend('method2', function(prop, value) {
return this.css(prop, value);
});
$('h1, h2').MyPlugin('method2', 'color', 'green');
Final demo: http://plnkr.co/edit/qqlfRqAM84goscU5BFNU?p=preview
You can't extend the prototype outside because you use hidden object PluginApi.
You can try to store PluginApi outside of a plugin function:
$[pluginName] = function(Element, Options) {
this.Element = Element;
this.Options = Options;
};
$[pluginName].prototype = {
method1: function(PublicOptions) {
// Process the options.
var Options = $.extend(true, {}, this.Options, PublicOptions);
return this.Options;
}
};
$.fn[pluginName] = function(PublicOptions) {
// Set private defaults.
var Defaults = {
param1: 'param1',
param2: 'param2',
onSuccess: function(){}
};
// Do a deep copy of the options.
var Options = $.extend(true, {}, Defaults, PublicOptions);
return new $[pluginName](this, Options);
};
and then you can extend the the prototype:
$.MyPlugin.prototype.method2 = function() {
return this;
}
Please suggest solutions to below mentioned scenario
Background (jQuery Plugin):
$.fn.myplugin = function (action) {
if (actions[action]) {
return actions[action].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof action === 'object' || !action) {
return actions.init.apply(this, arguments);
} else {
$.error('Action ' + action + ' does not exist on myplugin');
return this;
}
};
and variable actions looking like:
var actions = {
init: function (options) {
if (this.length) {
var settings = $.extend({}, $.fn.myplugin.defaults);
return this.each(function () {
if (options) {
settings = $.extend(settings, options);
}
$(this).data('myplugin', new MyPlugin($(this), settings));
});
} else {
throw new Error('unable to init undefined');
}
},
update: function () {
...
},
destroy: function () {
...
}
};
and MyPlugin looking like
function MyPlugin($el, settings) {
var $content = $('.content', $el);
var a = function setup() { ... };
var b = function hold() { ... }
$content.on({
'click': function(e) { ... },
'hover': function(e) { ... }
});
}
I get that I can dump $.cache to console and see what gets associated in .data().
Problem/Suggestions wanted:
If I call the update function like $('myEle').myplugin('update') then I need the update function to change state of the instance of MyPlugin created and cached using .data() API. What are the possible ways to do that?
My current result of $('myEle').data('myplugin') shows MyPlugin{} with nothing between the curly braces.
The problem doesn't have anything to do with jQuery or the data() API, it's due to a misunderstanding about functions, objects and constructors in JavaScript.
This is easy to test inside the JavaScript console in a browser:
> function MyPlugin() { var a = 1; var b = 2; }
undefined
> new MyPlugin()
MyPlugin {}
> function MyPlugin() { this.a = 1; this.b = 2; }
undefined
> new MyPlugin()
MyPlugin {a: 1, b: 2}
In my module pattern, options are 'undefined' for some reason.. does anyone see why they aren't being passed in properly?
Framework.MyModule = (function(options) {
var defaults = {
someOption : 1,
stuff : 2
};
if (!options) {
var options = defaults;
} else {
for (var index in defaults) {
if (typeof options[index] == 'undefined')
options[index] = defaults[index];
}
}
var module = {};
// Initialize
_something();
// Private Methods
function _something() {}
// Public Methods
module.click = function() {};
return module;
})();
... docready function ...
var options = {
someOption : 9,
stuff : 10
};
Framework.MyModule(options);
... end doc ready ...
Please see the fiddle: http://jsfiddle.net/kWHEZ/1/
var options = { /* ... */};
Framework.MyModule = (function(options) {
/* .. options are undefined ... */
})();
Framework.MyModule = (function(options) {
/* .. options are defined... */
})(options);
Now if you want the ability to add private/public variables AND still pass options you will need to make it so the a constructor method is returned with your public object - thus not passing options in the function that is run immediately. Because lets be honest .. this doesn't really make sense.
You could do something like this:
var Module = {};
Module.Foo = (function($){ // jQuery is accessible as $
var _private = {
defaults: {
url: '/default-url', container: '#dummy'
},
foos: []
};
return function(o){ // returns constructor
// other _private variables are accessible here
var opts = $.extend({}, _private.defaults, o);
var self = { // public return object
load: function(){
$(opts.container).load(opts.url);
}
};
_private.foos.push(self);
return self;
};
})(jQuery); // scope global variables
var foo1 = Module.Foo({
url: '/test.php',
container: '#success'
});
var foo2 = Module.Foo({
url: '/test2.php',
container: '#success2'
});
foo1.load();
foo2.load();
You're not passing in any options to the anonymous function call.
your call would have to end with })(options); if you want it to use custom options.
You're executing the function immediately. That function returns module, which is an object, not a function. Did you mean instead to return a function?
Then call it using:
Framework.MyModule(options);