Object JavaScript – Building Stateful jQuery UI Plugin Using Widget Factory

imageIn 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.


<!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.


$("#myClicker").clicker( {
startingValue: 4
});

The following code demonstrates a simple, stateful plugin using the jQuery UI Widget Factory.


(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.


(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.


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


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:


(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));

view raw

addMethod.js

hosted with ❤ by GitHub


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.


myClicker.clicker( { currentValue: 100 } );
newCurrentValue = myClicker.clicker("currentValue");
alert(newCurrentValue);


setting [currentValue]: 100
update

view raw

console.log

hosted with ❤ by GitHub


(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));

view raw

setOption.js

hosted with ❤ by GitHub

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.


<script>
var theClicker = $("#myClicker").clicker({
isUpdated: function (event, data) {
console.log("isOneHundredOne");
alert("Is " + data.myValue);
}
});
theClicker.clicker({ newValue: 101 });
</script>


setting [newValue]: 101
update:101
calling isUpdated
isOneHundredOne
isUpdated has been called

view raw

console.log

hosted with ❤ by GitHub


(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.


_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.


(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


One thought on “Object JavaScript – Building Stateful jQuery UI Plugin Using Widget Factory

Comments are closed.