WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

GridView と thead, tbody, tfoot

by WebSurfer 2012年12月16日 20:13

GridView も html にレンダリングされると table, tr, th, td などの要素になりますが、thead, tbody, tfoot 要素はデフォルトではレンダリングされません。今回は GridView で thead, tbody, tfoot 要素を追加する方法を書きます。

GridView は、内部で Table コントロール を利用しているようです。

従って、Table コントロール内の TableRow オブジェクトの TableSection プロパティTableRowSection 列挙体 のTableHeader, TableBody, TableFooter のいずれかに設定してやれば thead, tbody, tfoot 要素が追加されます。

以下のコードのような感じです。

実際に動かして試すことができるよう 実験室 にアップしました。興味のある方は試してみてください。html ソースを見れば thead, tbody, tfoot 要素が追加されているのが分かると思います。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  // データソース用の DataTable を作成
  protected DataTable CreateDataTable()
  {
    DataTable dt = new DataTable();
    DataRow dr;

    dt.Columns.Add(new DataColumn("ID", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Name", typeof(string)));
    dt.Columns.Add(new DataColumn("Price", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Qty", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Amount", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Note", typeof(string)));

    for (int i = 0; i < 10; i++)
    {
      dr = dt.NewRow();
      dr["ID"] = i;
      dr["Name"] = "Name_" + i.ToString();
      dr["Price"] = 123000 * (i + 1);
      dr["Qty"] = (i + 1) * 20;
      dr["Amount"] = 123000 * (i + 1) * (i + 1);
      dr["Note"] = "Note_" + i.ToString();
      dt.Rows.Add(dr);
    }
    return dt;
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    if (!IsPostBack)
    {
      GridView1.DataSource = CreateDataTable();
      GridView1.DataBind();
    }
  }

  protected void GridView1_RowCreated(
    object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.Header)
    {
      e.Row.TableSection = 
        System.Web.UI.WebControls.TableRowSection.TableHeader;
    }
    else if (e.Row.RowType == DataControlRowType.DataRow)
    {
      e.Row.TableSection = 
        System.Web.UI.WebControls.TableRowSection.TableBody;
    }
    else if (e.Row.RowType == DataControlRowType.Footer)
    {
      e.Row.TableSection = 
        System.Web.UI.WebControls.TableRowSection.TableFooter;
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:GridView ID="GridView1" 
      runat="server" 
      OnRowCreated="GridView1_RowCreated" 
      ShowFooter="True" 
      EnableViewState="False">
    </asp:GridView>
  </div>
  </form>
</body>
</html>

Tags: , , ,

ASP.NET

defer 属性つき script 定義と IE の問題

by WebSurfer 2012年12月3日 21:37

外部スクリプトファイルを定義する script 要素 に defer="defer" 属性を追加すると、あるケースで、internet explorer (IE) がそのスクリプトファイルを解析できなくなるという問題の紹介です。

defer 属性つき script 定義と IE の問題

「あるケース」というのは、div 要素などの innerHTML を書き換えることです。自分でそのようなコートを書かなくても、例えば、SWFObject を使って Flash を埋め込む場合に innerHTML の書き換えが行われます。

ただし、html コードを書く順番が問題で、defer 属性を追加した script タグが出現した後、innerHTML を書き換える場合に限ります。順番が反対の場合は問題は起こりません。

確証がないのではっきりしたことは言えませんが、自分が試した限りでは、スクリプトの取得に時間がかかる(サーバーの応答が遅い)と問題が発生する確率が高いようです。ブラウザの解析の速度も関係があるようで、IE6 であればほぼ 100% 問題が発生するのに対し、IE8 は微妙なタイミングで問題が発生したりしなかったりします。

検証のため、スクリプトをダウンロードする HTTP ハンドラを作って、Thread.Sleep メソッドを使って応答に時間がかかるようにしてみました。

<%@ WebHandler Language="C#" Class="JavaScriptHandler" %>

using System;
using System.Web;
using System.Text;
using System.Threading;
using System.Diagnostics;

public class JavaScriptHandler : IHttpHandler
{
  public void ProcessRequest (HttpContext context)
  {
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();

    StringBuilder sb = new StringBuilder();
    sb.Append("DateTime accessed: " 
      + DateTime.Now.ToString("d MMM yyyy HH:mm:ss zzz",
        System.Globalization.DateTimeFormatInfo.InvariantInfo)
      + ", ");
       
    string delay = context.Request.QueryString["delay"];
    int time;
    bool result = Int32.TryParse(delay, out time);
    if (result)
    {
      Thread.Sleep(time);
      sb.Append(String.Format(
        "delay time set: {0} ms", time) + ", ");
    }
    else
    {
      sb.Append("delay time set: none, ");
    }
       
    context.Response.ContentType = "text/javascript";
    context.Response.Cache.VaryByHeaders["Accept-Encoding"] = 
      true;
    context.Response.Cache.SetCacheability(
      HttpCacheability.NoCache);
    context.Response.Cache.SetExpires(
      DateTime.Now.ToUniversalTime());
    context.Response.Cache.SetMaxAge(
      new TimeSpan(0, 0, 0, 0));
    context.Response.AppendHeader("Pragma", "no-cache");

    stopWatch.Stop();
    TimeSpan ts = stopWatch.Elapsed;
    sb.Append(String.Format(
      "TimeSpan measured: {0:000} ms", ts.Milliseconds));
    string script = sb.ToString();
    script = "var msg = '" + script + "'";       
    context.Response.Write(script);
  }
 
  public bool IsReusable
  {
    get
    {
      return false;
    }
  }
}

上記の HTTP ハンドラを呼び出す際、例えば、クエリ文字列を delay=200 とすると、リクエストを受けてから約 200ms 後にアクセスした時間、クエリ文字列の設定、実際に計った時間をスクリプトとして返します。

以下のような簡単な HTML コードで試すことができます。たぶん delay はもっと少なくても問題が再現すると思います。実際に動かして試すことができるよう 実験室 にアップしました。興味のある方は試してみてください。

<!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>
    <title></title>
    <script type="text/javascript" 
        src="JavaScriptHandler.ashx?delay=200" defer="defer">
    </script>
    <script type="text/javascript">
    //<![CDATA[
        function write(id){
            document.getElementById(id).innerHTML = 
                "<h1>innerHTML changed!<\/h1>";
        }
        
        function ScriptTest() {
            var x = msg;
            alert(x);
        }
    //]]>
    </script>
</head>
<body>
    <div id="myContent">
        <h1>This will be replaced by write method</h1>
    </div>
    <script type="text/javascript">
    //<![CDATA[
        write("myContent");
    //]]>
    </script>
    <br />
    <input type="button" value="Script Test" 
        id="button1" onclick="javascript:ScriptTest();" />   
</body>
</html>

自分が検証した限りでは、IE6-9 で同じ問題が出ました(IE10 は未検証)。対応策は、(1) defer="defer" 属性を使用しない、または、(2) innnerHTML を書き換えた後で defer="defer" 属性付の script タグを読むよう順序を変更する、のいずれかしかなさそうです。

Tags: , ,

JavaScript

キャプチャリングとバブリング

by WebSurfer 2012年12月1日 00:14

JavaScript や jQuery を使ったプログラミングで、DOM イベントのバブリングという言葉を耳にします。本などを読んでもピンと来なかったので、理解するためにサンプルを作って動かしてみました。あまり面白くないかもしれませんが、せっかく作ったので書いておきます。

イベントのキャプチャリングとバブリング

上の画像のサンプルは、実際に動かして試すことができるよう 実験室 にアップしました。興味のある方は試してみてください。ソースコードはこの記事の下の方に記載しています。

DOM イベントは、イベントの原因となったオブジェクトで発生するだけでなく、キャプチャリングとバブリングというイベントの伝播があります。ここが .NET Framework のイベントとは異なっていて、自分が理解に苦しんだところです。

どこかの DOM オブジェクトでイベントが発生すると、window オブジェクト(ブラウザによっては document オブジェクト)とイベントが起きたオブジェクトの間を、DOM ツリーの親子関係を順にたぐって、イベントが伝播していきます。

伝播は 3 つのフェーズに分かれており、Capturing Phase(捕捉フェーズ)⇒ Target Phase(対象フェーズ)⇒ Bubbling Phase(浮上フェーズ)という順番になります。それぞれのフェーズの説明は以下の通りです。

  1. Capturing Phase では、window ⇒ document ⇒ その中の親 ⇒ 子 ⇒ 孫 ⇒ ひ孫 ・・・といった具合に、親子関係を親側から順にたぐって、イベントが起きたオブジェクトの親まで(「親まで」という点に注意)の各オブジェクトにイベントが送信されていきます。
  2. Target Phase では、イベントの原因となったオブジェクトにイベントが送信されます。
  3. Bubbling Phase では、イベントを発生させたオブジェクトの親から(「親から」という点に注意)順に浮上していき、window に達するまで各オブジェクトに順にイベントが送信されていきます。

window からイベントを発生させたオブジェクトの間の、親子関係にある任意のオブジェクトにリスナーを登録しておけば、そのオブジェクトにイベントが送信されたタイミングで必要な処置ができます。

オブジェクトにリスナーを登録する方法は、(1) 当該オブジェクトの addEventListener メソッド を呼び出す、(2) HTML 要素の属性を利用する、(3) 当該オブジェクトのプロパティを利用する、の 3 つがあります。

注意しなければならないのは、上記の (1) 以外の方法では、Capturing Phase でイベントを捕捉できないことです。

さらに注意しなければならないのは、IE8 以前では addEventListener メソッドはサポートされていないことです。代わりに attachEvent メソッドが使えますが、これは Capturing Phase でイベントを捕捉できません。(attachEvent メソッドの詳細は後述します)

IE9 は DOM Level 3 Events をサポートしているそうなので(詳しくは MSDN の IEBlog DOM Level 3 Events support in IE9 を参照)、addEventListener メソッド を使用可能です。

addEventListener メソッドを利用してリスナーを登録し、Capturing Phase、Target Phase、Bubbling Phase でイベントを補足するサンプルを書いてみました。そのソースコードをアップしておきます。

なお、Target Phase では、addEventListener メソッドで第 3 引数 useCapture を false に指定して登録したリスナーのみが呼び出されることになっているそうですが、自分が試した限りでは、useCapture を true に指定して登録したリスナーまでもが呼び出されてしまいました。

検証に使ったブラウザは、IE9、Firefox 17.0, Chrome 23.0.1271.95 m, Opera 12.02, Safari 5.1.7 で、すべて同じ結果になりました。

<!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>
    <title>Event Capturing and Bubbling</title>
    <style type="text/css">
        #div1 { border: 1px solid black; width: 200px; 
            height: 100px; }
        #table1 { border: 1px solid red; width: 150px; }
        td { border: 1px solid green; }
        #span1 { background-color: yellow; }
    </style>
    <script type="text/javascript">
    //<![CDATA[

        // IE9, Mozzilla 用リスナー。
        function listener1(event) {
            // イベントが発生すると event オブジェクトが生成
            // され、その参照が第一引数に渡される。それから
            // target プロパティでイベントを発生させたオブジ
            // ェクトを、eventPhase プロパティでフェーズ情
            // 報を取得できる。this はアタッチしたオブジェク
            // トへの参照となる。
            var e = document.getElementById("result");
            var phase = "";
            if (event.eventPhase === 1) {
                phase = "capturing phase";
            } else if (event.eventPhase === 2) {
                phase = "target phase";
            } else if (event.eventPhase === 3) {
                phase = "bubbling phase";
            }
            var str = "fired by: " + event.target.id + 
                      ", listened at: " + this.id + 
                      ", during " + phase + "<br />";
            e.innerHTML += str;
        }

        // IE6-8 用リスナー。
        function listener3(element) {
            // attachEvent を使うと、this が参照するオブジェ
            // クトは window になってしまい、アタッチしたオ
            // ブジェクトへの参照は取得できない。止むを得な
            // いので、オブジェクトへの参照は引数として渡す。
            // Mozilla 系ではリスナーの第一引数には event オ
            // ブジェクトへの参照が渡されるので注意。
            var e = document.getElementById("result");
            var str = 
                "fired by: " + window.event.srcElement.id +
                ", listened at: " + element.id + "<br />";
            e.innerHTML += str;
        }

        // Bubbling Phase のリスナーをアタッチ。
        function attachForBubbling(element) {
            var e = document.getElementById("result");
            if (element.addEventListener) {
                // Bubbling Phase のリスナーをアタッチするに
                // は第三引数を false に設定する。
                element.addEventListener('click', listener1, 
                    false);
                e.innerHTML += "listener1 attached to " +
                    element.id +
                    " by addEventListener" + 
                    " w/ useCapture=false<br />";
            } else if (element.attachEvent) {
                // アタッチするオブジェクトへの参照をリスナ
                // ーの引数に渡すため、以下のように匿名関数
                // を使う。ただし、匿名関数を使うとデタッチ
                // できなくなることに注意。
                element.attachEvent('onclick', 
                    function () { listener3(element) });
                e.innerHTML += "listener3 attached to " + 
                    element.id + " by attachEvent<br />";
            }
        }

        // Capturing Phase のリスナーをアタッチ。
        // (IE9, Mozilla のみ) 
        function attachForCapturing(element) {
            var e = document.getElementById("result");
            if (element.addEventListener) {
                // Capturing Phase 用リスナーをアタッチするに
                // は第三引数を true に設定する。
                element.addEventListener('click', listener1, 
                    true);
                e.innerHTML += "listener1 attached to " +
                    element.id +
                    " by addEventListener" + 
                    " w/ useCapture=true<br />";
            }
        }

        // リスナーを各オブジェクトにアタッチ。
        window.onload = function () {
            var element = document.getElementById("div1");
            attachForBubbling(element);
            attachForCapturing(element);

            element = document.getElementById("table1");
            attachForBubbling(element);
            attachForCapturing(element);

            element = document.getElementById("td1");
            attachForBubbling(element);
            attachForCapturing(element);

            element = document.getElementById("span1");
            attachForBubbling(element);
            attachForCapturing(element);
        }
    //]]>
    </script>    
</head>
<body>
    <div id="div1">
        <table id="table1">
            <tr>
                <td id="td1">
                    <span id="span1">span1</span>
                </td>
            </tr>
            <tr>
                <td id="td2">
                    <span id="span2">span2</span>
                </td>
            </tr>
        </table>
    </div>
    <input type="button" value="Clear Results" 
        onclick="javascript:result.innerHTML = '';"/>
    <br />
    <div id="result"></div>
</body>
</html>

上でも述べましたように、IE6-8 では、addEventListener メソッドはサポートされていませんが、代わりに attachEvent メソッド がリスナーを登録するのに使えます。

IE6-8 をサポートするためには以下のようにします。上のサンプルコードでもこのようにして IE6-8 でリスナーを登録しています。

if (element.addEventListener) {
    element.addEventListener('click', listener, false);
} else if (element.attachEvent){
    element.attachEvent('onclick', listener);
}

attachEvent メソッドには以下のデメリットがあるので注意してください。

  1. リスナー内の this で取得できるのが、window オブジェクトへの参照になる。(addEventListener メソッドの場合はリスナーをアタッチしたオブジェクトへの参照になる)
  2. Capturing Phase ではイベントを補足できない。(Bubbling Phase と Target Phase では補足可能)

上記 1 の問題に対応するため、サンプルコードでは、リスナーの引数にアタッチするオブジェクトへの参照を渡しています。さらに、attachEvent メソッドの引数に匿名関数を使って、リスナーを登録しています。

Tags: , , ,

JavaScript

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar