This course is out of date and does not reflect our standards or industry best practices. We now recommend you take the Complete Intro to Web Development, v3 course.
Table of Contents
Part 1 - Extending jQuery with Plugins
Anatomy of a Plugin
Scott Gonzáles introduces himself and his qualifications for presenting the material. Visit http://scottgonzalez.com for details on his experience and where to follow him via social media, etc. Scott begins by creating a jQuery plugin from scratch by adding a method to $.fn. Any plugin will require iterating over selected DOM elements and chaining must remain intact. To ensure that a jQuery plugin is functionally chainable, it must contain return.this.each. Scott gives a live example using DOM elements inside your “each” callback to change text color to red. All jQuery plugins naturally apply implicit iteration to reduce the bulkiness of code. When implementing something simple, you could just use jQuery’s CSS method instead of iteration. It is imperative that chaining remains true when doing a traversal in your plugin. There are two methods of implementation - one that uses iteration and one that does not. You must compare functionality to ensure its correct application. Be careful if you are not using return.this.each to iterate because you may return an element that you didn’t intend to return. Return.this.each is the most reliable way to ensure you don’t break chaining. Remember to only change a return value if you mean to do it – but also take into consideration whether or not your users expect it. Example why never to add more than one level into a stack: Users won’t know what to do if .end doesn’t lead back to the original plugin.Plugin Tips
Creating a useful plugin requires accepting parameters that set how your plugin will function on a wider scale. Add parameters to your function using the example of creating a plugin to pass RBG values to set the color. There can be a problem with this implementation since it is actually calculating the HEX color for every element it affects – terribly inefficient. Note: You can improve performance by putting the calculation before you iterate. Option hashes can be used instead of ordered parameters, as demonstrated when applied to the RGB example. It’s a good idea to set a default for when parameters are not specified. To help ensure “clean” code, define your defaults up front. You may choose to expose your defaults so that users can define their own across-the-board. Manipulating the defaults of the individual plugin allows the user to easily update App defaults with their own choices.Extending Plugins
Extending plugins becomes useful when a plugin already exists and you want to increase its functionality using jQuery’s built-in “position” plugin. The built-in “position” plugin is set to only return information - you can make it pass left and top by using the “proxying” function. Proxying the function works anywhere you can store off a reference to the original function and is not unique to jQuery. Define a new position method by deciding on your parameter and set the values and the context to the jQuery object, not globally. Note: Call is used to discern if the position is set correctly by calling the original scope to see if default functionality still works. Easily extend plugins by proxying existing plugins and pass the new functionality. Store the old function into global variable instead of position_old =fn.position, as it leaves the referenced function open this way.Safeguarding Plugins
Safeguard your plugins by wrapping them in an “immediately-invoked function expression” – keeping variables from leaking to the global scope. Example: Calling color finds that it doesn’t still work – but why? – because here, color is defined on the old method but the defaults no longer exist. Deal with this bug by using jQuery.extend to extend function with the old function without losing your defaults. One rule of writing plugins: Never take more than one name! The purpose of this section has been to show potential problems with writing plugins.Plugins & State
Most plugins don’t contain state. jQuery changed how we interact with the DOM by easily chaining several tasks together to create more complex tasks. Example is a notification system chaining four simple elements to create one task. Without querying the DOM, though, you cannot know the state. Another example is using ajax and progressive enhancement to create a complex task, though you still don’t know the state of the plugin. Can you build a stateful plugin in jQuery Core? Yes. API for ajax reveals object state and allows you to embed callbacks to alert you when something happens; however, it’s not a jQuery plugin. Use the ajax API to mimic the plugin to get the “state” of your plugin, but you must manage it by initializing it, and the calls, after initialization. Define a new API for how plugins work in state - Option 1 Use multiple functions on $.fn. - easy to understand, but can become lengthy. Option 2 Use prefixes for method names, making it a little more streamlined and “categorized,” and names more searchable. Option 3 Return the plugin instance. Not the best idea as it breaks chaining, works on one element and method is ambiguous. Option 4 Use a single method that proxies to other methods. Downside is this can look a little strange to users. The number one aim is to not pollute jQuery. Accomplish this by using the jQuery UI API.
Part 2 - jQuery UI Overview
jQuery UI Introduction
The basic jQuery UI APIs used for all jQuery widgets are: initialization, options, calling other methods, events, callbacks. Events and callbacks may be bound to prefixed events or as options within the jQuery UI. The following sections demonstrate each of the components available in jQuery UI in a real-time coding session with Scott. jQuery UI has different types of plugins: interaction plugins that add behaviors to elements (draggable, droppable, resizable, etc.), and widgets which are new types of elements (dialogue box, accordion, slider, etc), and finally, effects, and animations.Draggable Plugins
Making an element draggable by using a mouse is as simple as using the word “draggable” in jQuery UI. Customizing the new “draggable” behavior becomes a function of manipulating the applicable options, even after initialization. The helper option can be used to clone the element if it is complex or merely preferred (ie: the drag-to-shopping-cart functionality). Use “dragstart,” “drag,” and “dragstop” to bind draggable events so you can customize the “draggable” element’s behavior. You can use the “handle” option for instances when the “draggable” element has other interactive content.Droppable Plugins
“Droppable” is the counterpart to a “draggable” event and integral to building a “drag-and-drop” interface. Easily add visual feedback when you start to drag or hover over an object by using activeClass or hoverClass options. Set-up your droppable element where each draggable element can be limited to one, or allowed several scenarios. As with the “draggable” plugin, the “droppable” plugin can be enabled or disabled, setting limits on how often the event can be repeated. Additionally, use the “tolerance” command to set the sensitivity, or reactivity, you prefer for your drag-and-drop feature to display. Instead of disabling, the draggable element can be destroyed by using the “destroy” method. These are common functionalities among widgets. If you want to differentiate “click” from “drag,” you will need to declare a distance in pixels, to show where the “drag” action will begin.Resizable Plugins
“Resizable” is the only plugin with a visual interface that indicates where to drag the element in order to change its size. Use the “handles” option to specify which ones to be used, to synchronize resizing with other elements and set max/min limits. When working with an image, typically you’ll want to maintain proportion– easily done by setting the aspectRatio option to true.Sortable Plugins
The “sortable” plugin reorders elements in a list using handles and helpers, and the placeholder option for visual feedback. “Sortable” can be set-up with “items” and “cancel” options to govern what is included with- or excluded from- sorting. For multiple lists, use the “connectWith” option to sort from one to another, as well as set directional limits for sorting between lists. If not keeping track of your changes using events, you can use “serialize” and “toArray” methods to see your order of elements at this point.Accordion Widget
This widget, per the example, combines a group of header and content panels so that appropriate style values can be applied to the group. Choose the “heightStyle” option for your “accordion” to fill according to its largest panel, to content size or the height of the parent element. Other “accordion” options include “active” to designate the primary panel, and “collapsible” to enable panels to be collapsed. Scott inserts a great deal of base mark-up to demonstrate that even when the structure changes, the widget is able to accommodate it. “Accordion” is set-up to anticipate what the user is trying to accomplish by creating flat panels secondary to headers. Add jQuery-generated icons by using the name of the icon you’re looking to use (ie: circle, triangle, etc.), or create your own. As with other widget interactions, “accordion” has events that, when activated, will notify when a panel is opened.Autocomplete Widget
“Autocomplete” is an auto-correct widget to assist active typing by the user. It’s fed content using the “source” option to accept an ‘array’ for local data, a ‘string’ for server-specific data or a ‘function’ for dynamic data. Build-in a delay for the user to finish typing – increasing it from the default of 300ms for more time or decreasing it for a quicker response. “Autocomplete” has events for every interaction - typing causes a search to start, the mouse or keyboard interaction selecting an item. It’s important to understand that the suggested data can consist of more than just the display value: ie: storing a database ID in a hidden field. For “Autocomplete” the widget method gives you access to the suggestions menu for added functionality.Button Widget
This widget can be applied to regular button elements, anchors or input of any type submitted, including checkboxes and radio buttons. For checkboxes and radio buttons, the “buttonset” method also provides visual grouping, instead of only styling each individual button. “Icon” option has pri. (left) and sec. (right) icons for use together, use existing text, hidden or be overridden using the “label” option. The widget method comes in handy, as the original markup might not match the transformed “button” markup. For a radio button, you will be able to access the “label” element serving as the visual part of the button.Datepicker Widget
The “datepicker” widget can be tied to inputs or show-up inline. Use “minDate” and “maxDate” options to restrict “datepicker” from any date prior to- or after- the current date. “Datepicker” contains internationalization support, using the “dateFormat” option to accommodate different locales and cultures. Anecdotal commentary on this widget: Marc, who originally wrote the “datepicker”, shares a bit its implementation story and Scott adds some of his own experience with the API design of jQuery UI and dealing with hundreds of feature requests and different usecases. In the end, don’t try to solve everyone’s problems!Dialog Widget
“Dialog” takes an existing element and wraps some UI around it to define the new element. It can use “draggable” and “resizable”, or disable both. Similar to “resizable”, the dimensions can be restricted using “minHeight”, “maxWidth”, “ minWidth” and “maxWidth” options. It can be stacked so that one may open another. Note: This widget wraps the existing element in a new element without cloning it.Modal Dialogs
Scott takes a short detour to explain problems with modal dialogs and z-index handling. You can close a dialog from a timeout using the “close” method.Progress Bar and Slider Widgets
This widget visually indicates progress status by setting the range using the “max” option and the “current value” (value option). There is a plan to make the progress support a false value, making this widget indeterminate for instances when the end value is not yet clear. Slider is useful whenever you need to input a number value and precision isn’t important. It can have single or multiple handles (ie: to input a range), highlighting the area between the two handles. The “orientation” option lets you display it vertically or horizontally and its “step” option lets you control the values for more granular inputs.Spinner Widget
Unlike “slider”, “spinner” uses precise number values, using “min”, “max”, “step” and “page” only for a single value set by a regular text input. By default, “spinner” increases by step or page, incrementally. This method can be disabled to get a static increment. This widget can also accommodate internationalization for numbers (ie: currency) using the program’s Globalization Library. As with other widgets, events exposed by “spinner” include “spinstart”, “spin”, “spinstop” and “spinchange”.Tabs Widget
Similar to “accordion”, “tabs” is paired and uses headers to link each to the panel using an “a-element” with a “hash-href” attribute. Unlike “accordion”, “tabs” supports loading the content of a panel through ajax, either each time a panel is opened, or just the first time. Dynamic content on the client side can be called to refresh the method any time after adding new content to the base markup. Note: Nesting tabs generally works, however, it can get confusing when nested tabs trigger events to the parent “tabs” widget. Nesting example: Demonstrated by heavily-nested “draggable”, within an “accordion”, within an “accordion”, within “tabs”, within a “dialog”, which is also “draggable”.Position Widget
“Position” eliminates manual relative positioning math - setting one element relative to another, accounting for dimensions to ensure the element is visible. This widget has built-in “collision detection” that supports both “flipping” and “fitting”, or even a combination of both. You can also use “option” to animate the actual positioning, including a “bounce” effect.
Part 3 - Widget Factory
Widget Factory Introduction
The Widget Factory (TWF) is the underlying abstraction for all jQuery UI widgets and interactions (except “datepicker”, circa the recording). Example: Building a colorbox widget using TWF where defaults are specified in an “options” property and “_create” serves as the constructor. Note: TWF depends only on jQuery, but doesn’t have any dependencies on other modules within jQuery UI. TWF merges options with defaults, creates a widget instance for each selected element and allows you to ask for its state without a DOM query.Widget Factory Methods
“Reading” options works, though extra code is necessary after initialization and is done by overriding the “_setOption” method. Private methods (ie: _prefix) can’t be called through the plugin wrapper. Public methods must not have the underscore to ensure availability. Example: Using “_trigger” to create a custom event, Scott passes randomly-generated hex colors used in the running example. Implement the “prevention” of a default behavior via custom events, by looking at a false return value from “_trigger”. Deal with events within a widget using “_bind” (Note: in 1.9 Final it’s called “_on”).Advantages of the Widget Factory
Advantages of using jQuery’s event binding methods: Keeps the widget instance context intact, doesn’t callback when it is disabled and automatically cleans up events. Use the “data” method to eliminate the need to call methods through the plugin wrapper while also being able to access private methods. Plugin wrapper vs. direct access of plugin instance: Using the wrapper is most familiar to users, although direct access is sometimes preferred. “Inheriting” from existing widgets is done the same way as extending existing widgets, applying a second argument to the $.widget method. Extending widgets with versions 1.8 and 1.9 is very easy - simply “stick” new methods on existing widgets. Marc briefly touches on writing his own abstraction similar to TWF, but ended up with throwing it away and actually using TWF instead!