Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple dynamic segments in ember.js

Tags:

ember.js

I currently have my routes defined like this:

App.Router.map(function() {
    this.resource('players', { path: ':page_id' }, function() {
        this.resource('player', { path: ':player_id' });
    });
});

The idea is that I have a list of player names on the left. The player names displayed depend on the page_id. On the right, I display a single player and all its information based on the player_id. The thing is, both are independent, meaning that I could be on the third player page, while displaying the first player in the list, or no player at all.

What I keep trying to do is something like this, which is a method in the PlayersController that gets called when I click to go to the next player page:

doTransition: function() {
    var players = App.Player.findAllForPage(this.playersPerPage, this.currentOffset);
    players.reopen({
        id: this.currentOffset
    });
    var playerController = this.get('controllers.player');
    var currentPlayer = playerController.getWithDefault('content');
    if (currentPlayer) {
        this.transitionToRoute('player', players, currentPlayer);
    } else {
        this.transitionToRoute('players', players);
    }    
}

What I'm trying to do: When I click to go to the next player page, transition to the PlayersRoute if there is no player currently being displayed, otherwise transition to the PlayerRoute so that the player is still displayed when the transitioning is done.

The problem: sometimes the currentPlayer variable is not always null, even if no player is currently being displayed. Is there a way to get around this, perhaps by getting the current route from somewhere?

like image 856
Gabriel G. Roy Avatar asked Jan 13 '23 07:01

Gabriel G. Roy


2 Answers

Given that you say the two sections (list of players based on page_id, and player information based on player_id) are completely independent, it seems to me like you wouldn't nest the routes, and instead, have two named outlets (call them left and right, or page and player, etc) that you selectively render into.

Router:

App.Router.map(function() {
    this.resource('page', { path: ':page_id' });
    this.resource('player', { path: ':player_id' });
 });

application.hbs:

{{outlet page}}
{{outlet player}}

And then you can override your renderTemplate for your page and player routes to render into the appropriate template. To clarify, page route would display what you currently have as the players route (it's dependant on the page_id, but the page has many players, so the route displays the players based on the page), and player route would display the player information based on the player_id.

(As a side note, I don't think you can nest resources the way you do right now with putting resource player under resource players -- I think only routes can be nested.)

EDIT: Using single route with multiple dynamic segments

I think your suggestion could work. From the linked example it seems like you need to create the "parent" resources (not nested routes, but having more general paths, like /page/ and /page/:page_id/player/:player_id) anyway. You can then set up your models individually via the model in the appropriate route, and just provide a serialize hook for the double dynamic segment route:

serialize: function(model) {
    return {
        page_id : this.modelFor('page').get('id') 
        player_id : this.modelFor('player').get('id')
    };
}

Note we're not relying on the model object passed in because you've said that the page and player panels can be completely independent, so we use modelFor instead.

I think you can also handle your logic about default page to render / default player to render if none are suggested here via the redirect hook.

Finally, you would override renderTemplate in your PagePlayer route to actually do the rendering:

renderTemplate: function(model, controller) {
  this.render("page", { into: "page" });
  this.render("player", { into: "player"});
}

I think you have to be careful to NOT render the templates in the more general routes because if you if you move from /page/1/player/2 to /page/1/player/3, the page route is NOT re-entered.

like image 83
Sherwin Yu Avatar answered Feb 08 '23 14:02

Sherwin Yu


While Sherwin's answer gave me a good idea of where I was going, I just wanted to put a complete example and give a general idea of what I ended up implementing. This could be of help for future reference.

I'm going to make it simple by having the models be a simple int, that way we have a direct translation from url to model and vice versa.

Templates:

<script type="text/x-handlebars">
  {{outlet a}}
  {{outlet b}}
</script>

<script type="text/x-handlebars" id="a">
  {{model}}
</script>

<script type="text/x-handlebars" id="b">
  {{model}}
</script>

Application:

App = Ember.Application.create();

App.Router.map(function() {
  // This route has 2 dynamic segments
  this.resource("ab", { path: "/:a/:b" });
});

App.IndexRoute = Ember.Route.extend({
  redirect: function() {
    // At the entry point, encapsulate the 2 models in the context object,
    // and transition to the route with dual dynamic segments
    this.transitionTo('ab', {a: 3, b:4});
  }
});

App.AbRoute = Ember.Route.extend({
  model: function(params) {
    // The model is {a, b} directly
    return params;
  },

  renderTemplate: function(){
    // Render in the named outlet using the right controller
    this.render('a', {into: 'application', outlet: 'a', controller: 'a'});
    this.render('b', {into: 'application', outlet: 'b', controller: 'b'});
  },

  serialize: function(model) {
    return {
      a: model.a,
      b: model.b
    };
  },

  setupController: function(controller, model) {
    // Setup each controller with its own model
    this.controllerFor('a').set('model', model.a);
    this.controllerFor('b').set('model', model.b);
  }
});

Additional note:

It would've been possible to have a single 'ab' template rendering {{model.a}} and {{model.b}} from the AbController, but I thought having separate controllers and templates was cleaner, and that it enabled reusability. Additionally, one of the controllers could've been an ArrayController and it would've work perfectly fine.

JS Bin of the example

like image 26
Gabriel G. Roy Avatar answered Feb 08 '23 13:02

Gabriel G. Roy