JavaScriptでTABLEにソート機能を実装する(jQuery不要)
jQueryもそのプラグインも、外部ライブラリも使わず、自前で書いたJavaScriptでTABLEタグをソートするプログラミング例の紹介と解説です。
配列のソート機能はJavaScript自体が持っているので、あとはDOM操作だけで案外簡単に実装できるんですよ。
なぜJavaScriptでソートするのか
TABLEタグ、便利ですよね。
HTMLのレイアウト組みにTABLEタグを使うんじゃない、と言われるようになって久しいですし、スマホ対応のためにレスポンシブデザインを導入する場合にもやっぱり嫌われがちなTABLEタグですが、業務系Webシステムや、データベース系/ショップ系のサイトでたくさんのデータを一覧表示したい場合にはTABLEタグが使いやすいです。
そして一覧表といえばソート。ソートといえばデータベース。そんな流れで、1ページあたり20件ずつ表示し、[前へ][次へ]などのボタンでページ切り替えをする、いわゆるページネーションを導入するのも一般的です。
だがしかし。これだけ通信環境が発達した環境で、1ページ20件ずつってなんだよ、少ねーよ、と思うことありませんか? じゃあ100件にするのか1000件にするのか。プルダウンで1ページあたりの件数をユーザーに選ばせるのか。様々なアプローチがあることでしょう。
サイトごとに様々な事情はあれど、1ページに全件表示しても構わないケースも間違いなく存在するはず。そんなとき、表を並び替えるためだけにサーバーサイドスクリプトを動かすのか? という話です。
利用者の少ないサイトならそれもアリかも知れませんが、うちが運営している月数十万PV程度のサイトでも一覧表をソートするたびにページリクエストとデータベースへのアクセスが発生するのはけっこうな負荷になってきています。ケチって格安なサーバーを使っていると高負荷でいつ追い出されるか…とても不安になるのですよ…。
そこで今回はクライアント側のJavaScriptで一覧ページのソート機能を実装しちゃおう、とサンプルプログラムを作ってみました。
どこかで簡単なコードが公開されていたらそれで済まそうと思ったのですが、軽く探した限りはjQueryでの実装例ばかりだったので、うーん、それなら自分で書いたほうがいいかな、と。
JavaScriptでTABLEをソートする
実行例
No | 全角項目 | 数値項目 | カンマ |
---|---|---|---|
1 | かきくけこ | 1 | 1,000 |
2 | さしすせそ | 10 | 10,000 |
3 | あいうえお | 100 | 2,000 |
4 | カキクケコ | 2001 | 3,000 |
5 | アイウエオ | 2002 | 20,000 |
6 | サシスセソ | 2003 | 11,000 |
7 | 一丁目 | 1.234 | 12,000 |
8 | 二丁目 | 12.34 | 30,000 |
9 | 三丁目 | 123.4 | 31,000 |
10 | 1丁目 | 0.001 | 32,000 |
11 | 2丁目 | 0.002 | 400,000 |
12 | 3丁目 | 0.003 | 50 |
※仕様説明
- 灰色のタイトル部分をクリックするとソートされる
- 同じ項目をもう一度クリックすると逆順にソートされる
- 数字しか入っていない列は数字として扱われる(小数点付きも数字として扱われる)
- 1行でも文字列が含まれている列は文字列としてソートされる
何も考えずに文字列ソートで実装してしまうと、列名「No.」などは「1、10、2、3、4...」という並び順になりますが、そこは仕様説明のとおり、数値しか入っていない列は数値比較によってソートしています。
列名「数値項目」にあるとおり、1.234などの小数点付きの値もきちんと数値として扱っています。
ただし、列名「カンマ」にあるような3桁区切りされた数値は文字列として扱われるため、文字コード順になります。
HTMLソース
<table id="sort_table">
<tr>
<th>No</th>
<th>全角項目</th>
<th>数値項目</th>
<th>カンマ</th>
</tr>
<tr><td>1</td><td>かきくけこ</td><td>1</td><td>1,000</td></tr>
<tr><td>2</td><td>さしすせそ</td><td>10</td><td>10,000</td></tr>
<tr><td>3</td><td>あいうえお</td><td>100</td><td>2,000</td></tr>
<tr><td>4</td><td>カキクケコ</td><td>2001</td><td>3,000</td></tr>
<tr><td>5</td><td>アイウエオ</td><td>2002</td><td>20,000</td></tr>
<tr><td>6</td><td>サシスセソ</td><td>2003</td><td>11,000</td></tr>
<tr><td>7</td><td>一丁目</td><td>1.234</td><td>12,000</td></tr>
<tr><td>8</td><td>二丁目</td><td>12.34</td><td>30,000</td></tr>
<tr><td>9</td><td>三丁目</td><td>123.4</td><td>31,000</td></tr>
<tr><td>10</td><td>1丁目</td><td>0.001</td><td>32,000</td></tr>
<tr><td>11</td><td>2丁目</td><td>0.002</td><td>400,000</td></tr>
<tr><td>12</td><td>3丁目</td><td>0.003</td><td>50</td></tr>
</table>
<style>
#sort_table {
border-collapse:collapse;
}
#sort_table td {
border:1px solid lightgray;
}
#sort_table th {
cursor:pointer;
background-color:lightgray;
}
</style>
HTMLとCSSの部分は何の変哲もない普通のTABLEです。IDに"sort_table"をセットすることでJavaScript側でソートすべきTABLEを判別しています。
JavaScriptのソース
©2022 totchira
https://opensource.org/license/mit/
<script>
window.addEventListener('load', function () {
let column_no = 0; //今回クリックされた列番号
let column_no_prev = 0; //前回クリックされた列番号
document.querySelectorAll('#sort_table th').forEach(elm => {
elm.onclick = function () {
column_no = this.cellIndex; //クリックされた列番号
let table = this.parentNode.parentNode.parentNode;
let sortType = 0; //0:数値 1:文字
let sortArray = new Array; //クリックした列のデータを全て格納する配列
for (let r = 1; r < table.rows.length; r++) {
//行番号と値を配列に格納
let column = new Object;
column.row = table.rows[r];
column.value = table.rows[r].cells[column_no].textContent;
sortArray.push(column);
//数値判定
if (isNaN(Number(column.value))) {
sortType = 1; //値が数値変換できなかった場合は文字列ソート
}
}
if (sortType == 0) { //数値ソート
if (column_no_prev == column_no) { //同じ列が2回クリックされた場合は降順ソート
sortArray.sort(compareNumberDesc);
} else {
sortArray.sort(compareNumber);
}
} else { //文字列ソート
if (column_no_prev == column_no) { //同じ列が2回クリックされた場合は降順ソート
sortArray.sort(compareStringDesc);
} else {
sortArray.sort(compareString);
}
}
//ソート後のTRオブジェクトを順番にtbodyへ追加(移動)
let tbody = this.parentNode.parentNode;
for (let i = 0; i < sortArray.length; i++) {
tbody.appendChild(sortArray[i].row);
}
//昇順/降順ソート切り替えのために列番号を保存
if (column_no_prev == column_no) {
column_no_prev = -1; //降順ソート
} else {
column_no_prev = column_no;
}
};
});
});
//数値ソート(昇順)
function compareNumber(a, b)
{
return a.value - b.value;
}
//数値ソート(降順)
function compareNumberDesc(a, b)
{
return b.value - a.value;
}
//文字列ソート(昇順)
function compareString(a, b) {
if (a.value < b.value) {
return -1;
} else {
return 1;
}
return 0;
}
//文字列ソート(降順)
function compareStringDesc(a, b) {
if (a.value > b.value) {
return -1;
} else {
return 1;
}
return 0;
}
</script>
解説
解説も何もソースコード中にがっつりコメントを入れたので、そのまんまなのですが、流れとしては以下のとおり。
- THタグがクリックされる
- クリックされた列番号(cellIndex)を取得
- 対象列にある全データ(textContent)とTRオブジェクトを配列(sortArray)に保存
- 配列に保存するついでにそのデータが数値か文字列かを判定し、文字列がひとつでもあったら文字列ソートと判断する
- クリックされた列番号と、前回クリックされた列番号を比較し、同じだった場合は逆順(降順)ソートと判断する
- これらの判断結果により、使うコールバック関数を決める (compareNumber/compareNumberDesc/compareString/compareStringDesc)
- ソートが完了したら、その順番にTRオブジェクトをTBODYに追加(移動)する
基本を知らないとややこしく感じるかもですが、まずはJavaScriptは配列に対してソートを実行するsort()メソッドを持っている、という点が大事。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
let a = [ 4, 3, 2, 1 ]; みたいな配列があったら、a.sort(); と実行するだけで中身を1,2,3,4と並び変えてくれるわけですね。
ただ、デフォルトでは値は文字列として扱われ、ソート順も昇順ソートのみ。
細かいソート制御をするにはコールバック関数(比較処理のたびに呼ばれる関数)を設定してやる必要があります。サンプルプログラムで言うなら、 compareNumber/compareNumberDesc/compareString/compareStringDesc の4つがコールバック関数ですね。
これらの関数は値の比較が行われるたびに、比較用の値a, bを付けて呼び出されるので、compareほにゃらら関数内で自分に都合の良い比較をしてやり、-1、1、0の3種類の値を返しています。(-1=小さい 1=大きい 0=等しい)
ソート処理はこれで終わりなのですが、それだとクリックした列しかソートされません。実際に必要なのは行まるごとのソートなため、値と行オブジェクト(table.rows[r])を紐づけてsortArrayという配列に格納しているわけです。
そして最後にsortArrayに入った順番通り、TABLEタグ(のtbodyオブジェクト)に行移動(appendChild)して完了。
まとめ
サーバー負荷軽減のためにクライアント側のJavaScriptで一覧表をソートするサンプルを作ってみましたが、どうでしょう、役に立ちそうでしょうか。
一般的にはjQueryのプラグインあたりを使って実装しちゃうのかなーと思いますが、何かあったときに自分で中までいじれないと不安なんですよねぇ。
業務アプリでもなければそこまで気にしなくて良いかも知れませんが、お客様から細かいカスタマイズを要求された際、「それはプラグイン(やフレームワーク)が対応していないので無理です」なんて答えたくありません。
例によって大したコードではありませんが、サンプルはお好きにお使いください。たまたまこのページへ訪れたどこかの誰かのお役に立てば幸いです。
…………………………バグがあったらごめんね!
利用規約
基本的に当ブログで公開している私が書いたプログラムコードはご自由にお使い頂いて構いません。
◎個人利用可
◎商用利用可
◎改変可
◎著作権表記不要
ライセンスの表明が必要な場合は、MITライセンスとしますが、別に著作権表記は不要です。…それだと厳密にはダメなんでしたっけ? MITライセンスより緩いライセンスをよく知らくて…すみません。