Partager via


Chapter 3: jQuery UI Widgets

Introduction | Widget Fundamentals - Defining a Widget, Using a Widget | Managing Lifetime - Creation, Initialization, Destruction | Defining Options - When Options Change, Functions as Options | The Widget Method - Public Methods, Reusing an Instance, Using the Pseudo Selector | Private Members - Methods, Properties | Static Members | Events - Raising the Event, Binding Handlers, Event Naming, Options as Callbacks | Inheritance | Summary | Further Reading | Resources

Introduction

When building rich, client-side web applications, some of the visual elements on the page will naturally take on roles, responsibilities, and state. As more of these elements are added to the page, complexity will increase, so it's important for the design to support a maintainable code base. Maintainable solutions have at least three important characteristics: they have an intentional design, are modular, and they have unit tests. All of these characteristics should play to the strengths of the platform, language, and key parts of the environment.

The web browser is the platform, JavaScript is the language, and various JavaScript libraries represent key parts of the environment. Among other benefits, libraries such as jQuery and jQuery UI can simplify the code you write by:

  • Decreasing the amount of code you need to write and maintain.
  • Addressing typical challenges, such as browser compatibility issues.
  • Providing consistency for Ajax interactions, animations, and events.
  • Assisting in creating a maintainable code base through modularity.

A concept that is central to the visual parts of jQuery UI is the widget. According to the official jQuery UI project, jQuery UI "provides abstractions for low-level interaction and animation, advanced effects and high-level, themeable widgets, built on top of the jQuery JavaScript Library, that you can use to build highly interactive web applications." Widgets are objects attached to page elements that supply services for managing lifetime, state, inheritance, theming, and communication with other widgets or JavaScript objects.

One of the most valuable aspects of jQuery is that extensibility is built in and well defined. This extensibility is accomplished through the construction of jQuery plug-ins. Even though they have a number of extra features in addition to those in a typical jQuery plug-in, it's important to know that a widget is a jQuery plug-in. This may not be obvious because a widget is defined differently, but they are used the same way you use official jQuery methods and most custom plug-ins. Sometimes a plug-in is sufficient and other times a widget is more appropriate. When you need to apply behavior or state to individual elements and need to communicate between elements, widgets provide a number of capabilities you would otherwise have to write yourself. This chapter illustrates these capabilities. See the "Further Reading" section at the end of the chapter for more information about jQuery plug-ins and how to author them.

In this chapter you will learn:

  • How to define and apply widgets.
  • How to manage the lifetime of widgets.
  • How to define default options that permit overrides and change notifications.
  • How to use options for decoupling behavior and facilitating event subscriptions.
  • How to use private methods to improve the readability of the code.
  • How to define and use public methods, properties, and events.
  • How to inherit from a base widget.

The technologies discussed in this chapter are jQuery Plug-ins and the jQuery UI Widget Factory. The code examples used here largely come from the Widget QuickStart included with Project Silk.

Widget Fundamentals

If you know how to use jQuery, you know how to use a widget. In practical terms, a jQuery UI widget is a specialized jQuery plug-in. Using plug-ins makes it easy to apply behavior to the elements they are attached to. However, plug-ins lack some built-in capabilities, such as a way to associate data with its elements, expose methods, merge options with defaults, and control the plug-in's lifetime. Widgets have these capabilities built in.

A plug-in can be made to have the same features as a widget, but you must add these capabilities yourself. However, before you can use a widget, it must be defined. Once it has been defined, it can be applied to elements. Widgets are defined using the widget factory. When the widget factory is invoked, it creates a widget method on the jQuery prototype, $.fn, the same place that plug-ins and other jQuery functions are located. The widget method represents the primary interface for applying the widget to elements and using the widget after it's applied. This important concept is covered in more depth in "The Widget Method" later in the chapter.

Unlike other chapters, this chapter uses the Widget QuickStart for the code examples rather than the Mileage Stats Reference Implementation (Mileage Stats). The focus of the Widget QuickStart is to enable the client-side behavior for tagged keywords. When a user hovers over a keyword, the browser will display a pop-up list of popular links for that keyword from the Delicious.com bookmarking service. The following figure illustrates the QuickStart and the corresponding widgets.

The tagger and infobox widgets displayed

Hh404085.b6f70556-d278-4af7-97e2-0b7de0e61a19(en-us,PandP.10).png

