HTMLの<table>内をソートできるjQueryプラグイン

jQueryプラグインとして作ってはみたものの、sortableという名前がいろいろとかぶっていることに気づいたので、github公開はよしてこっちに

#使い方

* `<table>` に sortable クラスを付与
* ソートしたい `<thead>` の `<th>` に sort_number または sort_number_attr クラスを付与
* `<th>` に sort_number_attr クラスを付与した列の `<td>` には、sort-value 属性でソート順の数値を指定
* ソートアルゴリズムはマージソートなので安定ソート
* ソートのキーにした列の `<td>` には sort_by_asc および sort_by_desc クラスが付与されるので、cssで色をつけたりできる

#コード

```js:jquery.sortable.js

;(function($) {
    $.fn.sortableInit = function(){
        var elements = this;

        var sortKeyClass = [
            'sort_number',
            'sort_number_attr'
        ];

        elements.each(function(){
            $(this).find('thead').find('tr:last').find('th').each(function(index){
                var self = this;
                // 対象クラスを持たない列にはソートボタンを表示しない
                if(!sortKeyClass.some(function(v){
                    return $(self).hasClass(v);
                })) return this;
                
                var func = null;
                var isAttr = null;
                // 数値型
                if ($(this).hasClass('sort_number')) {
                    func = mergeSortNumber;
                    isAttr = false;
                }
                // 属性数値型
                else if ($(this).hasClass('sort_number_attr')) {
                    func = mergeSortNumber;
                    isAttr = true;
                }
                
                var ascObj = $('<span>').html('▲').click(function(){
                    $(this).sortable(index, true, func, isAttr);
                }).css({"cursor":"pointer"});
                var descObj = $('<span>').html('▼').click(function(){
                    $(this).sortable(index, false, func, isAttr);
                }).css({"cursor":"pointer"});
                $(this).append($('<div>').addClass('sort_cursor').append(ascObj).append(descObj));
            });
        });

        return this;
    };
    
    $.fn.sortable = function(index, isAsc, func, isAttr){
        var element = this;
        
        var tbody = element.closest('table').find('tbody');
        // 色付けクラス削除
        tbody.find('td').removeClass('sort_by_asc sort_by_desc');
        var trs = [];
        tbody.find('tr').each(function(i){
            trs[i] = this;
        });
        trs = func(trs, index, isAsc, isAttr);
        tbody.empty().append(trs);
        // 色付けクラス追加
        tbody.find('tr').each(function(){
            $(this).find('td').eq(index).addClass(isAsc ? 'sort_by_asc' : 'sort_by_desc');
        });
        return this;
    };

    function mergeSortNumber(a, index, isAsc, isAttr) {
        var split = function(array) {
                if (array.length < 2) {
                    return array;
                }
                var a, b, mid = array.length / 2 | 0;
                a = split(array.slice(0, mid));
                b = split(array.slice(mid, array.length));

                return merge(array, a, b);
            },
            merge = function(array, a, b) {
                if (b === void 0) {
                    return array;
                }
                var c = [],
                    i, j;
                while (a.length > 0 && b.length > 0) {
                    if (isAttr) {
                        i = getValueAttr(a[0], index);
                        j = getValueAttr(b[0], index);
                    }
                    else {
                        i = getValueHtml(a[0], index);
                        j = getValueHtml(b[0], index);
                    }

                    if (isAsc) {
                        if (i <= j) {
                            c.push(a.shift());
                        } else {
                            c.push(b.shift());
                        }
                    } else {
                        if (i >= j) {
                            c.push(a.shift());
                        } else {
                            c.push(b.shift());
                        }
                    }
                }
                if (a.length === 0) {
                    c = c.concat(b);
                } else if (b.length === 0) {
                    c = c.concat(a);
                }
                return c;
            }
        return split(a);
    }
    
    function getValueHtml(tag, index) {
        return 1 * $(tag).find('td').eq(index).html();
    } 
    function getValueAttr(tag, index) {
        return 1 * $(tag).find('td').eq(index).attr('sort-value');
    } 
})(jQuery);

```

#使用例

##HTML

```html:html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="jquery.sortable.js"></script>
<script>
$().ready(function(){
    $('.sortable').sortableInit();
});
</script>
<style>
.sort_by_asc {
    background-color: #ff9999;
}
.sort_by_desc {
    background-color: #9999ff;
}
</style>
</head>
<body>

<center>

<table class="sortable" border="1">

<thead>
<tr>
    <th class="sort_number">アラビア数字</th>
    <th class="sort_number_attr">漢字</th>
    <th>content</th>
</tr>
</thead>

<tbody>
<tr>
    <td>1</td>
    <td sort-value="1">壱</td>
    <td>漢字も数字も１番</td>
</tr>
<tr>
    <td>2</td>
    <td sort-value="3">参</td>
    <td>漢字では３番</td>
</tr>
<tr>
    <td>3</td>
    <td sort-value="2">弐</td>
    <td>漢字では２番</td>
</tr>
</tbody>


</table>

</center>

</body>
</html>

```

##元の状態

![スクリーンショット 2015-07-15 13.01.30.png](https://qiita-image-store.s3.amazonaws.com/0/1642/5ec744f5-4b9e-620b-624c-6ec0da2ccca4.png)

##漢字の昇順を押した

![スクリーンショット 2015-07-15 13.14.45.png](https://qiita-image-store.s3.amazonaws.com/0/1642/b81accfa-da1c-6c7f-b42c-5f0f459ea842.png)


##アラビア数字の降順を押した

![スクリーンショット 2015-07-15 13.14.33.png](https://qiita-image-store.s3.amazonaws.com/0/1642/a3be5423-b4a2-998b-5af4-999057726861.png)