feast
v1.4.0
Published
Feast — Экспериментальный шаблонизатор на основе xhtml-синтаксиса с конвертацией в vdom
Downloads
27
Readme
Feast
Экспериментальный шаблонизатор на основе xhtml-синтаксиса с конвертацией в vdom (citojs).
Установка и запуск локального сервера
cd feast
npm i --save-dev feast feast-dev
— установка необходимых пакетовnode ./node_modules/feast-dev/index.js --feast-demo
— запуск сервера: http://localhost:2015/- Подробнее о fesat-dev
Настройка IDE / Подсветка кода / Language Injection
Работа из консоли
./node_modules/feast/bin/assist create-block --name={name} --path=path/to/feast/blocks/
ES2015+ (+ @decorators)
import {configure, Block} from 'feast';
import UIIcon from '../path/to/Icon/Icon';
@configure({
name: 'btn', // название блока
events: {tap: 'handleTap'}, // обработчики события
isolate: false, // разрешить наследование значений аттрибутов родительского блока
isolateEvents: false, // разрешить всплытие событий
blocks: {icon: UIIcon}, // используемые блоки
defaults: {text: 'Wow!'}, // свойства по умолчанию
template: `<button remit:click="tap"><b:icon/>{attrs.text}</button>`,
})
export default class extends Block {
handleTap(evt) {
// ...
}
}
// Читый ES2015+ (без декоратора)
export default configure({
// Опции
})(class extends Block {
// Методы
});
TypeScript + TSX
import {configure, Block} from 'feast';
import UIIcon from '../path/to/Icon/Icon';
export interface ButtonProps {
text: string;
}
@configure({
name: 'btn', // название блока
events: {tap: 'handleTap'}, // обработчики события
})
export class extends Block<ButtonProps> {
template({text}) {
return (
<button remit-click="tap">
<Icon/>
{text}
</button>
);
}
handleTap(evt) {
// ...
}
}
Анимация
Базовые теги
- fn:if
- fn:choose: fn:when и fn:otherwise
- fn:for
- fn:value и короткий синтакиси
{...}
- fn:attr
- fn:add-class
- fn:match
- fn:apply-match
Базовые атрибуты
Модификаторы
BEM
CSS Modules
Создание блока
button.html — шаблон
<div>
<bem:mod name="disabled" test="attrs.disabled" />
<div bem:elem="text">
{attrs.text}
</div>
</div>
button.js — описание поведения блока
Полное описание методов feast.Block
import feast from 'feast';
import template from 'feast-tpl!./button.html';
import styleSheet from 'feast-css!./button.css';
export default feast.Block.extend({
name: 'button', // уникальное название блока
template,
styleSheet,
defaults: {
text: null,
disabled: false
}
});
fn:if
Условные оператор.
test
— любое javascript выражение
<fn:if test="attrs.value > 10">
Поздравляем!
</fn:if>
<!-- ИЛИ -->
<div fn:if="attrs.value === 777">
Wow!
</div>
fn:if animated
Анимированные if
.
test
— любое javascript выражениеanimated
илиanimated="slide"
<fn:if test="attrs.value > 10" animated>
<b>Поздравляем!</b>
</fn:if>
<!-- ↓ первый frame ↓ -->
<b class="fx-enter">Поздравляем!</b>
<!-- ↓ анимация ↓ -->
<b class="fx-enter-active fx-enter-to">Поздравляем!</b>
fn:transition
Анимация вложенного контента:
name
— название эффекта (optional)appear
— добавить аниация на изначальный рендер (optional)
Фазы
Изначальный рендер (appear)
- Первый frame:
fx[-name]-appear-enter
- Анимация:
fx[-name]-appear-enter-active fx[-name]-appear-enter-to
- Первый frame:
Добавление элемента
- Первый frame:
fx[-name]-enter
- Анимация:
fx[-name]-enter-active fx[-name]-enter-to
- Первый frame:
Удаление элемента
- Первый frame:
fx[-name]-leave
- Анимация:
fx[-name]-leave-active fx[-name]-leave-to
- Первый frame:
<fn:transition name="slide" appear>
<b>Wow!</b>
</fn:transition>
<!-- ↓ первый frame ↓ -->
<b class="fx-slide-appear-enter">Wow!</b>
<!-- ↓ анимация ↓ -->
<b class="fx-slide-appear-enter-active fx-slide-appear-enter-to">Wow!</b>
fn:choose
Блок ветвления.
<fn:choose>
<fn:when test="attrs.userName">
Привет, {attrs.userName}!
</fn:when>
<fn:otherwise>
Авторизуйтесь!
</fn:otherwise>
</fn:choose>
fn:for
Перебор массива или объекта.
ВАЖНО: У всех рутовых элементов внутри fn:for
должен быть задан уникальный key
-атрибут
data
— массив или объект (любое javascript выражение)as
— название переменной очередного элемент массива (опционально)key
— индекс или ключ (опционально)filter
— функция фильтрации списка, на вход получает два элемента:as
иkey
(опционально)cached
— vdom-кеширование по ключу (опционально)
<ul>
<fn:for data="attrs.items" as="item">
<li key="{item.id}">
<a href="{item.href}">{item.text}</a>
</li>
</fn:for>
</ul>
fn:value
Вывести любое javascript выражение
output
— режим выводаraw
— «как есть»text
— обычный текст (по умолчанию)
<h1>
Привет <fn:value>attrs.username</fn:value>!
</h1>
или короткий синтаксис
<h1>Привет {attrs.username}!</h1>
Чтобы вывести смивол {
и }
используйте экранирование \{
.
fn:attr
Заменить или установить атрибут родительского элементу
name
— названиеvalue
— значениеtest
— любое javascript выражение (опционально)
<a>
<fn:attr name="href" value="#!{attrs.href}" test="!attrs.disabled"/>
...
</a>
fn:add-class
Добавить css-класс родительскому элементу
name
— название класса (через пробел)test
— любое javascript выражение (опционально)
<div>
<fn:add-class name="selected" test="attrs.value === 'foo'"/>
...
</div>
fn:match и fn:apply-match
Определение и использование подшаблона.
fn:match
name
— имя подшаблонаargs
— названия аргументов через запятую, которые будут переданны вmatch
отapply-match
(опционально)
fn:apply-match
name
— имя вызываемого подшаблонаargs
— аргументы через запятую, которые нужно передать вmatch
(опционально)
notify.html
<div>
<h1 bem:elem="title"><fn:apply-match name="title"/><h1>
<div bem:elem="content">
<fn:apply-match name="content"/>
</div>
</div>
Использование:
<b:notify>
<fn:match name="title">Заголовок</fn:match>
<fn:match name="content">Какое-то содержание</fn:match>
</b:notify>
id
Доступ к вложеному feast.Block
по уникальному id
var App = feast.Block.extend({
name: 'app',
template: '<div><b:nav id="nav"/></div>',
didMount() {
const nav = this.ids.nav;
}
});
ref
Доступ к вложеному HTMLElement
по уникальному ref
var Form = feast.Block.extend({
name: 'form',
template: '<form><button ref="send"/></form>',
didMount() {
const sendEl = this.refs.send;
}
});
on-event
Подписаться на DOM-событие
<div on-click="_this.handleClick(evt)"/>
remit:event
Преобразование DOM-события в пользовательское
var Btn = feast.Block.extend({
name: 'btn',
template: '<button remit:click="tap" event:details="attrs.details"/>',
events: {
'tap': 'handleTap'
},
handleTap(evt) {
const details = evt.details; // если `event:details` не задан, то details будет ссылкой на `this`
}
});
Всплытие remit:event
Правильный способ организации всплытия remit
-событий
var SubscribeForm = feast.Block.extend({
name: 'subscribe-form',
template: feast.parse('<button remit:click="subscribe" value="..."/>')
});
var App = feast.Block.extend({
name: 'app',
blocks: {'subscribe-form': SubscribeForm},
template: feast.parse('<div><b:subscribe-form type="news" remit:subscribe="subscribe:news"/></div>'),
events: {
'subscribe:news': 'handleSubscribe'
},
handleSubscribe(evt) {
const type = evt.type.split(':').pop();
}
});
Модификаторы значения и выражения
Сигнатура
function format(value, format) {
// применяем модицикацию
return newValue;
}
Пример
import dateFormat from './data-format';
feast.Block.extend({
mods: {
trim: (val) => val.trim(),
ucfirst: (val) => val.charAt(0).toUpperCase() + val.substr(1),
'bem-prefix': (val, prefix) => prefix + val,
dateFormat
},
template: `<div class="{attrs.activeClass | trim | bem-prefix:'b-'}">
Hello, {attrs.name | ucfirst}!
Сейчас: {Date.now() | dateFormat:"hh:mm"}
</div>`
});
Модификаторы события
Это инструмент, позволяющий на этапе подписки на событие применить к нему разные методы или фильтрацию, например по названию клавиши или её коду:
<!-- Перехватываем `enter`, отменяем действие по умолчанию и останавливаем всплытие -->
<input remit:keydown.enter.prevent.stop="send"/>
- Модификатор события
prevent
— отменить действие по умолчаниюstop
— остановить всплытие
- Фильтрация по
- Названию клавиши
enter
esc
tab
left
right
up
down
spacebar
shift
ctrl
alt
XXXXX
— произвольныйkeyCode
- Зажатой клавише
alt-pressed
ctrl-pressed
shift-pressed
meta-pressed
- Кнопке мыши
left-btn
right-btn
- Названию клавиши
Расширение
// on-click.my-modifier="..."
feast.vdom.eventModifiers['my-modifier'] = function myModifier(evt) {
if (expression(evt)) {
// Отменяем исполнение слушателя события
return false;
}
};
// Добавляем имя клавиши
feast.vdom.eventModifiers.KEYS.q = 'Q'.charCodeAt(0); // да-да, сначало в нижнем, потом верхнем регистре
use:mixin
Использовать примеси на стадии компиляции.
feast.tags.$mixins['form-element'] = function formElementMixin(node, attrs) {
attrs.name = '{attrs.name}';
attrs.type = '{attrs.type || "text"}';
attrs.tabindex = '{attrs.index}';
attrs.placeholder = '{attrs.placeholder}';
attrs.required = '{attrs.required}';
// и так далее
};
var Inp = feast.Block.extend({
name: 'inp',
template: '<input use:mixin="form-element"/>'
});
bem:mod
name
— название модификатораvalue
— значение модикатора (опционально)test
— условие добавления модификатора (любое javascript выражение, опционально)
<div>
<bem:mod name="flat"/>
<bem:mod name="size" value="{attrs.size}"/>
<bem:mod name="expanded" test="attrs.expanded"/>
</div>
<!-- ИЛИ -->
<div bem:mod="size_{attrs.size}">
<!-- увы, но так можно добавить только один модификатор -->
</div>
bem:elem
Спец. атрибут для BEM-именования элементов (только плоское именование, никаких элемент элемента).
<div>
<h2 bem:elem="title">...</h2>
<div bem:elem="text">...</div>
</div>
CSS Modules
Для работы используйте feast/src/require-css.js
require.config({
// ...
map: {
'*': {
'feast-css': '/node_modules/feast/src/require-css.js',
// ...
},
},
});
feast-css
import css from "feast-css!./style.css";
console.log(css.className); // -jdy73jk
feast-css: expose
Обеспечивает доступ к классом из внешнего css
/* expose {form, container as formContainer} from "../form/form.css" */
.button { color: red; }
.form .button { color: green; }
.formContainer .container { margin: 10px; }
feast.Block
// Описание блока
import feast from 'feast';
import template from 'feast-tpl!path/to/block-name/block-name.html';
import styleSheet from 'feast-css!path/to/block-name/block-name.css'; // только если нужна модульность для CSS (уникальные имена CSS-селекторов)
// Используемые блоки
import UIBtn from 'path/to/btn/btn';
export default feast.Block.extend({
name: 'block-name', // Имя блока, оно же используется как CSS-класс
template, // или `'<div/>'`
styleSheet, // опционально, только если нужна модульность, css: `{'block-name': '{uniq_hash}'}
// Изолировать аттрибуты
// `true` — по умолчанию
// `false` — наследовать аттрибуты родителя
isolate: false,
// Изолировать события испускаемые блоком рамками этого блока, по умолчанию `true`
isolateEvents: false,
// Список используемых блоков
blocks: {
btn: UIBtn
},
// Обработка изменения атрибутов
attrChanged: {
'attr-name': function (newValue, oldValue) {
// Любое действие
}
},
// Обработка событий
events: {
'click': 'handleClick' // название события => название метода
},
handleClick(/** Event */evt) {
// Обработка события `click`
},
didMount() {
// Блок добавлен в DOM
// Подписываемся на DOM событие (unmount элемента такие события будут сняты автоматически)
this.$on(document, 'click', 'handleOutsideClick');
},
didUnmount() {
// Блок извлечен из DOM
},
handleOutsideClick(/** Event */evt) {
// Проверяем, что клик сделан за пределами блока
if (this.el.contains(evt.target)) {
}
}
});
// Использование
import UIBlockName from 'path/to/block-name/block-name';
const block = new UIBlockName({
'attr-bool': false,
});
// Рендер блока
block.renderTo(document.body);
// Уничтожить блок
block.destroy(); // генерируем событие `destroy`, на которое можно подписаться через `on`
Методы
- get(attrName:
string
):*
— получить значение атрибута - set(attrName:
string
, value:*
):void 0
— изменить значение атрибута - set(attributes:
object
):void 0
— изменить значения атрибутов - is(attrName:
string
):boolean
— проверить значение атрибута на истинность - invert(attrName:
string
):boolean
— инвертировать значение атрибута - on(name:
string
, fn:function
) — подписаться на событие блока - off(name:
string
, fn:function
) — отписаться от события блока - broadcast(name:
string
[, details:*
, [, originalEvent:Event
]) — распространить событие вверх по vdom-дереву (т.е. дереву блоков) - $on(target:
HTMLElement
, eventName:string
, handle:string|function
) — подписаться на DOM событие - $off(target:
HTMLElement
[, eventName:string
[, handle:string|function
]]) — отписаться от DOM события или событий - one(target:
HTMLElement
[, eventName:string
[, handle:string|function
]]) - setTemplateMatch(name:
string
, match:Function
) — установить функцию отвечающую заapply-match
WebStorm / Language Injection
- Официальная документация
- Editor > Language Injection > *click* [+] > XML Attributes Injection
- name:
feast-keywords
- Language > ID:
JavaScript
- XML Tag > Local name:
if|for|when|var|add-class|attr|match|mod|apply-match
- XML Attrbiute > Local name:
test|data|as|key|args
- Editor > Language Injection > *click* [+] > XML Attributes Injection
- name:
feast-interpolation-in-html
- Language > ID:
JavaScript
- XML Attrbiute > Local name:
[a-zA-Z-]+
- Advanced > Value pattern:
\{(.*?)\}
- Editor > Language Injection > *click* [+] > XML Attributes Injection
- name:
feast-interpolation-in-feast
- Language > ID:
JavaScript
- XML Tag > Local name:
mod|add-class|attr
- XML Tag > Local namespace:
bem|fn
- XML Attrbiute > Local name:
name|value
- Advanced > Value pattern:
\{(.*?)\}
- Editor > Language Injection > *click* [+] > XML Tag Injection
- name:
feast-value
- Language > ID:
JavaScript
- XML Tag > Local name:
value