jRIApp - новый HTML5 фреймворк для создания интернет бизнес приложений

Источник: habrahabr
MaximTsap

jRIApp - ещё один HTML5 фреймворк, созданный для разработки Web приложений, которые по своей функциональности мало чем уступают desktop приложениям.

Основное отличие от уже существующих фреймворков типа angularJS или emberJS, это наличие интегрированного с фреймворком сервиса данных, а также использование MVVM дизайн-архитектуры вместо наиболее распространенного в фреймворках такого типа MVC дизайна. 
В общих чертах его можно охарактеризовать как HTML5 Фреймворк реализующий привязку к данным, имеющий инфраструктуру для декларативного прикрепления логики к HTML элементам, имеющий классы для работы с данными (DbContext, DbSet) и имеющий реализованную серверную часть дата сервисов.

Клиентская часть фреймворка написана на javascript ( сейчас в разработке typescript версия ), а серверная часть на C#. 

Этот фреймворк опубликован на GitHub под MIT лицензией. Он включает демо-приложение написанное с использованием ASP.NET MVC4 и содержит документацию по его использованию.

Стиль приложений создаваемых с использованием jRIApp очень напоминает создание приложений с использованием Microsoft Silverlight с WCF RIA сервисом. Привязка к данным имеет схожий синтаксис - однонаправленная, двунаправленная, может использовать конвертер данных. Действия (action) можно привязывать через команды (command) к пользовательским элементам типа кнопки или гиперссылки.

К примеру, так создается в демо-приложении переключатель страниц:

<div style="margin-top:40px;text-align:left; border:none;width:100%;height:15%"> <!--пэйджер--> <div style="float:left;" data-bind="{this.dataSource,to=dbSet,source=VM.productVM}" data-view="name=pager,options={sliderSize:20,hideOnSinglePage=false}"> </div> <!--вывод общего кол-ва и кол-ва выбранных записей --> <div style="float:left; padding-left:10px;padding-top:10px;"> <span>Total:</span> <span data-bind="{this.value,to=totalCount,source=VM.productVM.dbSet}"></span>,   <span>Selected:</span> <span data-bind="{this.value,to=selectedCount,source=VM.productVM}"></span> </div>      <!--кнопка для добавления нового продукта--> <button style="float:right;" data-bind="{this.command,to=addNewCommand,mode=OneWay,source=VM.productVM}"> + New Product </button> </div>

Шаблоны, также имеют схожий тип создания - в них исключается использование скрипта подобного циклам foreach, которые повторяют вывод кусков разметки в результирующий HTML документ. Эта функциональность в шаблонах прекрасно заменяется, тем, что каждый DOM элемент к свойствам которого осуществляется привязка к данным, на самом деле обертывается при создании привязки видом элемента (element view), который по сути может привязывать любую логику к HTML элементу. Таким образом jQuery плагин привязывает логику к HTML элементу, однако фреймворк делает это в декларативном стиле.

Пример небольшого шаблона:

<div id="stackPanelItemTemplate" data-role="template" class="stackPanelItem" > <fieldset> <legend><span data-bind="{this.value,to=radioValue}"></span></legend> Time:  <span data-bind="{this.value,to=time,converter=dateTimeConverter,converterParam='HH:mm:ss'}"></span> </fieldset> </div>

Помимо этого имеются уже готовые пользовательские элементы, интегрированные с компонентами для работы с данными, такие как - DataGrid, DataForm, StackPanel, ComboBox и др. Валидация данных проходит как на клиенте, так и на сервере и использует метаданные задаваемые на серверной стороне.

В дополнение, я хотел бы включить в этот топик немного основ для начала работы с Фреймворком.

Базовый класс фреймворка:


Чем он так примечателен?
Во первых он несет в себе функции observer, т.е. к нему можно присоединять события, на которые подписчики хотят получать уведомления. Он также несет в себе базовую функцию уничтожения объекта, чтобы освободить занятые им ресурсы ( в основном ссылки на другие объекты и события ), и также может уведомить другие объекты о своём уничтожении с помощью события, и ещё может уведомлять подписчиков об ошибках, которые произошли в этом объекте ( как правило клиенты подписываются только на событие error у объектов Global и Application ).

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

