Evolving jQuery UI For Web Components

My job centers around writing reusable javascript components. As a result, I spend a lot of time reading, a lot of time writing code, and a lot of time revisiting those things I’ve previously written, and trying to improve upon them. In recent months, I’ve been obsessed with performance improvements, both with rendering state as well as optimizing them for the browser’s paint cycles. There are, by now, plenty of articles out there about window.requestAnimationFrame(), deferred objects and promises, development patterns, up-and-coming Web Components proposals, etc. This article is going to be an attempt to marry a lot of those disparate concepts in the context of the jQuery UI Widget Factory, which is one of the more widely used frameworks out there for reusable UI components.

jQuery UI

Why jQuery UI? Why not something like Angular or Polymer or something else new and shiny that’s built around the idea of encapsulated components? For one thing, market share. jQuery UI has been out there and widely used for years, whereas some of the newer framework offerings are still getting their footing. For another, familiarity. I work with jQuery UI on a daily basis, and have a solid understanding of what the factory itself provides, as well as where it’s lacking in features or functionality.

Development Patterns

As this concept is going to be at the heart of our discussion around a new structural framework for building components with jQuery UI, I’m going to put it up front in this article. Most of you are familiar with the concept of development patterns, some of you are familiar with more than others. The main ones I’ll be focusing on in this post are going to be the factory, promises, observers, composites, event mediators, and just maybe a slightly bastardized implementation of MVVM. I’ll go over each of these briefly, and touch on them some more as we come to them in our actual implementation. Seat belts fastened? Here we go!

The Factory Pattern

Anybody that’s done any work with jQuery UI widget development has used the factory pattern already, even if you didn’t realize it. A factory is nothing more than an interface that takes in data of one format, and returns an object type of another, normalized to some kind of interface. You could be dealing with a Car factory that you pass a few parameters to in order to define Make, Model, Year, etc., and internally, the factory takes those options and creates a new Car object from a constructor it has knowledge of, passing those options to that constructor, and returning the Car object. Now, no matter how many different types of makes/models/years you want to make Car objects for, you can do it with 1 simple function, and receive back a list of objects that all have the ability to speed up, slow down, honk their horn, etc.

The jQuery widget factory maintains a $.Widget.prototype object that it uses as a base for all of the widgets created through the factory, and receives 2-3 parameters for use: a (namespaced) widget name, an optional base widget to use for creating widget extensions, and an object literal that contains all of the prototype methods you want as a part of your new widget’s prototype. Since we’ll be dealing in creating a new generation of UI widgets from the jQuery UI widget factory, we’re obviously going to be using the factory pattern as part of our implementation.

The Promise Pattern

Promises are the new pattern on the block as far as javascript development is concerned. They came into popularity after a lot of frameworks started gaining complexity to the point that callback handling was becoming a nightmare, both visually and as far as testing was concerned. Anybody that’s done a lot of extensive inline nesting of callback functions will recognize the sideways tree that symbolized callback hell:

$('#foo').fadeIn(function () {
    $(this).slideUp(function () {
        $(this).slideDown(function () {
            $(this).fadeOut(function () {
                $(this).show(0, function () {
                    console.log('Done! Huzzah!');
                });
            });
        });
    });
});

Now, enter promises, which we can use to break things up into more semantically meaningful code while maintaining execution order. jQuery animation functions already return a promise object right now, which can be used for deferred chaining. Assuming all of these functions dealt with animations, we could simply rewrite the above code to read thusly:

var foo = $('#foo');

foo.fadeIn().promise()
    .then(function () {
        return foo.slideUp().promise();
    })
    .then(function () {
        return foo.slideDown().promise();
    })
    .then(function () {
        return foo.fadeOut().promise();
    })
    .then(function () {
        return foo.show(0).promise();
    });

Because of the work we plan to do with the requestAnimationFrame() functionality, our UI changes may not always be complete when our code continues on. If you’re writing unit tests to verify that some option update is reflected in the UI, you may have to wait up to 16ms for that change to actually take effect, based on when the last animation frame fired. That means we need to start having our functions return deferred object promises to their consumers, and resolve those promises after the next animation frame fires. It’s time for us to start truly embracing the asynchronous nature of javascript.

