วิธีสร้าง webapp โดยใช้ jquery-mobile และ knockoutjs


88

ฉันต้องการสร้างแอพมือถือที่สร้างขึ้นจากอะไรมากนอกจาก 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

รับในขณะที่อากาศร้อน!


7
ฉันไม่แน่ใจว่ามีคำถามที่เฉพาะเจาะจงเพียงพอสำหรับคนที่จะพูด ฉันชอบรายละเอียดที่คุณมีที่นี่ แต่ดูเหมือนว่าจะย้ายไปที่การสนทนา พูดน้อยลง: "Nice blog";)
Bernhard Hofmann

ฉันดีใจที่คุณชอบมัน. ฉันกังวลเล็กน้อยที่ฉันเขียนมากจนคนกลัวที่จะเขียนคำตอบสั้น ๆ อย่างไรก็ตามยินดีต้อนรับการอภิปรายใด ๆ และหาก stackoverflow เป็นสถานที่ที่ไม่ถูกต้องในการเริ่มการสนทนาเราสามารถเปลี่ยนไปใช้ google groups: groups.google.com/forum/#!topic/knockoutjs/80_FuHmCm1s
Christoph

สวัสดีคริสตอฟวิธีนี้ได้ผลสำหรับคุณอย่างไร?
hkon

อันที่จริงฉันย้ายไปที่กรอบ AngularJS ที่ยอดเยี่ยมกว่านี้ ;-)
Christoph

1
อาจจะดีกว่าถ้าคุณเก็บแค่สองสามย่อหน้าแรกไว้เป็นคำถามและย้ายส่วนที่เหลือไปเป็นคำตอบด้วยตนเอง
rjmunro

คำตอบ:


30

หมายเหตุ:ตั้งแต่ jQuery 1.7 .live()วิธีนี้เลิกใช้แล้ว ใช้.on()แนบตัวจัดการเหตุการณ์ ผู้ใช้รุ่นเก่าของ jQuery ควรใช้ในการตั้งค่า.delegate().live()

ฉันกำลังทำงานในสิ่งเดียวกัน (knockout + jquery mobile) ฉันพยายามเขียนบล็อกโพสต์เกี่ยวกับสิ่งที่ฉันได้เรียนรู้ แต่ในระหว่างนี้มีคำแนะนำบางส่วน จำไว้ว่าฉันกำลังพยายามเรียนรู้สิ่งที่น่าพิศวง / jquery mobile

View-Model และ Page

ใช้ออบเจ็กต์โมเดลมุมมองหนึ่ง (1) รายการต่อ jQuery Mobile-page เท่านั้น มิฉะนั้นคุณอาจได้รับปัญหาเกี่ยวกับเหตุการณ์การคลิกที่ทริกเกอร์หลายครั้ง

ดู - รุ่นแล้วคลิก

ใช้เฉพาะ ko.observable-fields สำหรับ view-models click-events

ko.applyBinding ครั้งเดียว

ถ้าเป็นไปได้ให้โทรหา ko.applyBinding เพียงครั้งเดียวสำหรับทุกเพจและใช้ ko.observable แทนการเรียก ko.applyBinding หลาย ๆ ครั้ง

pagehide และ ko.cleanNode

อย่าลืมทำความสะอาดโมเดลมุมมองบางส่วนใน pagehide ดูเหมือนว่า ko.cleanNode จะรบกวนการแสดงผล jQuery Mobiles ทำให้ต้องแสดง html อีกครั้ง หากคุณใช้ ko.cleanNode บนเพจคุณจำเป็นต้องลบ data-role และแทรก html jQuery Mobile ที่แสดงผลในซอร์สโค้ด

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

pagehide แล้วคลิก

หากคุณผูกพันกับเหตุการณ์คลิกอย่าลืมล้าง. ui-btn-active วิธีที่ง่ายที่สุดในการดำเนินการนี้คือการใช้ข้อมูลโค้ดนี้:

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

เนื่องจากคำถามของฉันไม่เจาะจงมากและคุณเป็นคนที่ให้คำตอบมากที่สุดฉันจะทำให้คำตอบของคุณเป็นที่ยอมรับ
Christoph

คุณเคยคิดออกไหม? ฉันมีช่วงเวลาหนึ่งที่รวม KO และ JQM และไม่มีคำแนะนำที่ดีเกี่ยวกับวิธีการทำ (หรือ jsFiddle ที่สาธิตการสาธิตแบบ end-to-end)
kamranicus

1
ไม่ฉันย้ายไปที่กรอบ AngularJS ฉันพบว่ามันเหนือกว่า KO และมีโครงการอะแดปเตอร์ที่ค่อนข้างดีที่จะทำให้ AngularJS / jqm เป็นเพื่อนที่ดีที่สุดตลอดกาล: github.com/tigbro/jquery-mobile-angular-adapterอย่างไรก็ตามสำหรับสิ่งที่ฉันทำจนถึงตอนนี้ดูเหมือนว่าจะใช้อะแดปเตอร์นั้นมากเกินไป ท้ายที่สุดมันค่อนข้างง่ายที่จะใช้ html / css ของ jqm และเปลี่ยนการควบคุมให้เป็นคำสั่งเชิงมุม: jsfiddle.net/zy7Rg/7
Christoph

คุณสามารถสร้างโครงสร้างผมได้กำหนดไว้ที่นี่ ฉันมั่นใจว่าวิธีนี้คุณจะสามารถควบคุมแอปพลิเคชันได้อย่างสมบูรณ์
มูฮัมหมัดราฮีล
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.