1.4.0 • Published 8 years ago

feast v1.4.0

Weekly downloads
1
License
MIT
Repository
gitlab
Last release
8 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

8 years ago

1.2.2

8 years ago

1.2.1

8 years ago

1.1.1

8 years ago

0.19.16

8 years ago

1.0.3

8 years ago

1.0.0

8 years ago

1.0.0-alpha.2

8 years ago

1.0.0-alpha.1

8 years ago

0.19.13

9 years ago

0.19.12

9 years ago

0.19.11

9 years ago

0.19.10

9 years ago

0.19.9

9 years ago

0.19.7

9 years ago

0.19.5

9 years ago

0.19.2

9 years ago

0.19.0

9 years ago

0.18.2

9 years ago

0.18.1

9 years ago

0.18.0

10 years ago

0.16.0

10 years ago

0.15.0

10 years ago

0.14.2

10 years ago

0.14.0

10 years ago

0.13.0

10 years ago

0.11.0

10 years ago

0.10.0

10 years ago