The Observer Pattern

The observer pattern can be simply put as event-driven development. We subscribe to a particular event, and when that event occurs, we process a callback. The observer pattern makes up the bulk of what most people use jQuery for: adding click listeners, mouseenter listeners, dialogopen and dialogclose listeners, and their own army of custom events. Used by itself, the observer pattern can lead to a whole lot of jQuery spaghetti code. It’s definitely more elegant than event handling using native javascript functions, but it’s just as much of a headache to manage and to try to scale.

Another drawback of the observer pattern is that it tightly couples your listeners to specific DOM implementations. If you’re listening for a click on a given anchor tag, you have to have that anchor tag in the document in order for your code to execute. That lessens the portability of your code to other pages, unless you’re also porting over the markup that it’s dependent on.

Part of the goal of componentization is to make sure that our code is as reusable as possible, so we want to make sure those dependencies are as minimal as possible, and that, wherever possible, we construct them ourselves as part of the creation of our widget(s).

The Composite Pattern

I’m a big believer in complexity through compositing. What is the composite pattern? It’s the foundation upon which all widgets should be built. It means sticking as close to the Single Responsibility Principle as we can while allowing flexibility in our individual components, and then stacking those components up to build more complex components. We don’t reinvent the wheel when we build bigger, we simply include more modules and delegate functional responsibilities to them.

You want ajax functionality for this widget? Write an ajax wrapper as a component, and then add that component to your current widget. You want template support? Don’t write it directly into your widget, write it as a separate component and tack it on. You want pagination? Extend your ajax wrapper into another module that requires it as a dependency, but adds number-of-items and index offset as parameters, then require that module as a dependency. The more you separate these things out into their own silos, the more portable your code becomes.

Create a public interface for the most common use cases your widget will provide. Use that to pass data into the widget, and use events to broadcast data back out of them whenever something interesting happens. This helps keep your components loosely coupled, and keeps the dependency flow moving downwards into the widget. Consumers know they require your widget as a dependency, so they can directly reference those public functions, but widgets used as a dependency have no clue who’s calling them, so should never try to directly access a caller. They should simply blast their data outward in an event and let whoever’s interested in it catch that event and process the data.

Keeping our widget modules small and loosely coupled means we can mix and match them as we want in order to rapidly build new mashups of functionality. It also means these modules are easy to test in a standalone fashion, and as long as we can ensure the integrity of those inputs and outputs, we can also ensure they’ll work properly within other widgets, reducing the need to extensively test increasingly complex widgets that are made up of them. We don’t need to test that an ajax component is working properly inside of a pagination component. We only need to test that the ajax component works properly on its own, and then we can separately test the public inputs and outputs of our pagination component itself.

The Mediator Pattern

The mediator pattern forms the glue between our observers and our composites. Within a more complex widget that may be made up of 2, 3, or N other components, we have to have a way of funneling data back and forth between those individual components. However, we don’t want to directly subscribe any of the dependency components to each other, as that code becomes less portable. We need our parent widget to play traffic cop between its children. It will be responsible for saying “Your ajax-loaded data is ready? Ok, I’ll take that and feed it to this data list for rending,” and “Ok, your data list is now rendered? In that case, I’ll tell our popover element to go ahead and show itself, so people can see you,” and “Someone clicked on an item in the list? Ok, I’ll take this information about their selection, and stick it into a form field”.

None of these individual sibling components have any direct knowledge of each other. They simply broadcast their events and data outward, the parent receives it, and directs it to where it needs to go via a public function, over and over and over. In this way, we reinforce that chain of dependency, that none of the siblings are dependent on each other. It’s the parent that’s dependent on all of them.

MVVM

MVVM had its start in WPF and Silverlight development from Microsoft. It’s increasingly gaining traction in the javascript community, particularly within frameworks like KnockoutJS and AngularJS. So what’s so special about it that’s not contained within MVC or MVP or MV*? Specifically, itโ€™s this concept of a ViewModel. The ViewModel sits between the model and the view and keeps those 2 pieces of the development stack separated, with no direct knowledge of each other, similar to how we want our components to have as little knowledge of each other as possible. The more we can reduce dependencies between our model and our view, the more portable we make each of those pieces. This can lead to reusable partial views, as well as the ability to drop any model into any page with only the minimal effort of tweaking the ViewModel to bind up its data properties to the view state.

