A better way to implement Back Button in Sencha Touch’s NavigationView

If you’re building a Sencha Touch 2 app and have deployed it to Android, expect that you’re going to get hit up about having the device’s hardware back button working. ¬†Sencha will try to push you towards using routes as evidenced in their docs when discussing history, but to me that’s invasive and goes against the very nature of event driven UI events that have made this framework so popular. Moreover, this gets more complicated when using NavigationView as your main mechanism to move back and forth on a given tab, especially for me where my application had multiple navigation tabs.

To that end, I decided it would be much easier to simply use the browser’s history to manage the back button. ¬†In this way, you can simply push the browser’s state as the user moves forward in your app and then pop the state as the user moves backward in the application. It was also important that different tabs would not interfere with each other’s state. Let’s take a look at how I did that assuming this is an MVC-based application.

Step 1: Add refs and controls to your navigation view’s controller for your view and your navbar with your application’s back button (different from your browser or device back button):

        refs: {
            portal: 'portal',  //Portal Tab Panel
            myContainer: 'MyContainer',  //NavigationView
            navBar: 'myContainer #navbar',  //itemId for navigationBar on NavigationView
        },
        control: {
            myContainer: {
                push: 'onPush'
            },
            navBar: {
                back: 'onBack'  //trap the back event for the app's back button
            }
        }

Here we’re just establishing references to the view components and binding the push and back events to methods we will implement in the next step. Notice that it’s important to trap your app’s back button so it pops the state similar to how the browser or device back button would.

Step 2: Add the implementation for onPush and onBack:

    onPush: function (view, item) {
        history.pushState(); //push the state
    },

    onBack: function () {
        history.back();  //pop the state to trigger listener in step 3
        return false;  // return false so listener will take care of this
    },

Here we leverage the JavaScript history object to push a new state on the stack as the user moves forward in the app’s NavigationView and pop the state from the stack as the user moves back.

Step 3: Add a “popstate” event listener to the window in your Controller’s launch method:

    launch: function () {
        var that = this;
        window.addEventListener('popstate', function () {
            var portal = that.getPortal();  // won't have portal until app is initialized
            if (portal) {
                var container = getTabContainer(portal.getActiveItem());
                if (container && container.getItemId() === "MyTab"
                    && container.getActiveItem().getItemId() !== "baseView") {
                    container.pop();
                }
            }
        }, false);
    },

Here we add a “popstate” event listener to the window so that we can pop the last window off the stack as the user moves back. Notice I do a few checks, one to be sure we have instantiated the portal and the other to check that the container I’m on is the one for this NavigationView (i.e., the “MyTab” check). You could imagine an app with multiple tabs that you want to make sure the other tab controllers aren’t responding to the event when the user uses the device back button (which is NOT tied to a controller; just the popstate event). The final check is to check to see if I am on the “baseView” because I have no need to pop the container if I’m at the root of a particular NavigationView.

That’s all there’s too it. No need to rearchitect your app to use Sencha routes and no complicated code to manage each NavigationView’s state. All you need to do is implement this same code in each of your NavigationView tabs and you’re all set.

Thanks to Greg from Sencha Touch support for pointing out this a viable alternative!