Jak zaprojektować webapp za pomocą jquery-mobile i knockoutjs

Chciałbym zbudować aplikację mobilną, opartą wyłącznie na html/css i JavaScript. Chociaż mam przyzwoitą wiedzę o tym, jak zbudować aplikację internetową za pomocą JavaScript, pomyślałem, że mogę spojrzeć na framework taki jak jquery-mobile.

Na początku myślałem, że jQuery-mobile to nic więcej niż framework widget, który jest skierowany do przeglądarek mobilnych. Bardzo podobny do jquery-ui, ale dla świata mobilnego. Ale zauważyłem, że jQuery-mobile to coś więcej. Zawiera kilka architektura i pozwala tworzyć aplikacje z deklaratywną składnią html. Więc dla najbardziej łatwej do myślenia aplikacji, nie trzeba by napisać pojedynczą linię JavaScript przez siebie (co jest fajne, bo wszyscy lubią pracować mniej, prawda?)

Aby wesprzeć podejście do tworzenia aplikacji przy użyciu deklaratywnej składni html, myślę, że dobrym rozwiązaniem jest połączenie jquery-mobile z knockoutjs. Knockoutjs jest frameworkiem MVVM po stronie klienta, który ma na celu przeniesienie super mocy MVVM znanych z WPF/Silverlight do Świata JavaScript.

Dla mnie MVVM to nowy świat. Chociaż czytałem już dużo na ten temat, nigdy wcześniej z niego nie korzystałem.

Więc ten post jest o tym, jak zaprojektować aplikację za pomocą jQuery-mobile i knockoutjs razem. Moim pomysłem było spisanie podejścia, które wymyśliłem po obejrzeniu go przez kilka godzin, i mieć jakiś jQuery-mobile / knockout yoda, aby to skomentować, pokazując mi, dlaczego jest do bani i dlaczego w ogóle nie powinienem robić programowania ;-)

Html

Jquery-mobile robi dobrą robotę dostarczając podstawowy model struktury stron. Chociaż jestem świadomy, że mogę mieć moje strony do załadowania przez ajax później, po prostu zdecydowałem się zachować wszystkie z nich w jednym indeksie.plik html. W tym podstawowym scenariuszu mówimy o dwóch stronach, aby nie było zbyt trudno pozostać na bieżąco.

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

Przejdźmy więc do zabawy-JavaScript!

Kiedy zacząłem myśleć o warstwowaniu aplikacji, miałem kilka rzeczy na myśli(np. testowalność, luźne sprzężenie). Pokażę Ci, jak zdecydowałem się podzielić moje pliki i skomentować rzeczy, takie jak dlaczego wybrałem jedną rzecz nad drugą, gdy idę...

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 jest punktem wejścia mojej aplikacji. Tworzy obiekt App i udostępnia przestrzeń nazw dla modeli widoku (wkrótce). Nasłuchuje zdarzenia mobileinit które jquery-mobile zapewnia.

Jak widzisz, tworzę instancję jakiejś usługi ajax (którą przyjrzymy się później) i zapisuję ją do zmiennej "service".

Podłączam również zdarzenie pagecreate dla strony głównej, w której tworzę instancję viewModel, która przekazuje instancję usługi. Ten punkt jest dla mnie kluczowy. Jeśli ktoś myśli, że to powinno być zrobione inaczej, proszę podzielić się swoimi przemyśleniami!

Chodzi o to, że model widoku musi działać na serwis (GetTour/, SaveTour itp.). Ale nie chcę, aby ViewModel wiedział o tym więcej. Tak na przykład, w naszym przypadku, jestem po prostu Przejście W wyśmiewany ajax usługi, ponieważ backend nie został jeszcze opracowany.

Kolejną rzeczą, którą powinienem wspomnieć, jest to, że ViewModel nie ma żadnej wiedzy o rzeczywistym widoku. Dlatego dzwonię do ko.applyBindings (viewModel, this) z poziomu obsługi pagecreate . Chciałem, aby model widoku był oddzielony od rzeczywistego widoku, aby go łatwiej go przetestować.

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)