The ViewModel can be thought of as a translation and notification layer between the model and the view. Anytime the model is updated, it alerts the ViewModel that it’s been updated. The ViewModel then alerts the view that there are updates that need to be reflected visually, and the view re-renders itself according to the ViewModel’s public functions, which in turn draw from the data in the model’s state. In some cases, there’s 2-way data binding going on, meaning that not only can the ViewModel alert the view that there are model changes that need to be reflected in the UI, but it can also alert the model that there are changes in the UI that need to be reflected in the model (which can cause other pieces of the UI to re-render themselves, accordingly).

One of the biggest downfalls to typical jQuery UI widget implementations is that people don’t put in the effort to keep their UI in sync with their underlying data model. When options change, those changes aren’t always reflected on the page. This can easily be remedied via the _setOption() function that the widget factory provides for us. Using this function, we can set up an intercept method for option changes, and update our visual representation according to whatever option is changing, and to whatever value. We can consider our _setOption function itself to be the core of our MVVM implementation, being the functional layer that sits between our model (this.options) and our view, and alerting the view to redraw itself whenever anything changes.

Bringing It All Together

Now that we’re over 2000 words into this, it’s time we started bringing all of these concepts together to form the future of jQuery UI widget development best practices. I mentioned that one of the goals was make sure that anything causing a repaint operation in the browser was tied to an animation frame via the requestAnimationFrame() function, and that this was going to cause programmatic delays. As we may never know what’s going to cause a repaint from the outside as a consumer, and since we don’t want our users to have to guess at what might return immediately, and what might happen in an asynchronous fashion, there’s really only one way to go forward. We need to make sure all of our public functions return deferred object promises.

Deconstructing Chainable Scope

But that’s going to break function chainability isn’t it? Absolutely, to a degree. You won’t be able to chain public methods with element scope anymore, but you can still use promise chaining to keep your code fairly elegant and straightforward. Our internal functions that are going to be public-facing need to be aware enough to know whether they’ll be performing an asynchronous task or not, and return a promise as appropriate. If a public function is not performing any asynchronous operations, it needs to spawn up a new deferred object anyway, resolve it immediately, and return the promise. This will allow our callers to chain off of that promise and have their code executed immediately. If anything changes in the future, so that the promise does rely on an asynchronous operation, their code is already written in such a way that it absorbs that change automatically, and continues to function as expected. We’ve just allowed our consumers to future-proof their own code against our internal changes.

requestAnimationFrame

We also need to add support for animation frame usage via the requestAnimationFrame() function. If you’re supporting older browsers, you can find a pretty good polyfill for it on Paul Irish’s blog, along with some good write-ups about what it does and why it’s a good thing. We want to try to confine all repaint operations to run within an animation frame going forward. Here’s how we start that bit of sorcery, in our base widget class:

$.widget('demo.base', {
    _create: function () {
        this._super();
        this._vars = {};
    },

    _requestAnimationFrame: function (callback) {
        var frameId, returnVal, pendingCallbacks;

        frameId = this._vars.pendingAnimationFrameId;

        if (!frameId) {
            this._vars.animationFrameDeferred = $.Deferred();

            frameId = window.requestAnimationFrame($.proxy(function () {
                var animationFrameDeferred = this._vars.animationFrameDeferred,
                    onAnimationFrame = this._vars.onAnimationFrame;

                this._vars.pendingAnimationCallbacks = {};
                this._vars.pendingAnimationFrameId = null;

                animationFrameDeferred.resolveWith(this);
            }, this));

            this._vars.pendingAnimationFrameId = frameId;
        }

        if (callback) {
            pendingCallbacks = this._vars.pendingAnimationCallbacks || {};

            // Force generation (or reuse if previously generated) of a function guid
            callback = $.proxy(callback, this);

            if (typeof pendingCallbacks[callback.guid] === 'undefined') {
                pendingCallbacks[callback.guid] = true;
                this._vars.animationFrameDeferred.done(callback);
            }
        }

        return this._vars.animationFrameDeferred.promise();
    }
});

