30
The Widget Factory Version 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget. js file and we can use it to create our very own jQuery UI plugins with ease. Like jQuery itself, which provides the fn.extend() method for easily creating plugins, jQuery UI also provides mechanisms to make plugin creation easier and to ensure that the common API functionality is retained in new plugins. In this chapter, we will cover the following topics: The benefits of using the Widget Factory to write plugins Creating and invoking widget methods Resetting a widget Creating an example calculator widget Adding options to our example widget Making widgets themeable Supporting the common API methods Adding methods and responding to events in our widget Using our widget in a web page Introducing the Widget Factory If you've spent some time working with jQuery, you will no doubt have created a plugin of some description, or have used ones already created by others, as part of developing with the library. The recent jQuery plugins are stateless, where the extent of the interaction is limited to us calling them on an element. In most cases, this works perfectly well, but there's a large set of functionality that doesn't fit into the basic plugin pattern.

The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

  • Upload
    others

  • View
    5

  • Download
    0

Embed Size (px)

Citation preview

Page 1: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget.js file and we can use it to create our very own jQuery UI plugins with ease. Like jQuery itself, which provides the fn.extend() method for easily creating plugins, jQuery UI also provides mechanisms to make plugin creation easier and to ensure that the common API functionality is retained in new plugins.

In this chapter, we will cover the following topics:

• The benefits of using the Widget Factory to write plugins• Creating and invoking widget methods• Resetting a widget• Creating an example calculator widget• Adding options to our example widget• Making widgets themeable• Supporting the common API methods• Adding methods and responding to events in our widget• Using our widget in a web page

Introducing the Widget FactoryIf you've spent some time working with jQuery, you will no doubt have created a plugin of some description, or have used ones already created by others, as part of developing with the library. The recent jQuery plugins are stateless, where the extent of the interaction is limited to us calling them on an element. In most cases, this works perfectly well, but there's a large set of functionality that doesn't fit into the basic plugin pattern.

Page 2: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 2 ]

To help fill this gap, jQuery UI has implemented a more advanced plugin system. The new system manages state, allows multiple functions to be exposed via a single widget, and provides various extension points. This system is called the Widget Factory and is exposed as jQuery.widget as part of jQuery UI.

Reasons for using the Widget FactoryWhen writing jQuery plugins, we would normally add or override a property or method within jQuery.prototype (or $.fn, as it is more commonly written), and follow some conventions such as returning this, to allow the plugin to be chained with others within our code.

So what is the Widget Factory all about, and why should we use something that is hundreds of lines long? There are two main reasons why we should use the factory:

• Eliminate the need to produce a lot of boilerplate code for the plugin: In most cases, jQuery plugins are stateless; they perform a single action on an element and don't need to be preconfigured. In these instances, we would normally extend jQuery's prototype. If a plugin is stateful, it requires code to manage state and react to changes. The Widget Factory is a means to help the abstract code for common tasks such as creating or destroying widgets into a clear, consistent format that can be used across all jQuery UI plugins.

• Produce a consistent API: The Widget Factory creates a common standard framework that you use to create and destroy widgets, get and set options, and invoke methods, of which a large part of this is already implemented by the Factory. Using a defined standard makes it easier for others to use your widgets.

In summary, the Widget Factory also provides a number of benefits that we can utilize when developing widgets. These include the following:

• We can create our own namespace rather than using the default of ui, which is not a good practice (jQuery.al)

• The widget can use its own class (jQuery.al.filterable.prototype) and extend the existing jQuery prototype (jQuery.fn.filterable)

• We can create our default settings, then merge-in any user-defined settings• We can use the .data() method to store plugin instance in .data()• Methods are accessible via use of string—plugin( "foo" ) or

directly .foo()• The factory helps prevent against multiple instantiation of the same widget

Page 3: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 3 ]

• The structure for implementing creation, destruction, and changes within the plugin has been written as a series of events, so we can break the widget code into a series of smaller functions, making it easier to manage

• We can easily expose callbacks using the ._trigger method such as _trigger( "event" )

• We can use this to maintain proper scoping of elements throughout the plugin

Let's now take a look at how to create the basic skeleton of your widget file, which we will use later in this chapter to create an example calculator widget.

Creating a skeleton pluginWhen using the Widget Factory to create widgets within jQuery UI, you will notice that there are a number of common methods that will be used in the code. Let's take a look at each of them in more detail.

We begin with creating the namespace for our widget; this should use a custom name in the format of <custom-ui-name>.<name-of-widget>. The namespace will be created automatically, if it doesn't already exist. We also need to set the options that will be available to our end users, and which they can use to change the widget's configuration:

