In this post, you will learn step-by-step to build your own custom, reusable, testable jQuery UI widget.
You will extend the jQuery library with custom UI code and then use it on a page. The initial plug-in will be trivial to demonstrate the jQuery Widget Factory pattern. You will provide properties that you can change to change the look of your widget and you will provide some methods that will respond to user input.
In this post example, you will learn how to create a simple click counter. Click a button, increase the count. The idea is to show you the steps to create a jQuery UI Widget.
The Widget Factory system manages state, allows multiple functions to be exposed via a single plugin, and provides various extension points.
Architecture
When you use Widget Factory, you encapsulate code used to generate the HTML, while separating the logic that is executed for the UI from the rest of your code. You can also provide customizability as needed for each consumer of the object.
In addition, you create tests for your widget to prove it can be reused in various projects.
Stateless vs Stateful
In the previous post, Building a Reusable Stateless jQuery Plugin, you learned how to build a stateless plugin using jQuery. With footnoter you could set the state, but the state of the control did not change with user interaction. The state of the footnote was moved from the span and put into a references section and stylized.
Some plugins are stateful, meaning they need to maintain state. They have full life cycles, maintain state, and react to changes. For example, a multi-select dropdown has state. At any moment, zero or more items can be selected.
If you are to write a stateful plugin from scratch, you would need to write lot of code dedicated to initialization and then handle your state management (and sometimes destruction).
This results in a lot of boilerplate for building stateful plugins. Even worse, each plugin author may manage life cycles and state differently, resulting in different API styles for different plugins. The widget factory aims to solve both problems, removing the boilerplate and creating a consistent API across plugins.
Prerequisites
Before you begin, you should be familiar with JavaScript and the the revealing module pattern in particular, jQuery particularly how to get and set elements in the DOM, and a bit about cascading style sheets to style the widget.
And you should understand how to write a jQuery Plugin because the Widget Factory uses the plug in code as its underpinnings. So review Building a Reusable Stateless jQuery Plugin or How to Create a Basic Plugin from the jQuery documentation.
Getting Started
Add jQuery UI to your project. In my case, I added jQuery UI using NuGet into the Scripts folder and call both jQuery and jQuery UI from an HTML page.
NOTE: You can call jQuery and jQuery UI from your favorite CDN: Google Developer CDN or Microsoft’s Ajax Content Delivery Network (there are others too). NOTE: For details on how to get jQuery and start jQuery for commercial product, see What is jQuery and How to Start using jQuery? and Load jQuery with Javascript and use jQuery. And in your ASP.NET projects, you will want to minify it.
You instantiate your widget, just as you did for a jQuery plug in.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Clicker</title> | |
</head> | |
<body> | |
<div id="myClicker"> | |
</div> | |
<script src="Scripts/jquery-2.1.3.js"></script> | |
<script src="Scripts/jquery-ui-1.11.3.js"></script> | |
<script src="Scripts/clicker/jquerywidget-clicker-0.0.1.js"></script> | |
<script> | |
$("#myClicker").clicker( { startingValue: 4 } ); | |
</script> | |
</body> | |
</html> |
Starting jQuery Widget Factory
Put your Widget in its own file. In this case, it is named jquerywidget-clicker-0.0.X.js in the Scripts/clicker folder.
Instantiate the Widget Factory as jQuery.widget
as part of jQuery UI 1.8; however, it can be used independently of jQuery UI. Call jQuery.widget
with two parameters: the name of the plugin to create and an object literal containing functions to support our plugin. The name of your plugin contains a namespace and the name of the plugin. And you should not use ui for your namespace name – it is reserved for jQuery UI. Your namespace should be one level deep.
When our plugin gets called, it will create a new plugin instance and all functions will be executed within the context of that instance. This is different from a standard jQuery plugin in two important ways. First, the context is an object, not a DOM element. Second, the context is always a single object, never a collection.
You receive two properties when your widget is created:
this.element
is a jQuery object containing exactly one element.this.options
is a hash containing key/value pairs for all of our plugin’s options.
You pass the option values a parameter to the plugin.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$("#myClicker").clicker( { | |
startingValue: 4 | |
}); |
The following code demonstrates a simple, stateful plugin using the jQuery UI Widget Factory.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function ($) { | |
$.widget("custom.clicker", { | |
_create: function () { | |
var startingValue = this.options.startingValue; | |
this.element.text("The starting value is " + startingValue); | |
} | |
}); | |
}(jQuery)); |
You should set the default value of any options. The following code fills out the sample by setting the default value for startingValue
option.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function ($) { | |
$.widget("custom.clicker", { | |
// Default options. | |
options: { | |
startingValue: 0 | |
}, | |
_create: function () { | |
var startingValue = this.options.startingValue; | |
this.element.text("The starting value is " + startingValue); | |
} | |
}); | |
}(jQuery)); |
Adding Properties and Methods
You will probably want a way work with your properties, such as getting the startingValue
and getting and setting a currentValue
. You access the methods by calling the name of the widget and passing it the name of the parameter. You can then pass it additional parameters.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
// initializing the startingValue | |
var myClicker = $("#myClicker").clicker({ startingValue: 4 }); | |
// getting the startingValue | |
alert(myClicker.clicker("startingValue")); | |
// setting the currentValue | |
myClicker.clicker("currentValue", 7); | |
// getting the currentValue | |
var newCurrentValue = myClicker.clicker("currentValue"); | |
alert(newCurrentValue); | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
startingValue: function() { | |
return this.options.startingValue; | |
}, | |
currentValue: function(currentValue) { | |
// No value passed, act as a getter. | |
if (currentValue === undefined) { | |
return this.options._currentValue; | |
// Value passed, act as a setter. | |
} else { | |
this.options._currentValue = currentValue; | |
this.element.text("The current value is " + this.options._currentValue); | |
} | |
} |
You can also create methods:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function ($) { | |
$.widget("custom.clicker", { | |
// Default options. | |
options: { | |
startingValue: 0, | |
_currentValue : 0 | |
}, | |
// _create and other methods to access properties ommitted .. | |
add: function (val) { | |
if (val === undefined) { | |
this.options._currentValue++; | |
} else { | |
this.options._currentValue += val; | |
} | |
} | |
}); | |
}(jQuery)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
myClicker.clicker("add"); | |
myClicker.clicker("add", 5); |
Setting Options
The _setOption
method is called internally whenever an option is set. So when an options value changes, you can respond. Pass just a name to use it as a getter, a name and value to use it as a single setter, or a hash of name/value pairs to set multiple values.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
myClicker.clicker( { currentValue: 100 } ); | |
newCurrentValue = myClicker.clicker("currentValue"); | |
alert(newCurrentValue); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
setting [currentValue]: 100 | |
update |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function ($) { | |
$.widget("custom.clicker", { | |
// Default options. | |
options: { | |
startingValue: 0, | |
_currentValue : 0 | |
}, | |
// code omitted | |
_setOption: function (key, value) { | |
console.log("setting [" + key + "]: " + value); | |
this.options[key] = value; | |
// calls my "private" method | |
this._update(); | |
}, | |
// Wiget Factory makes methods that begin with _ private and | |
// are not exposed to consumers | |
_update: function () { | |
console.log("update"); | |
this.element.text("The current value is " + this.options._currentValue); | |
} | |
}); | |
}(jQuery)); |
Callbacks
Callback functions allow the consumer of your widget to react to the new state. The following callback, named isOneHundredOne
is called when the _update
method is called and the _currentValue
is set to 101.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
var theClicker = $("#myClicker").clicker({ | |
isUpdated: function (event, data) { | |
console.log("isOneHundredOne"); | |
alert("Is " + data.myValue); | |
} | |
}); | |
theClicker.clicker({ newValue: 101 }); | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
setting [newValue]: 101 | |
update:101 | |
calling isUpdated | |
isOneHundredOne | |
isUpdated has been called |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function ($) { | |
$.widget("custom.clicker", { | |
// Default options. | |
options: { | |
newValue: 0 | |
}, | |
_create: function () { | |
this.element.text("The starting value is " + this.options.newValue); | |
}, | |
_setOption: function (key, value) { | |
console.log("setting [" + key + "]: " + value); | |
this.options[key] = value; | |
this._update(); | |
}, | |
_update: function () { | |
console.log("update:" + this.options.newValue); | |
this.element.text("The updated value is " + this.options.newValue); | |
if (this.options.newValue == 101) { | |
console.log("calling isUpdated"); | |
this._trigger("isUpdated", null, { myValue: this.options.newValue }); | |
console.log("isUpdated has been called"); | |
} | |
} | |
}); | |
}(jQuery)); |
Clicks
You can bind your widget to click events. Use the this._on()
method to bind your handler. This method is provided by the jQuery UI widget factory and will make sure that within the handler function, this
always refers to the widget instance.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
_create: function () { | |
… | |
this._on(this.elTitle, { | |
click: "_titleClick" // Note: function name must be passed as a string! | |
}); | |
}, | |
_titleClick: function (event) { | |
console.log(this); // 'this' is now the widget instance. | |
}, |
Destroy
It could make sense to allow users to apply and then later unapply your plugin.Use destroy
to undo anything your plugin may have done during initialization or later use. The destroy method is automatically called if the element that your plugin instance is tied to is removed from the DOM.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function ($) { | |
$.widget("custom.clicker", { | |
// the rest of the clicker is omitted | |
destroy: function () { | |
this.element.text(""); | |
// Call the base destroy function. | |
$.Widget.prototype.destroy.call(this); | |
} | |
}); | |
}(jQuery)); |
References
- Widget Factory sample in jQuery UI documentation
- Writing Stateful Plugins with the jQuery UI Widget Factory
- How To Use the Widget Factory in jQuery documentation
- Custom Drop-Down List Styling
- How to handle events in jQuery UI widgets
One thought on “Object JavaScript – Building Stateful jQuery UI Plugin Using Widget Factory”
Comments are closed.