1
votes

I am building an Ember app to show a simple Twitter-like tagging system. When a user visits /items, he or she will see a list of all items. When the user visits /tags, the user will see a list of tags as links. When the user clicks one of these links, the user should be directed to /tags/:id and will see all items tagged with that specific tag. Then the user will be able to search/sort/manipulate the items as he/she would be able to from the ItemsRoute.

How can I make TagRoute use ItemsController and render the items template, using the tag's associated items as the model?

I have tried different combinations of the hooks in TagRoute, and I'm not able to find a recipe that works. There seems to be a fundamental misunderstanding on my part.

Here is my relevant code:

router.js.coffee

App.Router.map ()->
  @resource 'items'
  @resource 'tags', ->
    @resource 'tag', path: ':tag_id'

routes/tag.js.coffee

App.TagRoute = Ember.Route.extend
  model: (params)->
    @get('store').find 'tag', params.tag_id
  controllerName: 'items'
  setupController: (controller, model)->
    @controllerFor('items').set('model', model.items)
  renderTemplate: ->
    @render 'items', ->
      into: 'tags'
      controller: 'items'

templates/tags.hbs

<ul class="tag-list">
  {{#each tag in model}}
  <li>
    {{#link-to 'tag' tag}}
      {{tag.name}}
    {{/link-to}}
  </li>
  {{/each}}
</ul>

{{outlet}}

models/items.js.coffee

App.Item = DS.Model.extend(
  body:       DS.attr('string')
  checked:    DS.attr('boolean')
  tags:       DS.hasMany('tag')
)

models/tags.js.coffee

App.Tag = DS.Model.extend(
  name:             DS.attr('string')
  taggings_count:   DS.attr('number')
  items:            DS.hasMany('item')
)

Currently, this give me an error:

Error while processing route: tag Cannot assign to read only property 'name' of function () {
        return {
          into: 'tags',
          controller: 'items'
        };
      } TypeError: Cannot assign to read only property 'name' of function () {
        return {
          into: 'tags',
          controller: 'items'
        };
      }

Looking at the Ember Routes Inspector in Chrome, the controllerName property is the only one which overrides Ember's defaults, and Ember still tries to render a generated tag template.

1
Bidirectional has many mappings are always making things more complicated. How about linking back to items with a query param?tag. Then you could pass it to the model and show only items with that tag. No need for a tags/:id route. Just link to items with query param {tag;tagname}ahmed.hoban
Could you put this code into a JSBin?Gaurav
@Gaurav I'm not sure how to do routing in a JSBin, but here is the link: link. I hope what is there is helpful. My latest idea was to have the TagRoute redirect to a TagItemsRoute, but I'm having similar problems there. For example, ember generates a 'tag/items' template instead of using my specified 'items' template.nufftenthousand
@ahmed.hoban Is it possible to filter a model by an associated model, using queryParams set in the controller? I'm trying it out. My understanding was that queryParams were best used for things like filtering and sorting on a model's attributes, and that routing was best for finding models by associations.nufftenthousand
Its transparent if your backend can process /items?tag={tag} or /items?tagId={tag.id}. This would be a filter, not an associated model. Im doing the same thing for a different case and it works like a charm. Just make tag or tagId a queryParam in your itemsController. queryParams: { page: { refreshModel: true }, Pass the tag/tagId as a queryParams in your links/ transitionTo calls and thats it. I dont think there is any drawback using a filter a like that.ahmed.hoban

1 Answers

1
votes

As ahmed.hoban suggested, I have solved this using query params. This helps me avoid duplicating routes and having a tangled router. It hits the database, which is not preferable, but I'm not sure at this point if I'll make that a requirement. I have control over the full-stack, so I was able to make adjustments on the back-end to support the request.

router.js.coffee

App.Router.map ()->
  @resource 'tags', path: '/', ->
    @resource 'items'

routes/tag.js.coffee - deleted

templates/tags.hbs

<ul class="tag-list">
  {{#each tag in model}}
  <li>
    {{#link-to 'items' (query-params tag=tag.id)}}
      {{tag.name}}
    {{/link-to}}
  </li>
  {{/each}}
</ul>

{{outlet}}

controllers/items.js.coffee

App.ItemsController = Ember.ArrayController.extend(
  needs: 'tags'

  queryParams: ['tag']
  tag: null

  items: (->
    tag = @get 'tag'
    if tag
      @store.find 'item', tag: tag
    else
      @get 'model'
  ).property('tag', 'model')
)