【问题标题】:How to architecture a webapp using jquery-mobile and knockoutjs如何使用 jquery-mobile 和 knockoutjs 构建 webapp
【发布时间】:2011-08-30 16:33:36
【问题描述】:

我想构建一个移动应用程序,只使用 html/css 和 JavaScript。虽然我对如何使用 JavaScript 构建 Web 应用程序有相当的了解,但我想我可能会研究一下 jquery-mobile 之类的框架。

起初,我认为 jquery-mobile 只不过是一个针对移动浏览器的小部件框架。与 jquery-ui 非常相似,但适用于移动世界。但我注意到 jquery-mobile 不止于此。它带有一堆架构,让您可以使用声明性 html 语法创建应用程序。因此,对于最容易想到的应用程序,您不需要自己编写一行 JavaScript(这很酷,因为我们都喜欢少工作,不是吗?)

为了支持使用声明式 html 语法创建应用程序的方法,我认为将 jquery-mobile 与 knockoutjs 结合起来是一个不错的选择。 Knockoutjs 是一个客户端 MVVM 框架,旨在将来自 WPF/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 方法来做到这一点。这个想法是在 pageshow 上启动轮询,并在用户导航到不同页面时停止它。

您可以忽略用于轮询服务的语法。这是 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)

好的,我写的比我最初计划写的要多。我的手指受伤了,我的狗要我带它们去散步,我感到筋疲力尽。我敢肯定这里缺少很多东西,而且我输入了一堆拼写错误和语法错误。如果有什么不清楚的地方就骂我,我稍后会更新帖子。

这个帖子可能看起来不是一个问题,但实际上它是!我希望您分享您对我的方法的看法,以及您认为它是好是坏,或者我是否遗漏了一些东西。

更新

由于这篇文章获得了很大的人气,并且因为有几个人要求我这样做,所以我把这个例子的代码放在了 github 上:

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

趁热吃!

【问题讨论】:

  • 我不确定是否有足够具体的问题供人们解决。我喜欢你在这里的细节,但它似乎有助于讨论。简而言之:“不错的博客”;)
  • 很高兴你喜欢它。我有点担心我写了这么多,以至于人们害怕写一个简短的答案。但是,欢迎任何讨论。如果 stackoverflow 不是开始讨论的地方,我们可以切换到 google 群组:groups.google.com/forum/#!topic/knockoutjs/80_FuHmCm1s
  • 嗨,Christoph,这种方法对您来说效果如何?
  • 实际上,我转向了更棒的 AngularJS 框架 ;-)
  • 如果您只保留前几段作为问题,然后将其余部分移到自我回答中,这可能会更好。

标签: mvvm jquery-mobile knockout.js


【解决方案1】:

注意:从 jQuery 1.7 开始,.live() 方法已被弃用。使用.on() 附加事件处理程序。旧版本 jQuery 的用户应该使用.delegate() 而不是.live()

我正在做同样的事情(淘汰赛 + jquery mobile)。我正在尝试写一篇关于我所学知识的博客文章,但同时这里有一些指示。请记住,我也在尝试学习 knout/jquery mobile。

视图模型和页面

每个 jQuery Mobile 页面仅使用一 (1) 个视图模型对象。否则,您可能会遇到多次触发的点击事件的问题。

查看模型并点击

仅将 ko.observable-fields 用于视图模型的点击事件。

ko.applyBinding 一次

如果可能:每个页面只调用一次 ko.applyBinding 并使用 ko.observable 的而不是多次调用 ko.applyBinding。

pagehide 和 ko.cleanNode

记得在 pagehide 上清理一些视图模型。 ko.cleanNode 似乎干扰了 jQuery Mobiles 渲染 - 导致它重新渲染 html。如果您在页面上使用 ko.cleanNode,您需要删除数据角色并在源代码中插入呈现的 jQuery Mobile html。

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

页面隐藏并点击

如果您绑定到点击事件 - 请记住清理 .ui-btn-active。完成此操作的最简单方法是使用以下代码 sn-p:

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

【讨论】:

  • 由于我的问题非常不具体,而您是在回答中付出最多努力的人,因此我会将您的回答设为可接受的答案。
  • 你有没有想过这个问题?我在集成 KO 和 JQM 方面度过了一段地狱般的时光,并且没有关于如何做到这一点的好的指南(或演示端到端演示的 jsFiddle)。
  • 不,我转移到了 AngularJS 框架。我发现它优于 KO。还有一个非常好的适配器项目可以让 AngularJS/jqm 成为永远的好朋友:github.com/tigbro/jquery-mobile-angular-adapter 但是,就我目前所做的而言,使用该适配器似乎有点矫枉过正。毕竟使用 jqm 的 html/css 并将控件转换为 Angular 指令非常容易:jsfiddle.net/zy7Rg/7
  • 你可以创建一个我定义的结构here。我相信这样您就可以完全控制应用程序。
猜你喜欢
  • 2013-07-16
  • 2012-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-17
  • 1970-01-01
  • 2013-02-27
  • 2011-11-20
相关资源
最近更新 更多