We can now use this new function to attach any number of callbacks to a promise object that will be resolved on the next animation frame. In addition, it wraps the callbacks in a $.proxy invocation, which forces a guid to be generated for the function object. That way, as we go forward and other things attach callbacks to animation frames, it will only store a single instance of each function, so if we set 3 different options that cause a _redrawUI() function to be invoked on the next animation frame, we only queue it up one time. When the animation frame hits, and we resolve our deferred object, it fires all of its callbacks with the proper widget scope, and resets its callback queue.

Here’s an example of how we can now use this functionality:

$.widget('demo.popover', $.demo.base, {
    options: {
        width: 300,
        maxheight: 300,
        content: ''
    },

    _render: {
        width: function () {
            this._html.wrapper.width(this.options.width);
        },

        maxheight: function () {
            this._html.wrapper.css('max-height', this.options.maxheight + 'px');
        },

        content: function () {
            var content = this.options.content;

            if (typeof content === 'function') {
                content = content.call();
            } else if (typeof content === 'string' && $(content).length) {
                content = $(content);
            }

            this._html.content
                .html(content)
                .children()
                    .show();
        },

        hide: function () {
            this._html.wrapper.hide();
            this._trigger('hide');
        },

        show: function () {
            this._html.wrapper
                .show()
                .position({
                    my: 'left top',
                    at: 'left bottom+15px',
                    of: this.element
                });
            this._trigger('show');
        }
    },

    _create: function () {
        this._super();

        this._html = {
            content: null,
            wrapper: null
        };

        this._buildWidget();
        this.option(this.options);
    },

    _setOption: function (key, value) {
        var returnVal = this._super(key, value);

        var optionMap = {
            width: function () {
                this._requestAnimationFrame(this._render.width);
            },
            maxheight: function () {
                this._requestAnimationFrame(this._render.maxheight);
            },
            content: function () {
                this._requestAnimationFrame(this._render.content);
            }
        };

        if (typeof optionMap[key] === 'function') {
            optionMap[key].call(this);
        }

        return returnVal;
    },

    _buildWidget: function () {
        this._html.wrapper = $('<div class="popover-wrapper"></div>').hide().appendTo('body');
        this._html.content = $('<div class="popover-content"></div>').appendTo(this._html.wrapper);
    },

    hide: function () {
        this._requestAnimationFrame(this._render.hide);
        return this;
    },

    show: function () {
        this._requestAnimationFrame(this._render.show);
        return this;
    }
});

“Observing” Our Options

We’re almost where we want to be now. We’ve started setting up our MVVM pattern by way of our _setOption() function override. Javascript doesn’t currently implement anything like the INotifyPropertyChanged interface in the .NET framework. There is a proposal in the works for an Object.Observe() method, which is meant to provide this sort of notification to javascript. Youtube has a great presentation on Object.Observe(), by Addy Osmani if you’re curious about it.

Without that sort of native functionality, MVVM can only really be implemented in one of two ways: dirty-checking or mutator functions. Dirty-checking (like AngularJS uses) involves either a polling interval to check the state of a given object and compare it to a previous state to identify changes, or manually invoking a function to do that comparison for you. Mutator functions involve invoking a function to specifically change a value on your object, either a function mask over a property name (like KnockoutJS uses), or a single function that you pass a key and property name into (like the jQuery UI .option() method). Since all option changes wind their way through the _setOption() function, it’s the perfect method to override and repurpose as our ViewModel.

Within this method, we call through to the original functional implementation of _setOption() via the _super() method, so that our options can be properly stored in the widget. Other things you could use this override for would be enforcing ranges of values (and setting defaults in the case of invalid values), triggering certain events when certain options are changed, or disallowing a change altogether based on the new value. For now, though, we’ll keep it simple. This example merely passes the option up the inheritance chain for storage, and then checks to see if we have any callbacks set up to be fired whenever various options change. If we do, those callbacks request an animation frame and pass one of our _render functions to it.

