javascriptでTableの行、列の幅を変更するjavascriptを縦横幅を動的に変更可能なテーブルをjavascriptで作ってみるで作成しました。今度はExtender化してみます。dllもダウンロードできるようにしましたので、ためしに使ってくれるとうれしいです。

動作確認環境は次の通り

  • 確認したブラウザIE 7.0
  • 開発環境:Visual Studio 2008 Professional (英語版)
  • .NETのバージョン3.5
今回,Extenderを作成するのに参考にさせてもらったページ
Web サーバー コントロールへの ASP.NET AJAX クライアント動作の追加
http://msdn.microsoft.com/ja-jp/library/bb386403.aspx 
MS AJAX LibでAJAX対応コントロールを開発しよう(後編) 
http://www.atmarkit.co.jp/fdotnet/ajaxlib/ajaxlib04/ajaxlib04_01.html

1. プログラムの作成

空のソリューションをTableResizeExtenderという名前で作成し、プロジェクトをプロジェクトのテンプレートにASP.NET AJAX Server Control Extenderを選択して、ComponentGeek.TableResizeExtenderという名前で作成しました。

既定で作成されるファイル名をjavascriptファイルをTableResizeBehavior.js,リソースファイルをTableResizeBehavior.resx,ExtenderをTableResizeExtender.csにリネームして、ファイルを編集します。

TableResizeExtender.csを次のように編集します。リサイズ中のテーブルにスタイルシートを設定できるようにResizingCssClassというプロパティを追加しています。

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml.Linq;

namespace ComponentGeek.TableResizeExtender
{
    [TargetControlType(typeof(Control))]
    public class TableResizeExtender : ExtenderControl
    {
        public TableResizeExtender()
        {
        }
        public string ResizingCssClass { get; set; }

        protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
        {
            ScriptBehaviorDescriptor descriptor;
            descriptor = new ScriptBehaviorDescriptor("ComponentGeek.TableResizeBehavior", targetControl.ClientID);
            if (!string.IsNullOrEmpty(this.ResizingCssClass))
            {
                descriptor.AddProperty("resizingCssClass", this.ResizingCssClass);
            }
            return new ScriptBehaviorDescriptor[] { descriptor };
        }

        protected override IEnumerable<ScriptReference> GetScriptReferences()
        {
            ScriptReference reference = new ScriptReference("ComponentGeek.TableResizeExtender.TableResizeBehavior.js", this.GetType().Assembly.FullName);

            return new ScriptReference[] { reference };
        }
    }
}

TableResizeBehavior.jsは次のように編集します。中身自体は縦横幅を動的に変更可能なテーブルをjavascriptで作ってみるで作成したないようとほぼ同じでが、対象table要素はIDではなく、要素として保持するように変更しています。また、イベントへの登録方法もMicrosoft AJAXライクな方法に変更しています。今回は_resizingCssClassというメンバを追加して、リサイズ実行時にスタイルシートを設定できるようにしています。

/// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace("ComponentGeek");

ComponentGeek.TableResizeBehavior = function(element){
    ComponentGeek.TableResizeBehavior.initializeBase(this, [element]);
    
    this._table;  // 対象のテーブルID
    this._target;   // サイズ変更対象セル
    this._startX;   // 幅変更開始X座標
    this._endX;     // 幅変更終了X座標
    this._startY;   // 高さ変更開始Y座標
    this._endY;     // 高さ変更開始X座標
    this._xResizing = false;     // 幅変更中フラグ
    this._yResizing = false;     // 高さ変更中フラグ
    this._resizeThreashold = 8;  // 最小変更幅
    this._edgeThreshold = 8;     // 幅,高さ変更カーソル表示閾値
    this._sizeThreshold = 20;    // 最小列,幅高さ
    this._vBarID = "vBarID";     // 変更時に表示する縦棒のID
    this._hBarID = "hBarID";     // 変更時に表示する横棒のID
    this._xResizeCursorVisible = false;  // 幅の変更カーソル表示フラグ
    this._yResizeCursorVisible = false;  // 高さの変更カーソル表示フラグ
    this._resizingCssClass = "";  // サイズ変更時のCss
    this._originalCssClass = "";
}

