How to choose an object from a nested model in a template

I have the following model tree already loaded using a single request to the backend:

- document
  - group_1
    - observation_1
    - observation_2
  - group_2
    - observation_3
    - observation_4

And here are the corresponding models.

app/models/document.js

export default DS.Model.extend({
  code: DS.attr('string'),
  name: DS.attr('string'),
  groups: DS.hasMany('group'),
});

app/models/group.js

export default DS.Model.extend({
  code: DS.attr('string'),
  name: DS.attr('string'),
  document: DS.belongsTo('document'),
  observations: DS.hasMany('observation')
});

app/models/observation.js

export default DS.Model.extend({
  code: DS.attr('string'),
  name: DS.attr('string'),
  value: DS.attr('string'),
  group: DS.belongsTo('group')
});

Having the following test data

- document
  code: 'progress_note'
  name: 'Progress note'
  - group
    code: 'patient_id'
    name: 'Patient data'
    - observation
      code: 'first_name'
      name: 'First name'
      value: 'David'
    - observation
      code: 'last_name'
      name: 'Last name'
      value: 'Bowie'
  - group
    code: 'vital_signs'
    name: 'Vital signs'
    - observation
      code: 'temperature'
      name: 'Temperature'
      value: '36.7'
    - observation
      code: 'heart_rate'
      name: 'Heart rate'
      value: '60'

I can show the data iterating over the model with the following template:

