Cách cấu trúc một ứng dụng web bằng jquery-mobile và knockoutjs


88

Tôi muốn xây dựng một ứng dụng di động, được tạo ra từ không gì khác ngoài html / css và JavaScript. Mặc dù tôi có kiến ​​thức kha khá về cách xây dựng ứng dụng web bằng JavaScript, nhưng tôi nghĩ mình có thể xem xét một khuôn khổ như jquery-mobile.

Lúc đầu, tôi nghĩ jquery-mobile không hơn gì một khung công cụ nhắm mục tiêu đến các trình duyệt di động. Rất giống với jquery-ui nhưng dành cho thế giới di động. Nhưng tôi nhận thấy rằng jquery-mobile còn hơn thế nữa. Nó đi kèm với một loạt kiến ​​trúc và cho phép bạn tạo các ứng dụng với cú pháp html khai báo. Vì vậy, đối với ứng dụng dễ nghĩ nhất, bạn sẽ không cần phải tự mình viết một dòng JavaScript (điều này thật tuyệt, bởi vì tất cả chúng ta đều thích làm việc ít hơn, phải không?)

Để hỗ trợ phương pháp tạo ứng dụng bằng cú pháp html khai báo, tôi nghĩ nên kết hợp jquery-mobile với knockoutjs. Knockoutjs là một khuôn khổ MVVM phía máy khách nhằm mục đích mang siêu năng lực MVVM được biết đến từ WPF / Silverlight đến thế giới JavaScript.

Đối với tôi MVVM là một thế giới mới. Mặc dù tôi đã đọc rất nhiều về nó, nhưng tôi chưa bao giờ thực sự sử dụng nó trước đây.

Vì vậy, bài đăng này là về cách cấu trúc một ứng dụng bằng cách sử dụng jquery-mobile và knockoutjs với nhau. Ý tưởng của tôi là viết ra cách tiếp cận mà tôi nghĩ ra sau khi xem xét nó trong vài giờ và nhờ một số jquery-mobile / knockout yoda nhận xét về nó, cho tôi biết lý do tại sao nó tệ và tại sao tôi không nên lập trình trong lần đầu tiên địa điểm ;-)

Html

jquery-mobile thực hiện tốt công việc cung cấp mô hình cấu trúc cơ bản của các trang. Mặc dù tôi biết rõ rằng tôi có thể tải các trang của mình qua ajax sau đó, nhưng tôi chỉ quyết định giữ tất cả chúng trong một tệp index.html. Trong kịch bản cơ bản này, chúng ta đang nói về hai trang để không quá khó để luôn cập nhật mọi thứ.

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

Vì vậy, chúng ta hãy đến với phần thú vị - JavaScript!

Khi tôi bắt đầu nghĩ về việc phân lớp ứng dụng, tôi đã nghĩ đến một số điều (ví dụ: khả năng kiểm tra, khớp nối lỏng lẻo). Tôi sẽ cho bạn thấy cách tôi quyết định tách các tệp của mình và nhận xét những điều như tại sao tôi chọn thứ này hơn thứ khác trong khi tôi đi ...

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 là điểm vào của ứng dụng của tôi. Nó tạo đối tượng Ứng dụng và cung cấp không gian tên cho các mô hình chế độ xem (sẽ sớm ra mắt). Nó lắng nghe sự kiện mobileinit mà jquery-mobile cung cấp.

Như bạn có thể thấy, tôi đang tạo một phiên bản của một số loại dịch vụ ajax (chúng ta sẽ xem xét sau) và lưu nó vào biến "service".

Tôi cũng kết nối sự kiện pagecreate cho trang chủ trong đó tôi tạo một phiên bản của viewModel nhận phiên bản dịch vụ được chuyển vào. Điểm này rất cần thiết đối với tôi. Nếu ai đó nghĩ, điều này nên được thực hiện theo cách khác, hãy chia sẻ suy nghĩ của bạn!

Vấn đề là, mô hình xem cần hoạt động trên một dịch vụ (GetTour /, SaveTour, v.v.). Nhưng tôi không muốn ViewModel biết thêm về nó. Vì vậy, ví dụ, trong trường hợp của chúng tôi, tôi chỉ chuyển qua một dịch vụ ajax bị chế nhạo bởi vì chương trình phụ trợ chưa được phát triển.

