ember-table
ember-table copied to clipboard
is it possible to apply a compare function on a per-column basis?
Hey!
The situation is that I want to be able to sort a date column. It looks like I can do this using compareFunction but I then need to implement sorting for other types besides dates since it appears all columns will be sorted using this function. Ideally, I'd like to specify my own function for a specific column and then default back to ember-table for everything else.
I could also be doing something terribly wrong, so don't hesitate to call me out 👍
Cheers
I think that sounds reasonable. We should probably update the name of compareFunction to sortCompareFunction to make sure it’s clear what it does, we’ll make that breaking change on tbody too while we still can
I do this in my addon. If you'd like to know how, I can show you what I do. It's not great, but it works.
Ok, here it is... Chris can tell me how dumb this is (and if there are any memory leaks), but it works. ;)
In my template:
{{#table.head
sortFunction=emberTableSortFunction
columns=columns
sorts=sorts
onUpdateSorts=(action updateSorts)
enableResize=false
enableReorder=false
resizeMode='fluid'
as |header|}}
A couple of my columns have an object for cellValue so I define some properties on how to sort that column. The key needs to be the same as valuePath defined in columns later.
sortPropertiesMapping: { prescriberInfo: ['lastName', 'firstName'] },
Here is where I map my rows
get(this, 'patientService.getRxHistoryTask').perform(patientId).then(
(rxHistory) => {
set(this, 'rows', rxHistory.map((historyItem) => {
let mappedValue = get(historyItem, 'attributes');
set(mappedValue, 'rxDate', moment(get(mappedValue, 'rxDate'), 'YYYY-MM-DD'));
set(mappedValue, 'prescriberInfo', {
firstName: get(mappedValue, 'prescriberFirstName'),
lastName: get(mappedValue, 'prescriberLastName'),
id: get(mappedValue, 'prescriberId')
});
set(mappedValue, 'prescriberContactInfo', {
phone: this.getFormattedPhone(get(mappedValue, 'prescriberPhone')),
fax: this.getFormattedPhone(get(mappedValue, 'prescriberFax'))
});
return mappedValue;
}));
},
(reason) => {
get(this, 'trackingService').reportError(reason);
}
);
Here is an example of what data I get back from the back end fetch:
[
{
id: 1, attributes: {
rxDate: '2017-12-25',
prescriberId: '1',
prescriberFirstName: 'lt',
prescriberLastName: 'Uhura',
prescriberPhone: '(614) 123-4567',
prescriberFax: '(614) 555-4567'
}
},
{
id: 2, attributes: {
rxDate: '2018-01-01',
prescriberId: '2',
prescriberFirstName: 'Capt',
prescriberLastName: 'Kirk',
prescriberPhone: '(614) 911-4567',
prescriberFax: '(614) 411-4567'
}
}
]
My columns
columns: computed(function () {
return [{
name: 'Rx Date',
valuePath: 'rxDate',
isDateColumn: true,
cssClass: 'col-date'
}, {
name: 'Drug Name',
valuePath: 'name',
cssClass: 'col-name'
}, {
name: 'Quantity',
valuePath: 'quantity',
cssClass: 'col-quantity'
}, {
name: 'Days Supply',
valuePath: 'daysSupply',
cssClass: 'col-supply'
}, {
name: 'Prescriber',
valuePath: 'prescriberInfo',
isPrescriberInfo: true,
sortProperties: ['lastName', 'firstName'],
cssClass: 'col-prescriber'
}, {
name: 'Prescriber Contact Info',
valuePath: 'prescriberContactInfo',
isPrescriberContact: true,
cssClass: 'col-contact'
}
];
}),
updateSorts (sorts) {
let sortPropertiesMapping = get(this, 'sortPropertiesMapping');
for (let i = 0; i < sorts.length; i++) {
let sortObj = sorts[i];
let customMapping = get(sortPropertiesMapping, get(sortObj, 'valuePath'));
set(sorts[i], 'sortProperty', isPresent(customMapping) ? customMapping : get(sortObj, 'valuePath'));
}
set(this, 'sorts', sorts);
},
Here is my sort function. It knows how to sort objects, instances of moment, and then just the regular compare. The sortPropertiesMapping allows me to set multiple properties per column to sort on in case there are more than one lastName that are the same (i.e., "Smith").
emberTableSortFunction (itemA, itemB, sorts, compare) {
let compareValue;
for (let {valuePath, isAscending, sortProperty} of sorts) {
let valueA = get(itemA, valuePath);
let valueB = get(itemB, valuePath);
if (typeof valueA === 'object' && typeof valueB === 'object') {
if (!isArray(sortProperty)) {
sortProperty = emberArray([sortProperty]);
}
sortProperty.some((thisSortProperty) => {
let customPropAValue;
let customPropBValue;
if (moment.isMoment(valueA) && moment.isMoment(valueB)) {
compareValue = (valueA > valueB) ? 1 : ((valueA < valueB) ? -1 : 0);
compareValue = isAscending ? compareValue : -compareValue;
} else {
customPropAValue = get(valueA, thisSortProperty);
customPropBValue = get(valueB, thisSortProperty);
compareValue = isAscending ?
compare(customPropAValue, customPropBValue) :
-compare(customPropAValue, customPropBValue);
}
return compareValue !== 0;
});
} else {
compareValue = isAscending ? compare(valueA, valueB) : -compare(valueA, valueB);
}
if (compareValue !== 0) {
break;
}
}
return compareValue;
},
Here's my row for reference:
{{#row.cell class=row.columnValue.cssClass as |cellValue columnValue|}}
{{#if columnValue.isDateColumn}}
{{moment-format cellValue 'MM/DD/YYYY'}}
{{else if columnValue.isPrescriberInfo}}
<div data-test-id="prescriber">
{{cellValue.lastName}}, {{cellValue.firstName}}
</div>
<div class="id">
ID #{{cellValue.id}}
</div>
{{else if columnValue.isPrescriberContact}}
{{#if cellValue.phone}}
<div class="phone">ph. {{cellValue.phone}}</div>
{{/if}}
{{#if cellValue.fax}}
<div class="fax">fx. {{cellValue.fax}}</div>
{{/if}}
{{else}}
{{cellValue}}
{{/if}}
{{/row.cell}}
@cah-danmonroe so helpful! Going to give this a shot tomorrow! Thanks man 👍