如何使用jQuery-Mobile和Knockout.js构建Web应用程序 [英] How to architecture a webapp using jquery-mobile and knockoutjs

查看:86
本文介绍了如何使用jQuery-Mobile和Knockout.js构建Web应用程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想构建一个移动应用程序,该应用程序仅由html/css和JavaScript编写而成.虽然我对如何使用JavaScript构建Web应用程序有相当的了解,但我认为我可以研究一下jquery-mobile之类的框架.

I would like to build a mobile app, brewed from nothing more but html/css and JavaScript. While I have a decent knowledge of how to build a web app with JavaScript, I thought I might have a look into a framework like jquery-mobile.

起初,我认为jquery-mobile只是针对移动浏览器的小部件框架.与jquery-ui非常相似,但适用于移动世界.但我注意到jquery-mobile不仅如此.它带有许多体系结构,让您使用声明性html语法创建应用.因此,对于最容易思考的应用程序,您无需自己编写一行JavaScript(这很酷,因为我们都喜欢减少工作量,不是吗?)

At first, I thought jquery-mobile was nothing more then a widget framework which targets mobile browsers. Very similar to jquery-ui but for the mobile world. But I noticed that jquery-mobile is more than that. It comes with a bunch of architecture and let's you create apps with a declarative html syntax. So for the most easy thinkable app, you wouldn't need to write a single line of JavaScript by yourself (which is cool, because we all like to work less, don't we?)

为支持使用声明性html语法创建应用程序的方法,我认为将jquery-mobile与kickoutjs结合起来是一个好方法. Knockoutjs是一个客户端MVVM框架,旨在将WPF/Silverlight中已知的MVVM超级功能带入JavaScript世界.

To support the approach of creating apps using a declarative html syntax, I think it's a good take to combine jquery-mobile with knockoutjs. Knockoutjs is a client-side MVVM framework that aims to bring MVVM super powers known from WPF/Silverlight to the JavaScript world.

对我而言,MVVM是一个新世界.虽然我已经读了很多关于它的信息,但是我以前从未真正使用过它.

For me MVVM is a new world. While I have already read a lot about it, I have never actually used it myself before.

所以这篇文章是关于如何一起使用jquery-mobile和knockoutjs构建应用程序的.我的想法是写下看了几个小时后想到的方法,并用jquery-mobile/knockout yoda进行评论,向我展示为什么它很烂,为什么我不应该在第一本书中进行编程地方;-)

So this posting is about how to architecture an app using jquery-mobile and knockoutjs together. My idea was to write down the approach that I came up with after looking at it for several hours, and have some jquery-mobile/knockout yoda to comment it, showing me why it sucks and why I shouldn't do programming in the first place ;-)

HTML

jquery-mobile在提供页面的基本结构模型方面做得很好.虽然我很清楚以后可以通过ajax加载我的页面,但我只是决定将所有页面保存在一个index.html文件中.在这种基本情况下,我们谈论的是两页,这样就不难掌握最新信息.

jquery-mobile does a good job providing a basic structure model of pages. While I am well aware that I could have my pages to be loaded via ajax afterwards, I just decided to keep all of them in one index.html file. In this basic scenario we are talking about two pages so that it shouldn't be too hard to stay on top of things.

<!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!

So let's come to the fun part - the JavaScript!

当我开始考虑对应用程序进行分层时,我想到了几件事(例如,可测试性,松散耦合).我将向您展示我是如何决定分割文件并发表评论的,例如为什么我在走时为什么选择一件事而不是另一件事……

When I started to think about layering the app, I have had several things in mind (e.g. testability, loose coupling). I'm going to show you how I decided to split of my files and comment things like why did I choose one thing over another while I go...

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 事件.

App.js is the entry point of my app. It creates the App object and provides a namespace for the view models (soon to come). It listenes for the mobileinit event which jquery-mobile provides.

如您所见,我正在创建某种ajax服务的实例(我们将在稍后进行介绍)并将其保存到变量"service".

As you can see, I'm creating a instance of some kind of ajax service (which we will look at later) and save it to the variable "service".

我还为主页连接了 pagecreate 事件,在该事件中,我创建了一个ViewModel实例,该实例将服务实例传递进来.这一点对我来说至关重要.如果有人认为这应该以不同的方式进行,请分享您的想法!

I also hook up the pagecreate event for the home page in which I create an instance of the viewModel that gets the service instance passed in. This point is essential to me. If anybody thinks, this should be done differently, please share your thoughts!

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