( function($,undefined) { $.widget( "al.progressbar", { // These options will be used as defaults options: { className : "" },

Next comes the initialization method for the widget; here is where we set up the widget when it is called from within our web pages:

_create: function() { ... },

We then need to implement our various methods that users can then call upon when using the widget in their code:

filter: function() { ... },

Page 4: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 4 ]

There will be methods that we need to implement in our widget, the scope of which is only intended to be within the widget itself and not for general consumption. We can implement these in the same manner as the public methods, but with the addition of the underscore before the name to denote a private method:

_hover: function() { ... },

Unless our widget only performs one operation or doesn't need to allow the user to change its configuration, we will very likely need to configure the widget with some options. We will have already created the array of options at the beginning of the plugin, we now need to respond to those option changes and process any requests.

We start with _SetOptions, which is called anytime the option() method is used:

_setOptions: function (options) { this._superApply(arguments); },

This in turn calls _SetOption, which processes each option change before calling the parent function:

_setOption: function( key, value ) { ... return this._superApply( arguments ); }

Finally we need to call _destroy to ensure our widget is reset back to the default, before either reinitializing it on screen or removing it completely:

_destroy: function() { return this._super(); }, });})(jQuery);

Setting options in a widgetThroughout this book, we've already seen that each of the different components in the library has a series of options that control widget features, the Widget Factory is no different. It has three base options you can use to configure the appearance of the widget; these are in addition to any that you add as custom options to your widget.

Page 5: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 5 ]

The available options to configure behaviors are shown in the following table:

Option Default value Usedisabled false Disables the widget if set to truehide null Sets if or how to animate the hiding of the widget—it

can accept boolean, string, number, or object as input

show null Sets if or how to animate the displaying of the widget—it can accept boolean, string, number, or object as input

Setting options on initializationIn addition, we can add our own options to the widget, in a similar fashion to that you might use for creating jQuery plugins. It's good practice to expose the defaults for each option, so that users can change them when configuring your plugin. We can then merge these into the standard options that are exposed by the widget, before destroying them when we are finished with the widget. A common pattern in jQuery plugins looks like this:

$.fn.plugin = function( options ) { options = $.extend( {}, $.fn.plugin.defaults, options ); // Plugin logic goes here.}; $.fn.plugin.defaults = { param1: "foo", param2: "bar", param3: "baz"};

The Widget Factory provides this functionality and takes it a bit further:

$.widget( "al.plugin", { // Default options options: { param1: "test", param2: "foo", param3: "sample" }, _create: function() {

Page 6: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 6 ]

// Options are already merged and stored in this.options // Plugin logic goes here. } });

Once we've set our options, we can then respond to changes in any of the options; we'll see how in the Watching out for changes section later in this chapter.

Creating the base widgetNow that we've set our initial options, we need to initiate our plugin. This is done using the _create() method. Here, we would call any methods that are needed to create our widget on screen and configure default values or settings for our widget:

Option Default value Use_create() jQuery This is the widget's constructor method, which we

use to initiate our widget. It doesn't require any parameters, but this.element and this.options are already set.

Setting widget methodsIn a similar fashion to other components in the library, the Widget Factory exposes a number of other methods that we can use when creating our widgets, in addition to the core methods of destroy, disable, enable, option, and widget.

Let's take a look at the some of the methods available, which are listed in the following table:

Method Returns Use_delay Number Invokes the specified functionality after a

specified delay._destroy jQuery Cleans up all common data, events, and so

on, and then delegates to _destroy() for custom, widget-specific, clean-up.

_focusable jQuery Sets up the specified element to apply the ui-state-focus class on focus.

Page 7: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 7 ]

Method Returns Use_getCreateEventData Object Returns an object which is passed as the

create event's data; the latter of which is triggered by all widgets.This event doesn't accept any arguments, nor does it provide any data in the event.

_getCreateOptions Object Allows a custom method to be used for defining options within a widget.

_hide jQuery Hides an element immediately, using either in-built animation effects, or through use of custom effects.

_hoverable jQuery Sets up an element to apply the ui-state-hover class on hover. The event handlers are automatically cleaned up once the widget has been destroyed.

_init jQuery Initializes a widget with no configuration options or with only an options hash.This is not to be confused with creating a widget, which is a different process. This should only be handled if a logical action should be performed on successive calls to the widget with no arguments.

_show jQuery Shows an element immediately, using built-in animation methods or custom effects.

_super jQuery Invokes a method of the same name from the parent widget, with any specified arguments. This performs the same action as a .call() method.

_superApply jQuery Invokes a method of the same name from the parent widget, but this time with an array of specified arguments. This performs the same action as an .apply() method.

disable jQuery Disables the widget.enable jQuery Enables the widgetoption Object Gets or sets the value(s) of one or more

options within the plugin.widget jQuery Returns a jQuery object that contains the

original element.

Page 8: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 8 ]

