lundi 18 septembre 2017

Ember sort property without computed

I've been struggling for a few days with a simple sorted search feature that a want to perform on a guest list for a party app I'm working on.

Searching on internet I came across computed.sort('model', 'sortProps') solution. At the guest list page I have a button that toggles the sort as asc and desc alongside with a search field. The problem is that once I set and attribute with computed it becomes read-only and therefore cannot be set again with the new search key being typed.

As computed was the only way of sorting that I could find, I approached the situation by creating one separated computed list for each filter scenario.

My question is. How can I perform filter and sorting on the same property? What is the best approach? For now I decided to remove the sort and computed. I'm using just the store.filter() which allowed me to reduce a lot code and work-around.

These are the models. Then I have my old/ugly/not-satisfying component approach and finally the new one.

1 event.js

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  eventDate: DS.attr('date'),
  details: DS.attr('string'),
  guestList: DS.hasMany('guest-list'),
});

2 guest.js

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  phone: DS.attr('string'),
  email: DS.attr('string'),
  details: DS.attr('string'),
  guestList: DS.hasMany('guest-list'),
});

3 guest-list.js

import DS from 'ember-data';

export default DS.Model.extend({
  info: DS.attr('string'),
  paid: DS.attr('boolean', { defaultValue: false }),
  arrived: DS.attr('boolean', { defaultValue: false }),
  vip: DS.attr('boolean', { defaultValue: false }),
  guest: DS.belongsTo('guest'),
  event: DS.belongsTo('event'),
  timestamp: DS.attr('number'),
});

My old component (ew)

import Ember from 'ember';
import FuzzySearch from 'npm:fuzzy-search';

const { computed } = Ember;

export default Ember.Component.extend({
  sortProps: [`name:asc`],
  currentSort: 'asc',
  display: 'all',
  list: null,

  searchList: null,

  active: {
    all: true,
    paid: false,
    notPaid: false,
    vip: false,
    arrived: false
  },

  all: computed.sort('model', 'sortProps'),
  paid: computed.filterBy('all', 'paid', true),
  notPaid: computed.filterBy('all', 'paid', false),
  vip: computed.filterBy('all', 'vip', true),
  arrived: computed.filterBy('all', 'arrived', true),
  free: computed.filterBy('all', 'free', true),

  filtered: computed('searchList', function() {
    return this.get('searchList');
  }),

  sorted: computed.sort('filtered', 'sortProps'),

  init() {
    this._super(...arguments);
    this.setList();
  },

  setActive(current) {
    this.set('active', {
      all: current === 'all',
      paid: current === 'paid',
      notPaid: current === 'notPaid',
      vip: current === 'vip',
      arrived: current === 'arrived'
    });
  },

  setList({ all, paid, notPaid, vip, arrived, free, filtered, sorted } = {}) {
    this.set('list', {
      all: all || this.get('all'),
      paid: paid || this.get('paid'),
      notPaid: notPaid || this.get('notPaid'),
      vip: vip || this.get('veip'),
      arrived: arrived || this.get('arrived'),
      filtered: filtered || this.get('filtered'),
      sorted: sorted || this.get('sorted')
    });
  },

  actions: {
    sortBy(val) {
      const { sortProps, currentSort } = this;
      const first = sortProps.get('firstObject');
      sortProps.popObject(first);
      const suffix = currentSort === 'asc' ? 'desc' : 'asc';
      this.set('currentSort', suffix);
      sortProps.pushObject(`${val}:${suffix}`);

      this.setList({ sorted: this.get('sorted') });
      this.set('display', 'sorted');
    },

    filterList(val) {
      this.set('display', val);
      this.triggerAction({
        action: 'search',
        target: this
      });

      this.setActive(val);
    },

    search() {
      const key = this.get('search');
      const display = this.display;
      const list = this.get(display).map(item => item._internalModel.__data);

      if (!list.length) return;

      const searcher = new FuzzySearch(list, ['name', 'phone'], {
        caseSensitive: false
      });
      const result = searcher.search(key);
      // this.model.modelName => 'guest' || 'guestmp'
      const filtered = this.model.store.filter(this.model.modelName, guest =>
        result.includes(guest._internalModel.__data)
      );

      this.set('searchList', filtered);

      this.setList({ [display]: this.get('filtered') });
    }
  }
});

My new component (looks a lot nicer but I can't get sorting to work on it)

import Ember from 'ember';
import FuzzySearch from 'npm:fuzzy-search';

const { computed } = Ember;

export default Ember.Component.extend({
  display: 'all',
  active: {
    all: true,
    paid: false,
    notPaid: false,
    vip: false,
    arrived: false
  },

  filteredList: [],
  isFiltered: false,

  setActive(current) {
    this.set('active', {
      all: current === 'all',
      paid: current === 'paid',
      notPaid: current === 'notPaid',
      vip: current === 'vip',
      arrived: current === 'arrived'
    });
  },

  getActive() {
    const active = this.get('active');
    return Object.keys(active).find(key => active[key] === true);
  },

  actions: {
    filterList(val) {
      this.setActive(val);
      this.triggerAction({
        action: 'search',
        target: this
      });
    },

    search() {
      this.set('isFiltered', true);
      const key = this.get('search') ||'';
      const lowerKey = key != '' ? key.toLocaleLowerCase() : key;
      const active = this.getActive();
      const filteredList = this.model.store.filter("guest-list", function(guestListItem) {
        const lname = guestListItem.get("guest").get('name').toLocaleLowerCase();
        const phone = guestListItem.get("guest").get('phone') || '';
        switch (active) {
          case 'paid':
            return (lname.includes(lowerKey) || phone.includes(lowerKey)) && guestListItem.get('paid');
          case 'notPaid':
            return (lname.includes(lowerKey) || phone.includes(lowerKey)) && !guestListItem.get('paid');
          case 'vip':
            return (lname.includes(lowerKey) || phone.includes(lowerKey)) && guestListItem.get('vip');
          case 'arrived':
            return (lname.includes(lowerKey) || phone.includes(lowerKey)) && guestListItem.get('arrived');
          default:
            return lname.includes(lowerKey) || phone.includes(lowerKey);
        }
      });
      this.set('filteredList', filteredList);
    }
  }
});




Aucun commentaire:

Enregistrer un commentaire