This is fine for at least getting our repaint operations deferred to animation frames, which is the heart of what we’re trying to accomplish. However, we’ve now created an unreliable interface for interacting with our widget. If we set an option, we can’t expect that it will be reflected in the UI immediately. We can’t easily test those conditions, and we can’t easily write code to set an option and immediately reference its updated UI state.

Making Promises

Let’s alter this widget so that it returns promises to our calling functions, and resolves those promises at the end of each animation frame. Any code that’s dependent on those UI updates can be chained off of the promise we return, and all will then be right with the world.

$.widget('demo.popover', $.demo.base, {
    options: {
        width: 300,
        maxheight: 300,
        content: ''
    },

    _render: {
        width: function () {
            this._html.wrapper.width(this.options.width);
        },

        maxheight: function () {
            this._html.wrapper.css('max-height', this.options.maxheight + 'px');
        },

        content: function () {
            var content = this.options.content;

            if (typeof content === 'function') {
                content = content.call();
            } else if (typeof content === 'string' && $(content).length) {
                content = $(content);
            }

            this._html.content
                .html(content)
                .children()
                .show();
        },

        hide: function () {
            this._html.wrapper.hide();
            this._trigger('hide');
        },

        show: function () {
            this._html.wrapper
                .show()
                .position({
                    my: 'left top',
                    at: 'left bottom+15px',
                    of: this.element
                });
            this._trigger('show');
        }
    },

    _create: function () {
        this._super();

        this._html = {
            content: null,
            wrapper: null
        };

        this._buildWidget();
        this.option(this.options);
    },

    _setOption: function (key, value) {
        var returnVal = $.Deferred().resolve();

        this._super(key, value);

        var optionMap = {
            width: function () {
                return this._requestAnimationFrame(this._render.width);
            },
            maxheight: function () {
                return this._requestAnimationFrame(this._render.maxheight);
            },
            content: function () {
                return this._requestAnimationFrame(this._render.content);
            }
        };

        if (typeof optionMap[key] === 'function') {
            returnVal = optionMap[key].call(this);
        }

        return returnVal.promise();
    },

    _buildWidget: function () {
        this._html.wrapper = $('<div class="popover-wrapper"></div>').hide().appendTo('body');
        this._html.content = $('<div class="popover-content"></div>').appendTo(this._html.wrapper);
    },

    hide: function () {
        var deferred = $.Deferred();

        this._requestAnimationFrame(this._render.hide)
            .done(function () {
                deferred.resolveWith(this.element);
            });

        return deferred.promise();
    },

    show: function () {
        var deferred = $.Deferred();

        this._requestAnimationFrame(this._render.show)
            .done(function () {
                deferred.resolveWith(this.element);
            });

        return deferred.promise();
    }
});

Now we need to make a change to our base widget. The default widget factory implementation of the public option() method returns the element scope for chaining purposes. We want to return the promise we receive back from _setOption() instead. The option() method doesn’t route directly to _setOption(), though, it goes through _setOptions(), so we’re going to have to override that method also. While we’re at it, we should go ahead and return promises from our other public factory methods, to make the overall public interface consistent. We don’t have to worry about enable() and disable(), though, as they already return the value they’re getting back from _setOption(). For now, we only really need to worry about widget() and destroy(). Finally, we’ll have to override _setOption() itself, because it references widget() internally, and will need to be updated to use the returned promise, as opposed to treating it as a synchronous function call.

