|  | 
|  | 1 | +/* | 
|  | 2 | + *   This content is licensed according to the W3C Software License at | 
|  | 3 | + *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document | 
|  | 4 | + * | 
|  | 5 | + *   File:   sortable-table.js | 
|  | 6 | + * | 
|  | 7 | + *   Desc:   Adds sorting to a HTML data table that implements ARIA Authoring Practices | 
|  | 8 | + */ | 
|  | 9 | + | 
|  | 10 | +'use strict'; | 
|  | 11 | + | 
|  | 12 | +class SortableTable { | 
|  | 13 | +  constructor(tableNode) { | 
|  | 14 | +    this.tableNode = tableNode; | 
|  | 15 | + | 
|  | 16 | +    this.columnHeaders = tableNode.querySelectorAll('thead th'); | 
|  | 17 | + | 
|  | 18 | +    this.sortColumns = []; | 
|  | 19 | + | 
|  | 20 | +    for (var i = 0; i < this.columnHeaders.length; i++) { | 
|  | 21 | +      var ch = this.columnHeaders[i]; | 
|  | 22 | +      var buttonNode = ch.querySelector('button'); | 
|  | 23 | +      if (buttonNode) { | 
|  | 24 | +        this.sortColumns.push(i); | 
|  | 25 | +        buttonNode.setAttribute('data-column-index', i); | 
|  | 26 | +        buttonNode.addEventListener('click', this.handleClick.bind(this)); | 
|  | 27 | +      } | 
|  | 28 | +    } | 
|  | 29 | + | 
|  | 30 | +    this.optionCheckbox = document.querySelector( | 
|  | 31 | +      'input[type="checkbox"][value="show-unsorted-icon"]' | 
|  | 32 | +    ); | 
|  | 33 | + | 
|  | 34 | +    if (this.optionCheckbox) { | 
|  | 35 | +      this.optionCheckbox.addEventListener( | 
|  | 36 | +        'change', | 
|  | 37 | +        this.handleOptionChange.bind(this) | 
|  | 38 | +      ); | 
|  | 39 | +      if (this.optionCheckbox.checked) { | 
|  | 40 | +        this.tableNode.classList.add('show-unsorted-icon'); | 
|  | 41 | +      } | 
|  | 42 | +    } | 
|  | 43 | +  } | 
|  | 44 | + | 
|  | 45 | +  setColumnHeaderSort(columnIndex) { | 
|  | 46 | +    if (typeof columnIndex === 'string') { | 
|  | 47 | +      columnIndex = parseInt(columnIndex); | 
|  | 48 | +    } | 
|  | 49 | + | 
|  | 50 | +    for (var i = 0; i < this.columnHeaders.length; i++) { | 
|  | 51 | +      var ch = this.columnHeaders[i]; | 
|  | 52 | +      var buttonNode = ch.querySelector('button'); | 
|  | 53 | +      if (i === columnIndex) { | 
|  | 54 | +        var value = ch.getAttribute('aria-sort'); | 
|  | 55 | +        if (value === 'descending') { | 
|  | 56 | +          ch.setAttribute('aria-sort', 'ascending'); | 
|  | 57 | +          this.sortColumn( | 
|  | 58 | +            columnIndex, | 
|  | 59 | +            'ascending', | 
|  | 60 | +            ch.classList.contains('num') | 
|  | 61 | +          ); | 
|  | 62 | +        } else { | 
|  | 63 | +          ch.setAttribute('aria-sort', 'descending'); | 
|  | 64 | +          this.sortColumn( | 
|  | 65 | +            columnIndex, | 
|  | 66 | +            'descending', | 
|  | 67 | +            ch.classList.contains('num') | 
|  | 68 | +          ); | 
|  | 69 | +        } | 
|  | 70 | +      } else { | 
|  | 71 | +        if (ch.hasAttribute('aria-sort') && buttonNode) { | 
|  | 72 | +          ch.removeAttribute('aria-sort'); | 
|  | 73 | +        } | 
|  | 74 | +      } | 
|  | 75 | +    } | 
|  | 76 | +  } | 
|  | 77 | + | 
|  | 78 | +  sortColumn(columnIndex, sortValue, isNumber) { | 
|  | 79 | +    function compareValues(a, b) { | 
|  | 80 | +      if (sortValue === 'ascending') { | 
|  | 81 | +        if (a.value === b.value) { | 
|  | 82 | +          return 0; | 
|  | 83 | +        } else { | 
|  | 84 | +          if (isNumber) { | 
|  | 85 | +            return a.value - b.value; | 
|  | 86 | +          } else { | 
|  | 87 | +            return a.value < b.value ? -1 : 1; | 
|  | 88 | +          } | 
|  | 89 | +        } | 
|  | 90 | +      } else { | 
|  | 91 | +        if (a.value === b.value) { | 
|  | 92 | +          return 0; | 
|  | 93 | +        } else { | 
|  | 94 | +          if (isNumber) { | 
|  | 95 | +            return b.value - a.value; | 
|  | 96 | +          } else { | 
|  | 97 | +            return a.value > b.value ? -1 : 1; | 
|  | 98 | +          } | 
|  | 99 | +        } | 
|  | 100 | +      } | 
|  | 101 | +    } | 
|  | 102 | + | 
|  | 103 | +    if (typeof isNumber !== 'boolean') { | 
|  | 104 | +      isNumber = false; | 
|  | 105 | +    } | 
|  | 106 | + | 
|  | 107 | +    var tbodyNode = this.tableNode.querySelector('tbody'); | 
|  | 108 | +    var rowNodes = []; | 
|  | 109 | +    var dataCells = []; | 
|  | 110 | + | 
|  | 111 | +    var rowNode = tbodyNode.firstElementChild; | 
|  | 112 | + | 
|  | 113 | +    var index = 0; | 
|  | 114 | +    while (rowNode) { | 
|  | 115 | +      rowNodes.push(rowNode); | 
|  | 116 | +      var rowCells = rowNode.querySelectorAll('th, td'); | 
|  | 117 | +      var dataCell = rowCells[columnIndex]; | 
|  | 118 | + | 
|  | 119 | +      var data = {}; | 
|  | 120 | +      data.index = index; | 
|  | 121 | +      data.value = dataCell.textContent.toLowerCase().trim(); | 
|  | 122 | +      if (isNumber) { | 
|  | 123 | +        data.value = parseFloat(data.value); | 
|  | 124 | +      } | 
|  | 125 | +      dataCells.push(data); | 
|  | 126 | +      rowNode = rowNode.nextElementSibling; | 
|  | 127 | +      index += 1; | 
|  | 128 | +    } | 
|  | 129 | + | 
|  | 130 | +    dataCells.sort(compareValues); | 
|  | 131 | + | 
|  | 132 | +    // remove rows | 
|  | 133 | +    while (tbodyNode.firstChild) { | 
|  | 134 | +      tbodyNode.removeChild(tbodyNode.lastChild); | 
|  | 135 | +    } | 
|  | 136 | + | 
|  | 137 | +    // add sorted rows | 
|  | 138 | +    for (var i = 0; i < dataCells.length; i += 1) { | 
|  | 139 | +      tbodyNode.appendChild(rowNodes[dataCells[i].index]); | 
|  | 140 | +    } | 
|  | 141 | +  } | 
|  | 142 | + | 
|  | 143 | +  /* EVENT HANDLERS */ | 
|  | 144 | + | 
|  | 145 | +  handleClick(event) { | 
|  | 146 | +    var tgt = event.currentTarget; | 
|  | 147 | +    this.setColumnHeaderSort(tgt.getAttribute('data-column-index')); | 
|  | 148 | +  } | 
|  | 149 | + | 
|  | 150 | +  handleOptionChange(event) { | 
|  | 151 | +    var tgt = event.currentTarget; | 
|  | 152 | + | 
|  | 153 | +    if (tgt.checked) { | 
|  | 154 | +      this.tableNode.classList.add('show-unsorted-icon'); | 
|  | 155 | +    } else { | 
|  | 156 | +      this.tableNode.classList.remove('show-unsorted-icon'); | 
|  | 157 | +    } | 
|  | 158 | +  } | 
|  | 159 | +} | 
|  | 160 | + | 
|  | 161 | +// Initialize sortable table buttons | 
|  | 162 | +window.addEventListener('load', function () { | 
|  | 163 | +  var sortableTables = document.querySelectorAll('table.sortable'); | 
|  | 164 | +  for (var i = 0; i < sortableTables.length; i++) { | 
|  | 165 | +    new SortableTable(sortableTables[i]); | 
|  | 166 | +  } | 
|  | 167 | +}); | 
|  | 168 | + | 
0 commit comments