none
[ASP.NET MVC2 + jquery mobile]ユーザーコントロール(View)にjquery mobileのスタイルが適用されない RRS feed

  • 質問

  • 環境:[Windows Vista SP1, VisualStudio 2010 Pro]

    ajaxでサーバーから取得したデータをユーザーコントロールに渡し描画を試みているのですが、jquery mobileのスタイルが適用されません。

    ユーザーコントロールを使わなければ大丈夫です。

    これはjquery mobileが対応していない(バグ)せいなのか、私のコーディングが間違っているのかわかる方おられませんか?

     

    以下再現コード

    ※プロジェクトはASP.NET MVC 2 Web アプリケーションで作成しています。

    ※検索ボタンを押すとリストが表示されますが、スタイルが適用されていません。

    --------------------------------[Site.Master]-----------------------------
    <span><span><%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %><br/>
    <br/>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><br/>
    <html xmlns="http://www.w3.org/1999/xhtml"><br/>
    <head id="Head1" runat="server"><br/>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><br/>
      <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title><br/>
    	<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.css" /> <br/>
    	<script type="text/javascript" src="http://code.jquery.com/jquery-1.5.min.js"></script><br/>
    	<script type="text/javascript" src="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.js"></script><br/>
      <script type="text/javascript" src="<%= ResolveUrl("~/Scripts/MicrosoftAjax.js") %>"></script><br/>
      <script type="text/javascript" src="<%= ResolveUrl("~/Scripts/MicrosoftMvcAjax.js") %>"></script><br/>
      <asp:ContentPlaceHolder ID="ScriptContent" runat="server" /><br/>
    </head><br/>
    <br/>
    <body><br/>
      <div class="page"><br/>
    <br/>
        <div id="header"><br/>
          <div id="title"><br/>
            <h1>マイ MVC アプリケーション</h1><br/>
          </div><br/>
           <br/>
          <div id="logindisplay"><br/>
            <% Html.RenderPartial("LogOnUserControl"); %><br/>
          </div> <br/>
          <br/>
          <div id="menucontainer"><br/>
          <br/>
            <ul id="menu">       <br/>
              <li><%: Html.ActionLink("ホーム", "Index", "Home")%></li><br/>
              <li><%: Html.ActionLink("このサイトについて", "About", "Home")%></li><br/>
            </ul><br/>
          <br/>
          </div><br/>
        </div><br/>
    <br/>
        <div id="main"><br/>
          <asp:ContentPlaceHolder ID="MainContent" runat="server" /><br/>
    <br/>
          <div id="footer"><br/>
          </div><br/>
        </div><br/>
      </div><br/>
    </body><br/>
    </html><br/>
    </span>
    </span>
    
    


    --------------------------------[Book.cs](Modelsに追加)-----------------------------

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    namespace MvcApplication7.Models
    {
      public class Book
      {
        public string Name { get; set; }
        public string Auther { get; set; }
        public DateTime Date { get; set; }
    
        public static IEnumerable<Book> GetAll()
        {
          return new Book[]{
            new Book{Name="組み込みGUI環境の設計と実装", Auther = "四ノ宮 力 ...他", Date=new DateTime(2007, 7, 20), },
            new Book{Name="デザインパターン入門", Auther = "結城 浩", Date=new DateTime(2004, 6, 30), },
            new Book{Name="日経ソフトウェア 2010 10月号", Auther = "日経BP社", Date=new DateTime(2010, 8, 24), },
          };
        }
      }
    }
    

    --------------------------------[HomeController.cs]-----------------------------
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using MvcApplication7.Models;
    
    namespace MvcApplication7.Controllers
    {
      [HandleError]
      public class HomeController : Controller
      {
        public ActionResult Index()
        {
          return View(Book.GetAll());
        }
    
        public ActionResult GetData()
        {
          if (Request != null && Request.IsAjaxRequest())
          {
            return PartialView("BookList", Book.GetAll());
          }
          else
          {
            return new EmptyResult();
          }
        }
    
        public ActionResult GetDateTime()
        {
          if (Request.IsAjaxRequest())
          {
            return Content(DateTime.Now.ToString());
          }
          return new EmptyResult();
        }
    
        public ActionResult About()
        {
          return View();
        }
      }
    }
    
    

    --------------------------------[Index.aspx]-----------------------------
    <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<MvcApplication7.Models.Book>>" %>
    
    <%@ Import Namespace="MvcApplication7.Models" %>
    
    <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
      ホーム ページ
    </asp:Content>
    <asp:Content ID="Content3" ContentPlaceHolderID="ScriptContent" runat="server">
      <script type="text/javascript">
        function onSuccess(context) {
          var data = context.get_data();
          $("#TimeDate").text(data);
          $.mobile.pageLoading(true);
        }
    
        function onComplete() {
          $.mobile.pageLoading(true);
        }
      </script>
    </asp:Content>
    <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
      <div data-role="page" data-theme="b">
      <% using (Ajax.BeginForm("GetDateTime",
                   new AjaxOptions { OnSuccess = "onSuccess" }))
        { %>
          <div data-role="fieldcontain">
            <input type="submit" data-icon="refresh" value="更新" />
          </div>
      <% } %>
      <p>日時:<span id="TimeDate" data-inline="true" data-role="button"></span></p>
      <div>
        <ul data-role="listview">
          <li data-role="list-divider">実際の形式<span class="ui-li-count"><%=Model.Count() %></span></li>
          <% foreach (Book book in Model)
            {%>
          <li>
          <h3><a href="#"><%=book.Name%></a></h3>
          <p class="ui-li-aside"><%=book.Date.ToShortDateString() %></p>
          <p><%=book.Auther %></p>
          </li>
          <%} %>
        </ul>
      </div>
      <% using (Ajax.BeginForm("GetData",
                   new AjaxOptions { UpdateTargetId = "results", OnComplete = "onComplete" }))
        { %>
          <div data-role="fieldcontain">
            <input type="submit" data-icon="search" value="検索" />
          </div>
      <% } %>
    
      <div id="results"></div>
      </div>
    </asp:Content>
    
    

    --------------------------------[BookList.ascx](Views\Homeに追加)-----------------------------
    <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<MvcApplication7.Models.Book>>" %>
    
    <%@ Import Namespace="MvcApplication7.Models" %>
    
    <div>
      <ul data-role="listview">
        <li data-role="list-divider">実際の形式<span class="ui-li-count"><%=Model.Count() %></span></li>
        <% foreach (Book book in Model)
          {%>
        <li>
        <h3><a href="#"><%=book.Name%></a></h3>
        <p class="ui-li-aside"><%=book.Date.ToShortDateString() %></p>
        <p><%=book.Auther %></p>
        </li>
        <%} %>
      </ul>
    </div>
    
    2011年3月8日 7:47