Responding to eventsIn addition, all plugins have two methods available for use, when there is a requirement to handle clicking of elements such as buttons. They are _on and _off respectively. Let's take a look at each in a little more detail:

Method Value returned Use_on jQuery Binds event handlers to a specified element(s)

within a widget_off jQuery Unbinds event handlers from a specified

element(s) within a widget

Triggering methodsAt various points, we will also need to trigger certain events in our widget. These can be handled by use of the _trigger method:

Method Value returned Use_trigger Boolean Triggers an event and its associated callback.

The option with the name equal to type is invoked as the callback.

Watching out for changesA key part of creating any widget is the setting of options within its configuration object. We first call _setOptions whenever an option() method is invoked, which in turn calls _setOption to alter the values for a configuration option:

Method Value returned Use_setOption jQuery Called by _setOptions() for each

configuration option within the widget to either get or set values for the configuration option.

_setOptions jQuery Called when the option() method is used to either set or get configuration values for options within the widget.

Page 9: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 9 ]

Reverting changesFinally, when we are all done with a widget, we must destroy it to prevent memory leaks, and return an element back to its original state before it can be reused.

Thankfully most of this can be handled using the destroy method, which leaves the need to handle any custom actions not covered by the Widget Factory such as removing custom styles:

Method Value returned Usedestroy jQuery Removes the widget functionality

completely and returns an element back to its pre-initialization state.

Invoking widget methodsWidgets created with the Widget Factory use methods to change their state and perform actions after initialization. There are two ways widget methods can be invoked—through the plugin created by the widget factory or by invoking the method on the element's instance object. Let's take a look at each in more detail.

Invoking a pluginTo invoke a plugin, we can pass it the name of the method as a string, as shown in this example which uses the dialog widget's close() method:

$(".selector").dialog("close");

If we need to pass it additional information, then this can be done in the form of parameters that we can pass using the option() method. Here, we're getting the value of the dialog's height option:

$(".selector").dialog("option", "height");

Invoking an instanceIn the background, each instance of every widget is stored on the element using jQuery.data(). To retrieve the instance object, we can call jQuery.data() using the widget's full name as the key:

var dialog = $(".selector").data("ui-dialog");

Page 10: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 10 ]

Once we have made a reference to the instance object, we can then invoke methods on it directly:

var dialog = $(".selector").data("ui-dialog");dialog.close();

You can read more about how to use .data() at http://api.jquery.com/jQuery.data/. There are plans in the works to make using this object easier to use from the next version of jQuery UI.

Return typesMost methods invoked through the widget's plugin will return a jQuery object so the method call can be chained with additional jQuery methods. This includes methods that return undefined when invoked on the instance, as shown in this example:

var dialog = $( ".selector" ).dialog(); // Instance invocation - returns undefineddialog.data( "ui-dialog" ).close(); // Plugin invocation - returns a jQuery objectdialog.dialog( "close" );

Therefore, plugin method invocation makes it possible to chain method calls with other jQuery functions, thus:

myDialog.dialog("close").css("color", "red");

The exception to this are methods that return information about the widget, such as this example using dialog's isOpen() method:

$(".selector").dialog("isOpen").css( "color", "red" );

This produces a TypeError error as isOpen(), returns a Boolean and not a jQuery object.

We've covered a lot of theory about how to create widgets using the Widget Factory. Let's now put some of this into practice and create a basic calculator widget.

Page 11: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 11 ]

Creating a calculator widget pluginWe've covered a lot of content on how you can use jQuery UI's Widget Factory to create widgets that either inherit from existing plugins or built from scratch. In this section, we'll create a very simple jQuery UI plugin so that we can see the conventions that should be adhered to and the different features we have at our disposal.

The widget we'll create will be a simple JavaScript calculator. This is based on a version written by the original author of this book, Dan Wellman, which he updated earlier this year to take advantage of jQuery 1.10. The following screenshot shows how the calculator will appear once we are finished:

Creating the plugin fileWe'll start by creating the base script file for our plugin. In a new file in your text editor, add the following code:

