[mvvm] 如何使用jquery-mobile和knockoutjs構建webapp



Answers

Question

我想建立一個移動應用程序,從沒有更多,但HTML / CSS和JavaScript釀造。 雖然我對如何使用JavaScript構建Web應用程序有一定的了解,但我想我可能會看看像jquery-mobile這樣的框架。

起初,我認為jquery-mobile不過是一個針對移動瀏覽器的小部件框架。 與jquery-ui非常相似,但是適用於移動世界。 但我注意到,jquery-mobile不止於此。 它帶有一堆體系結構,讓我們使用聲明性html語法創建應用程序。 因此,對於最易於理解的應用程序,您不需要自己寫一行JavaScript代碼(這很酷,因為我們都喜歡少工作,是嗎?)

為了支持使用聲明性html語法創建應用程序的方法,我認為將jquery-mobile與knockoutjs結合是一件好事。 Knockoutjs是一個客戶端MVVM框架,旨在將WPVM / Silverlight中已知的MVVM超級權力帶入JavaScript世界。

對我來說MVVM是一個新的世界。 雖然我已經閱讀了很多關於它的內容,但我以前從未真正使用過它。

因此,本文將介紹如何使用jquery-mobile和knockoutjs一起構建應用程序。 我的想法是寫下我在看了幾個小時後想到的方法,並且有一些jquery-mobile / knockout yoda來評論它,告訴我它為什麼會糟糕,以及為什麼我不應該在第一次編程地方;-)

html

jquery-mobile在提供頁面的基本結構模型方面做得很好。 雖然我很清楚我可以讓我的頁面通過ajax後來加載,但我只是決定將它們全部保存在一個index.html文件中。 在這個基本的場景中,我們正在討論兩個頁面,所以不要太難以保持最佳狀態。

<!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”中。

我還連接主頁的pagecreate事件,我在其中創建了一個獲取服務實例傳入的viewModel的實例。這一點對我來說非常重要。 如果有人認為,這應該有所不同,請分享您的想法!

關鍵是,視圖模型需要在服務上運行(GetTour /,SaveTour等)。 但是我不希望ViewModel更多地了解它。 舉例來說,在我們的例子中,我只是傳遞一個模擬的ajax服務,因為後端尚未開發。

我應該提到的另一件事是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使用對象字面量語法來查看模型的例子,但我使用了傳統的函數語法和'self'輔助對象。 基本上,這是一個品味問題。 但是當你想讓一個可觀察屬性引用另一個屬性時,你不能一次寫下對象字面值,這使得它不那麼對稱。 這就是我選擇不同語法的原因之一。

下一個原因是我可以作為參數傳遞的服務,正如我前面提到的。

這個視圖模型還有一件事,我不確定我是否選擇了正確的方式。 我想定期輪詢ajax服務以從服務器獲取結果。 所以,我選擇實現startServicePolling / stopServicePolling方法。 這個想法是在頁面展示上開始輪詢,並在用戶導航到不同的頁面時停止它。

您可以忽略用於輪詢服務的語法。 這是RxJS的魔力。 只要確定我正在輪詢它並使用返回的結果更新可觀察屬性,就可以在訂閱(函數(統計){..})部分中看到。

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/-knockout-example

獲取它,而它很熱!




Related