Podczas gdy znajdziesz Większość przykładowych modeli wyświetlania knockoutjs przy użyciu obiektowej składni, ja używam tradycyjnej składni funkcji z obiektami pomocniczymi "self". W zasadzie to kwestia gustu. Ale kiedy chcesz mieć jedną obserwowalną właściwość, aby odwoływać się do innej, nie możesz zapisać obiektu literalnie za jednym zamachem, co czyni go mniej symetrycznym. To jeden z powodów, dla których wybieram Inna składnia.

Kolejnym powodem jest usługa, którą mogę przekazać jako parametr, jak wspomniałem wcześniej.

Jest jeszcze jedna rzecz z tym modelem widoku, który nie jestem pewien, czy wybrałem właściwą drogę. Chcę okresowo przepytywać usługę ajax, aby pobrać wyniki z serwera. Więc wybrałem aby zaimplementować startServicePolling/stopServicePolling metody, aby to zrobić. Chodzi o to, aby rozpocząć ankietę na pageshow, i zatrzymać go, gdy użytkownik przechodzi do innej strony.

Możesz zignorować składnię, która jest używana do ankiety serwisu. To magia RxJS. Po prostu upewnij się, że sprawdzam i zaktualizuj obserwowalne właściwości o zwrócony wynik, jak widać w Subscribe(function (statistics){..}) part.

App.Wyśmiewana służba zdrowia.js

Ok, została Ci tylko jedna rzecz do pokazania. To jest rzeczywiste wdrożenie usługi. Nie zagłębiam się w szczegóły. To tylko kpina, że zwraca niektóre liczby po wywołaniu getStatistics . Istnieje inna metoda mockStatistics, której używam do ustawiania nowych wartości przez konsolę przeglądarki js podczas pracy aplikacji.
(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, napisałem o wiele więcej, tak jak początkowo planowałem. Bolał mnie Palec, moje psy proszą mnie, żebym zabrała je na spacer i czuję się wyczerpana. Jestem pewien, że wiele rzeczy tu brakuje i że wstawiłem kilka literówek i błędów grammer. Krzycz na mnie, jeśli coś nie jest jasne a ja zaktualizuję Post później.

Post może nie wydawać się pytaniem, ale tak naprawdę jest! Chciałbym, abyś podzielił się swoimi przemyśleniami na temat mojego podejścia i czy uważasz, że jest dobre lub złe, lub czy coś mi umyka.

UPDATE

Ze względu na dużą popularność tego postu zyskał i ponieważ kilka osób poprosiło mnie o to, umieściłem Kod tego przykładu na github:

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

Get it while it ' s hot!

Author: Christoph, 2011-05-22

1 answers

Uwaga: od jQuery 1.7, the .live() metoda jest przestarzała. Użycie .on() aby dołączyć procedury obsługi zdarzeń. Użytkownicy starszych wersji jQuery powinni używać .delegate() zamiast .live().

Pracuję nad tym samym (nokaut + jQuery mobile). Próbuję napisać post na blogu o tym, czego się nauczyłem, ale oto kilka wskazówek w międzyczasie. Pamiętaj, że staram się również nauczyć knockout / jquery mobile.

Widok-Model i Strona

Używaj tylko jednego (1) obiektu modelu widoku na jQuery Mobile-page. W przeciwnym razie możesz mieć problemy ze zdarzeniami kliknięć, które są wyzwalane wielokrotnie.

Zobacz-Model i kliknij

Używaj tylko ko.observable-fields for view-models click-events.

Ko.applyBinding once

Jeśli to możliwe: zadzwoń tylko do ko.applyBinding once for every page and use ko.obserwowalne zamiast dzwonić do ko.applyBinding multiple razy.

Pagehide i ko.cleanNode

Pamiętaj, aby wyczyścić niektóre modele widoku na stronie. ko.cleanNode wydaje się zakłócać renderowanie jQuery Mobiles-powodując ponowne renderowanie html. Jeśli używasz ko.cleanNode na stronie musisz usunąć role danych i wstawić renderowany jQuery Mobile html w kodzie źródłowym.

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

Pagehide and click

Jeśli jesteś związany z click-events-pamiętaj, aby posprzątać .ui-btn-active. Najprostszym sposobem na osiągnięcie tego celu jest użycie ten fragment kodu:

$('[data-role="page"]').live('pagehide', function() {
    $('.ui-btn-active').removeClass('ui-btn-active');
});
 30
Author: finnsson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-04 01:03:42