О проекте


Фреймворк для быстрого построения мобильных приложений. Оптимизирован для iOS, Android, поддерживается также Chrome, Firefox, Opera, Safari, IE 10, Windows Phone 8.

Общие положения

Чем является RAD.js:

Это фреймворк системного уровня. Он позволяет разрабатывать single-page-приложение, как классическое многостраничное, и берет на себя задачи системного уровня, такие как шину сообщений, создание и уничтожение экземпляров частей приложения, транзакции переходов между views и т.д.

Чем не является RAD.js:

  • MV* фреймворком, MV* в нем реализована на базе BackboneJS.
  • Фремворком уровня приложения. Приложением может выступать любой JavaScript-объект. Фреймворк не диктует условия, как должен быть построен объект приложения.
  • Layout-движком. Layout-движок реализован как плагин navigation к ядру фреймворка.
  • UI-фреймворком. Нет смысла писать очередной UI-фреймворк, так как от проекта к проекту в реальной жизни паттерны UI и оформление приложения меняется. В качестве расширения к базовой View, реализованы ScrollableView, PopupView и ToastView, то есть те, которые имеют расширенное и часто встречающееся поведение в разных проектах. Вы сами спокойно можете писать новые View, которые часто встречаются в Ваших проектах, например ListView, и т.д.

Достоинства:

  • Оптимизирован для PhoneGap и мобильных браузеров;
  • Возможность динамически управлять экземплярами модулей приложения (создавать, уничтожать), как на уровне приложения через функциональность ядра, так и на уровне view;
  • Возможность составить приложение из слабо связанных модулей: моделей, views, сервисов (часть приложения без визуального представления) и объекта приложения;
  • Древовидная структура сообщений;
  • Debug-режим ядра и сообщений;
  • Гибкая слабосвязанная архитектура — практически любой сторонний код может быть обернут в модуль несколькими строчками кода. Падение одного из модулей не ведет к падению всего приложения;
  • Возможность отслеживания жизненого цикла view и сервисов. Наличие callback методов на все события жизненого цикла;
  • Шаблонизация. Шаблоном выступает отдельный HTML-файл, который может быть наверстан отдельно;
  • Частичная шаблонизация view. Возможность указать части шаблона view, которые будут/не будут меняться (rerender) при изменении модели;
  • Объектом приложения может выступать любой объект JavaScript;
  • Возможность расширения функциональности ядра за счет плагинов;
  • Сложные вложенные views и декларируемые анимации перехода между ними;
  • Возможности наследования views, сервисов и моделей
  • Модальные и немодальные самопозиционируемые окна
  • Динамическая маршрутизация. Достаточно указать параметр backstack: true, и транзакция между views (новое расположение views на экране) будет занесена в history браузера;
  • Повторное использование модулей в других проектах;
  • Возможность модульного тестирования, внешними фреймворками.

Объект приложения

В качестве объекта application может выступать любой JS-объект. Он будет доступен в любом модуле(views и сервисах) через this.application.
Например: this.application.logout(); в любом модуле вызовет метод logout у объекта application.
Методы у приложения могут быть любыми - просто желательно, чтобы они отображали функциональность приложения (к примеру - залогиниться, вылогиниться, выйти из приложения и т.д.). То есть, принадлежали к уровню абстракции "приложение", а не к другим уровням, например, "сеть" или "модели".

Пример объявления конструктора объекта приложения с использование методов ядра:

RAD.application(function (core) {
    'use strict';

    var app = this;

    app.start = function () {
        var options = {
            container_id: '#screen',
            content: "view.parent_widget",
            animation: 'none'
        };
        core.publish('navigation.show', options);
    };

    return app;
});
                            
Обратите внимание, что вторым параметром в RAD.application передается булевое значение, которое определяет, создавать или нет экземпляр приложения.
Стоит заметить, что вызов регистрации приложения можно выполнить только один раз, т.к. это самоопределяющаяся функция. И после этого вызова в пространстве имен RAD.application будет доступен экземпляр Вашего приложения.

Core

  • Управление жизненым циклом частей приложения (позволяет добавлять и удалять модули; фактически контролируют загрузку памяти JavaScript-машины);
  • Коммуникационный интерфейс (позволяет частям приложения общаться между собой);
  • Возможность дальнейшего расширения функциональности за счет плагинов.

Методы ядра

К примеру, методы ядра могут вызываться так:

// если ядро доступно(из плагина или на уровне application)
core.publish('navigation.show', options);
// для модуля
this.publish('navigation.show', options);
                            

subscribe

subscribe(channel, fn, context);
                                

Подписывает callback-функцию fn на выполнение в контексте context при получении сообщения по каналу channel.

Модуль автоматически подписывается на сообщения, начинающиеся с "view." и его ID. Например, модуль, зарегестрированный как "view.myModule" при создании автоматически подпишется на канал "view.myModule", так и на "view.myModule.doSomeAction".
Обрабатывать такие сообщения можно в коллбэке onReceiveMsg.

publish

publish(channel, data)
                                

Публикует сообщение на канале channel, передавая подписчикам объект data.

В случае, если сообщение публикуется на канал "view_ID" некоторого модуля, под которым он зарегистрирован, и если вы не уверены, что этот модуль существует в момент отправки сообщения, то возможно указать autocreate: true, как одно из полей в данных, передаваемых в канал. В случае необходимости, экземпляр модуля будет создан автоматически.

unsubscribe

unsubscribe([channel,] context)
                                

Отменяет подписку на канал channel в контексте объекта context, в котором выполнялся коллбэк. Если в качестве channel передан null, то данный метод отменяет все подписки объекта.

channels

channels()
                                

Возвращает существующие каналы сообщений.

register

register(viewID/serviceID, fabric)
                                

Регистрирует в ядре модуль с именем viewID/serviceID и конструктором fabric. Если модуль зарегистрирован автоматически, этот метод не требуется (см. объявление модуля).

Экземпляр модуля при этом не создается.

registerAll

registerAll(arrayOfViews)
                                

Вызывает register для каждого модуля из перечисленных в массиве arrayOfViews. Пример массива:

views = [
    {"view.start_page": view.StartPage},
    {"view.second_page": view.SecondPage},
],
                                
Обратите внимание, что имена модулей начинаются с "view.", сервисов (модулей без визуального представления) - с "service.".

startViewOrService

startViewOrService(viewID, [extras])
                                

Создает экземпляр модуля с именем viewID; использует объект extras в коллбэке самого модуля onNewExtras.

startPlugin

startPlugin(pluginID)
                                

Создает экземпляр плагина с именем pluginID из списка зарегестрированных плагинов ядра plugins

stop

stop(viewID, callback, context)
                                

Останавливает работу модуля с именем viewID, после чего выполняет callback в контексте объекта context.

startAll

startAll()
                                

Создает экземпляры и запускает все модули.

getView

getView(viewID, [extras])
                                

Возращает экземпляр модуля с именем viewID, передавая ему объект extras (см. onNewExtras). Если экземпляр не создан, инстанциирует его.

getStartedView

getStartedView()
                                

Возвращает массив всех инстанциированных модулей, при этом их видимость на экране не обязательна.

initialize

initialize(application, options)
                                

Инициализирует ядро параметрами options и сохраняет ссылку на объект application для последующего внедрения во все модули непосредственной ссылки на приложение.

getService

getService(serviceID, [extras])
                                

Полностью аналогичен методу getView

Свойства ядра

Настройки ядра задаются объектом свойств, например:

coreOptions = {
        plugins: [
            {"plugin.navigator": plugin.navigation},
            {"plugin.fastclick": plugin.fastClick},
            {"plugin.router": plugin.router}
        ],
        defaultBackstack: false,
        backstackType: 'native',
        defaultAnimation: 'slide',
        animationTimeout: 3000,
        debug: false
    };

//initialize core by new application object
core.initialize(application, coreOptions);
                            

plugins

...
plugins: [
    {"plugin.navigator": plugin.navigation},
    {"plugin.fastclick": plugin.fastClick},
    {"plugin.router": plugin.router}
],
...
                                

Массив подключаемых плагинов. На данный момент плагинами реализованы роутер, layout manager и кроссбраузерные события для touch-устройств ("tap", "swipe" и др.). Подробнее об этом в разделе plugins.

Обратите внимание, что плагины "plugin.navigator", "plugin.fastclick", "plugin.router" уже зарегистрированы и специально передавать их через список нет необходимости.

defaultBackstack

...
// no backstack
defaultBackstack: false,
// backstack enabled
defaultBackstack: true,
...
                                

Бэкстек по умолчанию для всех транзакций. При установке false история изменений содержимого экрана(смены расположения views) запоминаться не будет. Чтобы внести в историю отдельную транзакцию нужно использовать опцию backstack: true при использовании navigation.
Подробнее о бэкстеке в разделе Backstack.

backstackType

...
//backstack с использованием history API
backstackType: 'native'
//backstack с использованием hash-ссылок для history API
backstackType: 'hashbang'
//внутренняя реализация backstack
backstackType: 'custom'
...
                                

Тип бэкстека для всех транзакций. Если значение этого свойства не определено, тип бэкстека будет выбран автоматически ('native' либо 'hashbang', в зависимости от браузера). Подробнее о типах бэкстека в разделе Backstack.

defaultAnimation

...
defaultAnimation: 'slide',
...
                                

Анимация смены модулей по умолчанию. Может принимать значения 'slide' (сдвиг), 'fade' (затухание) и 'none' (мгновенное замещение). Подробнее - анимация модулей.

animationTimeout

...
animationTimeout: 3000,
...
                                

Время, через которое будет снята блокировка интерфейса (включается при начале анимации) в случае непредвиденных ошибок.

debug

...
debug: false
...
                                

Режим отладки. При установке true пишет в консоль браузера информацию о событиях, объектах ядра и каналах.

Плагины ядра

Плагины реализуют дополнительный функционал ядра, необходимый для работы системы и не зависящий от конкретной реализации логики приложения. Если нужно реализовать функциональный модуль без визуального представления в конкретном приложении, рекомендуется использовать сервисы.

Navigation

Плагин navigation обрабатывает все сообщения с корневым узлом 'navigation.' и занимается управлением views (отображение, скрытие, обеспечение бэкстека и нотификация об изменении состояния).

show

...
var options = {
    container_id: '#screen',
    content: "view.start_page",
    animation: "none",
    backstack: false,
    callback: null,
    context: null,
    extras: null

}
this.publish('navigation.show', options);
...
                                

Отображает указанный в options.content модуль в контейнере options.container_id (css-селектор).

Параметры options:

  • content - зарегистрированное имя модуля (viewID), который будет отображен;
  • container_id - селектор элемента-контейнера для отображения модуля;
  • animation - анимация
  • , используемая при смене view в указанном контейнере;
  • backstack - сохранить историю изменения положения модулей (true или false)
  • callback - функция, которая будет выполнена по завершению отображения модуля content
  • context - контекст выполнения для callback
  • extras - см. OnNewExtras
Обязательные параметры - content и container_id (последний для toast.show и dialog.show не обязателен). Остальные параметры не обязательны.

back

...
this.publish('navigation.back', options);
...
                                

Аналогичен navigation.show. При этом по умолчанию используется обратная анимация.

dialog.show

...
this.publish('navigation.dialog.show', options);
...
                                

Аналогичен navigation.show, но показывает модуль как модальное окно. Может быть закрыт при помощи dialog.close

dialog.close

...
this.publish('navigation.dialog.close', options);
...
                                

Закрывает модальное окно.

toast.show

...
options = {
    content: "view.toast",
    gravity: 'left',
};
...
this.publish('navigation.toast.show', options);
...
                                

Показывает модуль как оповещение, закрывающееся автоматически (по умолчанию - через 3 секунды) или по клику.
Параметр options.gravity - положение, принимает значения center (в центре экрана), left (прижат к левому краю), right(прижат к правому краю), top (низ экрана) и bottom (верх).

Обязательные условие - view должно быть унасленованно от RAD.Blanks.Toast.

toast.close

...
this.publish('navigation.toast.close', options);
...
                                

Закрывает оповещение.

popup.show

...
options = {
        content: "view.popup",

        target: document.getElementById(targetID),
        width: 180,
        height: 200,
        gravity: 'right',

    };
...
this.publish('navigation.popup.show', options);
...
                                

Показывает модуль как неблокирующее всплывающее окно, которое может закрыватся автоматически по клику вне его.
Свойства options:

  • target: элемент, относительно которого позиционируется popup;
  • gravity: положение относительно target; принимает значения none (направление выбирается автоматически), center (в центре экрана), left, right, top и bottom
Обязательные условие - view должен быть унасленован от RAD.Blanks.Popup.

popup.close

...
this.publish('navigation.popup.close', options);
...
                                

Закрывает всплывающее окно.

Fastclick

Плагин ядра, реализующий кроссбраузерные события «swipe», «tap», «tapdown», «tapup», «tapmove», «tapcancel»

Router

Используется ядром для навигации, реализует backstack.

backstack

options = {
    ...
    backstack: true,
    ...
}
                                    

Компонент плагина router позволяющий динамически запоминать расположение (т.е. layout) views на экране для конкретной сессии, используя history API браузера либо внутреннюю реализацию, и таким образом возвращаться к предыдущим расположениям модулей. backstack не является аналогом Routers в Backbone.js или Angular.js.

В плагине реализованы три типа бэкстека (backstackType):

  • native - бэкстек с использованием history API браузера;
  • hashbang - бэкстек с использованием генерации hash-ссылки (для браузеров, не поддерживающих history.pushState);
  • custom - внутренняя реализация (без использования history API).

Если параметр defaultBackstack в настройках Core установлен в false, то для использования бэкстека достаточно указать параметр backstack: true в запросе на смену views; таким образом, следующее расположение views на экране сохранится.

Перемещение назад по стеку осуществляется:

  • кнопками браузера "Back" и "Forward", либо вызовом history.back() (для типов бэкстека 'native' и 'hashbang');
  • публикацией сообщения 'router.back' (для всех типов)
...
history.back(); // backstackType: 'native' или 'hashbang'
...
this.publish('router.back', null); // любой backstackType
...
                                    

Router подписан на следующие сообщения:

  • 'router.clear' - при получении сообщения удаляет всю историю навигации данной сессии (опустошает бэкстек);
  • 'router.back' - при получении сообщения вызывает возврат на один шаг назад по бэкстеку;
  • 'router.beginTransition' - сообщение публикуется плагином navigation перед началом анимации (transition) смены view;
  • 'router.endTransition' - сообщение публикуется плагином navigation, когда анимация смены view завершена. При этом происходит добавление URL предыдущего положения модулей в history (бэкстек-типы 'native', 'hashbang'), либо внутренний стек ('custom')
...
this.publish('router.clear', null); //обнуление существующего backstack
...
this.publish('router.back', null); //возврат назад по backstack
...
                                    

Router публикует следующие сообщения:

  • 'backstack.pop' - сообщение публикуется в момент, когда происходит возврат назад по бэкстеку;
  • 'backstack.empty' - сообщение публикуется в момент опустошения бэкстека.

Пример использования backstack:

RAD.view("view.screen_1", RAD.Blanks.View.extend({
    url: 'source/views/screen_1/screen_1.html',
    events: {
        'tap button.next-scr': 'open'
    }
    open: function () {
        this.publish('navigation.show', {
            content: 'view.screen_2',
            container_id: '#content',
            backstack: true
            //по завершению смены view будет сохранен URL соответствующий их текущему расположению
        });
    }
}));

RAD.view("view.screen_2", RAD.Blanks.View.extend({
    url: 'source/views/screen_2/screen_2.html',
    events: {
        'tap button.next-scr': 'open'
    }
    open: function () {
        this.publish('navigation.show', {
            content: 'view.screen_3',
            container_id: '#content',
            backstack: true
            //по завершению смены view будет сохранен URL соответствующий их текущему расположению
        });
    }
}));

RAD.view("view.top_widget", RAD.Blanks.View.extend({
    className: 'block',
    url: 'source/views/top_widget/top_widget.html',
    events: {
        'tap button.go-back': 'goBack'
    },
    goBack: function () {
        "use strict";
        this.publish('router.back', null); //возврат к последнему сохраненному расположению модулей
    }
}));
                                    

View

Основа для конструктора view в приложении, представляет собой расширенный backbone.view.

Объявление модуля

Пример файла модуля start_page.js, объявление view через шаблон 'namespace':

RAD.views.StartPage = RAD.Blanks.View.extend({
    url: 'source/views/start_page.html'
})
                            
Обратите внимание, что при таком объявлении необходимо использовать регистрацию view register, чтобы модуль был доступен ядру. Рекомендуется использовать способ объявления модулей, представленный ниже.
RAD.view("view.start_page", RAD.Blanks.View.extend({
    url: 'source/views/start_page.html'
}));
                            

Methods

subscribe

...
this.subscribe(channel, function, context);
...
                                

Подписывает данный экземпляр модуля на указанный канал, является просто прямой ссылкой на метод ядра subscribe.

