When Do You Set The URL, In A BackboneJS App?

Oscar Godson recently asked a question about when to set the URL in a Single Page App (Backbone, specifically):

I have a sidebar that pops in with form. It has a URL for it since it’s so commonly used. Going to app.com/foo/add would load the page and open the sidebar. Another view, TopMenu, has a button to open the sidebar. My question: who triggers the URL change? Should RightSidebar change the URL withBackbone.navigate (w/o trigger) or should the button change the URL and call the RightSidebar‘s openmethod?

There are a lot of possibilities for an answer, here. Oscar already alluded to several different options and there are potentially more. In my reply, I bring up a subject that I’ve talked about a lot – how to effectively use a router in a SPA. This topic is relevant to the question, but only one angle from which to view the problem. Ultimately, we’re looking for a single place in the code that can be in control of the application’s context and/or state – one place to rule them all, and in the router bind them … or something like that.

My Reply (With A Bit Of Formatting)

Any time you have multiple points of entry for the same behavior, you need to encapsulate that behavior’s kick-off in to a single thing… an object, a function, a single line of code… whatever that thing is, it needs to be the one thing that is called in order to produce the desired behavior and state of the application. This often means you have to do a lot of prep work to get the application in to a state where that one thing can be called. But, once you’re there you just call that one thing and the application puts itself in to the expected state with the expected behavior.

Here is a simple example:

var MyRouter = Backbone.Router.extend({

  routes: {
    "/foo": "foo"
  },

  foo: function(){
    myController.doFoo();
  }

});

var MyView = Backbone.View.extend({

  events: { 
    "click #foo": "fooClicked"
  },

  fooClicked: function(e){
    e.preventDefault();
    this.trigger("do:foo");
  }

});

var myController = {

  doFoo: function(){
    // do stuff here, put the app in to a specific state, show new views, etc.
  },

  show: function(){
    var view = new MyView();
    view.on("do:foo", this.doFoo, this);
  }

};

In both cases (using the router or clicking the DOM element), the code all comes back to the doFoo function. This is the one place in the application that knows how to put the app in to the “foo” state. In your case, this puts sidebar in place and puts the route in the URL as expected.

At this point, it doesn’t matter what code kicks off the `doFoo` process / app state. It could be a router, a button in a menu, a websockets event, image recognition technology from a webcam or anything else. When the doFoo function is called, the app moves in to the right state and does it’s thing, including setting the URL.

Of course this gets complicated when you start talking about very large apps. In very large apps, you need to partition your apps based on context to keep things sane. Sometimes it will make sense for a button click to just call a method on an object and the app is good to go. But, sometimes the feature being requested is in a completely different context with a completely different layout in the app. In these cases, it’s often easier to have a button click call router.navigate(“foo”, true)” to force the route handler to fire, or just use a link with the real url in the link.

In the end, the principle still applies: no matter how many entry points there are, you only want to call the `doFoo` function in order to get the app into the right state, with the right URL in the address bar.

Further Reading

I’ve alluded to a lot of things in this reply, and a handful of assumptions about application architecture. There is a lot of information that you may want to read up on, some of which is free and some of which is not. But if you’re interested in seeing how this works out, in the end, it’s worth your time and money.

There are probably dozens or hundreds more resources that I could list here, but that would be a very long list to compile. 


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Backbone, Design Patterns, Javascript, Marionette, Principles and Patterns. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Andrew Young

    In your example you encourage the `doFoo()` to handle putting the app in the “foo” state which includes setting the URL. But when the user comes in through the router, the URL is already set. Would it be redundant to navigate(“/foo”) in doFoo() when the URL is already there? Or do you add logic in doFoo() to detect that the URL is already in the right state?

    • http://mutedsolutions.com Derick Bailey

      good point, andrew. i should have talked about that a bit :)

      it is redundant, but you don’t need to add any logic to prevent the url from being updated with what it already contains. the router is smart enough to handle that for you.

  • Kelt Dockins

    In Marionette I use a module service called dispatcher which I can rely on. It is a mediator between the router and controller which I typically call before the controller action is called. The dispatcher is used like:

    App.execute(‘dispatcher:set:route’, ‘show:(id)’, fooId);

    And this would set the route to #/show/3 (assuming fooId = 3 here); Later I can request from the dispatcher the current route (which is really handy when you need to know which navigation bar item is active or inactive.

    var route = App.request(‘dispatcher:route’); // ‘show:(id)’
    var id = App.request(‘dispatcher:route:id’); // 3
    var opts = App.request(‘dispatcher:route:options’); // { name : ‘show:(id)’, params: [3] }