((function ($, undefined) {

var buttons = [ { label: 'MR' }, { label: 'MS' }, { label: 'MC' }, { label: 'Clear', classname: 'dw-calculator-clear dw-calculator-

Page 12: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 12 ]

clearfix', action: 'clear' }, { label: 'CE', action: 'clearEntry' }, { label: '*', classname: 'dw-calculator-times', action: 'operator' }, { label: 7, classname: 'dw-calculator-clearfix', action: 'number' }, { label: 8, action: 'number' }, { label: 9, action: 'number' }, { label: '+', classname: 'dw-calculator-plus', action: 'operator' }, { label: 4, classname: 'dw-calculator-clearfix', action: 'number' }, { label: 5, action: 'number' }, { label: 6, action: 'number' }, { label: '-', classname: 'dw-calculator-minus', action: 'operator' }, { label: 1, classname: 'dw-calculator-clearfix', action: 'number' }, { label: 2, action: 'number' }, { label: 3, action: 'number' }, { label: '/', classname: 'dw-calculator-divide', action: 'operator' }, { label: 0, classname: 'dw-calculator-clearfix dw-calculator-wide', action: 'number' }, { label: '.', classname: 'dw-calculator-dot', action: 'dot' }, { label: '=', classname: 'dw-calculator-equals', action: 'equals' } ];

$.widget('dw.calculator', { version: '0.0.1', options: { buttons: buttons, showOnCreate: false, }, _create: function() { }, destroy: function() { } });})(jQuery);

Save this file as jquery.dw.calculator.js in the js folder. jQuery UI plugins should always be named using this convention. This is just the shell of our plugin; we still have to add a good deal of code, but let's look at what we have so far. jQuery UI plugins should always be encapsulated in a self-executing anonymous function, which aliases the $ character to ensure that jQuery's noConflict() method works as intended.

Page 13: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 13 ]

We begin with a simple array that details the label to use for each button along with its action. We will use the former as part of rendering each button on screen, while the latter will be used to determine how to respond to each button click.

Next we use the $.widget() method to define our plugin. This method accepts two arguments; the first is the name of our widget, which should always be added to a custom namespace (you can add to the ui namespace, but it is not best practice), and the second is the options prototype, which contains the properties and methods that make the plugin function.

If we had been extending an existing jQuery UI widget, you will likely have seen a third argument passed; this is the base prototype or base widget that we are extending.

Widgets should normally have configurable options that the end developer can use to customize a particular implementation of the widget. These are defined using the key options in the second argument to the widget() method. The value of this property should be an object literal containing the actual options. The Widget Factory will handle interacting with the options object; all we need to do is define it.

In this basic example, we have several configurable options:

• showOnCreate: This determines (or will determine once we have added the code for it) whether the widget is displayed when the page loads

• buttons: This is an array of buttons that we will use to create our calculator• show/hide: This is used to control the display of the widget by clicking on an

element such as a button• beforeAddButton: This is a callback function that acts before buttons are

rendered on screen• shown/hidden: This is used to determine if the widget is visible or hidden

We then finish this section by creating a placeholder property in the options prototype. This we will use as an initialization function to build up any markup, create any variables that we will need at a global level, and assign any event handlers to functions later in the code.

After the _create function, we have also added a placeholder for the destroy method (this will be available for use by developers implementing the widget). In order to maintain consistency with the common API exposed by the rest of the library, we should always define this method.

Page 14: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 14 ]

Adding the _create functionThe code we looked at in the previous section forms the shell of the plugin. We'll now move on to add some more internal functions and fill out those already defined, starting with the _create function.

In your widget code, add the following code to the _create function:

_create: function() { this.element.addClass('dw-calculator ui-widget ui-corner-all'); this._createWrapper(); this._createButtons(); this._renderMarkup();

this._on({ 'click button': this._handleClick });

this.currentDisplay = []; this.runningDisplay = []; this.numberInput = false;},

The next property of the options prototype has the key _create.

This is used to add an initialization function for our widget so that we can build any markup required by the plugin, attach event handlers, or do any other processing that may be required.

In our _create function, we've added calls for _createWrapper(), which will create the shell of the calculator widget and determine if the widget should be displayed on loading the page. We've also created _createButtons(), which will determine if a beforeAddButton option has been set to override the standard buttons, otherwise will create the standard set of buttons based on details stored in the buttons array.

We then assign the _handleClick event handler to the click button event (which we will cover in more detail later in this chapter). We also set two global arrays to keep track of the current display, and one to confirm if the value being entered is a number, operator, or function.

Page 15: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 15 ]

Setting scope of functions in pluginsYou will have noted from the first part of our widget code that we prefixed the function supplied as the value of the _create function with an underscore.

This means that it can only be executed internally by jQuery UI; it is generally not accessible from outside of the widget. As nothing in JavaScript is ever truly private, in the sense that other languages can have public and private methods, this is not entirely true. But to invoke this code from outside of our plugin would require more advanced code (which is outside the scope of this book) and is not something that could be done accidentally.

Any additional functions that we want our widget to use only internally should be prefixed with an underscore. Any functions we wish to be exposed to developers as invocable methods should not have an underscore. This makes it incredibly easy to write robust code that only exposes the functionality that is required to use the widget.

You will see more use of the underscore for functions supplied to values within a widget, as we progress through this example widget.

Adding common API methodsIn the first part of our widget code, we have not added an enable or disable method. Prior to jQuery UI 1.10, it was necessary to add these in, to ensure that jQuery cleaned up after itself. It is no longer necessary do this, unless you need to override the standard method assigned for that purpose. We don't need to do this to enable or disable the widget, as the standard functionality will handle this perfectly.

