テーブル行ヘッダ列ヘッダ固定化Extender バージョン1.0 その1 から引き続き、ヘッダ固定化Extender の解説を行います。
ページ下部に動作確認用のテストソリューションつきのソリューションがダウンロードできるようになっています。
前回から続きです。テーブルの列、行をリサイズするためのjavascript の編集を行います。
1.5 TableResizeBehavior.js の編集
TableResizeBehavior.js を編集します。ビルドアクションが埋め込まれたリソースとなっていることを確認してください。このエクステンダーで行列ヘッダのリサイズと行ヘッダダブルクリック時に、サイズを文字幅にあわせて自動拡張できるようにしています(自動収縮はしません)。
/// <reference name="MicrosoftAjax.js"/>
Type.registerNamespace("ComponentGeek.Extender");
///////////////////
ComponentGeek.Extender.TableXResizeBehavior = function(element) {
ComponentGeek.Extender.TableXResizeBehavior.initializeBase(this, [element]);
this._header; // ヘッダ
this._body; // ボディ
this._target; // サイズ変更対象セル
this._startX; // 幅変更開始X座標
this._endX; // 幅変更終了X座標
this._xResizing = false; // 幅変更中フラグ
this._resizeThreashold = 8; // 最小変更幅
this._edgeThreshold = 8; // 幅,高さ変更カーソル表示閾値
this._sizeThreshold = 20; // 最小列,幅高さ
this._vBarID = "vBarID"; // 変更時に表示する縦棒のID
this._xResizeCursorVisible = false; // 幅の変更カーソル表示フラグ
// this._resizingCssClass = ""; // サイズ変更時のCss
// this._originalCssClass = "";
}
ComponentGeek.Extender.TableXResizeBehavior.prototype = {
initialize: ComponentGeek$Extender$TableXResizeBehavior$initialize,
dispose: ComponentGeek$Extender$TableXResizeBehavior$dispose,
_onMouseDown: ComponentGeek$Extender$TableXResizeBehavior$_onMouseDown,
_onMouseUp: ComponentGeek$Extender$TableXResizeBehavior$_onMouseUp,
_onMouseMove: ComponentGeek$Extender$TableXResizeBehavior$_onMouseMove,
_onDblClick: ComponentGeek$Extender$TableXResizeBehavior$_onDblClick,
createBar: ComponentGeek$Extender$TableXResizeBehavior$createBar,
clear: ComponentGeek$Extender$TableXResizeBehavior$clear,
getFirstRowCell: ComponentGeek$Extender$TableXResizeBehavior$getFirstRowCell,
get_header: function() {
return this._header;
},
set_header: function(value) {
if (this._header !== value) {
this._header = value;
this.raisePropertyChanged('header');
}
},
get_body: function() {
return this._body;
},
set_body: function(value) {
if (this._body !== value) {
this._body = value;
this.raisePropertyChanged('body');
}
}
}
function ComponentGeek$Extender$TableXResizeBehavior$initialize() {
ComponentGeek.Extender.TableXResizeBehavior.callBaseMethod(this, 'initialize');
if (!this._header) {
throw Error.create("headerを指定してください");
}
$addHandler(this._header, "mousedown", Function.createDelegate(this, this._onMouseDown));
$addHandler(this._header, "mouseup", Function.createDelegate(this, this._onMouseUp));
$addHandler(this._header, "mousemove", Function.createDelegate(this, this._onMouseMove));
$addHandler(this._header, "dblclick", Function.createDelegate(this, this._onDblClick));
this.createBar();
}
function ComponentGeek$Extender$TableXResizeBehavior$dispose() {
$clearHandlers(this._header);
ComponentGeek.Extender.TableXResizeBehavior.callBaseMethod(this, 'dispose');
}
function ComponentGeek$Extender$TableXResizeBehavior$getFirstRowCell(tbl, cellIndex) {
///<summary>ヘッダセル取得</summary>
// テーブルの1行目のセルを取得する
var headerCell = tbl.rows(0).cells(cellIndex);
return headerCell;
}
function ComponentGeek$Extender$TableXResizeBehavior$createBar() {
///<summary>変更バー作成</summary>
// リサイズ時に表示する縦横バー作成
// 横幅変更バー作成
var elem = $get(this._vBarID);
if (!elem) {
elem = document.createElement("span");
elem.id = this._vBarID;
elem.style.position = "absolute";
elem.style.top = "0";
elem.style.left = "0";
elem.style.height = "0";
elem.style.width = "2";
elem.style.background = "silver";
elem.style.borderLeft = "1px solid black";
elem.style.display = "none";
document.body.appendChild(elem);
}
}
function ComponentGeek$Extender$TableXResizeBehavior$clear() {
// プロパティをクリア
var elem = $get(this._vBarID);
if (elem) {
elem.runtimeStyle.display = "none";
}
this._target = null;
this._startX = null;
this._endX = null;
this._xResizing = false;
}
function ComponentGeek$Extender$TableXResizeBehavior$_onMouseDown(e) {
// マウスダウンイベント
// 変更カーソル表示中の場合はリサイズ開始
var vBar = $get(this._vBarID);
if (!vBar) return;
if (!this._header) return;
if (this._xResizeCursorVisible) {
this._target = e.target;
this._startX = e.clientX;
this._xResizing = true;
this._header.setCapture();
var pos = getElementPosition(this._body);
vBar.runtimeStyle.top = pos.top;
vBar.runtimeStyle.left = e.clientX + document.documentElement.scrollLeft;
if (this._body.parentElement.offsetHeight < this._body.offsetHeight) {
vBar.runtimeStyle.height = this._body.parentElement.offsetHeight;
} else {
vBar.runtimeStyle.height = this._body.offsetHeight;
}
vBar.runtimeStyle.display = "inline";
}
}
function ComponentGeek$Extender$TableXResizeBehavior$_onMouseMove(e) {
if (!this._header) return;
// マウス移動イベント
// マウスが列の左端,下端に近づくとカーソル変更
if (this._xResizing) {
var vBar = $get(this._vBarID);
if (!vBar) return;
// 変更バー移動
vBar.runtimeStyle.left = e.clientX + document.documentElement.scrollLeft;
document.selection.empty();
return;
}
if (e.offsetX >= (e.target.offsetWidth - this._edgeThreshold)) {
this._xResizeCursorVisible = true;
// e.target.runtimeStyle.cursor = "e-resize";
this._header.runtimeStyle.cursor = "e-resize";
} else {
this._xResizeCursorVisible = false;
// 行ヘッダかつ列ヘッダの場合にマウス移動イベントの競合対策
// 水平、垂直方向変更用にマウス変更イベントが重複して発生するため
// 垂直方向変更中にカーソルをクリアしないようにする
if (this._header.runtimeStyle.cursor == "s-resize") return;
if (this._header.style.cursor) {
debugger;
this._header.runtimeStyle.cursor = this._header.style.cursor;
} else {
this._header.runtimeStyle.cursor = "";
}
}
}
function ComponentGeek$Extender$TableXResizeBehavior$_onMouseUp(e) {
// マウスアップイベント
// サイズ変更中の場合は変更を行う
if (this._target == null) return;
this._endX = e.clientX;
if (!this._header) return;
if (this._xResizing && this._startX != null && Math.abs(this._endX - this._startX) > this._resizeThreashold) {
// 新しい幅
var newWidth = this._target.offsetWidth + (this._endX - this._startX);
// 閾値以上の場合はサイズを適用,そうでない場合、最小幅設定
if (newWidth > this._sizeThreshold) {
// ヘッダセル取得
var head = this.getFirstRowCell(this._header, this._target.cellIndex);
if (head) head.style.width = newWidth;
var body = this.getFirstRowCell(this._body, this._target.cellIndex);
if (body) body.style.width = newWidth;
}
}
this._header.releaseCapture();
this.clear();
}
function ComponentGeek$Extender$TableXResizeBehavior$_onDblClick(e) {
///<summary>リサイズカーソル表示外でダブルクリックが行われた場合</summary>
if (this._xResizeCursorVisible) return;
if (e.target.cellIndex == 'undefined') return;
var preferredWidth = this._sizeThreshold;
for (var i = 0; i < this._body.rows.length; ++i) {
var cell = this._body.rows(i).cells(e.target.cellIndex);
if (cell.scrollWidth > preferredWidth) {
preferredWidth = cell.scrollWidth;
}
}
// 求まったサイズで幅を広げる
var header = this.getFirstRowCell(this._header, e.target.cellIndex);
if (!header) return;
var width = eval(header.style.width.replace('px', ''));
// 変更幅が小さい場合は処理なし
if (Math.abs(width - preferredWidth) < this._resizeThreashold) return;
// サイズ変更
if (header) header.style.width = preferredWidth;
var body = this.getFirstRowCell(this._body, e.target.cellIndex);
if (body) body.style.width = preferredWidth;
}
ComponentGeek.Extender.TableXResizeBehavior.registerClass('ComponentGeek.Extender.TableXResizeBehavior', Sys.UI.Behavior);
/////////////////////////
ComponentGeek.Extender.TableYResizeBehavior = function(element) {
ComponentGeek.Extender.TableYResizeBehavior.initializeBase(this, [element]);
this._header; // ヘッダ
this._body; // ボディ
this._target; // サイズ変更対象セル
this._startY; // 高さ変更開始Y座標
this._endY; // 高さ変更開始X座標
this._yResizing = false; // 高さ変更中フラグ
this._resizeThreashold = 8; // 最小変更幅
this._edgeThreshold = 8; // 幅,高さ変更カーソル表示閾値
this._sizeThreshold = 20; // 最小列,幅高さ
this._hBarID = "hBarID"; // 変更時に表示する横棒のID
this._yResizeCursorVisible = false; // 高さの変更カーソル表示フラグ
}
ComponentGeek.Extender.TableYResizeBehavior.prototype = {
initialize: ComponentGeek$Extender$TableYResizeBehavior$initialize,
dispose: ComponentGeek$Extender$TableYResizeBehavior$dispose,
_onMouseDown: ComponentGeek$Extender$TableYResizeBehavior$_onMouseDown,
_onMouseUp: ComponentGeek$Extender$TableYResizeBehavior$_onMouseUp,
_onMouseMove: ComponentGeek$Extender$TableYResizeBehavior$_onMouseMove,
createBar: ComponentGeek$Extender$TableYResizeBehavior$createBar,
clear: ComponentGeek$Extender$TableYResizeBehavior$clear,
getFirstRowCell: ComponentGeek$Extender$TableYResizeBehavior$getFirstRowCell,
get_header: function() {
return this._header;
},
set_header: function(value) {
if (this._header !== value) {
this._header = value;
this.raisePropertyChanged('header');
}
},
get_body: function() {
return this._body;
},
set_body: function(value) {
if (this._body !== value) {
this._body = value;
this.raisePropertyChanged('body');
}
}
}
function ComponentGeek$Extender$TableYResizeBehavior$initialize() {
ComponentGeek.Extender.TableYResizeBehavior.callBaseMethod(this, 'initialize');
if (!this._header) {
throw Error.create("headerを指定してください");
}
$addHandler(this._header, "mousedown", Function.createDelegate(this, this._onMouseDown));
$addHandler(this._header, "mouseup", Function.createDelegate(this, this._onMouseUp));
$addHandler(this._header, "mousemove", Function.createDelegate(this, this._onMouseMove));
this.createBar();
}
function ComponentGeek$Extender$TableYResizeBehavior$dispose() {
$clearHandlers(this._header);
ComponentGeek.Extender.TableYResizeBehavior.callBaseMethod(this, 'dispose');
}
function ComponentGeek$Extender$TableYResizeBehavior$getFirstRowCell(tbl, cellIndex) {
///<summary>ヘッダセル取得</summary>
// テーブルの1行目のセルを取得する
var headerCell = tbl.rows(0).cells(cellIndex);
return headerCell;
}
function ComponentGeek$Extender$TableYResizeBehavior$createBar() {
///<summary>変更バー作成</summary>
// リサイズ時に表示する縦横バー作成
// 横幅変更バー作成
elem = $get(this._hBarID);
if (!elem) {
//elem = document.createElement("hr");
elem = document.createElement("span");
elem.id = this._hBarID;
elem.style.position = "absolute";
elem.style.top = "0";
elem.style.left = "0";
elem.style.height = "2";
elem.style.width = "0";
elem.style.background = "silver";
elem.style.borderLeft = "1px solid black";
elem.style.display = "none";
document.body.appendChild(elem);
}
}
function ComponentGeek$Extender$TableYResizeBehavior$clear() {
// プロパティをクリア
var elem = $get(this._hBarID);
if (elem) {
elem.runtimeStyle.display = "none";
}
this._target = null;
this._startY = null;
this._endY = null;
this._yResizing = false;
}
function ComponentGeek$Extender$TableYResizeBehavior$_onMouseDown(e) {
// マウスダウンイベント
// 変更カーソル表示中の場合はリサイズ開始
var hBar = $get(this._hBarID);
if (!hBar) return;
if (!this._header) return;
if (this._yResizeCursorVisible) {
this._target = e.target;
this._startY = e.clientY;
this._yResizing = true;
this._header.setCapture();
var pos = getElementPosition(this._body);
hBar.runtimeStyle.top = e.clientY + document.documentElement.scrollTop;
//hBar.runtimeStyle.left = this._table.parentElement.offsetLeft + document.documentElement.scrollLeft + document.body.offsetLeft;
hBar.runtimeStyle.left = pos.left;
if (this._body.parentElement.offsetWidth < this._body.offsetWidth) {
hBar.runtimeStyle.width = this._body.parentElement.offsetWidth;
} else {
hBar.runtimeStyle.width = this._body.offsetWidth;
}
hBar.runtimeStyle.display = "inline";
}
}
function ComponentGeek$Extender$TableYResizeBehavior$_onMouseMove(e) {
// マウス移動イベント
// マウスが列の左端,下端に近づくとカーソル変更
if (this._yResizing) {
var hBar = $get(this._hBarID);
if (!hBar) return;
// 変更バー移動
if (this._yResizing) {
hBar.runtimeStyle.top = e.clientY + document.documentElement.scrollTop;
document.selection.empty();
}
return;
}
if (e.offsetY >= (e.target.offsetHeight - this._edgeThreshold)) {
this._yResizeCursorVisible = true;
this._header.runtimeStyle.cursor = "s-resize";
} else {
this._yResizeCursorVisible = false;
// 行ヘッダかつ列ヘッダの場合にマウス移動イベントの競合対策
// 水平、垂直方向変更用にマウス変更イベントが重複して発生するため
// 水平方向変更中にカーソルをクリアしないようにする
if (this._header.runtimeStyle.cursor == "e-resize") return;
if (this._header.style.cursor) {
this._header.runtimeStyle.cursor = this._header.style.cursor;
} else {
this._header.runtimeStyle.cursor = "";
}
}
}
function ComponentGeek$Extender$TableYResizeBehavior$_onMouseUp(e) {
// マウスアップイベント
// サイズ変更中の場合は変更を行う
if (this._target == null) return;
this._endY = e.clientY;
if (!this._header) return;
if (this._yResizing && this._startY != null && Math.abs(this._endY - this._startY) > this._resizeThreashold) {
// 縦幅変更中
// 新しい高さ
var newHeight = this._target.offsetHeight + (this._endY - this._startY);
var rowSpan = this._target.rowSpan;
for (var i = 0; i < rowSpan; ++i) {
if (newHeight / rowSpan > this._sizeThreshold) {
newHeight = newHeight / rowSpan;
} else {
newHeight = this._sizeThreshold;
}
var headertr = this._header.rows(this._target.parentElement.rowIndex + i);
headertr.style.height = newHeight;
var bodytr = this._body.rows(this._target.parentElement.rowIndex + i);
bodytr.style.height = newHeight;
}
}
this._header.releaseCapture();
this.clear();
}
ComponentGeek.Extender.TableYResizeBehavior.registerClass('ComponentGeek.Extender.TableYResizeBehavior', Sys.UI.Behavior);
if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
/// 位置指定要素ではない要素の位置を取得する汎用関数
function getElementPosition(elem) {
var offsetTrail = elem;
var offsetLeft = 0;
var offsetTop = 0;
while (offsetTrail) {
offsetLeft += offsetTrail.offsetLeft;
offsetTop += offsetTrail.offsetTop;
offsetTrail = offsetTrail.offsetParent;
}
if (navigator.userAgent.indexOf("Mac") != -1 &&
typeof document.body.leftMargin != 'undefined') {
offsetLeft += document.body.leftMargin;
offsetTop += document.body.topMargin;
}
return { left: offsetLeft, top: offsetTop };
}
1.6 AssemblyInfo.cs の編集
プロジェクトのアセンブリインフォを編集します。変更と追加箇所をのみ記載します。javascript ファイルをダウンロードできるようにリソースとして設定しています。
// アセンブリのバージョン情報は、以下の 4 つの値で構成されています:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// すべての値を指定するか、下のように '*' を使ってリビジョンおよびビルド番号を
// 既定値にすることができます:
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: WebResource("ComponentGeek.Extender.FreezeHeaderBehavior.js", "text/javascript")]
[assembly: WebResource("ComponentGeek.Extender.TableResizeBehavior.js", "text/javascript")]
[assembly: ScriptResource("ComponentGeek.Extender.FreezeHeaderBehavior.js",
"ComponentGeek.Extender.FreezeHeaderBehavior", "ComponentGeek.Extender.Resource")]
[assembly: ScriptResource("ComponentGeek.Extender.TableResizeBehavior.js",
"ComponentGeek.Extender.TableResizeBehavior", "ComponentGeek.Extender.Resource")]
2. ソリューションのダウンロード
Visual Studio 2008 で作成した Extender のソリューションを公開しますので、よかったら使ってみてください。プログラムはオープンソースです。利用する場合はご連絡をいただけるとうれしいです。
3. まとめ
今回の説明は以上です。といってもあまり解説していませんが。ソリューションをダウンロードできるようにしているので、動作確認やASP.NET AJAX 1.0 用に書き換えて使用できると思います。
指摘点等あります場合はご連絡ください。
最近、ASP.NETでの仕事が増えてきて、てんてこ舞なPGをしております。
一人ボッチです。
「基本Webの画面はシンプルで、わかりやすく」
を念頭に開発を進めてきましたが、Accessなど現行のWeb化となると、
「従来どおり」という声も多く。
スクロールバーの表示に苦戦しておりましたところ。
こちらのサイトに行き着きました。
解説もわかりやすく、ASP.NETを初めてまもない私でも、
思ったとおりの動きを実現できて、感謝、感謝です。
今後も参考にさせていただきますので、よろしくお願いします。
ただライセンスの表示がないようですので、可能でしたら明記していただきますと使いやすいです。
よろしくお願いします。
質問させていただきます。
hikaruです。
横スクロールが発生するのが前提の場合、
ヘッダーと明細がずれてしまうのは改善できないでしょうか?
何卒よろしくお願いします。