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

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, и, что это были приложения для работы с данными. Поэтому, дизайн демо приложения простой, в нем показано в основном как работать с данными. Однако, если вы хотите хорошего дизайна, то этот Фреймворк ничего не ограничивает в плане использования стилей и дизайна. Дизайн будет зависеть только от дизайнера.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 14

    0
    Хотел посмотреть исходники, и скажите — вы и вправду весь код держите в одном файле js/jriapp.js? 1.5К строк можно было бы и по классам разложить, а потом собирать…
      0
      Не знаю, я раньше создавал другой меньший Фреймворк — его разбивал на отдельные скрипты — это было неудобно перелезать из файла в файл.
      Потом я стал использовать JetBrains WebStorm — в нем очень удобно работать и в таком формате, он все модули схлопывает и я
      просто выбираю тот который мне нужен и раскрываю его.
      Просто, если есть нормальный инструмент, то и кажущееся
      неудобным становится удобным.

      P.S. -к стати там где — то около 15 K строк (не 1.5 K).
        0
        Да, там много строк :) Просто, я более чем уверен, что в WebStorm не менее удобно и по файлам «прыгать». А преимущества налицо — видно сразу структуру проекта, но самое главное — изоляция модулей, а без этого покрывать тестами проект очень сложно. Ну и сборки можно разные делать.

        А так, я вижу, проект уже не маленький, и в след. раз можно статью тоже побольше написать — с примерами, особенностями… Удачи!
          0
          Спасибо.
          Это был топик для инвайта и я его писал около 12 ночи.
          Думаю потом написать что-то поподробнее про него.
            0
            Хотел еще отметить почему один файл для меня удобней.
            1) Над этим проектом работал один человек — нет конкуренции за файл.
            2) Если в программе происходит ошибка — я знаю номер строки и мне легче найти источник ошибки.
            3) Мне легче искать по тексту, чем по файлам если я ищу где используется функция.
            4) Изоляция в отдельный файл, это не тоже самое, что изоляция модулями. Весь проект состоит из модулей (где — то около 20). Помещены они в один файл или в 20 не имеет значения для изоляции.
            5) Такое расположение модулей в одном файле не мешает тестированию. Пишите тест, используйте модуль- какая разница в каком он файле?
            6) По поводу сборок — если вы заметили, то в одном файле только core modules (ключевые), пользовательские модули (как в демо) все имеют отдельный файлы. Возможно, это только мешает замене одного модуля на сторонний. Но, все равно вы его соберете потом в один файл. Модули написанные для ядра неизвестно кем могут все порушить, поскольку модули ядра работают с внутренним API, который может меняться- а сторонний модуль ничего не знает о внутренних изменениях. Да и смысл сборок здесь какой — например, оставить только Data Binding, а остальное отбросить? Только это ядро фреймворка, и там некоторые части могут зависеть одна от другой.
            То что, уже добавляется над ядром, то это можно комбинировать как угодно.

              0
              1) Ну вы же дотнэтер, я так понимаю, и не мне вас убеждать, что для бизнес лейэр: один класс — один файл. Хорошо, javascript — не c#, но как минимум один модуль — один файл.
              2) Можно для дебага и модули отдельно грузить — и вы не только номер строки видите, но и названия модуля/класса/файла, — и это уже плюс.
              3) Сугубо субьективная оценка.
              4,5) Файл-Модульная изоляция нужна для тестов, что бы тестировать внутренний апи который скрыт от внешнего доступа. Ведь вся библиотека в основном находится за замыканием, то как из тестов достать эти самые классы/модули?
              6) Модульность вам же улучшает разработку. Решили создать другую реализацию для модуля — создали новый файл, создали модуль, собрали проект с новым модулем — вот и у вас уже новая бета, а человек решит — или хочет stable пользоваться, где ещё старый модуль, или взять бетку. Или другой пример — модуль с разными реализациями. Один только для современных браузеров, другой кроссбраузерный — с кучей polyfills и другими вещами. Разрабатывая мобильное приложение, я бы взял более легкую сборку.
                0
                OK — во многом вы правы. Если бы я изначально решил публиковать проект на GitHub, то
                я бы так и сделал. Это все по историческим причинам.
                Я вообще хочу его перевести полностью на typescript, чтобы была готовность к любой версии EcmaScript в будущем.
                И внутри легче рефакторинг делать, перенес часть кода, и сразу видно, что перестает компилироваться.
                Скорее всего в этом месяце начну его переводить на другой язык.
                  0
                  О, typescript — отличная вещь, желаю вам всяческих успехов, надеюсь скоро ещё «услышимся». И главное не бросайте начатое!
        0
        В чем сакральный смысл делать видео демку HTML5 фреймворка?
          0
          В том, что это показывает его возможности по работе с данными.
          Намного проще посмотреть 4 минутный ролик, чем скачивать демо и запускать у себя на компе.
          А выкладывать демо в онлайн? Так за хостинг платить надо.
            0
            Попробуйте хостинг на appharbor.com. Там все просто, удобно и бесплатно.
              0
              В любом случае, это не совсем решает проблему.
              В демо используются база данных, что вынуждает при публикации демо сделать его в режиме только для чтения.
              (Не могу же я дать каждому возможность менять записи в базе)
              Если я сделаю хранение данных в сессии, то это приведет, к тому, что будет использоваться много памяти на сервере- и провайдер не даст ее сколько хочешь за бесплатно.
              Поэтому, пока только youtube.
              P.S.-
              К стати, опубликовал только, что продолжение этого топика.
          0
          Сегодня начал переводить этот фреймворк на typescript.
          Не знаю сколько это займет времени. Но первые результаты меня впечатляют.
          Рефакторинг становится простой задачей. Доступ ко всем классам с полным intelisense и
          ошибки теперь отлавливаются сразу. Жаль, что пока typescript в альфа версии, но опыт показывает,
          что работать с ним можно.
            0
            Сегодня опубликовал TypeScript версию на github.

            Only users with full accounts can post comments. Log in, please.