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!