ASPXに対するJavascriptのインテリセンスが有効にならないと思ったら、Resharperのせい?
Visual Studioを使っていると、外部Javascriptファイルのインテリセンスも結構頑張って出してくれるじゃないですか、WebFormsを使っていたりすると、ページ側にこんな変数を定義してページとは別に定義したJavascript側でこの変数を参照したりとかするわけですよ。
ASPX側(WebForm1.aspx)
<script type="text/javascript" src="<%= ResolveClientUrl("~/Scripts/jquery-1.9.0.js")%>"></script> <script type="text/javascript" src="<%= ResolveClientUrl("~/WebForm1.aspx.js")%>"></script> <script type="text/javascript"> var clientIds = { textBox1: "#<%= textBox1.ClientID %>", textBox2: "#<%= textBox2.ClientID %>", button1: "#<%= button1.ClientID %>" };
JS側(WebForm1.aspx.js)
/// <reference path="~/WebForm1.aspx"/> $(function() { $(clientIds.button1).on("click", function() { $(clientIds.textBox2).val($(clientIds.textBox1).val() + (new Date().toString())); }); });
JS側の1行目にreferenceディレクティブでASPXへのパスが指定されているので、javascript側でclientIdsのオブジェクト一覧がインテリセンスに出るはずなんです。MSDNにもそう書いてあります。。。が、なぜか出ないのです。どうもReSharper側の問題だったようです。ReSharperのオプション→InteliSense→GeneralからインテリセンスのタイプをVisual Studioにしたところ
ちゃんとclientIdsのプロパティーの一覧が候補に出てくれました!!やったー♪
だけれど、JSだけのために切り替えるのも微妙なんですよね。対応してくれないかなー。
jQueryからASP.NETのWCF Serviceの呼び出し
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」としておきます。
ここでは、ダミーの検索結果を返すメソッド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」としておきましょう。ちなみに、ソリューションはこんな感じで構成されています。
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に乗っているのかを確認して見ましょう。
今回の例では、WCFから返却されたリストの件数をalert関数で表示しています。aspxをデバック実行して、検索ボタンをクリックしてみましょう。サービスの結果件数(3件)が表示されたのが確認できます。
次回はViewModelの更新とViewのリスト表示です。
過去へのリンクはこちらから
Knockoutを利用したViewModelの追加
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として作成しておきます。
続いて、追加した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")); }); })($);
実行してみよう
実行されましたか?
次回はjavascriptのモデルからWCFの呼び出しです。
ソリューション作成とライブラリの追加(knockout,jQuery,jQueryMobile)
WebFormでJavascript主体なスマートフォン向けサイトを作ろう その2
と言うことで、まずはWebフォームのプロジェクトを作って、もろもろのプラグインたちを参照するマスターページを作りましょう。
プロジェクトの作成
まずは新規プロジェクトで、空のWebアプリケーションを作成します。名前はMobileTestとかにしておきましょうか。
必要なスクリプトの追加
次に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では、マスターページを利用すると簡単です。
先程追加した、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をマスターページとして利用します。
<%@ 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のデバック実行で画面を表示してみましょう。
こんなふうに表示されましたか?
現在のソリューションの状況
こんな感じです。
次回は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;
}
};
};
こんな感じで表示される。うーん。微妙に右によってますね。。。
「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主体なスマートフォン向けサイトを作ろう
いまさらですが、スマートフォン向けの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でいいんじゃね?という声が聞こえてきそうですが。とりあえずこれでやります。
何でもかんでもninifierしちゃうのはいけない?
Webプロジェクトに含まれるjavascriptは、チームビルド時に拡張子がjsだったらとりあえずajax minifierに食べさせているんだけれど、公開されているjavascriptライブラリでの場合、頭にライセンス情報なんかがあってこいつを削除して配布(?)しちゃうとライセンス違反になっちゃうんですよね。
まぁ何が言いたいかというと、公開されているjavascriptの場合、ほとんどが圧縮版を一緒に公開されているので、できるだけそっちを使うようにして、何でもかんでも圧縮しちゃうのは気をつけたほうがいいですよ。ってことでした。