The point is, the view model needs to operate on a service (GetTour/, SaveTour etc.). But I don't want the ViewModel to know any more about it. So for example, in our case, I'm just passing in a mocked ajax service because the backend hasn't been developed yet.

我应该提到的另一件事是ViewModel对实际视图的知识为零.这就是为什么我从 pagecreate 处理程序中调用ko.applyBindings(viewModel,this)的原因.我想将视图模型与实际视图分开,以使其更易于测试.

Another thing I should mention is that the ViewModel has zero knowledge about the actual view. That's why I'm calling ko.applyBindings(viewModel, this) from within the pagecreate handler. I wanted to keep the view model seperated from the actual view to make it easier to test it.

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)

虽然您会发现大多数使用对象文字语法的基因敲除视图模型示例,但我使用的是带有自我"帮助对象的传统函数语法.基本上,这是一个品味问题.但是,当您想拥有一个可观察的属性来引用另一个属性时,就不能一口气写下对象文字,这会使它的对称性降低.这就是我选择其他语法的原因之一.

While you will find most knockoutjs view model examples using an object literal syntax, I'm using the traditional function syntax with a 'self' helper objects. Basically, it's a matter of taste. But when you want to have one observable property to reference another, you can't write down the object literal in one go which makes it less symmetric. That's one of the reason why I'm choosing a different syntax.

下一个原因是我可以像前面提到的那样作为参数传递的服务.

The next reason is the service that I can pass on as a parameter as I mentioned before.

此视图模型还有另外一件事,我不确定是否选择了正确的方法.我想定期轮询ajax服务以从服务器获取结果.因此,我选择实现 startServicePolling / stopServicePolling 方法.想法是在页面显示上开始轮询,并在用户导航到其他页面时停止它.

There is one more thing with this view model which I'm not sure if I did choose the right way. I want to poll the ajax service periodically to fetch the results from the server. So, I have choosen to implement startServicePolling/stopServicePolling methods to do so. The idea is to start the polling on pageshow, and stop it when the user navigates to different page.

您可以忽略用于轮询服务的语法.这是RxJS的魔力.只需确保我正在轮询它,并使用返回的结果更新可观察的属性,就可以在 Subscribe(function(statistics){..})部分中看到.

You can ignore the syntax which is used to poll the service. It's RxJS magic. Just be sure I'm polling it and update the observable properties with the returned result as you can see in the Subscribe(function(statistics){..}) part.

App.MockedStatisticsService.js

好的,只剩下一件事要告诉你.这是实际的服务实现.我在这里不做详细介绍.这只是一个模拟,它会在调用 getStatistics 时返回一些数字.在应用运行时,还有另一种方法 mockStatistics ,我可以通过浏览器js控制台设置新值.

Ok, there is just one thing left to show you. It's the actual service implementation. I'm not going much into detail here. It's just a mock that returns some numbers when getStatistics is called. There is another method mockStatistics which I use to set new values through the browsers js console while the app is running.

(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)

好吧,我的写作比起初计划的要多得多.我的手指受伤了,我的狗要我带他们去散步,我感到筋疲力尽.我敢肯定,这里遗漏了很多东西,而且我犯了很多错别字和语法错误.如果不清楚,请对我大喊,稍后我将更新发布.

Ok, I wrote much more as I initially planned to write. My finger hurt, my dogs are asking me to take them for a walk and I feel exhausted. I'm sure there are plenty things missing here and that I put in a bunch of typos and grammer mistakes. Yell at me if something isn't clear and I will update the posting later.

发贴似乎不是问题,但实际上是一个问题!我想与您分享对我的方法的看法,以及您认为它的好坏还是我错过了一切.

The posting might not seem as an question but actually it is! I would like you to share your thoughts about my approach and if you think it's good or bad or if I'm missing out things.

更新

由于此帖子获得了广泛的欢迎,并且由于有几个人要求我这样做,所以我将该示例的代码放在了github上:

Due to the major popularity this posting gained and because several people asked me to do so, I have put the code of this example on github:

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

趁热拿它!

推荐答案

注意:从jQuery 1.7开始, .live() 方法不推荐使用.使用 .on() 附加事件处理程序.较旧版本的jQuery的用户应优先使用 .delegate() 而不是

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().

我正在做同样的事情(淘汰赛+ jQuery mobile).我正在尝试写一篇博文,介绍我所学到的知识,但与此同时,这里有一些提示.请记住,我也在尝试学习淘汰赛/jQuery mobile.

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.

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

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

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

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

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

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

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

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,然后单击

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

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');
});

这篇关于如何使用jQuery-Mobile和Knockout.js构建Web应用程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