jQuery Plugin Authoring Scoping - javascript

If I want to have a publicly accessible function on a jQuery plugin I'm making, is this the proper way to do it?
(function($) {
$.fn.myPlug = function(options) {
// Do this...
this.hello = function() {
return 1;
};
}
})(jQuery);
var foo = $("div").myPlug();
// then do this...
foo.hello();

You should structure your plugin so that method names can be passed as parameters to your plugin. This is recommended by the jQuery plugin authoring guide:
(function($) {
var methods = {
init: function(options) {
},
hello: function () {
return 1;
}
};
$.fn.myPlug = 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.myPlug');
}
};
})(jQuery);
The usage would go something like this:
$("div").myPlug({ ... });
$("div").myPlug("hello"); // returns 1

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.

How to extend a jquery plugin's public methods through its prototype?

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;
}

Variable scope in a jQuery plugin?

I continue a question, what I previously asked. It was a too simple sample :)
There is a plugin, what's syntax often used nowadays. I want to create an element at the initialization, and I want access to it from another methods. In the example below, I put it's declaration pretty deeply, but if I put it outside the init method, the open method drop an error, because it can't find it.
(I'm not sure am I call the methods from another in the right way...)
(function($, window, document, undefined) {
var opt = {
text : 'sample'
};
var methods = {
init: function(options) {
var self = $(this);
if (options) {
$.extend(opt,options);
}
return this.each(function () {
var container = $('<div class="container" />');
container.text(opt.text);
$(this).append(container);
$(this).click(function() {
self.pluginName('open');
});
});
},
open: function(args) {
container.text('opened');
},
submit: function(args) {
// call another method...
}
};
jQuery.fn.pluginName = 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.pluginname');
}
};
})(jQuery, window, document);
I would like to call it any of number element, like this:
$(function() {
$('div').pluginName();
});

Trying to use jQuery.data() API to cache a function in a jQuery plugin

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}

Best way to define custom jQuery object properties?

I want myproperty to be able to be accessed from other methods within Foo.
Here is the way I'm doing it now:
(function($){
$.fn.Foo = {
main: function(){
return this.each(function(){
this.myproperty = 'abc';
...
...bind('click', 'somemethod')...
...
});
}
somemethod: function(e){
// access here myproperty
alert($.fn.Foo.myproperty);
}
}(jQuery));
(function($){
$.fn.extend({ Foo : $.fn.Foo.main});
}(jQuery));
and seems to work. But is this the best way to do it? Are there any other ways?
I think your trying to make a class? You currently are extending the jQuery library with function you can call on elements like $("#test").Foo();
A good start setup is: http://jsfiddle.net/VhGpe/
(function($) {
var methods = {
init: function(settings) {
var options = $.extend({}, $.fn.FooMe.defaults, settings);
$(this).data("settings", options);
$(this).bind("click", methods.myOtherFunction);
},
myOtherFunction: function() {
alert($(this).data("settings").you);
}
};
$.fn.FooMe = 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.Foo');
}
};
$.fn.FooMe.defaults = {
you: "default"
};
}(jQuery));
$("#test").FooMe({
you: "TADAA"
});
can be exposed via a function of Foo
(function($){
$.fn.Foo = {
main: function(){
return this.each(function(){
this.myproperty = 'abc';
...
...bind('click', 'somemethod')...
...
});
getmyproperty: function(){
var myproperty = 'abc';
return myproperty;
}
}
somemethod: function(e){
// access here myproperty
alert($.fn.Foo.getmyproperty());
}
}(jQuery));

Categories

Resources