Stateful jQuery UI Widget Design Pattern
The standard design pattern for building stateful plugins in jQuery is the jQuery UI Widget Factory. This article provides an overview of the Widget Factory pattern, explaining how it manages state, handles component lifecycles, and encapsulates behavior. By standardizing initialization, option handling, event triggering, and teardown logic, this pattern allows developers to build robust, maintainable, and object-oriented user interface components without manually tracking plugin instances.
The jQuery UI Widget Factory
The jQuery UI Widget Factory, accessed via $.widget(),
provides an object-oriented base for creating stateful jQuery plugins.
When a widget is initialized on an element, the factory creates an
instance object, attaches it to the DOM element using jQuery's internal
data cache, and bridges method calls from the standard jQuery API to the
instance methods.
$.widget("custom.myWidget", {
// Widget implementation goes here
});The naming convention requires a namespace and a widget name
separated by a dot (e.g., "custom.myWidget"). This creates
both a jQuery method ($(selector).myWidget()) and a
constructor under the specified namespace
($.custom.myWidget).
Core Architecture and Lifecycle Methods
A stateful widget built with the factory relies on standardized lifecycle hooks:
options: An object containing default configuration values. When a widget instance is created, user-provided options are automatically merged with these defaults._create(): The primary setup method. It is executed once per element upon initial instantiation. All DOM manipulation, event binding, and one-time configuration should occur here._init(): Runs after_create()and also fires whenever the widget is called again with no arguments or only an options object. It is used for reset or re-initialization logic._destroy(): Cleans up DOM modifications, unbinds custom event listeners, and removes any injected styles or classes to return the element to its pre-widget state.
Method Visibility and Event Handling
The Widget Factory enforces encapsulation through naming conventions and internal utilities:
- Public vs. Private Methods: Methods prefixed with
an underscore (such as
_createor_render) are treated as private and cannot be invoked from the public jQuery API. Public methods (such asenable,disable, or custom public functions) can be invoked using string method calls:$(selector).myWidget("myMethod", arg1). - Event Triggering (
_trigger): Widgets communicate state changes to parent applications usingthis._trigger("eventName", eventObject, data). This automatically handles both callback execution defined in the options and standard jQuery DOM event dispatching (prefixed with the widget name, e.g.,mywidgeteventname). - Managing State (
_setOption): Changes made via theoptionmethod trigger_setOption(key, value). Overriding this method allows the widget to respond dynamically to configuration updates during runtime.
Implementation Example
Below is the standard boilerplate implementation demonstrating these concepts:
$.widget("custom.progressbar", {
// Default options
options: {
value: 0,
change: null,
complete: null
},
// Constructor/Initialization
_create: function() {
this.element.addClass("custom-progressbar");
this._update();
},
// Handling runtime option changes
_setOption: function(key, value) {
if (key === "value") {
value = this._constrain(value);
}
this._super(key, value);
this._update();
},
// Public method
value: function(val) {
if (val === undefined) {
return this.options.value;
}
this.option("value", val);
},
// Private worker method
_update: function() {
var progress = this.options.value + "%";
this.element.text(progress);
this._trigger("change");
if (this.options.value === 100) {
this._trigger("complete");
}
},
_constrain: function(val) {
return Math.max(0, Math.min(100, val));
},
// Cleanup logic
_destroy: function() {
this.element
.removeClass("custom-progressbar")
.text("");
}
});Using the Widget
Once registered, the widget maintains state per instance:
// Initialization
$("#my-element").progressbar({ value: 20 });
// Reading state via public method
console.log($("#my-element").progressbar("value")); // 20
// Modifying state
$("#my-element").progressbar("value", 50);
// Removing the instance and restoring DOM
$("#my-element").progressbar("destroy");