Search code examples
javascriptbackbone.jsbackbone-routing

Backbone router not triggering view change


I've been trying to solve a pretty irritating for the last couple of days now, and I'm finally admitting defeat, and appealing to SO.

First I'll give an overview of what I'm trying to do, then I'll give you specifics of where I'm running into issues.

The Goal: A user is filling out a form, and if any element is changed on the form and the user tries to leave the form without saving the changes, a modal should display with the following message:

You have made changes to this record. Do you want to save the changes?

The user gets a Yes/No/Cancel option.

If the user selects:

Yes: The record should save, then navigate to the originally intended route.

No: The record should not save, and the user should be navigated to the intended route.

Cancel: The modal should close and the user will remain on the current page with no route change.

I am using the backbone.routefilter library to detect route changes before the route change happens.

The Problem:

To solve the problem, I put a route change listener within my initialize method in the view that contains the form. Below is that code:

// If the user tries go to a different section but the record has been changed, confirm the user action
    app.circulationRouter.before = function( route, params ) {
        app.fn.clearNotifications();
        if(app.recordChanged){
            var confirmView = new app.views.confirm({
                header:"Patron Record Changed",
                bodyText:"You have made changes to this record. Do you want to save the changes?",
                trueLabel:"Yes",
                falseLabel:"No",
                cancelLabel:"Cancel",
                hasTrue:true,
                hasFalse:true,
                hasCancel:true,
                trueCallback:function(){
                    // Ignore the patron record change
                    _this.saveRecord();

                    // Render the patron section AFTER the save
                    _this.model.on({
                        'saved' : function(){
                            // Render the new seciton
                            app.circulationRouter.renderPatronSection(_this.model, params[1]);
                            app.currentPatronSection = params[1];

                            // Set the record changed to false
                            app.recordChanged = false;

                            // Remove the 'before' listener from the circulationRouter
                            app.circulationRouter.before = function(){};

                            if(con)console.log('in saved');

                            // Remove the listener
                            _this.model.off('saved');
                        },
                        'notsaved' : function(){
                            // Stay on the patron record page, keep the url at record
                            app.circulationRouter.navigate("patrons/"+_this.model.get('PatronID')+"/record",false);
                            _this.model.off('notsaved');
                        }
                    }, _this);

                },
                falseCallback:function(){
                    if(con)console.log(params);
                    if(params.length){
                        // Ignore the patron record change
                        app.circulationRouter.renderPatronSection(_this.model, params[1]);
                        app.currentPatronSection = params[1];
                    }else{
                        if(con)console.log('blah');
                        setTimeout(function(){
                            if(con)console.log('should navigate');
                            app.circulationRouter.navigate("", true);
                        }, 5000);
                    }
                    app.recordChanged = false;
                    app.circulationRouter.before = function(){};
                },
                cancelCallback:function(){
                    // Stay on the patron record page, keep the url at record
                    app.circulationRouter.navigate("patrons/"+_this.model.get('PatronID')+"/record",false);
                }

            });
            app.el.append(confirmView.render().el);
            return false;
        }
    };

Each of the three options has a callback that gets called within the initialize function that will control the outcome. The Cancel button always behaves correctly. The issue I'm running into is when the user wants to navigate to the home page, ie when this line is called: app.circulationRouter.navigate("", true);. Everything else works correctly if there is a new, defined route to navigate to.

Here is the sequence of events that creates the issue:

1) Modify a record

2) Try to navigate to the home page

3) Route is changed home page route, but record is still in view

4) Modal is automatically displayed with three options

5) Select the No button

6) falseCallback is triggered

7) Modal is closed and view remains on record page, but route displayed in browser is for home page

The expected behavior for #7 was to display the view for home page, but only the url reflects that. You can see in the falseCallback I even tried delaying the trigger for the redirection to make sure it wasn't a DOM issue, but that didn't work.

Does anyone know what may be happening?


Solution

  • Here's the problem. Once the route is changed, if you re-navigate to the same route, even if you set the param {trigger:true}, the page won't reload. There is a long discussion about it here.

    Based off of the discussion listed in github, I am using this solution and adding the refresh option. If the refresh option is set to true, then the view will reload, even if the url doesn't change. Otherwise, the functionality remains the same.

    _.extend(Backbone.History.prototype, {
                refresh: function() {
                    this.loadUrl(this.fragment);
                }
            });
    
    var routeStripper = /^[#\/]/;
    var origNavigate = Backbone.History.prototype.navigate;
    Backbone.History.prototype.navigate = function (fragment, options) {
        var frag = (fragment || '').replace(routeStripper, '');
        if (this.fragment == frag && options.refresh)
            this.refresh();
        else
            origNavigate.call(this, fragment, options);
    };