Monday, July 30, 2012

History and navigation with MVC and Ext 4.1 (Sencha)


I struggled for awhile with how to present the 'Are you sure you want to exit?' message when a user has unsaved data and attempts to close the browser or navigate elsewhere.  I also wanted pages in my application to be URL accessible so that a user could bookmark a URL to show a specific record or email a URL to another user.  I am using  Ext 4.1 and MVC.  

I decided on a two pronged approach.   First, I hooked directly into the window.beforeunload event for handling the user attempting to close the browser window or page away when they have unsaved data.  It was easy to setup a handler for the beforeunload.  The difficulty I ran into was finding the scope of my MVC application.

Secondly I used Ext.history to handle page navigation.



Here is an excerpt from my main MVC controller.

//-------------------------------------------------------
// function to handle the token that is passed in the URL for the page.
//-------------------------------------------------------
   dispatch: function (token) {

        if (token == "") {    // no parameters passed, go to home page.
            Ext.History.add("Main");
        }
        else if (token) {
            parts = token.split(':');

            if (parts[0] == "editCustomer") {  // Edit a specified customer
                this.editCustomer(parts[1]);
            } else if (parts[0] == "Main") {
                this.goHome();
            } else if (parts[0] == "tabpanel") {  // open a tab on main page
                this.goHome();
                var tab = this.getMainSearch().query('#tabs')[0];    //tabs
                tab.setActiveTab(parts[1]);  // set the main tab control to 'home'
            } else {
                this.goHome();
            }
        } else
            this.goHome();


    },
//-------------------------------------------------------
// Check if we need to ask if user needs to save.
//-------------------------------------------------------
    processHistoryToken: function (token, opt) {

        if (this.changesMade()) {  // if any dirty data that is not saved.
            this.token = token;
            Ext.Msg.show({
                title: 'Warning.',
                msg: "Save changes before exiting page?"  ,
                buttons: Ext.Msg.YESNO,
                fn: function (btn) {
                    if (btn == 'yes') {
// User has said yes so explicitly save the data to server.
                        if (this.changesMadeEvent())  // changes for my 'event' form
                            this.saveEvent(false);
                        if (this.changesMadeTicket())  // changes for my 'ticket' form
                            this.saveTicket(false);
                        }
// reset the forms so they are no longer dirty
                    this.getEventDetail().query('#EventDetailform')[0].getForm().reset();
                    this.getChangeDetail().query('#ChangeDetailform')[0].getForm().reset();
// proceed with doing the action
                    this.dispatch(this.token);
                },
                animEl: 'elId',
                icon: Ext.MessageBox.QUESTION,
                scope: this
            })
        } else {
            this.dispatch(token);
        }
    },
//-------------------------------------------------------
// Handler for the window.onbeforeonload.
//-------------------------------------------------------
 confirmExit: function () {

         // Using the previously save scope, call the 'changesMade' function to see if
        // any unsaved form data.
        if (this.MYscope.getController('AM.controller.smMain').changesMade())
            return "You will lose your changes if you leave this page !";
    },


//-------------------------------------------------------
// init function for my main MVC controller
//-------------------------------------------------------

   init: function () {
        // -------------------------------------------------
        // History management and navigation 'dispatcher' which handles some of the user navigation.
        // ----------------------------------------------------
        Ext.History.init();
        Ext.History.hasHistory = false;  // default to no history saved to prevent 'back' button completely out of the app.

        Ext.History.on('change', function (token, opt) {
            Ext.History.hasHistory = true;   // we have some history now so 'back' button is ok.
            this.processHistoryToken(token);
        }, this, this);


        //  Store our context as a property of the window.  I'm sure there are better ways to do this, but this works!
        window.MYscope = this;
        window.onbeforeunload = this.confirmExit;
......

// Support for URL.   Set the 'page' in the application based on the URL.
var token = document.location.hash.replace("#", "");
this.processHistoryToken(token);

...