我想建立一个移动应用程序,酿造了没有更多的 HTML/css 和 JavaScript。虽然我对如何使用 JavaScript 构建 Web 应用程序有一定的了解,但我认为我可能会研究一下 jquery-mobile 这样的框架。
起初,我认为 jquery-mobile 只不过是一个针对移动浏览器的小部件框架。与 jquery-ui 非常相似,但是适用于移动领域。但是我注意到 jquery-mobile 不仅仅是这样。它提供了一系列的体系结构,让您可以使用声明式 html 语法来创建应用程序。因此,对于最容易想到的应用程序,你不需要自己编写一行 JavaScript (这很酷,因为我们都喜欢少工作,不是吗?)
为了支持使用声明式 html 语法创建应用程序的方法,我认为将 jquery-mobile 与 linkoutjs 结合起来是一个很好的尝试。Knockoutjs 是一个客户端 MVVM 框架,旨在将 WPF/Silverlight 提供的超级 MVVM 引入 JavaScript 世界。
对我来说 MVVM 是一个新的世界。虽然我已经读了很多关于它的东西,但是我以前从来没有真正使用过它。
因此,这篇文章是关于如何使用 jquery-mobile 和 linkoutjs 一起构建一个应用程序。我的想法是写下我看了几个小时之后想到的方法,然后用一些 jquery-mobile/淘汰尤达大师来评论它,告诉我为什么它很糟糕,为什么我一开始就不应该做编程; -)
超文本标记语言
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 提供的 移动事件。
正如您所看到的,我正在创建某种 Ajax 服务的实例(稍后我们将介绍) ,并将其保存到变量“ service”中。
我还为主页挂接了 页面创建事件,在这个主页中,我创建了一个 viewModel 实例来获取传入的服务实例。这一点对我很重要。如果有人认为,这应该是不同的做法,请分享你的想法!
重点是,视图模型需要在服务上操作(GetTour/、 SaveTour 等)。但是我不希望 ViewModel 对此有更多的了解。例如,在我们的例子中,我只是传入了一个模拟的 ajax 服务,因为后端还没有开发。
另一件我应该提到的事情是 ViewModel 对实际的视图一无所知。这就是我在 页面创建处理程序中调用 ko.applicyBindings (viewModel,this)的原因。我希望保持视图模型与实际视图分离,以便更容易测试它。
应用程序。视图模型。 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)
虽然您会发现大多数敲出 js 视图模型示例都使用对象文本语法,但我使用的是传统函数语法和“ self”helper 对象。基本上,这就是品味的问题。但是,当您想要一个可观察的属性来引用另一个时,您不能一次写下对象文字,这使得它不那么对称。这就是我选择不同语法的原因之一。
下一个原因是我可以像前面提到的那样将服务作为参数传递。
这个视图模型还有一个问题,我不确定我是否选择了正确的方式。我希望定期轮询 ajax 服务,以便从服务器获取结果。因此,我选择实现 StartServicePolling/停止服务轮询方法来完成这项工作。其思想是在页面显示上启动轮询,并在用户导航到不同页面时停止轮询。
您可以忽略用于轮询服务的语法。这是 RxJS 魔法。只要确保我正在轮询它,并用返回的结果更新可观察的属性,就像您在 订阅(函数(统计){ . . })部分看到的那样。
应用程序: MockedStatistics 服务
好了,还有一样东西要给你看。它是实际的服务实现。我不想说太多细节。它只是一个调用 GetStatistics时返回一些数字的 mock。还有另一个方法 模拟统计,我用它在应用程序运行时通过浏览器 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
趁热吃!