We do need to override the default destroy() method, so go ahead and add the following code into the _destroy() method handler in our code:

_destroy: function () {

this.element.removeClass('dw-calculator');

this.element.empty();

this._super();

},

This handles the enabling or disabling of the method automatically for us when the widget is created; this makes adding either enable or disable methods redundant. The original method can be accessed through the widget prototype using the name of the method we wish to call, appended to $.Widget.prototype. We then provide any additional code specific to our widget.

Page 16: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 16 ]

In our _destroy function, we remove the classname we added to the element our plugin() method is called on, unbind the event handlers we attached, and then remove the DOM structure we created. This is done to tidy up our widget, which other widgets do, and to prevent memory leaks in the case of older versions of Internet Explorer and DOM inconsistency/integrity.

Adding the _createWrapper functionIn our _create function we created earlier, we inserted a reference to the _createWrapper. This function creates the shell of our calculator widget, assigning the right classes so that we can use Themeroller-created themes with our plugin.

Let's take a look at this function in more detail. Add the following as a new function immediately below the _create function we've just created:

_createWrapper: function() { var el = $('<div/>'), widget = this, displays;

this.shell = el.clone().addClass('dw-calculator-shell ui-widget-header ui-corner-all'); displays = el.clone().addClass('dw-calculator-displays ui-widget-content ui-corner-all').appendTo(this.shell); el.clone().addClass('dw-calculator-calculation').appendTo(displays); el.clone().text('0').addClass('dw-calculator-display').appendTo(displays);

if (!this.options.showOnCreate) { this._hide(this.element, this.options.hide, function () { widget._trigger('hidden'); }); }},

Here, we begin by assigning three variables, one of which includes a reference to the <div> element to which we will append our calculator plugin. We then create the base shell, by cloning the original <div> element, and adding the relevant style classes to it. We repeat the same process for the display, to which we also add the leading zero that we see when starting the calculator for the first time.

Page 17: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 17 ]

Adding the buttonsWe now need to add the buttons to our calculator. In the plugin code, add the following new function immediately after the _createWrapper function:

_createButtons: function () { var el = $('<button/>'), widget = this, btn, fname, container = $('<div/>').addClass('ui-helper-clearfix ui-widget-content ui-corner-all');

$.each(this.options.buttons, function (i, button) { if (widget._trigger('beforeAddButton', null, button)) { btn = el.clone().text(button.label); btn appendTo(container).button(); if (!!button.classname) { btn.addClass(button.classname); }

if (typeof button.action === 'string') { btn.data('action', button.action); } else if (typeof button.action === 'function') { fnName = 'custom' + i; widget['_' + fnName] = button.action; btn.data('action', fnName); } } });

container.appendTo(this.shell);},

We begin with creating a number of variables, including one to get the current instance of the widget (this), and one to create a new <button> instance which we will later render on screen. We then work through each of the elements of the buttons array to determine if any should be overridden with a new configuration (beforeAddButton is null), or if the default ones should be used.

Assuming that a function has not been specified for beforeAddButton, we then create each button in turn, add the appropriate styling if it doesn't already have it applied, and assign the correct action to it from the jQuery's data() store.

Page 18: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 18 ]

Rendering content on screenMoving on, it is now time to render all of the content on screen. In our widget file, add the following function immediately after _createWrapper:

_renderMarkup: function () { this.shell.appendTo(this.element);},

In this instance, you might feel disappointed not to expect more; the previous function actually does a lot of the heavy lifting for us! So in this instance, all we need to append the markup that we've created to the element on screen namely the calculator <div>.

Setting optionsNow that we have a configured a basic widget, we need to focus on providing a suitable capability for responding to any options that we may wish to set within our plugin.

One of the methods that are automatically available to our plugin and which we can use for this purpose is the option() method. The option() method allows you to get and set options after initialization. It works exactly like jQuery's .css() and .prop() methods: you can 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.

Let's first add in two new methods to our widget, before looking at each in detail. Add the following immediately after the previous function:

_setOptions: function (options) { this._superApply(arguments);},

The _setOptions() method is called as soon as the option() method is called from the page. This could be in a format similar to this:

$('#enable').click(function () { $('#calc').calculator('option', 'disabled', false);});

This calls _setOption for each individual option to get, set, or change values:

_setOption: function (key, val) { this._super(key, val); if (key === 'buttons') {

Page 19: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 19 ]

this.shell.find('button').remove(); this._createButtons(); this._renderMarkup(); } else if (key === 'disabled') { this.shell.find('button').button('option', key, val); }},

When used as a getter, the plugin will return the current value of the option that corresponds to the name that was passed in. If it is used as a setter, the plugin's _setOption method will be called for each option that is being set.

