WebSurfer's Home

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

ModalPopup と Firefox の問題

by WebSurfer 2014年11月6日 16:46

Ajax Control Toolkit の ModalPopup を Firefox で表示した場合、一旦表示して消した ModalPopup が、別ページに遷移する前に一瞬表示されてしまう問題とその対処方法の紹介です。

遷移前に一瞬表示された ModalPopup

上の画像は、[Show ModalPopup]ボタンをクリック ⇒ ModalPopup が表示される ⇒ ModalPopup 上の[Cancel]ボタンクリック ⇒ ModalPopup が非表示になる ⇒[ブログのページへ]のハイパーリンクをクリックした時、遷移先のページからの応答を待っている時のものです。応答を待っている間、本来表示されてはいけない ModalPopup が表示されてしまっています。

この問題は、ブラウザが Firefox の場合で(現時点での最新バージョン 33.0.2 で確認)、かつ、ModalPopup の DropShadow プロパティが true に設定された場合に発生します。

そのメカニズムは以下の通りです。

DropShadow="true" となっていると、ModalPopup の PopupControlID プロパティに設定した Panel の外側が div 要素で囲われ、その style 属性に "display: none;" が追加 / 削除さることによって ModalPopup の非表示 / 表示が切り替えられます。

たとえ Panel の Style プロパティに "display: none;" を設定しておいても、一旦 ModalPopup を表示するとその設定は削除され、その後 ModalPopup を非表示にしても Panel の "display: none;" は復活しません。(その外側の div 要素に動的に追加される "display: none;" によって非表示になる)

Firefox の場合、ハイパーリンクがクリックされて、遷移先のページを要求に行くとき、Panel の外側の div 要素そのものがなくなるか、その div 要素に設定されている "display: none;" の効果がなくなり、要求を出してから応答が帰ってくるまで Panel が表示されてしまうという感じです。(推測です。実際にどうなっているのかは不明)

ハイパーリンクでなくリダイレクトで遷移しても同じで、GET 要求を出してから応答が戻ってくるまで Panel は表示されてしまいます。

なお、この問題は DropShadow="false" になっていると起こりません。なぜなら、その場合は Panel は div 要素で囲われることはなく、Panel に直接 "display: none;" が追加 / 削除され ModalPopup の非表示 / 表示が切り替えられるからです。

対症療法的ですが、ハイパーリンクの onclick イベントで、ModalPopup (Panel) の style 属性に "display:none;" を設定することでこの問題を回避できます。

そのサンプルコードを以下に書いておきます。また、実際に動かして試すことができるよう 実験室 にアップしました。興味のある方は試してみてください。

<%@ Page Language="C#" %>

<%@ Register Assembly="AjaxControlToolkit" 
    Namespace="AjaxControlToolkit" TagPrefix="asp" %>

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

<script runat="server">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>ModalPopup と Firefox</title>
  <style type="text/css">       
    .modalBackground {
      background-color: Gray;
      filter: alpha(opacity=70);
      opacity: 0.7;
    }

    .modalPopup {
      height: 100px;
      width: 250px;
      background-color: White;
      border: solid 2px black;
    }
  </style>
  <script type="text/javascript">
  //<![CDATA[
    function setDisplayNone() {
      var ele = document.getElementById('<%=Panel1.ClientID%>');
      ele.setAttribute('style', 'display: none;');
    }
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ToolkitScriptManager ID="ToolkitScriptManager1" 
    runat="server">
  </asp:ToolkitScriptManager>

  <h1>ModalPopup と Firefox</h1>

  <asp:Button ID="Button1" 
    runat="server" 
    Text="Show ModalPopup"/>    
  <br />
  <asp:HyperLink ID="HyperLink1" 
    runat="server" 
    NavigateUrl="~/Default.aspx">
    ブログのページへ
  </asp:HyperLink>
  <br />
  <asp:HyperLink ID="HyperLink2" 
    runat="server" 
    NavigateUrl="~/Default.aspx"
    onclick="setDisplayNone();">
    ブログのページへ(display:none 設定)
  </asp:HyperLink>

  <asp:ModalPopupExtender ID="ModalPopupExtender1" 
    runat="server"
    TargetControlID="Button1" 
    PopupControlID="Panel1"        
    BackgroundCssClass="modalBackground" 
    DropShadow="True"
    CancelControlID="CancelButton">
  </asp:ModalPopupExtender>

  <asp:Panel ID="Panel1" 
    runat="server"  
    Style="display: none" 
    CssClass="modalPopup">        
    <p style="text-align: center;">ModalPopup</p>
    <p style="text-align: center;">
      <asp:Button ID="CancelButton" 
        runat="server" Text="Cancel" />
    </p>
  </asp:Panel>
  </form>