<p>{{document.name}} ({{document.code}})</p>
<ul>
  {{#each document.groups as |group|}}
    <li>{{group.name}}</li>
    <ul>
    {{#each group.observations as |observation|}}
      <li><label>observation.name</label>{{input type="text" value=observation.value}}</li>
    {{/each}}
    </ul>
  {{/each}}
</ul>

However I want to write a template in which the fields appear in a predefined order. As shown in the following (non working) code:

<ul>
  <li>{{getGroup('vital_signs').name}}<li>
  <ul>
    <li>{{input type="text" value=getObservation('vital_signs', 'heart_rate').value}}</li>
    <li>{{input type="text" value=getObservation('vital_signs', 'temperature').value}}</li>
  </ul>
  <li>{{getGroup('patient_id').name}}</li>
  <ul>
    <li>{{input type="text" value=getObservation('patient_id', 'first_name').value}}</li>
    <li>{{input type="text" value=getObservation('patient_id', 'last_name').value}}</li>
  </ul>
</ul>

Any hints in how to implement the getGroup and getObservation functions?

There are three approaches that jump to mind, at least to meā€¦ After typing this all out I think I prefer #3:

1. Use computed properties

on the controller/component OR on the models. This would work best for a finite and small set of predetermined/preordered groups. It also assumes there is only one group of each ā€˜codeā€™ per document:

vitalSignsGroup: Ember.computed.filterBy('model.document.groups', 'code', 'vital_signs'),
patientIdGroup: Ember.computed.filterBy('model.document.groups', 'code', 'patient_id'),
//... etc. 

Then reference them in your template like:

<ul>
  <li>{{vitalSignsGroup.name}}<li>
  <ul>
    <li>{{input type="text" value=vitalSignsGroup.heart_rate}}</li>
    <li>{{input type="text" value=vitalSignsGroup.temperature}}</li>
  </ul>
...
</ul>

(You may need to do vitalSignsGroup.firstObject, or instead of using filterBy in the controller/component you could use a computed property to do the same thing but instead of returning an array just return the value, like return this.get('model.document.groups').findBy('code', 'vital_signs');, etc.)

Also youā€™d need to extrapolate for observations since you have multiple levels of the same basic problem, aka you need to reference the children by a code.

2. Write a custom helper

to use in your template, with an interface similar to what you wrote above. Actually because youā€™ll need block structure you may want to use components. This could get kinda verbose and messy though.

3. Use computed properties along with the ā€œwithā€ helper:

Iā€™d actually put this computed property on your document model, and put a similar one on the group model (which collects the observations instead of the groups):

/* this would return an object of all your groups, keyed by the code, e.g.:
{
  vital_signs: <DS.Model Group>,
  patient_id: <DS.model Group>,
}
*/
codedGroups: Ember.computed('groups.@each.code', function(){
  return this.get('groups').reduce(function(collector, item){
    collector[item.get('code')] = item; 
    return collector;
  }, {});
}),

Then in your template:

{{#with model.document.codedGroups.vital_signs as |vitals|}}
  <li>{{vitals.name}}<li>
  <ul>
    <li>{{input type="text" value=vitals.codedObservations.heart_rate.value}}</li>
    ...
  </ul>
{{/with}}

Dan,

Thank you very much for your quick and detailed response. Iā€™ll do some experiments and post here about the results.

Regards, Hector

Since I am just learning Ember I used the first approach.

Here are some code snippets of the working code, sorry for using some property names in Spanish.

nota_evolucion_soap.js

import Ember from 'ember';
import MedicalRecordDocument from './medical-record-document';

export default MedicalRecordDocument.extend({
  fichaIdentificacionGroup: Ember.computed('document.groups', function(){
    return this.get('document.groups').findBy('code', 'ficha_identificacion');
  }),

  apellidoPaterno: Ember.computed('fichaIdentificacionGroup.observations', function() {
    return this.get('fichaIdentificacionGroup.observations').findBy('code', 'apellido_paterno');
  }),

  apellidoMaterno: Ember.computed('fichaIdentificacionGroup.observations', function() {
    return this.get('fichaIdentificacionGroup.observations').findBy('code', 'apellido_materno');
  }),

  signosVitalesGroup: Ember.computed('document.groups', function(){
    return this.get('document.groups').findBy('code', 'signos_vitales');
  }),

  sistolica: Ember.computed('signosVitalesGroup.observations', function() {
    return this.get('signosVitalesGroup.observations').findBy('code', 'sistolica');
  }),

  diastolica: Ember.computed('signosVitalesGroup.observations', function() {
    return this.get('signosVitalesGroup.observations').findBy('code', 'diastolica');
  }),

  notaEvolucionGroup: Ember.computed('document.groups', function(){
    return this.get('document.groups').findBy('code', 'nota_evolucion');
  }),

  subjetivo: Ember.computed('notaEvolucionGroup.observations', function() {
    return this.get('notaEvolucionGroup.observations').findBy('code', 'subjetivo');
  }),

  objetivo: Ember.computed('notaEvolucionGroup.observations', function() {
    return this.get('notaEvolucionGroup.observations').findBy('code', 'objetivo');
  }),

});

nota_evolucion_soap.hbs

<h4>{{document.name}}</h4>

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">{{fichaIdentificacionGroup.name}}</h3>
  </div>
  <div class="panel-body">
    {{string-field name=apellidoPaterno.name string=apellidoPaterno.value on-leave-field=(action 'updateObservation' apellidoPaterno)}}
    {{string-field name=apellidoMaterno.name string=apellidoMaterno.value on-leave-field=(action 'updateObservation' apellidoMaterno)}}
  </div>
</div>

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Subjetivo / Objetivo</h3>
  </div>
  <div class="panel-body">
    {{string-field name=subjetivo.name string=subjetivo.value on-leave-field=(action 'updateObservation' subjetivo)}}
    {{string-field name=objetivo.name string=objetivo.value on-leave-field=(action 'updateObservation' objetivo)}}
  </div>
</div>

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">{{signosVitalesGroup.name}}</h3>
  </div>
  <div class="panel-body">
    {{string-field name=sistolica.name string=sistolica.value on-leave-field=(action 'updateObservation' sistolica)}}
    {{string-field name=diastolica.name string=diastolica.value on-leave-field=(action 'updateObservation' diastolica)}}
  </div>
</div>

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">AnƔlisis / Plan</h3>
  </div>
  <div class="panel-body">
    Panel content
  </div>
</div>

This is a screenshot of the resulting page.

Iā€™ll keep studying to make this code less verbose.

Any comments are welcome.

1 Like

Awesome! For ā€œjust learning Emberā€ it looks like youā€™re doing really well. I like to think that weā€™re all ā€œjust learning Emberā€ :grin:

Also itā€™s great when people share their findings here for others.

Good luck!