如何使用 jquery-mobile 和 linkoutjs 构建 Web 应用程序

我想建立一个移动应用程序,酿造了没有更多的 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

趁热吃!

22963 次浏览

Note: As of jQuery 1.7, the .live() method is deprecated. Use .on() to attach event handlers. Users of older versions of jQuery should use .delegate() in preference to .live().

I'm working on the same thing (knockout + jquery mobile). I'm trying to write a blog post about what I've learned but here are some pointers in the meantime. Remember that I'm also trying to learn knockout/jquery mobile.

View-Model and Page

Only use one (1) view-model object per jQuery Mobile-page. Otherwise you can get problems with click-events that are triggered multiple times.

View-Model and click

Only use ko.observable-fields for view-models click-events.

ko.applyBinding once

If possible: only call ko.applyBinding once for every page and use ko.observable’s instead of calling ko.applyBinding multiple times.

pagehide and ko.cleanNode

Remember to clean up some view-models on pagehide. ko.cleanNode seems to disturb jQuery Mobiles rendering - causing it to re-render the html. If you use ko.cleanNode on a page you need to remove data-role’s and insert the rendered jQuery Mobile html in the source code.

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

pagehide and click

If you are binding to click-events - remember to clean up .ui-btn-active. The easiest way to accomplish this is using this code snippet:

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