Pass in a model (array) to helper fails to use findBy


#1

I’m trying to pass in a model containing an array of objects like that:

# routes/working-hours.js
model() {
    let monday = EmberObject.create({
      id: 1,
      day: 0,
      state: 0,
      opens: this._fromMillisToHuman(32400000), //9:00 am
      closes: this._fromMillisToHuman(72000000) //8:00 pm
    });
let tuesday = EmberObject.create({
      id: 2,
      day: 1,
      state: 0,
      opens: this._fromMillisToHuman(32400000), //9:00 am
      closes: this._fromMillisToHuman(72000000) //8:00 pm
    });
... // all the days of the week

return [monday, tuesday, wednesday, thursday, friday, saturday, sunday];
  },
});

from a template to a helper defined as follows:

#helpers/weekday-state.js

import { helper } from '@ember/component/helper';

export function weekdayState([model], namedArgs) {
  return model.findBy('id', namedArgs.weekDay).get('state');
}

export default helper(weekdayState);

When I call the helper from the template:

# templates/working-hours.hbs
...
{{#each weekdays as |weekday|}}
...
{{weekday-state model weekDay=weekday.id}}

I always get Cannot read property 'get' of undefined. I get all the 7 days array in the helper and the ids are correct.

If I call model[0].get('state') it returns the right value.

When I call

model.findBy('id', namedArgs.weekDay)

I get an Ember object:

found: <Ember.Object:ember588>

Model I get as parameter looks like this:

<Ember.Object:ember583>,<Ember.Object:ember584>,<Ember.Object:ember585>,<Ember.Object:ember586>,<Ember.Object:ember587>,<Ember.Object:ember588>,<Ember.Object:ember589>

What I’m trying to do is:

  • display all the days of the week (Monday through Sunday), row by day along with open/close time and state:
    • state: open
    • from: 8:00am
    • to: 8:00pm
  • some of the week days have their working hours saved by the user, some have not.
  • set the working hours (open/close time) as well as the corresponding state of the shop (open, closed, divided (has a lunch break)).

See the attached screenshot.

So I have an array of 7 days and another array of 0-7 days for which open/close times were set up. That’s why I’m trying to use findBy Array method to check if the week day I have as current in the loop is among the selected by User days.

What’s wrong with that ? Can’t I pass in objects to helpers, just primitive data (Strings, Integers…). If so, how to solve this ? Thank you.


#2

Hello!

I did some digging and it seems to work fine. I created a Ember Twiddle with your code and it works as expected.

https://ember-twiddle.com/3f10148e8dca08bb1622ed55e0079f91?openFiles=routes.index.js%2C

Note that in the template I changed weekdays to just model as to get the data from the route, this is the way to do that.

As for the helper, yes you should be able to pass arrays and ember objects etc to it, so that is not the problem.

I hope this helps you.


#3

@josemarluedke: Thank you for your response and your time. Yes, your code works fine. I think, the problem is that I pass weekdays as follows from working-hours.js route:

# routes/working-hours.js

export default Route.extend(AuthenticatedRouteMixin, {
  constants: service(), 

  setupController(controller, model) {
    // Call _super for default behavior
    this._super(controller, model);
    this.controller.set('weekdays', this.get('constants.days'));
    this.controller.set('states', this.get('constants.states'));
    let daysIds = model.map(weekday => weekday.day);
    //console.log('daysId: ' + daysIds);
    //console.log('friday: ' + model.findBy('id', 5).get('day'));
    this.controller.set('existingDaysIds', daysIds);
  },

model() {
    let monday = EmberObject.create({
      id: 1,
      day: 0,
      state: 0,
      opens: this._fromMillisToHuman(32400000), //9:00 am
      closes: this._fromMillisToHuman(72000000) //8:00 pm
    });
...// other days as before

I define weekdays as constants in constants.js service:

# services/constants.js

export default Service.extend({
  i18n: service('i18n'),
  init() {
      this._super(...arguments);
      this.set('states',
        [
          {id: 0, name: this.get('i18n').t('states.open')},
          {id: 1, name: this.get('i18n').t('states.closed')},
          {id: 2, name: this.get('i18n').t('states.divided')}
        ]
      );
      this.set('days',
        [
          {id: 0, name: this.get('i18n').t('weekdays.monday')},
          {id: 1, name: this.get('i18n').t('weekdays.tuesday')},
          {id: 2, name: this.get('i18n').t('weekdays.wednesday')},
          {id: 3, name: this.get('i18n').t('weekdays.thursday')},
          {id: 4, name: this.get('i18n').t('weekdays.friday')},
          {id: 5, name: this.get('i18n').t('weekdays.saturday')},
          {id: 6, name: this.get('i18n').t('weekdays.sunday')}
        ]
      );
    }

So, in working-hours.hbs I loop on weekdays (all the days of the weeks), model hook will return real days of the week for which open/close times were entered.

# templates/working-hours.hbs

{{#each weekdays as |weekday|}}
...

May by the problem in in difference of objects I pass in to the helper:

  • constants week days are not of the same type (or whatever) as the days I hard-coded in model hook of the router ? Here is what I get in the console when calling:
{{#each weekdays as |weekday|}}
      {{weekday-state model weekDay=weekday.id}}

# in the helper
console.log(model.findBy('id', namedArgs.weekDay));
=> Class {id: 1, day: 0, state: 0, opens: "09:00", closes: "20:00", …}
... //all the model passed in days
=> Class {id: 6, day: 5, state: 1, opens: 0, closes: 0, …}

Really weird :(. When I call findBy in the same way directly on the Array passed in to the helper, it works:

# Chrome console
let days = [{id:0, name: 'toto'}, {id:2, name:'yoyo'}];
days.findBy('id', 2);
=> {id: 2, name: "yoyo"}

#4

I’m not sure if I’m fully understanding what your bug is, but I do see opportunities to radically simplify this code, including not needing the findBy at all.

One thought is that it’s going to simpler if you delay the internationalization until you’re actually rendering out in a template. That would eliminate the need for a stateful constants service at all, and instead your model hook can look something like:

const days = [
  'weekdays.monday',
  'weekdays.tuesdays'
   ...
];

const states = [
  'states.open',
  'states.closed',
  ...
];

...

model() {
  // Here I'm assuming we'll start with the models you already have above
  let configuredDays = [{ id: 1, day: 0, state: 0, ... }];
  // Then we map the day and state numbers into translation names
  return configuredDays.map(c => ({
     day: days[c.day],
     state: states[c.state],
     opens: c.opens,
     closes: c.closes
  });
}

Then we do the translation in the template, where it’s easier:

{{#each model as |weekday|}}
   The day: {{t weekday.day}}
    The state: {{t weekday.state}}
{{/each}}

Even if you also need the translated names in Javascript, you can still delay until the point of use. Controller and Components can both inject the i18n service directly, and that would still avoid the complexity of trying to correlate things on a separate constants service.


#5

@ef4: Thank you for the response. I don’t think the problem is in translations. What I have is 2 arrays of week days: 1 -> with all the 7 days (defined in constants service 2 -> with the days (1 to 7) the User already defined open/close times.

I need always display all the 7 days but set/or not their state (open/close/divided) and times depending on if this day exists among the saved by the User days.


#6

What is strange and I can’t understand is why only the first element of the model is undefined and all the 6 others are Ember.Object:

# helpers/weekday-state.js
export function weekdayState([savedDays, weekdayId]) {
  let found = savedDays.findBy('id', weekdayId);
  console.log('found:' + found);
}

# templates/working-hours.hbs

{{#each weekdays as |weekday|}}
        {{weekday-state model weekday.id}}
...

# console
found:undefined
found:<Ember.Object:ember691>
found:<Ember.Object:ember699>
...

#7

Oh, my bad! I figured out what the reason was. Rails indexes the week days from 0 to 6 (starting from Monday). In the model hook I defined fake API days as follows:

# routes/working-hours.js

model() {
    let monday = EmberObject.create({
      id: 1,
      day: 0,
      state: 0,
      opens: this._fromMillisToHuman(32400000), //9:00 am
      closes: this._fromMillisToHuman(72000000) //8:00 pm
    });
let tuesday = EmberObject.create({
      id: 2,
      day: 1,
      state: 0,
      opens: this._fromMillisToHuman(32400000), //9:00 am
      closes: this._fromMillisToHuman(72000000) //8:00 pm
    });
... //etc. all others saved by User days

In the above data, the id is just a primary key of WorkingDayJSON coming from the backend. The day attribute correponds to the day index defined by Rails (0 to 6). So I had to search by day instead of id in the helper:

# helpers/weekday-state.js

export function weekdayState([savedDays, weekdayId]) {
  let found = savedDays.findBy('day', weekdayId);
  console.log('found:' + found.get('state'));
}

Because in constants.js I defined days constants so that their IDs match the day value coming from the backend. That’s it. Oufff …


#8

Not relevant to the question (I’m glad you figured it out), but this thread should have the “Questions” tag so that it appears in that category.


#9

Fixed, thank you Balint !