Source: infra-sk/modules/sort-sk/sort-sk.js

/**
 * @module module/sort-sk
 * @description <h2><code>sort-sk</code></h2>
 *
 * Allows sorting the members of the indicated element by the values of the
 * data attributes.
 *
 * Add children to <sort-sk> that generate click events and that have child
 * content, such as buttons. Add a data-key * attribute to each child element
 * that indicates which data-* attribute the children should be sorted on.
 *
 * Note that all sorting is done numerically, unless the
 * 'data-sort-type=alpha' attribute is set on the element generating the
 * click, in which case the sorting is done alphabetically.
 *
 * Additionally a single child element can have a data-default attribute with
 * a value of 'up' or 'down' to indicate the default sorting that already
 * exists in the data.
 *
 *
 * @example An example usage, that will present two buttons to sort the contents of
 *   div#stuffToBeSorted.
 *
 *    <sort-sk target=stuffToBeSorted>
 *      <button data-key=clustersize data-default=down>Cluster Size </button>
 *      <button data-key=stepsize data-sort-type=alpha>Name</button>
 *    </sort-sk>
 *
 *    <div id=stuffToBeSorted>
 *      <div data-clustersize=10 data-name=foo></div>
 *      <div data-clustersize=50 data-name=bar></div>
 *      ...
 *    </div>
 *
 * @attr target - The id of the container element whose children are to be sorted.
 *
 */
import { html, render } from 'lit-html'
import { ElementSk } from '../../../infra-sk/modules/ElementSk'
import { $, $$ } from 'common-sk/modules/dom'
import 'elements-sk/icon/arrow-drop-down-icon-sk'
import 'elements-sk/icon/arrow-drop-up-icon-sk'

// The states to move each button through on a click.
const toggle = {
  '': 'down',
  'down': 'up',
  'up': 'down',
};

// Functions to pass to sort().
const f_alpha_up = (x, y) => {
  if (x.value === y.value) {
    return 0;
  }
  return  x.value > y.value ? 1 : -1;
};
const f_alpha_down = (x, y) => f_alpha_up(y, x);
const f_num_up = (x, y) => (x.value - y.value);
const f_num_down = (x, y) => f_num_up(y, x);

window.customElements.define('sort-sk', class extends ElementSk {
  connectedCallback() {
    super.connectedCallback();
    $('[data-key]', this).forEach((ele) => {
      // Only attach the icons once.
      if (ele.querySelector('arrow-drop-down-icon-sk')) {
        return
      }
      ele.appendChild(document.createElement('arrow-drop-down-icon-sk'));
      ele.appendChild(document.createElement('arrow-drop-up-icon-sk'));
      ele.addEventListener('click', (e) => this._clickHandler(e));
    });

    // Handle a default value if one has been set.
    const def = $$('[data-default]', this);
    if (def) {
      this._setSortClass(def, def.dataset.default);
    }
  }

  _setSortClass(ele, value) {
    ele.setAttribute('data-sort-sk', value);
  }

  _clearSortClass(ele) {
    ele.removeAttribute('data-sort-sk');
  }

  _getSortClass(ele) {
    return ele.getAttribute('data-sort-sk') || '';
  }

  _clickHandler(e) {
    let ele = e.target;
    while (ele.parentNode !== this) {
      ele = ele.parentNode;
    }

    const dir = toggle[this._getSortClass(ele)];

    $('[data-key]', this).forEach((e) => {
      this._clearSortClass(e);
    });
    this._setSortClass(ele, dir);

    // Remember the direction we are sorting in.
    let up = dir === 'up';

    // Are we sorting alphabetically or numerically.
    let alpha = ele.dataset.sortType === 'alpha';

    // Sort the children of the element at #target.
    let sortBy = ele.dataset.key;
    let container = this.parentElement.querySelector(`#${this.getAttribute('target')}`);
    let arr = [];
    for (const ele of container.children) {
      let value = ele.dataset[sortBy];
      if (!alpha) {
        value = +value;
      }
      arr.push({
        value: value,
        node: ele
      });
    }

    // Pick the desired sort function.
    let f = f_alpha_up;
    if (alpha) {
      f = up ? f_alpha_up : f_alpha_down;
    } else {
      f = up ? f_num_up : f_num_down;
    }
    arr.sort(f);

    // Rearrange the elements in the sorted order.
    arr.forEach((e) => {
      container.appendChild(e.node);
    });
  }
});