unsubscribe

...
this.unsubscribe([channel,] context);
...
                                

Отписывает данный экземпляр модуля от указанного канала; является просто прямой ссылкой на метод ядра unsubscribe.

publish

...
this.publish(channel, data);
...
                                

Публикует сообщение содержащее данные data, в указанный канал channel; является прямой ссылкой на метод ядра publish.

$

...
this.$('css_selector');
...
                                

Ищет указанный CSS-селектор в данном модуле и оборачивает найденый елемент в JQuery.
Аналог:

this.$el.find('css_selector');
                            

finish

...
this.finish();
...
                                

Уничтожает данный экземпляр модуля.

getChildren

...
this.getChildren();
...
                                

Возвращает массив дочерних модулей, заданных при объявлении в children или находящихся в данный момент в этом view.

bindModel

...
this.bindModel(model);
...
                                

Устанавливает модель для модуля. При этом будет вызван render() для перерисовки модуля. Модуль будет автоматически подписан на события модели.

changeModel

...
this.changeModel(model);
...
                                

Заменяет модель модуля на переданную в model. При этом будет вызван render() для перерисовки модуля.

unbindModel

...
this.unbindModel(forceRender);
...
                                

Удаляет модель модуля. Если параметр равен true, то при этом будет вызван render() для перерисовки модуля.

Обратите внимание, что если в шаблоне не проверяется наличие модели, вызов метода может вызвать ошибку. Рекомендуется использовать только в крайнем случае, если задача иначе не решаема.

render

...
this.render();
...
                                

Перерисовывает содержимое модуля.

Обычно вызов этого метода вручную не требуется.

refreshScroll

...
this.refreshScroll();
...
                                

Обновляет границы скроллящегося контента для правильного отображения прокрутки.

Обычно вызов этого метода вручную не требуется. Может понадобиться только в случае ручной вставки контента в элемент модуля, что не рекомедуется.

Callbacks

Коллбэки описываются при объявлении view, например:

RAD.view("view.start_page", RAD.Blanks.View.extend({
    url: 'source/views/start_page.html',
    onEndRender: function () {
        "use strict";
        console.log('page rendered!');
    }
}));
                            

Представляют собой функции, вызываемые при событиях жизненого цикла view: life_cycle

 // выполняется во время создания экземпляра
onInitialize: function () {},

// выполняется при получении view данных через navigator
onNewExtras: function () {},

// выполняется при получении сообщения на канал совпадающий с viewID
onReceiveMsg: function () {},

// выполняется перед началом шаблонизации(создания html view)
onStartRender: function () {},

// выполняется после создания html содержимого из шаблона,
// iScroll в наследниках RAD.Blanks.ScrollableView при первом отображении еще не существует
onEndRender: function () {},

// выполняется перед началом анимации присоеденения view,
// iScroll в наследниках RAD.Blanks.ScrollableView уже существует
onStartAttach: function () {},

// выполняется после окончания анимации присоеденения view
onEndAttach: function () {},

// выполняется после удаления view из DOM
onEndDetach: function () {},

// выполняется при уничтожении модуля
onDestroy: function () {}
                                 

onInitialize

...
onInitialize: function(){ };
...
                                

Выполняется первым из коллбэков в конце конструктора при создании экземпляра view. Это последний момент, когда можно напрямую задать модулю модель. В дальнейшем необходимо использовать такие методы view, как bindModel и unbindModel.

onStartRender

...
onStartRender: function(){ };
...
                                

Выполняется перед render() модуля. На этом этапе HTML-представления модуля еще нет.

onEndRender

...
onEndRender: function(){ };
...
                                

Выполняется в render() модуля, когда HTML-представление сгенерировано из шаблона. Обработка всего, что связано с HTML(например, вставка дочерних модулей или добавление классов), должна происходить в этом методе.

Обратите внимание, что render() модуля вызывается при первом отображении модуля и при каждом изменении модели модуля, на которую он автоматически биндится при создании. Возможно также вручную вызвать render() модуля.

onNewExtras

...
var options = {
    ...
    extras: {
        hello : 'world'
    }

}
this.publish('navigation.show', options);
...
onNewExtras: function(extras){
    console.log(extras.hello);
};
...
                                

Выполняется при передачи через навигатор новых extras. Идеально подходит для передачи новой модели или параметров отображаемого модуля. Принимает параметр extras.

onStartAttach

...
onStartAttach: function(channel, options){ };
...
                                

Выполняется перед отображением модуля и перед началом анимации (даже если ее нет). В качестве параметров принимает канал, по которому пришло сообщение, и данные от плагина navigation.

onEndAttach

...
onEndAttach: function(channel, options){ };
...
                                

Выполняется после окончания отображениея модуля и после окончания анимации (даже если ее нет). В качестве параметров принимает канал, по которому пришло сообщение, и данные от плагина navigation.

onEndDetach

...
onEndDetach: function(channel, options){ };
...
                                

Выполняется после окончательного отсоеденения модуля из текущего DOM. В качестве параметров принимает канал, по которому пришло сообщение, и данные от плагина navigation.

onDestroy

...
onDestroy: function(){ };
...
                                

Вызывается перед уничтожением экземпляра модуля (деструктор). Нет входных параметров.

onReceiveMsg

...
onReceiveMsg: function(msg, data){ };
...
                                

Модуль при создании автоматически подписывается на сообщения, начинающиеся с "view." и его имени, например, модуль myModule подпишется как на сообщения "view.myModule", так и на "view.myModule.doSomeAction".
Параметры:
msg - канал сообщения, строка;
data - объект переданных данных (см. публикацию сообщений publish).

Cервисы аналогично подписываются на сообщения, начинающиеся с "service." + "имя_модуля"

loader.done

...
onNewExtras: function (extras) {
    var self = this;
    self.loader.done(function () {
        self.$("#options").html(extras.data);
    });
},
...
                            

Это не совсем коллбэк-метод. Каждый модуль имеет deferred-объект loader, которому возможно передать функцию, которая выполнится после загрузки HTML-представления модуля. В случае, если HTML или HTML-шаблон были загружены, функция выполнится сразу. Например, можно использовать этот метод тогда, когда переданные через extras данные необходимо вставить в готовый HTML.

Свойства View

Модули унаследованы от backbone.view, здесь описаны только некоторые свойства.

url

...
url: 'source/views/inner/third_widget/third_widget.html'
...
                                

Ссылка на файл HTML, используемый модулем в качестве шаблона.

tagName и ClassName

...
tagName: 'li',
className: 'my_list_item'
...
                                

Определяют, каким элементом будет представлен контейнер модуля и его класс(ы).

children

...
children:[
    {
        container_id: '.sidebar',
        content: 'view.sidebar_menu',
    },
    {
        container_id: '.content',
        content: 'view.default_content',
    }
]
...
                                

Массив дочерних модулей, которые будут загружены и показаны вместе с родительским. Атрибуты аналогичны 'navigation.show'

events

...
events:{
    'focus .search-by-name': 'showAutocomplete',
    'click .my_button':'buttonAction'
},
buttonAction: function(e){
    console.log(e.currentTarget + ' clicked');
},
showAutocomplete: function(){}
...
                                

Подписка элементов модуля на события.

model

...
//декларативный способ
model: RAD.models.noteList;
...
//задание модели при инициализации
...
onInitialize: function () {
    "use strict";

    this.model = RAD.models.noteList;
    this.model.add([
        {title:'test note 1', 'description':'test note description 1'},
        {title:'test note 2', 'description':'test note description 2'},
        {title:'test note 3', 'description':'test note description 3'}
    ]);
},
...
                                

Задает модель данных для модуля. Подробнее о моделях в разделе model. View автоматически подписывается на события модели.

произвольные свойства

...
currentPageDisplay: 2,
showMyCustomWidget: false
...
                                

Для хранения состояний модуля можно создавать и использовать любые свойтва.

Следите за тем, чтобы создаваемые свойства не переопределяли имена уже существующих, это может привести к ошибкам.

Toast

Модуль для оповещений, автоматически закрывающийся через определенное время (по умолчанию - 3 секунды). См. navigation.toast.show.

RAD.view("view.CompleteToast", RAD.Blanks.Toast.extend({
    //шаблон
    url: 'source/views/start_page.html',
    //время показа
    showTime: 5000
}));
                            
Обратите внимание: элемент модуля toast помещается в body, игнорируя container_id в опциях navigation.

Popup

Модуль реализует самопозиционирующийся попап. Способы позиционирования задаются в опциях navigation.popup.show.

RAD.view("view.PopupOverview", RAD.Blanks.Popup.extend({
    url: 'source/views/popup_dashboard_overview/popup_dashboard_overview.html',

    onInitialize: function (){
        var Model = Backbone.Model.extend();

        this.model = new Model();
    },
    onNewExtras: function (extras) {
        'use strict';

        this.model.set({msg: extras});
    },
    // будет ли popup закрыватся по клику вне модуля
    outSideClose: true,
    // будет ли уничтожатся инстанс модуля при закрытиии(по умолчанию - уничтожается)
    // если надо не уничтожать - прописать следующую строку
    onCloseDestroy: false
});
                            
Обратите внимание: элемент модуля popup помещается в body, игнорируя container_id в опциях navigation.

Scrollable view

Модуль со скроллящимся контентом, использует доработанную версию iScroll-lite.

RAD.view("view.start_page", RAD.Blanks.ScrollableView.extend({
    url: 'source/views/start_page.html'
}));
                            
Обратите внимание, что для работы IScroll'а необходимо наличие в HTML класса "scroll-view" для контейнера, в котором скролится контент (при наследовании от ScrollableView он автоматически вешается на весь view).
Для правильной работы скролла при изменении размера скроллящегося контента, для его контейнера необходим установленный атрибут data-template.

Animation

Вид анимации, используемой при отображении модулей. Возможны следующие значения:

  • 'slide' или 'slide-in'(абсолютно аналогично) - новый модуль выезжает справа, старый заезжает влево;
  • 'slide-out' - анимация противоположная 'slide-in';
  • 'fade' - альфа анимация; старый модуль затухает, а новый проявляется;
  • 'none' - без анимации, новый модуль просто вставляется вместо старого;
  • не определено - выставляется по умолчанию 'slide'.
/* Возможно использование кастомной анимации, */
/* для этого необходимо описать в CSS анимацию, подобную следующей */
/* суфиксы -in и -out говорят о направлении */
.new-page.fade-in,
.new-page.fade-out {
    opacity: 0;

    -webkit-transition: opacity 350ms ease;
    -moz-transition: opacity 350ms ease;
    -ms-transition: opacity 350ms ease;
    -o-transition: opacity 350ms ease;
    transition: opacity 350ms ease;
}

.old-page.fade-in,
.old-page.fade-out {
    opacity: 1;

    -webkit-transition: opacity 175ms 175ms ease;
    -moz-transition: opacity 175ms 175ms ease;
    -ms-transition: opacity 175ms 175ms ease;
    -o-transition: opacity 175ms 175ms ease;
    transition: opacity 175ms 175ms ease;
}

.animate > .new-page.fade-in,
.animate > .new-page.fade-out {
    opacity: 1;
}

.animate > .old-page.fade-in,
.animate > .old-page.fade-out {
    opacity: 0;
}
                            

Шаблоны

Шаблоны полезны при рендеринге объемных и сложных частей HTML-разметки из JSON-данных. Для осуществления шаблонизации используется метод из библиотеки Underscore.js, который компилирует шаблоны в функции, которые могут быть вызваны для рендеринга этого шаблона. Путь к HTML-шаблону передается в качестве свойства url при обьявлении модуля View. При рендеринге HTML-представления модуля в функцию-шаблонизатор передается view.model этого модуля, преобразованная в JSON-объект.

Пример разметки с шаблонами:

<div class="scroll-view-body">
    <div class="block">
        <div class="block-title">
            <h2>All Action Items</h2>
        </div>
        <ul class="list-items-view">
            {{# _(model).each(function(action) { }}
            <li >
                <span class="priority-indicator {{ action.priority }}"></span>
                <div class="list-item-row row-title">
                    <div class="info">{{ action.title }}</div>
                    <div class="details">Due: {{ action.due }}</div>
                </div>
                <div class="list-item-row">
                    <div class="info">Patient: {{ action.patient }} (DOB: {{ action.dob }} )</div>
                    <div class="details">CM Program: {{ action.cm_program }}</div>
                </div>
            </li>
            {{# }); }}
        </ul>
    </div>
</div>
                            

Синтаксис внутри шаблона:

  • {{ ... }} - для интерполяции переменных;
  • {{# ... }} - для выполнения вычислений (JavaScript-код внутри шаблона);
  • {{{ ... }}} - для экранирования спец-символов (HTML-escaped);
  • model - преобразованная в JSON-объект view.model
У переданного в шаблон объекта model отсутствуют свойства и методы Backbone.Model и Backbone.Collection. Если необходимо получить доступ непосредственно к view.model либо другим свойствам и методам можно использовать прямую ссылку this на текущий view внутри шаблона.

Для построения HTML из большого массива данных удобно использовать методы библиотеки Underscore, такие как each(), sortBy(), filter() и т.д.

Пример использования методов Underscore.js в шаблоне

RAD.view("view.persons_list", RAD.Blanks.View.extend({
    url: "source/views/persons_list.html",
    onInitialize: function () {
        this.model = RAD.model('persons', Backbone.Collection, true);
        this.model.add([
            {"firstName": "John", "lastName": "Doe"},
            {"firstName": "Homer", "lastName": "Simpson"},
            ...
            ...
            {"firstName": "Fox", "lastName": "Mulder"}
        ]);
    }
}));
                            
<!-- Шаблон persons_list.html: -->
<ul class="persons_list">
    {{# _(model).each(function (person) { }}
    <li>{{ person.firstName }} <b>{{ person.lastName }}</b></li>
    {{# }); }}
</ul>

<!-- После шаблонизации: -->
<ul class="persons_list">
    <li>John <b>Doe</b></li>
    <li>Homer <b>Simpson</b></li>
    ...
    ...
    <li>Fox <b>Mulder</b></li>
</ul>
                            

Для того, чтобы при изменении model была перерисована лишь часть модуля, контейнеру, содержащему шаблон, необходимо присвоить атрибут data-template.

Пример использования атрибута data-template:

<div id="my_module">
    <!-- содержимое <div> не будет перерисовано (радиокнопка не будет сброшена в положение по умолчанию) -->
    <div class="controls">
        <p>Sort by:</p>
        <label><input type="radio" name="sort_by" value="first_name" />First Name</label>
        <label><input type="radio" name="sort_by" value="last_name" checked />Last Name</label>
    </div>
    <!-- содержимое <ul> будет перерисовано -->
    <ul class="persons_list" data-template>
        {{# _(model).each(function (person) { }}
        <li>{{ person.firstName }} <b>{{ person.lastName }}</b></li>
        {{# }); }}
    </ul>
</div>
                            

Model

В качестве модели данных используется backbone.model.

//создание модели
RAD.model('name', <backbone.model>, [instantiate]);

//получение модели (вернет undefined, если модели нет)
RAD.model('name');
                            

Для работы с моделями вызывается метод RAD.model(...), где используются следующие параметры:

  • name - имя модели, под которым в RAD.models будет создана модель. Может быть задано в виде:
    <namespace>.<subnamespace>.name
  • backbone.model - модель backbone (пример ниже);
  • instantiate - параметр создания модели. true (или не передан) - создать экзепляр, false - сохранить как конструктор.
Создание конструктора модели:
RAD.model('note', Backbone.Model.extend({defaults: {
        title: "-",
        description: "-"
    }
}), false);
                            

Создание модели в модуле:

...
onInitialize: function () {
    "use strict";
    var md = RAD.model('note');
    this.model = new md();
    this.model.set({title:'test note 1', 'description':'test note description 1'})
},
...
                            

Использование модели в шаблоне:

...
<div class="my page">
    <h3>Use models, Luke!</h3>
    <div id="title">{{ model.title }}</div>
    <div id="description">{{ model.description }}</div>
</div>
...
                            
При изменении модели будет вызван метод render() модуля и автоматически обновится ВСЕ его содержимое. Для того, чтобы при изменении модели была перерисована лишь часть модуля, контейнеру содержащему шаблон необходимо присвоить атрибут data-template (см. примеры в разделе "Шаблоны")

Модель уровня приложения

Если необходимо, чтобы модель была доступна нескольким модулям, можно создать экземпляр модели на уровне приложения, передав последним параметром true.

Создание экземпляра модели:
RAD.model('message', Backbone.Model.extend({
        defaults: {
            title: "-",
            description: "-"
        }
}), true);


                            
Изменение данных модели:
...
RAD.model('message').set({title: 'new note', description: 'lorem ipsum dolor'});
...
                            

Service

Сервисы не имеют визуального представления и могут быть использованы для обработки внутренней логики приложения. Имеют некоторые callback-методы аналогичные view; также могут иметь произвольные свойства.

Объявление

Сервисы объявляются аналогично модулям и тоже инстанциируются при получении первого сообщения.

RAD.service("service.my_service", RAD.Blanks.Service.extend({
    onReceiveMsg: function (channel, data) {
        "use strict";
        var backway = data.split("").reverse().join("");
        this.publish('view.widget2', backway);
    }
}));
                            

Callbacks

Коллбэки сервисов аналогичны view callbacks, но из-за отсутствия визуального представления применимы не все.

onInitialize

...
onInitialize: function(){ };
...
                                

Выполняется первым из коллбэков в конце конструктора при создании экземпляра service. Это последний момент, когда можно напрямую задать модулю модель. В дальнейшем необходимо использовать такие методы view, как bindModel и unbindModel.

onDestroy

...
onDestroy: function(){ };
...
                                

Вызывается перед уничтожением экземпляра сервиса (деструктор). Нет входных параметров.

onReceiveMsg

...
onReceiveMsg: function(msg, data){ };
...
                                

Сервис при создании автоматически подписывается на сообщения, начинающиеся с "service." и его имени. Аналогично module.onReceiveMsg
Параметры:
msg - канал сообщения, строка;
data - объект переданных данных (см. публикацию сообщений publish).

Utils

Набор полезных методов для использования в модулях. Расположенных в отдельном файле utils.js

removeMultipleSpaces

RAD.utils.removeMultipleSpaces(str)
                                

Заменяет в строке str множественные (более одного) пробелы одним символом. Например, RAD.utils.removeMultipleSpaces('one,  two,    three,    four') вернет 'one, two, three, four'.

getCoords

RAD.utils.getCoords(elem, [parent])
                                

Возвращает top и left координаты html-элемента elem (в px) относительно родительского элемента parent. Если parent не передан - вернет координаты относительно окна браузера.

Пример:

...
<div id="foo" style="padding: 10px">
    <div id="bar">Lorem Ipsum</div>
</div>
...
                                
var elem = $el.find('#bar'),
    parent = $el.find('#foo');

RAD.utils.getCoords(elem, parent); //вернет {top: 10; left: 10}
                                

dispatchResizeEvent

RAD.utils.dispatchResizeEvent(targetEl)
                                

Этот метод будет полезен, когда необходимо принудительно обновить размеры скроллящегося контента внутри targetEl (scroll-view элемента, см. ScrollableView)

Base64

RAD.utils.Base64.encode(input); //кодирование
...
RAD.utils.Base64.decode(input); //декодирование
                                

Предоставляет методы для кодирования encode и декодирования данных input по системе Base64 для дальнейшего хранения либо передачи по сети.

Пример:

RAD.utils.Base64.encode('Hello, World!'); //вернет "SGVsbG8sIFdvcmxkIQ=="
...
RAD.utils.Base64.decode('SGVsbG8sIEJhc2U2NCE='); //вернет "Hello, Base64!"
                                

QueryFactory

var factory = new RAD.utils.QueryFactory();
                                

Конструктор для Promise-объектов

1) query = factory.createQuery ({options}) - в качестве опций указывается функции в случае успеха, в случае ошибки, и контекст. В каком случае выполняется функция ошибки: когда прерывается очередь выполнения или в каком-то другом случае?
2) query.then(fn) - добавляет в конец очереди выполнения функцию. в нее обязательно должен быть передан prom? prom - это наш query?
3) prom.next(data) - вызывает следующую функцию в очереди. аргументом являются какие-то данные которые хотим передать в следующую функцию?
4) судя по utils.js и по примеру query.when(fn) тоже добавляет в очередь выполнения функции но когда они выполняются?
5) prom.success(data) - это обязательно должно быть в последней функции в очереди или может быть вставлено в любую функцию в очереди? если нужно чтобы сработала error нужно сделать prom.error()?

serializeFormToObject

RAD.utils.serializeFormToObject(formSelector)
                                

Вернет поля формы найденной по селектору formSelector преобразованные в объект

Пример:

<form name="poll" id="poll">
    <p>Please, introduce yourself</p>
    <label for="firstname">First Name</label>
    <input id="firstname" name="firstname" type="text" /><br>
    <label for="lastname">Last Name</label>
    <input id="lastname" name="lastname" type="text" /><br>
    <p>Your age between:</p>
    <label><input name="age" value="6-17" type="radio" />6-17</label>
    <label><input name="age" value="18-29" type="radio" checked />18-29</label>
    <label><input name="age" value="30-41" type="radio" />30-41</label>
    <label><input name="age" value="42-99" type="radio" />42 and older</label><br><br>
    <label>I have read and accept the terms of <a href="#">privacy policy</a>
        <input name="privacy_pol" value="agree" type="checkbox" />
    </label>
</form>
                                
RAD.utils.serializeFormToObject('#poll');
// вернет
// {
//     firstname: "",
//     lastname: "",
//     age: ["6-17", "18-29", "30-41", "42-99"],
//     privacy_pol: "agree"
// }
                                

serializeFormToString

RAD.utils.serializeFormToString(formSelector)
                                

Вернет поля формы найденной по селектору formSelector преобразованные в строку

Так, например, для формы в примере выше:

RAD.utils.serializeFormToString('#poll');
// вернет "{age:['6-17', '18-29', '30-41', '42-99'],firstname:'',lastname:'',privacy_pol:'agree'}"
                                

Namespace

Фреймворк позволяет использовать шаблон 'namespace' (пространства имен) для структурированного хранения собственных конструкторов models, views, services и т.д., или наследования от существующих.

RAD.namespace(destination, [obj]);
                            
  • destination - местонахождение namespace
  • obj - объект, который будет определен в этом namespace
//Создание объекта authServer в пространстве имен RAD.network.nodes
RAD.namespace('network.nodes.authServer', {name: 'AuthServer', baseUrl: 'http://192.168.1.1/'});

//Создаст в пространстве RAD.utils метод getRndInt
RAD.namespace('RAD.utils.getRndInt', function (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
});

RAD.namespace('RAD.models.toDoList', Backbone.Collection.extend({
    comparator: function (task) {
      return task.get("name");
    }
}));

//Пример наследования view от RAD.Blanks.ScrollableView и последующего его объявления.
RAD.namespace('RAD.views.ListView', RAD.Blanks.ScrollableView.extend({
    model: new RAD.models.toDoList
}));
RAD.view('view.todo_list', RAD.views.ListView);
                            
Обратите внимание: если при наследовании view в качестве прототипа выступает не RAD.Blanks.View, а Backbone.View, то view-наследник не будет иметь callback-методов RAD.Blanks.View.

Фреймворком предоставляются для использования готовые namespace:

  • RAD.Class - для собственных классов;
  • RAD.models - для моделей и коллекций;
  • RAD.views - для конструкторов View;
  • RAD.services - для сервисов;
  • RAD.plugins - для плагинов;
  • RAD.utils - для полезных методов-утилит.

FAQ

Часто задаваемые вопросы.

  • Как узнать какие view видны на экране?
    querySelectorAll(«[view]»);
                                                
  • Как получить ссылку на HTML-елемент-контейнер, в котором находится view?
    querySelector(«[view='viewID']»);
                                                
  • Как получить список всех view, экземпляры которых созданы?
    RAD.core.getStartedViews();
                                                
    К счастью (или к сожалению), у нас пока не было не одного реально необходимого случая для вызова данного метода.
  • Получение экземпляра любого модуля приложения.
    В любой части приложения возможно получить непосредственную ссылку на любой зарегистрированный модуль приложения(view или сервис) через прямую ссылку на ядро:
    RAD.core.getView(viewID, extras);
                                                
    RAD.core.getService(viewID);
                                                
    Учтите, что архитектура приложения на RAD.js, рассчитана на слабую связанность модулей и динамическое создание/уничтожение частей приложения, поэтому использовать данный механизм не рекомендуется.
    Связано это с тем, что данные методы вернут вам ссылку на экземпляр уже существующего модуля с указанным ID, или же создадут новый. Если вы сохраните эту ссылку, например, в атрибуте вашего view, какая-нибудь другая часть вашего приложения (опять же, через методы ядра) может удалить экземпляр модуля, а потом создать его экземпляр уже с другими данными или моделью.
    
 Итогом будет наличие сильной связи, наличие ссылки на модуль, который уже уничтожен и нигде больше не используется.

В качестве рекомендации можно предложить использовать методы ядра только в объекте приложения. В этом случае вся работа по созданию и уничтожению модулей будет сосредоточена в одном месте, и найти логическую ошибку будет намного легче.
  • Модули являются Backbone.View?
    Нет, но они созданы на основе Backbone.View и имеют почти те же задачи, основная из которых - отображение модуля на странице
  • Что такое канал (channel)? У каждого модуля свой channel? Как происходит subscribe и publish в определенный channel? В канал публикуется просто некое сообщение, и модуль, который слушает канал в ожидании этого сообщения должен на него среагировать?
    Канал является совокупностью публикатора, модуля подписчика и медиатора. Сообщениями, которыми обмениваются эти компоненты, являются события (“команды”, “намерения”).
  • Что такое extras?
    Дополнительные данные, которые передаются во view при публикации сообщения через navigator.
  • На какие сообщения подписываются модули при регистрации?
    На события, имя которых начинается с "view." + имя модуля. При публикации такого сообщения вызывается onReceiveMsg этого модуля.
  • Что такое backstack? Как именно работает и какие свойства нужно задать для его правильной работы?
    Это компонент плагина router, позволяющий динамически запоминать расположение (т.е. layout) views на экране для конкретной сессии, используя history API браузера либо внутреннюю реализацию - и таким образом возвращаться к предыдущим расположениям модулей.

    Для использования бэкстека достаточно указать параметр backstack: true в запросе на смену views. Более подробная информация в разделе backstack
  • Будет ли работать анимация дочерних модулей, если задать её таким образом? Например:
    RAD.views.ParentWidget = RAD.Blanks.View.extend({
        url: 'source/views/parent_widget/parent_widget.html',
        children: [
            {
                container_id: '.content',
                content: "view.inner_first_widget",
                animation: 'slide'
            },
            {
                container_id: '.top',
                content: "view.inner_third_widget",
                animation: 'fade'
            }
        ]
    });
                                                
    Да, но использовать не рекомендуется - будет визуально некрасиво.
  • Что происходит с дочерними модулями ("children") во время рендеринга родительского модуля?
    Во время самого первого рендеринга родительского модуля происходит следующее:
    1. Выполняется коллбэк onStartRender() (если определен);
    2. Происходит рендер модуля;
    3. Происходит присоединение (attach) дочерних модулей;
    4. Выполняется коллбэк onEndRender() (если определен).

    Во время повторного рендеринга в порядок действий добавляется предварительное отсоединение дочерних модулей:
    1. Выполняется коллбэк onStartRender() (если определен);
    2. Происходит отсоединение (detach) дочерних модулей;
    3. Происходит рендер модуля;
    4. Происходит присоединение (attach) дочерних модулей;
    5. Выполняется коллбэк onEndRender() (если определен).

    Таким образом, заново рендерится только родительский модуль, а "children" - нет.
  • Можно ли "прикрепить" iScroll к какому либо дочернему HTML-элементу модуля при отображении этого модуля?
    Да, контейнеру для iScroll указать класс "scroll-view".
  • Какие существуют события iScroll?
    См. события в документации iScroll4.