</body>
</html>

Tags: , ,

AJAX

GridView と jQuery UI DatePicker

by WebSurfer 2014年10月26日 15:47

ASP.NET の GridView で jQuery UI の Datepicker を使用する例の紹介です。

GridView に jQuery UI の Datepicker

GridView と組み合わせるなら Ajax Control Toolkit の CalendarExtender を使う方が簡単で、かつ 日付表示の日本語対応 もやりやすいと思いますが、外観の統一を図るなどの理由で jQuery UI の Datapicker を使用したい場合があると思います。

その場合、どのように GridView 内で Datapicker を表示する TextBox を探して、その jQuery オブジェクトを取得し、それに datepicker() メソッドを適用するかを書きます。

通常、GridView では編集モードになった行に TextBox を表示してユーザー入力を受け付けるようにします。その時に datepicker() メソッドを当該 TextBox に適用すればいいわけです。

編集モードになった行は GridView.EditIndex プロパティで判定できますが、Page.Load のタイミングでは早すぎて判定できません。

GridView.RowEditing のタイミングでは、ユーザー入力用の TextBox が生成されていないので FindControl では取得できず、このタイミングでもダメです。

GridView.RowCreated のタイミングでは、最終的な TextBox.ClientID が取得できないので、これまたダメです。(このタイミングでは TextBox.ClientID にはまだ名前つきコンテナの id が付与されておらず TextBox1_x のようになります。最終的な TextBox.ClientID が取得できないと、クライアントスクリプトでその jQuery オブジェクトを取得できません)

GridView.PreLender のタイミングなら、編集モードになっているか否かおよび編集モードになっている行のインデックスは正しく判定できます。最終的な TextBox.ClientID も取得できます。

GridView の[編集]ボタンクリックでポストバックされた際に、GridView.PreLender イベントのハンドラで編集モードになっている行の中の Datapicker を適用する TextBox を探し、その ClientID を取得し、RegisterClientScriptBlock メソッド を使って当該 TextBox に datepicker() メソッドを適用するスクリプトを登録してやることで Datapicker を表示することができます。

以下のコードの GridView1_PreRender イベントハンドラののコードような感じです。GridView で[編集]ボタンをクリックして表示される TextBox にフォーカスを当てると、上の画像のように Datapicker が表示されます。

<%@ Page Language="C#" %>

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

