故郷を離れて三十数年、関東で暮らす永遠の少年のどーってことない日記
xxSEARCHxx
プロフィール

ST☆FF

Author:ST☆FF
HTTといえばインテルの Hyper-Threading Technology!・・・ではなく、放課後ティータイムのことだと思います。

最近の記事
最近のコメント
最近のトラックバック
月別アーカイブ
カテゴリー
ブロとも申請フォーム
ブログ内検索
RSSフィード
リンク

最近の記事


jQuery による HTML セルの動的展開・収納 

2013/09/08 17:01/Sun
複雑なリレーションを伴った膨大なデータ量を扱うWEB系システムの画面の悩みといえば、データ検索における問い合わせから結果画面表示完了までの待ち時間がどうしても長くなりがちなことです。
いっそのこと詳細データは省略して、上位キーの情報だけを先にレスポンスとして返して画面を構成出来ればDBへの負担も通信データ量も軽減することが出来ます。

これを何とか実現出来ないものかと前々から思案していたのですが、先日久しぶりに顔を合わせた昔の仕事仲間からかなり突っ込んだヒントを貰うことが出来たので、まとまった時間をとってコードを書いてみました。
最近のjQueryで関連するらしきアドオンとしては footable があるようですが情報が探しきれませんでした。そこで今回は特にそういったものを使うことなく、標準モジュールのみで実装する方法になります。

本来ならばAJAXを使用しサーバからデータを受信して動かすところですが、面倒なのでイベントハンドラ内部にスタブコードを書いてその中でダミーのJSONデータを作り、表示しています。
ただのHTMLファイルですから、クライアントマシン上のブラウザだけで動くようになっています。

コードを追いやすいように画面イメージを先にお見せします。
展開収納画面
列名ヘッダの上には一括展開レベルを選択するためのラジオボタンをつけてみました。
「レベル1まで」を指示してトップレベルの展開セルをクリックすると下画面の様になります。
レベル1の展開セルも同時に展開イベントが起動してcol4列の値が表示されますが、レベル2の展開セルは収納状態で表示されます。

それでは実際のコードを見てみましょう。

ところでHTML形式のコードをブログに貼り付けたら、勝手に広告コードが入ってしまいました。ハイライト表示にしてあるところが挿入された広告部分です。
その際にbodyタグは勝手に取り込まれ、html終了タグは消去されてしまっているので、動かしたい方は適宜補完して下さい。











- expand(jquery-1.4.2にて動作確認)-




























一括展開




col1 col2 col3 col4 col5 col6
値1
値2





なお、1,2,4,64行目は私がコメントアウトしたのでここもコメントをはずして下さい。
62行目<!-- ここにjavascriptコードを記述 -->
とある部分に、以降に掲載するjavascriptコードを書いていきます。


最初のコードでは、ユーザ関数adjRowspan()とexpand()を追加してライブラリを拡張します。

