backbone.routemanager icon indicating copy to clipboard operation
backbone.routemanager copied to clipboard

Version 0.2

Open tbranyen opened this issue 12 years ago • 13 comments

The initial version was shipped without much testing. A lot needs re-thinking.

I personally believe that a state manager is necessary and when used with something like LayoutManager will have some amazing benefits when it comes to partial view updates.

  • Priority one is state manager.

Before/after filters are interesting, but I think what we really need are decorators. An object that points a route to a decorator I think is far more useful.

  • Thoughts on decorators vs filters?

There is a difference between RouteManagers and Routers. This divide needs less blurring and any Router that gets funneled into RouteManager needs to have the same functionality. State, decorators, etc.

  • Cohesive API.

Other suggestions?

tbranyen avatar Dec 02 '12 21:12 tbranyen

What would be nice is have a hierarchy, sort of like how ember does it (without going down the transitions/named routes paths). Lets say I am making an app about warehousing, and it has two distinct parts -- shipping and receiving. It would be awesome to be able to do something like this (as really rough psudo code)

Route.define({
  shipping: {
    new: 'nameOfCreateMethod',
    view: 'nameOfViewMethod'
  },

  receiving: {
    new: 'nameOfCreateMethod',
    view: 'nameOfViewMethod'
  }
})

This is a really rough outline of the api, but imagine if each of those nodes had a way to define the behavior entering/leaving the node, as well as a shared context, and something to specify additional params if they are needed.

so if I am on shipping/new, and I want to go to shipping/<shipmentId>/view state, it knows to run the "leaving new" method, and the "entering view" method. If I am on shipping/new, and i want to go to receiving/view, it know to run the "leaving new" method, "leaving shipping" method "entering receving" method, and "entering new" method.

I recently worked on a webapp using backbone (with marionette), and proper state management was one of the really nasty parts. In a mobile app you have a lot less real-estate, so the routing tends to get a lot more complex (since it is switching more often)

mbriggs avatar Dec 02 '12 21:12 mbriggs

@mbriggs that's an interesting approach. @tbranyen I would agree that we need decorators.

Here's my two cents.

