Saya ingin membuat aplikasi seluler, yang dibuat hanya dari html / css dan JavaScript. Meskipun saya memiliki pengetahuan yang cukup tentang cara membuat aplikasi web dengan JavaScript, saya pikir saya mungkin telah melihat kerangka kerja seperti jquery-mobile.
Pada awalnya, saya pikir jquery-mobile tidak lebih dari kerangka widget yang menargetkan browser seluler. Sangat mirip dengan jquery-ui tetapi untuk dunia seluler. Tapi saya perhatikan bahwa jquery-mobile lebih dari itu. Muncul dengan banyak arsitektur dan memungkinkan Anda membuat aplikasi dengan sintaks html deklaratif. Jadi untuk aplikasi yang paling mudah dipikirkan, Anda tidak perlu menulis satu baris JavaScript sendiri (yang keren, karena kita semua suka bekerja lebih sedikit, bukan?)
Untuk mendukung pendekatan pembuatan aplikasi menggunakan sintaks html deklaratif, menurut saya sebaiknya kombinasikan jquery-mobile dengan knockoutjs. Knockoutjs adalah kerangka kerja MVVM sisi klien yang bertujuan untuk membawa kekuatan super MVVM yang dikenal dari WPF / Silverlight ke dunia JavaScript.
Bagi saya MVVM adalah dunia baru. Meskipun saya sudah banyak membaca tentangnya, saya sendiri sebenarnya belum pernah menggunakannya.
Jadi posting ini adalah tentang bagaimana merancang aplikasi menggunakan jquery-mobile dan knockoutjs bersama-sama. Ide saya adalah menuliskan pendekatan yang saya hasilkan setelah melihatnya selama beberapa jam, dan memiliki beberapa yoda jquery-mobile / knockout untuk mengomentarinya, menunjukkan kepada saya mengapa itu menyebalkan dan mengapa saya tidak boleh melakukan pemrograman pada awalnya tempat ;-)
Html
jquery-mobile berfungsi dengan baik dalam menyediakan model struktur dasar halaman. Meskipun saya sangat menyadari bahwa halaman saya dapat dimuat melalui ajax setelahnya, saya memutuskan untuk menyimpan semuanya dalam satu file index.html. Dalam skenario dasar ini kita berbicara tentang dua halaman sehingga seharusnya tidak terlalu sulit untuk tetap berada di puncak.
<!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
Jadi, mari kita ke bagian yang menyenangkan - JavaScript!
Ketika saya mulai berpikir untuk melapisi aplikasi, saya memiliki beberapa hal dalam pikiran (mis. Testabilitas, kopling longgar). Saya akan menunjukkan kepada Anda bagaimana saya memutuskan untuk membagi file saya dan mengomentari hal-hal seperti mengapa saya memilih satu hal daripada yang lain sementara saya pergi ...
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 adalah titik masuk aplikasi saya. Ini membuat objek App dan menyediakan namespace untuk model tampilan (segera datang). Ia mendengarkan acara mobileinit yang disediakan jquery-mobile.
Seperti yang Anda lihat, saya membuat instance dari beberapa jenis layanan ajax (yang akan kita lihat nanti) dan menyimpannya ke variabel "service".
Saya juga menghubungkan acara pagecreate untuk halaman beranda tempat saya membuat instance viewModel yang mendapatkan instance layanan yang diteruskan. Hal ini penting bagi saya. Jika ada yang berpikir, ini harus dilakukan secara berbeda, silakan bagikan pemikiran Anda!
Intinya adalah, model tampilan perlu beroperasi pada layanan (GetTour /, SaveTour, dll.). Tapi saya tidak ingin ViewModel mengetahui lebih banyak tentang itu. Jadi misalnya, dalam kasus kami, saya hanya meneruskan layanan ajax tiruan karena backend belum dikembangkan.
Hal lain yang harus saya sebutkan adalah bahwa ViewModel tidak memiliki pengetahuan tentang tampilan sebenarnya. Itulah mengapa saya memanggil ko.applyBindings (viewModel, this) dari dalam penangan pagecreate . Saya ingin memisahkan model tampilan dari tampilan sebenarnya agar lebih mudah untuk mengujinya.
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)
Meskipun Anda akan menemukan sebagian besar contoh model tampilan knockoutjs menggunakan sintaks literal objek, saya menggunakan sintaks fungsi tradisional dengan objek pembantu 'diri'. Pada dasarnya, ini masalah selera. Tetapi ketika Anda ingin memiliki satu properti yang dapat diamati untuk mereferensikan yang lain, Anda tidak dapat menuliskan literal objek sekaligus yang membuatnya kurang simetris. Itulah salah satu alasan mengapa saya memilih sintaks yang berbeda.
Alasan selanjutnya adalah layanan yang dapat saya sampaikan sebagai parameter seperti yang saya sebutkan sebelumnya.
Ada satu hal lagi dengan model tampilan ini yang saya tidak yakin apakah saya memilih cara yang benar. Saya ingin melakukan polling layanan ajax secara berkala untuk mengambil hasil dari server. Jadi, saya telah memilih untuk mengimplementasikan metode startServicePolling / stopServicePolling untuk melakukannya. Idenya adalah memulai polling di pageshow, dan menghentikannya saat pengguna menavigasi ke halaman yang berbeda.
Anda dapat mengabaikan sintaks yang digunakan untuk mengumpulkan layanan. Ini keajaiban RxJS. Pastikan saya melakukan polling dan memperbarui properti yang dapat diamati dengan hasil yang dikembalikan seperti yang Anda lihat di bagian Berlangganan (fungsi (statistik) {..}) .
App.MockedStatisticsService.js
Oke, tinggal satu hal lagi yang ingin Anda tunjukkan. Ini adalah implementasi layanan yang sebenarnya. Saya tidak akan membahas banyak hal di sini. Itu hanya tiruan yang mengembalikan beberapa angka saat getStatistics dipanggil. Ada metode lain mockStatistics yang saya gunakan untuk menetapkan nilai baru melalui konsol js browser saat aplikasi sedang berjalan.
(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)
Oke, saya menulis lebih banyak lagi seperti yang awalnya saya rencanakan. Jari saya sakit, anjing saya meminta saya untuk membawanya jalan-jalan dan saya merasa lelah. Saya yakin ada banyak hal yang hilang di sini dan saya memasukkan banyak kesalahan ketik dan kesalahan grammer. Teriaklah pada saya jika ada sesuatu yang tidak jelas dan saya akan mengupdate postingan nanti.
Posting tersebut mungkin tidak tampak sebagai pertanyaan, tetapi sebenarnya memang demikian! Saya ingin Anda membagikan pemikiran Anda tentang pendekatan saya dan jika menurut Anda itu baik atau buruk atau jika saya melewatkan sesuatu.
MEMPERBARUI
Karena popularitas utama postingan ini diperoleh dan karena beberapa orang meminta saya untuk melakukannya, saya telah meletakkan kode contoh ini di github:
https://github.com/cburgdorf/stackoverflow-knockout-example
Dapatkan selagi panas!