ember-table icon indicating copy to clipboard operation
ember-table copied to clipboard

is it possible to apply a compare function on a per-column basis?

Open jamesdixon opened this issue 7 years ago • 4 comments

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

jamesdixon avatar Aug 07 '18 23:08 jamesdixon

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

pzuraq avatar Aug 08 '18 01:08 pzuraq

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.

cah-danmonroe avatar Aug 08 '18 01:08 cah-danmonroe

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 avatar Aug 08 '18 02:08 cah-danmonroe

@cah-danmonroe so helpful! Going to give this a shot tomorrow! Thanks man 👍

jamesdixon avatar Aug 08 '18 05:08 jamesdixon