如何使用jquery-mobile和knockoutjs构build一个webapp

我想build立一个移动应用程序,从没有更多,但HTML / CSS和JavaScript酿造。 虽然我对如何使用JavaScript构buildWeb应用程序有一个很好的理解,但我想我可能会看看像jquery-mobile这样的框架。

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

为了支持使用声明性html语法创build应用程序的方法,我认为将jquery-mobile与knockoutjs结合是一个很好的方法。 Knockoutjs是一个客户端MVVM框架,旨在将WPF / Silverlight中已知的MVVM超级权力带入JavaScript世界。

对我来说MVVM是一个新的世界。 虽然我已经阅读了很多,但我以前从来没有真正使用过它。

所以这篇文章是关于如何构build一个应用程序使用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!

当我开始考虑分层的应用程序,我有几件事情(例如可testing性,松耦合)。 我要告诉你我是如何决定拆分我的文件和评论的,比如为什么我select一个而不是另一个呢?

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是我的应用程序的入口点。 它创build了App对象并为视图模型提供了一个名称空间(即将推出)。 它监听jquery-mobile提供的mobileinit事件。

正如你所看到的,我正在创build某种ajax服务的实例(我们稍后会看到),并将其保存到variables“service”中。

我也连接主页的pagecreate事件,在这个事件中我创build了一个viewModel的实例,它传递了服务实例。这一点对我来说是非常重要的。 如果有人认为,这应该是不同的做法,请分享您的想法!

重点是,视图模型需要在服务上运行(GetTour /,SaveTour等)。 但是我不希望ViewModel更多地了解它。 例如,在我们的例子中,我只是传递一个模拟的ajax服务,因为后端尚未开发。

我应该提到的另一件事是ViewModel对实际视图没有任何知识。 这就是为什么我从pagecreate处理程序中调用ko.applyBindings(viewModel,this)。 我想保持视图模型与实际视图分离,以便于testing。

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”辅助对象。 基本上,这是一个品味的问题。 但是当你想有一个可观察的属性来引用另一个,你不能一次写下对象字面值,这使得它不那么对称。 这是我select不同语法的原因之一。

下一个原因是我可以像前面提到的那样作为parameter passing服务。

这个视图模型还有一件事,我不确定我是否select了正确的方式。 我想定期轮询ajax服务从服务器获取结果。 所以,我select实现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) 

好吧,我写了更多,因为我最初打算写。 我的手指受伤了,我的狗要求我带他们散步,我感到筋疲力尽。 我确定这里有很多东西缺less,我input了一些拼写错误和语法错误。 如果有什么不清楚的话,大声吼我,我会稍后更新。

张贴可能不是一个问题,但实际上是! 我想请你分享你对我的方法的想法,如果你认为这是好还是坏,或者我错过了什么。

UPDATE

由于这个post主要受欢迎,因为有几个人要求我这样做,所以我把这个例子的代码放在github上:

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

得到它,而它很热!

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

我正在做同样的事情(淘汰赛+ jQuery的手机)。 我正在试着写一篇关于我所学知识的博客文章,但同时也提供了一些指导。 请记住,我也试图学习淘汰赛/ jQuery的移动。

视图模型和页面

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

查看模型并单击

仅对视图模型点击事件使用ko.observable字段。

应用绑定一次

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

pagehide和ko.cleanNode

请记住清除页面隐藏的一些视图模型。 ko.cleanNode似乎干扰jQuery Mobiles渲染 – 导致它重新呈现的HTML。 如果在页面上使用ko.cleanNode,则需要删除数据angular色,并在源代码中插入呈现的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'); });