- Beast
- BML-разметка
- Компонент
- Декларация
- Итого
- Справочник
- BemNode
- isBlock
- isElem
- selector
- parentBlock
- parentNode
- domNode
- defineMod
- defineParam
- mod
- toggleMod
- param
- domAttr
- css
- on
- onWin
- onMod
- trigger
- triggerWin
- index
- empty
- remove
- append
- appendTo
- prepend
- prependTo
- insertChild
- replaceWith
- implementWith
- text
- elem
- get
- has
- afterDomInit
- clone
- render
- renderHTML
- inherited
- isKindOf
- expand
- Beast.decl()
- Прочие методы Beast
- BemNode
- Hello, world
- Дальнейшая работа
Инструмент создания интерфейса веб-приложений. Покрывает собой весь цикл разработки: от шаблонизации до взаимодействия компонент. Идейный наследник БЭМ-методологии и инструментов i-bem.js, bh, React; результат бесед с Сергеем Бережным, Маратом Дулиным, Антоном Шеиным и Артемом Шитовым.
Основная идея: интерфейс делится на компоненты, каждый из которых характеризуется уникальным БЭМ-селектором, входными данными, правилами их преобразования в представление и описанием поведения.
В основе лежат три знания:
- BML-разметка компонента (Beast markup language, XML-подмножество)
- Методы компонента
- Декларация развертки и поведения компонента
## BML-РАЗМЕТКА
Любой интерфейс — это семантическая иерархия. Одни компоненты включают в себя другие, некоторые зависят от контекста, некоторым всё равно. Для независимых компонент введен термин блок. Блоки могут иметь вспомогательные компоненты — элементы. И те, и другие могут обладать изменяющимися признаками — модификаторами (состояния, типы поведения, темы внешнего вида).
Классический способ описания иерархий — XML. Его удобство в том, что он позволяет наглядно разделять содержимое сущности от ее признаков. Содержимое может быть текстом, другими сущностями или всем вперемешку. Пример иерархии интерфейса браузера:
<Browser>
<head>
<Tabs>
<tab State="active">Yandex</tab>
<tab State="release">Lenta.ru</tab>
<newtab/>
</Tabs>
<Addressbar>https://yandex.ru</Addressbar>
</head>
<content>
<Webview State="active" url="https://yandex.ru"/>
<Webview State="release" url="http://lenta.ru"/>
</content>
</Browser>
Разные люди могут описывать одни и те же вещи по-разному, и каждый будет прав по-своему. Так и с выделением зависимых и независимых компонент интерфейса. В данном примере независимыми, иными словами, самодостаточными блоками, являются:
- Browser
- Tabs
- Addressbar
- Webview
И это не единственно верное разбиение интерфейса на главные и второстепенные компоненты. Можно было бы не выделять табы в отдельный блок, а сделать блоком каждый таб, и это была бы уже другая история:
<Browser>
<head>
<Tab State="active">Yandex</Tab>
<Tab State="release">Lenta.ru</Tab>
...
</head>
...
</Browser>
Так или иначе, названия независимых компонент (блоков) начинаются с заглавных, чтобы выделить начало нового смыслового куска. Названия подчиняемых элементов, не имеющих ценности в отсутствии своего родителя, начинаются со строчных.
Элементами, как правило, назначаются компоненты, которые вынуждены часто общаться с себеподобными и зависят от какого-то общего параметра (модификатора родительского блока, например). Им просто удобно быть рядом и образовывать изолированную систему частого обмена сообщениями. Границы такой группы обозначает родительский компонент — блок; он же хранит общие знаменатели группы.
В примере с разметкой браузера помимо узлов есть еще атрибуты. Они тоже бывают двух типов: модификаторы и параметры. Модификаторы, как упоминалось ранее, описывают ограниченное (предвариательно описанное) подмножество признаков компонента: особенности внешнего вида, состояния вкл/выкл и тому подобное. К параметрам относят чаще всего неотображаемые атрибуты компонента, но влияющие на его работу или внешний вид; ими могут быть: счетчики, идентификаторы, URL-адреса для отображения документов и тому подобное. Названия модификаторов начинаются с заглавной буквы, а параметров — со строчной.
Для отображения BML-дерева в браузере, строится соответствующее HTML-дерево:
<div class="browser">
<div class="browser__head">
<div class="tabs">
<div class="tabs__tab tabs__tab_state_active">Yandex</div>
<div class="tabs__tab tabs__tab_state_release">Lenta.ru</div>
<div class="tabs__newtab"></div>
</div>
<div class="addressbar">https://yandex.ru</div>
</div>
<div class="browser__content">
<div class="webview webview_state_active"></div>
<div class="webview webview_state_release"></div>
</div>
</div>
Несмотря на то, что HTML генерируется автоматически, логику генерации все равно следует понимать, чтобы писать CSS и селекторы для декларакций (о последних речь идет в следующей части). Итак, в HTML вся семантика из названий узлов ушла в их классы. Названия тоже частично поменялись: вместо Browser
теперь div class="browser"
, а вместо tabs
div class ="browser__tabs"
. Сделано это для того, чтобы каждый блок или элемент с модификатором или без можно было описать одним CSS-селектором:
.webview {
position:absolute;
top:0;
bottom:0;
left:0;
right:0;
}
.webview_state_release {
display:none;
}
.webview_state_active {
display:block;
}
Такая нотация, во-первых, ускоряет время рендера стилей в браузере за счет сокращения количества селекторов для идентификации компонента и его модификации, а, во-вторых, страхует от персечений селекторов стилей при вложении одних компонент в другие. С использованием препроцессоров (например, Less) классы перестают пугать своей длиной:
.webview {
position:absolute;
top:0;
bottom:0;
left:0;
right:0;
&_state {
&_release {
display:none;
}
&_active {
display:block;
}
}
}
BML-разметка предварительно компилируется в js-эквивалент (HTML-разметка генерируется после):
Beast.node(
'Browser',
{'context':this},
Beast.node(
'head',
null,
Beast.node(
'Tabs',
null,
Beast.node('tab', {'State':'active'}, 'Yandex'),
Beast.node('tab', {'State':'release'}, 'Lenta.ru'),
Beast.node('newtab', null)
),
Beast.node('Addressbar', null, 'https://yandex.ru')
),
Beast.node(
'content',
null,
Beast.node('Webview', {'State':'active', 'url':'https://yandex.ru'}),
Beast.node('Webview', {'State':'release', 'url':'http://lenta.ru'})
)
)
А раз конечный формат — javascript, ничто не мешает использовать его элементы в BML:
<Button id="{Math.random()}">Save</Button>
На самом же деле, парсер обрабатывает только подстроки вида <item>...</item>
; и всё, что снаружи, остается всё тем же javascript. Поэтому можно писать так:
var label = "Save"
var node = <Button>{label}</Button>
В HTML-файл BML-разметка подключается двумя способами: либо тегом link
с указанием type="bml"
и адресом до файла, либо тегом script
тоже с указанием type="bml"
, а внутри BML-дерево.
<html>
<head>
<script src="beast.js"></script>
<link type="bml" href="browser.bml"/>
<script type="bml">
<Browser>...</Browser>
</script>
</head>
</html>
Для карткости теги html
и head
можно опускать — браузер сам сгенерирует эту структуру. И, если содержимое <script type="bml">
будет начинаться и заканчиваться BML-разметкой, парсер автоматически допишет финальный метод генерации и присоединения Beast.node(...).render(document.body)
— так DOM-дерево станет дочерним элементом тега body
. Рекомендуется также всегда указывать в начале кодировку utf-8. В итоге содержимое HTML-файла сократится до такого:
<meta charset="utf8">
<script src="beast.js"></script>
<link type="bml" href="browser.bml"/>
<script type="bml">
<Browser>...</Browser>
</script>
Хорошей практикой считается подключение через link
файлов с декларациями, а в script
хранение общей разметки экрана или страницы.
## Компонент (BemNode)
В javascript BML-разметка компилируется во вложенные друг в друга компоненты — экземпляры класса BemNode.
var button = <Button>Найти</Button>
↓
var button = Beast.node('Button', null, 'Найти')
↓
var button = new Beast.BemNode('Button', null, ['Найти'])
Методы BemNode могут модифицировать свой экземпляр: определять содержимое, поведение, параметры и так далее.
var button = <Button>Найти</Button>
var text = button.text()
button
.tag('button')
.empty()
.append(<label tag="span">{text}</label>)
.render(document.body)
.on('click', function () {
console.log('clicked')
})
Работа с компонентами ведется внутри деклараций. Соответствие компонента и декларации устанавливается через селектор. Декларации исполняются в момент вызова метода render()
. Метод render
вызывается автоматичсеки при создании корневого компонента внутри тега <script type="bml">
:
<meta charset="utf8">
<script type="text/javascript" src="beast.js"></script>
<link type="bml" href="browser.bml"/>
<script type="bml">
<Browser>...</Browser>
</script>
↓
<script type="text/javascript">
Beast.node('Browser', null, ...).render(document.body)
</script>
## ДЕКЛАРАЦИЯ
Описывает развертку (expand) и поведение (domInit) компонента. Представляет собой метод Beast.decl()
с двумя параметрами: селектор компонента (CSS-класс) и набор описаний.
Beast.decl('my-block', {
expand: function () {},
domInit: function () {}
})
Входные данные блока не должны максимально подробно описывать результирующую структуру — следует указывать лишь изменяемые части. Например, блоке tabs
:
<Tabs>
<tab State="active">Yandex</tab>
<tab State="release">Lenta.ru</tab>
<newtab/>
</Tabs>
На самом же деле у каждого таба должен быть крестик, и раз его наличие обязательно, нет смысла это указывать каждый раз при разметке интерфейса. Тем не менее, чтобы крестик оказался в результирующем HTML-дереве, финальное BML-дерево должно быть соответствующим:
<Tabs>
<tab State="active">
<label>Yandex</label>
<close/>
</tab>
<tab State="release">
<label>Lenta.ru</label>
<close/>
</tab>
<newtab/>
</Tabs>
Преобразование BML-дерева именуется разверткой. Правило развертывания описываются в декларации полем expand
:
Beast.decl('tabs__tab', {
expand: function () {
this.append(
<label>{this.text()}</label>,
<close/>
)
}
})
Функция expand
выполняется в контексте объекта BemNode
. C каждым компонентом интерфейса связывается такой объект, речь о котором пойдет в следующем разделе; а пока будут упоминаться лишь некоторые из его методов.
В последнем примере используется метод append
, разобраться в механизме и мотивах появления которого важно для понимания сути и удобства развертывания. Поскольку развертывание — это преобразование BML-дерева, должны быть состояния дерева до изменений и после. Можно было бы преобразовывать одно и то же дерево, тогда какой-нибудь более сложный код функции expand
с несколькими вызовами append
выглядел бы так:
<Article>
<title>...</title>
<author>...<author>
<text>...</text>
<Article>
Beast.decl('article', {
expand: function () {
var title = this.get('title')
var text = this.get('text')
var author = this.get('author')
this.empty()
if (!this.mod('no-title')) {
this.append(title)
}
this.append(text, author)
}
})
Метод mod
возвращает значение модификатора. В данном случае, если нет модификатора no-title
, можно выводить заголовок. Метод empty
очищает содержимое компонента перед началом присоединения дочерних элементов. Вне зависимости от порядка входных данных, имя автора должено стоять в конце статьи.
Из-за необходимости очищать дерево для перестановки его элементов, приходится делать множество предварительных сохранений этих самых элементов. Второй способ — складывать нужный порядок в массив и вызывать append единожды в конце:
Beast.decl('article', {
expand: function () {
var content = []
if (!this.mod('no-title')) {
content.push(
this.get('title')
)
}
this.append(
this.get('text'),
this.get('author')
)
this.empty()
this.append(content)
}
})
Уже лучше. Теперь content
играет роль временного массива дочерних элементов, который в конце работы метода заменяет старый. Но с таким подходом каждый метод expand
будет традиционно начинаться с var content = []
и заканчиваться методами empty
и append(content)
. От этой рутины можно избавиться, если метод append
в контексте развертывания будет автоматически складывать свои аргументы в новый массив дочерних элементов, который после завершения метода expand
заменит собой старый массив. Поэтому, с учетом вышесказанного, результирующий код выглядит так:
Beast.decl('article', {
expand: function () {
if (!this.mod('no-title')) {
this.append(
this.get('title')
)
}
this.append(
this.get('text'),
this.get('author')
)
}
})
Также на этапе развертывания можно определить модификаторы и параметры компонента с их значениями по умолчанию. Такие определения полезны для автоспецификации API.
Beast.decl('tabs__tab', {
mod: {
state:'release'
},
param: {
url:''
},
expand: function () {
}
})
Развертывание производится от родителя к ребенку. На примере с интерфейсом браузера:
<Browser>
<head>
<Tabs>...</Tabs>
<Addressbar>...</Addressbar>
</head>
<content>
<Webview/>
</content>
</Browser>
Последовательность обхода: Browser, head, Tabs, Addressbar, content, Webview. Сделано это для того, чтобы всегда оставался шанс доуточнить содержимое и развернуть его тоже:
Beast
.decl('browser', {
expand: function () {
this.append(
this.get('head'),
this.get('content'),
<statusbar/>
)
}
})
.decl('browser__statusbar', {
expand: function () {
console.log('statusbar expand')
}
})
При обратном обходе дерева добавленный элемент statusbar
не раскроется — пришлось бы с его добавлением запускать новый обход в противоположную сторону.
После процедуры развертывания запускается процедура генерации DOM-дерева и его инициализации — чаще всего навешивание обработчиков событий.
Beast.decl('tabs__tab', {
domInit: function () {
this.on('click', function () {
this.mod('state', 'active')
})
}
})
Методы обработки событий имеют свое поле в декларации:
Beast.decl('tabs__tab', {
on: {
click: function () {
this.mod('state', 'active')
}
}
})
DOM-инициализация производится от ребенка к родителю, в отличии от развертывания. Сделано это для того, чтобы родитель мог вызывать DOM-методы своих детей — наиболее частый сценарий работы на этом этапе. А при обратном обходе вложенные узлы будут инициализироваться первыми.
### Наследование декларацийОдни компоненты могут расширять или доуточнять другие. Например, общий принцип работы табов — переключение между друг другом — можно отделить от внешнего вида и прочих частностей в отдельный блок abstract-tabs. Так все последующие реализации табов смогут наследовать это поведение и дополнять его своим: например, табы браузера могут не только переключаться, но и закрываться
Beast
.decl('abstract-tabs__tab', {
on: {
click: function () {
this.parentBlock().get('tab').forEach(function (tab) {
tab.mod('state', 'release')
})
this.mod('state', 'active')
}
}
})
/* ... */
.decl('browser-tabs__tab', {
inherits:'abstract-tabs__tab',
expand: function () {
this.append(this.text(), <close/>)
},
domInit: function () {
this.get('close').on('click', function () {
this.remove()
}.bind(this))
}
})
Помимо стандартного набора методов, полный список которых приведен в справочнике, в декларации можно указывать дополнительные. Например, клик по крестику вкладки браузера сначала должен проиграть анимацию закрытия, а только потом удалиться:
.decl('browser-tabs__tab', {
expand: function () {
this.append(this.text(), <close/>)
},
domInit: function () {
this.get('close').on('click', function () {
this.close()
}.bind(this))
},
close: function () {
jQuery(this.domElem()).fadeOut(100, function () {
this.remove()
}.bind(this))
}
})
## Итого
- Интерфейс представляется иерархией компонент двух типов: независимые блоки и зависимые элементы; иерархию описывает BML-разметка.
- Разметка компилируется в цепочку вложенных методов
Beast.node()
, которая и формирует дерево компонентов. - Разметка должна иметь единственный корневой компонент.
- Структура компонентов преобразуется и дополняется поведением через декларации.
- Структуру компонентов повторяет соответствующее DOM-дерево, где каждый компонент получает DOM-узел.
- DOM-узел хранит ссылку на свой компонент в свойстве
bemNode
. - Компоненты — это экземпляры класса BemNode.
- Функции деклараций выполняются в контексте соответствущего компонента.
- Модификация структуры компонентов производится только методами компонентов. На этапе инициализации и далее, когда за иерархией компонент закреплено DOM-дерево, изменения отражаются и на нем.
## СПРАВОЧНИК ### BemNode #### isBlock ():boolean Является ли компонент блоком. #### isElem ():boolean Является ли компонент элементом. #### selector ():string Получить селектор элемента `block` или `block__elem`. #### parentBlock ([node:BemNode]) [:BemNode] Получить или назначить родительский блок. Если компонент является блоком, он сам себе родительский блок. Если элемент имплементируется блоком (см. метод `implementWith`), `parentBlock` для блока вернет родительский блок элемента. #### parentNode ([node:BemNode]) [:BemNode] Получить или назначить родительский компонент. #### domNode ():DOMElement Получить соответствующий элемент DOM-дерева. #### defineMod (defaults:object) Объявить модификаторы и их значения по умолчанию.
this.defineMod({
state:'release',
type:'action',
size:'M'
})
this.defineParam({
url:'',
maxlength:20
})
Beast.decl('popup', {
onMod: {
state: {
active: function (isSilent) {
if (!isSilent) this.triggerWin('active')
}
}
},
expand: function () {
this.mod('state', 'active', true)
}
})
То же самое, записанное иначе:
Beast.decl('popup', {
expand: function () {
this.onMod('state', 'active', function (isSilent) {
if (!isSilent) this.triggerWin('active')
})
.mod('state', 'active', true)
}
})
Несколько за один вызов:
.decl('tabs__tab', {
expand: function () {
this.mod({
state:'release',
theme:'normal'
})
}
})
Несколько за один вызов:
.decl('tabs__tab', {
expand: function () {
this.param({
url:'#foo',
num:1
})
}
})
Несколько за один вызов:
.decl('tabs__tab', {
expand: function () {
this.domAttr({
src: this.text(),
width: 200
})
}
})
Назначить несколько за один вызов:
.decl('tabs__tab', {
expand: function () {
this.css({
display:'block',
cursor:'pointer'
})
}
})
Beast.decl('button', {
domInit: function () {
this.on('click', function () {})
.on('mouseup mousedown', function (e, data) {})
}
})
Beast.decl('popup', {
domInit: function () {
this.onWin('resize', function () {
this.updatePosition()
})
.onWin('popup:open', function (e) {
if (!e.currentTarget.bemNode === this) {
this.hide()
}
})
}
})
Названия событий общей шины от компонент автоматически предворяются селектором (родителького в случае с элементом) блока, чтобы исключить пересечение имен и сделать подписку более наглядной. В примере popup
подписывается на событие открытия себеподобных и закрывается.
Beast.decl('popup', {
expand: function () {
this.onMod('state', 'active', function (isSilent) {
if (!isSilent) this.triggerWin('active')
})
.mod('state', 'active', true)
}
})
Beast.decl('popup', {
domInit: function () {
this.onWin('popup:open', function () {...})
.triggerWin('open')
}
})
Beast.decl('button', {
expand: function () {
this.append('Найти')
.append(
<content>
<icon/>
<label>Найти</label>
<content>
)
.append(
'Найти',
<label>Найти</label>,
<content>
<icon/>
<label>Найти</label>
<content>
)
},
domInit: function () {
this.append(
'Найти',
<label>Найти</label>,
<content>
<icon/>
<label>Найти</label>
<content>
)
}
})
Beast.decl('tabs__tab', {
expand: function () {
this.replaceWith(
<Tab>{this.text()}</Tab>
)
}
})
Полезно для раскрытия элементов через блоки с наследованием поведения первых:
.decl('tab__close', {
expand: function () {
this.implementWith(
<Button icon="/assets/icons/close.svg"/>
)
},
on: {
click: function () {
console.log('Tab was closed')
}
}
})
/* ... */
.decl('button', {
on: {
click: function () {
console.log('Button was clicked')
}
}
})
По нажатию на крестик таба в консоль выведется:
> Button was clicked
> Tab was closed
Имплементирующий компонент наследует не только поведение, но и пользовательские методы. Также имплементирующий откликается на имя имплементируемого в методе get()
. Модификаторы тоже становятся общими.
<Browser>
<head>
<Tabs>
<tab State="active">Yandex</tab>
<tab State="release">Lenta.ru</tab>
<newtab/>
</Tabs>
<Addressbar>https://yandex.ru</Addressbar>
</head>
<content>
<Webview State="active" url="https://yandex.ru"/>
<Webview State="release" url="http://lenta.ru"/>
</content>
</Browser>
Метод использует имена узлов, а не их селекторы по той причине, что ему приходится нырять в списки детей на разную глубину. Дети на тот момент еще не развернуты и не инициализированы; и, значит, пока не знают ничего о родительском узле, из контекста развертки которого к ним обращаются и который еще может поменяться; а также не имеют содержимого, обещанного соответствующей декларацией.
Метод одинаково работает как в контексте развертывания, так и в инициализации. Результаты работы метода не кешируются.
Beast.decl('browser', {
expand: function () {
this.get() // все дети включая текст
this.get('/') // все дочерние компоненты
this.get('../Player')[0] // соседний компонент с именем Player
this.get('head')[0] // компонент шапки
this.get('head')[0].sayTrue() // true
this.get('head/Tabs/tab') // массив табов
this.get('head/Tabs/') // все дочерные компоненты Tabs
this.get('head/Tabs/tab', 'content/Webview') // массив табов и страниц
this.get('head/Tabs/tab')[0].mod('state') // значение атрибута State первого таба
this.get('head/Addressbar')[0].text() // текстовое содержимое адресной строки
},
domInit: function () {
this.get('head')[0].sayTrue() // true
}
})
Beast.decl('head', {
sayTrue: function () {
return true
}
})
.decl('tabs', {
onMod: {
state: {
uncommon: function () {...}
}
}
})
.decl('tabs__tab', {
mod: {
state:'release'
},
domInit: function () {
var uncommonState = this.mod('state') !== 'release'
this.parentBlock().afterDomInit(function () {
if (uncommonState) {
this.mod('state', 'uncommon')
}
})
}
})
Обработчики onMod назначаются при создании DOM-узла компонента — на момент DOM-инициализации tabs__tab
компонент tab
еще не имел своего DOM-узла.
Создает полную копию себя.
#### render (domNode:DOMElement) По большей части служебный метод. Инициирует рекурсивный процесс развертки и инициализации поведения компонента. В аргументе указывается родительский DOM-элемент для корневого компонента. Например, так выглядит создание компонента и привязка его к DOM дереву с последующей инициализацией.var button = <Button><label>Найти</label></Button>
button.render(document.body)
Beast.decl({
foo: {
on: {
click: function () {
console.log('foo click')
}
}
},
bar: {
inherits: 'foo',
on: {
click: function () {
this.inherited()
console.log('bar click')
}
}
}
})
// foo click
// bar click
### Beast.decl (selector:string, rules:object)
Создание декларации для компонентов, соответствующих селектору. Поля декларации:
#### inherits Наследование декларации. Пользовательские методы текущей декларации при совпадении имен с наследуемыми перекроют их.Beast.decl('browser-tabs', {
inherits: 'abstract-tabs' | ['abstract-tabs', 'common-tabs']
})
Beast.decl('Locale', {
abstract:true,
string: function () {
...
}
})
Beast.decl('browser', {
expand: function () {
this.append(
<head>{this.get('Tabs')}</head>,
<content>{this.get('Webview')}</content>
)
}
})
Поля деклараций, описываемые ниже: mod, param, tag — компилируется в методы, исполняемые в этом самом контексте.
Beast.decl('browser', {
mod: {
state:'active'
}
})
↓
Beast.decl('browser', {
expand: function () {
this.defineMod({state:'active'})
}
})
Beast.decl('button', {
mod: {
state:'release',
type:'normal'
}
})
Beast.decl('button', {
param: {
href:'',
maxlength:20
}
})
Beast.decl('link', {tag:'a'})
<Button>
<Link>
<label>Найти</label>
</Link>
<Button>
Beast.decl('link', {noElems:true})
<div class="button">
<div class="link">
<div class="button__label">Найти</div>
</div>
</div>
Beast.decl('tabs__tab', {
domInit: function () {
this.get('close')
.on('click', this.onCloseClick.bind(this))
}
})
В этом же контексте выполняются скомпилированные в методы поля: on, onWin, onMod.
#### on Декларативная форма метода BemNode::on().Beast.decl('tabs__tab', {
on: {
click: function () {
this.mod('state', 'active')
},
'mouseover mouseout': function () {
this.playMouseReaction()
}
}
})
Beast.decl('popup', {
onWin: {
resize: function () {
this.updatePosition()
},
'popup:open': function (e) {
if (!e.currentTarget.bemNode === this) {
this.hide()
}
}
}
})
Beast.decl('tabs__tab', {
onMod: {
state: {
active: function () {
this.parentBlock().get('tab').forEach(function (tab) {
if (tab !== this) {
tab.mod('state', 'release')
}
}.bind(this))
}
}
}
})
<Button Size="M" href="#foo">Перейти</Button>
↓
Beast.node('Button', {'Size':'M', 'href':'#foo'}, 'Перейти')
Служебный метод. Создает экземпляр класса BemNode. Эквивалент BML-разметки.
#### onReady (callback:function)Выполнит callback-функцию, когда загрузятся все стили и скрипты.
## Hello, world
Чтобы запустить элементарный проект, к html-странице нужно подключить файл библиотеки /src/beast-min.js
, подключить js-декларации блоков, CSS-стили и составить семантическое дерево интерфейса:
<html>
<head>
<meta charset="utf8">
<!-- Инструмент -->
<script src="beast-min.js"></script>
<!-- Декларации -->
<link type="bml" href="browser.bml"/>
<!-- Стили -->
<link type="text/css" rel="stylesheet" href="browser.css">
<!-- Дерево интерфейса -->
<script type="bml">
<Browser>
<head>
<Tabs>
<tab State="active">Yandex</tab>
<tab State="release">Lenta.ru</tab>
<newtab/>
</Tabs>
<Addressbar>https://yandex.ru</Addressbar>
</head>
<content>
<Webview State="active" url="https://yandex.ru"/>
<Webview State="release" url="http://lenta.ru"/>
</content>
</Browser>
</script>
</head>
</html>
Теги html
и head
писать необязательно. И следует помнить об атрибуте type="bml"
для тегов script
и link
; в первом случае он сообщит Beast, что нужна прекомпиляция кода, а во втором еще и подгрузка самого файла. Политика безопасности браузеров не позволяет получать доступ к содержимому, загруженному через <script src="...">
— поэтому используется универсальный тег <link>
.
Практические советы, механизмы оптимальной организации сложных связей между компонентами, принятый codestyle, разработка проекта с чистого листа — всё это описывает учебник Beast Practice.
Welcome home, good hunter.