したがって、目標は、小売店の販売代理店とHTML5で注文を収集するためのモバイルアプリケーションを作成することです。 私はさまざまな企業のこれらの決定に出くわしたので、主題分野に精通しており、このトピックは夢に理想的です。
主な要件に、私自身の経験からいくつかのメモを追加します。
- プログラムは、多くのデバイスと異なるプラットフォームで動作するはずです。 通常、企業、特に大企業は、すでに多数のモバイルデバイスを所有しています。 配給会社の中には、自分の電話を使用することを強制するものもあります(いわば、自発的な強制BYOD )。
- オフライン作業をサポートします。 残念ながら、インターネットのカバレッジは貧弱です。 ネイティブソリューションは、この問題をうまく処理します。
- プログラムは簡単に拡張できる必要があります。 何らかの理由で、そのようなソリューションのサプライヤには、通常のバージョン更新の問題があります。
- 鉄の使用(カメラ、GPS)。
小さなメモ:この記事は、新技術の研究の対象となる資料を統合するために書かれました。 この種のアプリケーションの作成における実際の経験が完全に不足しているため、可能性のある欠陥について事前に謝罪します。
高度なアーキテクチャ:
バックエンド -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変数が必要です。必要に応じて追加のロードが行われます。
検索用の変数searchStringとshowSearch 。文字を入力してから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にレイアウトして、誰もが試せるようにします。