ComponentGeek.TableResizeBehavior.prototype = {
    initialize     : ComponentGeek$TableResizeBehavior$initialize,
    dispose        : ComponentGeek$TableResizeBehavior$dispose,
    _onMouseDown   : ComponentGeek$TableResizeBehavior$_onMouseDown,
    _onMouseUp     : ComponentGeek$TableResizeBehavior$_onMouseUp,
    _onMouseMove   : ComponentGeek$TableResizeBehavior$_onMouseMove,
    createBar      : ComponentGeek$TableResizeBehavior$createBar,
    clear          : ComponentGeek$TableResizeBehavior$clear,
    getFirstColumn : ComponentGeek$TableResizeBehavior$getFirstColumn,
    get_resizingCssClass : ComponentGeek$TableResizeBehavior$get_resizingCssClass,
    set_resizingCssClass : ComponentGeek$TableResizeBehavior$set_resizingCssClass
}

function ComponentGeek$TableResizeBehavior$initialize(){
    ComponentGeek.TableResizeBehavior.callBaseMethod(this, 'initialize');

    // TABLEタグのみサポート
    if(this.get_element().tagName == "TABLE") this._table = this.get_element();
    else{
        var tables = this.get_element().getElementsByTagName("TABLE");
        // 一番最初のテーブルを対象とする
        if(tables.length > 0) this._table = tables[0];
        else return;
    }
    this._originalCssClass = this._table.className;
    
    $addHandler(this._table, "mousedown", Function.createDelegate(this, this._onMouseDown));
    $addHandler(this._table, "mouseup", Function.createDelegate(this, this._onMouseUp));
    $addHandler(this._table, "mousemove", Function.createDelegate(this, this._onMouseMove));
    
    this.createBar();
}
function ComponentGeek$TableResizeBehavior$dispose(){
    $clearHandlers(this.get_element());
    ComponentGeek.TableResizeBehavior.callBaseMethod(this, 'dispose');
}

function ComponentGeek$TableResizeBehavior$get_resizingCssClass(){
    return this._resizingCssClass;
}
function ComponentGeek$TableResizeBehavior$set_resizingCssClass(value){
    if(this._resizingCssClass !== value)
    {
        this._resizingCssClass = value;
        this.raisePropertyChanged('resizingCssClass');
    }
}

function ComponentGeek$TableResizeBehavior$createBar(){
        /// <summary>イベント登録メソッド</summary>
    if(this._table){
        $addHandler(this._table, "mousedown", function(e){ self.MouseDown(e);});
        $addHandler(this._table, "mouseup", function(e){ self.MouseUp(e);});
        $addHandler(this._table, "mousemove", function(e){ self.MouseMove(e);});
        this._table.runtimeStyle.tableLayout = "fixed"; // リサイズ可能にする
    }
    this.CreateBar();
}

function ComponentGeek$TableResizeBehavior$getFirstColumn(tbl, cellIndex){
    ///<summary>ヘッダセル取得</summary>
    // テーブルの1行目のセルを取得する
    var headerCell = tbl.rows(0).cells(cellIndex);
    return headerCell;
}

