1.4.0 • Published 6 years ago

feast v1.4.0

Weekly downloads
1
License
MIT
Repository
gitlab
Last release
6 years ago

Feast

Экспериментальный шаблонизатор на основе xhtml-синтаксиса с конвертацией в vdom (citojs).

Установка и запуск локального сервера

Настройка 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) {
		// ...
	}
}

Анимация

Базовые теги


Базовые атрибуты


Модификаторы


BEM

  • bem:mod — элемент и атрибут
  • bem:elem — спец. атрибут

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)

    1. Первый frame: fx[-name]-appear-enter
    2. Анимация: fx[-name]-appear-enter-active fx[-name]-appear-enter-to
  • Добавление элемента

    1. Первый frame: fx[-name]-enter
    2. Анимация: fx[-name]-enter-active fx[-name]-enter-to
  • Удаление элемента

    1. Первый frame: fx[-name]-leave
    2. Анимация: fx[-name]-leave-active fx[-name]-leave-to
<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

  1. Официальная документация
  2. 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
  1. 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: \{(.*?)\}
  1. 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: \{(.*?)\}
  1. Editor > Language Injection > *click* + > XML Tag Injection
  • name: feast-value
  • Language > ID: JavaScript
  • XML Tag > Local name: value
1.4.0

6 years ago

1.2.2

7 years ago

1.2.1

7 years ago

1.1.1

7 years ago

0.19.16

7 years ago

1.0.3

7 years ago

1.0.0

7 years ago

1.0.0-alpha.2

7 years ago

1.0.0-alpha.1

7 years ago

0.19.13

7 years ago

0.19.12

8 years ago

0.19.11

8 years ago

0.19.10

8 years ago

0.19.9

8 years ago

0.19.7

8 years ago

0.19.5

8 years ago

0.19.2

8 years ago

0.19.0

8 years ago

0.18.2

8 years ago

0.18.1

8 years ago

0.18.0

8 years ago

0.16.0

8 years ago

0.15.0

8 years ago

0.14.2

9 years ago

0.14.0

9 years ago

0.13.0

9 years ago

0.11.0

9 years ago

0.10.0

9 years ago