Skip to content
/ z Public

Z (Zet) Framework - Структурно-событийный фреймворк для построения одностраничных приложений

License

Notifications You must be signed in to change notification settings

s0rr0w/z

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

z

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), так как на практике эта функциоальность не нужна. Данная "проблема" спокойно решается путем изменения зоны распространения события или дополнительной функцональностью обработчиков.

Структура zEvent

{
  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, в то время как относительные используют ссылку на другой диспатчер

Для инициализации диспатчеров используются следующие директивы:

  • Абсолютная директива <dispatch> »»»
  • Относительная директива <dispatch> »»»

Запуск событий через HTML директивы осуществляется с помощью:

  1. Директивы <handler> »»»
  2. Директивы <exec> »»»

Также можно использовать JS API для запуска событий:

  1. JS-метод z.dispatch() »»»
  2. JS-метод z.dispatchById() »»»

Интерактивный пример наглядно продемонстрирует логику работы диспатчера событий.

Абсолютная директива <dispatch>

Данная директива носит описательный характер и не запускает событие. Реальным инициатором запуска могут быть директивы <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 } являются не обязательными

Имя события e[vent]

Имя события должно удовлетворять правилам именования переменных JS, значения атрибутов и названию тегов.

Примеры названий

click, myCoolEvent2, Do_It_Again
Контейнер распространения события f[rom]

Параметр может принимать следующие значения:

  • . -- родительский элемент тега <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

Директивы распространения события p[ropagation]

Доступны следующие варианты директив:

  • 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 повторно попадает в зону видимости
Именование диспатчеров по id и name

Директивам <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-объекту.

Относительная директива <dispatch>

Данная инструкция позволяет использовать удаленный диспатчер по id или по name.

Формат
<dispatch use="dispatcherID">[{ JSON Object }]</dispatch>

Где:

  • use -- идентификатор или путь к удаленной ноде <dispatch>
Идентификатор или путь к удаленному диспатчеру (use)

Данный параметр может принимать или 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>
  1. Будет вызван D1 по id #farDispatcher1
  2. Будет вызван D2, так как контейнер для вызывающего диспатчера является span, в нем уже ищется диспатчер с name="localName"
  3. Будет вызван D1, так как указан контейнер div, но первый попавшийся диспатчер с именем localName скорее всего будет именно он
  4. Будет вызван 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>
  1. Будет вызван dispatch#farDispatcher1, который запустит событие e1, которое будет содержать данные { "x": 2, "y": 3 }
  2. Будет вызван тот же диспатчер, но данные будут те, которые определены первоначально: { "x": 1 }
  3. Результат будет аналогичный пункту 1, только с другим именем события
  4. Данные события e2 будут пустыми ({})

Директива <handler>

Данная директива навешивает 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-события on

Имя DOM-события должно удовлетворять правилам именования событий функции addEventListener.

Примеры названий

click, keypress, scroll
Контейнер target

По умолчанию обработчик 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.

Список допустимых клавиш keys

Если указан этот параметр, то события будут запускаться только в случае совпадения с указанным списком клавиш. Список формируется из определенных констант, разделенных запятой.

Допускаются следующие значения:

  • 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. В противном случае запуск события не производится.

Подтверждение действия confirm

Если необходимо подтверждение действия со стороны пользователя, то использование данной директивы вызовет стандартный JS-метод confirm() перед запуском события. В качестве параметра JS-методу будет передано содержимое атрибута confirm. Если пользователь подтвердит выбор, то события будут запущены. В противном случае запуск не производится.

Пример использования

<a href="#">
 <handler on="keypress" keys="BS,DEL" confirm="Вы уверены, что хотите удалить данный элемент?">
  <dispatch e="removeItem" f=".item" p="parent"></dispatch>
 </handler>
</a>

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

Время блокировки повторного срабатывания freeze

Данная директива позволяет блокировать на определенное количество секунд повторное срабатывание запуска обработчика события.

Пример использования

<button>
 <handler on="click" freeze="5">
  <dispatch e="sendData" p="global"></dispatch>
 </handler>
</button>

После нажатия на кнопку будет запущено глобальное событие sendData, и в течение 5 секунд будет заблокирована возможность повторного запуска данного события.