$.widget('demo.base', $.demo.base, {
    destroy: function () {
        this._super();
        return $.Deferred().resolveWith(this.element).promise();
    },

    widget: function () {
        this._super();
        return $.Deferred().resolveWith(this.element).promise();
    },

    option: function( key, value ) {
        var options = key,
            parts,
            curOption,
            i,
            deferred = $.Deferred();

        if ( arguments.length === 0 ) {
            // don't return a reference to the internal hash
            return $.widget.extend( {}, this.options );
        }

        if ( typeof key === "string" ) {
            // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
            options = {};
            parts = key.split( "." );
            key = parts.shift();
            if ( parts.length ) {
                curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
                for ( i = 0; i < parts.length - 1; i++ ) {
                    curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
                    curOption = curOption[ parts[ i ] ];
                }
                key = parts.pop();
                if ( value === undefined ) {
                    return curOption[ key ] === undefined ? null : curOption[ key ];
                }
                curOption[ key ] = value;
            } else {
                if ( value === undefined ) {
                    return this.options[ key ] === undefined ? null : this.options[ key ];
                }
                options[ key ] = value;
            }
        }

        this._setOptions( options )
            .done($.proxy(function () {
                deferred.resolveWith(this.element);
            }, this));

        return deferred.promise();
    },

    _setOptions: function( options ) {
        var key, deferred = $.Deferred(), promises = [];

        for ( key in options ) {
            promises.push(this._setOption( key, options[ key ] ));
        }

        $.when.apply(null, promises)
            .done($.proxy(function () {
                deferred.resolveWith(this);
            }, this));

        return deferred.promise();
    },

    _setOption: function( key, value ) {
        var self = this;

        this.options[ key ] = value;

        if (key === 'disabled') {
            this.widget()
                .done(function (widget) {
                    this
                        .toggleClass(self.widgetFullName + '-disabled ui-state-disabled', !!value )
                        .attr('aria-disabled', value );
                    });

            this._requestAnimationFrame(function () {
                this.hoverable.removeClass('ui-state-hover');
                this.focusable.removeClass('ui-state-focus');
            });
        }

        return this;
    }
});

It’s worth pointing out a couple of details here. Our destroy() and widget() methods are simply returning a promise to a new deferred object which is immediately resolved. Whenever possible, we don’t want to modify the core of jQuery UI, or else future upgrades become more of a chore. So, we eat the cost of any repaints associated with them, and let them do their thing immediately. We do still want to return a consistent interface, though, so we return a promise object for a deferred that’s already been resolved, meaning our consumers’ chained callbacks will execute immediately. Also note that we’re using resolveWith() in a lot of these cases. We want our callbacks to be executed with the same scope and parameters as they would have previously expected, we just want those callbacks written in a format that works with asynchronous tasks.

For our public functions, they would normally return the element scope back for chaining purposes, so we want to make sure we resolveWith() the element scope. Similarly, in _setOptions(), since that’s a private method, the normal return would be the widget instance scope, so we resolveWith() this instead. We would want to modify our _setOption() method to resolve its deferred object with the proper widget instance scope, too. You should also take special note of our _setOptions override. Because we’re potentially being handed an object literal with multiple options to be set, we don’t want to tell our code that it’s ok to continue executing until all of those options have been set. For that, we create an array to hold a list of N promise objects, one for each option we’re setting, and then pass the array to $.when() to get back one master promise for the whole list, and resolve() a custom deferred object in its success callback, returning the custom deferred’s promise.

We can now tie all of this together like so:

var myPopover = $('#myPopover').popover();

myPopover
    .popover('option', {
        content: function () {
            return 'I am the very model of a modern major general';
        }
    })
    .then(function () {
        return $(this).popover('show');
    })
    .then(function () {
        var deferred = $.Deferred();

        setTimeout($.proxy(function () {
            $(this).popover('hide')
                .then(deferred.resolve);
        }, this), 5000);

        return deferred.promise();
    })
    .then(function () {
        console.log('All done!');
    });

This example should instantiate a new popover widget on the #myPopover element in the DOM, set its content, show it, wait 5 seconds, hide it, and then log ‘All done!’ to the javascript console. Because our functions are all returning promises, we can continue to chain our then() statements one after another, and be confident that they won’t execute until the UI is ready each step of the way. Now we’ve got something really powerful going on, but technically it’s still just a really powerful implementation of a UI widget, as opposed to a true “component”. If this were a compound component that included popover as a dependency, there would be nothing stopping us from listening for the popoverhide and popovershow events bubbling their way up the DOM tree. We need to break the mentality that a compound component is just a collection of smaller components, and start thinking of them as an individual, unique component themselves. One way we can do that is by setting event boundaries.

Event Boundaries