Displaying the calculator widgetAs we've provided the showOnCreate option earlier in the widget configuration so that the widget can be appended to the page but not shown immediately, we should provide a method that can be used to show the widget when required.

Add the following function directly after the _setOption() method:

show: function () { var widget = this;

this._show(this.element, this.options.show, function () { widget._trigger('shown'); });},

Our method is stored with the key show so developers can use this in the same way as the standard API methods we have already defined, for example, we could implement $("#el").calculator("show") in our code to show the calculator widget on the click of a button. All we need to do in this function is _trigger the shown method, which is a default part of the widget factory, and therefore comes with each widget created using the library.

Responding to eventsSo far, we've created our base widget and provided a number of methods for it to perform various actions on the content displayed in the widget. However, we need to add some more event handlers to respond to button clicks within our widget. Let's begin by adding our event handler _handleClick() to manage these requests.

Page 20: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 20 ]

Adding the button handlerIn the _create() function, we bind to a single event, namely click, using jQuery UI's new _on() method that comes with 1.10. We now need to add the handler function that will react to that event being detected by our plugin. After the show() method, add the following code:

_handleClick: function (e) { var btn = $(e.target).closest('button'), fn = btn.data('action'); this['_' + fn](e, btn);},

This function first determines which button has been clicked using jQuery's .closest() function, then works out which action it should have from the data store, before initiating the appropriate event handler to deal with the request.

Clearing the displayWe've implemented two buttons in our calculator to manage the clearing of the display either when resetting for a new calculation (the Clear button) or backtracking if an incorrect number has been entered (the CE button). The actions for these are first assigned in the button array when the widget is initialized; we use _handleClick to determine which action should be performed depending on the button clicked.

Go ahead and add the following two event handlers in to our widget file, immediately after the _handleClick handler:

_clear: function (e, ui) { this.currentDisplay = []; this.runningDisplay = []; this.numericalInput = false; this._updateDisplay(); this._updateRunningDisplay();},

_clearEntry: function (e, ui) { this.currentDisplay = []; this._updateDisplay();},

Page 21: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 21 ]

If the Clear button was clicked, we empty the runningDisplay and currentDisplay arrays, reset the numericalInput attribute back to false, and refresh the calculator's display (and running total) to zero. However, if we had clicked the CE button, then all that happens is that the current display is reset; we can then carry on with entering a new number.

Displaying numbers on screenIn the _handleClick event handler we've just created, the widget determines which button has been pressed, and whether the result is a normal digit, one of the function keys such as MS or MC, or one of the operators. If a number button has been clicked, we need to render that number to the display. To do this, add the following function to our widget, immediately after the _handleClick method:

_number: function (e, ui) { this.currentDisplay.push(ui.text()); this._updateDisplay(); this.numericalInput = true;},

If a number button is clicked, we push the new number to currentDisplay and refresh screen. This is so that if a number has already been pressed, then the display is updated to include the original number as well as the new number.

Adding actions to our operandsNow that we can see the results of clicking the number buttons, our calculator is only really of any use if we can perform some form of action on these numbers. In the buttons array at the head of our widget code, we defined the action operator for a number of buttons, namely the operators.

To perform the calculations is a two-part process. We first need to work out which operator button has been pressed and render the correct result on screen before calculating the result of our operation. Let's first add in the method that will handle the working out of which button was pressed and its rendering of the resulting operator on screen. Add this immediately after the _number function:

_operator: function (e, ui) { if (!this.currentDisplay.length && !this.runningDisplay.length) { this.currentDisplay.push(this.element.find('.dw-calculator-display').text()); } else if (this.currentDisplay.slice(0).reverse()[0] === '.') { this.currentDisplay.pop();

Page 22: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 22 ]

}

if (!this.runningDisplay.length || this.numericalInput) { this.runningDisplay.push([this.currentDisplay.join(''), ' ', ui.text(), ' '].join('')) } else if (!this.numericalInput) { var length = this.runningDisplay.length, newStr = this.runningDisplay[length - 1].replace(/[\*\-\+\/]/, ui.text());

this.runningDisplay.pop(); this.runningDisplay.push(newStr); }

this.numericalInput = false; this._updateRunningDisplay(); this._calculate();},

In this method, we first check to ensure that the display has not reached its limit of 18 characters; if this is not the case, we work out which operator button was pressed, and render it to screen accordingly, before calling _updateRunningDisplay to refresh the calculator's display, and _calculate to work out the result of our arithmetic operation.

Working out the final calculationWe've almost reached the end of creating our widget. So far, we've created the basic shell, rendered the buttons on screen, and enabled limited functionality to respond to clicks from the keypad.

We now need to implement arguably the most important function of the widget—the main calculation! This method ensures that when we press any key, followed by an operator, and then a second key, we can keep the running total on screen.

Add the following method after the previous function for _operator:

