Як архітектурувати веб-додаток за допомогою 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)

Хоча ви знайдете більшість прикладів моделей подання knockoutjs, використовуючи синтаксис літералу об'єкта, я використовую традиційний синтаксис функції з допоміжними об'єктами "self". В основному, це справа смаку. Але коли ви хочете, щоб одна спостережувана властивість посилалася на іншу, ви не можете записати об’єктний літерал за один раз, що робить його менш симетричним. Це одна з причин, чому я вибираю інший синтаксис.

Наступною причиною є послуга, яку я можу передати як параметр, як я вже згадував раніше.

У цій моделі подання є ще одна річ, і я не впевнений, що вибрав правильний шлях. Я хочу періодично опитувати службу ajax, щоб отримати результати з сервера. Отже, я вирішив застосувати для цього методи startServicePolling / stopServicePolling . Ідея полягає в тому, щоб розпочати опитування на pagehow і зупинити його, коли користувач переходить на іншу сторінку.

Ви можете ігнорувати синтаксис, який використовується для опитування служби. Це магія 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
Я не впевнений, що є достатньо конкретне запитання для людей. Мені подобаються деталі, які ви маєте тут, але, здається, це дозволяє перейти до обговорення. Менше слів: "Гарний блог";)
Бернхард Хофманн,

Я рада, що вам сподобалось. Я трохи переживав, що написав так багато, що люди бояться написати коротку відповідь. Однак будь-яка дискусія вітається. І якщо stackoverflow є неправильним місцем для початку обговорення, ми можемо перейти до груп Google: groups.google.com/forum/#!topic/knockoutjs/80_FuHmCm1s
Крістоф,

Привіт, Крістофе, як такий підхід у вас вийшов?
hkon

Власне, я перейшов до більш дивовижної фреймворку AngularJS ;-)
Крістоф

1
Це може бути краще, якщо ви зберегли лише перші пару абзаців як питання, а решту перенесли на самовідповідь.
rjmunro

Відповіді:


30

Примітка: Починаючи з jQuery 1.7, .live()метод застарілий. Використовуйте .on()для приєднання обробників подій. Користувачам старих версій jQuery слід використовувати .delegate()перевагу .live().

Я працюю над одним і тим же (нокаут + мобільний jquery). Я намагаюся написати допис у блозі про те, що я дізнався, але тим часом є деякі підказки. Пам'ятайте, що я також намагаюся навчитися нокауту / jquery mobile.

Модель перегляду та сторінка

Використовуйте лише один (1) об’єкт моделі перегляду на кожну сторінку jQuery Mobile. В іншому випадку у вас можуть виникнути проблеми із подіями кліків, які запускаються кілька разів.

Перегляньте модель та натисніть

Використовуйте лише поля ko.observable для подій натискання на моделі перегляду.

ko.applyПрив'язка один раз

Якщо можливо: викликайте ko.applyBinding лише один раз для кожної сторінки та використовуйте ko.observable замість того, щоб викликати ko.applyBinding кілька разів.

pagehide та ko.cleanNode

Не забудьте очистити деякі моделі перегляду на сторінці прихованої сторінки. ko.cleanNode, здається, заважає візуалізації jQuery Mobiles - змушує її рендерити html. Якщо ви використовуєте ko.cleanNode на сторінці, вам потрібно видалити ролі даних та вставити відтворений jQuery Mobile html у вихідний код.

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

приховати сторінку та клацнути

Якщо ви прив’язуєтесь до подій кліків - не забудьте очистити .ui-btn-active. Найпростіший спосіб зробити це за допомогою цього фрагмента коду:

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

Оскільки моє запитання було дуже невизначеним, і саме ви вкладаєте найбільше праці у відповідь, я зроблю ваше прийнятим.
Крістоф

Ви коли-небудь це зрозуміли? У мене пекельний час інтегрування KO та JQM, і немає хороших посібників про те, як це зробити (або jsFiddle, що демонструє наскрізну демонстрацію).
kamranicus

1
Ні, я перейшов до фреймворку AngularJS. Я виявив, що це надзвичайно для KO. І є досить непоганий проект адаптера, щоб зробити AngularJS / jqm найкращими друзями назавжди: github.com/tigbro/jquery-mobile-angular-adapter Однак, для того, що я робив досі, здавалося, було б занадто добре використовувати цей адаптер. Адже досить просто просто використати html / css jqm і перетворити елементи керування в Angular директиву: jsfiddle.net/zy7Rg/7
Крістоф,

Ви можете створити структуру, яку я визначив тут . Я впевнений, що таким чином ви матимете повний контроль над заявкою.
Мухаммед Рахіл
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.