Пример создания нового класса объекта:

var NewObject = RIAPP.BaseObject.extend( { //конструктор _create:function (radioValue) { this._super(); this._radioValue = radioValue; }, //переопределение базового метода _getEventNames:function () { var base_events = this._super(); return ['radio_value_changed'].concat(base_events); }, //вызвать событие в собственном методе _onRadioValueChanged: function(){ this.raiseEvent('radio_value_changed',{value: this.radioValue}) } }, { //создание нового свойства radioValue:{ set:function (v) { if (this._radioValue !== v){ this._radioValue = v; this.raisePropertyChanged('radioValue'); this._onRadioValueChanged(); } }, get:function () { return this._radioValue; } } }, function (obj) { //регистрация класса, чтобы его можно было достать вне модуля //app - здесь доступно из внешнего closure scope app.registerType('custom.NewObject', obj); });

В дальнейшем, класс объекта можно получать из других модулей

var Instance = app.getType('custom.NewObject').create('radioValue1');

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

Например, в одном из модулей, newObjMod, мы экспортируем класс:
var thisModule = this; thisModule.NewObject = NewObject;
а в другом импортируем:
var NewObject = app.modules.newObjMod.NewObject;

Изучив основу определения классов в Фреймворке можно перейти к определению привязки к данным, т.е. к объекту,  Binding .

Привязка к данным:

В принципе, объект, Binding, можно создавать в коде, но более удобно, это, декларативное определение привязки к данным, которое в основном и используется в приложениях ( в документации есть пример создания привязки в коде ).

Пример привязки к данным HTML элемента,  select :

<select size="1" style="width:220px" data-bind="{this.dataSource,to=mailDocsVM.dbSet,mode=OneWay,source=VM.sendListVM} {this.selectedValue,to=selectedDocID,mode=TwoWay,source=VM.sendListVM} {this.toolTip,to=currentItem.DESCRIPTION,mode=OneWay,source=VM.sendListVM.mailDocsVM}" data-view="options:{valuePath=MailDocID,textPath=NAME}">

Атрибут, data-bind определяет выражения привязки к данным. Каждое выражение оборачивается в фигурные скобки и определяет путь к свойству приемника и путь к свойству источника данных. Для выражения можно задать режим -  mode , т.е. в каком направлении происходит перемещение данных: OneTime (один раз от источника к приемнику), OneWay (от источника к приемнику), и TwoWay (в обоих направлениях). Режим, OneWay - является режимом по умолчанию и его можно не указывать. Помимо путей к свойствам и режима привязки можно также задать, source . Это свойство привязки не обязательно, так как если его не указывать, то путь для свойства источника будет вычисляться от контекста данных. Если же его указать, то весь путь вычисляется от экземпляра Application. Как правило, пользовательские view models прикрепляются к свойству VM ( это пространство имен прикрепленное к объекту Application ). Поэтому,  source=VM.sendListVM , расшифровывается как [Объект Application].VM.sendListVM.

Пример инициализации (создания) пользовательских view model:

//функция которая передается в startUp метод объекта Application RIAPP.global.UC.fn_Main = function (app) { //инициализация пути к папке с изображениями app.global.defaults.imagesPath = '@Url.Content("~/Scripts/jriapp/img/")'; //создание пользовательских view model и прикрепление их к свойству VM на объекте Application app.VM.errorVM = app.getType('custom.ErrorViewModel').create(); app.VM.sendListVM = app.getType('custom.SendListVM').create(); //для примера инициализируем загрузку данных вызывая метод на view model app.VM.sendListVM.load(); };

В случае, если бы мы не задавали,  source , в выражении привязки, то свойство пути для источника определялось бы из того, в каком месте задано это выражение. Если, просто, на HTML странице, то весь путь вычисляется также от объекта Application. 
Однако, если выражение используется внутри шаблона ( Data Template ) или формы данных ( DataForm ), то source ( если не задан явно в выражении ) определяется от контекста данных шаблона или формы ( data context ).

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

Жизненный цикл привязки:

При создании Single Page Applications требуется, чтобы длительное использование приложения не приводило к утечке памяти.
Поэтому, Фреймворк имеет систему создания объектов, которая обеспечивает очистку памяти от ненужных объектов ( т.е. осуществляется удаление ссылок на не используемые объекты ).

Корнем жизни ( т.е. root object хранящий ссылки ) является экземпляр объекта Global, единичный ( Singleton ) экземпляр которого создается при загрузке jriapp.js автоматически. Этот объект хранит ссылки на созданные пользователем на HTML странице объекты Application ( как правило тоже один экземпляр, но можно создать несколько если создавать их в Web Parts ). Объект Application в свою очередь хранит ссылки на созданные им объекты, и так далее.
При уничтожении объекта ( вызвав его метод destroy ) ссылки на него удаляются. За это отвечает, тот объект который его создавал.

Основой Фреймворка являются привязки к данным - data bindings, и виды элемента - element view ( обертка с логикой прикрепленной к DOM элементу, на подобии jQuery плагина ). Они создаются и удаляются в больших количествах в течении жизненного цикла приложения. 
При создании объектов привязок Фреймворк в первую очередь определяет имеет ли HTML DOM элемент связанный с ним вид элемента. Например, в Фреймворке есть модуль, который определяет встроенные в Фреймворк классы element view для DOM элементов ( пользователи в своих модулях могут добавлять пользовательские объекты element view ). Некоторые из них, связаны с элементом по имени тега элемента. Например, для основных видов input элементов есть соответствующие типы element view. Однако, некоторые element view зарегестрированны по собственному имени ( т.е. не связаны с определёнными тэгами ). К примеру, TabsElView, зарегестрированна под именем tabs, a BusyElView под именем busy_indicator. На практике это означает, что для того, чтобы выбрать ( изменить ) какой тип element view создаст привязка можно использовать атрибут data-view.

В следующем примере, мы указали, что хотим, чтобы привязка создала element view зарегестрированный под именем expander.

<span data-bind="{this.command,to=expanderCommand,mode=OneWay,source=VM.headerVM}" data-view="name=expander"></span>

Также, для element view может понадобится передать опции для его создания ( element view получит их в своём конструкторе  ).

<input type="text" data-bind="{this.value,to=testProperty,mode=TwoWay,source=VM.testObject1}" data-view="options:{updateOnKeyUp=true}" /> //или <div data-bind="{this.dataSource,to=dbSet,source=VM.productVM}" data-view="name=pager,options={sliderSize:20,hideOnSinglePage=false}"> </div>

Модульная структура:

Фреймворк имеет иерархию модулей. 
Объект Global имеет верхнюю структуру модулей. В одном из его модулей определяется класс Application. 
Объект Application в свою очередь также имеет свои модули ( большинство модулей фреймворка ). 
При создании экземпляра приложения ( Application ) он инициализирует свои модули, которые в свою очередь делятся на ключевые ( которые определены в фреймворке ), и пользовательские ( определены пользователем ). 
Пользовательские модули обеспечивают расширение возможностей Фреймворка, а также служат для создания определений в них классов View Model, которые в пользовательских приложениях, MVVM, обеспечивают источники данных для привязок ( DataBindings ).

Итоги:

Я думаю, это только малая часть из всего, что имеется в фреймворке для создания приложений. Документация к нему имеет сейчас 80 страниц и описать все его возможности в коротком вводном топике не представляется возможным. Для более детального знакомства с ним понадобится написать несколько таких небольших топиков охватывающих каждый отдельную часть использования фреймворка. Но в первую очередь я предлагаю посмотреть проект на GitHub и посмотреть видео демо-приложения на youtube. 

Также, прошу учесть, что Фреймворк создавался мной для перевода приложений работающих в SilverLight, и, что это были приложения для работы с данными. Поэтому, дизайн демо приложения простой, в нем показано в основном как работать с данными. Однако, если вы хотите хорошего дизайна, то этот Фреймворк ничего не ограничивает в плане использования стилей и дизайна. Дизайн будет зависеть только от дизайнера.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=33084