Vanilla JSによる軽量tableソート

Webページのテーブルにソート機能が欲しいというのはよくある話で、JQueryあたりのライブラリを探して組み込むのが一般的だと思う。
ただ、組み込み先のページが10年以上前のprototype.jsを駆使した画面でJQueryと折り合いを付けるのが大変だったり、バンドルされたCSSの調整が面倒、といった問題が付きまとう。
そこで高速、かつ軽量で有名なフレームワーク[Vanilla js](http://vanilla-js.com/)での実装を試みた。

#仕様

 - `<table>`が`<thead>`と`<tbody>`で構成されていることが条件
 - ページ内全ての上記条件に当てはまる`<table>`をソート対象にする
 - ヘッダー行のカラム`table > thead > th`をクリックすると対象列の昇順でソートする
 - もう一回クリックすると降順でソートし、昇順、降順をクリックする度に切り替える
 - 数値変換可能文字列は数値として評価、その他は文字コードで評価しソートする

#コード

```html
<html>
<head>
  <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
  <title></title>
</head>
<body>
  <table>
    <thead>
      <th>key</th>
      <th>value</th>
    </thead>
    <tbody>
      <tr>
        <td>5</td>
        <td>aaa</td>
      </tr>
      <tr>
        <td>1</td>
        <td>bbb</td>
      </tr>
      <tr>
        <td>4</td>
        <td>ccc</td>
      </tr>
      <tr>
        <td>2</td>
        <td>ddd</td>
      </tr>
      <tr>
        <td>3</td>
        <td>eee</td>
      </tr>
    </tbody>
  </table>
<script type="text/javascript">
```

```javascript
(function () {
  function sort(tbody, compareFunction) {
    var rows = tbody.children;
    if(!rows || !rows[0] || rows.length == 1) return;
    var size = rows.length;
    var arr = [];
    for(var i = 0; i < size; i++) arr.push(rows[i]);
    arr.sort(compareFunction);
    for(var i = size - 1; i > 0; i--) tbody.insertBefore(arr[i-1], arr[i]);
  }
  function numConvert(s) {
    return s == Number(s) ? Number(s) : s; 
  }
  function asc(idx) {
    return function(a, b) {
      var a_ = numConvert(a.children[idx].innerText);
      var b_ = numConvert(b.children[idx].innerText);
      return a_ > b_ ? 1 : -1;
    };
  }
  function desc(idx) {
    return function(a, b) {
      var a_ = numConvert(a.children[idx].innerText);
      var b_ = numConvert(b.children[idx].innerText);
      return a_ < b_ ? 1 : -1;
    };
  }
  function sortEvent(tbody, idx) {
    var mode = true;
    return function(e) {
      if(mode) sort(tbody,  asc(idx));
      else     sort(tbody, desc(idx));
      mode = !mode;
    };
  }
  var ts = document.getElementsByTagName('table');
  for(var i = ts.length; i--; ) {
    var ths = ts[i].tHead.getElementsByTagName('th');
    for(var j = ths.length; j--; )
      ths[j].addEventListener("click", sortEvent(ts[i].tBodies[0], j));
  }
})();
``` 

```html
</script>
</body>
</html>
```

#動作確認

せっかくなのでQiitaページのテーブルをソートしてみた
やり方は[ギャル文字変換の記事](http://qiita.com/bakenezumi/items/5b8dbfabd57c0256ff07)を参照

ソート前
![image](https://qiita-image-store.s3.amazonaws.com/0/35910/34c2e85e-a688-b90d-8651-4b5fe381d5e6.png)

ソート後(key2)
![image](https://qiita-image-store.s3.amazonaws.com/0/35910/ee53b7aa-c9cd-a394-a9a9-d69e3e17b601.png)

OK!

#所感
ブラウザ互換の問題がなくなりつつある今、Vanillaの選択肢はアリな気がしてきた

#おまけ ES6版

書いてみたけどIEで動かないからお蔵入り
個人的にはブロックスコープとconst、letがかなり好き
アローはなくてもいいかな

```js
{
  const
    sort = (tbody, compareFunction) => {
      const rows = tbody.children;
      if(!rows || !rows[0] || rows.length == 1) return;
      const size = rows.length;
      const arr = [];
      for(let row of rows) arr.push(row);
      arr.sort(compareFunction);
      for(let i = size - 1; i > 0; i--) tbody.insertBefore(arr[i-1], arr[i]);
    },
    numConvert = (s) => (s == Number(s)) ? Number(s) : s, 
    getValue = (tr, idx) => tr.children[idx].innerText,
    asc  = (idx) => (a, b) => (numConvert(getValue(a, idx)) > numConvert(getValue(b, idx))) ? 1 : -1,
    desc = (idx) => (a, b) => (numConvert(getValue(a, idx)) < numConvert(getValue(b, idx))) ? 1 : -1,
    sortEvent = (tbody, idx) => {
      let mode = true; 
      return (e) => {
        mode ? sort(tbody, asc(idx)) : sort(tbody, desc(idx));
        mode = !mode;
      }
    }
  for(let table of document.getElementsByTagName('table')) {
    const ths = table.tHead.getElementsByTagName('th');
    for(let j = ths.length; j--; )
      ths[j].addEventListener("click", sortEvent(table.tBodies[0], j));
  }
}
```