Implementing sub-routing #202

Closed
mehcode opened this Issue Sep 26, 2012 · 9 comments

Comments

Projects
None yet
4 participants
@mehcode
Member

mehcode commented Sep 26, 2012

By sub-routing I mean starting with an index.html page that contains only <body /> and eventually getting to the point where when the URL changes I only want this one section of the page to update (rather than the entire page to be disposed, recreated, etc).

To achieve this I've implemented a routes function and route action in the base Controller class that when routed to from a parent controller it can pass in the remaining url to route against and continue down the chain. Re-routing, as long as something higher-up doesn't match, will only dispose/create the deepest layer.

An example:

# routes.coffee
  (match) ->
    match 'login', 'login#show'
    match '*url', 'site#route'

# controllers
class LoginController extends Chaplin.Controller
  show: -> # ...

class HeaderController extends Chaplin.Controller
  show: -> # ...

class SiteController extends Chaplin.Controller
  # These are instantiated after the action is fired
  # of the controller -- or in the case of a route
  # controller that 'technically' doesn't have an action,
  # these are instantiated before the route's action is invoked
  subcontrollers: [
    'header#show'
  ]

  routes: (match) ->
    match '', 'index#show'
    match 'admin', 'admin#show'

class IndexController extends Chaplin.Controller
  show: -> # ...

class AdminController extends Chaplin.Controller
  show: -> # ...

If the url routed is login then the login controller is fired, that's it. If the url routed is admin then the site controller is fired, firing the header controller and then forwarding the url splat to the route function and routing to the admin controller. If the user were then to route to the `` then the site controller would be matched again however only the admin controller would be disposed and the index controller created.

Hope I've explained it clearly. I have all this implemented somewhat nicely. I had wanted to gauge acceptance to this before I make a fork and pull request, etc.

Thoughts?

@paulmillr

This comment has been minimized.

Show comment Hide comment
@paulmillr

paulmillr Sep 26, 2012

Member

when the URL changes I only want this one section of the page to update

I achieve this via initiating header / footer etc. in Application and subscribing there to events like header:show.

Maybe I ain't understood stuff fully, but what advantages does your proposal have above this idea?

Member

paulmillr commented Sep 26, 2012

when the URL changes I only want this one section of the page to update

I achieve this via initiating header / footer etc. in Application and subscribing there to events like header:show.

Maybe I ain't understood stuff fully, but what advantages does your proposal have above this idea?

@mehcode

This comment has been minimized.

Show comment Hide comment
@mehcode

mehcode Sep 27, 2012

Member

I achieve this via initiating header / footer etc. in Application and subscribing there to events like header:show.

That works nicely for applications that have the header everywhere. I suppose I wasn't clear enough; apologies.

Take the login screen -- I only want one view -- no header. On most of the other pages I'd like the header always there and not refreshing its contents when a route is followed. At the same time I want when someone hits logout for the header to now not be there as the login controller only shows it self.

This approach allows for the above plus quite a bit more. Say we have a page that has a smaller area with tabs and we want the tabs to be routes themselves. We can have it so that when a tab is clicked it can be a sub-route of the main controller. The end result is that only the tabbed container is refreshing and not the rest of the page.

Yet another possibility is github's file explorer.

Member

mehcode commented Sep 27, 2012

I achieve this via initiating header / footer etc. in Application and subscribing there to events like header:show.

That works nicely for applications that have the header everywhere. I suppose I wasn't clear enough; apologies.

Take the login screen -- I only want one view -- no header. On most of the other pages I'd like the header always there and not refreshing its contents when a route is followed. At the same time I want when someone hits logout for the header to now not be there as the login controller only shows it self.

This approach allows for the above plus quite a bit more. Say we have a page that has a smaller area with tabs and we want the tabs to be routes themselves. We can have it so that when a tab is clicked it can be a sub-route of the main controller. The end result is that only the tabbed container is refreshing and not the rest of the page.

Yet another possibility is github's file explorer.

@paulmillr

This comment has been minimized.

Show comment Hide comment
@paulmillr

paulmillr Sep 27, 2012

Member

Got it, GitHub file explorer is a very good example! It’s a fucking pain to emit ten etc events in this case.

I’d be interested in a simple solution for problems like this, but maybe there is already something. Maybe @molily could tell us something?

Member

paulmillr commented Sep 27, 2012

Got it, GitHub file explorer is a very good example! It’s a fucking pain to emit ten etc events in this case.

I’d be interested in a simple solution for problems like this, but maybe there is already something. Maybe @molily could tell us something?

@paulmillr

This comment has been minimized.

Show comment Hide comment
@paulmillr

paulmillr Sep 27, 2012

Member

