Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using URL fragment (#) for modal views in a pushState single-page app

We're using Backbone.Router in a pushstate-only mode, so all our client routes are hash-less.
However, we're facing a difficulty with implementing modal views in our app.

The challenge is the following:

  • We want Back button to hide the current modal view (so we need it to have a URL);
  • We want Forward to show it again without redrawing the whole app;
  • We want to be able to show modals “on top of” any existing route and not just on one page;
  • We want to be able to make links that immediately show a specific modal (such as login modal view).

In other words, we want modal views to be represented in history.

Our first attempt was to use URL like /login for login modal and handle them specifically in route handler. When we're on /otherpage, opening modal would navigate to /login, and, when modal is closed, navigate again to /otherpage.

However, this has a very major problem: URL like /login doesn't “know” over which view it should be drawn, so we have to redraw everything when pressing Back and Forward.

This actually makes sense: Login modal over Home screen should have different URL from Login modal over Other Page.

My second thought was that maybe we can use hashes for indicating current modal view:

/
/#login
/otherpage
/otherpage#login

This makes the routing handler simple:

  • First, draw the actual views based on matched route, just like we did before.
  • After that, if hash is present, display a modal view on top.

This also fits with the idea of hash being a “fragment” of the visible document. (Yes, apps are not documents, bla bla bla. We still want them to be addressable.)

Are there any inherent problems in this approach?
Is there a better approach satisfying our conditions?

like image 442
Dan Abramov Avatar asked Feb 13 '14 15:02

Dan Abramov


1 Answers

If I understand your problem correctly, I don't believe you need a hash and I do believe you can implement the modal by simply adding more routes. If you need to support a login modal over other existing routes, simply add another route that adds login to the end. Here would be an example:

var Login = Backbone.Model.extend({
    closeLogin: function () {
        this.set({ open: false }, { silent: true });
        this.trigger('loginClosed');
    }
});

var LoginView = Backbone.View.extend({
    el: '#loginModal',     
    initialize: function () {
        this.listenTo(this.model, 'loginClosed', this.closeLogin);
    },
    closeLogin: function () {
        // do whatever you would normally do to close the modal
    }
});

var Router = Backbone.Router.extend({
    routes: {
        '/login': 'defaultRouteLogin',
        '': 'defaultRoute',
        'otherpage/login': 'otherPageRouteLogin',
        'otherpage': 'otherPageRoute'
    },
    defaultRoute: function () {
        // defaultRoute behaviors
        this.login.closeLogin();
    },
    defaultRouteLogin: function () {
        // defaultRoute behavior
        // instantiate login modal
    },
    otherPageRoute: function () {
        // otherPageRoute behavior
        this.login.closeLogin();
    },
    otherPageRouteLogin: function () {
        // otherPageRoute behavior
        // instantiate login modal
    },
    initialize: function () {
        this.login = new Login();
        this.loginView = new LoginView({ model: this.login });
    }
});

To support back-button behavior, use a method that closes your login modal on every route where you could open your modal. Be sure to order the routes correctly.

like image 200
kinakuta Avatar answered Nov 10 '22 00:11

kinakuta