Using the Backbone.js router to navigate through views modularized with require.js

44,536

Solution 1

As with pretty much any Backbone question, there are lots of ways to handle this. The way I approached it in my current project was to put everything in a global custom namespace, and use that to pass around the necessary references:

var MyNamespace = {};

MyNamespace.init = function() {
    MyNamespace.appView = new MyAppView();
    MyNamespace.router = new MyRouter();
    // etc
}

Views could then refer to MyNamespace.router as necessary. But it looks like this won't work/isn't encouraged with require.js, so here are some other options:

  • Don't ever call the router explicitly - instead, change a global state object that the router listens to. This is actually how I've done things in my current project - see this response for more details.

  • Attach the router to your top-level view, often called AppView, make that globally accessible, and use AppView.router.navigate().

  • Make another module that provides a navigate utility function that calls Backbone.history.navigate() internally. This isn't much different from what you're doing, but it would make it slightly more modular and keep you from using the global reference all the time. This also allows you to change the internal implementation.

Solution 2

In case anyone else is looking for a solution to this problem like I was, I'm posting what I ended up doing. If you're using the boilerplate backbone.js, then you will have an initialize() function in router.js. I modified my initialize() function to look like the following:

initialize = function () {
  var app_router;
  app_router = new AppRouter();

  // Extend the View class to include a navigation method goTo
  Backbone.View.goTo = function (loc) {
    app_router.navigate(loc, true);
  };

  Backbone.history.start();
};

Due to backbone.js's particular flavour of inheritance, this allows allows me to call MyView.goTo(location); from within any of my views.

Solution 3

You could do it the old fashioned way with window.location.hash :)

window.location.hash = "Edit/1"

Here's an alternate solution if you don't need explicit routes. When you app starts up create an object that extends Backbone Events

window.EventDispatcher = _.extend({}, Backbone.Events);

Then anywhere in you app you can listen for events

EventDispatcher.bind("mymodel:edit", this.editHandler, this);

And also from anywhere dispatch the event, data below are any params you want to send along for the ride

EventDispatcher.trigger("mymodel:edit", data);

Solution 4

For me, the solution with goTo function worked with a slight change

 Backbone.View.prototype.goTo = function (loc) {
      appRouter.navigate(loc, true);
    };

Solution 5

I know this question is old, but I am wondering why haven't you use require.js in order to get the router:

define(['./PathToRouter', ], function (router) {
    return Backbone.View.extend({
        template: Handlebars.compile($('#template').html()),

        events: {
            'click .edit': 'edit'
        },

        render: function () {
            //Create and insert the cover letter view
            $(this.el).html(this.template(this.model.toJSON()));
            $('#View').html(this.el);

            return this;
        },

        edit: function () {
            router.navigate('Edit/' + this.model.id, true);
        }
    });
});
Share:
44,536
MrGrigg
Author by

MrGrigg

Updated on February 20, 2020

Comments

  • MrGrigg
    MrGrigg about 4 years

    I am separating my views and router into separate files with require. I then have a main.js file that instantiates the router, and also renders my default view.

    My router has view ('View/:id') and edit ('Edit/:id') as routes. In main.js, when I instantiate the router, I can hardcode router.navigate('View/1', true) and the navigation works fine. In my view file, when I click on the edit link, I want to call router.navigate('View/' + id, true), but I'm not sure how I should do this.

    I've had success calling Backbone.history.navigate('View/' + id, true), but I don't feel like I should be relying on the global Backbone object.

    I tried passing ({ router: appRouter }) to my views so I could use this.options.router.navigate(), however that wasn't working for me.

    In case you're curious, here's a bunch of code from my app:

    Router:

    define(['./View', './Edit'], function (View, Edit) {
        return Backbone.Router.extend({
            routes: {
                'View/:id': 'view',
                'Edit/:id': 'edit'
            },
    
            view: function (id) {
                var model = this.collection.get(id);
                var view = new View({ model: model });
                view.render();
            },
    
            edit: function (id) {
                var model = this.collection.get(id);
                var edit = new Edit({ model: model });
                edit.render();
            }
        });
    });
    

    View:

    define(function () {
        return Backbone.View.extend({
            template: Handlebars.compile($('#template').html()),
    
            events: {
                'click .edit': 'edit'
            },
    
            render: function () {
                //Create and insert the cover letter view
                $(this.el).html(this.template(this.model.toJSON()));
                $('#View').html(this.el);
    
                return this;
            },
    
            edit: function () {
                Backbone.history.navigate('Edit/' + this.model.id, true); 
            },
        });
    });
    
  • MrGrigg
    MrGrigg over 12 years
    I tried using window.location before I found I could use the global Backbone object, but it wasn't actually firing my events, which is probably an entirely different problem.
  • MrGrigg
    MrGrigg over 12 years
    Are you declaring MyNamespace outside of the require modules? This is close to using the global Backbone object, just with my own object, right?
  • nrabinowitz
    nrabinowitz over 12 years
    I'm not using require.js, so it doesn't apply in my case. I think the last bullet in my list is probably the closest to the require.js idiom.
  • kreek
    kreek over 12 years
    If you're using routes only for triggering events (you don't need the url to visibly change, or for the user to use the back/forward buttons), then something I figured out this afternoon might help. You can make a global 'EventDispatcher' which I added to my answer above.
  • MrGrigg
    MrGrigg over 12 years
    Thanks KreeK. I've gone back and forth with wanting the URL to matter for this particular app. I've seen at least one other example similar to your global event dispatcher. Thanks for your help.
  • MrGrigg
    MrGrigg over 12 years
    Yeah, my own utility to call navigate isn't such a bad idea. If I were to do that, i could probably make the same utility present my router as an option, maybe that would solve the whole thing.
  • MrGrigg
    MrGrigg over 11 years
    I don't know why I didn't think of this either. The majority of things I've done since haven't required routes so I haven't revisited this for quite some time. I'm glad you chimed in though.
  • LarZuK
    LarZuK over 11 years
    In the example above, ./PathToRouter is not instance of a Router but a class definition. The only static method of a backbone Router is extend no navigate!
  • MrGrigg
    MrGrigg over 11 years
    @LarZuK I'm so far removed from this problem that I didn't even think of that, but you're totally correct.
  • Naor
    Naor over 11 years
    @LarZuK: You absolutely right. I forgot that. But, instead, you can pass a property to the view: new MyView({router: router }); and then accessing it using options.router.
  • chiborg
    chiborg over 11 years
    Passing the router in as an option like @Naor suggested is the only option if your router instantiates views. Otherwise you'll have circular dependencies with require.js (Router requires views, views require router).
  • Peter Ajtai
    Peter Ajtai about 11 years
    I had to use Backbone.View.prototype.goTo =
  • Lion789
    Lion789 over 10 years
    can you use this within a view, in a success callback or how would you go about it? Here is the question I have up if you want to see the code I am referring to... stackoverflow.com/questions/19472777/…
  • digaomatias
    digaomatias almost 10 years
    Very elegant solution! Thanks a lot for this. I've used the Backbone.View.prototype for another function but this one didn't come into my mind.