Encapsulate complex logic for cleaner code and reuse

April 3, 2013

Ext JS provides many reusable components for use in Javascript applications that shield the developer from complex timer and/or asynchronous code. However, you may still find yourself writing timing or asynchronous code directly in your application code that you would be better off encapsulating in a class.

Timer code is generally not pretty, bug prone and is not something I want to look at all the time. Once that logic is available as an object, it’s easy to mask the complexity and make that functionality available transparently to other developers.

Ext.util.DelayedTask

A perfect example is Ext.util.DelayedTask. This class came about while I was making an example for Ext 1.0 that searched the community forums. I wanted to listen to keystrokes by the user and when the user stops typing for a predefined time, hit the server to perform the search. Creating that code using early Ext event handlers was pretty hideous, and not something newbie developers would understand easily. “Buffering” function calls is a common problem as well and a perfect chance to create a useful class for developers using Ext JS.

buffer : Number
Causes the handler to be scheduled to run in an Ext.util.DelayedTask delayed by the specified number of milliseconds. If the event fires again within that time, the original handler is not invoked, but the new handler is scheduled in its place.

After writing DelayedTask, I added the “buffer” option to Observable and DOM event handlers, making it completely transparent to developers. If you use Ext JS, you’ve probably seen or used the option at least once. If not, you should read up about it.

Fast forward to today

Today I finished a new “Callout” class (a Tooltip that has an arrow designating what it applies to) for the reportcaster framework. I decided to write it from scratch to extend Component instead of using the Ext JS Tooltip class which extends Panel because I wanted it to be lightweight and it’s width / height need to dynamically update fast when I update the content as the mouse is moving.

The logic for hiding a tooltip is pretty consistent (dismiss after x time, mouseout, dismiss button, esc key covers 95% of use cases) but the logic for showing a tooltip requires more flexibility. After you show a tooltip, you can easily attach a listener to the element to detect when the mouse leaves and remove that listener after the tooltip is hidden – but to show tooltips, you definitely don’t want to attach listeners to every element in the document that has a tooltip. I could have coded event delegation (like QuickTips), but there are still many cases where you want to listen to events differently and control the showing of tooltips more efficiently (e.g. on grid row overs). Trying to cover every use case is impossible, and once that “show” logic is in the class, it’s hard to get around. This is one of the reasons I rarely use QuickTips.

The Callout class already contains configurable options to determine how it is hidden, but the show logic is intentionally left out for the reasons stated above. While testing it, I decided to add callouts to a Tree Panel that lists out available reports. Showing tooltips properly requires a lot of timing logic and I definitely didn’t want that in my controller or view code. Also, showing tooltips immediately as the mouse is moving over the document IMHO is very distracting and not an option. Here’s how I wanted it to work:

  • When the user’s mouse enters an item in the tree, start show timer A to show the callout in .5 seconds
  • If they leave before the timer gets called, cancel timer A
  • If they enter a different item, cancel and restart timer A
  • When timer A gets called, show the callout
  • After the callout is hidden, start timeout timer B for 1 second 
  • If they enter another item in the tree before B is called, the callout for that item should be shown immediately
  • If timeout timer B is called before they enter another item, then the next enter goes back to the beginning and timer A

Once it worked, the timers and handlers in my application code were difficult to follow and debug. Like DelayedTask, this code was going in an example for developers, so I decided to wrap it up in a class. Separated from my application code, the timer code became easy to follow.

xui.util.DelayTimer

The resulting code can be used as described above, but also has some helpers that make using it super simple.

listen(obj, startEvent, cancelEvent)
Attaches event handlers to automatically call start() / cancel() for you

test(arg1, arg2, etc)
Passed as a config option. Function to be called right before starting the timer in start(). It is called with the same arguments that start() was called with. Return false to cancel the start(). This is particularly useful for filtering calls to start() made by events bound with listen().

No more following a trail of callbacks and timers

For the Tree, the resulting code is pretty simple. I’m using the test() function described above to filter nodes without a description.

var timer = new xui.util.DelayTimer({
    fn: this.showCallout,
    scope: this,
    test: function(t, node){
        return !!node.description;
    }
});
timer.listen(this, 'itemmouseenter', 'itemmouseleave');

It it looks like:

Callout on Tree


Using it with the Ext JS Grid

Applying it to my custom xui.report.Grid subclass was also pretty easy, but a little bit different. Since I am already listening to “uievent” from the grid View, using the listen() helper function and attaching new event handlers didn’t make sense. Plus, I needed to make sure certain code executed first (alert conditions) to create the contents of the Callout.

First I create the timer after the grid is rendered in an onRender() override. The onEnd config callback is used to get notified every time the callout is hidden and I use it to track the activeCell that the callout is displayed for.

this.timer = new xui.util.DelayTimer({
    delay: this.alertDelay,
    fn: this.showAlerts,
    onEnd: this.onAlertHide,
    scope: this
});

Then in the uievent handler:

onUIEvent: function(type, view, cell, rowIndex, cellIndex, e, rec){
    ...
    if(alerts && e.newType == 'mouseenter'){
        this.timer.start(alerts, cell);
    }
    ...
}

And it looks like:

Grid Alerts

Conclusion

By encapsulating the complex timer logic described in the beginning of this post in a reusable class, I am shielded from the complexity of the multiple timers required to show callouts correctly and my application code is must cleaner, easier to read, debug and maintain. Since the DelayTimer code is not in the Callout class, I’m free to reuse it in a completely custom way. Because of that, I can create optimized and specific callouts (or Tooltip) on each component type very easily and with an API that is intuitive for the developer. If you have ever hand-coded hideous string concatenation to build  tdAttr  in the Ext JS Grid to do QuickTips, you know why that’s important.

If you find yourself doing complex timer or asynchronous logic, consider pulling it out into a class like DelayedTask / DelayTimer.

The code for xui.util.DelayTimer is available here as a Gist. MIT license. I added undefined properties on the prototype to make it easier to discover config options. You can safely delete them.

Comments

Comments are closed.