Html / css ve JavaScript dışında hiçbir şeyden oluşturulmuş bir mobil uygulama oluşturmak istiyorum. JavaScript ile bir web uygulamasının nasıl oluşturulacağına dair iyi bir bilgim olsa da jquery-mobile gibi bir çerçeveye bakabileceğimi düşündüm.
İlk başta, jquery-mobile'ın mobil tarayıcıları hedefleyen bir widget çerçevesinden başka bir şey olmadığını düşündüm. Jquery-ui'ye çok benzer, ancak mobil dünya için. Ancak jquery-mobile'ın bundan daha fazlası olduğunu fark ettim. Bir grup mimari ile birlikte gelir ve bildirim temelli html sözdizimi ile uygulamalar oluşturmanıza izin verin. Dolayısıyla, en kolay düşünülebilir uygulama için, tek bir JavaScript satırı yazmanıza gerek kalmaz (bu harika, çünkü hepimiz daha az çalışmayı seviyoruz, değil mi?)
Bildirim temelli bir html sözdizimi kullanarak uygulama oluşturma yaklaşımını desteklemek için jquery-mobile ile knockoutj'leri birleştirmenin iyi bir yaklaşım olduğunu düşünüyorum. Knockoutjs, WPF / Silverlight'tan bilinen MVVM süper güçlerini JavaScript dünyasına getirmeyi amaçlayan istemci tarafı bir MVVM çerçevesidir.
Benim için MVVM yeni bir dünya. Bu konuda zaten çok şey okumuş olsam da, aslında daha önce hiç kullanmadım.
Yani bu gönderi, jquery-mobile ve knockoutj'leri birlikte kullanarak bir uygulamanın nasıl yapılandırılacağıyla ilgili. Benim fikrim, birkaç saat boyunca ona baktıktan sonra ortaya attığım yaklaşımı yazmak ve yorumlamak için biraz jquery-mobile / knockout yoda almak, neden berbat olduğunu ve neden ilk başta programlama yapmamam gerektiğini göstermekti. yer ;-)
Html
jquery-mobile, sayfaların temel yapı modelini sağlayarak iyi bir iş çıkarır. Sayfalarımın daha sonra ajax aracılığıyla yüklenebileceğinin farkında olsam da, hepsini bir index.html dosyasında tutmaya karar verdim. Bu temel senaryoda, iki sayfadan bahsediyoruz, böylece konuların üstesinden gelmek çok zor olmasa gerek.
<!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
Hadi eğlenceli kısma gelelim - JavaScript!
Uygulamayı katmanlandırmayı düşünmeye başladığımda, aklımda birkaç şey vardı (örneğin test edilebilirlik, gevşek bağlantı). Size dosyalarımı nasıl bölmeye karar verdiğimi göstereceğim ve giderken neden bir şeyi diğerine tercih ettim gibi şeyler ...
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, uygulamamın giriş noktasıdır. App nesnesini oluşturur ve görünüm modelleri için bir ad alanı sağlar (yakında çıkacaktır). Jquery- mobile'ın sağladığı mobileinit olayını dinler .
Gördüğünüz gibi, bir çeşit ajax servisinin bir örneğini yaratıyorum (ki bunu daha sonra inceleyeceğiz) ve "servis" değişkenine kaydediyorum.
Ayrıca, içinde iletilen hizmet örneğini alan viewModel örneğini oluşturduğum ana sayfa için pagecreate olayını da bağlarım. Bu nokta benim için önemlidir. Herhangi biri bunun farklı yapılması gerektiğini düşünüyorsa, lütfen düşüncelerinizi paylaşın!
Mesele şu ki, görünüm modelinin bir hizmet (GetTour /, SaveTour vb.) Üzerinde çalışması gerekir. Ama ViewModel'in bunun hakkında daha fazla bilgi sahibi olmasını istemiyorum. Örneğin, bizim durumumuzda, sahte bir ajax hizmetinden geçiyorum çünkü arka uç henüz geliştirilmemiştir.
Bahsetmem gereken bir diğer şey de ViewModel'in gerçek görünüm hakkında sıfır bilgisine sahip olmasıdır. Bu yüzden pagecreate işleyicisinden ko.applyBindings'i (viewModel, this) çağırıyorum . Görünüm modelini, test etmeyi kolaylaştırmak için gerçek görünümden ayrı tutmak istedim.
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)
Bir nesne değişmez sözdizimi kullanarak çoğu nakavt görünüm modeli örneklerini bulacak olsanız da, geleneksel işlev sözdizimini bir 'öz' yardımcı nesnelerle kullanıyorum. Temelde bu bir zevk meselesi. Ancak bir diğerine başvurmak için bir gözlemlenebilir özelliğe sahip olmak istediğinizde, nesneyi tek seferde birebir yazamazsınız, bu da onu daha az simetrik yapar. Farklı bir sözdizimi seçmemin nedenlerinden biri bu.
Bir sonraki neden, daha önce bahsettiğim gibi parametre olarak aktarabileceğim hizmettir.
Bu görüş modelinde doğru yolu seçip seçmediğimden emin olmadığım bir şey daha var. Sonuçları sunucudan almak için ajax hizmetini periyodik olarak yoklamak istiyorum. Bu yüzden, bunu yapmak için startServicePolling / stopServicePolling yöntemlerini uygulamayı seçtim . Buradaki fikir yoklamayı sayfa gösterisinde başlatmak ve kullanıcı farklı bir sayfaya gittiğinde onu durdurmaktır.
Hizmeti yoklamak için kullanılan sözdizimini göz ardı edebilirsiniz. Bu RxJS büyüsü. Abone ol (işlev (istatistikler) {..}) bölümünde görebileceğiniz gibi onu yokladığımdan ve gözlemlenebilir özellikleri döndürülen sonuçla güncellediğimden emin olun .
App.MockedStatisticsService.js
Tamam, size gösterecek tek bir şey kaldı. Gerçek hizmet uygulamasıdır. Burada fazla ayrıntıya girmiyorum. Bu sadece getStatistics çağrıldığında bazı sayılar döndüren bir taklittir . Uygulama çalışırken tarayıcıların js konsolu aracılığıyla yeni değerler ayarlamak için kullandığım başka bir mockStatistics yöntemi daha var.
(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)
Tamam, başlangıçta yazmayı planladığım gibi çok daha fazlasını yazdım. Parmağım ağrıyor, köpeklerim benden onları yürüyüşe çıkarmamı istiyor ve kendimi bitkin hissediyorum. Eminim burada birçok şey eksiktir ve bir sürü yazım hatası ve gramer hatası koydum. Bir şey net değilse bana bağır ve gönderiyi daha sonra güncelleyeceğim.
Gönderi bir soru gibi görünmeyebilir ama aslında öyle! Yaklaşımım ve bunun iyi ya da kötü olduğunu düşünüyorsanız ya da bir şeyleri kaçırdığım konusunda düşüncelerinizi paylaşmanızı istiyorum.
GÜNCELLEME
Bu gönderinin büyük popülaritesi nedeniyle ve birkaç kişi benden bunu yapmamı istediği için, bu örneğin kodunu github'a koydum:
https://github.com/cburgdorf/stackoverflow-knockout-example
Sıcakken alın!