// Пример создания блока
const btn = new UIButton({
name: "make",
icon: "wow",
value: "Сделать",
});
#!+ // Рендер
#!- btn.renderTo(document.body);
#!+ // Меняем аттрибуты
#!- btn.set({value: "Сделать хорошо!"});
<button type="{attrs.type}">
<!-- Важно: корневой класс добавляется автоматически -->
#! <bem:mod name="with_icon" test="attrs.icon"/>
#!+ <span
bem:elem="icon"
**fn:if="attrs.icon"**
>
#!+ <!-- Использование блока `icon` -->
#!- <b:icon name="{attrs.icon}"/>
#!- </span>
#!+ <span bem:elem="value">
**{attrs.value}** <!-- аналог <fn:value>attrs.value </fn:value> -->
#!- </span>
</button>
.button {
// Кнопка с иконкой
&_with_icon { /*... */ }
// Иконка
&__icon { /*... */ }
// Текст/Значение
&__value { /*... */ }
}
import feast from "feast";
#!+ // Связанне ресурсы
import template from "feast-tpl!./button.html";
import styleSheet from "feast-css!./button.css";
#!-
#!+ // Используемые блоки
import UIIcon from "blocks/icon/icon";
#!-
#!+ /** @class UIButton */
export default feast.Block.extend(/** @lends UIButton# */{
#! name: "button", // Название блока, оно же корневой css-класс
#! template, // Шаблон
#! styleSheet, // Стили
#!+ blocks: { // Используемые блоки
icon: UIIcon, // "название" => UI-Блок
#!- },
#!+ defaults: {
icon: null,
type: "submit",
value: "Submit",
#!- }
#!- });
app:render() ⬇ event ⬇ app:render()
<!-- Блок «Список писем» -->
<div>
<!-- Используем абстрактный UIDataset -->
<b:dataset
models="{attrs.models}"
compact="{attrs.compact}"
>
<!-- Определяем итерируемую часть -->
<fn:match name="dataset-item" args="model">
<b:letter-list-item
key="{model.id}"
model="{model}"
/>
</fn:match>
</b:dataset>
</div>
#!+ const datasetLetter = new UIDatasetLetters({
models: mailbox.getLetters(),
});
#!- datasetLetter.renderTo(document.body);
#!+ // Где-то в коде добавляются модели
#!- letters.set([/* список моделей */]);
#!+ // Ещё где-то меняется статус письма
#!- letters[0].set("flags.unread", false);
// У элементов есть набор аттрибутов, // которые отвчеают за его внещний вид и размеры <b:button ico="delete" size="m" short borderless />
<b:layout use:mediator="layout-manager">
#!+ <fn:match name="left-column" args="layout">
#! <b:button short="{layout.leftColumn <= 4}"/>
#!+ <fn:choose>
<fn:when test="layout.is('xxs')">
<!-- ... -->
</fn:when>
#!+ <fn:when test="layout.isLessThen('xl') || layout.isLessThen('m')">
<!-- ... -->
#!- </fn:when>
#!- </fn:choose>
</fn:match>
<!-- И так далее -->
#!- <fn:match name="main-frame" args="layout"/>
</b:layout>
import feast from "feast";
export default feast.Mediator.create("layout-manager", {
components: {
#!+ "layout": { // название для обращения внутри медиатора
#! "class": UILayout,
#!+ "attrs": { // Список аттрибутов, которые будет изменять медиатор
#!+ "size": (mediator) => mediator.size,
"leftColumn": (mediator) => mediator.columnLeft,
"rightColumn": (mediator) => mediator.columnRight,
#!- "support3pane": (mediator) => mediator.support3pane
#!- },
#!- },
#!+ "portal-menu": {
"class": UIPortalMenu,
"attrs": { /* ... */ },
#!+ // события, на которые подписывается медиатор
#!- "events": ["layout:2pane", "layout:3pane"],
#!- },
#!+ "headline": {
"class": UIHeadline,
"attrs": { /* ... */ },
#!- },
},
handleEvent(evt) { /* ... */ }
});
import feast from "feast";
export default feast.Mediator.create("layout-manager", {
components: { /* ... */ },
// Обработчик событий от компонентов по умолчанию
// (можно указать свой для каждого компонента)
handleEvent(evt) {
const type = evt.type;
switch (type) {
case "layout:2pane":
case "layout:3pane":
// Обновляем свойство медиатора
this.type = type.split(":")[1];
break;
}
// После этого все связанные блоки будут обновлены!
}
});
<!-- Feast Style -->
<toolkit:btn
text="Click me!"
remit:click="tap"
/>
#!+ <!-- Toolkit Style -->
<toolkit:btn><![CDATA[{
"text": "Click me!",
"remit:click": "tap"
#!- }]]></toolkit:btn>
// Создаем роутер на основе карты сайта (всех маршрутов)
const app = Pilot.create(sitemap);
#!+ // Функция для получения данные для приложения
const getViewAttrs = () => ({
folder: app.model.folder,
folders: app.model.folders,
letters: app.model.letters,
letter: app.model.letter,
router: app,
route: app.route,
request: app.request,
#!- });
#!+ // Создаём view-приложения
#!- app.view = new UIApplication(getViewAttrs());
#!+ // Обновляем приложение на каждый routeend
#!- app.on("routeend", () => app.view.set(getViewAttrs()));
#!+ // Рендер приложения c удалением содержимого
#!- app.view.renderTo(document.body, {replace: true});
export default {
model: { // модели доступные всем маршрутам
folders: () => Folder.find(),
},
"#index": {
url: { // описание маски и правил валидации
pattern: "/:folder",
},
model: {
threads: (req) => Thread.find({folder: req.params.folder})
}
},
"#other-page-id": { ... }
};
В прошлом мирко-докладе «React, где скорость?», я уже проводил сравнение с React, цифры которого примерно такие-же, а то и медленней, чем у нашего даталиста в старой Почте.
Так что не буду приводить новых цифр, а просто попытаюсь объяснить, за счет чего мы выжимаем скорость.
#!+ // Тут же мы имеем такие штуки как <fn:attr name="..." value="..." test="..."/> #!- <fn:add-class name="..." test="..."/> #!+ // А главное <bem:mod name="..." test="..."/> <bem:mod name="..." value="..." test="..."/> #!- // и другие...