テーブル行ヘッダ列ヘッダ固定化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です。
横スクロールが発生するのが前提の場合、
ヘッダーと明細がずれてしまうのは改善できないでしょうか?
何卒よろしくお願いします。