1. ホーム
  2. mvvm

[解決済み] jquery-mobileとknockoutjsを使ったWebアプリケーションの構築方法

2023-04-28 22:14:25

質問

html/cssとJavaScriptだけでモバイルアプリを作りたいと思っています。私は JavaScript で Web アプリを構築する方法について適切な知識を持っていますが、jquery-mobile のようなフレームワークに目を向けるかもしれないと思いました。

最初は、jquery-mobile はモバイルブラウザをターゲットにしたウィジェットフレームワークに過ぎないと思っていました。jquery-ui と非常によく似ていますが、モバイルの世界向けです。しかし、jquery-mobile はそれ以上であることに気づきました。多くのアーキテクチャを備え、宣言的なHTML構文でアプリを作成することができます。そのため、最も簡単に考えられるアプリでは、自分でJavaScriptを1行も書く必要がないのです(これはクールなことです。)

宣言的なhtml構文を使ってアプリを作るというアプローチをサポートするために、私はjquery-mobileとknockoutjsを組み合わせるのは良いテイクだと考えています。KnockoutjsはクライアントサイドのMVVMフレームワークで、WPF/Silverlightで知られているMVVMのスーパーパワーをJavaScriptの世界にもたらすことを目的としています。

私にとって、MVVMは新しい世界です。それについてはすでにたくさん読んでいますが、これまで実際に自分で使ったことはありません。

この投稿は、jquery-mobileとknockoutjsを一緒に使ってアプリをアーキテクチャする方法についてです。私のアイデアは、私が数時間見て思いついたアプローチを書き留め、jquery-mobile/knockoutのヨーダがコメントして、なぜそれが最悪なのか、なぜ私がそもそもプログラミングをするべきではないのかを教えてもらうことでした;-)。

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の話です。

アプリのレイヤリングについて考え始めたとき、私はいくつかのことを念頭に置いていました (たとえば、テスト容易性、疎結合など)。これから、私がどのようにファイルを分割することに決めたかをお見せし、なぜ別のものよりも1つのものを選択したのかというようなことをコメントしながら、進めていきます...

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オブジェクトを作成し、ビューモデルのための名前空間を提供します(近日公開予定)。また mobileinit イベントをリスンします。

見ての通り、ある種のajaxサービス(後で見ます)のインスタンスを作成し、変数 "service"に保存しています。

また pagecreate イベントをフックして、渡されたサービスインスタンスを取得する viewModel のインスタンスを作成します。この点は、私にとって重要なポイントです。もし誰かが、これは違う方法で行うべきだと考えているなら、あなたの考えを共有してください。

ポイントは、ビューモデルはサービス(GetTour/、SaveTourなど)に対して操作する必要があるということです。しかし、ViewModelにはそれ以上のことを知られたくないのです。ですから、例えば、私たちの場合、バックエンドがまだ開発されていないので、モック化したajaxサービスを渡すだけです。

もう一つ言っておかなければならないのは、ViewModelは実際のビューに関する知識を全く持っていないということです。そのため、私は ko.applyBindings(viewModel, this) を pagecreate ハンドラ内から呼び出しています。私は、テストしやすくするために、ビューモデルを実際のビューから分離しておきたかったのです。

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のビューモデルの例では、オブジェクトリテラルの構文を使っているものが多いですが、私は「自己」ヘルパーオブジェクトを使った伝統的な関数の構文を使っています。基本的には、好みの問題です。しかし、ある観測可能なプロパティから別のプロパティを参照させたい場合、オブジェクトリテラルを一度に書き出すことができないので、対称性が悪くなってしまいます。それが、私が別の構文を選択する理由の一つです。

次の理由は、先程のパラメータとして渡すことができるサービスです。

このビューモデルにはもう一つ、私が正しい方法を選択したかどうかわからないものがあります。私は、サーバーから結果を取得するために定期的にajaxサービスをポーリングしたいと思います。そこで、私は実装することにしました。 startServicePolling / stopServicePolling メソッドで行います。アイデアは、pageshowでポーリングを開始し、ユーザが別のページに移動したときにそれを停止することです。

サービスをポーリングするために使用される構文は無視してもかまいません。これはRxJSのマジックです。ただ、私がポーリングしていることを確認し、返された結果でobservableプロパティを更新してください。 Subscribe(関数(統計){...}) の部分です。

App.MockedStatisticsService.js

さて、残すは1つだけです。それは、実際のサービスの実装です。ここではあまり詳しくは説明しません。このモックでは を取得すると数値を返すモックです。 が呼ばれたときにいくつかの数値を返すモックです。もうひとつのメソッド 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)

よし、当初予定していたよりだいぶ書きました。私の指は痛み、私の犬はそれらを散歩に連れて行くために私に尋ねるし、私は疲れを感じています。私はここに欠けている多くのものがあると確信していますし、私は誤字や文法のミスの束を置くこと。もし何かが明確でないなら、私に教えてください。

この投稿は質問ではないように見えるかもしれませんが、実はそうなのです! 私のアプローチについて、それが良いのか悪いのか、あるいは私が見逃しているものがあるのか、あなたの考えを共有してほしいと思います。

アップデイト

この投稿が大きな反響を呼んだことと、何人かの人からそうするように頼まれたので、この例のコードをgithubに置きました。

https://github.com/cburgdorf/stackoverflow-knockout-example

熱いうちに買え!

解決するには?

<ブロッククオート

注意してください。 jQuery 1.7以降では .live() メソッドは非推奨です。使用方法 .on() を使用してイベントハンドラをアタッチします。古いバージョンの jQuery を使用している場合は、イベントハンドラとして .delegate() よりも .live() .

私も同じようなこと(ノックアウト+jquery mobile)に取り組んでいます。私は学んだことについてブログ記事を書こうとしていますが、ここではその間のいくつかのポインタを紹介します。私もknockout/jquery mobileを学ぼうとしていることを忘れないでください。

ビューモデルとページ

jQuery Mobileのページには、1つのビュー・モデル・オブジェクトしか使用しないでください。さもなければ、クリックイベントが複数回トリガーされる問題が発生する可能性があります。

ビューモデルとクリック

ビューモデルのクリックイベントには ko.observable-fields のみを使用します。

ko.applyBindingを一度使用する。

可能であれば、各ページで一度だけko.applyBindingを呼び、ko.observableを複数回呼び出す代わりに使用します。

ページハイドとko.cleanNode

pagehideでビューモデルをクリーンアップすることを忘れないでください。 ko.cleanNodeはjQuery Mobilesのレンダリングを妨害するようで、htmlを再レンダリングする原因になります。もし、ページ上でko.cleanNodeを使用する場合は、data-roleを削除し、ソースコードにレンダリングされたjQuery Mobileのhtmlを挿入する必要があります。

$('#field').live('pagehide', function() {
    ko.cleanNode($('#field')[0]);
});

ページハイドとクリック

クリックイベントにバインドする場合、.ui-btn-active をクリーンアップすることを忘れないでください。これを実現する最も簡単な方法は、このコードスニペットを使うことです。

$('[data-role="page"]').live('pagehide', function() {
    $('.ui-btn-active').removeClass('ui-btn-active');
});