function  ComponentGeek$TableResizeBehavior$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);
    }
    // 縦幅変更用バー作成
    elem = $get(this._hBarID);
    if(!elem){
        elem = document.createElement("hr");
        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$TableResizeBehavior$clear(){
    // プロパティをクリア
    var elem = $get(this._vBarID);
    if(elem){
        elem.runtimeStyle.display = "none";
    }
    elem = $get(this._hBarID);
    if(elem){
        elem.runtimeStyle.display = "none";
    }
    this._target = null;
    this._startX = null;
    this._endX   = null;
    this._startY = null;
    this._endY   = null;
    this._xResizing = false;
    this._yResizing = false;
}

function ComponentGeek$TableResizeBehavior$_onMouseDown(e){
    // マウスダウンイベント
    // 変更カーソル表示中の場合はリサイズ開始
    var vBar = $get(this._vBarID);
    if(!vBar) return;

    var hBar = $get(this._hBarID);
    if(!hBar) return;

    if(!this._table) return;
    
    if(this._xResizeCursorVisible){
        this._target = e.target;
        this._startX = e.clientX;
        this._xResizing = true;
        
        this._table.setCapture();
        
        var pos = getElementPosition(this._table);
        
        vBar.runtimeStyle.top = pos.top;
        vBar.runtimeStyle.left = e.clientX + document.documentElement.scrollLeft;
        if(this._table.parentElement.offsetHeight < this._table.offsetHeight){
            vBar.runtimeStyle.height = this._table.parentElement.offsetHeight;
        }else{
            vBar.runtimeStyle.height = this._table.offsetHeight;
        }
        vBar.runtimeStyle.display = "inline";

        this._table.className = this._resizingCssClass;        
    }else if(this._yResizeCursorVisible){
        this._target = e.target;
        this._startY = e.clientY;
        this._yResizing = true;
        
        this._table.setCapture();

        var pos = getElementPosition(this._table);

        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._table.parentElement.offsetWidth < this._table.offsetWidth){
            hBar.runtimeStyle.width = this._table.parentElement.offsetWidth;
        }else{
            hBar.runtimeStyle.width = this._table.offsetWidth;
        }
        hBar.runtimeStyle.display = "inline";

        this._table.className = this._resizingCssClass;        
    }
}
 
function ComponentGeek$TableResizeBehavior$_onMouseMove(e){
    // マウス移動イベント
    // マウスが列の左端,下端に近づくとカーソル変更

    if(this._xResizing || this._yResizing){
        var vBar = $get(this._vBarID);
        if(!vBar) return;
        
        var hBar = $get(this._hBarID);
        if(!hBar) return;
        
        // 変更バー移動
        if(this._xResizing){
            vBar.runtimeStyle.left = e.clientX +  document.documentElement.scrollLeft;
            document.selection.empty();
        }else if(this._yResizing){
            hBar.runtimeStyle.top = e.clientY + document.documentElement.scrollTop;
            document.selection.empty();
        }
        return;
     }
    if(e.offsetX >= (e.target.offsetWidth - this._edgeThreshold)){
        this._xResizeCursorVisible = true;
        e.target.runtimeStyle.cursor = "e-resize"; 
    }else if(e.offsetY >= (e.target.offsetHeight - this._edgeThreshold)){
        this._yResizeCursorVisible = true;
        e.target.runtimeStyle.cursor = "s-resize"; 
    }else{
        this._xResizeCursorVisible = false;
        this._yResizeCursorVisible = false;
        if(e.target.style.cursor){
            e.target.runtimeStyle.cursor = e.target.style.cursor;
        }else{
            e.target.runtimeStyle.cursor = "";
        }
    }
}
 function ComponentGeek$TableResizeBehavior$_onMouseUp(e){
    // マウスアップイベント
    // サイズ変更中の場合は変更を行う
    if(this._target == null) return;
    this._endX = e.clientX;
    this._endY = e.clientY;
    
    if(!this._table) return;

    if(this._xResizing){
        // 幅変更中
        if(this._startX == null) return;
        // 新しい幅
        var newWidth = this._target.offsetWidth + (this._endX - this._startX);
        // 閾値以上の場合はサイズを適用,そうでない場合、最小幅設定
        if(newWidth > this._sizeThreshold){
            // ヘッダセル取得
            var head = this.getFirstColumn(this._table, this._target.cellIndex); 
            if(head) head.style.width = newWidth;
        }else{
            // ヘッダセル取得
            var head = this.getFirstColumn(this._table, this._target.cellIndex);
            if(head) head.style.width = this._sizeThreshold;
        }
    }else if(this._yResizing){
        // 縦幅変更中
        if(this._startY == null) return;
        // 新しい高さ
        var newHeight = this._target.offsetHeight + (this._endY - this._startY); 
        var rowSpan = this._target.rowSpan;
        for(var i = 0;i<rowSpan;++i){
            var row = this._table.rows(this._target.parentElement.rowIndex + i);
            for(var j=0;j<row.cells.length;++j){
                if((newHeight/rowSpan) > this._sizeThreshold){
                    row.cells(j).style.height = newHeight/rowSpan;
                }else{
                    row.cells(j).style.height =  this._sizeThreshold;
                }
            }
        }
    }

    this._table.releaseCapture();    
    this.clear();
    this._table.className = this._originalCssClass;
 }

