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

в 10:51, , рубрики: ASP, html5, javascript, javascript framework, Веб-разработка, метки: ,

В предыдущем топике я начал обзор нового HTML5 Фреймворка, который, собственно, я создавал для своих нужд. Я хотел перейти от SilverLight приложений к новому веянью — HTML5 Single Page Applications. Однако, на рынке, однозначно, нет готовых HTML5 Фреймворков для создания ориентированных на работу с данными приложений, так как для создания таких приложений есть много дополнительных требований, что не учитывалось, при их создании, в существующих Фреймворках. Да, и помимо этого, есть еще один плюс от собственного Фреймворка — полный контроль над его функциями. Если что-то там не хватает, можно добавить, ну, и изредка убавить.

Так вот, для начала, я хотел бы начать с начала — то есть с базового класса объекта, RIAPP.BaseObject.

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

Чем он так примечателен?
Во первых он несет в себе функции 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.customerVM.load();
}; 

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

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

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

При создании Single Page Applications требуется, чтобы длительное использование приложения не приводило к утечке памяти.
Поэтому, Фреймворк имеет систему создания объектов, которая обеспечивает очистку памяти от ненужных объектов (т.е. осуществляется удаление ссылок на не используемые объекты).
Основой Фреймворка являются привязки к данным — 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>

При создании объектов element view Фреймворк сохраняет ссылки на них в объекте Application, а при их уничтожении ссылки удаляются и на эти объекты уже никто не ссылается, поэтому браузер их уничтожит как мусор.
Если вы посмотрите в браузере HTML код (например, в firefox firebug), то увидите, что для элементов участвующих в привязках к данным есть атрибуты, data-elvwkey1 (состоит из data-elvwkey и порядкового номера объекта Application).

Например:

<div data-bind="{this.html,to=operInfo,source=VM.sendListVM.itemsVM}" style="display: inline-block;" data-elvwkey1="s_1"></div>

Этот атрибут хранит id хранящейся ссылки на объект element view в объекте Application. Он присваивается динамически при создании привязки к данным к свойствам этого элемент, а также, удаляется автоматически при уничтожении объекта element view.
Шаблоны и формы данных осуществляют создание привязок сами, поэтому и сами их уничтожают (вызывают их метод destroy).
Обычно, тот объект который создавал объект, его же и уничтожает при своем удалении.
Например, DataGrid, создает объекты row и cell, которые в свою очередь создают ресурсы. При уничтожении объект row уничтожает имеющиеся в нем объекты cell, а cell в свою очередь созданные им объекты. При этом удаляются все ссылки на объекты и они удаляются браузером как мусор.

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

Итоги:

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

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

Дополнительно:
ссылка на GitHub репозиторий фреймворка и демо.

Автор: MaximTsap

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js