I recently implemented Ext.util.History for the first time in a project that I’m working on. If you’re not familiar with History, it allows you to “register arbitrary tokens that signify application history state on navigation actions”. You can then take these “tokens” and use them in your application to display certain views, fire actions, etc. when the user uses their browser’s “back” and “forward” buttons. In other words, it can help you prevent your 1-page AJAX application from reloading completely when someone accidentally hits the back button. But on a more positive note, it allows your AJAX application to use regular navigation conventions of the browser to control in-application functionality. Pretty cool.

The best part about History, however, is that it is stupid-simple to implement. There are basically 3 steps:

  1. Initialize Ext.util.History
  2. Build in the “token-building” logic into your application at appropriate places
  3. Define the listener for handling History events.

That’s it. History is not only easy to implement, but it is also pretty unobtrusive–you can implement it fairly simply into an existing app without completely reworking the whole thing.
There’s a lot more to History, of course, so be sure to check out the docs and Sencha’s example.

First, My Example…

Before I talk about the limitation I found in History–and how I overcame it–let me lay out the flow in my application.
In my app, I have a navigation tree in which each item functions a link to retrieve the “main page” data from an external data source. So with each click in the navigation, there is one AJAX request to get data, and the data is then added to the body of a panel in my app.

The idea for History, then, is that I want to tokenize the necessary parameters for my AJAX call, so that when users go “back” or “forward” in the browser, the AJAX request will be fired for the “previous” or “next” page, and they’ll get the appropriate content returned…just as if they had clicked the link in the navigation tree.

So let’s walk through how I set this up:

First, I initialize Ext.util.History:

Ext.require(['Ext.util.History']);
Ext.util.History.init();

Pretty hard, right? 🙂

Next, I’ll set up my History “token” in the appropriate place. In this example, I’m placing it the handler for a custom event called “afterupdate”–this event fires after I receive my data back from the AJAX request, as well as after the content of my main body panel has been updated. I put the History tokenization here because if this event fires, I know that I have valid data to tokenize…

this.control({
    "maincontent": {
        afterupdate: function(target,type) {
            ...
            // add to real Ext History
            var oldtoken = Ext.util.History.getToken();
            var newtoken = type + ":" + target;
            if(oldtoken === null || oldtoken.search(newtoken) === -1) {
                Ext.History.add(newtoken);
            }
        }
    }
})

So this is pretty self-explanatory. We first get the old token (retrieved by calling Ext.util.History.getToken()), as well as build our new token. As you can see, the value is completely up to you, so you can be as specific as you want (just be sure to use valid URL characters…).

Finally, if the old token is not defined or empty string, OR not equal to the new token (e.g., we clicked the same link twice in a row), we execute History’s add() method.

The last part of this is the listener for the “change” event. It’s here that everything gets pulled together:

Ext.util.History.on('change', function(token) {
    if (token) {
        var page = token.split(":");
        me.getController("Navigation").loadfromhistory(page[1],page[0]);
    }
});

If a token exists, we split it on the same delimiter we used when creating the token. With these values, we can then do whatever we want–such as executing the loadfromhistory() method which will trigger an AJAX request for the specified page.

…Then the Limitation

While this works brilliantly, there is a small limitation, and it’s simply that the “change” event is fired when the browser back/forward buttons are clicked, OR when the add() method is executed. In the context of my application that basically means the following occurs:

Browser Back/Forward Click:

  • loadfromhistory() method executed (good)

add() Method:

  • Original AJAX request executed
  • Main panel updated
  • add() method fires –> triggers History change event
  • loadfromhistory() method executed (bad)

As you can see, when adding a token, I basically get the same AJAX request executed twice. While this doesn’t hurt anything really (well, unless I didn’t build my token right…), it’s a waste; there’s no need to make two requests for one set of data.

Unfortunately, out of the box I couldn’t figure out a way to get around this. There’s no “suppressChange” flag for the add method, and there’s no additional information that I can key in on within the change event itself to different between these two scenarios.

What’s more, it doesn’t *appear* to be bad implementation on my part (I’ll take correction on that if I’m off base on this). If you look at the example for Ext.util.History, the same behavior occurs: the setActiveTab() method is executed twice when the History add() method is invoked (e.g., when a tab is clicked), but only once when the browser buttons are used.

The Workaround

After doing a bit of Googling, and finding some common complaints about the lack of event suppression on the add() method of Ext.util.History, I decided to implement a “fix” via Ext.apply(). Here’s the final result:

Ext.apply(Ext.util.History,{
    suppressChange : false,
    add: function(token,preventDup,suppressChange) {
        var me = this;
        this.suppressChange = suppressChange ? true : false;
        if (preventDup !== false) {
            if (me.getToken() === token) {
                return true;
            }
        }
        if (me.oldIEMode) {
            return me.updateIFrame(token);
        } else {
            window.top.location.hash = token;
            return true;
        }
    },
    handleStateChange: function(token) {
        this.currentToken = token;
        // only fire "change" event if suppressChange is false
        if (!this.suppressChange) { this.fireEvent('change', token); }
        // now just reset the suppressChange flag 
        this.suppressChange = false;
    }
});

There are really 3 parts that are important.

The first is that I added another property to Ext.util.History, namely “suppressChange.” This flag will tell History whether or not to fire the change event.

The second is that I added another argument to the add() method, again, “suppressChange”. It will simply set the value of the Ext.util.History.suppressChange property.

Finally, in handleStateChange(), I wrapped the change event firing in a condition: if Ext.util.History.suppressChange is false, the change event will be fired; otherwise, it will not. And to wrap it up, the handleStateChange() method automatically resets the default value of the suppressChange property back to false so I don’t have to bother with that elsewhere, and since not suppressing the change event is the desired default behavior.

What this boils down to, then is that you don’t have to muck with your change event listener at all. Rather, you can simply add the suppressChange flag when executing the Ext.util.History.add() method, and let the rest flow on as normal:

// add() invocation with suppressChange flag set to true
Ext.History.add(newtoken,true,true); //token, preventDuplicates, suppressChange

Wrapping Up

And that’s that. To be perfectly honest, I’m not sure if this is *best* way to go about this, but in my testing it does work, at least for my purposes. I’d love to hear *constructive* feedback, suggestions on how to do it better, etc.