私はhtml / cssとJavaScriptだけから作成されたモバイルアプリを構築したいと考えています。JavaScriptを使用してWebアプリを構築する方法についてはかなりの知識がありますが、jquery-mobileなどのフレームワークを調べたほうがいいと思いました。
最初は、jquery-mobileはモバイルブラウザーをターゲットとするウィジェットフレームワークにすぎないと思っていました。jquery-uiによく似ていますが、モバイルの世界向けです。しかし、jquery-mobileはそれ以上のものであることに気付きました。多数のアーキテクチャが付属しており、宣言型のHTML構文でアプリを作成できます。したがって、最も簡単に考えられるアプリの場合、JavaScriptを1行で自分で書く必要はありません(これはクールです。
宣言的な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メソッドを実装することを選択しました。アイデアは、pageshowでポーリングを開始し、ユーザーが別のページに移動したときにポーリングを停止することです。
サービスのポーリングに使用される構文は無視できます。それはRxJSの魔法です。Subscribe(function(statistics){..})の部分で確認できるように、それをポーリングし、返された結果で監視可能なプロパティを更新していることを確認してください。
App.MockedStatisticsService.js
わかりました。あと1つだけお見せしましょう。実際のサービスの実装です。ここでは詳しく説明しません。getStatisticsが呼び出されたときにいくつかの数値を返すのは単なるモックです。アプリの実行中にブラウザーのjsコンソールを介して新しい値を設定するために使用する別のメソッドmockStatisticsがあります。
(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)初めに書こうと思っていたので、もっとたくさん書いた。指が痛くて、犬が散歩に連れて行ってくれと言ってきました。ここには足りないものがたくさんあると思いますし、私はたくさんのタイプミスと文法の間違いを犯しました。不明な点がある場合は私に怒鳴ってください。投稿は後で更新します。
投稿は質問のようには見えないかもしれませんが、実際はそうです!私のアプローチについてのあなたの考えと、それが良いか悪いか、または私が物を見落としているかどうかを教えてください。
更新
この投稿の人気が高まったことと、何人かから依頼があったため、この例のコードをgithubに配置しました。
https://github.com/cburgdorf/stackoverflow-knockout-example
暑いうちにゲット!