Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Leveraging the jQuery UI Widget Factory with Project Silk
Andrew Wirick | April 11, 2012
As a Web developer, you want to build an application with a rich, immersive user interface that allows your customers to interact with your app in an engaging and responsive way. You also want to design your application so that it is maintainable, testable and secure. When evaluating the technologies that are available, you need to know how a given technology will perform in real, complex scenarios. Simple proof-of-concept apps are everywhere, but an end-to-end, nontrivial Web application that you can poke and pry and put through the paces is hard to find.
Built by the Microsoft patterns & practices team to address large-scale, front-end Web development, Project Silk includes a realistic Web application called Mileage Stats and a book describing its design and implementation. Taken together, they provide insight and guidance about factors you should consider when creating your immersive, responsive, client-centric Web applications. The book is quite comprehensive, and I encourage you to read it.
Project Silk contains a wealth of information that can't be covered in a single article, so here I’ll offer a brief look at the architecture and then dive in to explain how the team used the jQuery UI Widget Factory to handle certain application concerns.
(Full disclosure: I assisted with the initial training and architectural review of the project.)
Widgets and the Architecture
To show why jQuery UI widgets proved useful, I’ll first address where widgets fit into the Mileage Stats application architecture.
Mileage Stats allows users to record and analyze gas mileage and other data about their vehicles. Users enter data after filling their gas tanks, and the application calculates their cars’ gas mileage and also creates reminders for future maintenance. Information is presented in overview as well as in interactive chart views for even better insight.
The Project Silk architecture chapter begins with a diagram that illustrates the responsibilities of front-end components, shown in Figure 1.
Figure 1 Project Silk Front-End Components
Note that the diagram doesn't have any specific technologies; it merely outlines the components needed to create the rich user experience that is the goal of the application. (Pub/sub is a specific communication mechanism, but it could be implemented using one of many technologies that support it.)
Later, technologies are chosen to implement the responsibilities of each component, as Figure 2 shows.
Figure 2 Technologies Selected for Each Component
The team chose the components after many practical considerations. We asked these questions before deciding which technologies to use:
- How closely does a given technology fit into the architecture ideal?
- How many technology dependencies do we want to introduce?
- What level of support can we get for each of the chosen technologies?
- How does the team's expertise line up with the chosen components?
You can try the same exercise with other technologies. How would backbone.js fit? What about the Dojo Toolkit? These technologies could come with some additional dependencies, but they might be worth using nevertheless.
You can read more about laying out the application architecture here.
So why use the jQuery UI Widget Factory to address the widget concerns in the app? First, let's briefly look at the widget factory, and then we’ll turn our attention to what made it a practical technology for Mileage Stats.
Managing State with jQuery UI Widgets
jQuery provides a useful extension mechanism in the form of plug-ins. Plug-ins are ideal for building stateless functionality, but with jQuery alone, if you need components that maintain state, you're out of luck; the jQuery core does not support state management.
Enter the jQuery UI Widget Factory. Developed by the jQuery UI team, the widget factory helps manage certain responsibilities of all jQuery UI interactions and widgets. These responsibilities include:
- Initialization and teardown callbacks
- Merging defaults with options set by the user
- Public methods that can be used to explicitly update state at any time
- jQuery event wrappers that allow you to easily write custom events
- Consistent default scoping (defining the this keyword)
Fortunately, the widget factory is available for you to create your own widgets. While the published widgets are very generic (e.g., dialog box), you can use the widget factory to create widgets specifically tailored to your application.
For Project Silk, the team identified a slew of UI components that fit nicely into the stateful plug-in model. These are shown in Figure 3.
Figure 3 UI Components for Mileage Stats
Each widget handles its own animations between states. You can see some of the same widgets with a different state after animations in Figure 4.
Figure 4 State Changes for Mileage Stats widgets
The widgets are also responsible for controlling their own data source and requerying for the appropriate data. The project includes a great primer and a comprehensive look at using the jQuery UI Widget Factory. If you are not familiar with jQuery UI widgets, it would be very helpful to read the primer before you tackle the next section of this article.
Now let's focus on a particular widget to see how the team addressed concerns in code.
The Fill-Ups Widget
The fill-ups widget is responsible for taking in fill-up data and displaying the data recorded by the user. Figure 5 shows the fill-up widget in action within the application.
Figure 5 The Fill-Ups Widget
The fill-ups widget provides a glimpse into some specific coding practices that can help you build a more maintainable application.
Fill-Ups Widget Interface
The application uses the fill-ups widget first by initializing it. The initialization call is similar to other jQuery plug-in calls:
$( "someSelector" ).fillups();
You could also initialize the widget by collecting options via a JavaScript object:
$( "someSelector" ).fillups({
selectedVehicleId: 1
});
If an interaction on the page results in a new vehicle being selected for the fill-ups widget, the application updates the widget through the options method:
$( "someSelector" ).fillups( "option", "selectedVehicleId", 2 );
If the widget is updated in this fashion, we expect it to update all its state appropriately.
Options Hash
Looking inside the fill-ups widget implementation, you run into the options hash with the defaults, as shown in Figure 6.
options: {
// Default to $.ajax when sendRequest is undefined.
// The extra function indirection allows $.ajax substitution because
// the widget framework is using the real $.ajax during options
// initialization.
sendRequest: function (ajaxOptions) { $.ajax(ajaxOptions); },
// option providing the event publisher used for communicating with the
// status widget.
publish: function () { mstats.log('The publish option on fillups has not been set'); },
// invalidateData allows the vehicleList to invalidate data
// stored in the data cache
invalidateData: function () { mstats.log('no data invalidation function provided.'); },
selectedVehicleId: 0,
selectedFillupId: 0,
templateId: ''
},
Figure 6 Options Hash Code
The options hash in a jQuery UI widget is merged with the passed-in options on initialization, resulting in a single options object used by the widget. Thus, you can supply defaults like you see above, and the user can override any defaults on initialization.
Take a look at the three properties in Figure 6 that have functions as their value. By supplying the functions as options, we hand off control of these dependencies to the invoker. By inverting control to the code using the widget, we create a more testable widget. A test fixture could supply test callbacks to track sendRequest, publish and invalidateData.
An alternative is to use an app namespace that contains the dependencies (e.g., app.publish for a publish mechanism). Yet the approach I’ve described has two advantages. First, the inversion of control allows the widget to remain free from knowing about any application-specific namespaces for the dependencies. Second, the widget dependencies (namely the sendRequest, publish and invalidateData functions) are evident from looking at the options hash alone.
Widget Initialization
Now let's look at the initialization of the widget, shown in Figure 7.
// Creates the widget, taking over the UI contained in it, the links in it,
// and adding the necessary behaviors.
_create: function () {
this._bindNavigation();
// ensure that setOption is called with the provided options
// since the widget framework does not do this.
this._setOptions(this.options);
// this could also be accomplished by calling this.option( this.options );
},
_bindNavigation: function () {
var that = this;
this.element.delegate('[data-action]', 'click.' + this.widgetName, function (event) {
if ($(this).data('action') === 'select-fillup') {
that._setOption('selectedFillupId', $(this).data('fillup-id'));
event.preventDefault();
}
});
},
Figure 7 Widget Initialization
When the widget is instantiated,_create() is automatically called. This happens upon the first use of the plug-in.
( $( "someSelector" ).fillups() )
In most cases, the initialization of a widget will include binding event handlers and setting up the initial widget state. Here, calls to _bindNavigation() and _setOptions() accomplish each of the respective tasks.
A great practice is to delegate most event handlers to the root widget element. The root widget element is the element that had the widget initialized on it with the initial $().fillups() call. The widget factory allows you to access the root widget element as a jQuery object at any point through the this.element property. You can then use the jQuery delegate method to create event handlers.
By using delegates, you get the benefit of scoping an event handler to the widget, which improves performance over .live() and prevents unintentional firings of the handler. These benefits are balanced with the more scalable solution of binding events at a higher level in the DOM tree and allow event propagation to bubble the fired event.
Handling State Updates
Let's look at one more piece of the fill-ups widget. The code is shown in Figure 8.
// handle setting options
_setOption: function (key, value) {
$.Widget.prototype._setOption.apply(this, arguments);
if (value <= 0) {
return;
}
switch (key) {
case 'selectedVehicleId':
this.refreshData();
break;
case 'selectedFillupId':
this._updateSelectedFillup();
this._applyTemplate();
break;
}
},
Figure 8 Setting Options in the Fill-Ups Widget
Another special function that is called whenever any single option is set by the invoker is _setOption(). For example, the invoker might set an option with a call like this:
$( "someSelector" ).fillups( "option", "selectedFillupId", 1 );
The underlying widget code will then invoke _setOption.
The first thing you might notice is this line of joy:
$.Widget.prototype._setOption.apply( this, arguments );
We are calling the underlying widget _setOption() here, which is the function that updates our options hash. This will be replaced with a far prettier method in jQuery UI 1.9.
For our widget to handle the specific property update, a good practice is to use the switch statement and call the appropriate helper method within our widget. For example, if the selectedFillupId option was set to the new value, we call the _updateSelectedFillup() and _applyTemplate() methods.
Maintaining Widget Communications with the Layout Manager Widget
One other very interesting widget is the layout manager. This piece explicitly handles each layout state by calling individual widget methods to update their state. This implementation creates some coupling between the specific UI widgets and the one layout manager widget.
// In this snippet, 'this' refers to the layout widget itself
// this._vehicleList refers to the vehicleList widget which was
// set up when the layout manager was initialized.
_setupInfoPaneOptions: function(state) {
this._vehicleList('option', 'selectedVehicleId', state.vid);
this._infoPane('option', 'selectedVehicleId', state.vid);
this._infoPane('option', 'activePane', state.layout);
},
Subscribing and publishing on topics within each widget may have prevented this coupling. The team actually tried this approach first. However, the team found that the app was harder to maintain and test when using publish/subscribe for layout-specific communication. Too much decoupling, in this case, was a bad thing. I'd encourage you to read through this section on communication, particularly the lessons learned from the team's endeavor.
Wrapping Up
From just the pieces we have looked at here, you can glean the following advice:
- Decide on how your ideal architecture would look on the front end. Find the right toolkits, libraries, or frameworks for the job to fit your architecture model. Keep technical factors in mind along with team expertise and possible maintenance costs.
- The jQuery UI Widget Factory is incredibly useful for creating stateful plug-ins and widgets, including application-specific widgets.
- Injecting dependencies is a great way to write testable and decoupled code.
- Be careful with publish and subscribe. Too much pub/sub can result in less maintainable code when compared to direct communication.
We haven't touched on design, practical JavaScript testing, navigation, security or other critical application concerns. Each chapter in the book is laid out with considerations and lessons learned from an actual implementation. I strongly encourage you to read through the entire book and use it as a reference for making practical considerations and choices when building complex Web applications.
About the Author
Andrew Wirickis a partner and brewmaster atGoodTwin Inc, a digital agency located in the secretly incredible Omaha, Nebraska. He spends his time in the front-end world and has a particular interest in using jQuery and jQuery UI to help solve many of his Web challenges. He helped put together the appendTo learn site,which contains free JavaScript and jQuery lessons. He has been a frequent conference speaker and has trained developers on topics from jQuery foundations to rich Web application development.
Andrew is also anaspiring well funder and a home brewer. He rarely passes up the chance to geek out on craft beer or single malt scotch. You can follow his non-development exploits at andrewwirick.com.