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);
}
}
});