_calculate: function (final) {

var ops = { '+': function (x, y) { return x + y }, '-': function (x, y) { return x - y }, '*': function (x, y) { return x * y }, '/': function (x, y) { return x / y }, };

Page 23: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 23 ]

function sequentialCalc(str) { var sumArray = str.split(' '), left = +sumArray[0], x, length = sumArray.length;

for (x = 1; x < length; x = x + 2) { left = ops[sumArray[x]](left, +sumArray[x + 1]); }

return left; }

if (final) { var running = this.element.find('.dw-calculator-calculation').text(), display = this.element.find('.dw-calculator-display').text(), sum = [running, display].join('');

this.currentDisplay = [sequentialCalc(sum)]; this._updateDisplay(); this.runningDisplay = []; this.numericalInput = false; this._updateRunningDisplay();

} else if (this.runningDisplay.length > 1) {

// This handles the trimming of trailing operators, to // prevent NaN values appearing tmp = this.runningDisplay.pop(), trimmed = tmp.replace(/\s[\*\-\+\/]\s/, ''); this.runningDisplay.push(trimmed); this.currentDisplay = [sequentialCalc(this.runningDisplay.join(''))]; this.runningDisplay.pop(); this.runningDisplay.push(tmp); this._updateDisplay(true); }}

This may appear complex, so let's break it down. We begin with creating an array of operators in ops; each is assigned a function which returns the appropriate result when invoked. We then call a dedicated function called sequentialCalc(), which first pushes each element within our calculation into an array, converts the appropriate elements into numbers, and performs the arithmetic calculation based on the operator passed to it.

The results of the calculation are then pushed to the display, which is then refreshed.

Page 24: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 24 ]

Working with decimal placesNow that we can perform our calculation, surely our calculator widget is good to go, right? Not quite—we can't perform any calculations that involve decimal places, as our widget is not set to handle them.

Let's fix that by adding the following method, immediately below the previous one:

_dot: function (e, ui) {

var hasDot = false, x = this.currentDisplay.length;

if (!x) { this.currentDisplay.push('0'); }

while (--x >= 0) { if (this.currentDisplay[x] === '.') { hasDot = true; break; } }

if (hasDot) { return false; } else { this.currentDisplay.push('.'); this._updateDisplay(); }},

This is a very simple function, which first performs a check to see if the initial display is zero so that we can't set a decimal place at this point, after all, 0.0 is still zero! If we then click on the button to enter a decimal figure, we set a variable hasDot to true before pushing a decimal place to the currentDisplay and updating the screen accordingly.

Displaying a totalOur final operator that we need to add into the widget is the equals key, after all, what use is a calculator if you can't get the result of your calculations, right? Let's add that now. Add the following method at the end of our widget after _dot:

_equals: function (e, ui) { this._calculate(true);},

Page 25: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 25 ]

All we need to do at this point is to call the _calculate method, which we defined earlier in the code.

Updating the displayThe last step in creating our widget is to add in the functionality that we will use to update the display. We need to ensure that the display doesn't try to show more than 18 characters (a limitation of JavaScript); if it doesn't, the display is updated with any changes, as a result of being called from earlier in the widget.

Add the following code immediately after the _equals method:

_updateDisplay: function (reset) { if (!this.currentDisplay.length) { this.element.find('.dw-calculator-display').text(0); } else if (this.currentDisplay.length < 18) { this.element.find('.dw-calculator-display').text(this.currentDisplay.join('')); }

if (reset) { this.currentDisplay = []; }},

_updateRunningDisplay: function () { this.element.find('.dw-calculator-calculation').text(this.runningDisplay.join('')); this.currentDisplay = [];},

At this point, we've now completed our widget. It is functionally ready for use, although it won't look very pretty. Let's take care of that now by adding some basic styling.

Styling the widgetWe need to add some basic styling specific to our widget. A large part of this will be taken care of by the inclusion of our theme files, when the widget is used on a web page. However, there are still some styles that need to be defined in order for our widget to appear correctly.

Page 26: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 26 ]

In a new file in your text editor, add the following code:

.dw-calculator .ui-button-disabled { opacity: 1; }

.dw-calculator button { float: left; }

.dw-calculator-clearfix { clear: left; }

.ui-button.dw-calculator-clear span { padding: .4em 1.05em; }

.ui-button.dw-calculator-times span { padding: .4em .91em; }

.ui-button.dw-calculator-plus span { padding: .4em .8em; }

.ui-button.dw-calculator-equals span { padding: .4em .8em; }

.ui-button.dw-calculator-wide span { padding: .4em 2.5em; }

.ui-button.dw-calculator-dot span { padding: .4em 1.1em; }

.dw-calculator { width: 12.3em; padding: .2em; }

.dw-calculator-shell { padding: .2em; }

