Archive

Posts Tagged ‘jQuery’

Knockoutで取得したデータをjQueryMobileに反映する。

WebFormでJavascript主体なスマートフォン向けサイトを作ろう その5 #タイトルの付け方を変えました。

前回までで、サーバー側のWebServiceをJavascriptから呼び出し、結果を取得するところまで作成しました。

次に結果を画面に反映する部分を作成しましょう。

ViewModelの変更

前回取得したWebサービスの値をKnockout経由でView側に通知させます。

JQuery Mobileを直に利用している場合、DOMを変更したら各要素をrefleshしてあげないとスタイルが反映されないんだけれど、knockoutを利用した場合は特に何もせずに反映されるんですよね。ただ、サンプルなんかを見るとrefleshを発行しているものもあり。必要かどうかよくわかりません(汗。見た目問題なく動いているんですけれどね。

var viewModel = new (function () {
var local = this;
var service = new customerService();

this.keyword = ko.observable("");
this.検索結果 = ko.observableArray([]);
this.件数 = ko.computed(function () {
return local.検索結果().length;
});

this.find = function () {
local.検索結果.removeAll();
service.Find($, this.keyword(),
function (result) {
var data = result.d;
ko.utils.arrayPushAll(local.検索結果, data);
// $("#リストのID").listview('reflesh');
});
};
})();

配列と計算用のプロパティー

一覧表示用のデータ等は、通知に対応した配列(ko.observableArray)をプロパティーとして用意します。

件数などで、特定のプロパティーの値をそのまま利用できるようなプロパティーは、計算に対応したプロパティー(ko.computed)として定義してあげるとプロパティーの更新漏れなんかがなくて良いです。今回は検索結果を格納した配列の要素数がそのまま検索結果件数なので、検索結果().lengthを返却しています。

検索結果の追加

検索した結果は、ko.observableArrayなプロパティーに1件づつpushしていっても良いのですが、ko.utilsに配列をまるごとpushするユーティリティー(arrayPushAll)が用意されているので、こいつを利用しましょう。

observableArrayに追加する要素は、本来observableなプロパティーを持つクラスを指定できますが、今回はリストに表示するだけで、かつ各要素に対する変更も受け取る必要が無いので、WCFから受け取ったデータ(result.d)をそのまま配列に追加しています。

Viewの変更

以前作ったViewに結果リストを表示するためのリストと、結果件数を表示するためのラベルを追加します。

<%@ Page Title="" MasterPageFile="~/Site.Master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div data-role="page" id="FindCustomerView">
<header data-role="header" data-position="fixed">
<h1>顧客検索</h1>
</header>
<div data-role="content">
<input type="text" data-bind="value: keyword"/>
<input type="button" value="検索" data-bind="click: find" />
<p>検索結果件数:<span data-bind="text: 件数"></span></p>
<ul data-role="listview" data-bind="foreach: 検索結果">
<li><a data-bind="attr: { href: 'Detail.aspx?jyu=' + ユーザーId }">
<h4><span data-bind="text: ユーザー名"></span></h4>
<p>(<span data-bind="text: ユーザーId"></span>)</p>
</a></li>
</ul>
</div>
</div>
</asp:Content>
listviewと配列のバインド
jQuery Mobileでリストを表示したい場合は、data-role属性でlistviewを指定します。
また、data-bind属性で、foreach: observableArrayなプロパティーを指定すると、配列をリストにバインドできます。
配列バインドのテンプレート
knockoutでは、foreachでバインディングした要素の配下を配列要素数分繰り返します。
検索結果という配列に3個の要素があったなら、この部分が3回繰り返されることになります。
<li><a data-bind="attr: { href: 'Detail.aspx?jyu=' + ユーザーId }">
<h4><span data-bind="text: ユーザー名"></span></h4>
<p>(<span data-bind="text: ユーザーId"></span>)</p>
</a></li>
この部分に関してはそれほど特筆する点はありませんね。実行してみると、liの部分がテンプレートで記述した要素通りに、3つ追加されているのを確認できます。
image
 
実行するとこんなかんじに表示されます。さて、一応表示までできましたね。

この画面から遷移する予定のDetail.aspx側(更新)も気が向いたら書きます。

image
 

過去へのリンクはこちらから

  • その1 はじめに
  • その2 ソリューション作成とライブラリの追加(knockout,jQuery,jQueryMobile)
  • その3 Knockoutを利用したViewModelの追加
  • その4 jQueryからASP.NETのWCF Serviceの呼び出し

jQueryからASP.NETのWCF Serviceの呼び出し

2012年6月19日 1件のコメント

WebFormでJavascript主体なスマートフォン向けサイトを作ろう その4

前回まででクライアント側のView-ViewModelの部分を作成したので、今回はクライアントのModelとそのModelが呼び出すサーバー側のサービスを作成していきます。今回は、knockout出てこないですね。WCFとJavascriptのお話です。

Webサービスの仕様

今回作成するサービスは、クライアントから渡されたキーワードをもとに検索を行い、検索にヒットしたデータの一覧を返すこととします。

WCFサービスとして作成するので、まずはデータコントラクトを作成します。今回はユーザーIdとユーザー名の2つを持つ簡単なエンティティークラスのリストを返却することします。データコントラクトはこんな感じになります。

Imports System.Runtime.Serialization

Namespace Entity
    <DataContract()>
    Public Class Customer
        <DataMember()>Property ユーザーId As String
        <DataMember()>Property ユーザー名 As String
    End Class
End Namespace

Webサービスの作成

次にWebサービスを本体を作成します。Visual Studioの新しい項目の追加から、「AJAX 対応 WCFサービス」を作成します。名前は、「FindService.svc」としておきます。

image

ここでは、ダミーの検索結果を返すメソッドFindを定義します。今回は、JavascriptとJSON形式のメッセージでやり取りしたいので、OperationContractにWebInvoke属性を追加して、Request、ResponseともにJSONでデータ交換を行うように宣言します。

<OperationContract()>
<WebInvoke(BodyStyle:=WebMessageBodyStyle.WrappedRequest, RequestFormat:=WebMessageFormat.Json, ResponseFormat:=WebMessageFormat.Json)>
Public Function Find(keyword As String) As List(Of Customer)
    Return New List(Of Customer) From {
            New Customer With {.ユーザーId = "karuakun", .ユーザー名 = "かるあくん"},
            New Customer With {.ユーザーId = "karua", .ユーザー名 = "かるあ"},
            New Customer With {.ユーザーId = "karukan", .ユーザー名 = "偽物"}
        }
End Function

クライアント側モデルクラスの作成(Webサービスの呼び出し)

実際にモデルクラスを作る前に、サーバーとはJSON形式でデータをやりとりするので、Javascriptオブジェクトをシリアライズ・デシリアライズするユーティリティークラスをひとつ導入しておきましょう。Nuget Package Consoleで、JSON2.jsをプロジェクトにインストールします。

PM> Install-Package json2
Successfully installed 'json2 1.0.2'.
Successfully added 'json2 1.0.2' to MobileTest.

クライアント側のモデルクラスを~/Script/Service以下に作成します。スクリプトの名前は、「CustomerService.js」としておきましょう。ちなみに、ソリューションはこんな感じで構成されています。

image

WebInvoke属性でマークしたWCFサービスのメソッドをjavascriptから呼び出す場合は、「WCFサービスのパス/メソッド名」で呼び出せます。WCFのパラメーター(data)はWCFのインターフェイスにあわせてJSON.stringifyメソッドでJSON文字列に変換します。

var customerService = (function () {
    var self = this;
    this.Find = function ($, keyword, successAction) {
        var data = JSON.stringify({keyword: keyword});
        $.ajax({
            type: "POST",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            url: "Service/CustomerService.svc/Find",
            data: data,
            success: successAction(response)
        });
    };
});

スクリプトをマスターページに追加

スクリプトを追加したら、忘れずにマスターページに追加します。jquery.mobileの前であればどこでもいいのですが、依存関係からしてServiceはViewModelの前に追加しておいたほうがいいでしょう。

<%@ Master Language="VB" AutoEventWireup="false" %>
<!DOCTYPE html>
<html>
<head runat="server">
    <title></title>
    <link type="text/css" rel="stylesheet" href="Content/jquery.mobile-1.1.0.min.css"/>
    <script type="text/javascript" src='<%= ResolveClientUrl("~/Scripts/jquery-1.7.2.min.js") %>'></script>
    <script type="text/javascript" src='<%= ResolveClientUrl("~/Scripts/knockout-2.1.0.js") %>'></script>
 <script type="text/javascript" src='<%= ResolveClientUrl("~/Scripts/json2.min.js") %>'></script> <script type="text/javascript" src='<%= ResolveClientUrl("~/Scripts/Service/CustomerService.js") %>'></script>     <script type="text/javascript" src='<%= ResolveClientUrl("~/Scripts/ViewModel/FindCustomerViewModel.js") %>'></script>
    <script type="text/javascript" src='<%= ResolveClientUrl("~/Scripts/jquery.mobile-1.1.0.min.js") %>'></script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>

とりあえず呼び出してみよう

ViewModelでは、作成したモデルクラスをインスタンス化して呼び出すだけです。前回作ったViewModelにモデルのFindメソッドを呼び出すコードを追加します。

(function ($) {
    var viewModel = new (function () {
        var local = this;
        var service = new customerService();
        this.keyword = ko.observable("");

        this.find = function () {
            service.Find($, this.keyword(),
                function (result) {
                    var data = result.d;
                    alert(data.length);
                });
        };
    })();

    $(document).on("pageinit", "#FindCustomerView", function (event) {
        ko.applyBindings(viewModel, document.getElementById("FindCustomerView"));
    });
})($);

WCFをJSON形式の返却にした場合、dプロパティーの下にDataContractの内容が入ってきます。Fiddlerで、どんな文字列がHTTPに乗っているのかを確認して見ましょう。

image image

今回の例では、WCFから返却されたリストの件数をalert関数で表示しています。aspxをデバック実行して、検索ボタンをクリックしてみましょう。サービスの結果件数(3件)が表示されたのが確認できます。

image

次回はViewModelの更新とViewのリスト表示です。

過去へのリンクはこちらから

  • その1 はじめに
  • その2 ソリューション作成とライブラリの追加(knockout,jQuery,jQueryMobile)
  • その3 Knockoutを利用したViewModelの追加

Knockoutを利用したViewModelの追加

2012年6月5日 2件のコメント

WebFormでJavascript主体なスマートフォン向けサイトを作ろう その3

なんか間があいちゃいましたね。UIができたので、今回は業務ロジックとUIをつなぐViewModelを作成していきます。

Viewの変更

まずは画面の項目とJavascriptのバインディングをしておきましょう。前回のHTMLをこんな風に書き換えます。

<%@ Page Title="" MasterPageFile="~/Site.Master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <div data-role="page" id="FindCustomerView">
        <header data-role="header" data-position="fixed">
            <h1>顧客検索</h1>
        </header>
        <div data-role="content">
            <input type="text" data-bind="value: keyword"/>
            <input type="button" value="検索"

data-bind=”click: find”

 />
        </div>
    </div>
</asp:Content>

今回は検索キーワードをViewModelのkeywordプロパティーに、検索ボタンクリック時のイベントをViewModelのfindメソッドに割り当てています。

バインディングの詳細については、knockoutのドキュメントを確認しましょう。日本語の情報を探している場合は、このあたりが参考になります。そのうちちょこちょこ触れるかもしれません。

→ knockout.js 入門-鬼畜編<http://www.slideshare.net/shibayan/knockout-11523371>

 

ViewModelの追加と参照

続いてViewModel側の追加です。とりあえずView=ページとして作って置くと管理が楽そうなので、FindCustomerViewのViewModelは/Scripts/ViewMode/FindCustomerViewModel.jsとして作成しておきます。

image

続いて、追加したViewModelのJSファイルをマスターページに追加しておいてあげます(jQuery Mobileの場合は、ランディングページ以外は基本headerタグは読み込まれないので、利用するスクリプトはすべてマスターページに定義しておきます)。なんとなくViewModelの定義はknockoutの後、jQueryMobileの前に定義しています。

<%@ Master AutoEventWireup="false" %>
<!DOCTYPE html>
<html>
<head runat="server">
    <title></title>
    <link type="text/css" rel="stylesheet" href="Content/jquery.mobile-1.1.0.min.css"/>
    <script type="text/javascript" src='<%= ResolveClientUrl("~/Scripts/jquery-1.7.2.min.js") %>'></script>
    <script type="text/javascript" src='<%= ResolveClientUrl("~/Scripts/knockout-2.1.0.js") %>'></script>
    <script type="text/javascript" src='<%= ResolveClientUrl("~/Scripts/ViewModel/FindCustomerViewModel.js") %>'></script>
    <script type="text/javascript" src='<%= ResolveClientUrl("~/Scripts/jquery.mobile-1.1.0.min.js") %>'></script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>

マスターページ自体は今のところ、変わったところはありませんね。

ViewModelの定義

続いてViewModelの定義を行います。ViewModelには、検索キーワードとバインドするkeywordプロパティーと、検索ボタンがクリックされた際に呼び出されるfind関数を定義します。

(function ($) {
    var viewModel = new (function () {
        var local = this;
        this.keyword = ko.observable("");
        this.find = function () {
            //ToDo:サービスを呼び出してビジネスロジックを実行する。
            alert(local.keyword());
        };
    })();

    $(document).on("pageinit", "#FindCustomerView", function (event) {
        ko.applyBindings(viewModel, document.getElementById("FindCustomerView"));
    });
})($);
ViewModelでプロパティーの値を参照する場合は、ゲッター経由(関数呼び出し)なので注意しましょう。同じく設定する場合もセッター経由になります。

実行してみよう

この状態で実行すると、こうなります。

image

実行されましたか?

次回はjavascriptのモデルからWCFの呼び出しです。

ASP.NET WebFormでjQueryMobileを利用すると、jQuery.Validationと相性が悪すぎる。

ASP.NET WebFormでjQuery Mobileのページを作っていて、ハマったのでメモを残しておきます。

runat=”server”なFormを作らない=コードビハインド作れない=Postback使えない

いきなりWebFormを否定しています。jQuery Validationはformに対して設定するわけだけれど、MasterPageなんかでContentPlaceHolderをrunat=”server”なFormで囲われていると、ページごとにValidationが設定できなくて困っちゃうんですよね。

まずMasterPaceからformを消してしまいます。この時点で、コードビハインドが使えなくなるので、サーバーコントロールはもとより、PostBackやViewStateといったWebFormのプログラミングモデルはほとんど使えなくなります。実際スクリプトやCSSの参照にMasterPageを使いたいだけで、それがなかったらクライアント側はASPXでなくHTMLでもいいんですよね。サーバー側コードを動かす場合はすべてJavascript経由でWebサービスに頼ることになります。

<body>
<!-- <form runat="server" id="form1"> –>
<div>
<asp:ContentPlaceHolder ID="Contents" runat="server">
</asp:ContentPlaceHolder>
</div>
<!-- </form> –>
</body>
 
 

Formはdata-role=”page”の下に、データバインドはuniqueNameを追加する。

jQUeryValidationを動作させるにはform要素が必要になるんだけれど、jQueryMobileのページ遷移の特性上、data-role=”page”以下に配置された要素でないと、2ページ目以降は無視されてしまいます。なので、コンテンツページ側では、data-role=”page”以下に何もしないValidationのためのダミーFormを追加します。input要素にはclassにjQuery Validationの検証属性を設定します。

ここで注意したい点として、knockoutを併用する場合はデータバインド時にuniqueNameバインディングを追加してあげる事です。jQuery Validationは対象要素のidを必要とするので、このバインディングがないとエラーの表示時に混乱してしまうようです。

(あっ、、、あれ???uniqueNameバインディングは、複数ページに渡ってユニークな名前にしてくれるのかな???すべての検証が必要なコントロールに名前を付けなきゃいけないんだとしたら嫌だな。)

<asp:Content ID="Content1" ContentPlaceHolderID="Contents" runat="server">
<div data-role="page" id="FindCustomerView">
<form action="" id="FindCustomerViewForm">
<header data-role="header" data-position="fixed">
<h1>検証のテスト</h1>
</header>
<div data-role="content">
<div><label>名前</label>
<input type="text" class="required"
data-bind="text: name, uniqueName: true"/></div>
<div><label>メールアドレス</label>
<input type="email" class="required email"
data-bind="text: email, uniqueName: true"/></div>
<div><label>年齢</label>
<input type="number" class="number"
data-bind="text: age, uniqueName: true"/></div>
<input type="button" value="確定"/>
</div>
</form>
</div>
</asp:Content>

Javascript側では、viewModelを適応させるついでに、pageinitのタイミングで先程追加したFormに対してValidationを追加してあげます。
(function ($) {
karua.viewModel.topViewModel = function () {
this.name = ko.observable("");
this.email = ko.observable("");
this.age = ko.observable(0);
this.find = function () {
};
};
var viewModel;
$(document).on("pageinit", "#FindCustomerView", function (e) {
viewModel = new karua.viewModel.topViewModel();
ko.applyBindings(viewModel, document.getElementById("FindCustomerView"));
$("#FindCustomerViewForm").validate({ onsubmit: false });
});
})($);

そもそも

まぁモバイルでの値検証を、単純にデスクトップを想定した場合と同じUXでやっていいのか?という議論はまずあるだろうけれどね。

ソリューション作成とライブラリの追加(knockout,jQuery,jQueryMobile)

2012年5月21日 3件のコメント

WebFormでJavascript主体なスマートフォン向けサイトを作ろう その2

と言うことで、まずはWebフォームのプロジェクトを作って、もろもろのプラグインたちを参照するマスターページを作りましょう。

プロジェクトの作成

まずは新規プロジェクトで、空のWebアプリケーションを作成します。名前はMobileTestとかにしておきましょうか。

image

必要なスクリプトの追加

次にjQueryやjQuery Mobile、Knockoutといったスクリプトたちをダウンロードして追加していきます。

Visual StudioのPackage Manager Consoleを開いてNugetからプロジェクトに追加します。

PM> install-package jquery
PM> install-package jquery.mobile
PM> install-package knockoutjs

マスターページの追加

jQuery Mobileでは、ユーザーが最初に開いたエントリーページ以降は、ページ遷移時にヘッダータグを読み飛ばすので、すべてのページには同じヘッダータグを記述して、スクリプトやCSSの読み込み漏れが起こらないようにします。

ASP.NET WebFormでは、マスターページを利用すると簡単です。

image

先程追加した、Javascriptファイルたちをマスターページに追加していきます。

<%@ Master Language="VB" AutoEventWireup="false" CodeBehind="Site.master.vb" Inherits="MobileTest.Site" %>
<!DOCTYPE html>
<html>
<head runat="server">
    <title></title>    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1"/>
    <link type="text/css" rel="stylesheet" href="~/Content/jquery.mobile-1.1.0.min.css"/>
    <script src='<%= ResolveClientUrl("~/Scripts/jquery-1.7.2.min.js") %>' type="text/javascript" ></script>
    <script src='<%= ResolveClientUrl("~/Scripts/knockout-2.1.0.js") %>' type="text/javascript" ></script>
    <script src='<%= ResolveClientUrl("~/Scripts/jquery.mobile-1.1.0.min.js") %>' type="text/javascript" ></script> 
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>

トップページの追加

次に最初に表示する画面(Top.aspx)を追加します。最初に表示する画面は、必要なスクリプト等を読み込んだSite.Masterをマスターページとして利用します。

image image

<%@ Page Title="" AutoEventWireup="false" MasterPageFile="~/Site.Master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <div data-role="page" id="FindCustomerView">
        <header data-role="header" data-position="fixed">
            <h1>顧客検索</h1>
        </header>
        <div data-role="content">
            <input type="text"/>
            <input type="button" value="検索" />
        </div>
    </div>
</asp:Content>

画面の表示

とりあえずVisual Studioのデバック実行で画面を表示してみましょう。

こんなふうに表示されましたか?

image

現在のソリューションの状況

こんな感じです。

image

次回はViewModelの追加です。

jQuery Mobileでトースト通知

jQueryMobileでシステムからのメッセージを表示する場合に、ダイアログ表示ではなくトースト通知っぽい表示をしたかったのでこんな関数を作ります。

karua.ui.message.showToast = function (message) {
var box = $("<div class='ui-loader ui-overlay-shadow ui-body-a ui-corner-all'>"
+ message + "</div>")
.css({
"padding": "7px 25px 7px 25px",
"display": "block",
"opacity": 0.8
})
.appendTo($.mobile.pageContainer);

var left = Math.floor(($(window).width() – box.width()) / 2);
box.css({
"top": $(window).scrollTop() + 100,
"left": left
})
.delay(1500)
.fadeOut(400, function () {
$(this).remove();
});
};

ViewModel側でメッセージを表示するの良くない。とかはとりあえずおいておいて、こんなふうに呼び出せば。

karua.viewModel.topViewModel = function () {
this.keyword = ko.observable("");
this.find = function () {
if (this.keyword() == "") {
karua.ui.message.showToast("検索キーワードが空だよ");
return;
}
};
};

こんな感じで表示される。うーん。微妙に右によってますね。。。

image

「jQuery Mobile toast」「jQuery Mobile トースト」 とかだとあんまり引っかからなかったけれど、「jQuery Mobile メッセージ」「jQuery Mobile ポップアップ」とかだと同じ事やっている人結構引っかかるのね。トースト通知ってあんまりメジャーな言い方じゃないのかな。

プラグインになってないのかなーと思ったら、nugetにjquery.mobile.messageという名前で、トースト通知を行うjQueryのプラグインがすでに登録されてた。はぅー。

https://packages.nuget.org/packages/jQuery.Mobile.Message/1.6.9

WebFormでJavascript主体なスマートフォン向けサイトを作ろう

2012年5月8日 2件のコメント

いまさらですが、スマートフォン向けのWebサイトが旬ですね。

スマートフォン向けのWebサイトは、今までのモバイルサイトと違い、HTML5に対応した記述で行えることもあって、WebFormよりもHTMLを自由に記述できるASP.NET MVCのほうが相性が良いです。

逆にWebFormだとスマートフォン向けサイトを作る上でこのあたりが障害になります。

  • マスターページなどを利用すると、HTMLの要素のIDをフレームワークがつけてしまう
  • 一部のコントロールは、インラインスタイルをつけてしまうのでCSSが適用しにくい
  • 一部のリスト系のコントロールは、構造がテーブルになってしまうものがあって使いにくい

特にフレームワークにjQuery Mobileを利用する場合は、jQuery Mobileの制約ともろにぶつかってしまってやりにくいです。

  • AJAXでのページ遷移を利用する場合は、すべてのページで要素のIDを固有にしたほうが良い
  • JavascriptやCSSはすべてのページで同じ物を読み込むようにする

じゃぁ、サーバーコントロール使わなきゃいいんじゃね?ということでこんな感じでスマートフォン向けサイトを作って見る。

  • Formを用意しない
  • サーバーコントロールを使わない(マスターページ除く)
  • ASPXのコードビハインドを使わない
  • 認証はASP.NETのものを使う
  • 通信はすべてJavascriptとWCFを使った非同期通信
  • モバイル用のフレームワークはjQueryMobileを使う
  • HTMLとJavascriptのバインディングにはKnockoutを使う

WebFormの特徴を完全に無視していますね。もうMVCでいいんじゃね?という声が聞こえてきそうですが。とりあえずこれでやります。