Một điều khác mà tôi nên đề cập là ViewModel không có kiến ​​thức về chế độ xem thực tế. Đó là lý do tại sao tôi đang gọi ko.applyBindings (viewModel, this) từ bên trong trình xử lý pagecreate . Tôi muốn tách riêng mô hình chế độ xem khỏi chế độ xem thực tế để giúp kiểm tra nó dễ dàng hơn.

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)

Trong khi bạn sẽ tìm thấy hầu hết các ví dụ về mô hình xem knockoutjs sử dụng cú pháp theo nghĩa đen của đối tượng, tôi đang sử dụng cú pháp hàm truyền thống với các đối tượng trợ giúp 'self'. Về cơ bản, đó là vấn đề về hương vị. Nhưng khi bạn muốn có một thuộc tính có thể quan sát được để tham chiếu đến một thuộc tính khác, bạn không thể viết ra đối tượng theo nghĩa đen trong một lần, điều này làm cho nó kém đối xứng hơn. Đó là một trong những lý do tại sao tôi chọn một cú pháp khác.

Lý do tiếp theo là dịch vụ mà tôi có thể truyền dưới dạng một tham số như tôi đã đề cập trước đây.

Còn một điều nữa với mô hình xem này mà tôi không chắc liệu mình có chọn đúng cách hay không. Tôi muốn thăm dò ý kiến ​​dịch vụ ajax định kỳ để lấy kết quả từ máy chủ. Vì vậy, tôi đã chọn để thực hiện startServicePolling / stopServicePolling phương pháp để làm như vậy. Ý tưởng là bắt đầu bỏ phiếu trên trình chiếu và dừng lại khi người dùng điều hướng đến trang khác.

Bạn có thể bỏ qua cú pháp được sử dụng để thăm dò dịch vụ. Đó là phép thuật RxJS. Chỉ cần đảm bảo tôi đang thăm dò ý kiến ​​và cập nhật các thuộc tính quan sát được với kết quả trả về như bạn có thể thấy trong phần Đăng ký (chức năng (thống kê) {..}) .

App.MockedSt StatisticsService.js

Ok, chỉ còn một thứ nữa để cho bạn xem. Đó là triển khai dịch vụ thực tế. Tôi không đi sâu vào chi tiết ở đây. Nó chỉ là một mô hình trả về một số số khi getSt Statistics được gọi. Có một phương pháp mockSt Statistics khác mà tôi sử dụng để đặt các giá trị mới thông qua bảng điều khiển js của trình duyệt trong khi ứng dụng đang chạy.

(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, tôi đã viết nhiều hơn những gì tôi dự định viết ban đầu. Ngón tay của tôi bị đau, những con chó của tôi đang yêu cầu tôi đưa chúng đi dạo và tôi cảm thấy kiệt sức. Tôi chắc chắn rằng có rất nhiều thứ còn thiếu ở đây và tôi đã mắc một loạt lỗi chính tả và grammer. La mắng tôi nếu có điều gì đó không rõ ràng và tôi sẽ cập nhật bài đăng sau.

Bài đăng có vẻ không phải là một câu hỏi nhưng thực sự là như vậy! Tôi muốn bạn chia sẻ suy nghĩ của bạn về cách tiếp cận của tôi và nếu bạn nghĩ nó tốt hay xấu hoặc nếu tôi đang bỏ lỡ những điều.

CẬP NHẬT

Do sự phổ biến lớn mà bài đăng này đã đạt được và vì một số người yêu cầu tôi làm như vậy, tôi đã đặt mã của ví dụ này trên github:

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

Hãy lấy nó khi còn nóng!


7
Tôi không chắc có một câu hỏi đủ cụ thể để mọi người giải quyết. Tôi thích chi tiết bạn có ở đây, nhưng nó dường như cho phép chuyển sang thảo luận. Ít từ hơn: "Blog đẹp";)
Bernhard Hofmann