The page accomplishes this through the use of two widgets:

  • tagger adds the hover behavior to the tagged keywords.
  • infoBox retrieves the links and controls the box that displays them.

For more information about the QuickStart or to walk through the process of building it, see Chapter 14, "Widget QuickStart."

Defining a Widget

The dependencies for a widget can be fulfilled with script references to the content delivery network (CDN) locations for jQuery and jQuery UI. Widgets often reside in their own .js file and are wrapped in an immediate function, as you can see in the following code example. This wrapper creates a JavaScript closure, which prevents new variables from being globally scoped. A single solution should allow no more than one global object to be created, as per well-accepted JavaScript practices.

The jQuery argument at the end of the following code example becomes the $ argument passed in, which allows you to use the common $ symbol to represent the jQuery function. Because there is no second argument, the undefined argument becomes truly undefined. Therefore the $ and undefined arguments reestablish their expected behavior inside the closure in case another script previously defined these variables as something else.

// Contained in jquery.qs.tagger.js
(function($, undefined) {
  $.widget('qs.tagger', {
    // definition of the widget goes here
  });
}(jQuery));

The call to $.widget invokes the widget factory, which makes the widget available for use. The first argument, qs.tagger, is the widget's namespace and name separated by a period (namespace.name). The name is used as the name of the widget method placed on the jQuery prototype. The second argument, called the widget prototype, is an object literal that defines the specifics of the widget. The widget prototype is the definition of the widget, and is used when the widget is applied to elements. The prototype is stored directly on the jQuery object under the namespace provided: $.qs.tagger.

Using a Widget

Once a widget has been defined, it's ready to be applied to DOM elements. To apply the widget to the matched elements, invoke the widget method just like you would other jQuery methods. The following code shows how to apply the tagger widget to all span elements with a data-tag attribute.

// Contained in startup.widget.js
$('span[data-tag]').tagger();

Because the widget method is used as the primary interface to the widget, it's not only called when initially applying the widget to the element, it's also used for calling methods and reading and writing options and properties on the widget. When widgets are applied to elements, an instance of the widget is created and stored inside each element. This is how the widget factory knows if a widget has already been attached to an element.

Managing Lifetime

There are three phases of a widget's lifetime that you can control: creation, initialization, and destruction.

Creation

The first time the widget is applied to an element, the widget's _create function is invoked. Method names preceded with an underscore have private scope by convention, which means they only expect to be invoked from inside the widget. The following code shows the _create method in the infobox widget.

// Contained in jquery.qs.infobox.js
_create: function () {
    var that = this,
        name = this.name;
    that.infoboxElement = $('<div class="qs-infobox" />');
    that.infoboxElement.appendTo('body')
    .bind('mouseenter.' + name, function () {
        mouseOverBox = true;
    })
    .bind('mouseleave.' + name, function () {
        mouseOverBox = false;
        that.hideTagLinks();
    });
},

Note

The that variable is defined to capture a reference to the widget so it can be accessed within the mouseleave event handler. Inside the event handler this refers to the element that raised the event, not the widget.
An alternative to using that is to use the jQuery.proxy function. This function, according to the jQuery API documentation at http://api.jquery.com/jQuery.proxy/, "takes a function and returns a new one that will always have a particular context." When used with event handlers, the widget can be used as the context, and event.target, which is normally this inside the event handler, can be used to reference the object that raised the event.

  • The _create method is the most appropriate place to perform a number of common tasks:
  • Adding classes to various elements the widget is attached to is the recommended way to apply styling, layout theming, and more to the widget.
  • Storing references to commonly accessed elements can increase performance when a particular set of elements is used from a number of methods. Simply create object-level variables for them once, and all other methods can use them. This is an accepted jQuery performance best practice.
  • Creating elements in the DOM is common for widgets that have requirements such as animations, effects, styling, accessibility, and cross-browser compatibility. As an example, consider the div.qs-infobox element created by the infobox widget.
  • Applying other widgets is recommended during creation when your widget relies on other widgets. Even if your widgets don't require each other, consider using the standard jQuery UI widgets from inside yours to add useful behaviors and interactions.

Initialization

The _init method is called after _create when the widget is first applied to its elements. The _init method is also called every time thereafter when the widget is invoked with no arguments or with options. This method is the recommended place for setting up more complex initialization and is a good way to support reset functionality for the widget if this is required. It's common for widgets to not implement an _init method.

Destruction

