ฉันต้องการสร้างแอพมือถือที่สร้างขึ้นจากอะไรมากนอกจาก html / css และ JavaScript ในขณะที่ฉันมีความรู้ดีเกี่ยวกับวิธีสร้างเว็บแอปด้วย JavaScript แต่ฉันคิดว่าฉันอาจดูกรอบงานเช่น jquery-mobile
ตอนแรกฉันคิดว่า jquery-mobile ไม่มีอะไรมากไปกว่านั้นแล้ววิดเจ็ตเฟรมเวิร์กที่กำหนดเป้าหมายไปที่เบราว์เซอร์มือถือ คล้ายกับ jquery-ui มาก แต่สำหรับโลกมือถือ แต่ฉันสังเกตว่า jquery-mobile เป็นมากกว่านั้น มันมาพร้อมกับสถาปัตยกรรมมากมายและให้คุณสร้างแอพด้วยไวยากรณ์ html ที่เปิดเผย ดังนั้นสำหรับแอปที่คิดได้ง่ายที่สุดคุณไม่จำเป็นต้องเขียน JavaScript เพียงบรรทัดเดียวด้วยตัวเอง (ซึ่งดีมากเพราะเราทุกคนชอบทำงานน้อยลงใช่ไหม)
เพื่อสนับสนุนแนวทางในการสร้างแอปโดยใช้ไวยากรณ์ html ที่เปิดเผยฉันคิดว่าการรวม jquery-mobile กับ knockoutjs เป็นสิ่งที่ดี Knockoutjs เป็นเฟรมเวิร์ก MVVM ฝั่งไคลเอ็นต์ที่มีเป้าหมายเพื่อนำพลังพิเศษ MVVM ที่รู้จักจาก WPF / Silverlight มาสู่โลก JavaScript
สำหรับฉัน MVVM คือโลกใบใหม่ ในขณะที่ฉันได้อ่านเกี่ยวกับเรื่องนี้มามากแล้ว แต่ฉันไม่เคยใช้มันด้วยตัวเองมาก่อนเลย
ดังนั้นโพสต์นี้จึงเกี่ยวกับการสร้างแอพโดยใช้ jquery-mobile และ knockoutjs ร่วมกัน ความคิดของฉันคือการเขียนแนวทางที่ฉันคิดขึ้นมาหลังจากดูมันเป็นเวลาหลายชั่วโมงและมี jquery-mobile / knockout yoda เพื่อแสดงความคิดเห็นแสดงให้ฉันเห็นว่าทำไมมันถึงแย่และทำไมฉันไม่ควรเขียนโปรแกรม สถานที่ ;-)
html
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 และจัดเตรียมเนมสเปซสำหรับโมเดลมุมมอง (เร็ว ๆ นี้) มันฟังเหตุการณ์mobileinitที่ jquery-mobile ให้
อย่างที่คุณเห็นฉันกำลังสร้างอินสแตนซ์ของบริการ ajax บางประเภท (ซึ่งเราจะดูในภายหลัง) และบันทึกลงในตัวแปร "service"
ฉันยังเชื่อมโยงเหตุการณ์pagecreateสำหรับโฮมเพจที่ฉันสร้างอินสแตนซ์ของ viewModel ที่รับอินสแตนซ์บริการที่ส่งผ่านเข้ามาจุดนี้จำเป็นสำหรับฉัน หากใครคิดว่าสิ่งนี้ควรทำแตกต่างออกไปโปรดแบ่งปันความคิดของคุณ!
ประเด็นคือโมเดลมุมมองต้องทำงานกับบริการ (GetTour /, SaveTour ฯลฯ ) แต่ฉันไม่ต้องการให้ ViewModel รู้เรื่องนี้อีกต่อไป ตัวอย่างเช่นในกรณีของเราฉันเพิ่งผ่านบริการ ajax ที่ถูกล้อเลียนเนื่องจากแบ็กเอนด์ยังไม่ได้รับการพัฒนา
สิ่งที่ฉันควรพูดถึงก็คือ ViewModel ไม่มีความรู้เกี่ยวกับมุมมองจริง นั่นเป็นเหตุผลที่ฉันเรียก ko.applyBindings (viewModel, this) จากภายในตัวจัดการpagecreate ฉันต้องการให้โมเดลมุมมองแยกจากมุมมองจริงเพื่อให้ง่ายต่อการทดสอบ
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)
ในขณะที่คุณจะพบตัวอย่างโมเดลมุมมองที่น่าพิศวงส่วนใหญ่โดยใช้ไวยากรณ์ตัวอักษรของวัตถุ แต่ฉันกำลังใช้ไวยากรณ์ของฟังก์ชันแบบเดิมกับวัตถุตัวช่วย 'ตัวเอง' โดยพื้นฐานแล้วมันเป็นเรื่องของรสนิยม แต่เมื่อคุณต้องการให้คุณสมบัติที่สังเกตได้อย่างหนึ่งเพื่ออ้างอิงอีกคุณสมบัติหนึ่งคุณจะไม่สามารถเขียนอ็อบเจกต์ลิเทอรัลได้ในครั้งเดียวซึ่งทำให้สมมาตรน้อย นั่นเป็นหนึ่งในเหตุผลที่ฉันเลือกไวยากรณ์อื่น
เหตุผลต่อไปคือบริการที่ฉันสามารถส่งต่อเป็นพารามิเตอร์ตามที่ฉันได้กล่าวไว้ก่อนหน้านี้
ยังมีอีกสิ่งหนึ่งในโมเดลมุมมองนี้ซึ่งฉันไม่แน่ใจว่าฉันเลือกวิธีที่ถูกต้องหรือไม่ ฉันต้องการสำรวจความคิดเห็นของบริการ ajax เป็นระยะเพื่อดึงผลลัพธ์จากเซิร์ฟเวอร์ ดังนั้นฉันจึงเลือกใช้เมธอด startServicePolling / stopServicePollingเพื่อทำเช่นนั้น แนวคิดคือการเริ่มต้นการสำรวจความคิดเห็นในการแสดงเพจและหยุดเมื่อผู้ใช้ไปที่หน้าอื่น
คุณสามารถละเว้นไวยากรณ์ที่ใช้ในการสำรวจบริการ มันคือเวทมนตร์ RxJS เพียงให้แน่ใจว่าฉันเลือกตั้งมันและอัปเดตคุณสมบัติที่สังเกตได้กับผลที่กลับมาในขณะที่คุณสามารถมองเห็นในที่สมัครสมาชิก (ฟังก์ชั่น (สถิติ) {} .. )ส่วนหนึ่ง
App.MockedStatisticsService.js
โอเคมีเพียงสิ่งเดียวที่จะแสดงให้คุณเห็น เป็นการใช้บริการจริง ฉันจะไม่ลงรายละเอียดที่นี่มากนัก เป็นเพียงการเยาะเย้ยที่ส่งคืนตัวเลขบางส่วนเมื่อเรียกใช้getStatistics มีอีกวิธีหนึ่งคือmockStatisticsที่ฉันใช้เพื่อตั้งค่าใหม่ผ่านคอนโซล 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
รับในขณะที่อากาศร้อน!