I was playing with backbone and was trying to create a one page search web app for github (https://github.com/2k00l/blick).

I ended up creating "controller" objects like so:

return Backbone.Router.extend({

        initialize: function () {

            _.extend(this, {
                dashboard: null,
                issues: null,
                repo: null,
                search: new SearchController({ el: '#main' }),
                results: null,
                issue: null
            });

        },

        routes: {

            '': 'index',
            'results/:term': 'results',
            ':org/:repo': 'repo',
            ':org/:repo/issues': 'issues',
            ':org/:repo/issues/:id': 'issue'

        },

        repo: function (org, repo) {

            this.dashboard = new DashboardController({ el: '#main', organization: org, repo: repo });
            this.repo = new RepoController({ el: '#repo' });
            this.repo.index(org, repo);

        },

        issues: function (org, repo) {

            this.dashboard = new DashboardController({ el: '#main', organization: org, repo: repo });
            this.issues = new IssuesController({ el: '#issues' });
            this.issues.index(org, repo);

        }
});

Each controller has certain actions defined, much like Rails controllers (index, show, edit, etc.). So you end up writing two lines of code to invoke the action in the router, everything else is in "controller".

twokul avatar Dec 03 '12 01:12 twokul

@2k00l couldn't you achieve that same functionality without a Controller? The View constructor in Backbone does the same thing.

tbranyen avatar Dec 03 '12 02:12 tbranyen

@tbranyen well, yes. but there's 'data retrieving' piece which I don't think belongs in the view. Then, imagine that you need more then one view? "Controller" is a nice (imo), entry point and a way for initializing and juggling objects.

twokul avatar Dec 03 '12 02:12 twokul

I think you are hitting on a great topic here, Tim. I have always been frustrated with any of the routing options out there for backbone. While most people think of routing as something purely related to hash changes, the reality is that it is more related to state changes (which may or may not include a hash change). Each state is typically associated with a series of nested views.

At first thought, you would think that you can just create an associative array to match the view hierarchy that will exist. The problem is that you don't really want to have all the views re-render for each state change. So, that is why I think a good solution for routing has to be paired up with region management. When a view exists within a region, it will not be re-rendered if no data has changed since the last render.

jeffwhelpley avatar Dec 03 '12 02:12 jeffwhelpley

I've been thinking about something like this: (experimental api)

// in assignments.js
Assignment.Router = Backbone.Router.extend({
  // Define and match routes to the state machine.
  routes: {
    // Default route to assignments.
    "": "assignments",

    // Remaining mapped.
    "assignments": "assignments",
    "assignments/migration": "assignments.migration",
    "assignments/migration/:id": "assignments.migration.detail"
  },

  // Define the state tree.
  assignments: {
    "enter:route": function() {
      // Top level, create a new Page model.
      this.page = new Page.Model({
        title: "Assignments",

        // The model will automatically attach it to the `app.layout`.
        view: new Assignment.Views.Layout()
      });

      // Default to migrations, triggering the callback is fine.  The state
      // will be detected and only partially updated.
      this.navigate("assignments/migration", true);
    },

    migration: {
      "enter:route": function() {
        var page = this.page;

        // Add a sub-page into the breadcrumbs.
        page.breadcrumbs.add({ subtitle: "Assignments" });

        // Get the current `Page` view and render the migration content.
        page.get("view").setView(".content": new Assignment.Views.Migration({
          model: new Assignment.Model()
        }));
      },

      detail: {
        "enter:route": function(id) {
          var page = this.page;

          // Add a third depth level into the breadcrumbs.
          page.breadcrumbs.add({ subtitle: "Viewing assignment: " + id });

          // Get access to the `Assignment.Model`, update it's `id`, and fetch
          // the latest contents.
          page.get("view").getView(".content").model.set({ id: id }).fetch();
        }
      }
    }
  }
});

// in router.js
var Router = Backbone.RouteManager.extend({
  // Map the default route to the sub router.
  routes: {
    "": new Assignment.Router()
  }
});

Edit: Updated.

tbranyen avatar Dec 03 '12 03:12 tbranyen

My biggest gripe with the backbone router is that the router gets too large and unwieldy as the app gets more complex. Especially if i'm splitting the app into several modules, all of the sudden the router requires every module and it becomes a mess of intertangled and yet unrelated views, model creation and state changes.

I would love to able to define routes (and override routes) from different views or modules themselves, and then have the router call the module to execute the actual pre-routing setup. Seems similar to what's proposed, except the assignment could be defined externally from the router itself, and is registered with the router.

drewzboto avatar Dec 03 '12 07:12 drewzboto

@drewzboto Hrm. you mean creating routes in the either rather than directly attaching ad-hoc with http://backbonejs.org/#Router-route ?

tbranyen avatar Dec 03 '12 15:12 tbranyen

@tbranyen 2 points on your example code. One is that you don't really want your model to be aware of your views, right? It looks like you are only doing this for the top level page view, but is there a reason why you can't just pass the Page.Model into Assignment.Views.Layout?

The other thing is that I am wondering to what degree the code you are laying out can be accomplished through some sort of configuration mapping instead of explicitly implementing the code for each state. I think you of course need to have the ability to implement a state handler/decorator, but ideally for the simple 80% of cases, you could do it all through configuration. what about something like this (thinking of the top of my head and I have not actually tried any of this out):

routingConfiguration: {
    "assignments": {
        model: Page.Model,  // or perhaps give function handler to return the model based on context
        layout: Assign.Views.Layout,
        views: [{
            selector: ".div1",
            view: Blah.View1,
            model: Some.Other.Model   //or the layout level view could be passed in or a function handler
        },
        {
            selector: ".div2",
            view: Blah.View2,
            model: Some.Other.Model2 
        }]
    }
}

For a given view you should be able to have a decorator that is used to render the view, but in simple cases you just use the configuration. I guess this may look a little complex now, but I think with some work you could come up with a streamlined version. In fact, you probably could just treat the layout just like any view in this type of configuration. Like I mentioned in my first comment, though, with something like this you would know for state X, render views a, b and c, but you would need to have a way of determining when you actually need to re-render a view or keep it as is because nothing has changed in the model even if the state is different.

jeffwhelpley avatar Dec 03 '12 17:12 jeffwhelpley

@tbranyen ha, that would be the API I should have noticed in the first place. thanks, i guess i learnt something new. not sure how i missed that. but yes, whatever new way the routemanager goes, please expose a similar mechanism!

drewzboto avatar Dec 04 '12 06:12 drewzboto

@jeffwhelpley The model is simply an example. Has nothing to do with routemanager. I've been using a model to keep the total state of the application in a single place that fires events.

The declarative approach you show is how LayoutManager is supposed to work already, with just a little bit more function noise.

tbranyen avatar Dec 04 '12 16:12 tbranyen

Cool. I will check out LayoutManager. Thanks.

jeffwhelpley avatar Dec 04 '12 16:12 jeffwhelpley

Hi Tim, I think your experimental api is pretty convincing. I think the same could roughly be achieved by nesting route manager? But it's sure a nice to have.

A simple point to add, would be to have easy access to the current route ( e.g. this.get('currentroute') or something similar ). Behind this, I think it would be usefull to prevent a route callback from injecting a view if the current route has changes in between.

Basically:

Backbone.Router.extend({

    routes: {
        "page1": "page1"
    },

    page1 : function() {
        var self = this;
        this.model.fetch().then(function() {
            if( self.currentRoute !== "page1" ) { return; }
            view.render();
        });
    }

});

SBoudrias avatar Dec 12 '12 17:12 SBoudrias