If you’ve done any reading up about Polymer and the Web Components proposal, you may have seen something about event propagation being killed at shadow DOM boundaries. Not every event is stopped, but several of them are. One nice thing about this is that when we start treating these components as truly individual components, as opposed to a collection of other components, and we stay blind to the internals, we only need to worry about what the component itself is broadcasting for events. Why is this a good thing? Let’s assume we have some autocomplete widget that’s using our popover widget internally, to display its list of results. If consumers had to know about those popovershow and popoverhide events to be able to subscribe a listener to our autocomplete widget, and be notified when it opened and closed, what happens if we swap out the popover implementation for something that uses the same functional interface? What if we extend our popover into a tooltip component, or a dropdown component, or something else, and use that inside of our autocomplete instead? Those things are no longer broadcasting their events with the same name, they’re now broadcasting tooltipopen or dropdownclose, meaning we’ve just caused breaking changes for all of our consumers, and event names need to be cleaned up to function the same way.

What we should be doing is adding a listener within our autocomplete widget, which subscribes itself to popoveropen and popoverclose, and rebroadcasts that data outwards as autocompleteopen and autocompleteclose. If we swap out the internal implementation for a functional equivalent like tooltip, we change our own internal event listener to subscribe to tooltipopen and tooltipclose and rebroadcast them as autocompleteopen and autocompleteclose. Now we’ve maintained the same external interface for our consumers, and they don’t need to have any knowledge of what’s going on inside of autocomplete.

To get started with our own makeshift event boundaries, it involves some manual data entry on our part. We need to make each widget aware of every custom event type that it may trigger. We can also create a public function in our base class to return them as a list, so that parent widgets have an easy facility for accessing all of the events of their child widgets. Finally, we add event listeners to all of those events, and stopPropagation() so that they don’t bubble past their parents. Here’s a sample implementation of something like that in our base widget class:

$.widget('demo.base', $.demo.base, {
    _events: [],

    getEvents: function () {
        return $.Deferred.resolveWith(this.element, this._events).promise();
    },

    _suppressChildEvents: $.noop
});

Nice and simple, right? Notice the promise return in our new getEvents() public function? We want to maintain our public function interface, even though there’s no asynchronous behavior here. Now we need to override the _events property in each widget class we create, and add a list of the events it broadcasts. Also, due to the nature of requiring other widgets as dependencies, we can tailor our _suppressChildEvents() function to the actual implementation details, knowing which elements to subscribe to, and what types of widgets they’re each using. Armed with that information, we can simply squash their events. Here’s an example, using our popover and autocomplete widgets.

$.widget('demo.popover', $.demo.popover, {
    _events: [
        'popovercreate',
        'popoveropen',
        'popoverclose'
    ]
});
$.widget('demo.autocomplete', $.demo.base, {
    _events: [
        'autocompletecreate',
        'autocompleteopen',
        'autocompleteclose'
    ],

    _suppressChildEvents: function () {
        this._html.popoverElement.popover('getEvents')
            .then($.proxy(function (events) {
                $.each(events, function (index, event) {
                    this.element.on(event, function (e) {
                        e.stopPropagation();
                    });
                });
            }, this));
    },

    _attachEventListeners: function () {
        this._on(this._html.popoverElement, {
            popoveropen: function (e) {
                this._trigger('open', e, {
                    list: this._html.popoverElement
                });
            },

            popoverclose: function (e) {
                this._trigger('close', e, {
                    list: this._html.popoverElement
                });
            }
        });
    }
});

Now these things are starting to get sexy. We’re adding an _attachEventListeners() function to our autocomplete widget, which uses our observer pattern to subscribe to each of the popover events, rebroadcasting them as autocomplete events. It also passes along the original event object just to add some additional data to the current event, and a parameter that contains a list property, which is a pointer to our popover itself. It also squelches any of the popover events from going past our autocomplete border and bubbling out into the DOM. Our popover widget itself is a little more simple, as it has no child widgets to worry about. All it needs to do is list out the events it broadcasts.

Playing Traffic Cop

Finally, I’ll come back around to using event mediation internally, to route data back and forth between all of these child components. Here’s a rough and oversimplified implementation of a component that wraps the $.ajax function, and a text input. We can use these in conjunction with our popover to continue assembling a very basic autocomplete component.