<script runat="server">    
  protected void GridView1_PreRender(object sender, EventArgs e)
  {
    int index = GridView1.EditIndex;

    if (index >= 0)
    {
      TextBox tb = 
        (TextBox)GridView1.Rows[index].FindControl("TextBox1");

      if (tb != null)
      {
        ClientScriptManager cs = Page.ClientScript;
        Type cstype = this.GetType();
        string csname = "jQueryUiDatepicker";

        if (!cs.IsClientScriptBlockRegistered(cstype, csname))
        {
          string cstext = "$(function () { $('#" 
              + tb.ClientID + "').datepicker(); });";

          cs.RegisterClientScriptBlock(
              cstype, 
              csname, 
              cstext, 
              true);
        }
      }
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>jQuery UI Datepicker in GridView</title>
  <script src="Scripts/jquery.js" type="text/javascript">
  </script>
  <script src="Scripts/jquery-ui.js" type="text/javascript">
  </script>
  <link href="css/jquery-ui.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <form id="form1" runat="server">
  <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
    ConnectionString="<%$ ConnectionStrings:Northwind %>" 
    SelectCommand="SELECT TOP 10 
      [OrderID], [CustomerID], [ShippedDate] 
      FROM [Orders]" 
    UpdateCommand="UPDATE [Orders] 
      SET [CustomerID] = @CustomerID, 
          [ShippedDate] = @ShippedDate 
      WHERE [OrderID] = @OrderID">
    <UpdateParameters>
      <asp:Parameter Name="CustomerID" Type="String" />
      <asp:Parameter Name="ShippedDate" Type="DateTime" />
      <asp:Parameter Name="OrderID" Type="Int32" />
    </UpdateParameters>
  </asp:SqlDataSource>

  <asp:GridView ID="GridView1" 
    runat="server" 
    AutoGenerateColumns="False" 
    DataKeyNames="OrderID" 
    DataSourceID="SqlDataSource1" 
    OnPreRender="GridView1_PreRender">
    <Columns>
      <asp:CommandField ShowEditButton="True" />
      <asp:BoundField DataField="OrderID" 
        HeaderText="OrderID" 
        InsertVisible="False" 
        ReadOnly="True" 
        SortExpression="OrderID" />
      <asp:BoundField DataField="CustomerID" 
        HeaderText="CustomerID" 
        SortExpression="CustomerID" />
      <asp:TemplateField HeaderText="ShippedDate" 
        SortExpression="ShippedDate">
        <EditItemTemplate>
          <asp:TextBox ID="TextBox1" 
            runat="server" 
            Text='<%# Bind("ShippedDate") %>'>
          </asp:TextBox>
        </EditItemTemplate>
        <ItemTemplate>
          <asp:Label ID="Label1" 
            runat="server" 
            Text='<%# Bind("ShippedDate") %>'>
          </asp:Label>
        </ItemTemplate>
      </asp:TemplateField>
    </Columns>
  </asp:GridView>
  </form>
</body>
</html>

サーバー側のコードで TextBox.ClientID を取得してピンポイントなスクリプトを設定しなくても、クライアント側で jQuery のセレクタで当該 TextBox を探して、それに datepicker() 適用する方が簡単ですが、ClientID の命名規則が変わるとうまくいかなくなる可能性があります。

そのリスクが許容できるのであれば、GridView1_PreRender イベントハンドラに代えて、以下のようなクライアントスクリプトを使っても同等なことが可能です。

<script type="text/javascript">
//<![CDATA[
  $(function () { 
    $('#<%=GridView1.ClientID%> input:text[id*=TextBox1]')
      .datepicker(); 
  });
//]]>
</script>

Tags: , ,

JavaScript

TreeView の SelectedNodeStyle-ImageUrl

by WebSurfer 2014年10月20日 17:21

TreeView の SelectedNodeStyle-ImageUrl(正確には TreeView.SelectedNodeStyle プロパティで取得できる TreeNodeStyle オブジェクトの ImageUrl プロパティ)にイメージの URL を設定しても無視されます。

TreeView の SelectedNodeStyle-ImageUrl

例えば、上の画像のように、選択されたノード(上の画像の例では Topic 1.0.1)の左隣に表示される三角形は青、それ以外は白抜きの三角形にしたいとします。

その場合、NodeStyle-ImageUrl には白抜きの三角形、SelectedNodeStyle-ImageUrl に青の三角形のイメージを設定するとよさそうですが、実際にそのように設定して試すと SelectedNodeStyle-ImageUrl の方は無視されます(ノードを選択しても白抜きの三角形のまま変わりません)。

これは stackoverflow のページ およびそのページにあるリンク先に書いてあるようにバグのようです。ASP.NET 4.5 でも修正されていません。

では、どうすれば選択されたノードの左隣に表示されるイメージを、期待した通り上の画像のような青三角のイメージにできるかですが、その方法を以下に書きます。

サンプルとして、MSDN ライブラリの TreeView.SelectedNodeStyle プロパティ にあるコードに手を加えてみました。

まず、選択されてないノード用のイメージですが、各 TreeNodeStyle の ImageUrl プロパティに白抜き三角のイメージの URL を静的に設定します。

SelectedNodeStyle-ImageUrl は静的に設定しても無視されるので、TreeView の SelectedNodeChanged イベントのハンドラで SelectedNode の ImageUrl に青三角のイメージの URL を動的に設定します。

ただし、設定しっぱなしではノードの選択を変更した時に前に選択したノードのイメージが青三角のまま元に戻りません。ノードの選択が変更された場合は、前に選択されたノードのイメージを白抜き三角イメージの URL に書き戻すコードが必要になります。

そのため、選択したノードの ValuePath を ViewState に保持しておき、ノードの選択が変更になった場合は ViewState に保持したパス情報を元に前に選択されたノードを探し、その ImageUrl を書き換えます。その後、新たに選択されたノードの ValuePath を ViewState に保存します。

ちなみに、ValuePath はルートノードから選択されたノードまでの Value をつなげた文字列になります。例えば Topic 1.0.1 を選択した場合は "Table of Contents/Chapter One/Section 1.0/Topic 1.0.1" になります(サンプルコードでは Value プロパティを明示的に設定してないので Text プロパティの値が使用されます)。

修正後のコードは以下の通りです。実際に動かして試すことができるよう 実験室 にアップしましたので、興味がありましたら試してみてください。

<%@ Page Language="C#" %>

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

<script runat="server">

    void Select_Change(Object sender, EventArgs e)
    {
        Message.Text = 
            "You selected: " + LinksTreeView.SelectedNode.Text;
        
        // これだけではダメ。イメージは元に戻らない。
        LinksTreeView.SelectedNode.ImageUrl = 
            "~/Images/right_selected.gif";

        // 前に選択されたノードのイメージを元に戻す。
        string valuePath = 
            (string)ViewState["TreeNodeValuePath"];
        
        if (!String.IsNullOrEmpty(valuePath))
        {
            LinksTreeView.FindNode(valuePath).ImageUrl = 
                "~/Images/right.gif";            
        }

        ViewState["TreeNodeValuePath"] = 
            LinksTreeView.SelectedNode.ValuePath;
    }
    
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head id="Head1" runat="server">
    <title>TreeView SelectedNodeStyle Example</title>    
</head>
<body>
    <form id="form1" runat="server">

    <h3>TreeView SelectedNodeStyle Example</h3>

    <asp:TreeView id="LinksTreeView"
        Font-Names= "Arial"
        ForeColor="Blue"
        SelectedNodeStyle-ForeColor="Green"
        SelectedNodeStyle-VerticalPadding="0"                
        OnSelectedNodeChanged="Select_Change"   
        runat="server">

        <LevelStyles>
            <asp:TreeNodeStyle ChildNodesPadding="10" 
                ImageUrl="~/Images/right.gif"
                Font-Bold="true" 
                Font-Size="12pt" 
                ForeColor="DarkGreen"/>
            <asp:TreeNodeStyle ChildNodesPadding="5" 
                ImageUrl="~/Images/right.gif"
                Font-Bold="true" 
                Font-Size="10pt"/>
            <asp:TreeNodeStyle ChildNodesPadding="5" 
                ImageUrl="~/Images/right.gif"
                Font-UnderLine="true" 
                Font-Size="10pt"/>
            <asp:TreeNodeStyle ChildNodesPadding="10" 
                ImageUrl="~/Images/right.gif"
                Font-Size="8pt"/>
        </LevelStyles>

        <Nodes>
            <asp:TreeNode Text="Table of Contents" 
                SelectAction="None">
                <asp:TreeNode Text="Chapter One">
                    <asp:TreeNode Text="Section 1.0">
                        <asp:TreeNode Text="Topic 1.0.1" />
                        <asp:TreeNode Text="Topic 1.0.2" />
                        <asp:TreeNode Text="Topic 1.0.3" />
                    </asp:TreeNode>

                    <asp:TreeNode Text="Section 1.1">
                        <asp:TreeNode Text="Topic 1.1.1"/>
                        <asp:TreeNode Text="Topic 1.1.2" />
                        <asp:TreeNode Text="Topic 1.1.3" />
                        <asp:TreeNode Text="Topic 1.1.4" />
                    </asp:TreeNode>
                </asp:TreeNode>

                <asp:TreeNode Text="Chapter Two">
                    <asp:TreeNode Text="Section 2.0">
                        <asp:TreeNode Text="Topic 2.0.1" />
                        <asp:TreeNode Text="Topic 2.0.2" />
                    </asp:TreeNode>
                </asp:TreeNode>
            </asp:TreeNode>
            <asp:TreeNode Text="Appendix A" />
            <asp:TreeNode Text="Appendix B" />
            <asp:TreeNode Text="Appendix C" />
        </Nodes>
    </asp:TreeView>

    <br /><br />

    <asp:Label id="Message" runat="server"/>

    </form>
</body>
</html>

Tags: ,

ASP.NET

About this blog

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

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar