HTML5モバイルアプリ:バグまたは成功。 試行番号0

数年間、Web開発の世界のニュースやイベントを読んで、私はピンクの夢を見ました。一度書いただけで、いつでもどこでも動作します。 同時に、HTML5でのモバイルアプリケーションの開発に関する否定的なレビューをよく目にします( ここと記事1および2のコメント)。 ストライカーの主な論点:ネイティブインターフェイスとの不整合、グリッチとスローダウン、データストレージの問題など。 このトピックで別のホーリーバラを立ち上げることは決してありません。 しかし、夢は存続し、レーキに対する自分の攻撃の後にのみ確認または拒否することができます。

したがって、目標は、小売店の販売代理店とHTML5で注文を収集するためのモバイルアプリケーションを作成することです。 私はさまざまな企業のこれらの決定に出くわしたので、主題分野に精通しており、このトピックは夢に理想的です。



主な要件に、私自身の経験からいくつかのメモを追加します。



小さなメモ:この記事は、新技術の研究の対象となる資料を統合するために書かれました。 この種のアプリケーションの作成における実際の経験が完全に不足しているため、可能性のある欠陥について事前に謝罪します。



高度なアーキテクチャ:


バックエンド -ODataを使用した.net MVC。 世界的には、この役割で何を使用するかは問題ではありません。主なことは、新しいWEB API標準に準拠することです。 フロントエンド -私にとってすべてが複雑です。 経験がない場合、何かを選択することは非常に困難です。 いくつかのブラウジングの後、 PhoneJSに決めました 。 これはSPAアプリケーションの本格的なフレームワークであるという事実に感銘を受けたため、できるだけ多くのライブラリをリンクする必要はなく、knockoutjsを使用する必要もありません。 データを扱うために、風を使うことにしました。 リストは開発プロセス中に変更されると確信しています。 次に、すべてをPhoneGapでパックして、アプリケーションの外観を取得します。

この記事では、最初に簡単なものを作成します。販売代理店の特定のルートにある販売時点情報のデータを表示します。



プロジェクトを作成します。


新しいASP.NET MVC 4 Webアプリケーションプロジェクトを作成し、「 MSales 」と呼びます。 [ 新しいASP.NET MVC 4プロジェクト ]ダイアログWeb APIテンプレートを選択します

パッケージUpdate-Package knockoutjs Update-Package jQuery



、インストール: Install-Package Breeze.WebApi Install-Package datajs





残念ながら、PhoneJS用のパッケージはないため、ペンを使用して、必要なすべての cssとjsをプロジェクトに追加します 。 選択できるレイアウトタイプはいくつかありますが、 _Layout.cshtmlファイルを変更してNavbarLayoutを使用しました。

 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> @Styles.Render("~/Content/css") @Styles.Render("~/Content/dx") @Styles.Render("~/Content/layouts") @Scripts.Render("~/bundles/modernizr") </head> <body> @Html.Partial("NavbarLayout") @RenderBody() @Scripts.Render("~/bundles/jquery") @RenderSection("scripts", required: false) </body> </html>
      
      





BundleConfigファイルには、すべてのコンテンツとスクリプトを記述します。 私はこのようになった:

Bundleconfig
 //    bundles.Add(new ScriptBundle("~/bundles/knockout").Include( "~/Scripts/knockout-{version}.js")); bundles.Add(new ScriptBundle("~/bundles/breeze").Include( "~/Scripts/q.js", "~/Scripts/datajs-{version}.js", "~/Scripts/breeze.debug.js" )); bundles.Add(new ScriptBundle("~/bundles/dx").Include( "~/Scripts/dx.phonejs.js", "~/Scripts/globalize" )); bundles.Add(new ScriptBundle("~/bundles/app").Include( "~/Scripts/App/app.init.js", "~/Scripts/App/app.viewmodel.js", "~/Scripts/App/NavbarLayout.js" )); bundles.Add(new StyleBundle("~/Content/dx").Include("~/Content/dx/dx.*")); bundles.Add(new StyleBundle("~/Content/layouts").Include("~/Content/layouts/NavbarLayout.css"));
      
      









モデルとコントローラー


現時点では、2つのファイルをモデルに含めます:ルートのクラス(販売代理店はこれらのルートに沿って移動します)とアウトレット(店舗):

