jQuery テーブルプラグインがIEでは遅すぎるのでプラグインを作成してみる
jQuery のテーブル用のプラグインを利用してシステム開発に利用することを想定していたのだが、画面の描画、およびスクロールしたときの反応が使用に耐えないくらい遅すぎる。
まぁ、今回行っているのは、列が数十あるようなテーブルのことで、各プラグインのデモで扱われている件数なら、問題ない。
そもそもそんなに列があるような画面設計にするなとか言う話かもしれないが、業務アプリだと結構そのくらい列をもったテーブルが必要な場合もありがちなので、今回だけ設計の変更で回避というのも違う気がする。
実は、Chrome ではそこそこの速度で動き、実用には耐えられそうなのだ。が、問題はIE8。システム要件上、IE8で動作する必要があるんだよなー
DataTable や、Frexigrid、tablesoter なんかをいろいろ設定を変えながら、試したのだが、どれも今ふたつみっつ。
まぁ、そもそもそんな高機能なことができる必要は無い。できればもちろんうれしいのだが、汎用性より応答性をとらなければならない局面。せっかくなので、jQueryの勉強がてら、プラグインを自作してみよう。
要件
随時、パフォーマンスを見ながら機能追加していくとして、最低限の要件は、
- 数十列 × 数百行 のデータが(通常のテーブルに比べ)必要以上に遅くならずに描画されること。
- テーブルヘッダーを固定
- ボディーの横スクロールにあわせてヘッダーをスクロール
んー最初はこんな程度でしょう。
jQuery プラグインの作成
jQueryプラグインの作成自体は簡単だ。jQueryクックブック に、丁寧に解説してくれている。乱暴に手順を書くと、以下の感じか。
- ラグインを匿名関数でラップし、すぐその関数を実行することで、$ でjQueryオブジェクトを参照できる (1)
- jQuery.fn オブジェクトをメソッド名で拡張しjQueryメソッドを作成する (2)
- jQuery オブジェクトは複数の結果に対応する必要があるので、each() 処理を行う (3)
;(function($){ // (1) $.fn.lightable = function(options) { // (2) var options = $.extend({ // デフォルトのオプション },options); return this.each(function(){ // (3) // 処理本体 }); }; })(jQuery);
テーブルの構成
肝心の、テーブルについてだが、少しでもIEのテーブルの描画反応をよくするために、テーブルの描画に、固定レイアウトアルゴリズムが用いられるようにする。
「表は通常、構成要素をすべて読み込んでから表示される。table-layout プロパティを利用することで、表のレイアウトに使用するアルゴリズムを選択できる。」
と、「Web標準の教科書―XHTMLとCSSでつくる“正しい”Webサイト」にあり、固定レイアウトアルゴリズムが採用されるためには、
- table-layout:fixed に設定する
- width プロパティで表全体の幅を指定する
と、する必要がある。width プロパティが未指定や「auto」の場合は、自動レイアウトアルゴリズムが採用されてしまう。
作成してみた
こんな感じ。まったく機能性がないので、寂しくはある。が、まぁスタートラインなのでよしとする。
以下内容。
jQueryプラグイン (jquery.lightable.js)
jQuery の概要やセレクタ、リファレンスとして、「jQuery逆引きマニュアル Webデザインの現場で役立つ基本と実践 」は非常にわかりやすくて、良書。参考にさせていただきながら、、、
やっていることは、
- $.extend で、オプションのデフォルト値を使用
- COL 要素に パラメータとして渡された、width をセット。 ここで align やらスタイルやら列ごとの定義を指定できればよいのだが、どうもできなさそうだ。IE6ではできたのだが、できてしまうのがどうやらおかしいようだ。列のスタイル適用方法については、改めて検討しよう。
- TABLE を DIV で囲んで、ヘッダーをコピーして別のDIVで囲み、横スクロールを同期させる
くらいか。
;(function($){ $.fn.lightable = function(options) { var cfg = $.extend({ height:"100%", },options); var table, headerTable; var wrapper, headWrapper, bodyWrapper; /** * 処理本体 */ return this.each(function(){ table = $(this); table.attr({cellspacing:"0", border:"0", cellpadding:"0" }); headerTable = $(document.createElement("table")); headerTable.attr({cellspacing:"0", border:"0", cellpadding:"0" }); headerTable.prepend($("thead", table).addClass("ui-widget-header").clone()); $('thead tr',headerTable).append("<th></th>"); // for scrollbar // 固定レイアウトアルゴリズムを利用するように属性を設定(table-layout=fixed かつ widthプロパティが指定される必要がある) var tattr = {"table-layout":"fixed", "width":"100%"}; table.css(tattr); headerTable.css(tattr); if (cfg.cols) { var cg = new Array(); $(cfg.cols).each(function(){ cg.push("<col"); if ("width" in this) { cg.push(" width='"); cg.push(this.width); cg.push("'"); } cg.push("/>"); }); table.prepend(cg.join("")); // for scrollbar cg.push("<col width='18px'/>"); headerTable.prepend(cg.join("")); } // 構造を作成 table.wrapAll("<div class='lightable-wrapper' style='width:100%;'>" + "<div class='lightable-body-wrapper' style='overflow:auto;width:100%;" + "height:" + cfg.height + ";'></div></div>"); wrapper = table.closest(".lightable-wrapper"); wrapper.prepend("<div class='lighttable-head-wrapper' style='overflow:hidden;width:100%;height:100%;'></div>"); headWrapper = $(".lighttable-head-wrapper", wrapper); bodyWrapper = table.closest(".lightable-body-wrapper"); headWrapper.prepend(headerTable); $("thead" ,table).hide(); bodyWrapper.scroll(function(){ headWrapper.scrollLeft(this.scrollLeft); }); }); }; })(jQuery);
HTML (table_test.html)
ざっくりこんな感じ。
$(テーブル).lightable(パラメータ); でプラグインを適用
ひとつ驚いた?のは、配列の要素の最後のカンマがあるか無いかで、IE とChrome でjQuery.each() で返される要素数が異なること。
どうも、[1,2,3,] という様な配列 の場合、IE だと、最後のカンマの後にnull があると判断してか、4要素処理されるが、Chrome だと、3回しか処理されない様だ。
<!DOCTYPE html> <head> <meta http-equiv="Content-Type" content="text/html; charset=shift-jis"> <link rel="stylesheet" type="text/css" href="./jquery/css/custom-theme/jquery-ui-1.8.14.custom.css" /> <link rel="stylesheet" type="text/css" href="./css/lightable.css" /> <style type="text/css"> .number { text-align:right; } </style> <title>Lightable Test</title> </head> <body> <table id="tbl_dummy" style="table-layout:fixed;width:100%;"> <thead> <tr> <th>カラム1</th> <th>カラム2</th> <th>カラム3</th> <th>カラム4</th> <th>カラム5</th> <th>カラム6</th> <th>カラム7</th> <th>カラム8</th> <th>カラム9</th> <th>カラム10</th> <th>カラム11</th> <th>カラム12</th> <th>カラム13</th> <th>カラム14</th> <th>カラム15</th> <th>カラム16</th> <th>カラム17</th> <th>カラム18</th> <th>カラム19</th> <th>カラム20</th> <th>カラム21</th> <th>カラム22</th> <th>カラム23</th> <th>カラム24</th> <th>カラム25</th> <th>カラム26</th> <th>カラム27</th> <th>カラム28</th> <th>カラム29</th> <th>カラム30</th> <th>カラム31</th> <th>カラム32</th> <th>カラム33</th> <th>カラム34</th> <th>カラム35</th> <th>カラム36</th> <th>カラム37</th> <th>カラム38</th> <th>カラム39</th> <th>カラム40</th> </tr> </thead> <tbody> </tbody> </table> <script type="text/javascript" src="./jquery/js/jquery-1.6.1.js"></script> <script type="text/javascript" src="./jquery-ui/js/jquery-ui-1.8.14.custom.min.js"></script> <script type="text/javascript" src="./js/jquery.lightable.js"></script> <script type="text/javascript" src="./dat.js"></script> <script type="text/javascript"> $(function(){ $("#tbl_dummy").lightable({ cols:[ {width:100,align:"right"}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100}, {width:200}, {width:300}, {width:100} ] , height:"500px", }); loadDummyData(); }); function loadDummyData() { var rows = getData(); var tb = $("#tbl_dummy"); var rb = null; rb = new Array(); for (var i=0; i<rows.length; i++) { var cells = rows[i]; rb.push("<tr>"); for (var j=0; j<cells.length; j++) { rb.push("<td class='number'>"); rb.push(cells[j]); rb.push("</td>"); } rb.push("</tr>"); } tb.append(rb.join("")); } </script> </body> </html>/pre>
CSS (lightable.css)
CSSはこんな感じ。
div.lightable-wrapper { border: 1px solid #DDD; } div.lightable-wrapper th { border-width: 0 1px 1px 0; border-style: solid; border-color: #DDD; background: none; font-weight: normal; padding: 5px; } div.lightable-wrapper td { padding: 5px; border-width: 0 1px 1px 0; border-style: solid; border-color: #DDD; background: none; }
テストデータ作成 (dat.js)
適当にテストデータの配列をつくる。
function getData() { var rows = []; for (var i=0; i<200; i++) { var row = []; row.push(i + 1); row.push("テストデータ テストデータ 2"); row.push("テストデータ テストデータ テストデータ 3"); row.push("テストデータ 4"); row.push("テストデータ テストデータ 5"); row.push("テストデータ テストデータ テストデータ 6"); row.push("テストデータ 7"); row.push("テストデータ テストデータ 8"); row.push("テストデータ テストデータ テストデータ 9"); row.push("テストデータ 10"); : 略 : rows.push(row); } return rows; }
んー普通に、既にあるプラグインが動いてくれれば幸せなんだけどなー
面倒くさいけど、これをベースに作り込んでいくしかないかなー