Html/cssとJavaScript以外の何ものからも作成されないモバイルアプリを構築したいと思います。 JavaScriptを使用してWebアプリを構築する方法については十分な知識がありますが、jquery-mobileのようなフレームワークを検討する必要があると考えました。
最初は、jquery-mobileはモバイルブラウザを対象とするウィジェットフレームワークにすぎないと考えていました。 jquery-uiに非常に似ていますが、モバイルの世界向けです。しかし、jquery-mobileはそれ以上のものであることに気付きました。たくさんのアーキテクチャが付属しており、宣言的なHTML構文でアプリを作成できます。そのため、最も簡単な考えられるアプリの場合、1行のJavaScriptを自分で記述する必要はありません(これは素晴らしいことです。
宣言的なhtml構文を使用してアプリを作成するアプローチをサポートするには、jquery-mobileとknockoutjsを組み合わせるのが良いと思います。 Knockoutjsは、WPF/Silverlightで知られているMVVMの超能力をJavaScriptの世界にもたらすことを目的とした、クライアント側のMVVMフレームワークです。
私にとって、MVVMは新しい世界です。私はすでにそれについて多くのことを読んでいますが、実際に自分で使ったことはありません。
したがって、この投稿は、jquery-mobileとknockoutjsを一緒に使用してアプリをアーキテクチャ化する方法に関するものです。私のアイデアは、数時間見てから思いついたアプローチを書き留めて、jquery-mobile/knockout yodaにコメントしてもらい、それがなぜ吸うのか、なぜ最初にプログラミングをしてはいけないのかを示すことでした場所 ;-)
html
jquery-mobileは、ページの基本構造モデルを提供するのに適しています。後でページをajax経由でロードできることはよくわかっていますが、すべてを1つのindex.htmlファイルに保存することにしました。この基本的なシナリオでは、物事を把握するのが難しくないように2ページについて説明しています。
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<link rel="stylesheet" href="libs/jquery-mobile/jquery.mobile-1.0a4.1.css" />
<link rel="stylesheet" href="app/base/css/base.css" />
<script src="libs/jquery/jquery-1.5.0.min.js"></script>
<script src="libs/knockout/knockout-1.2.0.js"></script>
<script src="libs/knockout/knockout-bindings-jqm.js" type="text/javascript"></script>
<script src="libs/rx/rx.js" type="text/javascript"></script>
<script src="app/App.js"></script>
<script src="app/App.ViewModels.HomeScreenViewModel.js"></script>
<script src="app/App.MockedStatisticsService.js"></script>
<script src="libs/jquery-mobile/jquery.mobile-1.0a4.1.js"></script>
</head>
<body>
<!-- Start of first page -->
<div data-role="page" id="home">
<div data-role="header">
<h1>Demo App</h1>
</div><!-- /header -->
<div data-role="content">
<div class="ui-grid-a">
<div class="ui-block-a">
<div class="ui-bar" style="height:120px">
<h1>Tours today (please wait 10 seconds to see the effect)</h1>
<p><span data-bind="text: toursTotal"></span> total</p>
<p><span data-bind="text: toursRunning"></span> running</p>
<p><span data-bind="text: toursCompleted"></span> completed</p>
</div>
</div>
</div>
<fieldset class="ui-grid-a">
<div class="ui-block-a"><button data-bind="click: showTourList, jqmButtonEnabled: toursAvailable" data-theme="a">Tour List</button></div>
</fieldset>
</div><!-- /content -->
<div data-role="footer" data-position="fixed">
<h4>by Christoph Burgdorf</h4>
</div><!-- /header -->
</div><!-- /page -->
<!-- tourlist page -->
<div data-role="page" id="tourlist">
<div data-role="header">
<h1>Bar</h1>
</div><!-- /header -->
<div data-role="content">
<p><a href="#home">Back to home</a></p>
</div><!-- /content -->
<div data-role="footer" data-position="fixed">
<h4>by Christoph Burgdorf</h4>
</div><!-- /header -->
</div><!-- /page -->
</body>
</html>
JavaScript
それでは、楽しい部分-JavaScriptに来ましょう!
アプリの階層化について考え始めたとき、いくつかのことを念頭に置いていました(たとえば、テスト容易性、疎結合)。ファイルを分割することを決めた方法を説明し、移動中になぜあるものを別のものに選んだのかなどをコメントします...
App.js
var App = window.App = {};
App.ViewModels = {};
$(document).bind('mobileinit', function(){
// while app is running use App.Service.mockStatistic({ToursCompleted: 45}); to fake backend data from the console
var service = App.Service = new App.MockedStatisticService();
$('#home').live('pagecreate', function(event, ui){
var viewModel = new App.ViewModels.HomeScreenViewModel(service);
ko.applyBindings(viewModel, this);
viewModel.startServicePolling();
});
});
App.jsは、私のアプリのエントリポイントです。 Appオブジェクトを作成し、ビューモデルのネームスペースを提供します(近日中に)。 jquery-mobileが提供するmobileinitイベントをリッスンします。
ご覧のとおり、私はある種のajaxサービスのインスタンスを作成し(これについては後で説明します)、変数「service」に保存します。
また、サービスインスタンスを渡すviewModelのインスタンスを作成するホームページのpagecreateイベントをフックします。この点は私にとって不可欠です。誰かが考えている場合、これは別の方法で行う必要があります、あなたの考えを共有してください!
ポイントは、ビューモデルがサービス(GetTour /、SaveTourなど)で動作する必要があるということです。しかし、ViewModelがそれについてそれ以上知る必要はありません。したがって、たとえば、このケースでは、バックエンドがまだ開発されていないため、モックされたajaxサービスを渡しています。
もう1つ言及しなければならないのは、ViewModelには実際のビューに関する知識がないことです。そのため、pagecreateハンドラー内からko.applyBindings(viewModel、this)を呼び出しています。ビューモデルを実際のビューから分離して、テストしやすくしたかったのです。
App.ViewModels.HomeScreenViewModel.js
(function(App){
App.ViewModels.HomeScreenViewModel = function(service){
var self = {}, disposableServicePoller = Rx.Disposable.Empty;
self.toursTotal = ko.observable(0);
self.toursRunning = ko.observable(0);
self.toursCompleted = ko.observable(0);
self.toursAvailable = ko.dependentObservable(function(){ return this.toursTotal() > 0; }, self);
self.showTourList = function(){ $.mobile.changePage('#tourlist', 'pop', false, true); };
self.startServicePolling = function(){
disposableServicePoller = Rx.Observable
.Interval(10000)
.Select(service.getStatistics)
.Switch()
.Subscribe(function(statistics){
self.toursTotal(statistics.ToursTotal);
self.toursRunning(statistics.ToursRunning);
self.toursCompleted(statistics.ToursCompleted);
});
};
self.stopServicePolling = disposableServicePoller.Dispose;
return self;
};
})(App)
オブジェクトリテラル構文を使用してほとんどのknockoutjsビューモデルの例を見つけることができますが、「自己」ヘルパーオブジェクトで従来の関数構文を使用しています。基本的に、それは好みの問題です。しかし、1つの監視可能なプロパティを使用して別のプロパティを参照する場合、一度にオブジェクトリテラルを書き留めることはできません。それが、私が別の構文を選択している理由の1つです。
次の理由は、前に述べたように、パラメーターとして渡すことができるサービスです。
このビューモデルにはもう1つありますが、正しい方法を選択したかどうかはわかりません。サーバーから結果を取得するために、ajaxサービスを定期的にポーリングします。そのため、私はstartServicePolling/stopServicePollingメソッドを実装することを選択しました。アイデアは、ページショーでポーリングを開始し、ユーザーが別のページに移動したときにポーリングを停止することです。
サービスのポーリングに使用される構文は無視できます。それはRxJSの魔法です。 Subscribe(function(statistics){..})の部分でわかるように、必ずポーリングして、返された結果で監視可能なプロパティを更新してください。
App.MockedStatisticsService.js
さて、あなたに見せたいことが一つだけあります。実際のサービスの実装です。ここでは詳しく説明しません。 getStatisticsが呼び出されたときにいくつかの数値を返すだけのモックです。別のメソッドmockStatisticsがあります。このメソッドを使用して、アプリの実行中にブラウザーのjsコンソールから新しい値を設定します。
(function(App){
App.MockedStatisticService = function(){
var self = {},
defaultStatistic = {
ToursTotal: 505,
ToursRunning: 110,
ToursCompleted: 115
},
currentStatistic = $.extend({}, defaultStatistic);;
self.mockStatistic = function(statistics){
currentStatistic = $.extend({}, defaultStatistic, statistics);
};
self.getStatistics = function(){
var asyncSubject = new Rx.AsyncSubject();
asyncSubject.OnNext(currentStatistic);
asyncSubject.OnCompleted();
return asyncSubject.AsObservable();
};
return self;
};
})(App)
わかりました、最初に書くことを計画していたので、私はもっと多くを書きました。私の指が痛くて、私の犬は散歩に連れて行ってくれるように頼み、疲れました。ここに欠けているものがたくさんあると確信しているので、タイプミスや文法の間違いをたくさん犯しました。何かはっきりしないことがあれば、私に叫んでください。後で投稿を更新します。
投稿は質問とは思えないかもしれませんが、実際はそうです!私のアプローチについてのあなたの考えと、それが良いか悪いか、または私が物事を逃していると思うなら、あなたに意見を共有してほしい。
[〜#〜] update [〜#〜]
この投稿が人気を博したことと、何人かの人々が私にそうするように頼んだため、この例のコードをgithubに置きました。
https://github.com/cburgdorf/stackoverflow-knockout-example
暑いうちに入手しましょう!
注:jQuery 1.7以降、
.live()
メソッドは廃止されました。.on()
を使用して、イベントハンドラーをアタッチします。 jQueryの古いバージョンのユーザーは、.delegate()
よりも.live()
を優先して使用する必要があります。
私は同じことに取り組んでいます(knockout + jquery mobile)。私は自分が学んだことについてブログ投稿をしようとしていますが、その間にいくつかのポインタがあります。また、ノックアウト/ jqueryモバイルを学習しようとしていることを忘れないでください。
JQuery Mobileページごとに1つのビューモデルオブジェクトのみを使用します。そうしないと、複数回トリガーされるクリックイベントで問題が発生する可能性があります。
ビューモデルのクリックイベントに対してのみko.observable-fieldsを使用します。
可能であれば、ページごとに1回だけko.applyBindingを呼び出し、ko.applyBindingを複数回呼び出すのではなく、ko.observableを使用します。
ページハイドでいくつかのビューモデルを整理することを忘れないでください。 ko.cleanNodeがjQuery Mobileのレンダリングを妨害しているようです。これにより、htmlが再レンダリングされます。ページでko.cleanNodeを使用する場合は、データロールを削除し、レンダリングされたjQuery Mobile htmlをソースコードに挿入する必要があります。
$('#field').live('pagehide', function() {
ko.cleanNode($('#field')[0]);
});
Click-eventsにバインドしている場合-.ui-btn-activeをクリーンアップすることを忘れないでください。これを達成する最も簡単な方法は、次のコードスニペットを使用することです。
$('[data-role="page"]').live('pagehide', function() {
$('.ui-btn-active').removeClass('ui-btn-active');
});