How I solved the problem on http://ost.io:

  • There is navigation that should be shown on user, repo & topic pages, but only on them (chaplinjs/chaplin/#202)
  • Base page View emits navigation:change with result of execution of getNavigationData
  • getNavigationData can be defined in child views. If the function ain’t defined, navigation:change will be emitted with empty arg.
  • NavigationView listens to the event and clears its model silently every time and then sets attributes to the result of getNavigationData. When navdata is empty, the view will be just empty too.

But this is only one entity (navigation).

Member

paulmillr commented Sep 27, 2012

How I solved the problem on http://ost.io:

  • There is navigation that should be shown on user, repo & topic pages, but only on them (chaplinjs/chaplin/#202)
  • Base page View emits navigation:change with result of execution of getNavigationData
  • getNavigationData can be defined in child views. If the function ain’t defined, navigation:change will be emitted with empty arg.
  • NavigationView listens to the event and clears its model silently every time and then sets attributes to the result of getNavigationData. When navdata is empty, the view will be just empty too.

But this is only one entity (navigation).

@molily

This comment has been minimized.

Show comment Hide comment
@molily

molily Oct 2, 2012

Member

Thanks for the proposal!

Some background thoughts in advance: A URL is typically supposed to reflect an identifiable state of the application. For example, Rails has the concept of individual resources and a standard URL scheme. However, a typical UI consists of several modules which have all their individual state. A resourceful URL cannot all capture these states, nonetheless it should be able to restore the main view.

Chaplin’s Router and Dispatcher allow for one active controller which is created and disposes with every URL change. Of course, this controller isn’t meant to build up and tear down the whole UI again and again. In a bigger app, there need to be additional controllers, which manage models and views which are independent or supplementary.

The standard way is to create persistent controllers in Chaplin.Application. For example a SessionController, HeaderController, NavigationController. They may govern some models and views and react to route matches or Pub/Sub messages, updating themselves independently. The Facebook example is using this approach.

This makes sense for most apps, especially those who have resourceful URLs, a main view which represents this resource (e.g. show or edit a product) and a fixed application chrome (e.g. header, sidebar, footer). It’s less suitable for apps that consist of multiple flexible widgets and heavily nested views. Chaplin isn’t prepared for that at the moment and it’s an important question how to extend Chaplin for this.

My two cents regarding this proposal: Nesting controllers and nested routing add a complexity that I personally would try to avoid as a general solution. It probably makes sense for nested resources which are displayed in heavily nested views. I guess managing these trees gets really difficult. I’d love to see the implementation though.

Chaplin borrowed the controller concept from Rails mainly because of its simplicity and clarity. There’s always one controller handling a “request”, there’s a central routes file. It’s easy to find your way in such an MVC structure. Of course, Rails offers more power through inheritance, mixins, before filters and so on.

On the front-end, I think it makes more sense to have something like Marionette’s Regions and Layouts. A controller might create views and show them, but they aren’t stricly dependent. Of course this contradicts with Chaplin’s conventional controller disposal, but I think it reaches its limits.

For simpler nested views like tabs, I wouldn’t dump the whole Chaplin concept though or start nesting controllers. On the mailing list, there was a similar question: https://groups.google.com/forum/#!topic/chaplin-js/4rRf1AfEw2M

Member

molily commented Oct 2, 2012

Thanks for the proposal!

Some background thoughts in advance: A URL is typically supposed to reflect an identifiable state of the application. For example, Rails has the concept of individual resources and a standard URL scheme. However, a typical UI consists of several modules which have all their individual state. A resourceful URL cannot all capture these states, nonetheless it should be able to restore the main view.

Chaplin’s Router and Dispatcher allow for one active controller which is created and disposes with every URL change. Of course, this controller isn’t meant to build up and tear down the whole UI again and again. In a bigger app, there need to be additional controllers, which manage models and views which are independent or supplementary.

The standard way is to create persistent controllers in Chaplin.Application. For example a SessionController, HeaderController, NavigationController. They may govern some models and views and react to route matches or Pub/Sub messages, updating themselves independently. The Facebook example is using this approach.

This makes sense for most apps, especially those who have resourceful URLs, a main view which represents this resource (e.g. show or edit a product) and a fixed application chrome (e.g. header, sidebar, footer). It’s less suitable for apps that consist of multiple flexible widgets and heavily nested views. Chaplin isn’t prepared for that at the moment and it’s an important question how to extend Chaplin for this.

My two cents regarding this proposal: Nesting controllers and nested routing add a complexity that I personally would try to avoid as a general solution. It probably makes sense for nested resources which are displayed in heavily nested views. I guess managing these trees gets really difficult. I’d love to see the implementation though.

Chaplin borrowed the controller concept from Rails mainly because of its simplicity and clarity. There’s always one controller handling a “request”, there’s a central routes file. It’s easy to find your way in such an MVC structure. Of course, Rails offers more power through inheritance, mixins, before filters and so on.

On the front-end, I think it makes more sense to have something like Marionette’s Regions and Layouts. A controller might create views and show them, but they aren’t stricly dependent. Of course this contradicts with Chaplin’s conventional controller disposal, but I think it reaches its limits.

For simpler nested views like tabs, I wouldn’t dump the whole Chaplin concept though or start nesting controllers. On the mailing list, there was a similar question: https://groups.google.com/forum/#!topic/chaplin-js/4rRf1AfEw2M

@mehcode

This comment has been minimized.

Show comment Hide comment
@mehcode

mehcode Oct 2, 2012

Member

I appreciate the detailed responses, thank you.

As for our implementation; I show you the below with a disclaimer that its nowhere near ideal. It's mostly serving as a proof-of-concept that it can be done.

It works and nests nicely in implementation. At your bequest I would be glad to integrate the functionality (in a significantly more cleaned up fashion) into Chaplin.

As for the complexity -- I agree that it increases when controllers are nested. However, if they are not nested there is no difference. And composite controllers like this can allow for some fairly complex layouts. The regions and layouts from Marionette look interesting as well.

Member

mehcode commented Oct 2, 2012

I appreciate the detailed responses, thank you.

As for our implementation; I show you the below with a disclaimer that its nowhere near ideal. It's mostly serving as a proof-of-concept that it can be done.

It works and nests nicely in implementation. At your bequest I would be glad to integrate the functionality (in a significantly more cleaned up fashion) into Chaplin.

As for the complexity -- I agree that it increases when controllers are nested. However, if they are not nested there is no difference. And composite controllers like this can allow for some fairly complex layouts. The regions and layouts from Marionette look interesting as well.

@Rendez

This comment has been minimized.

Show comment Hide comment
@Rendez

Rendez Oct 7, 2012

Contributor

The standard way is to create persistent controllers in Chaplin.Application. For example a SessionController, HeaderController, NavigationController. They may govern some models and views and react to route matches or Pub/Sub messages, updating themselves independently. The Facebook example is using this approach.

That's the way for now we're doing it at Moviepilot as @molily suggests. However I've come around the idea that Layout should perhaps leave soon the responsibility of handling link "click" events, or rather do so only in favor or Regions. The idea would be to create an interchangeable Layout system. This would be done by say a class ApplicationView by subscribing to the route-matching event.

If Layout is defined for every controller and if there's BaseController, then the default Layout is already defined in the prototype. Such ApplicationView would be on charge of disposing and creating those Layouts, which in turn contain Views such as Header, Navigation etc.

Let me know what you think. The reason to not implement this right away on my side is lack of time atm.

Contributor

Rendez commented Oct 7, 2012

The standard way is to create persistent controllers in Chaplin.Application. For example a SessionController, HeaderController, NavigationController. They may govern some models and views and react to route matches or Pub/Sub messages, updating themselves independently. The Facebook example is using this approach.

That's the way for now we're doing it at Moviepilot as @molily suggests. However I've come around the idea that Layout should perhaps leave soon the responsibility of handling link "click" events, or rather do so only in favor or Regions. The idea would be to create an interchangeable Layout system. This would be done by say a class ApplicationView by subscribing to the route-matching event.

If Layout is defined for every controller and if there's BaseController, then the default Layout is already defined in the prototype. Such ApplicationView would be on charge of disposing and creating those Layouts, which in turn contain Views such as Header, Navigation etc.

Let me know what you think. The reason to not implement this right away on my side is lack of time atm.

@mehcode

This comment has been minimized.

Show comment Hide comment
@mehcode

mehcode Oct 11, 2012

Member

I appreciate the detailed explanations; I'm starting to see the benefit that a flexible layout/region system would have.
The way I see it is say a certain controller#action would subscribe to a particular layout. This layout would be in charge of instantiating regions and attaching them as it sees fit. The controller action would be in charge with replacing a particular region with its view. When a layout is subscribed to it will only instantiate new regions if it cannot find them in some region pool. This would allow a header region to be used across controllers (and even layouts) without it re-rendering. This would also keep all the routes in one place rather than the nesting I'm doing now. Of course the default layout should have one region that the view in the controller action is replaced by on default so that simple chaplin apps would still work.

Let me know what you think; I'd love to implement something like this.

This could also allow for a nice place to put view transition animations (the region manager).

Member

mehcode commented Oct 11, 2012

I appreciate the detailed explanations; I'm starting to see the benefit that a flexible layout/region system would have.
The way I see it is say a certain controller#action would subscribe to a particular layout. This layout would be in charge of instantiating regions and attaching them as it sees fit. The controller action would be in charge with replacing a particular region with its view. When a layout is subscribed to it will only instantiate new regions if it cannot find them in some region pool. This would allow a header region to be used across controllers (and even layouts) without it re-rendering. This would also keep all the routes in one place rather than the nesting I'm doing now. Of course the default layout should have one region that the view in the controller action is replaced by on default so that simple chaplin apps would still work.

Let me know what you think; I'd love to implement something like this.

This could also allow for a nice place to put view transition animations (the region manager).

@paulmillr

This comment has been minimized.

Show comment Hide comment
@paulmillr

paulmillr Oct 15, 2012

Member

closing in favor of regions

Member

paulmillr commented Oct 15, 2012

closing in favor of regions

@paulmillr paulmillr closed this Oct 15, 2012

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment