Pull to refresh

jsForms

Reading time18 min
Views757
Добрый вечер, после написания предыдущего поста прошло уже, наверное, более трех недель, с тех пор мое направление немножко изменилось, да MVC хороший паттерн, но сейчас для js он еще слишком громоздкий. Мы стремимся выбрать более прозрачные и тонкие решения, которые бы позволяли видеть как все работает, вот почему мне нравится jQuery, он вводит тот самый минимум, который позволяет абстрагироваться от типа браузера, но при этом он не убирает ощущение того, что ты пишешь именно на js. Вот почему когда стремясь реализовать паттерн Document-View на js, хотелось сделать его как можно более тонким, незаметным и как мне кажется мне удалось добиться тех же легких ощущений, что все таки это тот же js, просто он немножко расширился.
Как и в WinForms, в jsForms все строится на компонентах. Итак приступим.

Компонент

Компонент состоит из двух частей:
  • Визуальной
  • Функциональной

Визуальная часть


Визуальной частью является шаблон, который содержит в себе HTML разметку, устанавливает свойства и события.
Шаблон это js файл имеющий следующий вид:
jsForms.Templates.ComponentType='HTML';

  • ComponentType — тип компонента
  • [HTML] — HTML код заключеный в ковычки

Важно — в HTML коде используйте символы "<" и ">" для обрамления тегов, в других местах используйте "& lt;" и "& gt;" соответственно.
HTML тег должен быть очень строгим, то есть все открытые теги должны быть закрыты и соблюден порядок следования.
Шаблон должен иметь только один root html тег.

Функциональная часть


Функциональная часть это тоже js файл, в котором содержится «класс», а так же декларирование свойств и событий компонента.
Функциональная часть имеет вид:
jsForms.Components.ComponentType=<br/>
{<br/>[JavaScript]<br/>};<br/>

  • ComponentType — тип компонента
  • [JavaScript] — в общем классический JS

Использование jsForms


Разберем все на примерах.
Например Template файл для кнопки будет иметь вид:
jsForms.Templates.Button='<div class="button">'+<br>    '<div class="button-background">'+<br>        '<div class="button-common button-w"/>'+<br>        '<div class="button-common button-e"/>'+<br>    '</div>'+<br>    '<span name="text" class="button-text"/>'+<br>'</div>';<br><br>* This source code was highlighted with Source Code Highlighter.

Component


Для использования в шаблоне других компонентов используйте тег: component
При этом компонент, шаблон которого содержит другие компоненты, будем называть контейнером.
Тег component иммеет вид
<component [name='ComponenName'][id='IdValue'][class='CSSClass']>[Settings]</component><br><br>* This source code was highlighted with Source Code Highlighter.

  • ComponentType — тип компонента
  • ComponenName — название компонента, необязательный параметр, будет описан ниже
  • id — устанаваливает как и для html тега, свойство id
  • class — устанаваливает как и для html тега, свойство class
  • Setting — набор тегов, о которых будет рассказано позже

Разберем на примере диалогового окна, которое содержит какой-то текст и две кнопки.
jsForms.Templates.InsertCSSDialog='<div>'+<br>    '<div>Test text</div>'+<br>    '<component type="Button"/>'+<br>    '<component type="Button"/>'+<br>    '</component>'+<br>'</div>';<br><br>* This source code was highlighted with Source Code Highlighter.

Но возникает вопрос как установить названия кнопкам?
Для установки свойств копонента служит тег properties.

Properties


Тег properties содержит все заданные свойства компонента, перечисление идет с помощью тега key
Тег key иммеет вид
<key name="PropertyName" [value="PropertyValue"][type="object|string|int|float..."][disable][direct][const]>[PropertyValue]</key><br><br>* This source code was highlighted with Source Code Highlighter.

  • PropertyName — название свойства, обязательный параметр
  • PropertyValue — значение, должно быть указано либо в атрибуте value, либо внутри тега key, приоритетным является атрибут
  • type — тип значения, не обязательный параметр
  • disable — атрибут указывает что свойство не будет задано
  • direct — атрибут указывает на то, что свойство устанавливается в обход set-теру свойства, сейчас игнорируется
  • const — атрибут зарезервирован для будущей поддержки мультиязычности.


