feast v1.4.0
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
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago