Bagaimana merancang aplikasi web menggunakan jquery-mobile dan knockoutjs


88

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!


7
Saya tidak yakin ada pertanyaan yang cukup spesifik untuk dijawab orang. Saya suka detail yang Anda miliki di sini, tetapi tampaknya memungkinkan untuk beralih ke diskusi. Singkatnya: "Blog yang bagus";)
Bernhard Hofmann

Saya senang Anda menyukainya. Saya sedikit khawatir karena saya menulis begitu banyak sehingga orang takut untuk menulis jawaban singkat. Bagaimanapun, diskusi apapun diperbolehkan. Dan jika stackoverflow adalah tempat yang salah untuk memulai diskusi, kita dapat beralih ke grup google: groups.google.com/forum/#!topic/knockoutjs/80_FuHmCm1s
Christoph

Hai Christoph, bagaimana pendekatan ini berhasil untuk Anda?
hkon

Sebenarnya, saya pindah ke kerangka kerja AngularJS yang lebih mengagumkan ;-)
Christoph

1
Ini mungkin lebih baik jika Anda hanya menyimpan beberapa paragraf pertama sebagai pertanyaan, dan memindahkan sisanya ke jawaban sendiri.
rjmunro

Jawaban:


30

Catatan: Mulai jQuery 1.7, .live()metode ini tidak digunakan lagi. Gunakan .on()untuk melampirkan penangan acara. Pengguna versi jQuery harus menggunakan .delegate()dalam preferensi untuk .live().

Saya sedang mengerjakan hal yang sama (knockout + jquery mobile). Saya mencoba untuk menulis posting blog tentang apa yang telah saya pelajari tetapi berikut adalah beberapa petunjuk untuk sementara. Ingatlah bahwa saya juga mencoba mempelajari sistem gugur / jquery seluler.

Tampilan-Model dan Halaman

Hanya gunakan satu (1) objek model tampilan per halaman seluler jQuery. Jika tidak, Anda bisa mendapatkan masalah dengan peristiwa klik yang dipicu beberapa kali.

Lihat-Model dan klik

Hanya gunakan bidang ko.observable untuk peristiwa klik model tampilan.

ko.applyBinding sekali

Jika memungkinkan: hanya panggil ko.applyBinding sekali untuk setiap halaman dan gunakan ko.observable's alih-alih memanggil ko.applyBinding beberapa kali.

pagehide dan ko.cleanNode

Ingatlah untuk membersihkan beberapa model tampilan di sembunyikan halaman. ko.cleanNode tampaknya mengganggu rendering jQuery Mobiles - menyebabkannya merender ulang html. Jika Anda menggunakan ko.cleanNode di halaman, Anda perlu menghapus peran data dan memasukkan html jQuery Mobile yang telah dirender ke dalam kode sumber.

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

sembunyikan halaman dan klik

Jika Anda mengikat ke peristiwa klik - ingatlah untuk membersihkan .ui-btn-active. Cara termudah untuk melakukannya adalah menggunakan cuplikan kode ini:

$('[data-role="page"]').live('pagehide', function() {
    $('.ui-btn-active').removeClass('ui-btn-active');
});

Karena pertanyaan saya sangat tidak spesifik dan Anda adalah orang yang paling banyak berusaha untuk menjawabnya, saya akan membuat jawaban Anda diterima.
Christoph

Apakah Anda pernah memikirkan hal ini? Saya mengalami kesulitan untuk mengintegrasikan KO dan JQM dan tidak ada panduan yang baik tentang cara melakukannya (atau jsFiddle mendemonstrasikan demo ujung ke ujung).
kamranicus

1
Tidak, saya pindah ke kerangka AngularJS. Saya menemukan bahwa menjadi superior bagi KO. Dan ada proyek adaptor yang cukup bagus untuk menjadikan AngularJS / jqm teman terbaik selamanya: github.com/tigbro/jquery-mobile-angular-adapter Namun, untuk apa yang saya lakukan sejauh ini tampaknya berlebihan untuk menggunakan adaptor itu. Bagaimanapun juga cukup mudah untuk hanya menggunakan html / css dari jqm dan mengubah kontrol menjadi arahan Angular: jsfiddle.net/zy7Rg/7
Christoph

Anda dapat membuat struktur yang telah saya tentukan di sini . Saya yakin dengan cara ini Anda akan memiliki kendali penuh atas aplikasi tersebut.
Muhammad Raheel
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.