The widget's destroy method is used to detach a widget from an element. The goal of the destroy method is to leave the element exactly like it was before the widget was attached. Therefore, it's not surprising that common tasks are to remove any CSS classes your widget added to the element, detach any elements your widget added to the DOM, and destroy any widgets your widget applied to other elements. Here is the destroy method for the tagger widget.

// Contained in jquery.qs.tagger.js
destroy: function () {
    this.element.removeClass('qs-tagged');

    // if using jQuery UI 1.8.x
    $.Widget.prototype.destroy.call(this);
    // if using jQuery UI 1.9.x
    //this._destroy();
}

The last part calls the widget's base implementation of destroy and is a recommended practice when you provide your widget with a destroy method. The base destroy method will be called if you don't define one for your widget or if you explicitly call it, as in the code example above. The base implementation will remove the instance of the widget from the element and unbind all namespaced event bindings (this topic is discussed in more detail later in this chapter).

Defining Options

Options give widgets the ability to be extended with values and functions from the JavaScript code that creates and uses the widget. Options are automatically merged with the widget's default options during creation, and the widget factory supports change notifications when option values change.

Options and their default values are defined in the options property of the widget prototype, as shown below in the infobox widget.

// Contained in jquery.qs.infobox.js
$.widget('qs.infobox', {
    options: {
        dataUrl: ''
        maxItems: 10,
    },
    ...

To override default options during the creation of the widget, pass them in as an object literal to the widget method, as shown in this startup code of the widget.

// Contained in startup.widget.js
var infobox = $('body').infobox({
    dataUrl: 'http://feeds.delicious.com/v2/json/popular/'
});

To read the options from inside the widget, use the options property directly, as shown in the last line of this code.

// Contained in jquery.qs.infobox.js
displayTagLinks: function (event, tagName) {
    var i,
        that = this,
        options = that.options,
        url = options.dataUrl + tagName + '?count=' + options.maxItems,
        ...

Reading the values directly from options is acceptable when reading values from inside the widget, but you should not use this approach when changing the value of options. Instead, use the option method (without an 's').

// Code illustration: not in QuickStart
var max = this.option('maxItems');
this.option('maxItems', max + 4);

The option method is called with one argument when reading an option's value, two arguments when setting a value, and a single object hash when setting more than one option. The option method should always be used to change the value of options so that change notifications will work as expected. Changing the option directly on the options property bypasses the notification mechanism.

When Options Change

The options on your widgets should be aware that their values can change and should be prepared when they do. To respond to changes, widgets use the _setOption method. This method is called by the widget factory just after the value has been set on the options property. The Widget QuickStart doesn't have a need for _setOption; but, as an example, if the number of links in the infobox widget were configurable by the user, the widget might need to adjust the size of the box when maxItems changes.

// Code illustration: not in QuickStart
_setOption: function (name, value) {
    if(name === 'maxItems') { 
        this._resizeBoxForMaxItemsOf(value); 
    }
    $.Widget.prototype._setOption.apply(this, arguments);
},

In the code above, if maxItems is the name of the option being set, the _resizeBoxForMaxItemsOf method will be called. Rather than placing a lot of code in the _setOption method, you should place the logic in private methods. This allows you to call the logic from other places that might need it, such as _create. The last line calls the base widget's _setOption method. This will set the value of the option and will be useful for supporting a disabled state.

Note

All widgets support the notion of being disabled, whether they choose to implement it or not. The Boolean value is stored at this.options.disabled or $(selector).widget('option', 'disabled') if you're asking from the outside. In return for honoring this option (whatever that would mean for the user interface (UI) and behavior of your widget) the widget factory will default it to false and manage some CSS classes related to theming and accessibility.

The _setOption method is not called for the options passed in during the creation of the widget.

Functions as Options

Defining functions as options is a powerful way to decouple the widget from functionality better located elsewhere.

Note

The widgets in Mileage Stats use this approach for publishing and subscribing to global events by using their publish and subscribe options and getting data from the dataManager using their sendRequest option. To learn more about the pub/sub engine, see Chapter 8, "Communication." For more details on the dataManager, see Chapter 6, "Client Data Management and Caching."

For example, rather than forcing the tagger widget to know how to get a reference to the infobox widget and invoke the public methods on the infobox widget, the widgets can be kept free of any knowledge of each other by passing in the functions from the startup script, since the startup script already knows about both widgets. To set this up, the tagger widget defines activated and deactivated options.

// Contained in jquery.qs.tagger.js
$.widget('qs.tagger', {
    options: {
        activated: $.noop,
        deactivated: $.noop
    },

Just like normal options, these can either define defaults or omit them. The use of $.noop as a default value saves you the effort of having to ensure that the value isn't null before calling the option. Calling $.noop has no effect and won't throw any exceptions. The startup script will provide these options when it applies the tagger widget to the span elements, as shown here.

// Contained in jquery.qs.tagger.js
$('span[data-tag]').tagger({
    activate: function (event, data) {
        // call displayTagLinks() on infobox here
    },
    deactivate: function () {
        // call hideTagLinks() on infobox here
    }
});

In the code examples above, the options are defined inside the widget's implementation and passed in during creation. Later in this chapter you'll see how function-based options are used as callbacks for events.

The Widget Method

Well-designed objects have public interfaces that are intentional, intuitive, and focused. Widgets go one step further and provide a single method, referred to as the widget method, which is the entire public interface of the widget. The action the widget performs when you call this method depends on the number and type of arguments provided in the call. In addition to creating and initializing the widget, as shown earlier, the widget method is also used to do the following:

  • Invoke public methods
  • Read and write public properties
  • Read and write options

Public Methods

Public methods are defined on the widget prototype, as you can see here in the infobox widget. The public methods are hideTagLinks and displayTagLinks.

// Contained in jquery.qs.infobox.js
$.widget('qs.infobox', {
    hideTagLinks: function() {
        ...
    },
    displayTagLinks: function(event, tagName) {
        ...
    }

Widgets must be created before their methods can be called. The following calls to the infobox widget assume the widget method has already been called once to apply the widget to the body element. To call hideTagLinks from outside the widget, use a jQuery selector to match the element and pass the name of the method to the widget method as its only argument.

// Code illustration: not in QuickStart
$('body').infobox('hideTagLinks');

When you need to pass any arguments into the call, such as displayTagLinks, simply add the arguments after the method name.

// Code illustration: not in QuickStart
$('body').infobox('displayTagLinks', event, tag.name);

The option method covered earlier (in the section "Defining Options") is an example of a public method. When one argument is passed to it, the method will return the value of that option. When two arguments are passed, it will set the option specified in the first argument to the value of the second argument. When calling the option method from outside the widget, pass the method name as the first argument, the name of the option as the second, and the value as the third argument, as shown here.

// Code illustration: not in QuickStart
$('body').infobox('option', 'maxItems', 10);

Public methods can also return values by placing the expression on the right-hand side of the assignment operator (=). Returning a value from methods on infobox is reasonable because infobox is only attached to a single element. But be aware that if you call a method on a wrapped set that contains more than one element, the method will only be called on and returned from the first element.

In the examples so far, each time the widget method is invoked it is being called on the instance returned by the jQuery function, $(selector), which requires accessing the DOM. The next section recommends a couple of alternatives.

Reusing an Instance

Each time the jQuery function uses a selector to invoke the widget method, it must search the DOM. This has a negative impact on performance and is unnecessary because widget methods return a jQuery object, which includes the wrapped set of matched elements.

// Code illustration: not in QuickStart
var ib = $('body').infobox();  // queries the DOM
ib.infobox('displayTagLinks'); // does not query the DOM

Rather than use a selector with the jQuery method each time you need to call a method on a widget, create a variable when the widget is initially attached to the elements. The DOM will be accessed during this initialization, but it should be the only time you need to access it. In subsequent calls, such as the second line in the snippet above, you can call the widget method on the variable you created and it won't access the DOM.

Using the Pseudo Selector

In a situation where neither the selector nor the instance is available, there is still a way to obtain all instances of a particular widget. As long as you know the name of the widget, you can use a pseudo selector to get all instances that have been applied to elements.

// Contained in an older, more tightly coupled version of startup.js
$('body').infobox();

// Contained in an older, more tightly coupled version of jquery.qs.tagger.js
var ibInstance = $(':qs-infobox');
ibInstance.infobox('displayTagLinks',      // method name
                   $(this).text(),         // tag
                   event.pageY + offsetY,  // top
                   event.pageX + offsetX); // left

A pseudo selector begins with a colon, followed by the widget's namespace and name separated by a hyphen. The pseudo selector in the example above is :qs-infobox. Pseudo selectors have the potential to increase coupling between widgets, so be aware of this if you intend to use them.

Private Members

Private methods and properties have private scope, which means you can only invoke these members from inside the widget. Using private members is a good idea because they improve the readability of the code.

Methods

Private methods are methods that start with an underscore. They are expected to be accessed directly using the this keyword. Private methods are common and recommended.

Private methods are only private by convention and cannot be enforced. This means that if a widget isn't called according to the convention for calling public methods (described later), its private methods can still be accessed. The convention is easy and consistent, and the underscore makes it easy to distinguish between the public and private interface.

Properties

Methods are designated as private by using underscores. Unlike methods, properties on the widget prototype are private by default; they are not designated private by prepending an underscore. The reason properties don't need underscores is that they cannot be accessed through the widget method.

// Code illustration: not in QuickStart
$.widget('qs.infobox', {
    dataUrl: '',   // should only be accessed using this.dataUrl
    _maxItems: 10  // unnecessary; properties are already private
});

Because each element contains its own instance of the widget, the dataUrl property can be different for each element.

Clearly dataUrl is best exposed as an option, but if this was not a configurable option you would probably want to define it so that only one copy of the value is available to all instances of the widget. Let's call these static members.

Static Members

To define a variable that's available to all instances of the widget, but nowhere else, place them inside the self-executing function wrapper and above the call to the widget factory, as shown in the tagger widget.

// Contained in jquery.qs.tagger.js
(function ($) {

    var timer,
        hideAfter = 1000; // ms

    $.widget('qs.tagger', {
        ...

Because the timer variable is defined outside the widget prototype, only a single timer will be created and shared across all instances of the tagger widget. Functions that don't rely on the instance of the widget can also be defined here.

If you need access to static members from outside the widget, they can be added to the widget after the widget's definition. They are defined afterwards because they extend the widget, as you will see in a moment.

Let's make a fictitious change to the infobox widget to illustrate this by moving an isolated function to a more accessible location. Inside the displayTagLinks method in the infobox widget, a function variable called displayResult is defined.

// Contained in jquery.qs.infobox.js
var displayResult = function () {
    elem
    .html(html);
    .css({top: top, left: left});
    .show();
};

The variable, displayResult, is defined in displayTagLinks because this is the only method that uses it. In our fictitious change, let's say the infobox widget needs to make Ajax calls from other methods. That means the displayResult function will need to be moved so that it is available to all methods that need it. Defining it as a static member outside the scope of the widget is a way to make this happen.

// Code illustration: not in QuickStart
$.widget('qs.infobox', {
    ...
}); 
$.extend($.qs.infobox, {
    displayResult: function(elem, html, top, left) {
        elem
        .html(html);
        .css({top: top, left: left})
        .show();
    }
});

The $.extend method is used to merge the object passed as the second argument into the object passed as the first argument. Therefore, the displayResult method is merged into the prototype of the widget, $.qs.infobox. With displayResult defined here, the infobox widget can use it from anywhere, as shown in this code.

// Code illustration: not in QuickStart
// assume elem, html, top, and left variables were already defined
$.qs.infobox.displayResult(elem, html, top, left);

Events

Events are an effective way to communicate between widgets without forcing them to be tightly coupled. jQuery supports and extends the DOM event model and provides the ability to raise and handle custom events that are not defined in the DOM.

Raising the Event

A widget raises events by using the _trigger method. The first argument to _trigger is the name of the event you are raising. If the event you are raising originates from a DOM event, the DOM event can optionally be passed as the second argument. The third argument is any data to be passed to the event handler and is also optional. The following code sample shows one way the tagger widget might raise the activated event when the mouse enters the element.

// Code illustration: not in QuickStart
_create: function () {
    var that = this,
        tag = that.infoboxElement.text();

    that.infoboxElement
        .bind('mouseenter', function (event) {
            that._trigger('activated', event, {name: tag});
        });
},

In this fictitious code example, infobox is raising an activated event by binding to the mouseenter event of an element. You can also use bind, as well as the live and delegate methods, to handle events triggered from widgets.

Binding Handlers

Event handlers bind to widget events the same way as they bind to other events, although the name of the event is influenced by the widget's name.

// Code illustration: not in QuickStart
$('span[data-tag]').bind('taggeractivated', function(event, data) {
    // handle the event
});

Notice how the name of the event being bound to has the name of the widget prepended. This is the default behavior for event names. If you prefer a different name so that your code is more readable, this behavior can be changed, as shown in the following section.

Event Naming

The widgetEventPrefix property defines what will be prepended to the names of the events the widget raises. By default, the value is the name of the widget and is set by the widget factory. If you want to use something other than the widget name, simply define this property and provide an alternative value.

// Contained in jquery.qs.tagger.js
$.widget('qs.tagger', {

    widgetEventPrefix: 'tag',

    ...

When widgetEventPrefix has a value, it will be used instead of the widget name. The code that uses this widget and binds to its activated event will use the event name tagactivated.

Options as Callbacks

When options are defined as functions and the option name corresponds to an event name (without the prefix), they are referred to as callbacks. The _trigger method on the base widget will automatically invoke the callback whose name matches the event being raised.

// Contained in jquery.qs.tagger.js
widgetEventPrefix: 'tag',

options: {
    activated: $.noop,
    deactivated: $.noop
},

_create: function () {
    var that = this,
        name = this.name(),
        tag = this.element.text();

    this.element
        .bind('mouseenter.' + name, function (event) {
            that._trigger('activated', event, {name: tag});
        });
},

The JavaScript that creates the tagger widget can now define the handler for the activated and deactivated events when it creates the widgets.

$('span[data-tag]').tagger({
    activated: function (event, data) {
        infobox.infobox('displayTagLinks', event, data.name);
    },
    deactivated: function () {
        infobox.infobox('hideTagLinks');
    }
});

This allows the two widgets to interact without explicitly knowing about each other. Using this approach causes the script that invokes the widgets to act as connective tissue that describes a lot about the solution in a succinct, readable format.

Inheritance

Sometimes, when building a widget, another widget already has many properties and much of the functionality the new widget requires. The widget factory's inheritance support is designed for this case. For illustration purposes, consider the following widget.

// Code illustration: not in QuickStart
(function ($) {
    $.widget('a.container', {
        ...
        resize: function() {
            // resize width and height
        },
        ...
    });
}(jQuery));

If this widget was built elsewhere and you wanted to change its resizing behavior to an animation, a reasonable approach would be to inherit from a.container and override its resize method. Inheritance is accomplished by passing three arguments into the widget factory. The first argument is the namespace and name of the widget, the second is the prototype of the widget you want to extend from, and the third argument is the object you want to extend it with.

// Code illustration: not in QuickStart
(function ($) {
    $.widget('an.animatedContainer', $.a.container, {
        ...
        resize: function() {
            // override with animations
        }
    });
}(jQuery));

The only difference between the signature above and the signature usually used for defining widgets is the addition of the second parameter.

Inheritance is a useful tool when you are using a widget that almost does what you want it to do. In version 1.9 of jQuery UI, widgets can inherit from themselves. This makes it easy to add functionality to a widget for your application without the need of changing the original implementation. The jQuery UI bridge method allows you to retain the name of the original widget to be used with your specialized widget.

Summary

Using jQuery UI widgets is a great way to add modularity to client-side web applications. Widgets are objects that attach to page elements and supply services for managing lifetime, state, inheritance, theming, and communication with other widgets or JavaScript objects.

Options give widgets the ability to have state that is public, readable, writable, and callable. Options are automatically merged with the widget's default options during creation, and the widget factory supports change notifications when option values change. In addition, defining functions as options is a powerful way to decouple the widget from functionality better located elsewhere.

Widgets provide just a single method that represents the entire public interface of the widget. Widgets also allow for private methods that can only be invoked from within the widget.

jQuery supports and extends the DOM event model and provides the ability to raise and handle custom events that are not defined in the DOM. Widgets can trigger and handle these events and options can be used as callbacks.

Finally, widgets can inherit from other widgets, and in jQuery UI version 1.9, a widget can inherit from itself.

Further Reading

For more information about the QuickStart or to walk through the process of building it, see Chapter 14, "Widget QuickStart."

For more information about the pub/sub engine, see Chapter 8, "Communication."

For more information on the dataManager, see Chapter 6, "Client Data Management and Caching."

Widget Factory documentation on the jQuery UI wiki:
http://wiki.jqueryui.com/w/page/12138135/Widget-factory

jQuery Documentation for Plugins/Authoring:
http://docs.jquery.com/Plugins/Authoring

jQuery UI Developer Guidelines:
http://jqueryui.com/docs/Developer_Guide

jQuery UI source code:
https://github.com/jquery/jquery-ui

Resources

Microsoft Ajax Content Delivery Network (CDN) addresses:
https://www.asp.net/ajaxlibrary/cdn.ashx

Next | Previous | Home | Community