.dw-calculator-displays { padding: 1.25em .2em .2em; margin-bottom:

.2em; overflow: hidden; position: relative; }

.dw-calculator-calculation { width: 1000%; position: absolute; top:

.2em; right: .2em; font-size: .6em; text-align: right; }

.dw-calculator-display { font-size: .7em; text-align: right; }

Save this in the css folder as jquery.dw.calculator.css within the css folder in your project folder.

Using the widgetOur widget is now pretty much complete. There's still a lot of functionality we could add, and probably a number of bugs to fix, but this simple demonstration was more to see how to make a jQuery UI widget, and not how to make a JavaScript calculator.

To use the widget, a developer or web integrator could use the following code:

<!DOCTYPE html><html lang="en"><head> <title>jQuery UI Calculator Widget</title> <link rel="stylesheet" href="development-bundle/themes/redmond/jquery.ui.all.css"> <link rel="stylesheet" href="css/jquery.dw.calculator.css" /> <script src="js/jquery-2.0.3.js"></script> <script src="development-bundle/ui/jquery.ui.core.js"></script> <script src="development-bundle/ui/jquery.ui.widget.js"></script> <script src="development-bundle/ui/jquery.ui.button.js"></script> <script src="js/jquery.dw.calculator.js"></script> <script>

Page 27: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 27 ]

$(document).ready(function($){ $('#calc').calculator(); }); </script></head><body> <div id="calc"></div></body></html>

This page can be saved in the main project folder as calculator1.html. The basic calculator can be added and initialized using just the widget method:

$("#calc").calculator();

We could then add in a custom handler for the beforeAddButton event that we've defined in our plugin. Change the calculator configuration object as follows:

$("#calc").calculator({

beforeAddButton: function (e, ui) {

if (ui.label === 'MR' || ui.label === 'MS' || ui.label === 'MC') {

return false;

}

});

We could instead add in a handler for the shown option that we've defined:

$("#calc").calculator({

shown: function (e) {

console.log('Shown!');

}

});

We've defined a number of attributes in our options configuration. All of these can be configured using a similar approach to produce the results we need from the widget.

Using the methodsWe are not limited to merely defining options within the widget's configuration—we can instead decide to make changes at any time, once the original widget has been initialized.

Page 28: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 28 ]

As the widget inherits the base option method from within the factory, we can change options using the options method within jQuery UI. To see this in action, first add the following code after the calc <div> in calculator1.html:

<body> <div id="calc"></div>

<button id="change">Change!</button>

</body>

Next, add the following immediately after the configuration object for our calculator widget:

$(document).ready(function($){ $('#calc').calculator();

$('#change').click(function () {

$('#calc').calculator('option', 'buttons', [{ label: 'Test'}]);

});

});

Save the file as calculator2.html. If we preview the results in a browser, you will see an extra Change! button has been added. This overrides the existing button array within the widget's configuration to override it with a new set of buttons as shown in the following screenshot:

While in our demo, showing a single button isn't very useful, we could easily change the buttons array to show a different set of numbers such as Roman numerals in place of our standard buttons.

Page 29: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

Chapter 15

[ 29 ]

Let's implement something that is likely to be of more use. In calculator2.html, add the following markup, immediately after the Change! button:

<button id="change">Disable!</button>

Next, add the following event handler to disable our widget, immediately after the click handler for #change:

$('#disable').click(function () { $('#calc').calculator('option', 'disabled', true);});

Save the file as calculator3.html; if we preview the results in a browser, we can see the calculator widget appear faded, once the Disable! button has been pressed to disable the widget:

Note that we could equally have used this format; when adding our event handler, jQuery UI will accept both when changing options within a widget:

$('#disable').click(function () { $('#calc').calculator('disable');});

Page 30: The Widget FactoryVersion 1.8 of jQuery UI split the functionality that allows widgets to be easily created into a separate and standalone utility file. This is the jquery.ui.widget

The Widget Factory

[ 30 ]

We've only scratched the surface of what is possible when using the widget. It is strongly recommend reading the jQuery UI API documentation on the main website, to get a feel of what is possible; you can view the documentation at http://api.jqueryui.com/jQuery.widget/.

SummaryWe ended the chapter with an extensive look at the Widget Factory and how it allows us to quickly and easily create our very own jQuery UI plugins. We saw how easy it was to make our widget conform to the common API exposed by official jQuery plugins, how we can add custom methods for implementing developers to use, how to add protected functions that are only accessible from within our plugin, and how to add custom events that implementing developers can easily hook into.

The Widget Factory is only one way of creating stateful plugins. There are a few different models that can be used and each has their own advantages and disadvantages. The Widget Factory solves lots of common problems for you and can greatly improve productivity; it also greatly improves code re-use, making it a great fit for jQuery UI, as well as many other stateful plugins.