【发布时间】: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