Z (Zet) Framework - Структурно-событийный фреймворк для построения одностраничных приложений
-- Программируй UI без использования JS!
-- Это как? o_O
Примеры можно постмотреть тут: http://s0rr0w.github.io/z/examples
Версия: 0.8.4
Как и любой реактор, данный фреймворк оперирует следующими сущностями: обработчик события (handler), диспатчер (dispatcher) и событие (event). Диспатчер запускает именованое событие в определенной структуре, которое потом обрабатывается какими-то обработчикам. На этом построена вся логика фреймворка, и именно на этом принципе строится весь UI. Без MVC, без модулей, без роутеров и без лишнего JS кода.
Вам потребуется подключить всего два файла:
<script type="text/javascript" src="z.js"></script>
<script type="text/javascript" src="z_handlers.js"></script>
Где:
z.js
-- код самого фреймворкаz_handlers.js
-- код обработчиков, которые используются в вашем проекте (аналог расширений или плагинов в других фреймворках)
События Z фреймворка очень похожи на DOM-события, но их распространение в DOM-дереве принципиально отличается. Z-событие распространяется от какого-то контейнера и, при определенных условиях, на определенные элементы внутри. Эта зона видимости указывается непосредственно при запуске события и не зависит от наличия или отсутствия обработчика на каком-либо элементе.
События бывают двух типов: локальные и глобальные. Локальные события имеют локализацию зоны распространения события определенным контейнером. Глобальные события работают в рамках всего документа, и срабатывают только на элементах, которым добавлены глобальные обработчики. Основное предназначение глобальных событий - рендеринг данных, которые присылаются с сервера, или одновременная модификация распределенных по всему DOM-дереву элементов (аналог data-binding)
Фреймворк не содержит методов остановки распространения событий (stopPropagation), так как на практике эта функциоальность не нужна. Данная "проблема" спокойно решается путем изменения зоны распространения события или дополнительной функцональностью обработчиков.
{
e: "eventName",
t: targetNode,
f: containerNode,
p: "propagation",
data: JSON || Object,
[b: isBroadcastedEvent,]
[use: "dispatcherID",]
[DOMEvnt: Event object]
}
Где:
e
-- имя событияt
-- ссылка на элемент, который был инициатором событияf
-- ссылка на элемент, с которого начнется распространяться событиеp
-- директивы распространения событияdata
-- данные, которые передаются вместе с событиемb
-- признак того, что событие было переадресовано дочерним элементамuse
-- ссылка на реальный диспатчер событияDOMEvnt
-- ссылка на DOM-событие при запуске из конструкции<handler>
Поля b
, use
и DOMEvnt
могут отсутствовать в структуре события
Процесс запуска событий несколько отличается от того, что существует в браузере или других фреймворках. Существует два раздельных процесса: инициализация (описание) диспатчеров и непосредственно запуск события. Запуск z-события делается или по событиям браузера, или в процессе шаблонизации с помощью директивы <exec>
.
Диспатчеры описывают правила запуска события и бывают двух типов: абсолютные и относительные. Абсолютные диспатчеры непосредственно формируют объект zEvent
, в то время как относительные используют ссылку на другой диспатчер
Для инициализации диспатчеров используются следующие директивы:
Запуск событий через HTML директивы осуществляется с помощью:
Также можно использовать JS API для запуска событий:
Интерактивный пример наглядно продемонстрирует логику работы диспатчера событий.
Данная директива носит описательный характер и не запускает событие. Реальным инициатором запуска могут быть директивы <exec/>
или <handler/>
, которые будут описаны ниже.
<dispatch e="eventName" [f="cotainerNode"] [p="propagation"] [id="dispatchID"] [name="dispatchName"]>[{ JSON Object }]</dispatch>
Где:
e
-- имя событияf
-- контейнер, с которого начинать распространение событияp
-- директивы распространения событияid
-- идентификатор диспатчераname
-- имя диспатчера{ JSON Object }
-- объект с данными, который будет путешествовать вместе с событием
Атрибуты f
, p
, id
, name
и содержимое ноды { JSON Object }
являются не обязательными
Имя события должно удовлетворять правилам именования переменных JS, значения атрибутов и названию тегов.
Примеры названий
click, myCoolEvent2, Do_It_Again
Параметр может принимать следующие значения:
.
-- родительский элемент тега<dispatch>
#id
-- идентификатор ноды.className
-- имя класса первого из вышестоящих родительских элементовtagName
-- имя первого из вышестоящих элементов
По умолчанию, если параметр не задан, в качестве контейнера будет взят родительский элемент тега <dispatch>
Для глобальных событий данный параметр игнорируется
Например
<body>
<header class="superClass">
<div class="superClass">
<dispatch e="e1" f="#pageFooter"></dispatch>
<dispatch e="e2" f=".superClass"></dispatch>
<dispatch e="e3" f="header"></dispatch>
<dispatch e="e4"></dispatch>
</div>
</header>
<footer id="pageFooter">
</footer>
</body>
Событие e1
начнет свое распространение с footer#pageFooter
, события e2
и e4
с div.superClass
(первый родительский элемент, который удовлетворяет условиям), а событие e3
с тега header
Доступны следующие варианты директив:
global
-- событие является глобальным, распространяется только среди елементов с глобальными обработчикамиparent
-- распространяется исключительно на контейнере, описанном в атрибутеf
childNodes
-- распространяется на прямых потомках контейнераnodeName
-- на дочерних элементах с данным именем тега.className
-- на дочерних элементах с данным классом
По умолчанию событие распространяется на все элементы внутри контейнера, в том числе и на те, которые были только что созданы при помощи системы шаблонизации. Это может быть причиной деградации скорости работы.
Директивы global
и parent
являются исключительными и их использование допускается только в единичном виде. Все остальные директивы можно записывать через запятую.
Зона распространения события для неглобальных событий всегда идет в следующем порядке: сначала событие отправляется родительскому контейнеру, а потом уже тем элементам, которые указаны в списке директив. Элементы перебираются вне зависимости от того, есть там обработчик, или нет.
Важно! При том, что директивы перебираются в прямом порядке (в том, в котором они указаны в списке), сами элементы перебираются в обратном!
Например
<body>
<dispatch e="e1" p="global"></dispatch>
<dispatch e="e2" p="parent"></dispatch>
<dispatch e="e3" p="childNodes,span"></dispatch>
<dispatch e="e4" p="li,span,.newItem"></dispatch>
<ul>
<li>Item <span>1</span></li>
<li>Item <span>2</span></li>
<li class="newItem">Item <span>3</span></li>
</ul>
</body>
Событие e1
глобальное, а так как мы не зарегистрировали ни одного соответствующего обработчика, то событие не будет распространяться ни на один из тегов.
В зону видимости события e2
попадет только тег body
.
У события e3
зона распространения будет следующая:
body
как родительский контейнер диспатчераul
как дочерний элемент (childNodes
) контейнера- 3 тега
span
У события e4
зона распространения события будет следующая:
body
как родительский контейнер диспатчера- 3 тега
li
- 3 тега
span
li.newItem
повторно попадает в зону видимости
Директивам <dispatch>
могут быть присвоены абсолютное имя id
или относительное name
, значения которых должны удовлетворять стандартным требованиям к именованию тегов.
Содержимое тега <dispatch>
сначала интерпретируется с помощью встроенной системы шаблонизации, а уже потом результат в виде текстового содержимого парсится как JSON-объект.
Допускается пустое значение тега <dispatch>
, в данном случае у события поле data
будет равно пустому JSON-объекту.
Пример использования
<dispatch e="e1">{ "x": 1 }</dispatch>
<dispatch e="e2">{ "x": <value>someX</value> }</dispatch>
Событие e1
будет содержать поле data
, равное JSON-объекту { "x": 1 }
, а событие e2
- результат замещения тега <value>
на значение переменной someX
, а потом уже получившемуся JSON-объекту.
Данная инструкция позволяет использовать удаленный диспатчер по id
или по name
.
<dispatch use="dispatcherID">[{ JSON Object }]</dispatch>
Где:
use
-- идентификатор или путь к удаленной ноде<dispatch>
Данный параметр может принимать или id
удаленного диспатчера, или же путь обращения по имени ноды name
.
Формат обращения по имени ноды следующий:
use="containerSelector/[/]dispatcherName"
Где:
containerSelector
-- селектор, аналогичный параметруf
директивы<dispatch>
dispatcherName
-- имяname
диспатчера
Для поиска прямого потомка с необходимым именем используется один /
, на любом уровне вложенности - //
(по аналогии с XPath). Если контейнер содержит больше одного диспатчера с одинаковым именем, будет вызван первый попавшийся.
Пример использования
<div class="container">
<!-- D1 -->
<dispatch e="e1" id="farDispatcher1" name="localName"></dispatch>
<span>
<!-- D2 -->
<dispatch e="e1" name="localName"></dispatch>
<exec>
<!-- 1 --><dispatch use="farDispatcher1"></dispatch>
<!-- 2 --><dispatch use="./localName"></dispatch>
<!-- 3 --><dispatch use="div/localName"></dispatch>
<!-- 4 --><dispatch use=".container/localName"></dispatch>
</exec>
</span>
</div>
- Будет вызван
D1
поid
#farDispatcher1
- Будет вызван
D2
, так как контейнер для вызывающего диспатчера являетсяspan
, в нем уже ищется диспатчер сname="localName"
- Будет вызван
D1
, так как указан контейнерdiv
, но первый попавшийся диспатчер с именемlocalName
скорее всего будет именно он - Будет вызван
D1
, аналогично 3
Если JSON-объект определен, то событие будет запущено именно с этим объектом. Фактически, мы переопределяем данные удаленного диспатчера. Если JSON-объект не определен, то будут использованы данные вызываемого диспатчера.
Например
<dispatch e="e1" id="farDispatcher1">{ "x": 1 }</dispatch>
<dispatch e="e2" id="farDispatcher2"></dispatch>
<exec>
<!-- 1 --><dispatch use="farDispatcher1">{ "x": 2, "y": 3 }</dispatch>
<!-- 2 --><dispatch use="farDispatcher1"></dispatch>
<!-- 3 --><dispatch use="farDispatcher2">{ "x": 2, "y": 3 }</dispatch>
<!-- 4 --><dispatch use="farDispatcher2"></dispatch>
</exec>
- Будет вызван
dispatch#farDispatcher1
, который запустит событиеe1
, которое будет содержать данные{ "x": 2, "y": 3 }
- Будет вызван тот же диспатчер, но данные будут те, которые определены первоначально:
{ "x": 1 }
- Результат будет аналогичный пункту 1, только с другим именем события
- Данные события
e2
будут пустыми ({}
)
Данная директива навешивает DOM-обработчик определенного DOM-события на родительскую ноду.
Важно! В случае успешного срабатывания обработчика, у DOM-события вызываются два метода: stopPropagation()
и preventDefault()
<handler on="DOMEventName" [target="target"] [keys="keyList"] [confirm="confirmMessage"] [freeze="seconds"] [allowPropagation] [allowDefault] [excludingSelection]>
<dispatch />
...
</handler>
Где:
on
-- имя DOM-событияtarget
-- контейнер, на который будет навешен обработчикkeys
-- список допустимых клавишconfirm
-- текст для подтверждения действияfreeze
-- время блокировки повторного срабатыванияallowPropagation
-- разрешение продолжать распространение браузерного событияallowDefault
-- разрешение выполнять действия по-умолчанию для событий браузераexcludingSelection
-- запрет на исполнение при наличии выделения в документе
Атрибуты target
, keys
, confirm
, freeze
, allowPropagation
, allowDefault
и excludingSelection
являются не обязательными
Имя DOM-события должно удовлетворять правилам именования событий функции addEventListener.
Примеры названий
click, keypress, scroll
По умолчанию обработчик DOM-события навешивается на ноду, которая содержит <handler>
, однако допускается вариант навешивания DOM-события на window
, для этого нужно указать атрибут target="window"
Примеры использования
<a href="#">
<handler on="click">
<dispatch e="clickOnLink" f="BODY" p=".box"></dispatch>
<dispatch use="openModal"></dispatch>
</handler>
<handler on="scroll" target="window">
<dispatch e="globalScroll" p="global"></dispatch>
</handler>
</a>
При всем при том, что оба обработчика находятся внутри тега <a>
, первый <handler>
добавит обработчик DOM-события click
своему родителю <a>
, а второй обработчик DOM-события scroll
будет добавлен window
.
Если пользователь нажмет на данную ссылку, то будут запущены два события: clickOnLink
, которое начнет свое распространение с <body>
для всех элементов с классом .box
, и удаленное абсолютное событие с id="openModal"
Если пользователь использует прокрутку, то будет запущено глобальное событие globalScroll
.
Если указан этот параметр, то события будут запускаться только в случае совпадения с указанным списком клавиш. Список формируется из определенных констант, разделенных запятой.
Допускаются следующие значения:
DEL
(46) -- кнопкаDel
BS
(8) -- кнопкаBackspace
ENTER
(13) -- кнопкаEnter
ESC
(27) -- кнопкаEsc
TAB
(9) -- кнопкаTab
INS
(45) -- кнопкаIns
LA
(37) -- кнопка управления курсоромLeft Arrow
UA
(38) -- кнопка управления курсоромUp Arrow
RA
(39) -- кнопка управления курсоромRight Arrow
DA
(40) -- кнопка управления курсоромDown Arrow
Пример использования
<a href="#">
<handler on="keypress" keys="BS,DEL">
<dispatch e="removeItem" f=".item" p="parent"></dispatch>
</handler>
</a>
Если на данной ссылке нажать кнопку Del
или Backspace
, то будет запущено событие removeItem
. В противном случае запуск события не производится.
Если необходимо подтверждение действия со стороны пользователя, то использование данной директивы вызовет стандартный JS-метод confirm()
перед запуском события. В качестве параметра JS-методу будет передано содержимое атрибута confirm
. Если пользователь подтвердит выбор, то события будут запущены. В противном случае запуск не производится.
Пример использования
<a href="#">
<handler on="keypress" keys="BS,DEL" confirm="Вы уверены, что хотите удалить данный элемент?">
<dispatch e="removeItem" f=".item" p="parent"></dispatch>
</handler>
</a>
В данном примере запуск события удаления будет производиться и с ограничением нажимаемых клавиш, и с обязательным подтверждением со стороны пользователя.
Данная директива позволяет блокировать на определенное количество секунд повторное срабатывание запуска обработчика события.
Пример использования
<button>
<handler on="click" freeze="5">
<dispatch e="sendData" p="global"></dispatch>
</handler>
</button>
После нажатия на кнопку будет запущено глобальное событие sendData
, и в течение 5 секунд будет заблокирована возможность повторного запуска данного события.
Данные две директивы не допускают срабатывания Event.stopPropagation()
и Event.preventDefault()
, которые по-умолчанию блокируются фреймворком
Пример использования
<button>
<handler on="click" allowPropagation allowDefault>
<dispatch e="sendData" p="global"></dispatch>
</handler>
</button>
После нажатия на кнопку будет запущено глобальное событие sendData
, при этом событие браузера click
не будет заблокировано и может сработать на других элементах
При наличии директивы excludingSelection
и любого выделения контента на странице, запуск событий не будет произведен.
Пример использования
<button>
<handler on="click" excludingSelection>
<dispatch e="editMe" p="global"></dispatch>
</handler>
</button>
После нажатия на кнопку будет запущено глобальное событие editMe
только при отсутствии выделенного текста или другого выделенного контента на странице
Данная директива запускает диспатчеры, которые содержатся в ней.
Пример использования
<exec>
<dispatch use="extEvent"></dispatch>
</exec>
Данный метод позволяет запускать события zEvent
z.dispatch(zEvent[,zEvent,...]);
Пример использования
z.dispatch(
{
e: "e1",
f: document.getElementById("container"),
p: "childNodes",
data: { "x": 1 }
},
{
e: "e2",
p: "global"
}
)
Данный метод является аналогом удаленного вызова директивы <dispatch>
, за исключением того, что данные не замещаются, а добавляются к существующим. Функция возвращает ссылку на объект zEvent
z.dispatchById( "dispatcherID"[, mixinData ] );
Где
dispatcherID
-- идентификатор ноды<dispatch>
mixinData
-- объект данных, который будет добавлен к существующим данным
Во фреймворке в качестве обработчиков выступают примитивные JavaScript-функции. Следуя идеологии *nix-way, они должны выполнять одну функцию, но делать это наилучшим образом.
- Установка обработчиков
<e>
»»» - Регистрация обработчиков
z.addHandler
»»» - Предустановленные обработчики »»»
Данная директива устанавливает обработчик родительской ноде
<e [global] on="eventName" do="handlerAlias">[params]</e>
Где:
global
-- признак глобального обработчикаon="eventName"
-- имя события, по которому будет срабатывать обработчикdo="handlerAlias"
-- имя функции-обработчика, которая будет выполненаparams
-- дополнительные параметры, которые будут переданы функции-обработчику, в форматеparam1,param2,...,paramN
Например, мы хотим, чтобы тег <body>
реагировал на события setLayout
, добавляя класс modal
<body>
<e on="setLayout" do="addClass">modal</e>
</body>
Фреймворк в качестве обработчиков воспринимает только те функции, которые были предварительно зарегистрированы через интерфейс z.addHandler
z.addHandler("handlerAlias", function(eventObject,passedParams){});
Где:
handlerAlias
-- имя функции-обработчикаfunction
-- ссылка на функциюeventObj
-- объект событияzEvent
passedParams
-- массив дополнительных параметров, которые были указаны для установщика обработчика<e>
Внимание! Ключевое слово this
в функции-обработчике будет ссылаться на ноду, которой был установлен данный обработчик
Пример использования
z.addHandler("addClass", function(e, data){
if (data[0]) this.classList.add(data[0]);
});
Если обработчики были следующими
<e on="setLayout" do="addClass">modal</e>
<e on="setLayout" do="addClass">myClass</e>
то значение data[0]
для вызова первого обработчика addClass
будет равно modal
, а для второго уже myClass
По умолчанию в системе установлены следующие обработчики:
- template
- templateIfMatch
- templateIfAttrMatch
- templateIfExists
- templateScopeIfExists
- templateOnce
- broadcastEvent
- dispatchEvent
Подробнее о шаблонизации можно прочитать в соответствующем разделе
<e on="eventName" do="template">templateID[,templatingMode]</e>
Где
templateID
-- идентификатор шаблона<template>
templatingMode
-- режим шаблонизацииadd
,before
,replace
илиonce
Обработчик выполняется без дополнительных условий срабатывания
Например
<div>
<e on="test" do="template">myTemplate</e>
<exec>
<dispatch e="test"></dispatch>
</exec>
</div>
В режиме add
контент будет добавлен к уже существующему контенту в контейнере, который содердит данный обработчик. При значении before
контент будет вставлен перед существующим контентом. В режиме replace
контент контейнера будет заменен на новый. Режим once
схож с replace
, только шаблонизация будет произведена всего один раз.
<e on="eventName" do="templateIfMatch">propertyName,constant,templateID[,templatingMode]</e>
Где
propertyName
-- имя свойства объектаzEvent.data
constant
-- требуемое значение свойства для срабатывания обработчикаtemplateID
-- идентификатор шаблона<template>
templatingMode
-- режим шаблонизацииadd
,replace
илиonce
Обработчик срабатывает, если переданные с событием данные (zEvent.data
) содержат свойство propertyName
и оно равняется constant
.
Например
<div>
<div>
<e on="test" do="templateIfMatch">tab,one,myTemplate</e>
<!-- Этот обработчик будет вызван, так как свойство tab в данных события равно "one" -->
</div>
<div>
<e on="test" do="templateIfMatch">tab,two,myTemplate</e>
<!-- Этот обработчик не будет выполнен -->
</div>
<exec>
<dispatch e="test">{ "tab": "one" }</dispatch>
</exec>
</div>
<e on="eventName" do="templateIfAttrMatch">attributeName,templateID[,templatingMode]</e>
Где
attributeName
-- имя атрибута и свойства объектаzEvent.data
templateID
-- идентификатор шаблона<template>
templatingMode
-- режим шаблонизацииadd
,replace
илиonce
Обработчик срабатывает, если переданные с событием данные (zEvent.data
) содержат свойство attributeName
и его значение равняется значению одноименного атрибута ноды. Допускается использование JS-пути к значению свойства в данных события, однако в качестве имени атрибута будет взято последнее имя в пути.
Например
<div>
<div tab="one">
<e on="test" do="templateIfAttrMatch">tabs.tab,myTemplate</e>
<!--
Этот обработчик будет вызван, так как свойство tabs.tab в данных события равно значению
одноименного атрибута "one"
-->
</div>
<div tab="two">
<e on="test" do="templateIfAttrMatch">tabs.tab,myTemplate</e>
<!-- Этот обработчик не будет выполнен -->
</div>
<exec>
<dispatch e="test">{ "tabs": { "tab": "one" } }</dispatch>
</exec>
</div>
<e on="eventName" do="templateIfExists">propertyName,templateID[,templatingMode]</e>
Где
propertyName
-- имя свойства объектаzEvent.data
templateID
-- идентификатор шаблона<template>
templatingMode
-- режим шаблонизацииadd
,replace
илиonce
Обработчик срабатывает, если переданные с событием данные (zEvent.data
) содержат свойство propertyName
и его значение строго не равно undefined
Например
<div>
<div>
<e on="test" do="templateIfExists">one,myTemplate</e>
<!-- Этот обработчик будет вызван, так как свойство one присутствует в данных события -->
</div>
<div>
<e on="test" do="templateIfExists">two,myTemplate</e>
<!-- Этот обработчик не будет выполнен, данные события не содержат свойства two -->
</div>
<div>
<e on="test" do="templateIfExists">three,myTemplate</e>
<!-- Этот обработчик также будет вызван, так как свойство three определено -->
</div>
<exec>
<dispatch e="test">{ "one": true, "three": { "x": 10, "y": 20 } }</dispatch>
</exec>
</div>
<e on="eventName" do="templateScopeIfExists">propertyPath,templateID[,templatingMode]</e>
Где
propertyPath
-- путь к имени свойства объектаzEvent.data
templateID
-- идентификатор шаблона<template>
templatingMode
-- режим шаблонизацииadd
,replace
илиonce
Обработчик срабатывает, если переданные с событием данные (zEvent.data
) содержат свойство по пути propertyPath
и его значение строго не равно undefined
. В шаблон будет передана копия события, у которого данные будут равны копии свойства zEvent.data[propertyName]
, где propertyName
- последний элемент в цепочке переменных, разделенных точкой .
Например
<div>
<div>
<e on="test" do="templateScopeIfExists">data.one,myTemplate</e>
<!-- Этому шаблону будут переданы данные { "one": { "x": 0, "y": 50 } } -->
</div>
<div>
<e on="test" do="templateScopeIfExists">data.two,myTemplate</e>
<!-- Этому { "two": { "x": 10, "y": 20 } } -->
</div>
<exec>
<dispatch e="test">{ "data": { "one": { "x": 0, "y": 50 }, "two": { "x": 10, "y": 20 } } }</dispatch>
</exec>
</div>
<e on="eventName" do="templateOnce">templateID</e>
Где
templateID
-- идентификатор шаблона<template>
Обработчик срабатывает один раз
Например
<div>
<e on="test" do="templateOnce">myTemplate</e>
<exec>
<dispatch e="test">{ "color": "red" }</dispatch>
<!-- Это событие будет обработано -->
<dispatch e="test">{ "color": "blue" }</dispatch>
<!-- Это уже нет -->
</exec>
</div>
<e on="eventName" do="broadcastEvent">[p1,p2,...,pn]</e>
Где
p1,p2,...pn
-- директивы распространения события
Данный обработчик создает и запускает копию события с указанными в параметрах обработчика директивами распространения, при этом данные события (zEvent.data
) передаются от инициирующего события по ссылке
Например
<div>
<div>
<e on="test" do="broadcastEvent">p,span</e>
<!-- Этот обработчик будет вызван -->
<p>
<e on="test" do="something"></e>
<!-- Этот обработчик будет вызван благодаря broadcastEvent -->
Этот параграф содержит
<span>
<e on="test" do="somethingElse"></e>
<!-- И этот тоже -->
span
</span>
</p>
</div>
<exec>
<dispatch e="test" p="childNodes"></dispatch>
<!-- Запускаем событие test только для дочерних элементов -->
</exec>
</div>
<e on="eventName" do="dispatchEvent">dispatcherID[,executionMode][,mixinProperty]</e>
Где
dispatcherID
-- идентификатор ноды<dispatch>
executionMode
-- дополнительные режимы:checkEmpty
,mixin
илиdefault
minixProperty
-- имя свойства, которому будут подмешаны данные
Данный обработчик производит удаленный запуск диспатчера по его dispatcherID
. По умолчанию используется режим default
.
В случае, если указан режим checkEmpty
, запуск диспатчера выполняется только если нода обработчика не содержит значимого текстового контента (проверка выполняется по текстовому содержимому, при этом пробелы считаются "пустотой")
Если указан режим mixin
, то удаленный диспатчер будет запускаться с переопределенными данными. По умолчанию будут взяты данные zEvent.data
в качестве примеси. Но если указан mixinProperty
, то данные события будут обернуты в дополнительный объект { mixinProperty: zEvent.data }
Например
<div>
<div>
<e on="test" do="dispatchEvent">coolDispatcher,mixin</e>
<!-- 2. Этот обработчик запустит диспатчер с id="coolDispatcher", и передаст ему данные { "x": 1 } -->
<e on="doIt" do="something"></e>
<!-- 4. Выполнится обработчик something -->
</div>
<exec>
<dispatch e="test" p="childNodes">{ "x": 1 }</dispatch>
<!-- 1. Запускаем событие test только для дочерних элементов -->
</exec>
<dispatch id="coolDispatcher" e="doIt">{ "x": 2 }</dispatch>
<!-- 3. Запускаем событие doIt, данные события будут от 1-го диспатчера, а не текущего -->
</div>
Процесс шаблонизации происходит двумя способами: путем вызова обработчиков template, templateIfMatch, templateIfAttrMatch, templateIfExists, templateScopeIfExists, templateOnce, или прямом вызове JS API метода z.template()
Шаблоны добавляются в тело документа в специальный контейнер
<template id="zTemplates">
<template />
...
</template>
Шаблонизатор использует следующие XML конструкции
- if..then..else
- value
- inner_html
- dom
- val_by
- include
- tag
- attr
- class
- foreach
- capture
- flush
- datetime
- z:table, z:caption, z:colgroup, z:col, z:thead, z:tfoot, z:tbody, z:tr, z:td, z:th, z:select, z:option, z:textarea
<if expr="expression">
<then></then>
<else></else>
</if>
Если выполняется условие expression
, шаблонизируется код в <then>
, иначе <else>
. В случаях, когда <else>
отсутствует, <then>
можно не указывать, тогда шаблонизатор будет использовать код в <if>
.
Условие expression
описывается как обычное JS-условие оператора if
, но со всеми органичениями with
. Дополнительно для условия передаются две ссылки на данные: _data_
и _captures_
.
Данная конструкция не создает локальную зону видимости переменных
Примеры использования
<if expr="mode=='new'">
<then>
<h1>Новый пользователь</h1>
</then>
<else>
<h1>Редактирование существующего пользователя</h1>
<if expr="user.access=='disabled'">
<p>Пользователь заблокирован</p>
</if>
</else>
</if>
При шаблонизации данных
{
"mode": "edit",
"user":
{
"login": "abc",
"access": "disabled"
}
}
Получим результат
<h1>Редактирование существующего пользователя</h1>
<p>Пользователь заблокирован</p>
<value [default="defaultValue"]>path</value>
Используется как для вывода данных, которые переданы с событием, так и для формирования текстовых строк, которые учитывают особенности изменений в различных языковых группах для разных исчисляемых значений (одно яблоко, два яблока, сто яблок). Подробнее в разделе Числовые формы
Для простого вывода значения используется стандартная JS-нотация.
По пути path
ищется ключ в локальной зоне видимости переменных, в которой расположен тег value
. По умолчанию это zEvent.data
. Если параметр не найден, или же он равен null
или пустой строке, то, если указан атрибут default
, будет выведено это значение по умолчанию, иначе не будет выведено ничего.
Значение path
не допускает наличия других директив шаблонизатора.
Пример использования
<h1><value default="Новый пользователь">user.name</value></h1>
При шаблонизации данных
{
"user":
{
"name": "Котигорошко",
"login": "cgman"
}
}
Получим результат
<h1>Котигорошко</h1>
При шаблонизации следующих данных
{
"user": {}
}
Результат будет следующим
<h1>Новый пользователь</h1>
<inner_html [default="defaultValue"]>path</inner_html>
Данная директива аналогична value
, за исключением того, что данные вставляются небезопасным способом через innerHTML
. Не рекомендуется использовать с небезопасными данными.
Пример использования
<article><inner_html>email.body</inner_html></article>
При шаблонизации данных
{
"email":
{
"body": "<p>Привет!</p><p>Как дела?</p>"
}
}
Получим результат
<article><p>Привет!</p><p>Как дела?</p></article>
<dom>path</dom>
Директива dom
позволяет вставить в контейнер набор DOM нод или фрагмент документа
Пример использования
<article><dom>fragment</dom></article>
При шаблонизации данных
{
"fragment": DocumentFragment
}
Получим результат
<article><p>Я - содержимое фрагмента</p></article>
<val_by [default="defaultValue"]>path</val_by>
Данная директива принимает на вход ключ, который хранит путь к ключу с данными :) В остальном схожа по поведению с директивой value
Пример использования
<h1><val_by>header</val_by></h1>
При шаблонизации данных
{
"header": "user.name",
"user":
{
"name": "Котигорошко"
}
}
Получим результат
<h1>Котигорошко</h1>
<include tpl="templateID">[{ JSON Object }]</include>
Данная диркетива производит вставку шаблона c templateID
в указанное место. При этом может быть сформирована локальная зона видимости переменных путем добавления содержимого тега в виде { JSON Object }
.
Содержимое тега допускает наличие других директив шаблонизации.
Пример использования
Исходный код
<template id="article">
<article>
<h1><value>article.title</value></h1>
<include tpl="universalBody">{"data": <value>JSON.stringify(article.body)</value>}</include>
</article>
</template>
Темлейт universalBody
<template id="universalBody">
<p><inner_html>data.replace(/\[br\]/, "<br>")</inner_html></p>
</template>
При шаблонизации данных
{
"article":
{
"title": "Привет",
"body": "Как дела? [br] Что нового?"
}
}
Получим результат
<h1>Привет</h1>
<p>Как дела?<br>Что нового?</p>
Для темплейта universalBody
локальными будут данные, которые передаются в теле <include>
, а не данные темплейта article
.
<tag>
<name>tagName</name>
content
</tag>
Данная директива создает тег с определенным именем tagName
. Значение тега <name>
и содержимое <tag>
шаблонизируется.
Пример использования
<tag>
<name>
<if expr="url">
<then>a</then>
<else>span</else>
</if>
</name>
<value>text</value>
</tag>
При шаблонизации данных
{
"text": "Нет данных"
}
Получим результат
<span>Нет данных</span>
<attr name="attributeName">attributeValue</attr>
Создает у родительского тега атрибут с именем attributeName
и присваивает значение attributeValue
. Содержимое тега <attr>
шаблонизируется.
Пример использования
<a>
<attr name="href">
<value default="#">author.website</value>
</attr>
Страница автора
</a>
При шаблонизации данных
{
"author":
{
"name": "Котигорошко",
"website": "http://kotihoroshko.info"
}
}
Получим результат
<a href="http://kotihoroshko.info">Страница автора</a>
<class>className</class>
Добавляет родительскому тегу класс с именем className
. Содержимое тега <class>
шаблонизируется.
Пример использования
<div class="access">
<class>
<if expr="demo">
<then>demo</then>
<else>normal</else>
</if>
</class>
Доступ
</div>
При шаблонизации данных
{
"demo": false
}
Получим результат
<div class="access normal">Доступ</div>
<foreach from="list" item="item" [key="key"]></foreach>
Итерирует list
(Object
или Array
), создавая на каждой итерации локальную зону видимости переменных item
, и, если указано, ключ key
. Для получения доступа к родительской зоне видимости, снаружи foreach
, используется ключевое слово _parent_
. Содержимое тега <foreach>
шаблонизируется.
Пример использования
<ul>
<foreach from="menu" item="menuItem">
<li>
<value>menuItem.text</value>
<if expr="_parent_.active == menuItem.id">
<class>active</class>
</if>
</li>
</foreach>
</ul>
При шаблонизации данных
{
"menu":
[
{ "text": "Файл", "id": "file" },
{ "text": "Настройки", "id": "preferences" },
{ "text": "Помощь", "id": "help" }
],
"active": "file"
}
Получим результат
<ul>
<li class="active">Файл<li>
<li>Настройки<li>
<li>Помощь<li>
</ul>
<capture [expr="expression"] to="captureName">capturedContent</capture>
Накапливает capturedContent
в массив captureName
контейнера, в котором находится <capture>
. Если указан атрибут expr
, то накопление данных будет производиться только в том случае, если выполняется условие expression
. Содержимое тега <capture>
шаблонизируется.
Следующие директивы являются "прозрачными" для <capture>
, т.е. не являются контейнерами, в котором накапливаются данные:
if
,then
,else
include
tag
attr
class
foreach
capture
datetime
Пример использования
<p>
Администраторы:
<foreach from="users" item="user">
<capture expr="user.admin" to="admins">
<b><value>user.name</value></b>
</capture>
</foreach>
<flush>admins.join(", ")</flush>
</p>
При шаблонизации данных
{
"users":
[
{ "name": "Малькольм «Мэл» Рейнольдс", "admin": true },
{ "name": "Зои Эллейн Уошбёрн", "admin": true },
{ "text": "Хобан «Уош» Уошбёрн", "admin": false },
{ "text": "Инара Серра", "admin": false },
{ "text": "Джейн Кобб", "admin": false }
]
}
Получим результат
<p>
Администраторы: <b>Малькольм «Мэл» Рейнольдс</b>, <b>Зои Эллейн Уошбёрн</b>
</p>
<flush>expression</flush>
Вставляет накопленный <capture>
контент. У данной директивы присутствуют аналогичные ограничения <capture>
: зависимость от контейнера, в котором она вызывается, и "прозрачность" других директив. expression
должен содержать JavaScript код вывода данных.
Пример использования
<p>
<capture to="list">один</capture>
<capture to="list">два</capture>
<flush>list.map(String.toUpperCase).join(", ")</flush>
</p>
Получим результат
<p>
ОДИН, ДВА
</p>
<datetime use="path" [utc]>content</datetime>
По пути path
ищется ключ в локальной зоне видимости переменных, в которой расположен тег <datetime>
. По умолчанию это zEvent.data
. Если значение по ключу удалось преобразовать в объект Date()
, то будет использоваться дата значения, иначе текущая дата. Содержимое тега <datetime>
шаблонизируется. При добавлении атрибута utc
вместо стандартных будут использоваться utc аналоги функций по работе с датами
Директива создает новую локальную зону видимости dt
следующего формата:
{
"dt":
{
"raw": dt, // [object Date]
"y": dt.getFullYear(), // Год в формате YYYY
"m": dt.getMonth(), // Месяц 0..11
"d": dt.getDate(), // День 1..31
"H": dt.getHours(), // Часы 0..23
"M": dt.getMinutes(), // Минуты 0..59
"S": dt.getSeconds(), // Секунды 0..59
"wd": dt.getDay(), // День недели 0=ВС, 1=ПН, ... , 6=СБ
"daysDiff": daysDiff, // Разница в днях между искомой и текущей датой
"rel": alias // Относительное значение ["past", "yesterday", "today", "tomorrow", "future"]
}
}
Пример использования
<time>
Вы посещали нас <datetime use="lastVisitDateTime"><value>dt.daysDiff</value></datetime> дней тому
</time>
При шаблонизации данных
{
"lastVisitDateTime": 1451599200000
}
Получим результат
<time>
Вы посещали нас 35 дней тому
</time>
Из-за особенностей парсера браузера вы не всегда можете использовать директивы Z фреймворка вместе с некоторыми тегами. Вместо этого вы можете использовать теги-обертки, которые на момент шаблонизации будут трансформированы в привычные браузеру теги.
Список оберток:
z:table
z:caption
z:colgroup
z:col
z:thead
z:tfoot
z:tbody
z:tr
z:td
z:th
z:select
z:option
z:textarea
Пример использования
<z:table class="prettyTable">
<z:tbody>
<z:tr>
<z:th>Цена</z:th>
<z:td>100$</z:td>
</z:tr>
</z:tbody>
</z:table>
Получим результат
<table class="prettyTable">
<tbody>
<tr>
<th>Цена</th>
<td>100$</td>
</tr>
</tbody>
</table>
Фреймворк позволяет выводить контент в правильной числовой форме для различных языковых групп. На данный момент можно использовать следующие языки и языковые группы (ruleID
):
0
Chinese (1 форма)1
English (2 формы)2
French (2 формы)3
Latvian (3 формы)4
Scottish Gaelic (4 формы)5
Romanian (3 формы)6
Lithuanian (3 формы)7
Ukrainian (3 формы)8
Slovak (3 формы)9
Polish (3 формы)10
Slovenian (4 формы)11
Irish Gaeilge (5 форм)12
Arabic (6 форм)13
Maltese (4 формы)14
Macedonian (3 формы)15
Icelandic (2 формы)16
Breton (6 форм)
Уточнить необходимую языковую группу можно в соответствующей документации.
Для декларации языковых форм для определенной группы языков используется специальная директива <plurals>
, которая должна быть расположена в <template id="zTemplates"/>
<plurals rule="ruleID">
{
"alias": [ "form1", ... ]
}
</plurals>
Арибут rule
указывает на правило из вышестоящего списка, по которому будет определяться необходимая числовая форма.
Содержимое тега <plurals>
- JSON со списком ключей, каждый из которых должен быть массивом. Например, для русского языка массив должен содержать три формы, для английского всего две, для китайского одна.
Пример использования
<plurals rule="7">
{
"apples": [ "яблоко", "яблока", "яблок" ],
"bananas": [ "банан", "банана", "бананов" ]
}
</plurals>
<template id="pluralTest">
<p><value>apples</value> <value>"apples"^apples^7</value></p>
</template>
При шаблонизации данных
{
"apples": 7
}
Получим результат
<p>7 яблок</p>
Для вывода правильной числовой формы используется директива <value>
следующего формата:
<value>alias^path[^ruleID]</value>
Где
alias
-- ключ из директивы<plurals>
path
-- путь к свойству объекта в локальной зоне видимости (по умолчаниюzEvent.data
)ruleID
-- идентификатор правила определения числовой формы (по умолчанию 1)
Для alias
и path
допускаются более сложные JS-конструкции, например конкатенация строк или сложные пути.
Например
<value>"stat_"+item.type^item.value</value>