Tôi vui vì bạn thích nó. Tôi lo lắng một chút rằng tôi đã viết quá nhiều nên mọi người sợ viết một câu trả lời ngắn. Tuy nhiên, mọi cuộc thảo luận đều được hoan nghênh. Và nếu stackoverflow không phải là nơi bắt đầu thảo luận sai, chúng ta có thể chuyển sang nhóm google: groups.google.com/forum/#!topic/knockoutjs/80_FuHmCm1s
Christoph,

Xin chào Christoph, cách tiếp cận này phù hợp với bạn như thế nào?
hkon

Trên thực tế, tôi đã chuyển sang khuôn khổ AngularJS tuyệt vời hơn ;-)
Christoph

1
Điều này có thể tốt hơn nếu bạn chỉ giữ một vài đoạn đầu tiên làm câu hỏi và chuyển phần còn lại của nó sang một câu trả lời tự.
rjmunro

Câu trả lời:


30

Lưu ý: Kể từ jQuery 1.7, .live()phương thức này không được dùng nữa. Sử dụng .on()để đính kèm trình xử lý sự kiện. Người dùng các phiên bản cũ hơn của jQuery nên .delegate()ưu tiên sử dụng .live().

Tôi đang làm điều tương tự (knockout + jquery mobile). Tôi đang cố gắng viết một bài đăng trên blog về những gì tôi đã học được nhưng đây là một số gợi ý trong thời gian chờ đợi. Hãy nhớ rằng tôi cũng đang cố gắng học knockout / jquery mobile.

Xem-Mô hình và Trang

Chỉ sử dụng một (1) đối tượng mô hình xem trên mỗi trang jQuery Mobile. Nếu không, bạn có thể gặp sự cố với các sự kiện nhấp chuột được kích hoạt nhiều lần.

Xem-Mô hình và nhấp vào

Chỉ sử dụng ko.observable-fields cho các sự kiện nhấp chuột của chế độ xem.

ko.applyBinding một lần

Nếu có thể: chỉ gọi ko.applyBinding một lần cho mỗi trang và sử dụng ko.observable's thay vì gọi ko.applyBinding nhiều lần.

pagehide và ko.cleanNode

Hãy nhớ xóa một số mô hình xem trên pagehide. ko.cleanNode dường như làm phiền kết xuất jQuery Mobiles - khiến nó hiển thị lại html. Nếu bạn sử dụng ko.cleanNode trên một trang, bạn cần xóa vai trò dữ liệu và chèn html jQuery Mobile được hiển thị trong mã nguồn.

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

pagehide và nhấp vào

Nếu bạn liên kết với các sự kiện nhấp chuột - hãy nhớ xóa .ui-btn-active. Cách dễ nhất để thực hiện điều này là sử dụng đoạn mã này:

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

Vì câu hỏi của tôi rất không cụ thể và bạn là người dành nhiều công sức nhất để đưa ra câu trả lời, tôi sẽ biến bạn thành câu trả lời được chấp nhận.
Christoph

Bạn đã bao giờ con số này ra? Tôi đang gặp khó khăn trong việc tích hợp KO và JQM và không có hướng dẫn tốt về cách thực hiện (hoặc một jsFiddle trình diễn bản trình diễn end-to-end).
kamranicus

1
Không, tôi đã chuyển sang khung công tác AngularJS. Tôi thấy rằng điều đó thật tuyệt vời đối với KO. Và có một dự án bộ điều hợp khá tốt để biến AngularJS / jqm thành những người bạn tốt nhất mãi mãi: github.com/tigbro/jquery-mobile-angular-adapter Tuy nhiên, đối với những gì tôi đã làm cho đến nay, dường như sử dụng bộ điều hợp đó là quá mức cần thiết. Sau khi tất cả của nó khá dễ dàng để chỉ cần sử dụng html / css của jqm và biến các điều khiển vào một chỉ thị góc: jsfiddle.net/zy7Rg/7
Christoph

Bạn có thể tạo một cấu trúc mà tôi đã xác định ở đây . Tôi chắc chắn bằng cách này bạn sẽ có toàn quyền kiểm soát ứng dụng.
Muhammad Raheel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.