回答

  • あ、OnSuccessが後だったんですね。これまた勘違いしていました。
    OnSuccessを利用、以下のようにすることでいけました。ポイントはlistviewの引数"refresh"を削ったことです。
       function onComplete() {
         $.mobile.pageLoading(true);
         $("#booklist").listview();
       }
    
    refreshの意味をあまり理解していなかったせいで、
    とりあえず付けとけ感覚で進んでいたら以下のように意味のわからない現象に会って途方にくれていました。

    alertが出ない、スタイルも適用されない。
       function onComplete() {
         $.mobile.pageLoading(true);
         $("#booklist").listview("refresh");
         alert($("#booklist").length);
       }
    
    alertが出ない、スタイルも適用されない。loadingアニメーションが消えない。
       function onComplete() {
         $("#booklist").listview("refresh");
         $.mobile.pageLoading(true);
         alert($("#booklist").length);
       }
    
    append等による個別対応以外(今回のようにlistviewをごっそり子要素とする場合)はrefreshを付けてはいけないようですね。
    これで、無事スタイルが適用できました。
    みなさんありがとうございます。
    • 回答としてマーク NZ-000 2011年3月10日 9:07
    2011年3月10日 9:07

すべての返信

  • 初めまして、吉川と申します。

    何やらツイッターに質問が流れていたので回答してみます(最近は色々ありますね)。

    ASP.NET MVC 2がどういったものかはわからないので、jQuery Mobileからの視点になります。

     

    動的にDOMに追加した要素は、そのままではjQuery Mobileのスタイルは適用されませんので、

    JavaScriptでDOMに要素を追加した後に以下のスクリプトを実行する必要があります。

     

    ・セレクトボックス

    $('#select_id').selectmenu();

     

    ・チェックボックス

    $('#checkboxradio_id').checkboxradio();

     

    ・リスト

    $('#list_id').listview();

     

    ・ページ

    $('#page_id').page();

     

    個別に項目を増やすような場合は、引数に'refresh'を渡します。

    例えば、リストに1項目だけ追加した場合などは、下記のようになります。

    $('#list_id').listview('refresh');

     

    他にも各要素に対応するメソッドは色々ありますが、

    すべて $('#page_id').page()でやってしまうのもありだと思います。

     

    ※「$('#id')」のような、セレクタの内容についてはご自由にお使いください

     




    2011年3月8日 8:48
  • 回答ありがとうございます。

    ListViewに要素を追加した場合は、更新処理を行う必要があるんですね。
    知らなかったので参考になりました。

    ただ、今回のケースはASP.NET MVCが絡んでいることもあって、更新処理がいるのかについては疑問が残ります。

    それはhtmlをごっそり差し替えているからです。
    C#で動的にリストを生成しているようには見えますが、生成されるものは静的なHTMLです。
    そのため、実際に更新処理を行わなくとも、Index.aspxの以下の部分に対してはjquery mobileのスタイルが適用されます。

    <ul data-role="listview">
       <li data-role="list-divider">実際の形式<span class="ui-li-count"><%=Model.Count() %></span></li>
       <% foreach (Book book in Model)
        {%>
       <li>
       <h3><a href="#"><%=book.Name%></a></h3>
       <p class="ui-li-aside"><%=book.Date.ToShortDateString() %></p>
       <p><%=book.Auther %></p>
       </li>
       <%} %>
      </ul>
    
    
    2011年3月8日 10:23
  • あと、ListViewに更新処理をかけることも試みたんですが、ユーザーコントロール側の要素がIndex.aspx側のjavaScriptから見えないようです。
    Webページのソースを見ても置き換わった部分のhtmlがありませんので見えないことは理解できるのですが、
    ユーザーコントロール側の要素を指定することができないのかと調査中です。
    2011年3月8日 10:28
  • あぁ、見えないから適用されないんですね。自分で書いていて納得。

    これは特定の要素を指定することができたとしても根本的な解決にはならないですね。

    どうしよう。。。

    2011年3月8日 10:35
  • ブラウザーに見えてるのにHTMLがないなってことはあり得ないです。

    MVCはあんまり触ってないので外してるかもしれないけれど、AJAXでデータを取ってくる前にjQueryでスタイルの適応をしていませんか?

    スタイルシートと違って、jQueryでスタイルを適用するなら、AJAXでデータを取り終わってから(HTMLがレンダリングされてから)でないと。

     

    2011年3月9日 7:57
  • 回答ありがとうございます。

    >ブラウザーに見えてるのにHTMLがないなってことはあり得ないです。

    確かにその通りです。勘違いしていたようで、HTMLはありました。すいません。

    (ajax完了時にjavaScriptで追加した要素を探しても見つかりませんでした。完了後にブラウザでソースを取得しても見えなかったのですが、もう一度要素を探してみるとありました。謎)

    >MVCはあんまり触ってないので外してるかもしれないけれど、AJAXでデータを取ってくる前にjQueryでスタイルの適応をしていませんか?

    ユーザーコントロールだとそうなっている気がします。ユーザーコントロール側にスタイルを考慮したHTMLがあるわけですから。けど、

    >スタイルシートと違って、jQueryでスタイルを適用するなら、AJAXでデータを取り終わってから(HTMLがレンダリングされてから)でないと。

    このようにスタイル適用を任意タイミングで行うようなことが可能なんでしょうか?

     

    2011年3月10日 0:32
  • HTMLをごっそり差し替えているの部分がよくわからないのですが、

    それはページを最初から再描画しているということでしょうか?

     

    Ajaxでサーバーと通信すると書いてあったので、てっきり該当部分のみ

    DOM操作で更新すると勝手ながら予想していたのですが。

    HTMLをごっそり差し替えるといっても、ページの再描画なのか、

    body要素以下をすべて差し替えるのか、そのあたりが

    はっきりすると答えやすいかもしれません。

     

    見た目は見えるけど、ソースにはないというのは、JavaScriptで

    DOMを操作している結果が見えてないだけだと思うのですが、

    ソースではなく、開発者ツールか何かでDOMツリーを見てみると良いかもしれません。

     

    IEであれば、F12で、ChromeであればCTL+Shift+Jなどで。

    FirefoxであればFirebugを入れて確認すると良いかと思います。

     

     


    2011年3月10日 0:38
  • 試してないんだけれど、ユーザーコントロール側にjQueryの初期化コードを追加してあげればいいんじゃないですか?

    2011年3月10日 1:02
  • >HTMLをごっそり差し替えているの部分がよくわからないのですが、

    >それはページを最初から再描画しているということでしょうか?

    http://www.atmarkit.co.jp/fdotnet/aspnetmvc/aspnetmvc03/aspnetmvc03_04.html
    の技術を参考にしました。

    私は、ajaxで取得したデータをユーザーコントロール側で処理し(HTML化)、
    できたHTMLを元のHTMLの指定したタグに反映させる(入れ替える)と解釈しました。
    が、入れ替えるという表現は間違いでその部分のDOMを再構築と捉えた方がいいんでしょうか?・・・。
    2011年3月10日 1:10
  • jQueryの初期化コードって何を指しているのでしょう?

    script指定の部分のことでしょうか?

    試しに、BookList.ascxを以下のようにしてみたところ、「Error Loading Page」というダイアログ状のエラーが出ました。

    ajax通信時にでる「Loading」アニメーションと似たような感じで出ましたからjQuery のエラーでしょうか。

    う~ん、わかりません。

    <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<MvcApplication7.Models.Book>>" %>
    
    <%@ Import Namespace="MvcApplication7.Models" %>
    
    <asp:Content ID="Content4" ContentPlaceHolderID="ScriptContent" runat="server">
    	<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.css" /> 
    	<script type="text/javascript" src="http://code.jquery.com/jquery-1.5.min.js"></script>
    	<script type="text/javascript" src="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.js"></script>
    </asp:Content>
    
    <div>
     <ul data-role="listview">
      <li data-role="list-divider">実際の形式<span class="ui-li-count"><%=Model.Count() %> 件</span></li>
      <% foreach (Book book in Model)
       {%>
      <li>
      <h3><a href="#"><%=book.Name%></a></h3>
      <p class="ui-li-aside"><%=book.Date.ToShortDateString() %></p>
      <p><%=book.Auther %></p>
      </li>
      <%} %>
     </ul>
    </div>
    

     

    • 編集済み NZ-000 2011年3月10日 2:38 ソースが間違っていた
    2011年3月10日 2:10
  • >HTMLをごっそり差し替えているの部分がよくわからないのですが、

    >それはページを最初から再描画しているということでしょうか?

    http://www.atmarkit.co.jp/fdotnet/aspnetmvc/aspnetmvc03/aspnetmvc03_04.html
    の技術を参考にしました。

    私は、ajaxで取得したデータをユーザーコントロール側で処理し(HTML化)、
    できたHTMLを元のHTMLの指定したタグに反映させる(入れ替える)と解釈しました。
    が、入れ替えるという表現は間違いでその部分のDOMを再構築と捉えた方がいいんでしょうか?・・・。

    HTML を入れ替えてはいません。検索ボタンの部分だけをピックアップしてみます。

     <% using (Ajax.BeginForm("GetData",
            new AjaxOptions { UpdateTargetId = "results", OnComplete = "onComplete" }))
      { %>
       <div data-role="fieldcontain">
        <input type="submit" data-icon="search" value="検索" />
       </div>
     <% } %>
    
     <div id="results"></div>
    
    

    上記のコードは、

     ・submit されたら(検索ボタンが押されたら)、HomeController の GetData アクションを呼び出す。
     ・サーバー側で HomeController の GetData アクションを実行する。
     ・アクションから戻ってきた内容を "results" という ID で示された要素の子供として追加する。
     ・JavaScript の "onComplete" を呼び出す。

    という順序で動くはずです。
    HomeController の GetData では、BookList 分割ビューにデータを渡していますので、クライアント側に返ってくるのは部分的な HTML であり、これを results に追加しているだけです。(DOM に動的に追加します。)
    Toru Yoshikawa さんによると、この部分的に返ってきた HTML には自力でスクリプトを実行する必要があるとのことですので、これをやるとするならば、

    function onComplete() {
     $.mobile.pageLoading(true);
    }
    
    

    この中に入れてあげる必要があるのではないでしょうか。
    2011年3月10日 7:25

  • totojoさん回答ありがとうございます。

    なるほど、私の勘違いのようで、申し訳ありません。

    試しにlistviewに"booklist"というIDを割り振り、以下のようにonCompleteを書き換えてみたんですが、

    onCompleteの時点ではまだDOMが書き換わっていないのか、スタイルは適用されませんし、booklist要素も見つかりません(alertで0と表示される)。

    もう一度検索ボタンを押すとbooklist要素が見つかります。これはonCompleteイベントの時点ではDOMが更新されていないということかもしれません。

    確かにalertによるダイアログが表示されてから、再描画されているように見えます。onCompleteが無理ならどうすべきなのか・・・。タイマー・・・?調査中です。

    それがわかればうまくスタイルが適用できる気がしてきました。

       function onComplete() {
         $.mobile.pageLoading(true);
         $("#booklist").listview("refresh");
         alert($("#booklist").length);
       }
    

    2011年3月10日 7:56
  •  

    >私は、ajaxで取得したデータをユーザーコントロール側で処理し(HTML化)、

    >できたHTMLを元のHTMLの指定したタグに反映させる(入れ替える)と解釈しました。

    >が、入れ替えるという表現は間違いでその部分のDOMを再構築と捉えた方がいいんでしょうか?・・・。

     

    単純に、Ajaxで取得したデータでDOMを構築して、特定のタグの部分に追加または入れ替えしたような感じですよね?

    それであれば、お互いの認識はあっているかと思います。

     

    私の次のコメントに対して

     

    >動的にDOMに追加した要素は、そのままではjQuery Mobileのスタイルは適用されませんので、

    >JavaScriptでDOMに要素を追加した後に以下のスクリプトを実行する必要があります。

     

     

    以下の返信が帰ってきたので

     

    >それはhtmlをごっそり差し替えているからです。

    >C#で動的にリストを生成しているようには見えますが、生成されるものは静的なHTMLです。

    >そのため、実際に更新処理を行わなくとも、Index.aspxの以下の部分に対してはjquery mobileのスタイルが適用されます。

     

     

    認識に齟齬があるかと思い確認させて頂きました。

    最初に表示されるHTMLは静的なHTMLなのでもちろんリフレッシュを行う必要はありません。

    >できたHTMLを元のHTMLの指定したタグに反映させる(入れ替える)と解釈しました。

    この部分はリフレッシュをかける必要があります。

     

    すみません、ASP.NET MVC 2 Web アプリケーションはわかっていないので、

    これ以上の説明はできませんがjQuery Mobileの挙動は以下のサンプルコードを

    お手元で表示すればわかるかと思います(UTF-8で保存して表示してください)。

     

    あとは、ASP.NET MVC 2 Web アプリケーションの挙動次第ですが、

    そちらは、ご自身か他の方々にお任せします。

     

    <!DOCTYPE html>
    <html lang="ja">
    <head>
    	<meta charset="UTF-8">
    	<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.css" />
    	<script src="http://code.jquery.com/jquery-1.5.min.js"></script>
    	<script src="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.js"></script>
    	<script>
    		function addItemWithoutRefresh(){
    			$('#list').append($('<li>追加</li>'));
    		}
    		function addItem(){
    			$('#list').append($('<li>追加</li>'));
    			$('#list').listview('refresh');
    		}
    	</script>
    </head>
    <body>
    
    <div data-role="page">
    
    	<a href="#" onclick="addItemWithoutRefresh()">追加(リフレッシュなし)<a><br>
    	<a href="#" onclick="addItem()">追加(リフレッシュあり)<a>
    	<ul data-role="listview" id="list">
    		<li>登録済み</li>
    	</ul>
    </div>
    </body>
    </html>
    

    2011年3月10日 8:08
  • AjaxOptions で指定された UpdateTargetId と OnComplete とどっちが先なのか今一つ自信はなかったのですが、OnComplete が先に走ってしまうのですね。
    そうであれば、OnComplete のかわりに OnSuccess を使うといいかもしれません。(OnSuccess は OnComplete より後に発生します。)
    あるいは、UpdateTargetId を利用して DOM に追加している部分を、OnComplete なり OnSuccess なりの中で自力で追加してやればいいように思います。
    • 回答としてマーク NZ-000 2011年3月10日 9:04
    • 回答としてマークされていない NZ-000 2011年3月10日 9:05
    2011年3月10日 8:11
  • あ、OnSuccessが後だったんですね。これまた勘違いしていました。
    OnSuccessを利用、以下のようにすることでいけました。ポイントはlistviewの引数"refresh"を削ったことです。
       function onComplete() {
         $.mobile.pageLoading(true);
         $("#booklist").listview();
       }
    
    refreshの意味をあまり理解していなかったせいで、
    とりあえず付けとけ感覚で進んでいたら以下のように意味のわからない現象に会って途方にくれていました。

    alertが出ない、スタイルも適用されない。
       function onComplete() {
         $.mobile.pageLoading(true);
         $("#booklist").listview("refresh");
         alert($("#booklist").length);
       }
    
    alertが出ない、スタイルも適用されない。loadingアニメーションが消えない。
       function onComplete() {
         $("#booklist").listview("refresh");
         $.mobile.pageLoading(true);
         alert($("#booklist").length);
       }
    
    append等による個別対応以外(今回のようにlistviewをごっそり子要素とする場合)はrefreshを付けてはいけないようですね。
    これで、無事スタイルが適用できました。
    みなさんありがとうございます。
    • 回答としてマーク NZ-000 2011年3月10日 9:07
    2011年3月10日 9:07