Extend jQuery Plugin with additional function / override function - javascript

I need to extend a jQuery Plugin (https://github.com/idiot/unslider) in order to add additional behavior with another public method.
(function(){
// Store a reference to the original remove method.
var originalMethod = $.fn.unslider;
// Define overriding method.
$.fn.unslider = function(){
// Execute the original method.
originalMethod.apply( this, arguments );
console.log( "Override method" );
function test() {
console.log("test called");
}
this.each(function() {
// Operations for each DOM element
console.log("each dom element?");
}).data('unslider', {
// Make test accessible from data instance
test: test
});
return this;
}
})(jQuery);
I already managed to make the public method accessible when calling
var slider = $('#slider');
slider.data('unslider').test();
However, I want to keep the old behavior of unslider anyways, but extend the Plugin with another function. Does anyone have an idea?
I created a fiddle, so you can check whats happening:
My new function gets called, but the old ones are gone:
http://jsfiddle.net/b2os4s7e/1/

If you look at the source of unslider, you can see it stores the Unslider instance inside the data:
// Enable multiple-slider support
return this.each(function(index) {
// Cache a copy of $(this), so it
var me = $(this),
key = 'unslider' + (len > 1 ? '-' + ++index : ''),
instance = (new Unslider).init(me, o);
// Invoke an Unslider instance
me.data(key, instance).data('key', key);
});
In your code you're overwriting this object with your own object. However, the slider expects there to be an Unslider instance. So what you want to do is get this instance and then extend it with your own functions:
var key = $(this).data('key');
var obj = $(this).data(key);
obj.test = function() { console.log('Working!'); };
See http://jsfiddle.net/b2os4s7e/2/

Just define:
$fn.unslider2 = function() { ... }
With any name and behaviour you like.

For extend JQuery should use .fn.extend
(function($){
$.fn.extend({
helloworld: function(message){
return this.each(function(){
$(this).click(function(){
alert(message);
});
});
}
});
})(jQuery)
the object .fn.extend is used for extend funcionality of jQuery

Thanks for your answers! I did it this way:
(function($){
var originalMethod = $.fn.unslider;
$.fn.extend({
unslider: function(o) {
var len = this.length;
var applyMethod = originalMethod.apply( this, arguments );
var key = applyMethod.data('key');
var instance = applyMethod.data(key);
// Cache a copy of $(this), so it
var me = $(this);
if (instance) {
instance.movenext = function (callback) {
return instance.stop().to(instance.i + 1, callback);
};
instance.moveprev = function (callback) {
return instance.stop().to(instance.i - 1, callback);
};
}
return applyMethod.data(key, instance);
}
});
})(jQuery)
The key was to address the data attribute as sroes suggested.
Moreover i needed to apply the original method, since i need the old methods.

Related

Recursion with prototype function

I would like to create a new jQuery function, in which I could pass one or many value in the option. Then, For all matched elements of the DOM, iterate on all the different options, but one option at a time. So, for all the elements not treated, replay the prototype function. All the treatment should be in the same function, so I thought at recursion, but since I am not really experience with prototyping in javascript, I am not sure how to do that.
For now, There is no error, but nothing happen even.
Is that correct ?
Thanks for your enlightenment.
(function($) {
$.fn.foo = function (prop) {
var options = $.extend({
"elt" : "", // Declaration of my options
"callback" : function(){}
}, prop)
var optionsTab = options.elt.split('#');
var current = optionsTab.slice(0,1),
var remaining = optionsTab.slice(1);
var result = this.each(function() {
var el = $(this);
// Code handling the first element in the option
});
if (remaining.length > 0) {
$({ "elt": remaining.join("#") }); // Trial of recursion of foo
return result;
}
}
I think you're trying to call recursively in a timeout am I correct?
var result = this.each(function() {
var el = $(this);
// Code handling the first element in the option
});
if (remaining.length > 0) {
$({ "elt": remaining.join("#") }); // Trial of recursion of foo
var me = this;
setTimeout(function(){
me.foo(parameters);//not sure what the parameters should be
},500);
}
return this;
Some helpful info: https://stackoverflow.com/a/16063711/1641941 (under "The this variable")
Created this fiddle and it works:
$.fn.foo = function (prop) {
var options = $.extend({
"elt" : "", // Declaration of my options
"callback" : function(){}
}, prop);
var optionsTab = options.elt.split('#');
var current = optionsTab.slice(0,1);
var remaining = optionsTab.slice(1);
this.each(function() {
var el = $(this);
// Code handling the first element in the option
});
if (remaining.length > 0) {
this.foo({ "elt": remaining.join("#") }); // Trial of recursion of foo
}
return this;
};

jquery plugin method ands callbacks

I have a basic plugin that populates an array within the plugin. How can I get that array via a method call with parameters. This is my first plugin so please go easy on me if this is a dumb question.
basic Plugin
(function($) {
$.fn.myPlugin = function() {
return this.each(function(){
tagArray = []; // my array that is populated
//code that does stuff to populate array
});
}
})(jQuery);
I would like to get the tagArray like so...
var arr = $('.className').myPlugin("getArray");
Where I can then use that array elsewhere. How can I accomplish this?
Thank you for any help.
I don't see why you would need the "getArray" parameter. In any case you need to define only 1 array and make your function return it:
(function($) {
$.fn.myPlugin = function() {
var tagArray = [];
this.each(function(){
// add something to tagArray
});
return tagArray;
}
})(jQuery);
That's a rather strange requirement, but an easy way to do that if there is only parameter would be something like :
(function($) {
$.fn.myPlugin = function(param) {
var tagArray = [],
elems = this.each(function(){
tagArray.push( $(this).text() ); // whatever you do ??
});
return param == 'getArray' ? tagArray : elems;
} // ^^ if the parameter is passed, return the array, otherwise the elems
})(jQuery);
FIDDLE
It's a bit hackish, but it works. You could also just return this.map(function() {... to always return an array etc, or read up on how to pass multiple parameters to a plugin and do different things etc. instead of the hardcoded check for 'getArray' used above.
Try
(function($) {
function Plugin($el, opts){
this.tagArray = [];
this.tagArray.push($el.attr('id')) //for testing the retuned instance
$el.data('myPlugin', this);
}
Plugin.prototype.getTagArray = function(){
return this.tagArray;
}
$.fn.myPlugin = function(opts) {
if($.type(opts) == 'string'){
var plugin = this.data('myPlugin');
return plugin[opts]();
}
return this.each(function(){
var $this = $(this);
new Plugin($this);
});
}
})(jQuery);
jQuery(function(){
$('#e1, #e2, #e3').myPlugin();
console.log($('#e1').myPlugin('getTagArray'))
console.log($('#e2').myPlugin('getTagArray'))
console.log($('#e3, #e1').myPlugin('getTagArray'))
})
Demo: Fiddle
I just finished writing a JQuery Plugin myself and here is the basic structure I settled on:
(function (window, document, $, undefined) {
//Local Methods
var methods = {
init : function(options){
//stuff you want to do when your plugin initializes i.e. when you do $('selector').myPlugin(options)
},
getArray: function(){
//your getArray method. Put your get array logic here
}
}
//Plugin Initialize
$.fn.myPlugin = function(args){
if ( methods[args] )
{
//execute JQuery Plugin Method
return methods[ args ].apply( this, Array.prototype.slice.call( arguments, 1 ));
}
else if ( typeof args === 'object' || ! args )
{
//Process JQuery Plugin Options
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 on myPlugin' );
}
};
//Define Default Options
$.fn.myPlugin.defaults = {
option_1: '',
option_2: '',
option_n: ''
}
//API Methods
var M = $.myPlugin = function(){};
$.extend(M, {
getArray: function(){
return methods.getArray();
}
});
}(window, document, jQuery));
Doing this allows you to start your plugin like this (as usual):
$('.className').myPlugin(options);
and/or call your getArray function like this:
$.myPlugin.getArray();
I hope this helps you get closer to where you want to be.

How to create a javascript library using a closure

I have written some javascript that I would to encapsulate in a closure so I can use it elsewhere. I would like do do this similar to the way jQuery has done it. I would like to be able to pass in an id to my closure and invoke some functions on it, while setting some options. Similar to this:
<script type="text/javascript">
_snr("#canvas").draw({
imageSrc : someImage.png
});
</script>
I have read a lot of different posts on how to use a closure to do this but am still struggling with the concept. Here is where I left off:
_snr = {};
(function (_snr) {
function merge(root){
for ( var i = 1; i < arguments.length; i++ )
for ( var key in arguments[i] )
root[key] = arguments[i][key];
return root;
}
_snr.draw = function (options) {
var defaults = {
canvasId : 'canvas',
imageSrc : 'images/someimage.png'
}
var options = merge(defaults, options)
return this.each(function() {
//More functions here
});
};
_snr.erase = function () {};
})(_snr);
When ever I try to call the draw function like the first code section above, I get the following error, '_snr is not a function'. Where am I going wrong here?
EDIT
Here is what I ended up doing:
function _snr(id) {
// About object is returned if there is no 'id' parameter
var about = {
Version: 0.2,
Author: "ferics2",
Created: "Summer 2011",
Updated: "3 September 2012"
};
if (id) {
if (window === this) {
return new _snr(id);
}
this.e = document.getElementById(id);
return this;
} else {
// No 'id' parameter was given, return the 'about' object
return about;
}
};
_snr.prototype = (function(){
var merge = function(root) {
for ( var i = 1; i < arguments.length; i++) {
for ( var key in arguments[i] ) {
root[key] = arguments[i][key];
}
}
return root;
};
return {
draw: function(options) {
var defaults = {
canvasId : 'canvas',
imageSrc : 'images/someimage.png'
};
options = merge(defaults, options);
return this;
},
erase: function() {
return this;
}
};
})();
I can now call:
<script type="text/javascript">
_snr("#canvas").draw({
imageSrc : someImage.png
});
</script>
Because you declared _snr as an object and not a function. Functions can have properties and methods, so there's various ways to achieve what you want, for example one of them would be say...
_snr = function(tag) {
this.tag = tag;
}
_snr.foo = function() {
//Code goes here
}
You can also pass the outer context into a closure to hide your variables from accidentally polluting the global namespace, so like...
(function(global) {
var _snr = function(tag) {
this.tag = tag;
}
_snr.foo = function() {
//Code goes here
}
//export the function to the window context:
global._snr = _snr;
})(window);
window._snr('#tag').foo('wat');
Happy coding.
Because your _snr is an object, not a function. You have to call it like this:
_snr.draw({
canvasId: '#canvas',
imageSrc: 'someImage.png'
});
When you do _snr('#canvas') that is a function call which is why you're getting that error. _snr is an object with some methods attached to it such as draw() and erase(). The reason jQuery is able to pass arguments into the $ is because they return the $ as a function object which is why we're able to pass it various selectors as arguments.
You are going wrong at the first line _snr = {}
It needs to be
_snr = function(){
selector = arguments[0]||false;
//snr init on dom object code
return _snrChild;
}
Im on a mobile phone but when im on a pc I will maybe fix the whole code c:
Here you have a snr object and that has erase and draw methods. What you intend to do is to write a _snr function which will get an id and return a wrapper object. That returned object should have erase and draw methods. so you can do
var returnedObject = _snr("my_id");
returnedObject.draw("image.png");

Customer JQuery Plugin

I am learning JQuery. I have a need to create a custom control. This control is going to basically render some HTML. Sometimes, I want to just get the HTML. My hope is to use the following syntax:
// Put generated html inside of "myElement". "myElement" is a div element.
$("#myElement").myPlugin({ value: 10 });
// Retrieve the html that myPlugin would place of a div element.
// Basically, I want the javascript equivalent of a C# static function here.
// But I think the following approach is wrong:
var html = myPlugin().getHtml(10);
alert(html);
In an attempt to accomplish this, I'm using the following:
(function ($) {
$.fn.myPlugin = function (element, options) {
var defaults = { theValue: 0 }
var plugin = this;
plugin.settings = {};
var $element = $(element),
element = element;
plugin.init = function () {
plugin.settings = $.extend({}, defaults, options);
var html = createHtml(defaults.theValue);
$(element).html(html);
}
plugin.getHtml = function (v) {
return createHtml(v);
}
var createHtml(v) {
return "<span>" + v + "</span>";
}
})(jQuery);
I have two problems: 1) I can see that the HTML is being generated, but it does not appear to be added to the DOM. 2) I can't statically call the function. How can I make my function statically visible?
Thank you!
There are a few tweaks you need to make:
(function($) {
$.fn.myPlugin = function(options) {
var defaults = {
theValue: 0
},
settings = $.extend({}, defaults, options);
this.html($.myPlugin.getHtml(settings.theValue));
};
$.myPlugin = {
getHtml: function(value) {
return "<span>" + value + "</span>";
}
};
})(jQuery);
Usage:
$("#foo").myPlugin({ theValue: 10 });
var html = $.myPlugin.getHtml(10);
First off, you were never calling the init method, so nothing was going to work. It isn't going to be called automatically. Perhaps you were thinking of the jQueryUI widget factory?
Additionally, element doesn't get passed to the plugin, this refers the the element the plugin was called on. You only get passed options.
Finally, to create a static jQuery method, just attach it right to $.
Example: http://jsfiddle.net/xF2S6/

jQuery plugin template - best practice, convention, performance and memory impact

I've started to write few jQuery plugins and figured it'd be nice to setup my IDE with a jQuery plugin template.
I have been reading some articles and posts on this site related to plugin convention, design, etc.. and thought I'd try and consolidate all of that.
Below is my template, I am looking to use it frequently so was keen to ensure it generally conforms to jQuery plugin design convention and whether the idea of having multiple internal methods (or even its general design) would impact performance and be prone to memory issues.
(function($)
{
var PLUGIN_NAME = "myPlugin"; // TODO: Plugin name goes here.
var DEFAULT_OPTIONS =
{
// TODO: Default options for plugin.
};
var pluginInstanceIdCount = 0;
var I = function(/*HTMLElement*/ element)
{
return new Internal(element);
};
var Internal = function(/*HTMLElement*/ element)
{
this.$elem = $(element);
this.elem = element;
this.data = this.getData();
// Shorthand accessors to data entries:
this.id = this.data.id;
this.options = this.data.options;
};
/**
* Initialises the plugin.
*/
Internal.prototype.init = function(/*Object*/ customOptions)
{
var data = this.getData();
if (!data.initialised)
{
data.initialised = true;
data.options = $.extend(DEFAULT_OPTIONS, customOptions);
// TODO: Set default data plugin variables.
// TODO: Call custom internal methods to intialise your plugin.
}
};
/**
* Returns the data for relevant for this plugin
* while also setting the ID for this plugin instance
* if this is a new instance.
*/
Internal.prototype.getData = function()
{
if (!this.$elem.data(PLUGIN_NAME))
{
this.$elem.data(PLUGIN_NAME, {
id : pluginInstanceIdCount++,
initialised : false
});
}
return this.$elem.data(PLUGIN_NAME);
};
// TODO: Add additional internal methods here, e.g. Internal.prototype.<myPrivMethod> = function(){...}
/**
* Returns the event namespace for this widget.
* The returned namespace is unique for this widget
* since it could bind listeners to other elements
* on the page or the window.
*/
Internal.prototype.getEventNs = function(/*boolean*/ includeDot)
{
return (includeDot !== false ? "." : "") + PLUGIN_NAME + "_" + this.id;
};
/**
* Removes all event listeners, data and
* HTML elements automatically created.
*/
Internal.prototype.destroy = function()
{
this.$elem.unbind(this.getEventNs());
this.$elem.removeData(PLUGIN_NAME);
// TODO: Unbind listeners attached to other elements of the page and window.
};
var publicMethods =
{
init : function(/*Object*/ customOptions)
{
return this.each(function()
{
I(this).init(customOptions);
});
},
destroy : function()
{
return this.each(function()
{
I(this).destroy();
});
}
// TODO: Add additional public methods here.
};
$.fn[PLUGIN_NAME] = function(/*String|Object*/ methodOrOptions)
{
if (!methodOrOptions || typeof methodOrOptions == "object")
{
return publicMethods.init.call(this, methodOrOptions);
}
else if (publicMethods[methodOrOptions])
{
var args = Array.prototype.slice.call(arguments, 1);
return publicMethods[methodOrOptions].apply(this, args);
}
else
{
$.error("Method '" + methodOrOptions + "' doesn't exist for " + PLUGIN_NAME + " plugin");
}
};
})(jQuery);
Thanks in advance.
A while back I've build a plugin generator based on a blog article I have read: http://jsfiddle.net/KeesCBakker/QkPBF/. It might be of use. It is fairly basic and straight forward. Any comments would be very welcome.
You can fork your own generator and change it to your needs.
Ps. This is the generated body:
(function($){
//My description
function MyPluginClassName(el, options) {
//Defaults:
this.defaults = {
defaultStringSetting: 'Hello World',
defaultIntSetting: 1
};
//Extending options:
this.opts = $.extend({}, this.defaults, options);
//Privates:
this.$el = $(el);
}
// Separate functionality from object creation
MyPluginClassName.prototype = {
init: function() {
var _this = this;
},
//My method description
myMethod: function() {
var _this = this;
}
};
// The actual plugin
$.fn.myPluginClassName = function(options) {
if(this.length) {
this.each(function() {
var rev = new MyPluginClassName(this, options);
rev.init();
$(this).data('myPluginClassName', rev);
});
}
};
})(jQuery);
[Edit] 7 months later
Quoting from the github project
jQuery is no good, and jQuery plugins is not how do modular code.
Seriously "jQuery plugins" are not a sound architecture strategy. Writing code with a hard dependency on jQuery is also silly.
[Original]
Since I gave critique about this template I will propose an alternative.
To make live easier this relies on jQuery 1.6+ and ES5 (use the ES5 Shim).
I've spend some time re-designing the plugin template you've given and rolled out my own.
Links:
Github
Documentation
Unit tests Confirmed to pass in FF4, Chrome and IE9 (IE8 & OP11 dies. known bug).
Annotated Source Code
The PlaceKitten example plugin
Comparison:
I've refactored the template so that it's split into boilerplate (85%) and scaffolding code (15%). The intention is that you only have to edit the scaffolding code and you can keep leave boilerplate code untouched. To achieve this I've used
inheritance var self = Object.create(Base) Rather then editing the Internal class you have directly you should be editing a sub class. All your template / default functionality should be in a base class (called Base in my code).
convention self[PLUGIN_NAME] = main; By convention the plugin defined on jQuery will call the method define on self[PLUGIN_NAME] by default. This is considered the main plugin method and has a seperate external method for clarity.
monkey patching $.fn.bind = function _bind ... Use of monkey patching means that the event namespacing is done automatically for you under the hood. This functionality is free and does not come at the cost of readability (calling getEventNS all the time).
OO Techniques
It's better to stick to proper JavaScript OO rather then classical OO emulation. To achieve this you should use Object.create. (which ES5 just use the shim to upgrade old browsers).
var Base = (function _Base() {
var self = Object.create({});
/* ... */
return self;
})();
var Wrap = (function _Wrap() {
var self = Object.create(Base);
/* ... */
return self;
})();
var w = Object.create(Wrap);
This is different from the standard new and .prototype based OO people are used to. This approach is preferred because it re-inforces the concept that there are only Objects in JavaScript and it's a prototypical OO approach.
[getEventNs]
As mentioned this method has been refactored away by overriding .bind and .unbind to automatically inject namespaces. These methods are overwritten on the private version of jQuery $.sub(). The overwritten methods behave the same way as your namespacing does. It namespaces events uniquely based on plugin and instance of a plugin wrapper around a HTMLElement (Using .ns.
[getData]
This method has been replaced with a .data method that has the same API as jQuery.fn.data. The fact that it's the same API makes it easier to use, its basically a thin wrapper around jQuery.fn.data with namespacing. This allows you to set key/value pair data that is immediatley stored for that plugin only. Multiple plugins can use this method in parallel without any conflicts.
[publicMethods]
The publicMethods object has been replaced by any method being defined on Wrap being automatically public. You can call any method on a Wrapped object directly but you do not actually have access to the wrapped object.
[$.fn[PLUGIN_NAME]]
This has been refactored so it exposes a more standardized API. This api is
$(selector).PLUGIN_NAME("methodName", {/* object hash */}); // OR
$(selector).PLUGIN_NAME({/* object hash */}); // methodName defaults to PLUGIN_NAME
the elements in the selector are automatically wrapped in the Wrap object, the method is called or each selected element from the selector and the return value is always a $.Deferred element.
This standardizes the API and the return type. You can then call .then on the returned deferred to get out the actual data you care about. The use of deferred here is very powerful for abstraction away whether the plugin is synchronous or asynchronous.
_create
A caching create function has been added. This is called to turn a HTMLElement into a Wrapped element and each HTMLElement will only be wrapped once. This caching gives you a solid reduction in memory.
$.PLUGIN_NAME
Added another public method for the plugin (A total of two!).
$.PLUGIN_NAME(elem, "methodName", {/* options */});
$.PLUGIN_NAME([elem, elem2, ...], "methodName", {/* options */});
$.PLUGIN_NAME("methodName", {
elem: elem, /* [elem, elem2, ...] */
cb: function() { /* success callback */ }
/* further options */
});
All parameters are optional. elem defaults to <body>, "methodName" defaults to "PLUGIN_NAME" and {/* options */} defaults to {}.
This API is very flexible (with 14 method overloads!) and standard enough to get used to the syntnax for every method your plugin will expose.
Public exposure
The Wrap, create and $ objects are exposed globally. This will allow advanced plugin users maximum flexibility with your plugin. They can use create and the modified subbed $ in their development and they can also monkey patch Wrap. This allows for i.e. hooking into your plugin methods. All three of these are marked with a _ in front of their name so they are internal and using them breaks the garantuee that your plugin works.
The internal defaults object is also exposed as $.PLUGIN_NAME.global. This allows users to override your defaults and set plugin global defaults. In this plugin setup all hashes past into methods as objects are merged with the defaults, so this allows users to set global defaults for all your methods.
Actual Code
(function($, jQuery, window, document, undefined) {
var PLUGIN_NAME = "Identity";
// default options hash.
var defaults = {
// TODO: Add defaults
};
// -------------------------------
// -------- BOILERPLATE ----------
// -------------------------------
var toString = Object.prototype.toString,
// uid for elements
uuid = 0,
Wrap, Base, create, main;
(function _boilerplate() {
// over-ride bind so it uses a namespace by default
// namespace is PLUGIN_NAME_<uid>
$.fn.bind = function _bind(type, data, fn, nsKey) {
if (typeof type === "object") {
for (var key in type) {
nsKey = key + this.data(PLUGIN_NAME)._ns;
this.bind(nsKey, data, type[key], fn);
}
return this;
}
nsKey = type + this.data(PLUGIN_NAME)._ns;
return jQuery.fn.bind.call(this, nsKey, data, fn);
};
// override unbind so it uses a namespace by default.
// add new override. .unbind() with 0 arguments unbinds all methods
// for that element for this plugin. i.e. calls .unbind(_ns)
$.fn.unbind = function _unbind(type, fn, nsKey) {
// Handle object literals
if ( typeof type === "object" && !type.preventDefault ) {
for ( var key in type ) {
nsKey = key + this.data(PLUGIN_NAME)._ns;
this.unbind(nsKey, type[key]);
}
} else if (arguments.length === 0) {
return jQuery.fn.unbind.call(this, this.data(PLUGIN_NAME)._ns);
} else {
nsKey = type + this.data(PLUGIN_NAME)._ns;
return jQuery.fn.unbind.call(this, nsKey, fn);
}
return this;
};
// Creates a new Wrapped element. This is cached. One wrapped element
// per HTMLElement. Uses data-PLUGIN_NAME-cache as key and
// creates one if not exists.
create = (function _cache_create() {
function _factory(elem) {
return Object.create(Wrap, {
"elem": {value: elem},
"$elem": {value: $(elem)},
"uid": {value: ++uuid}
});
}
var uid = 0;
var cache = {};
return function _cache(elem) {
var key = "";
for (var k in cache) {
if (cache[k].elem == elem) {
key = k;
break;
}
}
if (key === "") {
cache[PLUGIN_NAME + "_" + ++uid] = _factory(elem);
key = PLUGIN_NAME + "_" + uid;
}
return cache[key]._init();
};
}());
// Base object which every Wrap inherits from
Base = (function _Base() {
var self = Object.create({});
// destroy method. unbinds, removes data
self.destroy = function _destroy() {
if (this._alive) {
this.$elem.unbind();
this.$elem.removeData(PLUGIN_NAME);
this._alive = false;
}
};
// initializes the namespace and stores it on the elem.
self._init = function _init() {
if (!this._alive) {
this._ns = "." + PLUGIN_NAME + "_" + this.uid;
this.data("_ns", this._ns);
this._alive = true;
}
return this;
};
// returns data thats stored on the elem under the plugin.
self.data = function _data(name, value) {
var $elem = this.$elem, data;
if (name === undefined) {
return $elem.data(PLUGIN_NAME);
} else if (typeof name === "object") {
data = $elem.data(PLUGIN_NAME) || {};
for (var k in name) {
data[k] = name[k];
}
$elem.data(PLUGIN_NAME, data);
} else if (arguments.length === 1) {
return ($elem.data(PLUGIN_NAME) || {})[name];
} else {
data = $elem.data(PLUGIN_NAME) || {};
data[name] = value;
$elem.data(PLUGIN_NAME, data);
}
};
return self;
})();
// Call methods directly. $.PLUGIN_NAME(elem, "method", option_hash)
var methods = jQuery[PLUGIN_NAME] = function _methods(elem, op, hash) {
if (typeof elem === "string") {
hash = op || {};
op = elem;
elem = hash.elem;
} else if ((elem && elem.nodeType) || Array.isArray(elem)) {
if (typeof op !== "string") {
hash = op;
op = null;
}
} else {
hash = elem || {};
elem = hash.elem;
}
hash = hash || {}
op = op || PLUGIN_NAME;
elem = elem || document.body;
if (Array.isArray(elem)) {
var defs = elem.map(function(val) {
return create(val)[op](hash);
});
} else {
var defs = [create(elem)[op](hash)];
}
return $.when.apply($, defs).then(hash.cb);
};
// expose publicly.
Object.defineProperties(methods, {
"_Wrap": {
"get": function() { return Wrap; },
"set": function(v) { Wrap = v; }
},
"_create":{
value: create
},
"_$": {
value: $
},
"global": {
"get": function() { return defaults; },
"set": function(v) { defaults = v; }
}
});
// main plugin. $(selector).PLUGIN_NAME("method", option_hash)
jQuery.fn[PLUGIN_NAME] = function _main(op, hash) {
if (typeof op === "object" || !op) {
hash = op;
op = null;
}
op = op || PLUGIN_NAME;
hash = hash || {};
// map the elements to deferreds.
var defs = this.map(function _map() {
return create(this)[op](hash);
}).toArray();
// call the cb when were done and return the deffered.
return $.when.apply($, defs).then(hash.cb);
};
}());
// -------------------------------
// --------- YOUR CODE -----------
// -------------------------------
main = function _main(options) {
this.options = options = $.extend(true, defaults, options);
var def = $.Deferred();
// Identity returns this & the $elem.
// TODO: Replace with custom logic
def.resolve([this, this.elem]);
return def;
}
Wrap = (function() {
var self = Object.create(Base);
var $destroy = self.destroy;
self.destroy = function _destroy() {
delete this.options;
// custom destruction logic
// remove elements and other events / data not stored on .$elem
$destroy.apply(this, arguments);
};
// set the main PLUGIN_NAME method to be main.
self[PLUGIN_NAME] = main;
// TODO: Add custom logic for public methods
return self;
}());
})(jQuery.sub(), jQuery, this, document);
As can be seen the code your supposed to edit is below the YOUR CODE line. The Wrap object acts similarly to your Internal object.
The function main is the main function called with $.PLUGIN_NAME() or $(selector).PLUGIN_NAME() and should contain your main logic.
How about something like this ? It's much clearer but again it would be nice to hear from you if you can improve it without overcomplicating its simplicity.
// jQuery plugin Template
(function($){
$.myPlugin = function(options) { //or use "$.fn.myPlugin" or "$.myPlugin" to call it globaly directly from $.myPlugin();
var defaults = {
target: ".box",
buttons: "li a"
};
options = $.extend(defaults, options);
function logic(){
// ... code goes here
}
//DEFINE WHEN TO RUN THIS PLUGIN
$(window).on('load resize', function () { // Load and resize as example ... use whatever you like
logic();
});
// RETURN OBJECT FOR CHAINING
// return this;
// OR FOR FOR MULTIPLE OBJECTS
// return this.each(function() {
// // Your code ...
// });
};
})(jQuery);
// USE EXAMPLE with default settings
$.myPlugin(); // or run plugin with default settings like so.
// USE EXAMPLE with overwriten settings
var options = {
target: "div.box", // define custom options
buttons: ".something li a" // define custom options
}
$.myPlugin(options); //or run plugin with overwriten default settings
I've been googling and landed here so, I have to post some ideas: first I agree with #Raynos.
The most code out there that tries to build a jQuery plugin actually...is not a plugin! It's just an object stored in memory which is refered by the data property of a node/element. That's because jQuery should be seen and used as a tool side by side with a class library (to remedy js inconsistencies from OO architecture) to build better code and yes this is not bad at all!
If you don't like classical OO behaviour stick to a prototypal library like clone.
So what our options really?
use JQueryUI/Widget or a similar library that hides technicalities and
provides abstraction
don't use them because of complexities, learning curve and god knows future changes
don't use them becuase you want to insist on modular design, build small-increase later
don't use them because you might want porting/connecting your code with different libraries.
Suppose the issues addressed by the following scenario (see the complexities from this question: Which jQuery plugin design pattern should I use?):
we have nodes A, B and C that store an object reference into their data property
some of them store info in public and private accessible internal objects,
some classes of these objects are connected with inheritance,
all of these nodes also need some private and public singletons to work best.
What would we do? See the drawning:
classes : | A B C
------------------case 1----------
members | | | |
of | v v v
an object | var a=new A, b=new B, c=new C
at | B extends A
node X : | a, b, c : private
------------------case 2---------
members | | | |
of | v v v
an object | var aa=new A, bb=new B, cc=new C
at | BB extends AA
node Y : | aa, bb, cc : public
-------------------case 3--------
members | | | |
of | v v v
an object | var d= D.getInstance() (private),
at | e= E.getInstance() (public)
node Z : | D, E : Singletons
as you can see every node refers to an object - a jQuery approach - but these objects change wildely; they contain object-properties with different data stored in or, even singletons that should be...single in memory like the prototype functions of the objects. We don't want every object's function belonging to class A to be repeatedly duplicated in memory in every node's object!
Before my answer see a common approach I've seen in jQuery plugins - some of them very popular but I don't say names:
(function($, window, document, undefined){
var x = '...', y = '...', z = '...',
container, $container, options;
var myPlugin = (function(){ //<----the game is lost!
var defaults = {
};
function init(elem, options) {
container = elem;
$container = $(elem);
options = $.extend({}, defaults, options);
}
return {
pluginName: 'superPlugin',
init: function(elem, options) {
init(elem, options);
}
};
})();
//extend jquery
$.fn.superPlugin = function(options) {
return this.each(function() {
var obj = Object.create(myPlugin); //<---lose, lose, lose!
obj.init(this, options);
$(this).data(obj.pluginName, obj);
});
};
}(jQuery, window, document));
I was watching some slides at: http://www.slideshare.net/benalman/jquery-plugin-creation from Ben Alman where he refers at slide 13 to object literals as singletons and that just knock me over: this is what the above plugin does, it creates one singleton with no chance whatsover to alter it's internal state!!!
Furthermore, at the jQuery part it stores a common reference to every single node!
My solution uses a factory to keep internal state and return an object plus it can be expanded with a class library and split in different files:
;(function($, window, document, undefined){
var myPluginFactory = function(elem, options){
........
var modelState = {
options: null //collects data from user + default
};
........
function modeler(elem){
modelState.options.a = new $$.A(elem.href);
modelState.options.b = $$.B.getInstance();
};
........
return {
pluginName: 'myPlugin',
init: function(elem, options) {
init(elem, options);
},
get_a: function(){return modelState.options.a.href;},
get_b: function(){return modelState.options.b.toString();}
};
};
//extend jquery
$.fn.myPlugin = function(options) {
return this.each(function() {
var plugin = myPluginFactory(this, options);
$(this).data(plugin.pluginName, plugin);
});
};
}(jQuery, window, document));
My project: https://github.com/centurianii/jsplugin
See: http://jsfiddle.net/centurianii/s4J2H/1/

Categories