最近、いまさらですがjQuery始めました。
で、今日のお題です。
tableタグ表形式のデータを表示する場合で、もし画面内に収まらない場合に
自動的にスクロールバーを表示するには
<div style="overflow: auto; height: 600px"> <table> ...(略) </table> </div>
なんてやりますが、overflowってヘッダ行(th)を固定してくれないので、ヘッダ行ごとスクロールしちゃう。
これはExcelに慣れた身からすると、やや不便。
作ってみた
そこで、overflow:autoを適用しつつ、自動的に固定ヘッダを表示してくれるスクリプトを、
jQueryの練習も兼ねて書いてみました。
"HTML テーブル ヘッダ 固定"でググれば多種多様な対応策を見つけられますが、
僕が書いたスクリプトの特徴は『素直なやり方』ということ。
スタイルシートの奥義を駆使するわけでもなく、
Javascriptでゴニョゴニョするわけでもなく、
実行後はタダのHTMLです。
やっていることを簡単に説明すると、
『テーブルのTHタグを取り出して、固定ヘッダとしてのテーブル要素を新規に追加する』
だけ。スクリプトの半分以上の処理は単なるサイズ調整です。
サイズ調整も、ピクセル単位で細かく計算しているわけではなく
パーセンテージで合わせているだけ。
なので、
- 既にスクリプトやスタイルシートの調整が入りまくったテーブルに対して適用しても、副作用が少ない
ウィンドウのリサイズに対応(2011/03/02追記、ウィンドウリサイズ対応用のスクリプトを足しました)
というメリットがあります。
/* * Copyright (C) 2011 root42 Inc. All rights reserved. */ /** * テーブルにoverflow:autoを適用し、ヘッダ行を固定化します。 * * ※テーブルの中身は由緒正しくtheadとtbodyで囲まないとダメ。たぶん。 * * @param listTableSelector * 固定ヘッダを表示するテーブルを指定するセレクタ * @param listTableHeight * 固定ヘッダを表示するテーブルの高さ(オプション)。デフォルト値は550px。 * * @author root42 Inc. */ function setFixedHeaderTableScrolling(/* String */ listTableSelector, /* int */ listTableHeight) { listTableHeight = listTableHeight || 550; // コレをそのまま利用 // http://stackoverflow.com/questions/986937/javascript-get-the-browsers-scrollbar-sizes /* int */ var scrollBarWidth = function() { document.body.style.overflow = 'hidden'; var width = document.body.clientWidth; document.body.style.overflow = 'scroll'; width -= document.body.clientWidth; if(!width) width = document.body.offsetWidth - document.body.clientWidth; document.body.style.overflow = ''; return width; }(); $(listTableSelector).each(function() { /* Table */ var listTable = $(this); /* 対象のテーブルをdivで囲む。 要素のデタッチ→アタッチが走るため、 内部のスクリプトが再実行されてしまうことに注意!! */ listTable.wrap("<div />").parent() .css('overflow', 'auto') .css('height', listTableHeight + 'px'); /* boolean */ var overflowed = (listTable.parent().height() < listTable.parent().attr('scrollHeight')); /* スクロールバーが表示されないなら何もしない */ if (!overflowed) { return; } /* 固定ヘッダ(table) */ /* Table */ var fixedHeader = $("<table><thead><tr /></thead></table>") // 見た目を同じにするため、クラスをコピー .attr('class', listTable.attr('class')); /* 固定ヘッダのtr */ listTable.find('>thead>tr>th').each(function() { /* 固定ヘッダに、対象テーブルのthタグの内容をコピーする */ fixedHeader.find('tr').append($('<th />').html($(this).html())); }); /* 対象テーブルを囲むdivタグの一つ前に固定ヘッダを挿入する */ listTable.parent().before(fixedHeader); /* int */ var tableWidth = listTable.parent().width(); /* 固定ヘッダと対象テーブルのカラムの比率を一致させる */ fixedHeader.find('th').each(function(/* int */ idx) { /* String */ var tdSelector = '>tbody>tr:first-child>td:nth-child(' + (idx + 1) + ')'; /* Td */ var td = listTable.find(tdSelector); /* int */ var colWidth = Math.max(td.width() , $(this).width()); /* String */ var colRatio = Math.round((colWidth / tableWidth) * 100) + '%'; $.each([$(this), td], function() { this.width(colRatio); }); }); // ウインドウリサイズに対応 $(window).resize(function() { /* overflow:auto分のスクロールバーの幅だけマージンを取る */ fixedHeader.css('width', listTable.parent().width() - scrollBarWidth); return arguments.callee; }()); /* 対象テーブルのtheadを消す */ listTable.find('>thead').detach(); }); }
僕の環境では大体うまく動いてます。(Chrome10 beta、IE8、Firefox3.6)
ただし前述の通り、
"width: 98%"などとしている所から分かる通り、
一応はスクロールバーのサイズを計算して調整していますが、
ヘッダ列とデータ列の位置を、ピクセル単位でピッタリ合わせているわけではないです。
CSSを、なんとなく違和感がないように後付で調整する*1ことで誤魔化してます。
まぁ、パッと見で気が付かなければ、まぁいいかな、と。
ぶっちゃけ手抜きです。ハイ。だってめんどくさいんだもん。
不具合や、『jQueryの???を使うと、もっと短く書けるよ!』
などがあったらお知らせ下さい。忙しくなければ出来る限り対応します。
それにしても
jQueryってなんだか楽しいですね。。。ワンライナーに目一杯、処理を詰め込めるところが特に。
ソースは壊滅的に読みづらくなるけど。
『何でもワンライナーでやっちまうのが好き!』という方にはピッタリ。
2011-02-22追記
サンプルのアプリケーション込みの全体のHTMLはコチラ。
そのまま実行できます。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Style-Type" content="text/css" /> <meta http-equiv="Content-Script-Type" content="text/javascript" /> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script> </head> <body onload="setFixedHeaderTableScrolling('#data', 100);"> <script type="text/javascript"> /* * Copyright (C) 2011 root42 Inc. All rights reserved. */ /** * テーブルにoverflow:autoを適用し、ヘッダ行を固定化します。 * * ※テーブルの中身は由緒正しくtheadとtbodyで囲まないとダメ。たぶん。 * * @param listTableSelector * 固定ヘッダを表示するテーブルを指定するセレクタ * @param listTableHeight * 固定ヘッダを表示するテーブルの高さ(オプション)。デフォルト値は550px。 * * @author root42 Inc. */ function setFixedHeaderTableScrolling(/* String */ listTableSelector, /* int */ listTableHeight) { listTableHeight = listTableHeight || 550; // コレをそのまま利用 // http://stackoverflow.com/questions/986937/javascript-get-the-browsers-scrollbar-sizes /* int */ var scrollBarWidth = function() { document.body.style.overflow = 'hidden'; var width = document.body.clientWidth; document.body.style.overflow = 'scroll'; width -= document.body.clientWidth; if(!width) width = document.body.offsetWidth - document.body.clientWidth; document.body.style.overflow = ''; return width; }(); $(listTableSelector).each(function() { /* Table */ var listTable = $(this); /* 対象のテーブルをdivで囲む。 要素のデタッチ→アタッチが走るため、 内部のスクリプトが再実行されてしまうことに注意!! */ listTable.wrap("<div />").parent() .css('overflow', 'auto') .css('height', listTableHeight + 'px'); /* boolean */ var overflowed = (listTable.parent().height() < listTable.parent().attr('scrollHeight')); /* スクロールバーが表示されないなら何もしない */ if (!overflowed) { return; } /* 固定ヘッダ(table) */ /* Table */ var fixedHeader = $("<table><thead><tr /></thead></table>") // 見た目を同じにするため、クラスをコピー .attr('class', listTable.attr('class')); /* 固定ヘッダのtr */ listTable.find('>thead>tr>th').each(function() { /* 固定ヘッダに、対象テーブルのthタグの内容をコピーする */ fixedHeader.find('tr').append($('<th />').html($(this).html())); }); /* 対象テーブルを囲むdivタグの一つ前に固定ヘッダを挿入する */ listTable.parent().before(fixedHeader); /* int */ var tableWidth = listTable.parent().width(); /* 固定ヘッダと対象テーブルのカラムの比率を一致させる */ fixedHeader.find('th').each(function(/* int */ idx) { /* String */ var tdSelector = '>tbody>tr:first-child>td:nth-child(' + (idx + 1) + ')'; /* Td */ var td = listTable.find(tdSelector); /* int */ var colWidth = Math.max(td.width() , $(this).width()); /* String */ var colRatio = Math.round((colWidth / tableWidth) * 100) + '%'; $.each([$(this), td], function() { this.width(colRatio); }); }); // ウインドウリサイズに対応 $(window).resize(function() { /* overflow:auto分のスクロールバーの幅だけマージンを取る */ fixedHeader.css('width', listTable.parent().width() - scrollBarWidth); return arguments.callee; }()); /* 対象テーブルのtheadを消す */ listTable.find('>thead').detach(); }); } </script> <div> <style> #data { width: 100%; } th, td { border: solid 1px; word-break: break-all; } </style> <table id="data"> <thead> </thead> <tbody> </tbody> </table> <script type="text/javascript"> var charNum = 10; var colNum = 10; var rowNum = 3; $('#data>thead').append($("<tr />")); for (var i = 0; i < colNum; i++) { $('#data>thead>tr').append($("<th />")); for (var m = Math.ceil(Math.random() * charNum); 0 <= m ; m--) { $('#data>thead>tr>th:last-child').append('あ'); } } for (var i = 0; i < rowNum; i++) { $('#data>tbody').append($("<tr />")); $('#data>tbody>tr:last-child').append($("<td>" + i + "</td>")); for (var k = 1; k < colNum; k++) { $('#data>tbody>tr:last-child').append($("<td />")); for (var m = Math.ceil(Math.random() * charNum); 0 <= m ; m--) { $('#data>tbody>tr:last-child>td:last-child').append('い'); } } } </script> </div> </body> </html>
*1: この処理は上記スクリプトに入っていないので、自分でやる必要アリ