ComponentGeek.TableResizeBehavior.registerClass('ComponentGeek.TableResizeBehavior', 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};
}

 TableResizeBehavior.resxは編集対象ではないので、そのままにして、最後に、AssemblyInfo.cs(プロジェクトのPropertiesを展開すると表示されます)の末尾に既定で作成される、WebResourceアトリビュートを編集して完了です。

[assembly: WebResource("ComponentGeek.TableResizeExtender.TableResizeBehavior.js", "text/javascript")]
[assembly: ScriptResource("ComponentGeek.TableResizeExtender.TableResizeBehavior.js",
   "ComponentGeek.TableResizeExtender.TableResizeBehavior", "ComponentGeek.TableResizeExtender.Resource")]

ビルドしてエラーが発生しなければOKです。

2. Extenderを使う

WebSiteプロジェクトを作成して、作成したExtenderプロジェクトを参照に追加すれば、aspxファイル編集時にToolboxにTableResizeExtenderが表示されるので、それをTableやGridViewに付与すればOKです。以下に作成例(Default.aspx)を示します。例ではGridViewとTableに対してTableResizeExtenderを設定しています。

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register Assembly="ComponentGeek.TableResizeExtender" Namespace="ComponentGeek.TableResizeExtender"
    TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
    <style type="text/css">
        .Resizing
        {
            background-color: Gray;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1" />
    <div>
        <asp:Panel runat="server" ID="Panel1">
            <asp:Table ID="Table2" runat="server" BorderWidth="2px">
                <asp:TableRow ID="TableRow1" runat="server">
                    <asp:TableCell ID="TableCell1" runat="server">ヘッダ1</asp:TableCell>
                    <asp:TableCell ID="TableCell2" runat="server">ヘッダ2</asp:TableCell>
                    <asp:TableCell ID="TableCell3" runat="server">ヘッダ3</asp:TableCell>
                </asp:TableRow>
                <asp:TableRow ID="TableRow2" runat="server">
                    <asp:TableCell ID="TableCell4" runat="server">セル1</asp:TableCell>
                    <asp:TableCell ID="TableCell5" runat="server">セル2</asp:TableCell>
                    <asp:TableCell ID="TableCell6" runat="server">セル3</asp:TableCell>
                </asp:TableRow>
                <asp:TableRow ID="TableRow3" runat="server">
                    <asp:TableCell ID="TableCell7" runat="server">セル4</asp:TableCell>
                    <asp:TableCell ID="TableCell8" runat="server">セル5</asp:TableCell>
                    <asp:TableCell ID="TableCell9" runat="server">セル6</asp:TableCell>
                </asp:TableRow>
            </asp:Table>
        </asp:Panel>
        <cc1:TableResizeExtender ID="TableResizeExtender2" runat="server" TargetControlID="Table2" />
        <%-- <cc1:TableResizeExtender ID="TableResizeExtender2" runat="server" TargetControlID="Panel1" /> -->
    </div>
    <asp:GridView ID="GridView1" runat="server">
    </asp:GridView>
    <cc1:TableResizeExtender ID="TableResizeExtender1" runat="server" TargetControlID="GridView1"
        ResizingCssClass="Resizing" />
    </form>
</body>
</html

 説明は以上です。問題点、指摘点、疑問点とうございましたら、連絡ください。

3. おみやげ

ソリューションの内容は掲載したまんまなのでdllだけダウンロードできます。こちらからダウンロードして下さいませ。