Таким образом мы можем задать «Text» нашим кнопкам
jsForms.Templates.InsertCSSDialog='<div>'+<br>    '<div>Test text</div>'+<br>    '<component type="Button">'+<br>        '<properties>'+<br>            '<key name="Text" value="Ok"/>'+<br>        '</properties>'+<br>    '</component>'+<br>    '<component type="Button">'+<br>        '<properties>'+<br>            '<key name="Text" value="Cancel"/>'+<br>        '</properties>'+<br>    '</component>'+<br>'</div>';<br><br>* This source code was highlighted with Source Code Highlighter.

Теперь прибиндим события к кнопкам, для этого воспользуемся тегом events

Events


Тег events содержит все заданные события компонента, перечисление идет с помощью тега key
<key name="EventName" value="EventCallback"/><br><br>* This source code was highlighted with Source Code Highlighter.

  • EventName — название события, обязательный параметр
  • EventCallback — название метода из компонента «класса» контейнера.


Внесем изменения в функциональную часть нашего компонента, добавив туда два метода: bOk_Click,bCancel_Click
jsForms.Components.InsertCSSDialog=<br>{<br>//<br>// другой код<br>//<br>    bOk_Click:function()<br>    {<br>        alert('OK');<br>    },<br>    bCancel_Click:function()<br>    {<br>        alert('Cancel');<br>    }<br>//<br>// другой код<br>//<br>};<br><br>* This source code was highlighted with Source Code Highlighter.

И изменим наш шаблон
jsForms.Templates.InsertCSSDialog='<div>'+<br>    '<div>Test text</div>'+<br>    '<component type="Button">'+<br>        '<properties>'+<br>            '<key name="Text" value="Ok"/>'+<br>        '</properties>'+<br>        '<events><key name="onClick" value="bOk_Click"/></events>'+<br>    '</component>'+<br>    '<component type="Button">'+<br>        '<properties>'+<br>            '<key name="Text" value="Cancel"/>'+<br>        '</properties>'+<br>        '<events><key name="onClick" value="bCancel_Click"/></events>'+<br>    '</component>'+<br>'</div>';<br><br>* This source code was highlighted with Source Code Highlighter.


Шаблоны обладают еще одним важным свойством для нас, они позволяют давать каждому html тегу и компоненту имена,
которые будут доступны в «классе». Для того, чтобы задать имя нужно установить атрибут name; для всех компонентов, у которых
не выставлен данный атрибут, автоматически при компиляции генерируется название вида "_CX", где X какое-то число,
зачем это нужно будет понятно позже. Как использовать имена в коде компонента будет описано ниже.
Шаблон позволяет делать вложения одних компонентов в дургие. Например, давайте создадим компонент MultiPanel.
Шаблон:
jsForms.Templates.MultiPanel='<div>'+<br>    '<div class="MPanelTop" name="top"/>'+<br>    '<div class="MPanelCenter" name="content"/>'+<br>    '<div class="MPanelBottom" name="bottom"/>'+<br>'</div>';<br><br>* This source code was highlighted with Source Code Highlighter.

«Класс»:
jsForms.Components.MultiPanel={};

Content


Теперь изменим наше диалоговое окно — поместим клавиши в нижнюю часть панели, текст в верхнюю, добавим также инпут в центральную.
jsForms.Templates.InsertCSSDialog='<div>'+<br>    '<component type="MultiPanel">'+<br>    '<content into="top">'+<br>        '<div>Please enter css class name</div>'+<br>    '</content>'+<br>    '<content>'+<br>        '<input name="iStyleClassName"/>'+<br>    '</content>'+<br>    '<content into="bottom">'+<br>        '<component type="Button">'+<br>            '<properties>'+<br>                '<key name="Text" value="Ok"/>'+<br>            '</properties>'+<br>        '</component>'+<br>        '<component type="Button">'+<br>            '<properties>'+<br>                '<key name="Text" value="Cancel"/>'+<br>            '</properties>'+<br>        '</component>'+<br>    '</content>'+<br>'</div>';<br><br>* This source code was highlighted with Source Code Highlighter.

как видно из примера с помощью тега content мы можем разместить один компонент внутри другого.
Тег content иммеет вид
<content [into="ComponentName"]>[HTML|Component]</content><br><br>* This source code was highlighted with Source Code Highlighter.

  • into — указывает в какой контрол будет добавлено содержимое, по умолчанию равно «content»
  • [HTML|Component] — любой html код + компоненты.


Компилирование



Картинка из предыдущего поста, суть осталась таже, а вот сам механизм разбора изменился.
При первом запросе какого-либо компонента, движок проверит скомпилирован ли данный компонент, если нет — то произведет компиляцию. Для этого он распарсит шаблон и сделат на основе него js функцию.
При вызове функции будет создана DOM модель, обвернутая в jQuery, а так же к этому объекту будет прекреплена базовая функциональность и функциональность«класса» компонента.Базовой называется функциональность, заданная компонентом Component.А все теги, отмеченые атрибутом name, будут находиться в свойстве Components.
То есть если откомпилировать:
jsForms.Templates.InsertCSSDialog='<div id="test">'+<br>    '<component type="MultiPanel">'+<br>    '<content into="top">'+<br>        '<div name="SimpleText">Please enter css class name</div>'+<br>        '<div>Unnamed div</div>'+<br>    '</content>'+<br>    '<content>'+<br>        '<input name="iStyleClassName"/>'+<br>    '</content>'+<br>    '<content into="bottom">'+<br>        '<component type="Button">'+<br>            '<properties>'+<br>                '<key name="Text" value="Ok"/>'+<br>            '</properties>'+<br>        '</component>'+<br>        '<component type="Button" name="bCancel">'+<br>            '<properties>'+<br>                '<key name="Text" value="Cancel"/>'+<br>            '</properties>'+<br>        '</component>'+<br>    '</content>'+<br>'</div>';<br><br>* This source code was highlighted with Source Code Highlighter.

На выходе мы получим объект содержащий:
  • Свойства
    • _Type — тип компонента
    • _Name — название тега установленное в атрибуте name или сгенерированное автоматически.
    • P или Properties — по умолчанию пустой объект, предназначенный для хранения свойств конкретного объекта
    • E или Events — по умолчанию пустой объект, предназначенный для хранения событий конкретного объекта
    • C или Components — объект содержащий все теги и компоненты, обвернутые в jQuery, отмеченные атрибутом name в нашем случае
      C = {
      • SimpleText
      • iStyleClassName
      • bCancel
      }

  • Методы jQuery объекта
    • append
    • appendTo
    • show
    • т.д
  • Методы Component
    • Init — инициализация компонента, вызывается пользователем, после добавления компонента в DOM
    • _GetProperty(name) — получить свойство
    • _SetProperty(name, value) — установить свойство
    • _SetEvent(name,obj,func) — установить событие, нужно указать объект(obj) и содержащуюся в нем функцию(func)
    • _GetEvent(name) — получить структуру {obj,func}
    • _ExecEvent(name,arguments) — выполнить событие, аргумент должен быть один, но не обязателен.
  • Методы самого компонента
    • bOk_Click() — функция пользователя
    • bCancel_Click() — функция пользователя
    • т.д.

Так же вы должны знать, что сам DOM объект компонента содержит ссылку на объект и хранит ее в свойстве _OBJ, т.е.
document.getElementById('test')._OBJ — будет равен конкретному объекту класса InsertCSSDialog.
Для более простой записи лучше использовать jsForms.GetObject(element), а для поиска вверх конкретного типа компонента
jsForms.GetObjectByType(element,foundType)

Реализация функциональной части


Теперь перейдем конкретно к написанию функциональной части.
Я рекомендую использовать подход «цепочек» который очень удачно реализован в jQuery, для этого в конце каждого метода возвращайте указатель на себя[return this; ].
Так же используйте конкретно отведенные места для данных, то есть this.P.<PropertyName> для хранения свойств.
Давайте своим методам и свойствам названия с большой буквы,- это убережет вас от перекрытия методов jQuery, но все же держите в памяти то обстоятельство, что базовым объектом является jQuery и его плагины, иногда названия методов в плагинах начинаются с большой буквы, что может потрепать вам нервы)
При «перегрузке» метода Init, вставляйте в конце вызов базового класса [return jsForms.Components.Component.Init.call(this);], он необходим для инициализации вложеных компонентов.
Помните что в свойстве this.C у вас содежаться jQuery Обвертки над тегами и другие компоненты шаблона, манипулируйте ими.
Давайте добавим валидацию в наше диалоговое окно:
jsForms.Components.InsertCSSDialog=<br>{// другой код<br>    bOk_Click:function()<br>    {<br>        var text = this.C.iStyleClassName.val();<br>        if (text==undefined || $.trim(text)=='')<br>        {<br>            alert('Please enter css class name');<br>            return;<br>        }<br>        alert('OK, class="'+text+'");<br>    },<br>    bCancel_Click:function()<br>    {<br>        alert('Cancel');<br>    },<br>// другой код<br>};<br><br>* This source code was highlighted with Source Code Highlighter.

Если мы хотим добавить к нашему компоненту свойство, событие или контейнер, нам нужно это продекларировать, для этого проще всего вызвать функцию jsForms.CreateCompileInfoByObject(objDeclaration), в качестве параметра принимающей специальным образом составленый объект.
objDeclaration={<br>{Name:<ComponentType>,<br>    Properties:{// Property list<br>         <PropertyName1>:{ Type:<PropertyType><,Values:{object}>, Access:<PropertyAccess>,Description:<String>},<br>         <PropertyName2>:{ Type:<PropertyType>,Access:<PropertyAccess>},<br>         e.t.c<br>         }<br>     Events:{<br>         <EventName1>:{Description:<String>},<br>         <EventName2>:{Description:<String>},<br>         <EventName3>:{Description:<String>},<br>         e.t.c},<br>     Contents:<br>     {<br>         <ContentName1>:{Description:<String>},<br>         <ContentName2>:{Description:<String>},<br>         <ContentName3>:{Description:<String>},<br>         e.t.c}<br> }<br>};<br><br>* This source code was highlighted with Source Code Highlighter.

  • Description — описание, необязательно.
  • ContentName — название контейнера, в шаблоне должен быть тег с атрибутом name как у контейнера.
  • PropertyName — название свойства, регистр важен.
  • PropertyType — тип входных данных свойства, если выбран Object, то входное значение будет выполнено через eval(...) и передано в свойство
  • PropertyAccess — доступ, если доступ установлен в 'Direct', то запись будет произведена через метод _SetProperty в обход set-ра свойства, иначе будет вызван метод Set<PropertyName> и в него передано значение, метод сеттер нужно определить.

PropertyType in ('Boolean','String','Object','Int','Float','Values',<undefined>) <undefined> — is default
PropertyAccess in ('Direct','Set','Get','GetSet') 'GetSet' — is default
Давайте на примере клавиши рассмотрим как декларировать свойства и события.
jsForms.Components.Button = {<br>    Init: function()<br>    {<br>        // В данном случае нет надобности <br>        //вызывать базовый метод Init, так как нет ни одного контейнера и <br>        //вложенного компонента<br>        return this.SetEnabled(true);<br>    },<br>    eventClick : function(e)<br>    {<br>        if (e.data) {//e.data содержит ссылку на объект<br>            if (e.data.P.Enabled) {<br>                e.data._ExecEvent('onClick');<br>            }<br>        }<br>    },<br>    SetEnabled: function(flag)<br>    {<br>        if (this.P.Enabled != flag) {<br>            this.P.Enabled = flag==true;<br>            this.attr('state', flag ? 'enabled' : 'disabled');<br>            if (flag) {<br>                    this.unbind('click').bind('click',this,this.eventClick );<br>            }<br>            else {<br>                this.unbind('click');<br>            }<br>        }<br>        return this;<br>    },<br>    GetEnabled:function()<br>    {<br>        return this.P.Enabled;<br>    },<br>    GetText: function()<br>    {<br>        return this.P.text;<br>    },<br>    SetText: function(newText)<br>    {<br>        if (this.P.text != newText) {<br>            this.P.text = newText;<br>            this.C.text[0].innerHTML = newText;<br>        }<br>        return this;<br>    }<br>};<br>// -----------------------------------------------<br>// Compilation INFO<br>// -----------------------------------------------<br>jsForms.CreateCompileInfoByObject(<br>{<br>    Name: 'Button',<br>    Properties: {<br>        Enabled: {<br>            Type: 'Boolean',<br>            Access: 'GETSET'<br>        },<br>        Text: {}<br>    },<br>    Events: {<br>        onClick: {}<br>    }<br>});<br><br>* This source code was highlighted with Source Code Highlighter.


Вставка компонентов в страницу


Давайте теперь перейдем к рассмотрению самого процесса использования компонентов на странице.
Для создания компонента необходимо вызвать функцию jsForms.CreateComponent(compType)
var button = jsForms.CreateComponent('Button');<br>//Далее "лучше" скрыть компонент, чтобы не видеть как он перестраивается и добавить его в DOM<br/><br>button.hide().appendTo(...);<br>//После добавления в DOM нужно инициализировать его, методом Init() и показать<br/><br>button.Init().show();<br>//А так как мы используем цепочки, то можно все записать одной строкой.<br/><br>var button = jsForms.CreateComponent('Button').hide().appendTo(...).Init().show();<br><br>* This source code was highlighted with Source Code Highlighter.


Производительность


Для замера производительности создавалось 2000 простых объектов, каждый тест повторялся 10 раз.
Тестовое окружение:
WinXP Sp2, FireFox 3.0.4 + FireBug, CPU Intel Pentium Dual E2160@1.80GHz, 2Gb Mem

Как видно из графиков результаты плавают, предположительно это связано со сборщиком мусора, который изредка подтормаживает js.
Подробная статистика:
  • jQuery(Template)
    Rt:10 R:2000, T:1440 ms [min:1315, max:2333 ms] [min:0.6575 avr:0.7198, max:1.1665 ms] delta=1018
  • Manual DOM building
    Rt:10 R:2000, T:340 ms [min:330, max:375 ms] [min:0.1650 avr:0.1701, max:0.1875 ms] delta=45
  • jsForms
    Rt:10 R:2000, T:442 ms [min:434, max:457 ms] [min:0.2170 avr:0.2210, max:0.2285 ms] delta=23

Протестировать можно тут.

П.С.


На данный момет реализованы следующие компоненты:
  • Button — «толстая» клавиша.
  • ButtonDiv — див`ная клавиша, очень легкая.
  • ListBox
  • TreeView
  • TabControl
  • ProgressBar
  • мелочь

Живым примером работы данного подхода является сайт, редактор компонентов
Весь сайт и сама технология доступны под GPL3, скачать можно с оф.сайта.
Редактор находиться в разработке, так что буду рад предложениям и критике.
Технология испытана на: FF2+, IE7, Safari, Chrome
У кого есть под руками IE6 отпишитесь, заранее спасибо.
Так же спасибо хабру, так как многие идеи были подчерпнуты иммено с его страниц.
Спасибо.

Tags:
Hubs:
Total votes 55: ↑48 and ↓7+41
Comments30

Articles