A jQuery Plugin Factory that provides a consistent foundation and API for building stateful jQuery widgets. Inspired by the jQueryUI Widget Factory.
##Notable Features
- Supports jQuery prototype namespacing and event namespacing
- Includes an elegant, promises-based, solution for plugins that rely on asynchronous behavior on initialization
- AMD support
- Implements a simple prototypal inheritance paradigm that allows you to reuse methods/properties from objects and jqfactory itself
- Saves all instances within the jQuery
data()
method and allows you to call plugin methods by passing a string to the plugin method to easily construct a public API - Provides over 10 public/private convenience methods/properties that are useful for plugin development
- Supports easy event binding/delegation (similar to Backbone.js views) and event removal
- Prevents multiple plugin initializations per element
- Provides a jQuery plugin pseudo selector to query for all DOM elements that have called the plugin
##Requirements jQuery 1.8.3+
##Browser support IE8+, Modern Browsers
##Quick Start
-
Download jQuery 1.8.3+ and jqfactory, create your plugin file, and include them all as
script
tags on an HTML page<script src="http://code.jquery.com/jquery-1.9.1.js"></script> <script src="./jqfactory.js"></script> <script src="./myawesomeplugin.js"></script>
-
Start building your jQuery plugin with jqfactory!
##Developer Guide
Note: All methods used in this section are referenced in the API documentation (further down)
-
Wrap your entire plugin in an immediately invoked function expression (IIFE)
(function($, window, document, undefined) { // Your plugin will go here }(jQuery, window, document));
-
Use the
$.jqfactory()
method to start creating your jQuery plugin.Note: The first argument of the
$.jqfactory()
method requires a namespace and base name.(function($, window, document, undefined) { // Your plugin will go here // namespace - person // name - greg $.jqfactory('person.greg', { // Your plugin instance properties will go here }); }(jQuery, window, document));
-
Include an
options
object that will store your default plugin options(function($, window, document, undefined) { // Your plugin will go here // namespace - person // name - greg $.jqfactory('person.greg', { // Your plugin instance properties will go here // Default plugin options options: { occupation: 'JavaScript Engineer', age: 24 } }); }(jQuery, window, document));
-
Include a
_create
method that will act as your plugin constructor. This method is only called once per element.Note: You can also create an
_init
method that will be called when your plugin is reinitialized(function($, window, document, undefined) { // Your plugin will go here // namespace - person // name - greg $.jqfactory('person.greg', { // Your plugin instance properties will go here // Default plugin options options: { occupation: 'JavaScript Engineer', age: 24 }, // Plugin Constructor _create: function() { // This is where you can set plugin instance properties this.fullname = 'Greg Franko'; }, // Plugin re-initialized _init: function() { // You can include any logic that you like when your plugin constructor is re-called } } }); }(jQuery, window, document));
-
Include a
_render
method that will handle all of your plugin dom manipulation on initialization (e.g. creating, appending, etc). The_render()
method is called after the_create()
method and only called once per element.Note: If the previous
_create()
method had returned a jQuery Deferred object, then the_render()
method would wait to be called until after the Deferred object was resolved.(function($, window, document, undefined) { // Your plugin will go here // namespace - person // name - greg $.jqfactory('person.greg', { // Your plugin instance properties will go here // Default plugin options options: { occupation: 'JavaScript Engineer', age: 24 }, // Plugin Constructor _create: function() { // This is where you can set plugin instance properties this.fullname = 'Greg Franko'; }, // Plugin re-initialized _init: function() { // You can include any logic that you like when your plugin constructor is re-called }, // Dom manipulation goes here _render: function() { // This is a perfect spot for creating and appending your plugin dom elements to the page var greg = $('<div/>', { 'class': 'developer' }); greg.appendTo('body'); } }); }(jQuery, window, document));
-
Include an
_events
object that will hold all of the event bindings for your plugin. These events are bound after the_render()
method is called and only called bound once per element.Note: If the previous
_render()
method had returned a jQuery Deferred object, then the_events
object would have bound all events after the Deferred object was resolved.(function($, window, document, undefined) { // Your plugin will go here // namespace - person // name - greg $.jqfactory('person.greg', { // Your plugin instance properties will go here // Default plugin options options: { occupation: 'JavaScript Engineer', age: 24 }, // Plugin Constructor _create: function() { // This is where you can set plugin instance properties }, // Plugin re-initialized _init: function() { // You can include any logic that you like when your plugin constructor is re-called }, // Dom manipulation goes here _render: function() { // This is a perfect spot for creating and appending your plugin dom elements to the page var greg = $('<div/>', { 'class': 'developer' }); greg.appendTo('body'); }, // Plugin event bindings _events: { // Delegated event that assumes the .face element is inside of the element that called the plugin '.face click': function (ev) { }, // The exclamation point tells jqfactory that this event should be directly bound (not delegated) '!.developer click': 'somePluginMethod', // Click event that is triggered on the element that called the plugin 'click': function() { }, // Special event that is triggered on the element that called the plugin 'disable': function() { } } }); }(jQuery, window, document));
-
Include a
_postevents
method that will be called after all events within the_events
object are bound. Within the_postevents()
method you are now free to trigger any events that are directly bound to elements. The_postevents()
method is only called once per element.(function($, window, document, undefined) { // Your plugin will go here // namespace - person // name - greg $.jqfactory('person.greg', { // Your plugin instance properties will go here // Default plugin options options: { occupation: 'JavaScript Engineer', age: 24 }, // Plugin Constructor _create: function() { // This is where you can set plugin instance properties }, // Plugin re-initialized _init: function() { // You can include any logic that you like when your plugin constructor is re-called }, // Dom manipulation goes here _render: function() { // This is a perfect spot for creating and appending your plugin dom elements to the page var greg = $('<div/>', { 'class': 'developer' }); greg.appendTo('body'); }, // Plugin event bindings _events: { // Delegated event that assumes the .face element is inside of the element that called the plugin '.face click': function (ev) { }, // The exclamation point tells jqfactory that this event should be directly bound (not delegated) '!.developer click': 'somePluginMethod', // Click event that is triggered on the element that called the plugin 'click': function() { }, // Special event that is triggered on the element that called the plugin 'disable': function() { } }, // All event listeners are now bound _postevents: function() { // You can now manually trigger directly bound events using the jqfactory _trigger() method this._trigger('.developer', 'click'); } }); }(jQuery, window, document));
-
Include any custom methods/properties that you like to complete your plugin! Remember that jqfactory provides you a bunch of helper methods including
_on()
,_off()
,_trigger()
,_superMethod()
,delay()
,disable()
,enable()
,destroy()
, andoption()
that you can use throughout your plugin.Note: If you override a default jqfactory method, you can still use the jqfactory method by using
_superMethod()
(function($, window, document, undefined) { // Your plugin will go here // namespace - person // name - greg $.jqfactory('person.greg', { // Your plugin instance properties will go here // Default plugin options options: { occupation: 'JavaScript Engineer', age: 24 }, // Plugin Constructor _create: function() { // This is where you can set plugin instance properties }, // Plugin re-initialized _init: function() { // You can include any logic that you like when your plugin constructor is re-called }, // Dom manipulation goes here _render: function() { // This is a perfect spot for creating and appending your plugin dom elements to the page var greg = $('<div/>', { 'class': 'developer' }); greg.appendTo('body'); }, // Plugin event bindings _events: { // Delegated event that assumes the .face element is inside of the element that called the plugin '.face click': function (ev) { }, // The exclamation point tells jqfactory that this event should be directly bound (not delegated) '!.developer click': 'somePluginMethod', // Click event that is triggered on the element that called the plugin 'click': function() { }, // Special event that is triggered on the element that called the plugin 'disable': function() { } }, // All event listeners are now bound _postevents: function() { // You can now manually trigger directly bound events this._trigger('.developer', 'click'); }, // Overriding the destroy method destroy: function() { // Calling the jqfactory `destroy()` method this._superMethod('destroy'); }, // Example of a custom method that I added to my plugin customMethod: function() { // Binding another event manually this._on('.hand click', function(ev) { console.log('hand clicked!'); }); } }); }(jQuery, window, document));
-
You can now call your plugin like this:
$('.test').greg();
If you had added a
true
third parameter (the enforceNamespace parameter) to the$.jqfactory()
method, then you would call your plugin like this:$('.test').person().greg();
To call API methods, you have two options:
Option 1
Retrieve the plugin instance using the jQuery
data()
method.Example:
$('.test').greg().data('person-greg').someMethod();
Option 2
Pass a string to the plugin after it is initialized
Example:
$('.test').greg(); $('.test').greg('someMethod');
Continue building on top of jqfactory and make great plugins!
##API
###Properties
_super - A reference to the parent widget object (can be overriden)
callingElement - The native DOM element that calls your plugin
$callingElement - The native DOM element that calls your plugin, wrapped in a jQuery object
element - The native DOM element that is used for all delegated events (Can be overriden)
$element - The native DOM element, wrapped in a jQuery object, that is used for all delegated events
options - The currently used plugin options
namespace - Used internally (e.g. `'person')
basename - Used internally (e.g. 'greg')
fullname - Used internally (e.g. 'person-greg')
eventnamespace - Used internally (e.g. '.person-greg')
jqfactory - A convenience property that contains the $.jqfactory.common object
###Methods
$.jqfactory(String namespace.name, Object properties, Boolean enforceNamespace)
-
The jqfactory is a simple function on the global jQuery object - jQuery.jqfactory - that accepts 2 or 3 arguments. The first argument to jqfactory is a string containing a namespace and name, separated by a period.
-
The namespace is mandatory, and it refers to the location on the global jQuery object where the widget instance properties will be stored. If the namespace does not already exist, jqfactory will create it for you.
-
The second argument is an object that is used to set the plugin instance properties.
-
The third argument is a boolean that determines if the namespace, provided in the first argument, is included on the jQuery prototype along with the plugin name. (eg: $('.example').bootstrap().tooltip())
_create() - Function
Description:
- The first method called when your jQuery plugin is initialized. Acts as your plugin constructor function. Supports deferred/promise objects.
_init(arguments) - Function
Description:
- The method called when your jQuery plugin is re-initialized. All user arguments are passed.
_render() - Function
Description:
- Called after the
_create()
method. Meant to be where all of your plugin dom manipulation initialization happens. Supports deferred/promise objects.
_events - Object
Description:
- All events within this object are bound after the
_render()
method is called.
Examples:
- Supports event delegation:
'.test click': function(){}
- Supports direct event binding:
'!.test click': function(){}
- Supports special events:
'superfantastic': function(){}
- Supports automatic binding that corresponds to the this.$element property:
'click': function(){}
_postevents() - Function
Description:
- Called after all events from the
_events
object are bound.
_on(String selector or Object, Function) - Function
Description:
- Binds/Delegates event handlers using the correct event namespace and binds the correct this context within the callback function
Examples:
- Single event binding :
this._on('.test click', function(){});
- Multiple event binding:
this.on({
'.test click': function(){},
'.test mouseenter': 'someMethod'
});
_off(String selector or Array) - Function
Description:
- Unbinds/Undelegates event handlers using the correct event namespace
Examples:
- Single event unbinding :
this._off('.test click');
- Multiple event unbinding (array):
this.off(['.test click', '.test mouseenter']);
- Multiple event unbinding (object):
this.off({ '.test click': function(){}, '.test mouseenter': function(){} });
_trigger(String selector or Array) - Function
Description:
- Triggers an event using the correct event namespace
Examples:
- Element Event Trigger:
this._trigger('.test', 'click');
- This.element Event Trigger:
this._trigger('click');
_superMethod(String methodName, arguments) - Function
Description:
- Call's a jqfactory method and correctly sets the context to the plugin instance
Examples:
- Method with no arguments:
this._superMethod('destroy');
- Method with one argument:
this._superMethod('option', 'exampleOption');
- Method with two arguments:
this._superMethod('option', 'exampleOption', true);
delay(String methodName or Function method, Number delay) - Function
Description:
Delays the execution of a method by the number of milliseconds specified in the second argument (essentially setTimeout). Defaults to 0.
Examples:
- Specifying a method with a string:
this.delay('destroy', 2000);
- Specifying a method with a function:
this.delay(this.destroy, 2000);
disable() - Function
Description:
Set's the disabled
option to to true
and triggers a special disable
event on the element that called the plugin. This method is meant to be overriden and then called within the overriden method. Like this:
Examples:
disable: function() {
// Your custom disable logic goes here
this._superMethod('disable');
}
enable() - Function
Description:
Set's the disabled
option to to false
and triggers a special enable
event on the element that called the plugin. This method is meant to be overriden and then called within the overriden method. Like this:
Examples:
enable: function() {
// Your custom disable logic goes here
this._superMethod('enable');
}
destroy() - Function
Description:
Helps with memory clean-up of your plugin by unbinding all events with your plugin's event namespace and removing all event handlers bound in the _events
object. Also triggers a special destroy
event on the element that called the plugin. This method is meant to be overriden and then called within the overriden method. Like this:
Examples:
destroy: function() {
// Your custom disable logic goes here
this._superMethod('destroy');
}
option() - Function
Description:
This method combines the functionality of setOption(String key, String val)
, setOptions(Object props)
, and getOption(String key)
methods. Depending on which functionality is used, a getOption
, setOption
, or setOptions
event is triggered on the element that called the plugin.
Examples:
- Get a single option:
this.option('someOption');
- Get a single nested option:
this.option('someOption.someNestedOption');
- Set a single option:
this.option('someOption', 'example');
- Set a single nested option (short-form):
this.option('someOption.someNestedOption', 'example');
- Set a single nested option (object-form):
this.option('someOption': { 'someNestedOption': 'example' });
- Set multiple options:
this.option({ 'someOption': 'example', 'someOtherOption': 'anotherExample' });
- Set multiple nested options (short-form):
this.option({ 'someOption': 'example', 'someOtherOption.someNestedOption': 'anotherExample' });
- Set multiple nested options (object-form):
this.option({ 'someOption': 'example', 'someOtherOption': { 'someNestedOption': 'anotherExample' } });
###Default Events
Note: All events are triggered with the event namespace (e.g. setOption.person-greg
)
disable - Triggered when the disable()
method is called
enable - Triggered when the enable()
method is called
destroy - Triggered when the destroy()
method is called
getOptions - Triggered when the option()
method is called with zero arguments
getOption - Triggered when the option()
method is called to retrieve a single option
setOptions - Triggered when the option()
method is called with an object literal argument
setOption - Triggered when the option()
method is called to set a single option
##FAQ
Should I use this instead of the jQueryUI Widget Factory?
- It depends. Both are great solutions, but here are some of the differences:
Namespacing:
jQueryUI Widget Factory - Does not support jQuery prototype namespacing. All plugins are place on the jQuery prototype using just their base name.
jqfactory - Supports jQuery prototype namespacing using the namespace and basename, or just basename.
Initialization:
jQueryUI Widget Factory - Calls the _create()
method once and calls the _init()
method on successive calls to the widget with no arguments.
jqfactory - First the _create()
method is called, then the _render()
method is called, _events
are bound, and then the _postaction()
method is called. Also supports returning promises inside of the _create()
and/or _render()
methods for plugins that depend on an asynchronous action during initialization. Calls the init()
method on successive calls to the plugin and passes all arguments.
Inheritance:
jQueryUI Widget Factory - Allows an individual widget to inherit from a function. By default, inherits from $.Widget
jqfactory - Allows an individual widget to inherit from an object. By default, inherits from $.jqfactory.common
.
AMD:
jQueryUI Widget Factory - Does not provide AMD support. Note: I talked to the jQueryUI Widget Factory core developer, Scott Gonzalez, recently and he said AMD support will be coming in a future release.
jqfactory - AMD is supported. The named AMD module, jqfactory
, is exported.
Events:
jQueryUI Widget Factory - Allows you to add and remove plugin event handlers using the _on()
and _off()
methods. Also allows you to trigger events and their associated option callbacks by using the _trigger()
method.
jqfactory - Allows you to easily group the majority of your event handlers inside of an _events
object (similar to Backbone.js Views). Event handlers can also be added and removed at different times using the _on()
and _off()
methods, and events can be triggered using the _trigger()
method. It is important to note that jqfactory does not natively support passing an event callback as an option.
_How do I change which DOM element my events property delegates from?
- If you would like your event handlers to delegate from a different element than one that called your plugin, then you can overwrite the
element
instance property. If you overwrite theelement
property, then you can still find out which element called your plugin by using thecallingElement
property.
How do I get my widget to inherit from an object?
- By default jqfactory widgets inherit from the $.jqfactory.common object. If you would like to inherit from a different object, then you can override the
_super
property like this:
$.jqfactory.create('example.plugin', {
_super: {
exampleMethod: function() {}
}
// The rest of your plugin goes here
});
When you override the _super
property, the $.jqfactory.common
object is merged with the _super
object. If your _super
object overrides a jqfactory method, you can still access the jqfactory method using the jqfactory
shortcut property.
What do I do if my plugin depends on asynchronous code to start?
- Both the
_create()
and_render()
methods support returning a jQuery deferred/promise object. Therender()
method will make sure to wait until the promise object returned by the_create()
method is resolved to be called. Similarly, all events will wait to be bound and the_postevents()
method will wait to be called until after the jQuery deferred/promise object returned by therender()
method is resolved.
__What is the point of the on() method if all events inside of the events property are already bound on initialization?
- There are use cases for dynamically binding event handlers only when certain conditions are met in your plugin. The
_on()
method allows developers to bind later, which can improve page load performance. Internally, the_on()
method will keep track of which dynamic events are bound, making sure that they get destroyed if the jqfactorydestroy()
method is called.
_When would I use the superMethod method?
- jqfactory provides you a bunch of helpful convenience methods such as
destroy()
,disable()
, and more. If you want to overwrite one of the helper methods to provide your own implementation, then inside of your overridden method you can call the jqfactory parent method using the_superMethod()
method.
Why would you use jQuery prototype namespacing?
- If you are creating more than one jQuery plugin, then you can place all of your plugins under one top-level namespace function on the jQuery prototype object. This greatly reduces the possibility of running into a naming collision with another jQuery plugin. A great example would be the Twitter Bootstrap library; it would be great if the Bootstrap library placed all of their individual jQuery plugins under a common bootstrap() function namespace.
Do you provide AMD support?
- Yes! jqfactory exports a named AMD module called,
jqfactory
, that allows plugin developers to provide AMD support by doing the following:
if (typeof define === 'function' && define.amd) {
// An AMD loader is on the page. List jquery and the jqfactory as dependencies and register your plugin as an anonymous AMD module.
define(['jquery', 'jqfactory'], function() {
// Call your plugin
});
} else {
// An AMD loader is not on the page. Call your plugin.
}
##Changelog
0.3.0
- July 26, 2013
- Inheritance - Widgets are now able to inherit from an object.
- API Methods - Methods can now be called by passing a string to the plugin method (jQueryUI Widget Factory style)
- Support string method names in the
events
object and_on()
methods (Similar to Backbone.js Views) - Triggered handy default events (e.g. disable, enable, destroy, setOptions, etc)
- All widgets now have a jqfactory property (useful if you are inheriting from an object that has overriden a jqfactory method)
0.2.0
- July 7, 2013
- First stable release - PRODUCTION READY
- Added Jasmine unit tests
0.1.0
- June 10, 2013
- Initial Release!
##Contributors Greg Franko
##License Copyright (c) 2013 Greg Franko Licensed under the MIT license.