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!

Advertisements

17 comments on “A better way to implement Back Button in Sencha Touch’s NavigationView

  1. do I need phonegap for this to work? when I package this code for android, the application is still left/closed when I hit the device’s back button …

    • If your packaging your app for an appstore, then the recommended approach is to use PhoneGap because they have a global option for this. In my case, I have written a Mobile Web application, which is accessed directly from a webkit browser, so it works fine in those cases.

  2. Interesting approach. But I think Sencha guys should provide a way to bind NavigationView (or even carousels) with browser history, for example an attribute like useHistory: true on navigation view.
    But for wirting a new application what do you suggest? Your approach or using routes?

    • I think it depends on your app. If your using NavViews like I am and drive the application through events, then my approach is way cleaner than using routes. You can accomplish something similar with routes, but it takes more code to do that. Routes would also be a better choice for deep linking but I don’t see this much as a use case for mobile apps.

      I agree with your assessment and have a Jira enhancement request with Sencha regarding this topic. I’d suggest putting your own in too and reference case #TOUCH-3991.

  3. getTabContainer not defined error on running the app,and this error caught each time back button is pressed in the browser.

    • That’s just a helper function to determine which tab in your application that you’re on. Your navigation view would define n number of tabs you could look for by ID. My implementation looks something like this:


      function getTabContainer (tab) {
      // Determine which container the user is in
      var container = null;
      var id = tab.get('itemId');
      switch (id) {
      case 'Tab1':
      container = Ext.ComponentQuery.query('tabContainer1')[0];
      break;
      case 'Tab2':
      container = Ext.ComponentQuery.query('tabContainer2')[0];
      break;
      }

      return container;
      }

  4. Hello does anyone has a way to bind the hardware button on android to the back and doing it in a safe mode? I simply listen to the “Hardware back” event, then, I pop from my navigationView, but if the user is fast, and press twice back before the veri first previous view is completely shown, it crash the app… it will eventually go back two views, but will never be able to access the views again…
    Thanks!

    • Yeah, you’re looking at it. 🙂 This method binds to the Android device’s back button. I’m not sure what you mean by “safe mode” in the context of a Mobile Web app. Can you clarify?

  5. Do you have an example project that we could look at? We’re still struggling to get this working using Sencha Architect. What do the “myTab” and “baseView” id’s reference in the launch function? Thanks for your help.

    • Those are just the itemId’s of your view components. Read through the second from last paragraph. I explain it well there. I cannot provide the full code, sorry.

  6. How can i achieve this with containers instead of navigation view.?My requirement is to capture the device back button and navigate within the application.

    Thanks,
    Pooja

    • It’s really not any different. History is a not a Sencha specific feature so you just alter the code to fit your container model.

  7. Have you generated the APK and tested it on a device?? App runs fine when running it in a browser, but it doesn’t seem to work when I tried it in a device. Please let me know.

    Thanks in Advance

    • Yes, I have multiple apps in production using this approach. It works great across several Android/iOS devices.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s