diff --git a/order.yml b/order.yml index 6ea213f9..b760f791 100644 --- a/order.yml +++ b/order.yml @@ -97,3 +97,7 @@ - jquery-mobile: - getting-started - theme-roller +- jquery-ui: + - how-jquery-ui-works + - why-use-the-widget-factory + - how-to-use-the-widget-factory diff --git a/page/jquery-ui.md b/page/jquery-ui.md new file mode 100644 index 00000000..e3c61368 --- /dev/null +++ b/page/jquery-ui.md @@ -0,0 +1,11 @@ +--- +title: jQuery UI +customFields: + - + key: "icon" + value: "magnet" +--- + +[jQuery UI](http://jqueryui.com) is a curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library. Whether you're building highly interactive web applications or you just need to add a date picker to a form control, jQuery UI is the perfect choice. + +jQuery UI contains many widgets that maintain state and therefore have a slightly different usage pattern than typical jQuery plugins. All of jQuery UI's widgets use the same patterns, so if you learn how to use one, then you'll know how to use all of them. \ No newline at end of file diff --git a/page/jquery-ui/how-jquery-ui-works.md b/page/jquery-ui/how-jquery-ui-works.md new file mode 100644 index 00000000..2dae7827 --- /dev/null +++ b/page/jquery-ui/how-jquery-ui-works.md @@ -0,0 +1,164 @@ +--- +title: How jQuery UI Works +level: Beginner +--- + +jQuery UI contains many widgets that maintain state and therefore have a slightly different usage pattern than typical jQuery plugins. All of jQuery UI's widgets use the same patterns, so if you learn how to use one, then you'll know how to use all of them. This document will walk you through the common functionality, using a progressbar widget for the code examples. + +## Initialization + +In order to track the state of the widget, we must introduce a full life cycle for the widget. +The life cycle starts when the widget is initalized. +To initialize a widget, we simply call the plugin on one or more elements. + +``` + $( "#elem" ).progressbar(); +``` + +This will initialize each element in the jQuery object, in this case the element with an id of "elem". +Because we called the `progressbar()` method with no parameters, the widget is initialized with its default options. +We can pass a set of options during initialization in order to override the default options. + +``` + $( "#elem" ).progressbar({ value: 20 }); +``` + +We can pass as many or as few options as we want during initialization. +Any options that we don't pass will just use their default values. + +The options are part of the widget's state, +so we can set options after initialization as well. +We'll see this later with the `option` method. + +## Methods + +Now that the widget is initialized, we can query its state or perform actions on the widget. +All actions after initialization take the form of a method call. +To call a method on a widget, we pass the name of the method to the jQuery plugin. +For example, to call the `value` method on our progressbar widget, we would use: + +``` + $( "#elem" ).progressbar( "value" ); +``` + +If the method accepts parameters, we can pass them after the method name. +For example, to pass the parameter `40` to the `value` method, we can use: + +``` + $( "#elem" ).progressbar( "value", 40 ); +``` + +Just like other methods in jQuery, most widget methods return the jQuery object for chaining. + +``` + $( "#elem" ) + .progressbar( "value", 90 ) + .addClass( "almost-done" ); +``` + +### Common Methods + +Each widget will have its own set of methods based on the functionality that the widget provides. +However, there are a few methods that exist on all widgets. + +#### option + +As we mentioned earlier, we can change options after initialization through the `option` method. +For example, we can change the progressbar's value to 30 by calling the `option` method. + +``` + $( "#elem" ).progressbar( "option", "value", 30 ); +``` + +Note that this is different from the previous example where we were calling the `value` method. +In this example, we're calling the `option` method and saying that we want to change the value option to 30. + +We can also get the current value for an option. + +``` + $( "#elem" ).progressbar( "option", "value" ); +``` + +In addition, we can update multiple options at once by passing an object to the `option` method. + +``` + $( "#elem" ).progressbar( "option", { + value: 100, + disabled: true + }); +``` + +You may have noticed that the `option` method has the same signature as getters and setters in jQuery core, such as `.css()` and `.attr()`. +The only difference is that you have to pass the string "option" as the first parameter. + +#### disable + +As you might guess, the `disable` method disables the widget. +In the case of progressbar, this changes the styling to make the progressbar look disabled. + +``` + $( "#elem" ).progressbar( "disable" ); +``` + +Calling the `disable` method is equivalent to setting the `disabled` option to `true`. + +#### enable + +The `enable` method is the opposite of the `disable` method. + +``` + $( "#elem" ).progressbar( "enable" ); +``` + +Calling the `enable` method is equivalent to setting the `disabled` option to `false`. + +#### destroy + +If you no longer need the widget, you can destroy it and return back to the original markup. +This ends the life cycle of the widget. + +``` + $( "#elem" ).progressbar( "destroy" ); +``` + +Once you destroy a widget, you can no longer call any methods on it unless you initialize the widget again. +If you're removing the element, either directly via `.remove()` or by modifying an ancestor with `.html()` or `.empty()`, +the widget will automatically destroy itself. + +#### widget + +Some widgets generate wrapper elements, or elements disconnected from the original element. +In these cases, the `widget` method will return the generated element. +In cases like the progressbar, where there is no generated wrapper, the `widget` method returns the original element. + +``` + $( "#elem" ).progressbar( "widget" ); +``` + +## Events + +All widgets have events associated with their various behaviors to notify you when the state is changing. +For most widgets, when the events are triggered, the names are prefixed with the widget name. +For example, we can bind to progressbar's change event which is triggered whenever the value changes. + +``` + $( "#elem" ).bind( "progressbarchange", function() { + alert( "The value has changed!" ); + }); +``` + +Each event has a corresponding callback, which is exposed as an option. +We can hook into progressbar's `change` callback instead of binding to the `progressbarchange` event, if we wanted to. + +``` + $( "#elem" ).progressbar({ + change: function() { + alert( "The value has changed!" ); + } + }); +``` + +### Common Events + +While most events will be widget specific, all widgets have a `create` event. +This event will be triggered immediately after the widget is created. \ No newline at end of file diff --git a/page/jquery-ui/how-to-use-the-widget-factory.md b/page/jquery-ui/how-to-use-the-widget-factory.md new file mode 100644 index 00000000..d567c8a9 --- /dev/null +++ b/page/jquery-ui/how-to-use-the-widget-factory.md @@ -0,0 +1,399 @@ +--- +title: How to use the widget factory +level: Beginner +--- + +To start, we’ll create a progress bar that just lets us set the progress +once. As we can see below, this is done by calling `jQuery.widget` with +two parameters: the name of the plugin to create and an object literal +containing functions to support our plugin. 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. + +``` + $.widget( "custom.progressbar", { + _create: function() { + var progress = this.options.value + "%"; + this.element + .addClass( "progressbar" ) + .text( progress ); + } + }); +``` + +The name of the plugin must contain a namespace, in this case we’ve used +the `custom` namespace. There is currently a limitation that exactly one +namespace must be used. We can also see that the widget factory has +provided two properties for us. `this.element` is a jQuery object +containing exactly one element. If our plugin is called on a jQuery +object containing multiple elements, a separate plugin instance will be +created for each element, and each instance will have its own +`this.element`. The second property, `this.options`, is a hash +containing key/value pairs for all of our plugin’s options. These +options can be passed to our plugin as shown here. + +``` + $( "
" ) + .appendTo( "body" ) + .progressbar({ value: 20 }); +``` + +When we call `jQuery.widget` it extends jQuery by adding a function to +`jQuery.fn` (the system for creating a standard plugin). The name of the +function it adds is based on the name you pass to `jQuery.widget`, +without the namespace; in our case “progressbar”. The options passed to +our plugin are the values that get set in `this.options` inside of our +plugin instance. As shown below, we can specify default values for any +of our options. When designing your API, you should figure out the most +common use case for your plugin so that you can set appropriate default +values and make all options truly optional. + +``` + $.widget( "custom.progressbar", { + // default options + options: { + value: 0 + }, + _create: function() { + var progress = this.options.value + "%"; + this.element + .addClass( "progressbar" ) + .text( progress ); + } + }); +``` + +### Calling plugin methods + +Now that we can initialize our progress bar, we’ll add the ability to +perform actions by calling methods on our plugin instance. To define a +plugin method, we just include the function in the object literal that +we pass to `jQuery.widget`. We can also define “private” methods by +prepending an underscore to the function name. + +``` + $.widget( "custom.progressbar", { + options: { + value: 0 + }, + _create: function() { + var progress = this.options.value + "%"; + this.element + .addClass( "progressbar" ) + .text( progress ); + }, + // create a public method + value: function( value ) { + // no value passed, act as a getter + if ( value === undefined ) { + return this.options.value; + // value passed, act as a setter + } else { + this.options.value = this._constrain( value ); + var progress = this.options.value + "%"; + this.element.text( progress ); + } + }, + // create a private method + _constrain: function( value ) { + if ( value > 100 ) { + value = 100; + } + if ( value < 0 ) { + value = 0; + } + return value; + } + }); +``` + +To call a method on a plugin instance, you pass the name of the method +to the jQuery plugin. If you are calling a method that accepts +parameters, you simply pass those parameters after the method name. + +**Note:** Executing methods by passing the method name to the same +jQuery function that was used to initialize the plugin may seem odd. +This is done to prevent pollution of the jQuery namespace while +maintaining the ability to chain method calls. Later in this article +we’ll see alternative uses that may feel more natural. + +``` + var bar = $( "
" ) + .appendTo( "body" ) + .progressbar({ value: 20 }); + + // get the current value + alert( bar.progressbar( "value" ) ); + + // update the value + bar.progressbar( "value", 50 ); + + // get the current value again + alert( bar.progressbar( "value" ) ); +``` + +### Working with options + +One of the methods that are automatically available to our plugin is the +`option` method. The `option` method allows you to get and set options +after initialization. This method works exactly like jQuery’s `css` and +`attr` 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. When used as a getter, the plugin will return the +current value of the option that corresponds to the name that was passed +in. When used as a setter, the plugin’s `_setOption` method will be +called for each option that is being set. We can specify a `_setOption` +method in our plugin to react to option changes. +For actions to perform independent of the number of options changed, we +can override `_setOptions`. + +``` + $.widget( "custom.progressbar", { + options: { + value: 0 + }, + _create: function() { + this.element.addClass( "progressbar" ); + this.refresh(); + }, + _setOption: function( key, value ) { + if ( key === "value" ) { + value = this._constrain( value ); + } + this._super( key, value ); + }, + _setOptions: function( options ) { + this._super( options ); + this.refresh(); + }, + refresh: function() { + var progress = this.options.value + "%"; + this.element.text( progress ); + }, + _constrain: function( value ) { + if ( value > 100 ) { + value = 100; + } + if ( value < 0 ) { + value = 0; + } + return value; + } + }); +``` + +### Adding callbacks + +One of the easiest ways to make your plugin extensible is to add +callbacks so users can react when the state of your plugin changes. We +can see below how to add a callback to our progress bar to signify when +the progress has reached 100%. The `_trigger` method takes three +parameters: the name of the callback, a jQuery event object that +initiated the callback, and a hash of data relevant to the event. The +callback name is the only required parameter, but the others can be very +useful for users who want to implement custom functionality on top of +your plugin. For example, if we were building a draggable plugin, we +could pass the mousemove event when triggering a drag callback; +this would allow users to react to the drag based on the x/y coordinates +provided by the event object. Note that the original event passed to `_trigger` +must be a jQuery event, not a native browser event. + +``` + $.widget( "custom.progressbar", { + options: { + value: 0 + }, + _create: function() { + this.element.addClass( "progressbar" ); + this.refresh(); + }, + _setOption: function( key, value ) { + if ( key === "value" ) { + value = this._constrain( value ); + } + this._super( key, value ); + }, + _setOptions: function( options ) { + this._super( options ); + this.refresh(); + }, + refresh: function() { + var progress = this.options.value + "%"; + this.element.text( progress ); + if ( this.options.value == 100 ) { + this._trigger( "complete", null, { value: 100 } ); + } + }, + _constrain: function( value ) { + if ( value > 100 ) { + value = 100; + } + if ( value < 0 ) { + value = 0; + } + return value; + } + }); +``` + +Callback functions are essentially just additional options, so you can +get and set them just like any other option. Whenever a callback is +executed, a corresponding event is triggered as well. The event type is +determined by concatenating the plugin name and the callback name. The +callback and event both receive the same two parameters: an event object +and a hash of data relevant to the event, as we’ll see below. Your +plugin may have functionality that you want to allow the user to +prevent. The best way to support this is by creating cancelable +callbacks. User’s can cancel a callback, or its associated event, the +same way they cancel any native event, by calling event.preventDefault() +or returning false. If the user cancels the callback, the `_trigger` +method will return false so you can implement the appropriate +functionality within your plugin. + +``` + var bar = $( "
" ) + .appendTo( "body" ) + .progressbar({ + complete: function( event, data ) { + alert( "Callbacks are great!" ); + } + }) + .bind( "progressbarcomplete", function( event, data ) { + alert( "Events bubble and support many handlers for extreme flexibility." ); + alert( "The progress bar value is " + data.value ); + }); + + bar.progressbar( "option", "value", 100 ); +``` + +## Looking under the hood + +Now that we’ve seen how to build a plugin using the widget factory, +let’s take a look at how it actually works. When you call +`jQuery.widget`, it creates a constructor for your plugin and sets the +object literal that you pass in as the prototype for your plugin +instances. All of the functionality that automatically gets added to +your plugin comes from a base widget prototype, which is defined as +`jQuery.Widget.prototype`. When a plugin instance is created, it is +stored on the original DOM element using `jQuery.data`, with the plugin +name as the key. + +Because the plugin instance is directly linked to the DOM element, you +can access the plugin instance directly instead of going through the +exposed plugin method if you want. This will allow you to call methods +directly on the plugin instance instead of passing method names as +strings and will also give you direct access to the plugin’s properties. + +``` + var bar = $( "
" ) + .appendTo( "body" ) + .progressbar() + .data( "progressbar" ); + + // call a method directly on the plugin instance + bar.option( "value", 50 ); + + // access properties on the plugin instance + alert( bar.options.value ); +``` + +You can also create an instance without going through the plugin method, +by calling the constructor directly, with the options and element +arguments: + +``` + var bar = $.custom.progressbar( {}, $( "
" ).appendTo( "body") ); + + // same result as before + alert( bar.options.value ); +``` + +### Extending a plugin’s prototype + +One of the biggest benefits of having a constructor and prototype for a +plugin is the ease of extending the plugin. By adding or modifying +methods on the plugin’s prototype, we can modify the behavior of all +instances of our plugin. For example, if we wanted to add a method to +our progress bar to reset the progress to 0% we could add this method to +the prototype and it would instantly be available to be called on any +plugin instance. + +``` + $.custom.progressbar.prototype.reset = function() { + this._setOption( "value", 0 ); + }; +``` + +### Cleaning up + +In some cases, it will make sense to allow users to apply and then later +unapply your plugin. You can accomplish this via the `_destroy` method. +Within the `_destroy` method, you should undo anything your plugin may +have done during initialization or later use. `_destroy` is called by +the `destroy` method, which is automatically called if the element that your +plugin instance is tied to is removed from the DOM, so this can be used for +garbage collection as well. That base `destroy` method also handles some +general cleanup operations, like removing the instance reference from the +widget's DOM element, unbinding all events in the widget's namespace from the +element, and unbinding generally all events that were added using `_bind`. + +``` + $.widget( "custom.progressbar", { + options: { + value: 0 + }, + _create: function() { + this.element.addClass( "progressbar" ); + this.refresh(); + }, + _setOption: function( key, value ) { + if ( key === "value" ) { + value = this._constrain( value ); + } + this._super( key, value ); + }, + _setOptions: function( options ) { + this._super( options ); + this.refresh(); + }, + refresh: function() { + var progress = this.options.value + "%"; + this.element.text( progress ); + if ( this.options.value == 100 ) { + this._trigger( "complete", null, { value: 100 } ); + } + }, + _constrain: function( value ) { + if ( value > 100 ) { + value = 100; + } + if ( value < 0 ) { + value = 0; + } + return value; + }, + _destroy: function() { + this.element + .removeClass( "progressbar" ) + .text( "" ); + } + }); +``` + +## Closing comments + +The widget factory is only one way of creating stateful plugins. There +are a few different models that can be used and each have 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 reuse, making it a great fit for jQuery UI as well as many +other stateful plugins. + +You may have noticed that in this article we used the `custom` namespace. The +`ui` namespace is reserved for official jQuery UI plugins. When building +your own plugins, you should create your own namespace. This makes it +clear where the plugin came from and if it is part of a larger +collection. diff --git a/page/jquery-ui/why-use-the-widget-factory.md b/page/jquery-ui/why-use-the-widget-factory.md new file mode 100644 index 00000000..ac2b6562 --- /dev/null +++ b/page/jquery-ui/why-use-the-widget-factory.md @@ -0,0 +1,80 @@ +--- +title: Why Use the Widget Factory +level: Beginner +--- + + +Writing jQuery plugins is as simple as adding a method to `jQuery.prototype` +(more commonly seen as `$.fn`) and following some simple conventions like returning `this` for chainability. +So why does the widget factory exist? And why is it hundreds of lines of code? + +In this document, we'll walk through the benefits of the widget factory and find out +when and why it makes sense to use it. + +## Stateless vs. Stateful Plugins + +Most jQuery plugins are stateless; they perform some action and their job is done. +For example, if you set the text of an element using `.text( "hello" )`, +there is no setup phase and the result is always the same. +For these types of plugins, it makes sense to just extend jQuery's prototype. + +However, some plugins are stateful; they have full life cycles, maintain state, and react to changes. +These plugins require a lot of code dedicated to initialization and 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. + +## Consistent API + +The widget factory defines how to create and destroy widgets, +get and set options, invoke methods, and listen to events triggered by the widget. +By using the widget factory to build your stateful plugins, +you are automatically conforming to a defined standard, +making it easier for new users to start using your plugins. +In addition to defining the interface, +the widget factory also implements much of this functionality for you. +If you're not familiar with the API provided by the widget factory, +you should read [How jQuery UI Works](how-jquery-ui-works.md). + +## Setting Options on Initialization + +Whenever you build a plugin that accepts options, +you should define defaults for as many options as possible, +then merge the user-provided options with the defaults on initialization. +It's also a good idea to expose the defaults so that users can even change the default values. +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 even takes it a bit further. +Let's see what this looks like with the widget factory. + +``` + $.widget( "ns.plugin", { + + // default options + options: { + param1: "foo", + param2: "bar", + param3: "baz" + }, + + _create: function() { + // options are already merged and stored in this.options + // plugin logic goes here + } + }); +``` \ No newline at end of file