adjRowspan関数(4行目)
引数numはRowspan属性値の増減を指定する整数値です。
展開/収納イベント発生セルの左側に存在する全セルのRowspan属性値を書き換える機能を持たせます。
展開時には挿入行数分を増分し、収納時には1にするために使用します。
// 展開&収納設定
$.fn.extend({
//自階層と上位階層の rowspan 調整
adjRowspan: function(num) {
var me = this;
return me.prevAll().add(me.parent().prevAll().children(function() {
//上位階層の rowspan 調整対象セレクタ
var selectors = ["dmy"];
for (var i = 0; i < me.attr("class").match(/lv(\d)/)[1]; i++) {
selectors.push(".collapse.lv" + i + ":first");
}
return selectors.join();

} ()).prevAll()).attr("rowspan", function(i, attr) { return attr + num; }).end().end();

},
//展開
expand: function(getHtml) {
var me = this;
var level = me.attr("class").match(/lv(\d)/)[1];

// 実務では非表示inputタグのvalue属性に
// パラメータ(PK情報)付きのasp URLを持たせる
var keyval = me.children().val();

$.ajax({
//.aspのURL
//url: me.children().val(),
url: "about:blank", // デモ用:""でも可
cache: false,
dataType: "json",
success: function(json) {
//rowspan の調整と行追加
var d = eval(json).reverse();
me.adjRowspan(d.length);
$.each(d, function() { me.parent().after(getHtml(this)); });

//一括展開
setTimeout(function() {
me.parent().nextUntil(":has(.lv0)")
// ヘッダで選択されたラジオボタンからセレクタ文字列を取得
.filter(":has(.expand):has(" + $("#autoExpand :checked").val() + "):first")
.children(":last").click();
});
},
// 簡略化の為、サーバ処理は書かずに通信完了イベントに対して
// 正常処理系callbackをバインド(スタブ)
complete: function() {
var jsondata = null;

// レベル切り替え(スタブ)
if (level === '0') { //Level0の展開
if (keyval === 'pk01') {
jsondata = [
{ITEM1:'値11',ITEM2:'値11A',ITEM3:'pk11'},
{ITEM1:'値12',ITEM2:'値12A',ITEM3:'pk12'},
{ITEM1:'値13',ITEM2:'値13A',ITEM3:'pk13'}
]
} else if (keyval === 'pk02') {
jsondata = [
{ITEM1:'値21',ITEM2:'値21A',ITEM3:'pk21'},
{ITEM1:'値22',ITEM2:'値22A',ITEM3:'pk22'},
{ITEM1:'値23',ITEM2:'値23A',ITEM3:'pk23'}
]
};
} else if (level ==='1') { //Level1の展開
if (keyval === 'pk11') {
jsondata = [
{ITEM1:'値111',ITEM2:'pk101'},
{ITEM1:'値112',ITEM2:'pk101'}
]
} else if (keyval === 'pk12') {
jsondata = [
{ITEM1:'値121',ITEM2:'pk103'}
]
} else if (keyval === 'pk13') {
jsondata = [
{ITEM1:'値131',ITEM2:'pk104'},
{ITEM1:'値132',ITEM2:'pk105'}
]
} else if (keyval === 'pk21') {
jsondata = [
{ITEM1:'値211',ITEM2:'pk201'},
{ITEM1:'値212',ITEM2:'pk202'}
]
} else if (keyval === 'pk22') {
jsondata = [
{ITEM1:'値221',ITEM2:'pk203'}
]
} else if (keyval === 'pk23') {
jsondata = [
{ITEM1:'値231',ITEM2:'pk204'}
]
};
} else if (level ==='2') { //Level2の展開
if (keyval === 'pk101') {
jsondata = [
{ITEM1:'値1111',ITEM2:'値1111A'}
]
} else if (keyval === 'pk102') {
jsondata = [
{ITEM1:'値1121',ITEM2:'値1121A'}
]
} else if (keyval === 'pk103') {
jsondata = [
{ITEM1:'値1211',ITEM2:'値1211A'},
{ITEM1:'値1212',ITEM2:'値1212A'}
]
} else if (keyval === 'pk104') {
jsondata = [
{ITEM1:'値1311',ITEM2:'値1311A'}
]
} else if (keyval === 'pk105') {
jsondata = [
{ITEM1:'値1321',ITEM2:'値1321A'}
]
} else if (keyval === 'pk201') {
jsondata = [
{ITEM1:'値2111',ITEM2:'値2111A'},
{ITEM1:'値2112',ITEM2:'値2112A'}
]
} else if (keyval === 'pk202') {
jsondata = [
{ITEM1:'値2121',ITEM2:'値2121A'}
]
} else if (keyval === 'pk203') {
jsondata = [
{ITEM1:'値2211',ITEM2:'値2211A'}
]
} else if (keyval === 'pk204') {
jsondata = [
{ITEM1:'値2311',ITEM2:'値2311A'},
{ITEM1:'値2312',ITEM2:'値2312A'},
{ITEM1:'値2313',ITEM2:'値2313A'}
]
};
};
//.after()する為、予め逆順にする
var d = eval(jsondata).reverse();
me.adjRowspan(d.length);

$.each(d, function() { me.parent().after(getHtml(this)); });

//下層にレベル0のTRタグが現れるまで一括展開
setTimeout(function() {
me.parent().nextUntil(":has(.lv0)")
.filter(":has(.expand):has(" + $("#autoExpand :checked").val() + "):first")
.children(":last").click(); });
}
});
}
});
expand関数(18行目)
引数getHtmlはサーバから受け取ったJSON形式のデータをHTMLに成形する関数へのポインタです。
24行目ではスタブで利用する為だけにkeyvalを取っています。
26-45行目はサーバとの通信が書いてありますが、本版はサーバ処理を必要としないデモ用なので32行目のsuccessが呼ばれることはなく、代わりに必ず呼ばれる48行目のcompleteにcallback関数を書いてあります。
callback関数内では、まずlevelとkeyvalに応じてJSONデータを模造します。
その後139行目で、展開イベント発生セルの直下に挿入する為の準備としてデータの並び順を逆転しています。
140行目では左側に存在する全セルのRowspan属性値を増分した後、142行目で各データに対し、HTML変換と自行直下への挿入を行います。
144行目以下の処理は一括展開の実装部です。画面上部のラジオボタンで自動展開レベルを指定可能にしました。
指定されたレベル毎にセレクタ文字列を取得し、マッチする全イベントセルの展開イベントを起動しています。


