The right way to save multiple lines (records)


#1

As I’m still in the very beginning of Ember learning curve, I wonder what is the best and correct way to implement the following functionality.

  • I have to display a shop working hours like in the attached screenshot.
  • as you can see some days have their hours entered, some do not. I have to display all the 7 week days, no matter if the hours were entered or not, - set the state of the day as Closed and disable its corresponding hours fields in this case.
  • how to save the entered hours:
    • by every modified/created day ? (by adding focus-out to every opens/closes hours fields, for ex.?)
    • all the days via the Save button
    • saving all the days, no matter if they have hours entered (anyway, every shop will have 7 days with one or another state, - closed, open, divided (i.e. with a break) ?
    • how can all the hours values be caught if they don’t have IDs in case of new records ?

Here is how I defined a template:

# templates/working-hours.hbs
...
<form {{action "saveHours" on="submit"}}>
      {{#each weekdays as |weekday|}}
        {{weekday-row weekday=weekday modelDays=model}}
      {{/each}}

      <div class="float-right">
        <button type="submit" class="btn btn-success">{{t 'buttons.save'}}</button>
      </div>
    </form>
...

Here is the component passed in to the above for loop:

#templates/components/weekday-row.hbs

<div class="form-group row">
  <label for="state" class="col-sm-2 col-form-label">{{dayRow.name}}</label>
  <div class="col-sm-2">
    <select class="form-control" onchange={{action "selectState" value="target.value"}}>
      {{#each states as |state|}}
        <option value={{state.id}} selected={{eq state.id dayRow.state}}>{{state.name}}</option>
      {{/each}}
    </select>
  </div>
  <div class="col-sm-2">
    {{input type="time"
      class="form-control"
      min="06:00"
      max="22:00"
      disabled=isClosed
      pattern="[0-9]{2}:[0-9]{2}"
      focus-out="alertMessage"
      value=dayRow.opens
    }}
  </div>
  <label for="closes" class="col-form-label">{{t 'working.hours.labels.to'}}</label>
  <div class="col-sm-2">
    {{input type="time"
      class="form-control"
      min="06:00"
      max="22:00"
      disabled=isClosed
      pattern="[0-9]{2}:[0-9]{2}"
      focus-out="alertMessage"
      value=dayRow.closes
    }}
  </div>
</div>
{{#if isDivided}}
  <div class="form-group row">
    <div class="col-sm-2 offset-sm-2">{{t 'working.hours.labels.and'}}</div>
    <div class="col-sm-2">
      {{input type="time"
        class="form-control"
        min="06:00"
        max="22:00"
        disabled=isClosed
        pattern="[0-9]{2}:[0-9]{2}"
        value=dayRow.opens
      }}
    </div>
    <label for="closes"class="col-form-label">{{t 'working.hours.labels.to'}}</label>
    <div class="col-sm-2">
      {{input type="time"
        class="form-control"
        min="06:00"
        max="22:00"
        disabled=isClosed
        pattern="[0-9]{2}:[0-9]{2}"
        value=dayRow.closes
      }}
    </div>
  </div>
{{/if}}

And finally, the component itself:

# components/weekday-row.js

import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import EmberObject from '@ember/object';


export default Component.extend({
  constants: service(),
  weekday: null,
  state: null,
  modelDays: [],
  weekdays: [],
  states: [],
  tagName: '',
  dayRow: null,

  init() {
    this._super(...arguments);
    this.states = this.get('constants.states');
    this.dayRow = this.buildDayRow();
  },

  buildDayRow() {
    let dayRow = EmberObject.create({ name: this.get('weekday.name')});
    let foundDay = this.get('modelDays').findBy('day', this.get('weekday.day'));
    if (typeof foundDay != 'undefined') {
      dayRow.set('state', foundDay.get('state'));
      dayRow.set('opens', foundDay.get('opens'));
      dayRow.set('closes', foundDay.get('closes'));
    } else {
      let closedState = this.get('states')[1];
      dayRow.set('state', closedState.id);
    }

    return dayRow;
  },

  isClosed: computed('dayRow.state', function() {
    return this.get('dayRow').get('state') === 'closed';
  }),

  isDivided: computed('dayRow.state', function() {
    return this.get('dayRow').get('state') === 'divided';
  }),

  actions: {
    selectState(state) {
      this.get('dayRow').set('state', state);
      this.get('dayRow').set('opens', null);
      this.get('dayRow').set('closes', null);
    },

    alertMessage(value) {
      console.log('focus off for: ' + value);
    }
  }
});

The route handler:

# routes/working-hours.js

import Route from '@ember/routing/route';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
import { inject as service } from '@ember/service';

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

  setupController(controller, model) {
    this._super(controller, model);
    this.controller.set('weekdays', this.get('constants.days'));
  },

  model() {
    return this.store.query('working-hour', { shop_id: this.get('currentShop.shop').get('id')});
  },
...

Just in case here is how weekdays are defined in constants.js service:

# services/constants.js

import Service from '@ember/service';
import { inject as service } from '@ember/service';

export default Service.extend({
    i18n: service('i18n'),

    init() {
      this._super(...arguments);
      this.set('states',
        [
          {id: 'opened', name: this.get('i18n').t('states.open')},
          {id: 'closed', name: this.get('i18n').t('states.closed')},
          {id: 'divided', name: this.get('i18n').t('states.divided')}
        ]
      );
      this.set('days',
        [
          {day: 'monday', name: this.get('i18n').t('weekdays.monday')},
          {day: 'tuesday', name: this.get('i18n').t('weekdays.tuesday')},
          {day: 'wednesday', name: this.get('i18n').t('weekdays.wednesday')},
          {day: 'thursday', name: this.get('i18n').t('weekdays.thursday')},
          {day: 'friday', name: this.get('i18n').t('weekdays.friday')},
          {day: 'saturday', name: this.get('i18n').t('weekdays.saturday')},
          {day: 'sunday', name: this.get('i18n').t('weekdays.sunday')}
        ]
      );
    }

});

Thank you!!


#2

Finally, the solution that I found is to use currentModel in the route action as follows:

# routes/working-hours.js

actions: {
    saveHours() {
      let currentShop = this.get('currentShop.shop');
      let workingHours = this.get('currentModel');

      let workingHoursToUpdate = workingHours.filterBy('hasDirtyAttributes');
      workingHoursToUpdate.forEach(function(workingHour) {
        workingHour.set('shop', currentShop);
        workingHour.save();
      });
    }
  }

currentModel- is an array of working-hour model I get in model hook and display in the template. Filtering with workingHours.filterBy('hasDirtyAttributes'); makes it possible to update only modified working hours and not all the 7.