Ember way of outputting one row per model attribute (hasMany)

Hello,

I’m still experimenting with my first ember app. Now I’ve found a solution for the topics issue. However, I think there must be a smarter one.

Summary: I have two models which have a one to many relationship:

app/models/event.js:

import DS from 'ember-data';

export default DS.Model.extend({
  title: DS.attr('string'),
  appointments:  DS.hasMany('appointment'),
});

app/models/appointment.js:

import DS from 'ember-data';

export default DS.Model.extend({
  date: DS.attr('date'),
  event: DS.belongsTo('event'),
});

Given the following data:

Appointments:

ID | Date | Event
1 | 2015-04-01 20:00 | 1
2 | 2015-04-30 19:00 | 1
3 | 2015-04-02 12:00 | 2

Events:

ID | Title | Appointments
1 | Event A | [1,2]
2 | Event B | [3]

When I just do a {{#each event in events}} at my event template, I get two rows. One for each event.

However, my output has to be like this (note the sorting!):

2015-04-01 20:00 Event A
2015-04-02 12:00 Event B
2015-04-30 19:00 Event A

This is how my current solution looks like:

app/controllers/events.js:

import Ember from 'ember';

export default Ember.Controller.extend({
  rows: function() {
    var events = this.get('events'),
    rows = [];

    events.forEach(function(event) {
      var appointments = event.get('appointments');
      appointments.forEach(function(appointment) {
        // Deep copy not working: var row = Ember.$.extend(true, {}, event);
        var row = {id: event.get('id')};
        row.date = appointment.get('date');
        row.aid = appointment.get('id');
        row.event = event;
        rows.pushObject(row);
      });
    });

    var rowsController = Ember.ArrayController.create({
      content: rows,
      sortProperties: ['date'],
      sortAscending: true
    });

    return rowsController;
  }.property('@each.appointments')
});

app/templates/events.hbs:

<div class="row">
  <table class="table">
    <thead>
      <tr>
        <th>Date</th>
        <th>Title</th>
        <th></th>
      </tr>
    </thead>
    <tbody>
      {{#if events}}
        {{#each row in rows}}
          <tr>
            <td>
              {{format-date row.date}}
            </td>
            <td>
              {{row.event.title}}
            </td>
            <td>
                {{#link-to "events.show" row.event}}Tickets{{/link-to}}
            </td>
          </tr>
        {{/each}}
      {{else}}
        <tr><td colspan="3">No Events found.</td></tr>
      {{/if}}
    </tbody>
  </table>
</div>

Is there any “Ember way” of doing this more nicely?

Kind regards, haggis

The problem with this approach is, that the object/model sent to the route via {{#link-to "events.show" row}}Tickets{{/link-to}} is not the original one and therefore I get errors at the other route.

So how is this done correctly?

Any hint would be highly appreciated :wink:

edit: saving the object in row.event helps. But it still feels wrong.

I’m a little bit fuzzy on the all the details of what you’re doing so I apologize if this is off.

It maybe easier to grab the appointments off of the store filter, sort, and display them instead of creating new objects.

Why not just sort by date on appointments appointments.sortBy('date') , generate table with {{each appointment in appointments}} and use appointment.event.title ?

Thanks for your suggestions - sounds reasonable. I guess I was irritated by the fact, the routers model are events.

So you mean instead of looping about all events I should just do something like the following?

var appointmentsUnsorted = this.store.all('appointment');
var appointmentsSorted = Ember.ArrayController.create({
      content: appointmentsUnsorted,
      sortProperties: ['date'],
      sortAscending: true
});

To maximize future proofing it’s best not to use the proxying properties of the controller see this for more suggestions to this end https://gist.github.com/samselikoff/1d7300ce59d216fdaf97.

If both events and appointments are rendered on the same page then it may make sense to have both be present on your model and you’re using the attrs property then you can just set both appointments and events as models on the controller via the router. If you want to still use model you could return a hash with both appointments and events as the model.