次はレベル毎のイベントセルに対応するハンドラの登録部分です。
展開ハンドラ内では無名関数を使用し、その中で拡張関数expand()を呼んでいます。
expand関数の引数はHTML変換関数へのポインタで、この関数の処理を内側の無名関数で記述しています。
HTML変換処理は、関数の引数であるJSONデータdからHTMLタグ文字列を生成して返しているだけです。
//レベル0の展開
$("div.tableview1 td.expand.lv0").live("click", function() {
$(this).expand(function(d) {
return "" +
"" + d.ITEM1 + "" +
"" + d.ITEM2 + "" +
"" +
"開" + "" +
"" +
"";
});
});
//レベル1の展開
$("div.tableview1 td.expand.lv1").live("click", function() {
$(this).expand(function(d) {
return "" +
"" + d.ITEM1 + "" +
"" +
"開" + "" +
"" +
"";
});
});
//レベル2の展開
$("div.tableview1 td.expand.lv2").live("click", function() {
$(this).expand(function(d) {
return "" +
"" + d.ITEM1 + "" +
"" + d.ITEM2 + "" +
"";
});
});

収納ハンドラ内では、無名関数を使用し、イベントセルの左側に存在する全セルのRowspan属性値が1になるまでデクリメントしながら、同時に下層にぶら下がっているデータ行を削除しています。
// 収納
$("div.tableview1 td.collapse").live("click", function() {
//rowspan の調整と下層行の削除
while ($(this).prev().attr("rowspan") > 1) $(this).adjRowspan(-1).parent().next().remove();

//収納によりスクロールが無効になる場合、
//ヘッダ位置調整
$("div.tableview1").scroll();
});

最後はイベントセルの開閉文字をトグル切り替えする処理です。
実業務では更にGIFアイコン等を使用して視覚的に仕上げるのも良いかもしれません。
//クリック毎に収納⇔展開を切替
$("div.tableview1 td.collapse,div.tableview1 td.expand").live("click", function(e) {
$(this)
.toggleClass("collapse").toggleClass("expand")
.html(function(i, htm) {
return $(this).hasClass("collapse") ? htm.replace("開", "閉") : htm.replace("閉", "開");
});
});

以上、簡単に説明をして来ましたが、jQueryは時間さえ許せばなかなか面白いものかもしれません。
仕事で使う分には、単純に「面白い/楽しい」では済まされないものではありますが・・・。



スポンサーサイト
CATEGORY:仕事関係 | CM(0) | TB(0) |
xx HOME xx