モデル
 public class Route { public int RouteID { get; set; } [Required] [StringLength(30)] public string RouteName { get; set; } } public class Customer { public int CustomerID { get; set; } [Required] [StringLength(50)] public string CustomerName { get; set; } [StringLength(150)] public string Address { get; set; } public string Comment { get; set; } [ForeignKey("Route")] public int RouteID { get; set; } virtual public Route Route { get; set; } }
      
      







コントローラーは非常にシンプルです(ODataの詳細については、 こちらをご覧ください )。

コントローラー
 public class RoutesController : EntitySetController<Route, int> { private MSalesContext db = new MSalesContext(); public override IQueryable<Route> Get() { return db.Routes; ; } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); } } public class CustomersController : EntitySetController<Customer, int> { private MSalesContext db = new MSalesContext(); public override IQueryable<Customer> Get() { return db.Customers; ; } protected override Customer GetEntityByKey(int key) { return db.Customers.FirstOrDefault(p => p.CustomerID == key); } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); } }
      
      









WebApiConfigファイルの小さなストローク:
 public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Routes.MapODataRoute("odata", "odata", GetEdmModel()); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.EnableQuerySupport(); config.EnableSystemDiagnosticsTracing(); } public static IEdmModel GetEdmModel() { ODataModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet<Route>("Routes"); builder.EntitySet<Customer>("Customers"); builder.Namespace = "MSales.Models"; return builder.GetEdmModel(); } }
      
      







ODataプロトコルのルートを登録する場合、次の行を指定する必要がありますbuilder.Namespace = "MSales.Models";



breezeおよびdatajsライブラリが機能するために必要です。



フロントエンド


Scripts / appフォルダーで、スクリプトファイルapp.init.jsを作成してライブラリーを初期化します。

 window.MyApp = {}; $(function () { MyApp.app = new DevExpress.framework.html.HtmlApplication({ namespace: MyApp, defaultLayout: "navbar", navigation: [ { title: "Routes", action: "#route", icon: "home" }, { title: "About", action: "#about", icon: "info" } ] }); MyApp.app.router.register(":view/:id", { view: "route", id: 0 }); MyApp.app.navigate(); var serverAddress = "/odata/"; breeze.config.initializeAdapterInstances({ dataService: "OData" }); MyApp.manager = new breeze.EntityManager(serverAddress); });
      
      





レイアウトとナビゲーションパラメーターを指定するHTMLアプリケーションを作成します。このパラメーターは、routesとaboutの2つのポイントで構成されます。 breezeライブラリを初期化します。

Index.cshtmlファイルに 、dxViewと、通常のリストを表示する「コンテンツ」と呼ばれる特別な領域を配置する必要があります。

 <div data-options="dxView : { name: 'route', title: 'Routes' } " > <div class="route-view" data-options="dxContent : { targetPlaceholder: 'content' } " > <div data-bind="dxList: { dataSource: dataSource }"> <div data-options="dxTemplate : { name: 'item' }" data-bind="text: RouteName, dxAction: '#customers/{RouteID}'"/> </div> </div> </div>
      
      





これらの数行を機能させるには、Viewmodelを作成する必要があります。そのため、 Scripts / appフォルダーにapp.viewmodel.jsファイルを作成します。

 MyApp.route = function (params) { var viewModel = { dataSource: { load: function (loadOptions) { if (loadOptions.refresh) { var deferred = new $.Deferred(); var query = breeze.EntityQuery.from("Routes").orderBy("RouteID"); MyApp.manager.executeQuery(query, function (result) { deferred.resolve(result.results); }); return deferred; } } } } return viewModel; };
      
      





Viewmodelという名前がdxViewという名前と一致し、dataSourceオブジェクトのみが含まれていることに注意してください。dataSourceオブジェクトには、データを読み込むための1つの読み込みメソッドを定義します。 更新パラメータは、ウィジェットデータを完全に更新するかどうかを決定します。 このメソッドでは、RouteIDフィールドでソートしてリクエストを作成し、実行します。

別のビューを追加- 概要

  <div data-options="dxView : { name: 'about', title: 'About' } "> <div data-options="dxContent : { targetPlaceholder: 'content' } "> <div data-bind="dxScrollView: {}"> <p style="padding: 5px">This is my first SPA application.</p> </div> </div> </div>
      
      