$.widget('demo.textinput', $.demo.base, {
    _events: [
        'textinputcreate',
        'textinputkeypress'
    ],

    _attachEventListeners: function () {
        this._on({
            keypress: $.proxy(function (e) {
                this._trigger('keypress', e, {
                    value: this.value()
                });
            }, this)
        });
    },

    value: function (newValue) {
        if (typeof newValue === 'undefined') {
            return this.element.val();
        } else {
            return this._requestAnimationFrame(function () {
                var changed = this.element.val() !== newValue;

                this.element.val(newValue);

                if (changed) {
                    this.element.trigger('change');
                }
            });
        }
    });
});
$.widget('demo.dataloader', $.demo.base, {
    _events: [
        'dataloadercreate',
        'dataloadercomplete',
        'dataloadererror',
        'dataloadersuccess'
    ],

    options: {
        url: null,
        data: null
    },

    updateData: function (data) {
        return this._setOption('data', data);
    },

    load: function () {
        $.ajax({
            url: this.options.url,
            data: this.options.data,
            success: $.proxy(function (data) {
                this._trigger('success', null, {
                    data: data
                });
            }, this),
            error: $.proxy(function (xhr, status, error) {
                this._trigger('error', null, {
                    status: status,
                    error: error
                });
            }, this),
            complete: $.proxy(function (xhr, status) {
                this._trigger('complete', null, {
                    status: status
                });
            }, this)
        });

        return $.Deferred.resolveWith(this.element).promise();
    }
});

Now that our base components are decently fleshed out, we can assemble them into our autocomplete component and use event mediation to direct our event traffic and data back and forth fairly easily.

$.widget('demo.autocomplete', $.demo.base, {
    _attachEventListeners: function () {
        this._on(this._html.popoverElement, {
            'click li': $.proxy(function (e) {
                this._html.input.textinput('value', $(e.target).text());
                this._html.popoverElement.popover('hide');
            }, this);
        });

        this._on(this._html.input, {
            textinputkeypress: $.proxy(function (e, value) {
                this._html.loader.dataloader('updateData', { search: value })
                    .then($.proxy(function () {
                        this._html.loader.dataloader('load');
                    }, this));
            }, this);
        });

        this._on(this._html.loader, {
            dataloadersuccess: $.proxy(function (e, data) {
                this._html.popoverElement.popover('option', 'content', this._vars.template(data))
                    .then($.proxy(function () {
                        this._html.popoverElement.popover('show');
                    }, this);
            }, this);
        });
    }
});

Now we can finally call this rough draft implementation finished. As you’ll notice, all of the child components maintain loose couplings by only having interactions with the autocomplete layer itself. None of them directly interact with each other, and within their own internals, none of them make any references to any other components. The autocomplete widget itself subscribes to their events, and funnels the data back and forth between them, delegating responsibilities to each of them as appropriate and, for the most, remaining completely passive.

Conclusion

Once you start structuring your own components this way, you can compose these things together fairly quickly, and if each component actually handles its task(s) well, you need very little business logic to glue them together. This is the beauty of composition. Once you have a base set of widgets fleshed out, you can rapidly prototype various mashups and crank out more and more complex widgets in a fairly short period of time. If you can embrace asynchronous development patterns on top of that, and keep your repaint operations confined to animation frames where they belong, you wind up with a very powerful component framework that helps minimize CPU overhead, and by extension, battery consumption, as well as increasing responsiveness of the page as a whole. The introduction of an MVVM-style pattern of observing the changes to our internal options object means that we can keep our UI in sync with the data model at all times, the same as any other MVVM framework, but without the need for adding an entire layer of extra resource bloat to obtain that kind of functionality.

I would love to see these kinds of changes adopted by the jQuery UI team officially, as well as seeing them move towards a more AMD-like style of publishing their framework (similar to jQuery’s recent 1.11 and 2.1 releases), so that people that specialize in writing custom web components based on their factory can load just the factory portions they need, instead of having to use an all-or-nothing approach to whatever custom build they have downloaded into their project. As always, if you have any questions or comments, please feel free to leave them in the section below ๐Ÿ™‚

Leave a Reply

Your email address will not be published. Required fields are marked *