MarkUs Blog

MarkUs Developers Blog About Their Project

Custom Sort Functions for React Tables

with one comment

Recently, we discovered that some columns in the new React.js Tables were not sorting the rows as expected. In particular, the GROUP NAME and REPOSITORY columns in the Submissions and Summaries views exhibited consistent but very strange behaviour, as shown in the figures below.

sort_by_group_name

sort_by_repo_name

(The COMMIT DATE column was also sorting incorrectly, but this issue was less mysterious in that it was obviously performing string comparison on the dates. In order to correctly parse the date strings into a numerically comparable value, a customized sorting method was definitely required.) I was assigned the task of refactoring the React Table class (and related classes) in order to allow customized sorting methods for columns that required special treatment.

As it was, sorting was handled by sort_by_column(). The cell values we converted to lowercase and used the dangerouslySetInnerHTML.__html property for React Components. Values of types other than string were not interpreted.

function sort_by_column(data, column, direction) {
  function makeSortable(a)
  {
    if (typeof a == 'string') {
      return a.toLowerCase().replace(' ', '');
    } else if (a.hasOwnProperty('props')) {
      // Is a react component, get innerHTML
      return a.props.dangerouslySetInnerHTML.__html.toLowerCase();
    } else {
      return a;
    }
  }
  // sorts column id
  var r = data.sort(function(a, b) {
    if (makeSortable(a[column]) > makeSortable(b[column])) {
      return 1;
    } else if (makeSortable(b[column]) > makeSortable(a[column])) {
      return -1;
    }
    return 0;
  });
  // flips order if direction descending.
  if (direction == 'desc') r.reverse();

  return r;
}

 

table.js ]

In some table columns, we use a React <span> components with dangerouslySetInnerHTML.__html set in order to properly render html contents such as links and icons in a cell. (Though not shown above, the cells in the COMMIT DATE column will contain an icon if the submission commit date is past due.)

Of course, when we attempt to sort by dangerouslySetInnerHTML.__html, we are comparing strings of html code rather than the text displayed in the table cells. Naturally, the unexpected behaviours exhibited by the GROUP_NAME and REPOSITORY columns were the result of string comparison between the hrefs for the anchor elements, rather than the hyperlink text. Since both links differed by group number, we were effectively performing an alphanumeric sort on the unformatted group numbers.

For example, group_0007 would appear below group_0011 on an ascending sort because we were comparing “11” and “7” rather than the repository names. In the first figure, the group names did not appear to follow any kind of logical order because the display text is completely unrelated to group number.

My solution was to leave the existing function makeComparable() intact, for  primary processing of sortable data, while optionally allowing a custom compare function to be passed in as needed for any particular column.

function sort_by_column(data, column, direction, compare) {
  //...
  // determine sort behaviour
  compare = compare || compare_values;

  // sort row by column id
  var sorted = data.sort(function(a, b) {
    return compare(makeComparable(a[column]), makeComparable(b[column]));
  });

  // flip order if direction descending
  if (direction == 'desc') {
      sorted.reverse();
  }

  return sorted;
}

table.js ]

The compare function to use when the table rows are rendered is stored in the Table state variable sort_compare. When the selected sort column changes sort_compare is updated to the compare member of the appropriate column.

// Header col was clicked. Adjust state accordingly.
synchronizeHeaderColumn: function(sort_column, sort_direction) {
  var compare_func = this.props.columns.filter(function(col) {
      return col.id == sort_column;
  })[0].compare;
  this.setState({
    sort_column: sort_column,
    sort_direction: sort_direction,
    sort_compare: compare_func
  });
}

table.js ]

Since we have the default comparison function compare_values, we are able to leave compare undefined for any given column.

function compare_values(a, b) {
  if (!b || a > b) {
    return 1;
  } else if (!a || a < b) {
    return -1;
  }
  return 0;
}

table_sorts.js ]

Now we define functions such as compare_anchor_text() to handle special cases, and assign the custom sort functions to column objects. In the code below, notice that the SECTION column does not define a compare member since the data is just a string and so the default compare_values will suffice.

function compare_anchor_text(a, b) {
  function parse_anchor(a) {
    var open_tag_end = a.indexOf('>', a.indexOf('<a'));
    var close_tag_start = a.indexOf('</a', open_tag_end + 1);
    if (open_tag_end !== -1 && close_tag_start !== -1) {
      return a.substring(open_tag_end + 1, close_tag_start);
    }
    return a;
  }

  return compare_values(parse_anchor(a), parse_anchor(b));
}

table_sorts.js ]

var SubmissionsTable = React.createClass({
  getDefaultProps: function() {
    // Defines the columns used for the table and whether they
    // are sortable searchable. The default initially sorted
    // column is the first sortable column in the array.
    return {
      columns: [
        {
          id: 'group_name',
          content: '<%= j raw I18n.t(:'browse_submissions.group_name') %>',
          sortable: true,
          compare: compare_anchor_text,
          searchable: true
        },
        {
          id: 'repository',
          content: '<%= j raw I18n.t(:'browse_submissions.repository') %>',
          sortable: true,
          compare: compare_anchor_text,
          searchable: true
        },
        {
          id: 'section',
          content: '<%= j raw I18n.t(:'browse_submissions.section') %>',
          sortable: true,
          searchable: true
        },
//...

_submissions_table.js.jsx.erb ]

Written by Victoria Verlysdonk

November 25th, 2014 at 10:51 pm

Posted in Uncategorized

One Response to 'Custom Sort Functions for React Tables'

Subscribe to comments with RSS or TrackBack to 'Custom Sort Functions for React Tables'.

  1. Hello I currently have the same task at my job sorting a table with react. We are using flux for our data & was wondering if you could take a look at my code and point me in the right direction.

    Thank You.

    Josuel Garcia

    26 Aug 15 at 1:09 pm

Leave a Reply