iPhoneの結果:

画像

おそらく、 dxAction: '#customers/{RouteID}'



イベントがリストdxAction: '#customers/{RouteID}'



でハングしていることに気づいたでしょう。ここで、指定されたナビゲーションによれば、 '#customers



は呼び出されたビューで、 RouteID



はこのビューに渡されるパラメーターです。

 <div data-options="dxView : { name: 'customers', title: 'Customers' } " > <div data-bind="dxCommand: { title: 'Search', placeholder: 'Search...', location: 'create', icon: 'find', action: find }" ></div> <div data-options="dxContent : { targetPlaceholder: 'content' } " > <div data-bind="dxTextbox: { mode: 'search', value: searchString, visible: showSearch, valueUpdateEvent: 'search change keyup' }"></div> <div data-bind="dxList: { dataSource: dataSource }"> <div data-options="dxTemplate : { name: 'item' } " data-bind="text: name, dxAction: '#customer-details/{id}'"/> </div> </div> </div>
      
      





多くのバイヤーが存在する可能性があるため、検索機能を追加しました。dxCommand- 検索機能を呼び出す検索ボタン、およびリストの前の入力フィールドを追加しました。

ビューモデル:

 MyApp.customers = function (params) { var skip = 0; var PAGE_SIZE = 10; var viewModel = { routeId: params.id, searchString: ko.observable(''), showSearch: ko.observable(false), find: function () { viewModel.showSearch(!viewModel.showSearch()); viewModel.searchString(''); }, dataSource: { changed: new $.Callbacks(), load: function (loadOptions) { if (loadOptions.refresh) { skip = 0; } var deferred = new $.Deferred(); var query = breeze.EntityQuery.from("Customers") .where("CustomerName", "substringof", viewModel.searchString()) .where("RouteID", "eq", viewModel.routeId) .skip(skip) .take(PAGE_SIZE) .orderBy("CustomerID"); MyApp.manager.executeQuery(query, function (result) { skip += PAGE_SIZE; console.log(result); var mapped = $.map(result.results, function (data) { return { name: data.CustomerName, id: data.CustomerID } }); deferred.resolve(mapped); }); return deferred; } } }; ko.computed(function () { return viewModel.searchString(); }).extend({ throttle: 500 }).subscribe(function () { viewModel.dataSource.changed.fire(); }); return viewModel; };
      
      





データの一部(この場合は10レコード)をロードするにはskip 変数PAGE_SIZE変数が必要です。必要に応じて追加のロードが行われます。

検索用の変数searchStringshowSearch 。文字を入力してから0.5秒の遅延で検索がトリガーされます。

結果:

画像

最後に、選択した購入者に関する情報を表示します。

表示:

 <div data-options="dxView : { name: 'customer-details', title: 'Product' } " > <div data-options="dxContent : { targetPlaceholder: 'content' } " > <div class="dx-fieldset"> <div class="dx-field"> <div class="dx-field-label">Id: </div> <div class="dx-field-value" data-bind="text: id"></div> </div> <div class="dx-field"> <div class="dx-field-label">Name: </div> <div class="dx-field-value" data-bind="text: name"></div> </div> <div class="dx-field"> <div class="dx-field-label">Address: </div> <div class="dx-field-value" data-bind="text: address"></div> </div> <div class="dx-field"> <div class="dx-field-label">Comment: </div> <div class="dx-field-value" data-bind="text: comment"></div> </div> </div> </div> </div>
      
      





ViewModel:

 MyApp['customer-details'] = function (params) { var viewModel = { id: parseInt(params.id), name: ko.observable(''), address: ko.observable(''), comment:ko.observable('') }; var data = MyApp.manager.getEntityByKey("Customer", viewModel.id); console.log(data); viewModel.name(data.CustomerName()); viewModel.address(data.Address()); viewModel.comment(data.Comment()); return viewModel; };
      
      





画像

注:スクリーンショットは、Ripple Emulator(Beta)エミュレーターから取得されます。



まとめ


ナビゲーションとデータのロードを備えたモバイルデバイス用の非常にシンプルな本格的なSPAアプリケーションを取得しました。 現時点では、品質/速度/などを判断することは困難です。 次の記事では、機能を少し拡張し、Azureにレイアウトして、誰もが試せるようにします。



All Articles