- We design and build extraordinary applications for companies looking to make the next great idea a reality.
- learn more
Implementing linked multiselects with jQuery, LiveQuery, and Low Pro: Part 2: First pass at the actual code
In last week's post, I introduced the linked multiselect widget I was asked to implement on a tight deadline for an unexpected project assignment. I showed some demo code in action and discussed the user experience issues that shaped my requirements. This week, I'll walk through the actual code - or at least my first pass at it.
Like a lot of developers who should know better, I sometimes shirk the technical design phase on quick projects, then regret it later. The code I handed off for this project got the job done, but it wasn't very DRY or elegant. Luckily, I've continued to refine it into something I'm not ashamed to blog about. Next week, I'll show off the final, refactored code and try to draw some conclusions about the entire experience. But first - the original, unrefactored code:
The good
Working with new jQuery plugins always makes a project more interesting. I decided now was the time to get started with Dan Webb's Low Pro for jQuery, a library that adds class-based inheritance and other syntactic conveniences to the jQuery object, including an attach method for linking DOM nodes to prepackaged behaviors. The best, most reusable code I wrote for my initial pass at this project was constructed using Low Pro.
One requirement for my project was the creation of a simple color fade effect to signal DOM updates to the user. Using Low Pro, I was able to abstract this single behavior into its own reusable class for deployment within my larger application. The code looks like this:
//a reusable class to add
//a classic Web 2.0 yellow fade
//to DOM elements that have changed
SignalChange = $.klass({
defaults: {
//length of the fadeout
duration: 1250
//baseline and highlight colors
, defaultBorderColor: "#ccc"
, defaultBackgroundColor: "#eee"
, highlightBorderColor: "#000"
, highlightBackgroundColor: "#ffa"
}
, initialize: function(options) {
var opts = $.extend({}, this.defaults, options);
$.extend(this, opts);//just copy over all the options
}
, onredraw: function() {
var that = this;
this.element
//cancel previous events of this type
.stop("onredraw")
.css({
backgroundColor: that.highlightBackgroundColor
, borderColor: that.highlightBorderColor
})
.animate({
backgroundColor: that.defaultBackgroundColor
, borderColor: that.defaultBorderColor
}, this.duration)
;
}
});
$('.multiselect .scrollbox').attach(SignalChange);
jQuery pros will notice that the CSS magic in my onredraw method relies on jQuery Color Animations, a tiny little add-on by jQuery creator John Resig that extends the library's core FX mechanism to handle color transformations. In fact, I had to hack the plugin itself, since it handles borderBottomColor, borderLeftColor, borderRightColor and borderTopColor - but not borderColor. Strange, that, but I got over it. I wanted my color fade effect to support borders as well as backgrounds, and it seemed easier to add a single item to an array in the plugin than to handle each section of the border separately in my own behavior.
The syntax of Low Pro's klass method looks pretty similar to Prototype's Class mechanism, which makes sense. The original Low Pro is a library for making Prototype more like jQuery, while Low Pro for jQuery is designed to give Prototype-style support for large, complex and subclassable objects to jQuery.
Notice some of the elegance in Webb's syntax: Any method that conforms to the onwhatever naming convention is assumed to be an event listener - even if it's a custom event such as my own onredraw. Once I've pointed my SignalChange behavior at a DOM node using the attach method, I just need to fire that event to trigger my custom listener. The same is true for native events.
Given the simplicity of my behavior, it could easily have been constructed as a plain old jQuery plugin. But even for such a low-level behavior, Low Pro offers some advantages:
- If Live Query is running, then any calls to Low Pro's
attachmethod are routed through Live Query so they'll be applied to all matching DOM nodes, even those constructed subsequently via DHTML or Ajax. - I can also subclass my behavior more easily using Low Pro than jQuery's built-in plugin mechanism.
This inheritance becomes all the more important when your behaviors are more complex, with multiple linked event listeners. Because you can just subclass a Low Pro klass, you don't need to overload it with some insane options hash. You can just create a new subclass for each slightly different implementation. I'll take advantage of this mechanism in the refactored code I show off next week.
The not-so-good
In the meantime, I have to take responsibility for the other Low Pro klass I created in this initial version: Subscriptions, which you can view in full in the source code. Before I get into this code's deficiencies, let's look at what it does right:
-
It makes great use of jQuery Templates to simplify the construction of DOM nodes. Templates such as the following keep me from having to concatenate strings to create my on-the-fly markup:
this.tmplMemberView = $.template( '<div class="item">' + '${name}' + ' <span class="info">(' + '${groupCount:pluralize(group)})</span>' + '</div>' )I even added some useful helper methods to the
templatemethod so I could customize the formatting of my templates://add some additional template helpers //for the template plugin; //use these to format our markup $.extend($.template.helpers, { addConditionalClass: function(value, trueClass, falseClass) { return value === true ? trueClass : falseClass; } , addIfTrue: function(value, conditionalText) { return value === true ? conditionalText : ''; } , addIfFalse: function(value, conditionalText) { return value === false ? conditionalText : ''; } //only works for standard plurals , pluralize: function(value, label) { return value + " " + label + (value != 1 ? "s" : ""); } , count: function(value, label) { var r = value.length || 0; if (label) { r = $.template.helpers.pluralize.call(this, r, label); } return r; } }); -
It leverages Live Query to attach permanent behaviors to DOM nodes that get destroyed and recreated many times over the application lifecycle.
-
It does a good job of wiring up the complex interactions between two interdependent linked multiselects.
However, my code accomplishes most of these things in a most inelegant way. Looking back, I should have modeled a more complex object hierarchy with some sort of reusable LinkedMultiselect object. My Subscriptions object should have been responsible solely for wiring together two of these LinkedMultiselects and enforcing data dependencies between them.
By starting with one great big all-encompassing class, I ended up having to everything four times: once each for the "selected" and "unselected" states of each of the two linked multiselects. The result is code like this, in which I have to create a single method and then create four more methods to bind it to different circumstances:
, getSubGroup: function(coll, prop, state) {
return $.grep(coll, function(n) {
return n[prop] === state;
});
}
, getSubscribedMembers: function() {
return this.getSubGroup(this.members, "subscribedNow", true);
}
, getUnsubscribedMembers: function() {
return this.getSubGroup(this.members, "subscribedNow", false);
}
, getEligibleGroups: function() {
return this.getSubGroup(this.groups, "eligibleNow", true);
}
, getIneligibleGroups: function() {
return this.getSubGroup(this.groups, "eligibleNow", false);
}
By now, we've all been exposed to enough proper object-oriented JavaScript to do better than this. I know I certainly have, but I fell into the trap of coding rather than designing first. I look forward to showing you the refactored version next week.
Topics: Ajax, Design Patterns, Javascript, jQuery
Leave a comment
About Pathfinder
Recent
- iPhone SDK: UIViewController Testing & TDD
- Icons are evil; so are menus - unless you do them right
- The Truth About Designing For Security
- GWT, Gadgets and OpenSocial, Part 2
- Has Many has_many: A Refactoring Story
- The Hidden Power of Canvas
- Review of fixture_replacement2 plugin
- Chess Game Viewer in GWT
- From JSP to Ruby on Rails: First thoughts on front-end coding conventions
- Helpers and Partials
Archives
- November 2008
- October 2008
- September 2008
- August 2008
- July 2008
- June 2008
- May 2008
- April 2008
- March 2008
- February 2008
- January 2008
- December 2007
- November 2007
- October 2007
- September 2007
- August 2007
- July 2007
- June 2007
- May 2007
- April 2007
- March 2007
- February 2007
- January 2007
- December 2006
- November 2006
- October 2006
- September 2006
- August 2006
- July 2006
- June 2006
- May 2006
- April 2006
- March 2006