"Прозрачность" для событий браузера: allowPropagation и allowDefault

Данные две директивы не допускают срабатывания Event.stopPropagation() и Event.preventDefault(), которые по-умолчанию блокируются фреймворком

Пример использования

<button>
 <handler on="click" allowPropagation allowDefault>
  <dispatch e="sendData" p="global"></dispatch>
 </handler>
</button>

После нажатия на кнопку будет запущено глобальное событие sendData, при этом событие браузера click не будет заблокировано и может сработать на других элементах

Блокировка события по наличию выделения excludingSelection

При наличии директивы excludingSelection и любого выделения контента на странице, запуск событий не будет произведен.

Пример использования

<button>
 <handler on="click" excludingSelection>
  <dispatch e="editMe" p="global"></dispatch>
 </handler>
</button>

После нажатия на кнопку будет запущено глобальное событие editMe только при отсутствии выделенного текста или другого выделенного контента на странице

Директива <exec>

Данная директива запускает диспатчеры, которые содержатся в ней.

Пример использования

<exec>
 <dispatch use="extEvent"></dispatch>
</exec>

JS-метод z.dispatch()

Данный метод позволяет запускать события zEvent

Формат
z.dispatch(zEvent[,zEvent,...]);

Пример использования

z.dispatch(
 {
  e: "e1", 
  f: document.getElementById("container"),
  p: "childNodes",
  data: { "x": 1 }
 },
 {
  e: "e2",
  p: "global"
 }
)

JS-метод z.dispatchById()

Данный метод является аналогом удаленного вызова директивы <dispatch>, за исключением того, что данные не замещаются, а добавляются к существующим. Функция возвращает ссылку на объект zEvent

Формат
z.dispatchById( "dispatcherID"[, mixinData ] );

Где

  • dispatcherID -- идентификатор ноды <dispatch>
  • mixinData -- объект данных, который будет добавлен к существующим данным

Обработчики

Во фреймворке в качестве обработчиков выступают примитивные JavaScript-функции. Следуя идеологии *nix-way, они должны выполнять одну функцию, но делать это наилучшим образом.

  • Установка обработчиков <e> »»»
  • Регистрация обработчиков z.addHandler »»»
  • Предустановленные обработчики »»»

Установка обработчиков <e>

Данная директива устанавливает обработчик родительской ноде

Формат
<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

Формат
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
<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, только шаблонизация будет произведена всего один раз.

Обработчик templateIfMatch
<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>
Обработчик templateIfAttrMatch
<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>
Обработчик templateIfExists
<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>
Обработчик templateScopeIfExists
<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>
Обработчик templateOnce
<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>
Обработчик broadcastEvent
<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>
Обработчик dispatchEvent
<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>

<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>

<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>

<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 нод или фрагмента <dom>

<dom>path</dom>

Директива dom позволяет вставить в контейнер набор DOM нод или фрагмент документа

Пример использования

<article><dom>fragment</dom></article>

При шаблонизации данных

{
 "fragment": DocumentFragment
}

Получим результат

<article><p>Я - содержимое фрагмента</p></article>

Вывод значения по пути <val_by>

<val_by [default="defaultValue"]>path</val_by>

Данная директива принимает на вход ключ, который хранит путь к ключу с данными :) В остальном схожа по поведению с директивой value

Пример использования

<h1><val_by>header</val_by></h1>

При шаблонизации данных

{
 "header": "user.name",
 "user":
  {
   "name": "Котигорошко"
  }
}

Получим результат

<h1>Котигорошко</h1>

Вставка шаблонов <include>

<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\]/, "&lt;br&gt;")</inner_html></p>
</template>

При шаблонизации данных

{
 "article":
  {
   "title": "Привет",
   "body": "Как дела? [br] Что нового?"
  }
}

Получим результат

<h1>Привет</h1>
<p>Как дела?<br>Что нового?</p>

Для темплейта universalBody локальными будут данные, которые передаются в теле <include>, а не данные темплейта article.

Динамическое создание тега <tag>

<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>

<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>

<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>

<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>

<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>

<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>

<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>

About

Z (Zet) Framework - Структурно-событийный фреймворк для построения одностраничных приложений

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published