├── .gitignore
├── README.md
├── faq
├── active_round.png
├── bem-vs-bevis.md
└── pseudo_shadow.png
├── how-to-make
├── backend-requests.md
├── css.md
├── dependencies.md
├── enb.md
├── i18n.md
├── modules.md
├── mvc-app.md
├── new-block.md
├── new-page.md
├── similar-block.md
├── tests.md
└── yblock.md
├── manual-for-beginner.md
├── manual-for-master.md
├── practice.md
└── practice
├── backend-requests.md
├── css.md
├── dependencies.md
├── enb.md
├── i18n.md
├── modules.md
├── mvc-app.md
├── new-block.md
├── new-page.md
├── oop.md
├── tests.md
└── yblock.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Быстрый старт
2 | ```
3 | git clone git@github.com:bevis-ui/bevis-stub.git your-project
4 | cd your-project
5 | make
6 | ```
7 | Откройте в браузере ```localhost:8080``` и скажите: «Привет, Бивис!»
8 |
9 | ## BEViS?
10 | Это `javascript` фреймворк для создания веб-сайтов. Не нужно знать ни одного серверного языка
11 | программирования, чтобы сверстать настоящий коммерческий большой сайт. Достаточно лишь небольших знаний
12 | о `HTML`, `CSS`, `Javascript`. Вот это и есть `BEViS`. Заинтересовались? :)
13 |
14 | Мы подготовили для вас
15 | [весёлые учебники](#%D0%92%D0%B5%D1%81%D1%91%D0%BB%D1%8B%D0%B5-%D1%83%D1%87%D0%B5%D0%B1%D0%BD%D0%B8%D0%BA%D0%B8),
16 | от которых (мы смеем надеяться) вы не сможете оторваться и ещё готовим [практические занятия](practice.md), на которых
17 | вместе с вами создадим проект и пощупаем все технологии.
18 |
19 | А для тех из вас, кто не хочет читать, а хочет только глазком заглянуть в референс, скоро напишем пишем серьёзные
20 | [справочные руководства](#%D0%A1%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D1%87%D0%BD%D1%8B%D0%B5-%D1%80%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%B0),
21 | в которых нет даже намёка на шутливость.
22 |
23 | Выбирайте, что вам по душе, и добро пожаловать :)
24 |
25 |
26 |
27 | ----
28 |
29 |
30 |
31 | ### Весёлые учебники
32 |
33 | > ##### «Про сложные темы невозможно говорить серьёзно.»
34 | _Андрей Кармацкий_
35 |
36 |
37 |
38 | Наши дети в школах страдают от недостатка _интересной_ учебной литературы, а мы страдаем от этого же на работе,
39 | когда руководитель даёт нам задание срочно освоить что-то новое.
40 |
41 | К сожалению, хорошие учебники для взрослых можно пересчитать по пальцам. Я знаю про
42 | [Git Magic](http://www-cs-students.stanford.edu/~blynn/gitmagic/index.html) и про
43 | [Why's Poignant Guide To Ruby](http://mislav.uniqpath.com/poignant-guide/). Я осмелюсь заявить, что наши весёлые
44 | учебники можно поставить с ними на одну полку — они нескучные, написаны простым русским языком, с аналогиями и
45 | понятными метафорами.
46 |
47 | Их два. Один расчитан на специалистов самого широкого профиля, то есть даже вообще неспециалистов, а второй
48 | написан для тех, кто уже понюхал в жизни пороху и знает, что такое MVC, шаблоны представления и какие сервера
49 | кроме Apache бывают на свете. Выбирайте по вкусу :)
50 |
51 | * [Учебник для новичков](manual-for-beginner.md)
52 | * [Учебник для старичков](manual-for-master.md)
53 |
54 | Между делом заведите себе гиковский блог на [Бивис-движке](http://github.com/bevis-ui/bevis-blog).
55 | Это займёт не больше двух минут, включая публикацию блога на бесплатный хостинг. Пишите статьи в маркдауне,
56 | забудьте о вордпрессе ;)
57 |
58 | Или посмотрите на одностраничное приложение, реализующее
59 | функциональность [TODO MVC](http://github.com/bevis-ui/bevis-todo).
60 |
61 | А ещё лучше, посетите наш практикум. Он не скучный, быстрый, понятный.
62 |
63 |
64 |
65 | #### Практикум
66 |
67 | _На вопросы, отмеченные звёздочкой, мы либо ещё не ответили, либо ответили не полностью._
68 |
69 | 1. [Создаём новую страницу проекта](practice/new-page.md)
70 | 2. [Добавляем на страницу новый блок](practice/new-block.md)
71 | 3. [Описываем зависимости между блоками](practice/dependencies.md)
72 | 4. [Пишем стили для блока](practice/css.md)
73 | 5. [Пишем `js`-поведение для блока](practice/yblock.md)
74 | 6. [Настраиваем локализацию в проекте](practice/i18n.md)
75 | 7. [Используем `MVC`-концепцию в `BEViS`-проекте](practice/mvc-app.md)
76 | 8. [Взаимодействуем с бекендом](practice/backend-requests.md) [*]
77 | 9. [Пишем тесты](practice/tests.md) [*]
78 |
79 |
80 |
81 | ----
82 |
83 |
84 |
85 | ### Справочные руководства
86 |
87 | > #####«Краткость — сестра таланта.»
88 | _Антон Павлович Чехов_
89 |
90 |
91 |
92 | #### Решение типовых задач
93 |
94 | _На вопросы, отмеченные звёздочкой, мы либо ещё не ответили, либо ответили не полностью._
95 |
96 | В этих документах мы кратко пересказываем то, что описано в соответствующих уроках практикума. Только суть без
97 | подробных объяснений.
98 |
99 | 1. [Новая страница](how-to-make/new-page.md) [*]
100 | 2. [Новый блок на странице](how-to-make/new-block.md) [*]
101 | 3. [Зависимости между блоками](how-to-make/dependencies.md) [*]
102 | 4. [`CSS`-стили блока](how-to-make/css.md) [*]
103 | 5. [`JS`-поведение блока](how-to-make/yblock.md) [*]
104 | 6. [Локализация проекта](how-to-make/i18n.md) [*]
105 | 7. [`MVC` в `BEViS`](how-to-make/mvc-app.md) [*]
106 | 8. [Взаимодействие с бекендом](how-to-make/backend-requests.md) [*]
107 | 9. [Тесты](how-to-make/tests.md) [*]
108 |
109 | #### О технологиях
110 | 1. [bt](https://github.com/enb-make/bt)
111 | 2. [ENB](https://github.com/enb-make/enb)
112 | 3. [Ymaps Modules](how-to-make/modules.md)
113 | 4. [Stylus](http://learnboost.github.io/stylus/)
114 |
115 | #### Отвечаем на частые вопросы
116 | 1. [Зачем нужен BEViS, если уже есть BEM?](faq/bem-vs-bevis.md)
117 | 2. [Я сверстал блок. Теперь нужен такой же, только чуть-чуть другой. Как это сделать?](how-to-make/similar-block.md) [*]
118 |
119 |
--------------------------------------------------------------------------------
/faq/active_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bevis-ui/docs/8d39012843de0261dbb3861ffa7ff9a5f9e16529/faq/active_round.png
--------------------------------------------------------------------------------
/faq/pseudo_shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bevis-ui/docs/8d39012843de0261dbb3861ffa7ff9a5f9e16529/faq/pseudo_shadow.png
--------------------------------------------------------------------------------
/how-to-make/backend-requests.md:
--------------------------------------------------------------------------------
1 | # Справочник: Взаимодействие с бекендом
2 |
3 | в разработке
4 |
--------------------------------------------------------------------------------
/how-to-make/css.md:
--------------------------------------------------------------------------------
1 | # Справочник: `CSS`-стили блока
2 |
3 | в разработке
4 |
--------------------------------------------------------------------------------
/how-to-make/dependencies.md:
--------------------------------------------------------------------------------
1 | # Справочник: Зависимости
2 |
3 | в разработке
4 |
--------------------------------------------------------------------------------
/how-to-make/enb.md:
--------------------------------------------------------------------------------
1 | # Справочник: Настраиваем сборку проекта
2 |
3 | в разработке
4 |
--------------------------------------------------------------------------------
/how-to-make/i18n.md:
--------------------------------------------------------------------------------
1 | # Справочник: Локализация проекта
2 |
3 | в разработке
4 |
5 |
6 |
--------------------------------------------------------------------------------
/how-to-make/modules.md:
--------------------------------------------------------------------------------
1 | # Модульная система YMaps Modules
2 |
3 | Что такое Модульная система? Зачем и для кого? Как этим пользоваться?
4 |
5 | На эти вопросы я давно хотел получить ответы, но стеснялся их задать. Если вы такой же робкий, этот документ для вас :)
6 |
7 | Что это?
8 | --------
9 | Вы web-разработчик, пишете сайты; на страницах верстаете формы с контролами,
10 | различные компоненты, большие и маленькие; для большинства из них
11 | пишете клиентский `js`-код. Вот и продолжайте это делать. Пока у вас нет своего клиентского `js`-кода,
12 | модульная система вам не нужна. Никому не нужен пустой фантик без конфеты внутри.
13 |
14 | Модуль - это фантик, в который вы заворачиваете конфету. На фантике написано имя конфеты и больше ничего. Оберните
15 | код пяти своих контролов каждый в свой отдельный фантик, у вас получится 5 модулей — система из пяти **ваших**
16 | модулей. Конфеты ссыпают в корзинку для сладостей, откуда конфеты удобно таскать детям. Модульная система и есть
17 | такая корзинка.
18 |
19 | Зачем это и для кого?
20 | --------------------
21 | Как вы связываете `js`-компоненты между собой? Например, вы объявляете на странице блок с формой авторизации,
22 | пишете для неё клиентский `js`-код, в котором описываете поведение формы: если нажали на кнопку сабмита — отправь
23 | авторизационные данные аяксом, а не через стандартный `form.submit()`.
24 |
25 | И предположим, в вашем проекте уже есть специальный компонент "кнопка", в котором реализовано всё поведение кнопки,
26 | например, "если меня нажали, выкинь вверх некое событие, чтобы форма могла это событие услышать и отреагировать нужным
27 | образом".
28 |
29 | То есть, ваша "форма" должна уметь найти кнопку и слушать на ней событие клика.
30 |
31 | Вопрос: а как вы определяете, что кнопка **уже готова** взаимодействовать с вашей формой? Наверное, в `js`-файле вы
32 | сначала описываете функциональность для кнопки, а ниже - для формы? А если js-код кнопки и формы лежат в разных
33 | файлах? Тогда вы так настраиваете сборку финального `js`-файла, чтобы всё равно код формы оказался описан ниже
34 | кода кнопки?
35 |
36 | Модульная система избавляет нас от этих трудностей. Она избавляет нас от головной боли отслеживать зависимости между
37 | компонентами, поддерживать руками эти зависимости в актуальном состоянии.
38 |
39 | Если вы оформляете форму в виде `ymaps-модуля`, вы просто декларируете зависимость от кнопки, и знаете,
40 | что код формы будет активирован _только тогда_, когда активирован код кнопки. Вам не нужно самому отслеживать
41 | готовность ваших компонентов взаимодействовать друг с другом. Это делает за вас модульная система.
42 |
43 |
44 | Как этим пользоваться?
45 | ---------------------
46 | Это болванка `ymaps-модуля`:
47 |
48 | ````javascript
49 | modules.define(
50 | 'A',
51 | ['B', 'C'],
52 | function(provide, b, c) {
53 | var a = {};
54 |
55 | provide(a);
56 | }
57 | );
58 | ````
59 |
60 | В этом примере ваш собственный код - единственная строка:
61 | ````javascript
62 | var a = {};
63 | ````
64 |
65 | В реальности, конечно, вместо этой строчки вы напишете намного больше кода, но для знакомства с модульной системой
66 | этого достаточно.
67 |
68 | Всё остальное кроме этой строки и есть модуль. Модуль - это своего рода обёртка, фантик — несколько специальных
69 | инструкций, которые и делают из вашего кода модуль. Внутрь фантика вы складываете нужные ингредиенты (зависимости от
70 | других модулей), перемешиваете, как вам хочется (пишете код своего компонента), фантик скручиваете и втыкаете
71 | в бок зубочистку-флажок с именем модуля. Вы сделали модуль `А`.
72 |
73 | Давайте посмотрим на конфету внимательно.
74 |
75 | Так объявляется модуль. Позовите метод `define()`
76 | ````javascript
77 | modules.define(
78 | );
79 | ````
80 |
81 | Скажите модулю: "Тебя зовут `А`"
82 | ````javascript
83 | modules.define(
84 | 'A'
85 | );
86 | ````
87 |
88 | Потом добавьте: "Вообще-то ты зависишь от двух модулей, их зовут`В` и `С`"
89 | ````javascript
90 | modules.define(
91 | 'A',
92 | ['B', 'C'],
93 | );
94 | ````
95 |
96 | Опишите код модуля в анонимной функции. В него параметрами `b` и `c` придёт код модулей `B` и `С`, и вы сможете
97 | использовать их внутри модуля.
98 | ````javascript
99 | modules.define(
100 | 'A',
101 | ['B', 'C'],
102 | function(provide, b, c) {
103 |
104 | }
105 | );
106 | ````
107 |
108 | Чтобы другие модули могли использовать ваш модуль, пусть он громко произнесёт вслух:
109 | "Модуль `А` - это я!". Это делается вызовом функции `provide(а)`, в которую аргументом передаётся ссылка на ваш класс
110 | или объект, то есть на сам программный код вашего модуля.
111 | ````javascript
112 | modules.define(
113 | 'A',
114 | ['B', 'C'],
115 | function(provide, b, c) {
116 | var a = {};
117 |
118 | provide(a);
119 | }
120 | );
121 | ````
122 |
123 | И, наконец, напишите свой компонент. Пусть это будет что-нибудь хорошее ;)
124 | ````javascript
125 | modules.define(
126 | 'A',
127 | ['B', 'C'],
128 | function(provide, b, c) {
129 | var a = 'Yandex is a good company';
130 |
131 | provide(a);
132 | }
133 | );
134 | ````
135 |
136 | Всё. Сложности закончились :)
137 |
138 | А вот так, к примеру, может выглядеть код модуля, отвечающий за форму логина. Создадим фантик,
139 | позовём в него кнопку и что-нибудь запрограммируем внутри:
140 | ````javascript
141 | modules.define(
142 | 'form',
143 | ['button'],
144 | function(provide, button) {
145 | var form = getElementById('my-form');
146 | form.on('submit', onSubmited);
147 |
148 | function onSubmited() {
149 | if ($(button).css('disabled')) {
150 | return false;
151 | }
152 | form.submit();
153 | }
154 |
155 | provide(form);
156 | }
157 | );
158 | ````
159 |
160 | Это выдуманный пример, скорее всего он нерабочий, но для иллюстрации годится. В теле анонимной функции мы программируем
161 | поведение формы в зависимости от того, активна или неактивна кнопка. А после этого методом `provide(form)`
162 | выбрасываем вверх флаг: "Форма — это я". Теперь и этот модуль может быть вызван другими модулями. Метод `provide` -
163 | это выкинутая вверх рука и всемирно известное: "Свободная касса!"
164 |
165 | Возвращаясь к аналогии с конфетой, фантик — это метод `define()` с анонимной функцией внутри. В фантик воткнута
166 | зубочистка, за которую конфетку удобно взять — это метод `provide()`. Модуль независим и работоспособен, потому что
167 | он будет запущен только после того, как все зависимости будут предоставлены (правильные программисты используют
168 | термины "разрезолвлены" или "разрешены") внутрь модуля.
169 |
170 | Осталось сказать, что зависимости резолвятся асинхронно, дерево зависимостей строится в рантайме,
171 | модули можно переопределять и доопределять.
172 |
173 | Если захотите заглянуть в репозиторий модульной системы, проследуйте сюда:
174 | [https://github.com/ymaps/modules](https://github.com/ymaps/modules/blob/master/README.ru.md), и возвращайтесь к
175 | документации про `BEViS`, я вас там жду.
176 |
--------------------------------------------------------------------------------
/how-to-make/mvc-app.md:
--------------------------------------------------------------------------------
1 | # Справочник: `MVC` в `BEViS`
2 |
3 | в разработке
4 |
--------------------------------------------------------------------------------
/how-to-make/new-block.md:
--------------------------------------------------------------------------------
1 | # Справочник: Новый блок
2 |
3 | в разработке
4 |
--------------------------------------------------------------------------------
/how-to-make/new-page.md:
--------------------------------------------------------------------------------
1 | # Справочник: Новая страница
2 |
3 | в разработке
4 |
--------------------------------------------------------------------------------
/how-to-make/similar-block.md:
--------------------------------------------------------------------------------
1 | в разработке [немного информации из faq](https://github.com/bevis-ui/docs/blob/master/faq/bem-vs-bevis.md#%D0%9D%D0%B5-%D1%82%D0%BE%D1%82-%D0%B6%D0%B5-%D1%81%D0%B0%D0%BC%D1%8B%D0%B9-%D0%B1%D0%BB%D0%BE%D0%BA-%D0%B0-%D0%BD%D0%B5%D1%87%D1%82%D0%BE-%D0%B8%D0%BD%D0%BE%D0%B5)
2 |
--------------------------------------------------------------------------------
/how-to-make/tests.md:
--------------------------------------------------------------------------------
1 | # Справочник: Тесты
2 |
3 | в разработке
4 |
--------------------------------------------------------------------------------
/how-to-make/yblock.md:
--------------------------------------------------------------------------------
1 | # Справочник: `JS`-поведение блока
2 |
3 | в разработке
4 |
5 |
--------------------------------------------------------------------------------
/manual-for-master.md:
--------------------------------------------------------------------------------
1 | #### Предисловие
2 |
3 | Методология ли это? Я имею в виду `BEViS`. Методология? Ну, можно и так, если очень хочется. Но мы так не считаем.
4 | «Методология» — затёртое до дыр понятие с десятком различных определений. Попробуй пойми, что имеет в виду автор WEB-фреймворка,
5 | когда называет свой проект методологией: «Учение о методах и средствах познания мира», «Учение об организации абстрактной
6 | деятельности» или вообще «Систему принципов и подходов научной исследовательской деятельности»?
7 | Методология сейчас — модное наукообразное слово, которое ничего не помогает понять, а только вводит в заблуждение. Buzzword.
8 |
9 | _Так что, нет, `BEViS` — не методология._
10 |
11 | `BEViS` — эволюционная ветвь вёрстки `Абсолютно Независимыми Блоками`. Вы слышали о ней? Виталий Харисов, Яндекс, 2009 год — слышали?
12 | Мы переосмыслили `АНБ` и поняли, как эту концепцию можно улучшить, как можно её упростить. Бивис в первую очередь — это соглашения об именовании тегов
13 | и `CSS`-классов, о выделении логических частей внутри вашего сайта. Это рекомендации о том,
14 | как эффективно управлять кодом вашего проекта. Эти рекомендации призваны упорядочить хаос из тегов,
15 | стилей и скриптов, сделать ваш код стабильным, поддерживаемым и расширяемым.
16 |
17 | `BEViS — это АНБ, но со строгим API`
18 |
19 | Мы, разработчики `BEViS`, предлагаем вам думать о нашем продукте, так же, как вы думаете
20 | о `Webkit`-е, когда изредка вспоминаете, что `Webkit` — это вообще-то форк почившего в бозе `KHTML`,
21 | и даже не столько форк, сколько его эволюционная ветвь, то есть нечто большее, чем был `KHTML` изначально,
22 | нечто лучшее. И смотрите, ещё какой нюанс. Про `Webkit` сейчас знают все. А кто из вас вспоминает про `KHTML`? А про `АНБ`? ;)
23 |
24 | Но Бивис — это не только какие-то бестелесные советы и рекомендации. Это ещё и готовые инструменты,
25 | с которыми сверстать полноценный динамический сайт может даже подросток. Для этого не нужно учить `PHP`, `Ruby`
26 | или `Python`. Всё, что нужно для разработки полноценных сайтов на Бивисе, вы скорее всего знаете и так — `HTML`, `CSS`,
27 | `Javascript`. Скажите, заманчиво? ;)
28 |
29 | В добрый путь? :)
30 |
31 | ## Приложение
32 |
33 | Как работает обычное веб-приложение? Это зависит от языка, но схематично это выглядит примерно у всех одинаково:
34 |
35 |
36 |
37 | Запрос из браузера попадает на некий `HTTP`-сервер. Это может быть `Apache` или `Nginx` или любой другой.
38 | Запрос направляется в контроллер — программу, написанную, например, на `PHP` или на `Python`, которая
39 | получает данные из бекенда (из `MySql` или серванта), и передаёт в шаблоны представления, которые и генерят конечный `HTML`.
40 |
41 | Эта схема известна каждому. Ну так мы её и не меняли. Мы поменяли технологии и шаблонизатор.
42 |
43 |
44 |
45 | Вместо `Apache` или `Nginx` у нас `Node.js`. Вместо `PHP` мы пишем программу на серверном `JS`, в которой обращаемся в сервант за данными,
46 | накладываем на них шаблоны представления, которые тоже написаны на серверном `JS`, и отдаём пользователю конечный `html`.
47 |
48 | Видите — схема та же. Только всё на яваскрипте, на серверном яваскрипте.
49 |
50 | ----
51 |
52 | Зачем изобретать новый велосипед, если есть `Smarty`, есть `Django`, есть `XSLT`, в конце концов?
53 |
54 | Две причины: простота и асинхронность. Простота в том, что сверстать приложение может любой школьник, который умеет на `HTML`/`CSS`/`JS`
55 | Ему не нужно учить новый язык программирования — ни `PHP`, ни `Ruby`, ни `Python`. Что ещё учить не надо? `Perl`, `Java`, `C#`, `XSL`, продолжайте.
56 | Я понимаю, что у каждого языка свои преимущества в определённых ситуациях. Но мы и не говорим, что они не нужны. Мы говорим,
57 | что типовые задачи веб-разработки можно успешно делать на серверном `JS`, который мы уже знаем.
58 |
59 | И вторая причина — асинхронность. Для больших проектов важна скорость. Да и просто это модно, а мы ребята модные :)
60 |
61 | Иными словами, мы захотели обрести полный контроль над фронтендом. Самое время! Есть все возможности!
62 |
63 | ----
64 |
65 | Как выглядит наш контроллер `index.page.js?`
66 | Это набор `JS`-команд. Так как мы пишем на `Node.js`, наш яваскрипт завернут в `Node.js` модули. И выглядит это примерно так:
67 |
68 | ```javascript
69 | module.exports = function (pages) {
70 |
71 | pages.declare('index', function () {
72 | return [
73 | { block: 'header' },
74 | { block: 'authorization' }
75 | ];
76 | });
77 |
78 | };
79 | ```
80 | Если вы на `Node.js` не писали, не пугайтесь, не обращайте внимания на первую строку. Относитесь к ней, как к обвязке, без которой нельзя.
81 | Всё интересное происходит внутри. А внутри обычный `JS`. Видите вызов функции `pages.declare()`?
82 |
83 | В бивис-приложении есть объект `pages`, у которого есть метод `declare`.
84 | В нём мы декларируем, что `index`-страница будет состоять из двух компонентов — из шапки и формы авторизации.
85 | Выражаясь языком юристов, это «декларация о намерениях».
86 |
87 | Это абсолютно валидный `JSON`. Совершенно обычный `JSON`. Бивис создавался, как фреймворк, который использует валидные нативные конструкции.
88 | В этом `JSON` есть только одно служебное поле, оно называется `block`. Остальные поля можно называть как бог на душу положит.
89 |
90 | Почему блок? Зачем какой-то `JSON`? И что оно вообще такое?
91 |
92 | Подойдите вплотную к любой картине. Подойдите так близко, чтобы коснуться её носом. Что-то видно?
93 | Хаос из цветных мазочков. Ни черта не разобрать.
94 |
95 |
96 |
97 | А теперь отойдите на несколько шагов и снова посмотрите на картину.
98 | О! Так это же Шишкин Иван Иванович с его знаменитыми мишками в сосновом бору.
99 |
100 |
101 |
102 | И с вёрсткой так же. Когда мы получаем эскиз сайта от дизайнера, мы смотрим на него и
103 | видим не теги, которые будет в `HTML`-файле. Мы видим какие-то абстракции. Ага — это будет шапка, это подвал, а тут форма авторизации.
104 |
105 | Вы тоже, скорее всего, так делаете. Кто-то из вас называет такие абстракции модулями, кто-то компонентами или контролами.
106 | Мы их называем `блоками`, потому что мы из Яндекса, а там родилась концепция верстки абсолютно независимыми блоками,
107 | в которой впервые прозвучало слово `блок`. Ну и пусть, хороший же термин :)
108 |
109 | А если вы не верстальщик, а настоящий программист, то вам станет яснее, когда я скажу, что блоком мы называем
110 | примерно такую же абстракцию, как паттерн «Модуль» в `JS`.
111 |
112 | Эдди Османи [говорит](http://largescalejs.ru/module-pattern/):
113 | > «Модуль» — это популярная реализация паттерна, инкапсулирующего приватную информацию, состояние и структуру…
114 |
115 |
116 | ## Описание страницы обязано быть простым
117 |
118 | Бивис-блок — это очень простая декларация на `JSON`. Простота — это очень важно.
119 | Чем предмет проще, тем легче им пользоваться. Тем больше удовольствия от использования.
120 |
121 | Бабушке моей жены восемьдесят девять лет. Она не любит покупной хлеб, печёт сама. Мы подарили бабушке хлебопечку, чтобы ей было легче.
122 |
123 | Но она не пользуется!
124 |
125 | Оказалось, пожилому человеку трудно запомнить
126 | в какой последовательности какие кнопки нажимать — из инструкции старому человеку это
127 | непонятно. Она по-прежнему пользуется газовой духовкой — куда проще — открыл дверцу, сунул тесто, закрыл дверцу.
128 |
129 | Какой веб-фреймворк для верстки ни возьми, он в какой-то степени напоминает хлебопечку или пульт от телевизора.
130 | Вы, кстати, обращали внимание, какими кнопками на пульте пользуется ваша бабушка? Только тремя: включить, сделать громче и переключить канал.
131 |
132 | Мы с Маратом люди пожилые. И в отделе у нас работают ребята, которым уже за тридцать, а кому-то и под сорок, как мне :)
133 | Нам это всё надоело. Надоело описывать страницу тегами, размечать их кастомными атрибутами. Это неоправданно сложно.
134 | Слишком много суеты, слишком мало смысла.
135 |
136 | Поэтому вот так, мы решили, должно выглядеть описание страницы. Максимально просто, максимально плоско.
137 | ```javascript
138 | [
139 | { block: 'header' },
140 | { block: 'authorization' }
141 | ]
142 | ```
143 |
144 | Здесь сказано, что на странице будет два блока — шапка и форма авторзации.
145 | Здесь нет ни одного тега, ни одного атрибута. Но такого описания достаточно, чтобы Бивис сгенерил полноценный
146 | развесистый `HTML`, например такой:
147 |
148 | ```
149 |
159 |
169 | ```
170 |
171 | Как мы этого добиваемся? Главное, что мы сделали — приняли закон, главенствующий приницип Бивиса.
172 |
173 | ####Чтобы пользоваться блоком, достаточно одного имени
174 |
175 | Чтобы на странице оказался блок, о нём нужно знать только его имя и больше ничего. Вот… вообще ничего.
176 |
177 | И эта та вещь в Бивисе, которой мы гордимся-гордимся.
178 |
179 | И это очень похоже на реальную жизнь. Ведь,
180 | когда я хочу позвать к себе в гости Марата, я делаю это очень просто — я зову его по имени и говорю: «Марат,
181 | а приходи к нам в гости сегодня, жена яблочный пирог испечёт, посидим». А у Марата жена и двое детей,
182 | к примеру. Я же не зову отдельно Марата, отдельно его жену и каждого ребенка по отдельности. Нет. Только его,
183 | а уж он сам приведёт всё своё семейство, будьте уверены.
184 | ```javascript
185 | {
186 | друг: 'Марат'
187 | }
188 | ```
189 |
190 | И с блоком так же. Когда я хочу, чтобы на станице появился какой-то блок, Я НЕ ХОЧУ думать, о том, из каких тегов и атрибутов он внутри устроен.
191 |
192 | Ну, в самом деле, я когда в ресторан прихожу, и заказываю солянку, я же не даю указаний официанту: «Так, дорогуша, в солянке должно быть четыре кусочка картошки, 50 граммов салями,
193 | мелко нарезанный солёный огурчик…» Нет. Я просто заказываю солянку. Ну, максимум, могут предложить: «Вам со сметаной?» Да, конечно, мне со сметаной:
194 |
195 | ```javascript
196 | {
197 | суп: 'солянка',
198 | сметанаНужна: 'да'
199 | }
200 | ```
201 |
202 | То есть могут ещё задать какой-то дополнительный параметр. Но при этом, обратите внимание, и без сметаны суп можно есть. Просто с ней
203 | возникает новый вкус.
204 |
205 | Командир в окопе кричит сержанту: «Сержант Петренко, прикрыть правый фланг!»
206 |
207 | ```javascript
208 | {
209 | сержант: 'Петренко',
210 | прикрыть: 'правый фланг'
211 | }
212 | ```
213 |
214 | Всё! Командир приказал — Петренко сделал. И пусть Петренко сам решает, как это делать — то ли вести самому автоматный огонь
215 | в указанном направлении, то ли гранатами точку подавлять, то ли других бойцов послать штурмом фланг занять. Командиру
216 | какое дело до этих деталей вообще?
217 |
218 | Ну что, похоже на правду?
219 |
220 | ```javascript
221 | {
222 | block: 'header',
223 | showSearch: true
224 | }
225 | ```
226 |
227 | Пусть блоки сами решают, в какой `HTML` они должны превратиться, чтобы лучшим образом выполнить свою задачу.
228 | Как они устроены внутри, я как пользователь блока, думать не хочу.
229 |
230 | И за это я люблю Бивис. Создавать страницы из готовых блоков никогда ещё не было так просто.
231 |
232 | ####Блок — структура-представление-поведение
233 |
234 | Да и сами блоки создавать тоже просто, потому что это очень жизненная абстракция.
235 |
236 | Любое изобретение человека существует долго только в том случае, когда оно не противоречит законам природы.
237 |
238 | Квадратное колесо, например, не прижилось, хотя человек пытался наладить его промышленный выпуск. Пытался. А почему не попытаться-то?
239 | Квадратное легче производить, его удобнее транспортировать к точкам продажи. Но квадратное не может катиться. Это закон физической природы.
240 |
241 | Всё, что создано человеком и смогло сохраниться веками — это то, что следует законам природы.
242 |
243 | Блок, как божья тварь, состоит из трёх начал. Чтобы было понятнее, представьте меня в виде модуля, в виде блока:
244 |
245 | 1. У меня есть скелет, мышцы и внутренние органы. Это моя структура. У блока эту роль выполняет `HTML`-разметка.
246 |
247 | 2. У меня есть какой-то рост, вес, цвет кожи и выражение лица. Это мой внешний вид. У блока эту роль выполняют `CSS`-стили.
248 |
249 | 3. Я сейчас размахиваю руками и говорю с вами со сцены. Это моё поведение. Блок тоже умеет как-то взаимодействовать с пользователем на странице.
250 |
251 | Меня можно позвать в гости, меня можно накормить солянкой, можно отослать меня в окоп.
252 | Но при этом внутри себя я очень сложный. Кому-то пришлось потрудиться, чтобы я был таким, как есть.
253 |
254 | И разработчику блока тоже нужно потрудиться. Чтобы мне в ресторане принесли солянку, нужно чтобы кто-то заранее её сварил, правильно?
255 |
256 | Если я приду домой, и как в ресторане заявлю жене, что хочу на ужин борщ с фасолью и сметаной, знаете, что я услышу в ответ?
257 |
258 | — Легко! Картошку почисть!
259 |
260 | И я, кстати, не против. Жене по хозяйству нужно помогать. Я в этом убеждён твёрдо.
261 |
262 | Чтобы в проекте появился блок, который легко позвать на страницу, нужно этот блок создать — его структуру, внешний вид и поведение. В Бивисе это делается легко.
263 |
264 | ### Императивные шаблоны
265 |
266 | Как мы генерим `HTML` в смарти, джанго и других `MVC`?
267 |
268 | Мы пишем шаблоны, которые выглядят, как `HTML`-код, в который точечно добавляются данные. Как будто шприцом обкалываем:
269 | ```html
270 |
Уважаемый {{ person }}
271 |
Ваш заказ от {{ date:"F j, Y" }} принят на обработку.
272 |
Пожалуйста, убедитесь, что всё выбрано верно:
273 |
274 |
275 | {% for item in item_list %}
276 |
{{ item }}
277 | {% endfor %}
278 |
279 |
280 | {% if ordered_warranty %}
281 |
Гарантия - 12 месяцев.
282 | {% else %}
283 |
Со всеми неполадками обращайтесь в наш сервисный центр.
284 | {% endif %}
285 | ```
286 |
287 | Я напомню, шаблоны эти пишутся вот в этом участке схемы.
288 |
289 |
290 |
291 | И в случае с `PHP/Smarty` пишутся в файле с расширением `*.tpl.php`
292 |
293 |
294 |
295 | В бивисе мы шаблоны пишем в этом же месте схемы. Отличия только в том, что мы пишем шаблоны в файлах с расширением `*.bt.js`
296 | и шаблоны пишем не такие, а другие. Декларативные.
297 |
298 | ### Декларативные шаблоны
299 |
300 | На `XSLT` писали? `XML` в `HTML` трансформировали? Тогда вы нас понимаете — шаблоны как в `XSLT`
301 | А кто не писал на `XSL`, сейчас поймёте. Это легко.
302 |
303 | Что это?
304 |
305 | ```css
306 | h1 {
307 | color: red;
308 | }
309 | ```
310 |
311 | Даже школьник ответит: «Это `CSS`-селектор». Ну тогда поздравляем, вы умеете писать декларативные шаблоны.
312 |
313 | `CSS`-селекторы — это пример самых настоящих декларативных шаблонов. Заголовок станет красным только тогда, когда он есть на странице. Если h1 на
314 | странице нет, то и красить нечего, правда?
315 |
316 | В этом вся соль декларативных языков программирования. Вместо инъекций шприцем (как в смарти-шаблонах) — только матчеры,
317 | которые применяются к данным, если они есть. Это как горчичники — если есть куда их приложить — доктор их обязательно приложит.
318 | Горчичники заматчатся или не заматчатся.
319 |
320 | Предположим, наш контроллер выглядит так — на странице мы хотим видеть одну только шапку. Больше никаких блоков.
321 |
322 | ```javascript
323 | module.exports = function (pages) {
324 |
325 | pages.declare('index', function () {
326 | return {
327 | block: 'header'
328 | }
329 | });
330 | };
331 | ```
332 |
333 | Фактически, мы видим декларацию только одного блока. Не смотрите на всю мишуру вокруг, сконцентрируйтесь только на этом `JSON`:
334 |
335 | ```javascript
336 | {
337 | block: 'header'
338 | }
339 | ```
340 |
341 | Мы хотим, чтобы из этого `JSON`-а на странице появился такой `HTML`:
342 | ```html
343 |
344 | ```
345 |
346 | Декларативный шаблон мог бы выглядеть почти, как в `CSS`
347 |
348 | ```javascript
349 | header {
350 | tag: div;
351 | }
352 | ```
353 |
354 | Этот шаблон бы читался так: если на странице объявлен блок header, вылей для него тег div.
355 | В реальности, мы почти так и пишем, только на чистом валидном `JS`:
356 |
357 | Это шаблон на Бивисе:
358 | ```javascript
359 | bt.match('header', function (ctx) {
360 | ctx.setTag('div');
361 | });
362 | ```
363 |
364 | Похоже на это, правда?
365 | ```javascript
366 | header {
367 | tag: div;
368 | }
369 | ```
370 |
371 | Шаблон записывается в виде функции `match`, в которую мы первым параметром передаём имя блока.
372 | А вторым аргументом пишем функцию, внутрь которой приходит исходный `btjson` в переменной `ctx`
373 | А в функции сказано — создай тег `div`
374 |
375 | Функция `match` — это метод шаблонизатора `bt`. `bt` — это и есть повар в ресторане или ваша жена на кухне.
376 | У шаблонизатора прекрасная документация, которую поймёт даже школьник, почитайте.
377 |
378 | Так что же такое «матчинг», наложение шаблонов?
379 |
380 | В `CSS` такой шаблон вы знаете под именем «селектор» (от английского to select), потому что селектор как бы
381 | выбирает блоки, к которым нужно применить стилевые свойства, а в других декларативных языках эта же
382 | декларация зовётся «шаблоном» (трудно сказать почему). И ещё говорят, что шаблон матчится на
383 | блок (от английского to match, что переводится, как «подбирать», «приводить в соответствие»).
384 | Кстати to select имеет те же самые значения, что и to match. То есть это равнозначные слова с одинаковым смыслом :)
385 |
386 | Матчинг — это и есть селекция, выбор, проверка совпадения условий в селекторе (матчере).
387 | То же самое, что вы делаете, когда пишете `CSS`-стили.
388 |
389 | Только эти шаблоны вы пишете не в `CSS`-файле, а в `index.bt.js`.
390 | Посмотрите ещё раз на схему, чтобы сориентироваться.
391 |
392 |
393 |
394 | А теперь вернёмся к шаблону
395 | ```javascript
396 | bt.match('header', function (ctx) {
397 | ctx.setTag('div');
398 | });
399 | ```
400 |
401 | Он довольно бесполезный. Чтобы вылить тег, столько всего пришлось написать.
402 | Давайте усложнять. Я хочу, чтобы внутри шапки появился заголовок.
403 |
404 | Я добавил комментарии в код, чтобы вы всё поняли, а я помолчу.
405 |
406 | ```javascript
407 | bt.match('header', function (ctx) {
408 | ctx.setTag('div');
409 |
410 | // Генерим внутри шапки новый btjson и устанавливаем его, как содержимое шапки
411 | ctx.setContent({
412 | elem: 'title', // указываем элемент title в качестве содержимого
413 | text: 'CodeFest 2014' // произвольный текст для заголовка
414 | });
415 | });
416 |
417 | // регистрируем матчер для элемента title блока header
418 | bt.match('header__title', function (ctx) {
419 | ctx.setTag('h1');
420 |
421 | // Получаем параметр text
422 | var text = ctx.getParam('text');
423 |
424 | // задаём содержимое заголовку
425 | ctx.setContent(text);
426 | });
427 | ```
428 |
429 | Результат:
430 | ```html
431 |
432 |
CodeFest 2014
433 |
434 | ```
435 |
436 | Но это синтетический пример.
437 |
438 | А вот вам живое демо.
439 |
440 | [слайд](http://bevis-ui.github.io/bevis-and-bt-speech/?full#28)
441 |
442 | Поиграйтесь и понаблюдайте, как наличие того или иного параметра влияет на внешний вид блока.
443 | Это возможно благодаря простому вызову блока и декларативным шаблонам. Конечно, этого же можно добиться и с помощью императивных
444 | шаблонов. И тут возникает вопрос: «Императивные шаблоны Смарти и Джанго привычные, понятные, их понимают все.
445 | Почему мы не используем их?»
446 |
447 | #### Чем декларативные шаблоны лучше?
448 |
449 | Императивные шаблоны остаются понятными, пока их немного.
450 | Когда проект разрастается, все больше и больше визуальных фрагментов переиспользуются, тогда шаблоны становятся сложными, запутанными и многоуровневыми.
451 |
452 | В такой ситуации BT-шаблоны позволяют сохранять сложность на одном уровне.
453 | Более того, BT-шаблоны позволяют нам иметь явный и понятный API каждого фрагмента страницы.
454 |
455 | Наш подход хорош тем, что на странице нет описания того, КАК должен выглядеть блок, а только само указание, что «здесь есть такой-то блок и
456 | у него такие-то параметры». Это же прям как в настоящем программировании — вызов класса с передачей в него параметров.
457 | Класс выставляет наружу интерфейсные методы, которые не меняются и поддерживаются пожизненно, а внутрення реализация класса скрыта от глаз,
458 | находится в приватной области.
459 |
460 | Мы создали то же самое для `HTML`-верстки.
461 |
462 | `BTJson` блока — это его внешнее АПИ, а `bt`-шаблоны — это приватная реализация блоков.
463 |
464 | Ощутите мощь этой идеи на примере.
465 |
466 | ```javascript
467 | {
468 | block: "y-header",
469 |
470 | view: "islet-search",
471 | showSearch: true,
472 | showSuggest: true,
473 | }
474 | ```
475 |
476 | При такой простой декларации шапки, конечная `HTML`-верстка может какой угодно сложной.
477 |
478 | ```html
479 |
480 |
510 |
511 |
512 | ```
513 |
514 | Но самое ценное в том, что отныне можете менять шаблоны, генерирующие `HTML` как угодно часто, вам не нужно при этом переписывать
515 | все страницы, и бояться, что на какой-то из них что-то пойдёт не так. АПИ блока гарантирует результат.
516 |
517 | Возможно, кто из вас читал про Web Components.
518 |
519 | Бивис реализует именно эту же идею. Но отличие в том, что Web Components пока ещё не
520 | поддерживаются браузерами, а бивис вы можете использовать уже сегодня.
521 |
522 | Кто-то может возразить, что мы пишем больше кода — каждый декларативный шаблон нужно явно описать, там задать какой тег выливать,
523 | какие элементы внутри блока генерить. Ну так вы же на Смарти шаблонах тоже всё это пишете, только в виде чистого `HTML`. А мы пишем это на `JS`.
524 |
525 | Вы скажете, что мы пишем куда больше букв и в доказательство приведёте эти две строки.
526 | ```javascript
527 | ctx.setTag('div'); // 18 символов
528 | ```
529 |
530 | ```html
531 |
532 | ```
533 |
534 | Да, наша запись на семь символов длиннее, но только при условии, что вы руками пишете каждый символ, когда пишете теги.
535 | Вы же не в `notepad.exe` код пишете, надеюсь?
536 |
537 | Вы пользуетесь аутокомплитом, то есть написали открывающую скобку, затем букву `d` и нажали хоткей для аутокомплита.
538 | Или если [Дзен-кодингом](http://pepelsbey.net/2008/08/zen-html/) набираете `HTML`, который придумал Вадим Макеев, а разработал Сергей
539 | Чикуёнок, и теперь он называется [Emmet](http://emmet.io/), тогда вы вообще пишете три символа `div` и нажимаете `tab`.
540 |
541 | Ну, а мы пользуемся сниппетами. Нажал хоткей, вписал только три символа `div` и у нас готова строка ```ctx .setTag ('div');```
542 |
543 | Я веду к тому, что вы пишете императивные шаблоны, а мы декларативные, и прикладываем мы с вами сравнительно одинаковое количество сил для написания шаблонов.
544 |
545 | ## CSS блока
546 |
547 | Тему стилей мы начнём с заявления, с которым многие из вас будут категорически не согласны. Но тем интереснее, правда?
548 |
549 | ### Смешивание блоков
550 |
551 | Мы убеждены, что имя у блока должно быть одно. Мы не согласны с теми из вас, кто использует в верстке такой приём:
552 |
553 | ```
554 |
555 | ```
556 |
557 | Что это — шапка и форма авторизации одновременно? Это как? Я и папа, я и мама?
558 |
559 | Вы смотрели фильм Георгия Данелия «Мимино«?
560 |
561 | Мимино, главный герой фильма, грузинский летчик из горного селения. Играет Вахтанг Кикабидзе.
562 | По сценарию Мимино захотел работать в большой авиации, прилетел в Москву, найти работу в Аэрофлоте. Жить ему негде, в гостиницах мест нет.
563 | Это происходило во времена CCCP. Но знакомые знакомых договорились по блату поселить его в гостиницу, в которой как раз проводится всесоюзный слёт врачей-эндокринологов.
564 | И чтобы Мимино заселили, ему нужно было на ресепшине солгать, что он тоже врач-эндокринолог.
565 |
566 | И вот представьте. Мимино приходит в гостиницу. Синяя форма лётчика. Выглаженные стрелки на брюках. Погоны. Фуражка с эмблемой авиации.
567 | И когда его удивлённо спрашивают «Вы, что — лётчик?», знаете, что он отвечает?
568 |
569 | — Да. Иногда... Вообще-то, я эндокринолог.
570 |
571 | Хороший летчик не может быть одновременно хорошим эндокринологом. Попробуй совмещать обе профессии — сплошные проблемы,
572 | такова жизнь, не нам с ней спорить. Я, кстати, пробовал быть врачом и программистом одновременно ;)
573 |
574 | В верстке с множественными именами у блоков сплошные хаос и боль, потому что в `CSS` нет надёжного механизма разруливания весов
575 | у стилевых селекторов с одинаковой специфичностью: какой селектор позже записан, тот и применится к тегу.
576 |
577 | Если для блока с `class="header authorization"` написать селектор `.header {border-color: red}`, а до него другой `.authorization {border-color: green}`,
578 | то блок окажется окружён красным бордером. А если вдруг селекторы придут в обратном порядке — вокруг блока будет зелёный бордер.
579 | А приходить они могут в разной последовательности, когда у вас динамическое построение `CSS`-файла.
580 |
581 | Когда мы смешиваем два блока на одной `HTML`-ноде, нет способа влиять на эту ситуацию, нет возможности контролировать её.
582 | Управлять ей. Точнее, он есть, но он вообще не гарантирует результат — надо повышать специфичность селекторов или загружать стилевые селекторы в «правильном» порядке,
583 | собирать `CSS`-файл в «правильном» порядке.
584 |
585 | А правильный — это какой? Как понять, в каком порядке, если есть возможность смешать два произвольных блока в произвольном порядке?
586 | А если три блока? Или четыре? Количество сочетаний блоков на одной простой странице исчисляется тысячами.
587 | Точно ли разработчик сможет учесть всё многообразие сочетаний? Сможет?
588 |
589 | В `BEViS` мы отказались от смешивания блоков друг с другом. Нет миксов блоков на одной ноде — нет конфликтов.
590 |
591 | Бивис-блок имеет всегда только одно имя. Всегда одно. Это важно.
592 |
593 | ### Имя и фамилия
594 |
595 | Имя у блока может быть простым — просто Марат, или просто Вадим, или просто header. Но бывают случаи, когда оно должно быть более точным.
596 | Например, Марат Дулин, или Вадим Макишвили. Чтобы добавить в имя уникальности.
597 |
598 | Кого из вас зовут Вадим?
599 |
600 | Представьте, вы сидите в кино, и вдруг в дверь вбегает кто-то запыхавшийся и кричит на весь кинотеатр:
601 | «Вадим, у тебя в продакшене 404 на главной странице!»
602 |
603 | Сколько Вадимов встрепенётся и занервничает? Да, все Вадимы. Но если он крикнет «Вадим Макишвили», то
604 | встрепенусь только я, а остальные Вадимы лишь недовольно оглянутся на шум.
605 |
606 | Жизнь доказывает, что имени и фамилии достаточно для идентификации человека.
607 |
608 | Когда мы зовём block: 'header', мы зовём наш блок по имени. Но если мы хотим добавить ещё больше уникальности, мы добавляем здесь
609 | же «фамилию», которую у блока мы именуем словом `view`
610 |
611 | ```javascript
612 | {
613 | block: 'header',
614 | view: 'search'
615 | }
616 | ```
617 |
618 | Читать это следует так: создай на странице шапку, да не простую, а поисковую.
619 | Получится такой HTML:
620 |
621 | ```html
622 |
623 | ....
624 |
625 | ```
626 |
627 | Понимаете, блок один — шапка, но на одной странице она может быть поисковая, а на другой непоисковая.
628 | Поэтому у неё есть два варианта отображения на странице, два представления, две темы, два скина, две шкурки...
629 | два `View`. Разный вид одного и того же блока мы называем разным представлением. Разным `View`.
630 |
631 | Но имя у блока, как вы видите, у нас по-прежнему одно.
632 |
633 | #### Не смешивай несмешиваемое
634 | Не знаю, как у вас в проектах, в Яндексе довольно часто можно встретить такие записи:
635 | ```html
636 |
кнопка
637 | ```
638 |
639 | Есть некий базовый класс и несколько модификаторов, каждый добавляет к базовому классу что-то или переопределяет
640 | какие-то стили из базового класса.
641 |
642 | Чем это опасно?
643 | Вы добавляете или изменяете какое-то свойство в одном из этих трёх классов, у вас нет никакой гарантии, что
644 | ваши исправления не придут в конфликт весов со стилями других классов на этой ноде.
645 |
646 | В большом проекте даже после небольших изменений в таких стилях хорошо бы перетестировать весь проект с нуля,
647 | чтобы убедиться, что верстка не сломалась в тех блоках, где есть множественные классы. Проблема же в том, что нет
648 | другого механизма, кроме ручного перебора каждой страницы. А если у вас Single Page Application, часть блоков не видна,
649 | потому что строятся по данным, которые запрашиваются аяксом... В общем, верстка становится нестабильной.
650 |
651 | И это совсем не похоже на независимые блоки. Какая же это независимость, если даже внутри одного блока могут
652 | родиться проблемы отображения? Трудно себе даже представить потенциальные конфликты в стилях, если мы смешаем
653 | два разных блока на одной ноде.
654 |
655 | Что делает Бивис?
656 | Мы в такой ситуации рассуждаем так: если в проекте нужна кнопка с темой normal и тенью, то должен существовать отдельный класс,
657 | в котором сразу будет и тема normal и тень. В одном классе. Поэтому та запись с тремя классами превращается в один класс:
658 |
659 | ```html
660 |
кнопка
661 | ```
662 |
663 | То есть мы делам блок button с `view: "normal-shadow"`. В `JSON` это записывается так:
664 |
665 | ```javascript
666 | {
667 | block: 'button',
668 | view: 'normal-shadow'
669 | }
670 | ```
671 |
672 | А уже внутри этого вью мы смешиваем все эти разновидности кнопки и разруливаем все конфликты.
673 | И делаем это с помощью препроцессора.
674 |
675 | Кто сейчас не знает, что такое `CSS`-препроцессор? Все знают.
676 |
677 | Кроме меня. Я относился к `CSS`-препроцессорам с брезгливостью. «Баловство это», — так я считал, — «Тот же `CSS`, только с синтаксическим сахаром.»
678 | А сахар я не люблю, кофе могу пить и несладкий.
679 |
680 | Только оказалось, что сахар в препроцессорах — это не то, ради чего они нужны.
681 | Мы с их помощью делаем верстку гибкой и модульной.
682 |
683 | Как мы решаем задачу?
684 |
685 | ### Публичные селекторы и приватные функции
686 |
687 | ```css
688 | .button {
689 | skin-common()
690 | skin-theme-normal()
691 | }
692 |
693 | .button_normal-shadow {
694 | skin-common();
695 | skin-theme-normal();
696 | skin-shadow();
697 | }
698 | ```
699 |
700 | Мы создаём два селектора — `.button` и `.button_normal-shadow`. Они не зависят друг от друга. Каждый — самодостаточный.
701 | В каждом из них я зову некоторые функции, как мы это делаем в `JS`.
702 |
703 | Это своего рода геттеры — функции, которые что-то возвращают вместо себя.
704 | В Stylus-препроцессоре они называются миксины. Примеси. Внутри миксинов мы описываем стилевые правила.
705 |
706 | ```css
707 | skin-common() {
708 | /* общие стили для кнопки */
709 | }
710 |
711 | skin-theme-normal() {
712 | /* цветовая тема */
713 | }
714 |
715 | skin-shadow() {
716 | /* стили добавляют тень */
717 | }
718 |
719 | ```
720 |
721 | Мощь в том, что эти функции приватны. Они не попадают напрямую в скомпилированный `css`-файл.
722 | Пока из конкретного селектора не вызову этот миксин, его содержимое нигде не светится.
723 |
724 | ### Все конфликты под контролем
725 |
726 | И здесь же, на этапе препроцессора мы можем разрулить все конфликты между правилами разных миксинов, если они возникнут.
727 |
728 | ```css
729 | .button_normal-shadow {
730 | skin-common();
731 | skin-theme-normal();
732 | skin-shadow();
733 |
734 | /* а здесь разрулил все конфликты этих миксинов */
735 | }
736 | ```
737 |
738 | То есть мы решаем все конфликты на этапе разработки, а не на этапе использования.
739 | В браузер пойдёт для одного блока один css-селектор. Гарантированно работающий.
740 |
741 | Идею так использовать препроцессор мы позаимствовали у Романа Комарова. Он предложил такую же схему для своего `CSS`-фреймворка Stylobat. Рома — это тот парень,
742 | который является сейчас мейнтейнером препроцессора Stylus.
743 |
744 | Рома, спасибо тебе большое!
745 |
746 | Что мы имеем на выходе: один блок имеет один `CSS`-класс. Мы по-прежнему утверждаем, что блок — это просто, одного имени достаточно.
747 |
748 | ## JS-поведение блока
749 |
750 | Как мы пишем `JS` для блоков на странице?
751 |
752 | В маленьких проектах мы делаем так:
753 |
754 | ```javascript
755 | $( document ).ready(function() {
756 |
757 | var form = $('#my-form');
758 | form.submit(onSubmited);
759 |
760 | function onSubmited() {
761 | if ($('#my-form .button').hasClass('disabled')) {
762 | return false;
763 | }
764 | }
765 |
766 | });
767 | ```
768 |
769 | Для каждого блока мы описываем, что он должен делать.
770 | Здесь же описываем реакции на пользовательские события.
771 |
772 | В бивисе мы делаем то же самое, только не внутри ```$( document ).ready()```, а внутри модуля.
773 |
774 | Модульные системы — это и `RequireJS`, и `CommonJS`.
775 | Модуль — это очень простая идея. Это специальный `JS`-объект, внутрь которого мы вкладываем свой программный код.
776 | Ваш код будет работать, как и прежде, в нём ничего не меняется.
777 |
778 | Модуль нужен только для того, чтобы обеспечить взаимодействие с другими фрагментами вашего кода, другими вашими модулями.
779 | Модуль, как кассир в Макдональдсе — когда он готов работать, он вскидывает руку: «Свободная касса!»
780 |
781 | Когда модуль загрузился в память, он сообщает об этом в модульную систему. И другие модули с этого момента могут использовать ваш
782 | код из этого модуля. Прям как по `domReady`.
783 |
784 | У нас есть своя модульная система, она асинхронная на всех этапах своей работы.
785 | Примерно так выглядит описание модуля в терминах модульной системы.
786 |
787 | ```javascript
788 | modules.define(
789 | 'form',
790 | ['jquery'],
791 | function(provide, $) {
792 |
793 | var form = $('#my-form');
794 | form.submit(onSubmited);
795 |
796 | function onSubmited() {
797 | if ($('#my-form .button').hasClass('disabled')) {
798 | return false;
799 | }
800 | }
801 |
802 | provide(form);
803 | }
804 | );
805 | ```
806 | Сам код остался тем же, обвязка немного поменялась.
807 |
808 | Мы объявили модуль в первой строке.
809 | Во второй мы дали модулю имя.
810 | В третьей описали зависимости — в нашем случае это только `JQuery`
811 | В четвёртой описали содержимое модуля. Именно в этой функции и происходит программирование поведения блока.
812 | И пред-предпоследняя строка — это и есть вскинутая рука: «Свободная касса». Модуль form готов к работе. Так другие модули понимают, что могут использовать этот модуль.
813 |
814 | Обратите внимание, мы не используем напрямую `JQuery`.
815 | Мы используем свой базовый класс для написания блоков. Это такой `JS`-класс,
816 | который позволяет оперировать в `JS` именно нашими абстрактными блоками, а не тегами. Если нам надо найти блок, или элемент блока, мы делаем это не через dom-методы, типа `getElementById()`,
817 | а нашими методами.
818 |
819 | ```javascript
820 | modules.define(
821 | 'form',
822 | ['button', 'input', 'y-block'],
823 | function(provide, button, input, YBlock) {
824 |
825 | var Form = inherit(YBlock, {
826 | __constructor: function () {
827 | this._submitButton = YButton.find(this);
828 | this._bindTo(this.getDomNode(), 'submit', this._onSubmited);
829 |
830 | },
831 |
832 | _onSubmited: function (e) {
833 | if (this._submitButton.isDisabled()) {
834 | e.preventDefault();
835 | } else {
836 | this.emit('submit');
837 | }
838 | }
839 | });
840 |
841 | provide(Form);
842 | }
843 | );
844 | ```
845 |
846 | Зачем оно такое надо? Модули и свой `JS`-класс для блоков? Почему не просто `jQuery`?
847 |
848 | Во-первых, мы получаем код, который можно вновь и вновь использовать.
849 |
850 | Во-вторых, мы получаем абстракцию над `HTML`-структурой блока. Нам не важно, как блок представлен на странице. Порой нам даже не важно, где он лежит — в body или в каком-то другом элементе.
851 | Мы переходим от DOM-взгляда на компонент на другой. Мы смотрим на блоки с точки зрения семантики их работы.
852 |
853 | И ещё это нужно, чтобы максимально разделить визуальное отображение и поведение. У Бивиса получилось.
854 |
855 | Когда бивис сгенерит `HTML` для шапки, вы увидите там такое:
856 | ```html
857 |
860 |
861 | ```
862 | Слово `header`, записанное в атрибуте class участвует исключительно в стилях.
863 | А то, что написано в `data-block` — именно по этому атрибуту Бивис находит шапку и назначает ей `JS`-поведение.
864 |
865 | В бивисе блок может иметь один `CSS`-класс, а дата-атрибут совершенно другой. То есть вы можете спокойно удалить атрибут —
866 | поведение вы потеряете, но внешний вид блока не сломается.
867 |
868 | Состояние блока — это единственное место, где поведение и отображение пересекаются. Этого никак не избежать, потому что и в жизни оно так устроено.
869 |
870 | Когда я бегаю по стадиону, я краснею и потею. Меняется мой внешний вид. Когда я плаваю долго в море, кожа на пальцах сморщивается и бледнеет.
871 | У нас есть знакомые, которые так определяют, можно ли ребенку ещё плавать в бассейне:
872 |
873 | — Сын, у тебя там кожа на ладошках уже состарилась?
874 |
875 | — Нет, пап, не состарилась!
876 |
877 | А сам тем временем прячет за спину помятые ладошки.
878 |
879 | В блоке тоже самое. Возьмём ту же форму авторизации. По нажатию на кнопку сабмита надо проверить поле логина. Если пустое — обвести поле
880 | логина стыдливой красной рамкой. То есть изменить отображение блока, сменить его состояние с обычного на тревожное.
881 |
882 | Из `JS` мы добавляем дополнительный класс, который отвечает за изменение внешнего вида в состоянии «тревога».
883 |
884 | Вот что произойдёт в `HTML`-элементе блока
885 | ```html
886 |
887 | ```
888 |
889 | а вот что мы заранее напишем в стилях
890 | ```css
891 | /* обычное состояние */
892 | .login {
893 | ...
894 | }
895 |
896 | /* тревожное состояние */
897 | .login._unfilled {
898 | border: 1px solid red;
899 | }
900 | ```
901 |
902 | И когда мы на клиенте проверим, что поле логина пустое, мы добавим в ноду к инпуту класс «_unfilled»
903 | Мы называем такие классы состояниями. State
904 | Имя стейта может быть любым, какое вы выберете. Вам всего лишь нужно в стилях предусмотреть, что
905 | если `JS` добавит такой-то стейт-класс, чтобы в отображении что-то изменилось.
906 |
907 | Почему класс начинается с подчёркивания? Чтобы подчеркнуть, что он приватный для блока и чтобы легче его замечать.
908 |
909 | Теперь понимаете, почему мы назвали наш подход BEViS?
910 | ```
911 | B — [B]lock
912 | E — [E]lement
913 | Vi— [Vi]ew
914 | S — [S]tate
915 | ```
916 |
917 | ## Сборка статики без боли
918 |
919 | Мы почти закончили. Осталось сказать, что конечно же мы не пишем все стили в одном файле, все шаблоны в одном файле,
920 | весь клиентский `JS` в одном файле. Каждый блок представлен на файловой системе своей папкой, это удобно и очень по-человечески.
921 |
922 | Ведь у меня, как и у любого человека, есть дом — место, откуда я выхожу утром на работу и куда возвращаюсь каждый вечер.
923 | Ну, хорошо, это не дом. Это квартира в многоэтажке.
924 |
925 | У блока тоже есть свой дом — место, где блок отдыхает и ждёт, когда его «позовут поработать» в веб-странице.
926 | В корне проекта есть директория `/blocks`, в ней живут все блоки. Только в ней. Это дом блоков. Не ищите других мест.
927 | Если блока здесь нет, больше нет нигде.
928 |
929 | ````
930 | /blocks
931 | /header
932 | header.bt.js
933 | header.styl
934 | header.js
935 | /form
936 | form.bt.js
937 | form.styl
938 | form.js
939 | ````
940 |
941 | Каждый блок хранится в своей собственной папке, где шаблоны для генерации `HTML` описаны в `*.bt.js`, стили в `*.styl`, клиентский яваскрипт в `*.js`.
942 | Эффективно, хотя и не ново. Когда моя дочка готовится к математике, она на парту выкладывает учебник по математике, тетрадь по математике,
943 | угольник и карандаш — всё, что нужно именно для урока математики. У нас так же — всё, что нужно для блока находится в одной папке.
944 |
945 | А когда учитель хочет проверить у всего класса домашнюю работу по математике, она ходит по рядам и собирает тетради в стопку.
946 | У нас тоже есть такой сборщик. Когда в сервер приходит запрос на страницу, собирается не только `HTML`, но и `CSS`, и `JS`. Это всё делает
947 | сборщик. Почему не Ant, Make, Grunt?
948 |
949 | Сборка часто является узким местом в сложных компонентных системах, но в `BEViS` мы полностью интегрировали сборку в процесс разработки.
950 | Работая над проектом достаточно лишь обновлять страницу в браузере, чтобы всегда видеть актуальные стили, скрипты и верстку. Но не по этой причине мы писали свой сборщик :)
951 |
952 | Наш [ENB](https://github.com/enb-make/enb)-сборщик отличается огромной скоростью сборки для проектов с блочной структурой.
953 | Он собирает проекты со скоростью пули. Вот та самая причина!
954 |
955 | И теперь, когда мы знаем всё это, давайте посмотрим на схему. Давайте свяжем все ниточки в один клубок:
956 |
957 | 1. В адресную строку в браузере пользователь вводит `http://my-server.ru/`.
958 |
959 | 2. Браузер отправляет `HTTP`-запрос к серверу и ждёт.
960 |
961 | 3. ``Node.js``-сервер принимает запрос. Понимает, что нужно создать
962 | страницу `index.html` и открывает на чтение файл `/pages/index/index.page.js`
963 |
964 | 4. Выполняет там `JS`-инструкции, а именно: ходит за данными в бекенд, формирует декларацию страницы из `btjson`,
965 | анализирует каждый элемент `btjson`-массива и запоминает из каких блоков будет построена будущая `HTML`-страница.
966 |
967 | 5. После этого сервер запускает `ENB`-сборщик. На этом этапе, когда сервер
968 | принял запрос, `ENB` начинает с того, что создаёт пустые файлы
969 | * `/pages/index/index.styl`,
970 | * `/pages/index/index.js`,
971 | * `/pages/index/index.bt.js`
972 |
973 | Если такие файлы уже есть, `ENB` пропускает этот шаг и переходит к следующему.
974 |
975 | 6. Дальше `ENB` получает от сервера на вход имя первого блока — "header". `ENB` идёт на файловую систему проекта, находит директорию `/blocks/header` и заходит в неё.
976 |
977 | 7. Ищет файл `header.js`. Если он есть — копирует его содержимое в `/pages/index/index.js`. Делает то же самое для
978 | `bt`-файла, создавая `/pages/index/index.bt.js`. А для стилей делает то же самое, только сначала запускает `Stylus`-компилятор,
979 | который из `header.styl` генерит `CSS`-селекторы, а потом уже их копирует в `/pages/index/index.css`.
980 |
981 | 8. Дальше `ENB` выходит из `/blocks/header` и заходит в следующий блок, объявленный в btjson, например в `/blocks/form` и делает то же самое. И так повторяется для всех блоков.
982 |
983 | 9. В результате работы сборщика файлы `index.bt.js`, `index.css`, `index.js` заполнены `BT`-шаблонами,
984 | `CSS`-селекторами и `JS`-скриптами, необходимыми для генерации `HTML` всех блоков на странице, для их стилизации и
985 | для программирования `JS`-поведения. То есть `ENB` занимается тем, что создаёт страничные файлы.
986 |
987 | 10. Это почти всё, стилевой и яваскриптовый файлы готовы к отправке в браузер, но мы не можем пока отправлять, потому что у нас
988 | не готов конечный `HTML` страницы. Поэтому `ENB` отключается, и контроллер `index.page.js` вызывает `BT`-шаблонизатор.
989 |
990 | 11. `BT` принимает из контроллера `btjson` и накладывает на него только что
991 | сгенерённый `index.bt.js`, в результате чего происходит трансформация из `btjson`-а в
992 | `HTML`-теги. В результате, в памяти сервера
993 | формируется `HTML`-код страницы
994 |
995 | 12. Всё, это финал. `BT` создал `HTML`, `ENB` собрал `CSS` и `JS`. Сервер отправляет всё это назад в браузер по `HTTP`-протоколу.
996 |
997 | 13. И ура! В окне браузера у пользователя отобразилась наша страница.
998 |
999 | Теперь вы точно представляете, как работает Бивис-приложение.
1000 |
1001 | ## Заключение
1002 |
1003 | Начать работать на бивисе очень просто:
1004 | ```
1005 | git clone git@github.com:bevis-ui/bevis-stub.git your-project
1006 | cd your-project
1007 | make
1008 | ```
1009 | После этого мы открываем в браузере ```localhost:8080``` и говорим: «Привет, Бивис!»
1010 |
1011 | Пользоваться бивисом нам радостно по нескольким причинам.
1012 |
1013 | 1. Бивис на 99% — это простой `JS`.
1014 | 2. Весь программный код покрыт юнит-тестами.
1015 | 3. Бивис написан в очень понятном кодстайле, которого придерживается команда Яндекс.Карт.
1016 |
1017 | Убедиться в этом можно на двух тестовых проектах.
1018 |
1019 | * Одностраничное приложение, реализующее функциональность [TODO MVC](http://github.com/bevis-ui/bevis-todo)
1020 |
1021 | * Движок для ведения блогов — [BEViS Blog](http://github.com/bevis-ui/bevis-blog)
1022 |
1023 | И самое главное… конечно же мы любим бивис, потому что сами его и придумали ;)
1024 |
--------------------------------------------------------------------------------
/practice.md:
--------------------------------------------------------------------------------
1 | # Немного практики
2 |
3 | Хватит теории, давайте попишем код!
4 |
5 | Читайте последовательно, выполняйте задания вместе с нами на вашей локальной машине.
6 | Со всеми вопросы добро пожаловать в Issues :)
7 |
8 | _На вопросы, отмеченные звёздочкой, мы либо ещё не ответили, либо ответили не полностью._
9 |
10 | 1. [Создаём новую страницу проекта](practice/new-page.md)
11 |
12 | 2. [Добавляем на страницу новый блок](practice/new-block.md)
13 |
14 | 3. [Описываем зависимости между блоками](practice/dependencies.md)
15 |
16 | 4. [Пишем стили для блока](practice/css.md)
17 |
18 | 5. [Пишем `js`-поведение для блока](practice/yblock.md)
19 |
20 | 6. [Настраиваем локализацию в проекте](practice/i18n.md)
21 |
22 | 7. [Используем `MVC`-концепцию в `BEViS`-проекте](practice/mvc-app.md)
23 |
24 | 8. [Взаимодействуем с бекендом](practice/backend-requests.md) [*]
25 |
26 | 9. [Пишем тесты](practice/tests.md) [*]
27 |
--------------------------------------------------------------------------------
/practice/backend-requests.md:
--------------------------------------------------------------------------------
1 | # Взаимодействуем с бекендом
2 |
3 |
4 | ----
5 |
6 | в разработке
7 |
8 | ----
9 |
10 | План:
11 | 1. Преположим есть путь к ручкам сохранения и чтения данных. Какое-то api
12 | 2. дергаем их $.ajax
13 | 3. Нельзя дернуть то, что лежит на другом домене. Ошибка в браузере, текст.
14 | 4. Нужно сделать прокисирующие ручки на сервере и уже они будут ходить по http на другой домен.
15 | 5. Как бы хотелось, чтобы они работали? В виде цепочки методов, как мы привыкли в jQuery
16 | ```
17 | do()
18 | .then()
19 | .fail();
20 | ```
21 |
22 | В `then` параметром то, что вернул `do()`. Метод `fail` вызовется, если `do` упал с ошибкой.
23 |
24 | Тогда мы написали бы так:
25 | ```
26 | request('путь к серверным ручкам')
27 | .then(function () {
28 | this.emit('saved')
29 | })
30 | .fail(function () {
31 | this.emit('not-saved')
32 | });
33 | ```
34 |
35 | Цепочка методов, совсем как в jQuery. ТОлько там выполнялись бы все три метода подряд, а здесь выполнится первый, а
36 | потом или второй или третий.
37 |
38 | На деле, мы только что описали Promises, Обещания. На человеческом языке это выглядело бы так:
39 | ```
40 | обещаю вызвать('путь к серверным ручкам')
41 | .в_случае_успеха_вызову(function () {
42 | this.emit('saved')
43 | })
44 | .в_случае_провала_вызову(function () {
45 | this.emit('not-saved')
46 | });
47 | ```
48 |
49 | 6. перепишем Модель, как нам бы хотелось, чтобы она работала. Пишем на промисах методы `_setUserData` и `_getUserData`
50 | Предположим, что такой модуль есть и он называется просто - `api`.
51 | ```
52 | api
53 | .exec('путь к серверным ручкам')
54 | .then(function () {
55 | this.emit('saved')
56 | })
57 | .fail(function () {
58 | this.emit('not-saved')
59 | });
60 | ```
61 |
62 | 7. Такой модуль действительно есть, называется BLA
63 | добавляем в package.json
64 | npm update
65 | рассказываю, что он умеет, как работает в общих чертах.
66 |
67 |
--------------------------------------------------------------------------------
/practice/css.md:
--------------------------------------------------------------------------------
1 | Первые два шага мы уже сделали — [создали новую страницу](new-page.md), [создали новый блок](new-block.md).
2 |
3 | У нас уже есть страница `/pages/test-page/test-page.page.js`, а в ней единственный блок `input`:
4 |
5 | ```javascript
6 | module.exports = function (pages) {
7 | pages.declare('test-page', function (params) {
8 | var options = params.options;
9 | return {
10 | block: 'page',
11 | title: 'test page',
12 | styles: [
13 | {url: options.assetsPath + '.css'}
14 | ],
15 | scripts: [
16 | {url: options.assetsPath + '.' + params.lang + '.js'}
17 | ],
18 | body: [
19 | {
20 | block: 'input',
21 | value: 'Привет, Бивис!'
22 | }
23 | ]
24 | };
25 | });
26 | }
27 | ```
28 |
29 | Ещё по адресу `/blocks/input/` есть `bt`-шаблоны для `input`, стилевой файл и файл с `JS`-поведением этого блока.
30 | Откроем стилевой файл `/blocks/input/input.styl` и начнём :)
31 |
32 | Если вы выполнили шаги в [предыдущем руководстве](new-block.md), то файл у вас выглядит совершенно бесполезно.
33 | У меня там написано вот что:
34 |
35 | ```css
36 | .input {
37 | border: 1px solid red;
38 | }
39 | ```
40 |
41 | Откроем в браузере `http://localhost:8080/test`. Блок `input` выглядит непрезентабельно - голое текстовое поле
42 | с красной обводкой. Именно это мы и будем это исправлять.
43 |
44 | ## Дизайнер дал картинку
45 |
46 | Вот реальный эскиз из Яндекса.
47 |
48 |
49 |
50 | Дизайн без особых затей:
51 |
52 | * текстовое поле плоское, белый фон, серая рамка
53 | * жёлтое свечение вокруг поля, когда поле получает фокус
54 | * текст-приглашение, если поле пустое
55 | * крестик для очистки поля
56 |
57 | Ещё дизайнер предусмотрел разные размеры блока. И, конечно, предусмотрел неактивное состояние,
58 | когда поле недоступно для действий пользователя.
59 |
60 | Глядя на эскиз, становится очевидно, что одним DOM-узлом мы не обойдёмся, придётся верстать дополнительный
61 | элемент `крестик`, потому что такого нативного браузерного контрола, к сожалению, не существует. Значит блок `input`
62 | придётся усложнить. Сейчас он выглядит так:
63 |
64 | ```html
65 |
66 | ```
67 |
68 | А мы сделаем его таким:
69 |
70 | ```html
71 |
72 |
73 |
74 |
75 | ```
76 |
77 | Текстовое поле, как и элемент "крестик" станут детьми блока, его элементами. И как мы видим,
78 | у текстового поля появится атрибут `placeholder`, в котором и будет храниться тот самый
79 | текст-приглашение.
80 |
81 | Мы не хотим хардкодить текст-приглашение, потому что блок `input` получится универсальным блоком,
82 | и нам захочется использовать его в разных формах. По той же причине мы не будем хардкодить `name` текстового поля.
83 | Вместо этого мы расширим API блока — разрешим при декларации блока указывать значения для этих атрибутов. Для этого в
84 | `/pages/test-page/test-page.page.js` в вызове блока добавим два новых поля - `name` и `placeholder`:
85 |
86 | ```javascript
87 | module.exports = function (pages) {
88 | pages.declare('test-page', function (params) {
89 | var options = params.options;
90 | return {
91 | block: 'page',
92 | title: 'test page',
93 | styles: [
94 | {url: options.assetsPath + '.css'}
95 | ],
96 | scripts: [
97 | {url: options.assetsPath + '.' + params.lang + '.js'}
98 | ],
99 | body: [
100 | {
101 | block: 'input',
102 | value: 'Привет, Бивис!',
103 | name: 'loginField',
104 | placeholder: 'на сайте'
105 | }
106 | ]
107 | };
108 | });
109 | }
110 | ```
111 |
112 | И теперь поддержим все изменения в шаблонах.
113 |
114 | ## Усложним HTML-структуру
115 |
116 | Откроем файл `/blocks/input/input.bt.js`, сейчас он выглядит вот так:
117 |
118 | ```javascript
119 | module.exports = function (bt) {
120 |
121 | bt.match('input', function (ctx) {
122 | ctx.enableAutoInit();
123 | ctx.setTag('input');
124 |
125 | var currentValue = ctx.getParam('value');
126 | ctx.setAttr('value', currentValue);
127 | });
128 |
129 | };
130 | ```
131 |
132 | Пусть блок `input` превратится в небольшой фрагмент DOM-дерева с двумя дочерними элементами - собственно текстовое поле
133 | и крестик для очистки текстового поля:
134 |
135 | ```javascript
136 | module.exports = function (bt) {
137 |
138 | bt.match('input', function (ctx) {
139 | ctx.enableAutoInit();
140 |
141 | var value = ctx.getParam('value');
142 | var name = ctx.getParam('name');
143 | var placeholder = ctx.getParam('placeholder');
144 |
145 | ctx.setContent([
146 | {
147 | elem: 'control',
148 | inputValue: value,
149 | inputName: name,
150 | placeholder: placeholder
151 | },
152 | {
153 | elem: 'clear'
154 | }
155 | ]);
156 | });
157 |
158 | };
159 | ```
160 |
161 | Мы выкинули метод `ctx.setTag('input')`, потому что обертка должна быть тегом `div`,
162 | а такой тег генерится шаблонизатором по умолчанию даже если мы этого не указали явно.
163 |
164 | Мы создали локальные переменные `value`, `name`, `placeholder` и сохранили в них значения параметров из контекста с
165 | помощью метода `ctx.getParam()`. Из какого такого контекста? В контексте чего работает шаблон блока? Шаблон блока
166 | работает в контексте данных, которые описаны в `test-page.page.js`, то есть фактически в контексте этого
167 | фрагмента страницы:
168 |
169 | ```javascript
170 | {
171 | block: 'input',
172 | value: 'Привет, Бивис!',
173 | name: 'loginField',
174 | placeholder: 'на сайте'
175 | }
176 | ```
177 |
178 | То есть, когда мы в шаблоне сказали, к примеру, `var placeholder = ctx.getParam('placeholder')`,
179 | то в локальной переменной `placeholder` окажется строка "на сайте", которую мы указали в декларации страницы — мы
180 | получили значение параметра из контекста.
181 |
182 | Затем мы вызвали метод `ctx.setContent()`, в который передали массив элементов. Мы решили,
183 | что внутри блока будут жить два элемента - те самые `input__control` и `input__clear`. Элементы описываются так же,
184 | как блок, с той лишь разницей, что вместо слова `block` мы зовём элемент словом `elem`.
185 |
186 | Если сейчас обновить страницу в браузере, текстовое поле со страницы исчезнет, но заглянув в HTML-код страницы,
187 | мы увидим почти то, что нужно:
188 |
189 | ```html
190 |
191 |
192 |
193 |
194 | ```
195 |
196 | Осталось сгенерить правильный тег для `input__control` и передавать в него значение для атрибутов
197 | `value`, `name` и `placeholder`. Для этого добавим новый шаблон (смотрите ниже,
198 | там появился шаблон для `input__control`):
199 |
200 | ```javascript
201 | module.exports = function (bt) {
202 |
203 | bt.match('input', function (ctx) {
204 | ctx.enableAutoInit();
205 |
206 | var value = ctx.getParam('value');
207 | var name = ctx.getParam('name');
208 | var placeholder = ctx.getParam('placeholder');
209 |
210 | ctx.setContent([
211 | {
212 | elem: 'control',
213 | inputValue: value,
214 | inputName: name,
215 | placeholder: placeholder
216 | },
217 | {
218 | elem: 'clear'
219 | }
220 | ]);
221 | });
222 |
223 | bt.match('input__control', function (ctx) {
224 | ctx.setTag('input');
225 |
226 | var currentValue = ctx.getParam('inputValue');
227 | var currentName = ctx.getParam('inputName');
228 | var currentPlaceholder = ctx.getParam('placeholder');
229 |
230 | ctx.setAttr('value', currentValue);
231 | ctx.setAttr('name', currentName);
232 | ctx.setAttr('placeholder', currentPlaceholder);
233 | });
234 |
235 | };
236 | ```
237 |
238 | В шаблоне для `input__control` контекст меняется. Шаблон этого элемента работает в контексте данных,
239 | которые пришли к нему из предыдущего шаблона, то есть в контексте вот этого `btjson`:
240 |
241 | ```javascript
242 | ctx.setContent([
243 | {
244 | elem: 'control',
245 | inputValue: value,
246 | inputName: name,
247 | placeholder: placeholder
248 | }
249 | ```
250 |
251 | Именно поэтому в шаблоне элемента `control` мы ожидаем параметры под другими именами. Я специально назвал их иначе,
252 | нежели в родительском шаблоне, чтобы вы ухватили идею.
253 |
254 | Ещё раз о контексте, потому что это важный момент.
255 |
256 | ```javascript
257 | // В шаблоне для `input` я получил параметры из контекста `test-page.page.js`
258 | var value = ctx.getParam('value');
259 | var name = ctx.getParam('name');
260 | var placeholder = ctx.getParam('placeholder');
261 |
262 | // а потом прокинул их значения в элемент `control`
263 | ctx.setContent([
264 | {
265 | elem: 'control',
266 | inputValue: value,
267 | inputName: name,
268 | placeholder: placeholder
269 | }
270 | ```
271 |
272 | ```javascript
273 | // В шаблоне дочернего `input__control` я получил ровно эти же значения,
274 | // но уже из параметров самого элемента `control`
275 | var currentValue = ctx.getParam('inputValue');
276 | var currentName = ctx.getParam('inputName');
277 | var currentPlaceholder = ctx.getParam('placeholder');
278 |
279 | // и создал атрибуты для html-тега:
280 | ctx.setAttr('value', currentValue);
281 | ctx.setAttr('name', currentName);
282 | ctx.setAttr('placeholder', currentPlaceholder);
283 | ```
284 |
285 | Теперь смотрим в браузере. Да, всё отлично, структура приходит.
286 |
287 | ```html
288 |
289 |
290 |
291 |
292 | ```
293 |
294 | Наконец-то займёмся стилями.
295 |
296 | ## Стили для блока и его элементов
297 |
298 | Возвращаемся в `/blocks/input/input.styl` и начинаем :)
299 |
300 | Если вы выполнили шаги в [предыдущем руководстве](new-block.md), то файл у вас выглядит совершенно бесполезно.
301 | У меня там написано вот что:
302 |
303 | ```css
304 | .input {
305 | border: 1px solid red;
306 | }
307 | ```
308 |
309 | Напишем стили, реализующие задумку дизайнера.
310 |
311 | ```css
312 | .input {
313 | $height = 26px;
314 |
315 | position: relative;
316 | display: inline-block;
317 | box-sizing: border-box;
318 |
319 | background-color: #fff;
320 | border: 1px solid rgba(0, 0, 0, 0.2);
321 |
322 | /* чтобы под бордером не было белого фона */
323 | background-clip: padding-box;
324 |
325 | vertical-align: baseline;
326 | line-height: normal;
327 | cursor: text;
328 |
329 | /* анимация желтой фокусной рамки */
330 | transition:box-shadow .05s ease-out;
331 |
332 | /* */
333 | &__control {
334 | font-family: Arial, Helvetica, sans-serif;
335 | font-size: 13px;
336 |
337 | position: relative;
338 | display: inline-block;
339 | box-sizing: border-box;
340 | margin: 0;
341 | padding: 0 .6em;
342 | width: 100%;
343 | height: $height;
344 | /* Чтобы не заползал под крестик */
345 | padding-right: $height;
346 |
347 | border: none;
348 | vertical-align: baseline;
349 |
350 | &:focus {
351 | outline: none;
352 | }
353 |
354 | /*
355 | @see http://alexbaumgertner.ya.ru/replies.xml?item_no=2112
356 | @see http://www.cssnewbie.com/input-button-line-height-bug/
357 | */
358 | &::-moz-focus-inner {
359 | padding: 0;
360 | border: none;
361 | }
362 |
363 |
364 | /* прячем стандартный крестик IE10 */
365 | &::-ms-clear {
366 | display: none;
367 | }
368 | }
369 |
370 | &._focused {
371 | outline: 0;
372 | box-shadow: 0 0 10px #fc0;
373 |
374 | if (ie == 8) {
375 | border-color: #d1bb66;
376 | } else {
377 | border-color: rgba(178, 142, 0, .6);
378 | }
379 | }
380 |
381 | &._disabled {
382 | border-color: transparent !important;
383 | cursor: default;
384 |
385 | if (ie == 8) {
386 | background-color: #ebebeb !important;
387 | } else {
388 | background-color: rgba(0, 0, 0, 0.08) !important;
389 | }
390 | }
391 |
392 | &._disabled &__control {
393 | user-select: none;
394 | background-color: inherit;
395 | }
396 |
397 | &__clear {
398 | position: absolute;
399 | top: 0;
400 | right: 0;
401 | z-index: 2;
402 |
403 | width: $height;
404 | height: $height;
405 | vertical-align: middle;
406 |
407 | background: url('images/clear-14.png') 50% 50% no-repeat;
408 | cursor: pointer;
409 | opacity: (ie != 8) ? 0.3 : 1;
410 |
411 | &:hover {
412 | background-image: url('images/clear-14-hover.png');
413 | opacity: 1;
414 | }
415 |
416 | &._visible {
417 | display: block;
418 | }
419 | }
420 | }
421 | ```
422 |
423 | Обычный `CSS`-код с базовыми конструкциями препроцессора: переменные (`$height`), условные операторы (`if`) и ссылка на
424 | родительский селектор `&`. Возможно, вы захотите использовать циклы, перебирающие методы и другие богатства `Stylus`. Мы
425 | сознательно не используем все возможности препроцессора, чтобы код оставался максимально похожим на `Plain CSS` — простым и наглядным.
426 |
427 | Скопировали? Теперь обновите страницу в браузере. Не хватает картинки для крестика. Не хватает жёлтой рамки вокруг блока
428 | при установке фокуса. А в остальном похоже на то, что хочет дизайнер.
429 |
430 | Стили незамысловатые, селекторы понятные, поэтому о них всего по паре слов.
431 |
432 | ```css
433 | background-color: #fff;
434 | border: 1px solid rgba(0, 0, 0, 0.2);
435 |
436 | /* чтобы под бордером не было белого фона */
437 | background-clip: padding-box;
438 | ```
439 |
440 | Дизайнер придумал для блока темно-серый полупрозрачный бордер. Страница у нас имеет светло-серый фон, бордеру задаём
441 | `rgba(0, 0, 0, 0.2)` и ждём, что скзвозь бордер мы увидим фон страницы. Но почему-то сквозь бордер мы видим белый
442 | фоновый цвет блока. Почему?
443 |
444 | Бордер, по всем спецификациям, откладывается _вне_ блока. Но фоновый цвет, заданный блоку, почему-то оказывается и
445 | под бордером тоже. Такое поведение фона цвета кажется необычным с точки зрения человеческой логики, даже если оно по
446 | спецификации. Вот для чего нужен `background-clip: padding-box` — этим мы заставляем фоновый цвет ограничиваться зоной
447 | внутренних отступов. Это такая хитрость, вдруг вы раньше не сталкивались, а теперь будете знать.
448 |
449 | Нативный браузерный `` в нашем блоке представлен элементом с классом `.input__control`. В `Stylus` мы описываем его
450 | в селекторе `&__control {}`, где `&` - ссылка на родительский селектор `.input{}`. Нам нужно лишить нативный браузерный
451 | `` всякого нативного оформления, а родительский блок `.input{}` наоборот оформить согласно
452 | дизайну.
453 |
454 | Про фокус. Так как браузерный `` оказался внутри нашего кастомного блока, стал безликим и словно растворился в
455 | дизайне, мы больше не можем разрешать браузеру показывать дефолтный фокус на ``. Отменяем:
456 |
457 | ```css
458 | /* */
459 | &__control {
460 |
461 | &:focus {
462 | outline: none;
463 | }
464 |
465 | /*
466 | @see http://alexbaumgertner.ya.ru/replies.xml?item_no=2112
467 | @see http://www.cssnewbie.com/input-button-line-height-bug/
468 | */
469 | &::-moz-focus-inner {
470 | padding: 0;
471 | border: none;
472 | }
473 | }
474 | ```
475 |
476 | Но фокус показывать мы должны, поэтому эмулируем его на самом блоке с помощью специального `состояния блока`. Блок
477 | оказался в фокусе.
478 |
479 | ## Стили состояний
480 |
481 | Состояние, оно же `state`, выражается специальным `CSS`-классом, который _немного_ (это важное уточнение) изменяет
482 | внешний вид блока. Чаще всего состояние меняется из `javascript`.
483 |
484 | Например, на блок нажали — у блока появилось состояние `pressed`. Блок неактивный — у него сразу выставлено состояние
485 | `disabled`
486 |
487 | Здесь описано состояние, когда блок `input` оказался в фокусе.
488 |
489 | ```css
490 | &._focused {
491 | outline: 0;
492 | box-shadow: 0 0 10px #fc0;
493 |
494 | if (ie == 8) {
495 | border-color: #d1bb66;
496 | } else {
497 | border-color: rgba(178, 142, 0, .6);
498 | }
499 | }
500 | ```
501 |
502 | Жёлтое свечение вокруг блока. Однопиксельный бордер, почти невидимый. В IE8 бордер сплошной без прозрачности.
503 |
504 | Кстати, для IE ничего особенного не пишем.
505 |
506 | Простые условия:
507 |
508 | ```css
509 | if (ie == 8) {
510 | border-color: #d1bb66;
511 | } else {
512 | border-color: rgba(178, 142, 0, .6);
513 | }
514 | ```
515 |
516 | Или тернарные:
517 |
518 | ```css
519 | opacity: (ie != 8) ? 0.3 : 1;
520 | ```
521 |
522 | Ничего сложнее, кажется, и не нужно.
523 |
524 | Классы состояний мы начинаем с символа подчёркивания.
525 |
526 | В программировании символ подчёркивания в начале имени переменной иллюстрирует, что эта переменная — приватная.
527 | Мы в `BEViS` этим же символом иллюстрируем, что состояние — тоже приватно. Сам символ ни на что не влияет, спокойно
528 | можно и без него жить. Но нам нравится, мы используем.
529 |
530 | Состояние само по себе ничего не делает. Оно лишь _добавляет_ или _переопределяет_ стили, чтобы блок сделать чуть-чуть
531 | иным — раскрасить чуть иначе, в соответствии с текущим состоянием.
532 |
533 | Классам состояний мы даём имена, как если бы они были образованы для формирования страдательного залога английского
534 | языка. Непонятно? Я бы и сам не понял, прочитав такое. А можно проще:
535 |
536 | "Block is focused" — отбрасываем "Block is" — что осталось, то и пишем.
537 |
538 | "Block is disabled", "Block is visible", "Block is active". Мы называем так. Вы можете называть иначе. Главное, чтобы
539 | было удобно. Догмы нет.
540 |
541 | Блок может находиться в разных состояниях одновременно: `_focused` и `_pressed`, `_visible` и `_disabled`. Комбинации
542 | могут быть самые разные, хоть все сразу.
543 |
544 | Так как состояния доопределяют или переопределяют стили блока, то сочетания разных состояний грозит возможными
545 | конфликтами между селекторами. Или скажем иначе: "Не грозит, а грозило бы, если в BEViS не существовало жёсткого
546 | правила — на одной html-ноде может быть только один блок". А если так, тогда всё многообразие состояний блока существует
547 | в пределах одного стилевого файла. И мы способны все сочетания состояний продумывать заранее.
548 |
549 | Что будет, когда блок нажат и стоит в фокусе? Подумаем и напишем:
550 |
551 | ```css
552 | &._focused._pressed {
553 | /* здесь стили */
554 | }
555 | ```
556 |
557 | А когда задизейблен и не скрыт? Напишем:
558 |
559 | ```css
560 | &._disabled._visible {
561 | /* здесь стили */
562 | }
563 | ```
564 |
565 | Стили готовы, но увидеть их в браузере польностью мы не можем, потому что ещё не написан скрипт, меняющий состояния.
566 | Скрипт мы будем писать позже и в [отдельном документе](yblock.md), а здесь только про стили.
567 |
568 | ## Нужны разные отображения?
569 |
570 | Дизайнер нарисовал текстовые поля в разных размерах — у них совершенно одинаковые стили, кроме размеров, каких-то
571 | отступов, величины гарнитуры шрифта. Нарисовал целых 4 размера и назвал их, как маркируют бирки на одежде - S, M, L, XL.
572 |
573 | В таких случаях верстальщики создают базовый класс `.input`, соответствующий дефолтному размеру, и 3 дополнительных,
574 | модифицирующих базовый класс, определяющих другие размеры и отступы. Мы называли их `.small`, `.large`, `.xlarge` и
575 | всё это выглядело бы примерно так:
576 |
577 | ```css
578 | .input {
579 | /* все базовые стили */
580 | }
581 |
582 | .input.small {
583 | height: 16px;
584 | font-size: 11px;
585 | padding: 2px;
586 | }
587 |
588 | .input.large {
589 | height: 22px;
590 | font-size: 18px;
591 | padding: 6px;
592 | }
593 |
594 | .input.xlarge {
595 | /* и здесь уточнения для XL */
596 | }
597 | ```
598 |
599 | Если внимательно вглядеться в эти составные селекторы, легко заметить, что они читаются как человеческие имена: "Инпут,
600 | Маленький Инпут, Большой Инпут, Очень Большой Инпут". Почему же эти имена выражены через два `CSS`-класса?
601 |
602 | В Бивисе мы создаём четыре отдельных класса - по одному для каждого размера:
603 |
604 | ```css
605 | .input_normal {
606 | /* все базовые стили + стили для размера M */
607 | }
608 |
609 | .input_small {
610 | /* все базовые стили + стили для размера S */
611 | }
612 |
613 | .input_large {
614 | /* все базовые стили + стили для размера L */
615 | }
616 |
617 | .input_xlarge {
618 | /* все базовые стили + стили для размера XL */
619 | }
620 | ```
621 |
622 | Каждый такой класс будет в себе содержать все базовые стили (которые раньше хранились в классе `.input`) и нужные
623 | размеры. Если мы так сделаем, код станет простым и очевидным — для каждого размера свой класс, который реализует только
624 | нужное отображение. Представление.
625 |
626 | Эти четыре селектора выше - это `представления` или `отображения` или `view` блока. Инпут может иметь нормальное
627 | представление (`normal`), маленькое (`small`), большое (`large`) или любое другое, которое мы ему создадим.
628 |
629 | Сразу решим ваши сомнения. Дублируется ли код базовых стилей блока в каждом вью? Нет:
630 |
631 | ```css
632 | input_mixin() {
633 | /* все базовые стили */
634 | }
635 |
636 | .input_normal {
637 | input_mixin();
638 | /* здесь только стили для размера M */
639 | }
640 |
641 | .input_small {
642 | input_mixin();
643 | /* здесь только стили для размера S */
644 | }
645 |
646 | .input_large {
647 | input_mixin();
648 | /* здесь только стили для размера L */
649 | }
650 |
651 | .input_xlarge {
652 | input_mixin();
653 | /* здесь только стили для размера XL */
654 | }
655 | ```
656 |
657 | Миксины - это четвертая и последняя возможность, которую мы используем в `Stylus`. Миксин - молчаливая функция, которая
658 | не попадает в конечный `CSS` сама по себе. Но если её в каком-то селекторе позвать, она вернёт вместо себя стили,
659 | описанные внутри.
660 |
661 | Это мы и делаем, чтобы обеспечить каждое представление инпута базовыми стилями. Именно внутрь `input_mixin()` мы
662 | перенесём все стили, которые сейчас описаны в `input.styl`. Откройте его на редактирование и замените первую строку
663 | файла, чтобы вместо `.input {` получилось `input_mixin() {`. Обратите внимание - нужно удалить точку (это не селектор,
664 | а функция) и дописать круглые скобки (что лишний раз показывает, что это всё-таки функция, а не селектор).
665 |
666 | Если сейчас обновить страницу в браузере, блок потеряет все стили. Ну вот, миксин - не селектор, сам по себе не
667 | применяется.
668 |
669 | Допишем в конец файла селектор для Большого Инпута. У вас должно получиться так:
670 |
671 | ```css
672 | input_mixin() {
673 | $height = 26px;
674 |
675 | /* все остальные базовые стили */
676 | }
677 |
678 | .input_large {
679 | input_mixin($height = 32px);
680 |
681 | &__control{
682 | font-size: 16px;
683 | }
684 | }
685 | ```
686 |
687 | Обратили внимание, что в миксин мы передаём параметр? Поддержим этот параметр в самом миксине и удалим жёстко
688 | зашитую переменную:
689 |
690 | Было:
691 | ```css
692 | input_mixin() {
693 | $height = 26px;
694 | ```
695 |
696 |
697 | Сделаем так:
698 | ```css
699 | input_mixin($height) {
700 |
701 | ```
702 |
703 | Теперь мы принимаем значение высоты, как аргумент миксина.
704 |
705 | На всякий случай сверим содержимое файла `input.styl`. У вас там сейчас написано следующее? Если нет, самое время
706 | поправить, чтобы было именно так:
707 |
708 | ```css
709 | input_mixin($height) {
710 |
711 | position: relative;
712 | display: inline-block;
713 | box-sizing: border-box;
714 |
715 | background-color: #fff;
716 | border: 1px solid rgba(0, 0, 0, 0.2);
717 |
718 | // чтобы под бордером не было белого фона
719 | background-clip: padding-box;
720 |
721 | vertical-align: baseline;
722 | line-height: normal;
723 | cursor: text;
724 |
725 | transition:box-shadow .05s ease-out; /* анимация желтой фокусной рамки */
726 |
727 | &._focused {
728 | outline: 0;
729 | box-shadow: 0 0 10px #fc0;
730 |
731 | if (ie == 8) {
732 | border-color: #d1bb66;
733 | } else {
734 | border-color: rgba(178, 142, 0, .6);
735 | }
736 | }
737 |
738 | /* */
739 | &__control{
740 | font-family: Arial, Helvetica, sans-serif;
741 | font-size: 13px;
742 |
743 | position: relative;
744 | display: inline-block;
745 | box-sizing: border-box;
746 | margin: 0;
747 | padding: 0 .6em;
748 | width: 100%;
749 | height: $height;
750 | // Чтобы не заползал под крестик
751 | padding-right: $height;
752 |
753 | border: none;
754 | vertical-align: baseline;
755 |
756 | &:focus{
757 | outline: none;
758 | }
759 |
760 | /*
761 | @see http://alexbaumgertner.ya.ru/replies.xml?item_no=2112
762 | @see http://www.cssnewbie.com/input-button-line-height-bug/
763 | */
764 | &::-moz-focus-inner {
765 | padding: 0;
766 | border: none;
767 | }
768 |
769 |
770 | /* прячем стандартный крестик IE10 */
771 | &::-ms-clear {
772 | display: none;
773 | }
774 | }
775 |
776 | &._disabled{
777 | border-color: transparent !important;
778 | cursor: default;
779 |
780 | if (ie == 8) {
781 | background-color: #ebebeb !important;
782 | } else {
783 | background-color: rgba(0, 0, 0, 0.08) !important;
784 | }
785 | }
786 |
787 | &._disabled &__control{
788 | user-select: none;
789 | background-color: inherit;
790 | }
791 |
792 | &__clear {
793 | position: absolute;
794 | top: 0;
795 | right: 0;
796 | z-index: 2;
797 |
798 | width: $height;
799 | height: $height;
800 | vertical-align: middle;
801 |
802 | background: url('images/clear-14.png') 50% 50% no-repeat;
803 | cursor: pointer;
804 | opacity: (ie != 8) ? 0.3 : 1;
805 |
806 | &:hover {
807 | background-image: url('images/clear-14-hover.png');
808 | opacity: 1;
809 | }
810 |
811 | &._visible {
812 | display: block;
813 | }
814 | }
815 | }
816 |
817 | .input_large {
818 | input_mixin($height = 32px);
819 |
820 | &__control{
821 | font-size: 16px;
822 | }
823 | }
824 | ```
825 |
826 | Обновим страницу в браузере. Ничего не изменилось. Смотрим в html - видим, что блок выглядит так
827 |
828 | ```html
829 |
830 |
831 |
832 |
833 | ```
834 |
835 | Уверен, вы поняли что пошло не так. Мы написали селектор для блока с вью — `input_large`, а в файле `test-page.page.js` мы декларируем
836 | инпут без всякого вью. Поэтому стили не применяются.
837 |
838 | Поправим декларацию блока. Добавим `view: 'large'`
839 |
840 | ```javascript
841 | {
842 | block: 'input',
843 | view: 'large',
844 | value: 'Привет, Бивис',
845 | name: 'loginField',
846 | placeholder: 'на сайте'
847 | }
848 | ```
849 |
850 | Обновим страницу в браузере. Блок вообще исчез! Смотрим в файрбаге - видим только обертку блока:
851 |
852 | ```html
853 |
854 | ```
855 |
856 | Если внутренности не генерятся, это значит, не отрабатывают `bt`-шаблоны.
857 |
858 | И действительно. Шаблоны сейчас матчатся на `input`, а у нас уже не просто `input`, у нас уже `input_large`.
859 |
860 | Решение простое - переписать match-и c `match('input'` на `match('input_large'`.
861 |
862 | Переписали, смотрим в браузере - да! Теперь инпут стал большим.
863 |
864 | Теперь мы можем в `input.styl` написать ещё один селектор для Маленького Инпута? И ещё один для Нормального Инпута? А
865 | для Очень Большого, для Красного Инпута, для Серо-Буро-Малинового? Для любого можем и оно заработает?
866 |
867 | Нет, потому что мы умудрились с вами захардкодить имя вью в шаблонах, когда переписали матчи на на `match('input_large'`
868 | Эти шаблоны применятся _только_ для Большого Инпута. Мы можем в шаблонах использовать "маски".
869 |
870 | К имени блока вместо конкретного вью добавьте маску `*`:
871 |
872 | Было:
873 | ```javascript
874 | module.exports = function (bt) {
875 | bt.match('input_large', function (ctx) {
876 | // ...
877 | });
878 | };
879 | ```
880 |
881 | Стало:
882 | ```javascript
883 | module.exports = function (bt) {
884 | bt.match('input*', function (ctx) {
885 | // ...
886 | });
887 | };
888 | ```
889 |
890 | Вот теперь эти шаблоны будут отрабатывать для инпутов с любым `view`, то есть для любого из нижеописанных вызовов:
891 |
892 | ```javascript
893 | {
894 | block: 'input',
895 | view: 'normal',
896 | value: 'Hello'
897 | }
898 | // Сгенерит
899 |
900 | {
901 | block: 'input',
902 | view: 'active',
903 | value: 'Hello'
904 | }
905 | // Сгенерит
906 | ```
907 |
908 | Маску нужно добавлять к матчеру каждого элемента. Иначе шаблон не выполнится.
909 |
910 | Было:
911 | ```javascript
912 | bt.match('input_large', function (ctx) {
913 | // ...
914 | }
915 |
916 | bt.match('input_large__control', function (ctx) {
917 | // ...
918 | }
919 |
920 | bt.match('input_large__clear', function (ctx) {
921 | // ...
922 | }
923 | ```
924 |
925 | Стало
926 | ```javascript
927 | module.exports = function (bt) {
928 | bt.match('input*', function (ctx) {
929 | // ...
930 | }
931 |
932 | bt.match('input*__control', function (ctx) {
933 | // ...
934 | }
935 |
936 | bt.match('input*__clear', function (ctx) {
937 | // ...
938 | }
939 | ```
940 |
941 | А ещё можно задавать какое-либо `view` как дефолтное. Чтобы даже если вы не задекларировали никакого `view`, оно
942 | сгенерировалось бы само. Это нужно, когда на сайте большинство инпутов имеет, к примеру, размер `large`. Тогда мы везде
943 | декларируем блок без `view`:
944 |
945 | ```javascript
946 | {
947 | block: 'input',
948 | value: 'Hello'
949 | }
950 | ```
951 |
952 | А чтобы построился такой html ``, мы в файле с шаблонами объявляем дефолтный `view` с
953 | помощью метода `bt.setDefaultView('input', 'large');`
954 |
955 | Было:
956 | ```javascript
957 | module.exports = function (bt) {
958 |
959 | bt.match('input*', function (ctx) {
960 | // ...
961 | ```
962 |
963 | Стало
964 | ```javascript
965 | module.exports = function (bt) {
966 | bt.setDefaultView('input', 'large');
967 |
968 | bt.match('input*', function (ctx) {
969 | // ...
970 | ```
971 |
972 | И вот теперь наш блок готов работать с любыми `view`. Как только нам понадобится новое представление, мы создаём новый
973 | селектор для представления, звоём в него базовый миксин и доопределяем или переопределяем стили базового миксина.
974 |
975 | И всё.
976 |
977 | Сложное объяснение? Ээх, оно таким сложным выглядит только на бумаге. Если бы я объяснял вам это же голосом, вам бы оно
978 | показалось простым. :(
979 |
980 | ## Файловая система для `view`
981 |
982 | У нас получилось так, что все-все стили оказались в одном файле. Файл рискует стать большим. На наш взгляд это совсем
983 | неудобно. Так можно. Но нам неудобно. Мы стараемся разбивать стили на файлы согласно простому правилу: "Миксин - в
984 | отдельный файл с именем миксина, вью - в отдельный файл с именем вью".
985 |
986 | Работать с таким кодом легче, мы редактируем всегда только то, что нужно в данный момент,
987 | не рискуя задеть код соседних вью или, не дай боже, миксина. Как это организовать?
988 |
989 | Создайте файл `input_large.styl`. Это будет файл для вью "Большой Инпут". В него из файла `input.styl` перенесите
990 | селектор для этого вью:
991 |
992 | ```css
993 | .input_large {
994 | input_mixin($height = 32px);
995 |
996 | /* */
997 | &__control{
998 | font-size: 16px;
999 | }
1000 | }
1001 | ```
1002 |
1003 | А в файле `input.styl` таким образом останется только код миксина `input_mixin($height) {...}`.
1004 |
1005 | Проверяем в браузере, стили исчезли. Почему? Сейчас научу, как понять. Откройте собранный `css`-файл. Он находится по
1006 | адресу `/build/test/test.css`. Там увидите такое:
1007 |
1008 | ```css
1009 | /* /bevis/blocks/page/page.styl:begin */
1010 | .page {
1011 | background: #eaeaea url(../../blocks/page/page.png);
1012 | }
1013 | /* /bevis/blocks/page/page.styl:end */
1014 | /* /bevis/blocks/input/input.styl:begin */
1015 | /* /bevis/blocks/input/input.styl:end */
1016 | ```
1017 |
1018 | Видно, что сборщик заглядывает в файл `input.styl`, но оттуда ничего не забирает, потому что там только миксин. А вот
1019 | содержимого файла `input_large.styl` в собранном `css` нет. Как сделать, чтобы подключался и он?
1020 |
1021 | Создайте `input.deps.yaml` и напишите в нём одну строку:
1022 |
1023 | ```
1024 | - view: "*"
1025 | ```
1026 |
1027 | Теперь обновите браузер — стили для селектора `.input_large` придут. И если вы рядом положите файлик
1028 | `input_small.styl`, то и его содержимое попадёт в собранный `css`. И для любого другого вью тоже.
1029 |
1030 | Теперь всё почти хорошо. Не нравится только то, что код миксина лежит в файле с именем `input.styl`. Можно так и
1031 | оставить, если вас это устраивает. Но если миксинов будет несколько? Доведём объяснение до логического конца.
1032 |
1033 | Создадим для миксинов своё особенное место, чтобы они не мозолили глаза.
1034 |
1035 | 1. Создайте папку `blocks/input/_skin` (именно так, с подчеркиванием впереди), перенесите в неё файл `input.styl` и
1036 | сразу его переименуйте в `input_skin_base.styl`. Вместо `base` можно написать что вам больше нравится,
1037 | а вот конструкция `input_skin` в имени файла - обязательна и неизменна.
1038 |
1039 | 2. Откройте его на редактирование и замените для консистентности имя миксина с `input_mixin($height)` на
1040 | `input_skin_base($height)`.
1041 |
1042 | 3. Сразу же в файле `input_large.styl` замените вызов миксина с `input_mixin($height = 32px);` на
1043 | `input_skin_base($height = 32px);`
1044 |
1045 | Слово `skin` - служебное. Папка `_skin` - служебная, специально для хранения миксинов. Мы назвали её так,
1046 | потому что такие штуки, как миксины, напоминает нам скины из WinAmp.
1047 |
1048 | Обновите страницу в браузере. Стили исчезли? Исчезли, но не все. Остались только те, чтобы в `input_large.styl`. А код
1049 | миксина не попал внутрь селектора `.input_large {...}`. Почему? Потому что вью "Большой Инпут" не знает, где ему брать
1050 | код миксина. Он не знает, что нужно открыть папку с названием `_skin`, открыть там файл с именем `input_skin_base.styl`
1051 | и в нём искать одноименный миксин.
1052 |
1053 | Обо всём этом сборщику нужно сказать явно. Через зависимость. Нужно сказать, что такие-то вью инпута (а лучше,
1054 | что любые вью инпута) зависят от таких-то миксинов. Создадим файл `input_view.deps.yaml`
1055 |
1056 | ```
1057 | - skin: "*"
1058 | required: true
1059 | ```
1060 |
1061 | Этот файл надо понимать так: "Любые вью инпутов (это следует из названия `deps`-файла ) зависят от любых миксинов,
1062 | находящихся в папке `_skin` (это следует из инструкции `- skin: "*"`). При этом миксины должны попасть в сборку раньше,
1063 | чем сами вью (об этом сообщает инструкция `required: true`)"
1064 |
1065 | Обновляем страницу в браузере, стили вернулись.
1066 |
1067 | Теперь в папке `/blocks/input/` мы видим только файлы разных отображений. А миксины спрятаны в папке
1068 | `/blocks/input/_skin`. И если нам понадобится, мы можем сделать ещё скинов, сложить в папку `_skin` и звать их из
1069 | разных вью. Получается, что папка `_skin` стала своеобразной библиотекой скинов.
1070 |
1071 | Уфф. Про вью и миксины это всё.
1072 |
1073 | Итого, миксин `input_skin_base.styl` хранит всё стили текстового поля, общие для любых отображений блока,
1074 | а `input_large.styl` хранит `CSS`-селектор для отображения типа "Большой Инпут".
1075 |
1076 | Нужно другое отображение, например,"Маленький Инпут"? Быстрый путь такой - скопировать и отредатировать:
1077 |
1078 | ```
1079 | cd blocks/input
1080 | cp input_large.styl input_small.styl
1081 | ```
1082 | Отредактировать `input_small.styl`:
1083 |
1084 | Было:
1085 | ```css
1086 | .input_large {
1087 | input_skin_base($height = 32px);
1088 |
1089 | /* */
1090 | &__control{
1091 | font-size: 16px;
1092 | }
1093 | }
1094 | ```
1095 |
1096 | Стало:
1097 | ```css
1098 | .input_small {
1099 | input_skin_base($height = 22px);
1100 |
1101 | /* */
1102 | &__control{
1103 | font-size: 13px;
1104 | }
1105 | }
1106 | ```
1107 |
1108 | Теперь вы знаете всё, чтобы создавать отображения блока.
1109 |
1110 | Нам осталось исправить одну недоделку в файле `input_skin_base.styl`. Посмотрим, как сейчас выглядит селектор
1111 | крестика для очищения поля:
1112 |
1113 | ```css
1114 | &__clear {
1115 | position: absolute;
1116 | top: 0;
1117 | right: 0;
1118 | z-index: 2;
1119 |
1120 | width: $height;
1121 | height: $height;
1122 | vertical-align: middle;
1123 |
1124 | background: url('images/clear-14.png') 50% 50% no-repeat;
1125 | cursor: pointer;
1126 | opacity: (ie != 8) ? 0.3 : 1;
1127 |
1128 | &:hover {
1129 | background-image: url('images/clear-14-hover.png');
1130 | opacity: 1;
1131 | }
1132 |
1133 | &._visible {
1134 | display: block;
1135 | }
1136 | }
1137 | ```
1138 |
1139 | Во-первых, у вас нет этих картинок. Возьмите их у нас и скопируйте в проект в папку `/blocks/input/images`.
1140 |
1141 | * [крестик 10 px](http://bevis-ui.github.io/docs/images/input__clear/clear-10.png)
1142 | * [крестик 10 px для hover](http://bevis-ui.github.io/docs/images/input__clear/clear-10-hover.png)
1143 | * [крестик 14 px](http://bevis-ui.github.io/docs/images/input__clear/clear-14.png)
1144 | * [крестик 14 px для hover](http://bevis-ui.github.io/docs/images/input__clear/clear-14-hover.png)
1145 |
1146 | Во-вторых, стили сейчас написаны так глупо, что картинка `clear-14.png` будет применяться для обоих вью - для
1147 | "Большого" и "Маленького Инпута". Это место хочется параметризировать: для "Большого Инпута" показывать
1148 | картинку `clear-14.png`, а для "Маленького Инпута" - `clear-10.png`. Что делаем? Конечно, новый миксин! :)
1149 |
1150 | Создаём файл `_skin/input_skin_clear.styl` и выносим в него из файла `input_skin_base.styl` только тот код, который
1151 | можно параметризировать:
1152 |
1153 | ```css
1154 | input_skin_clear($size) {
1155 | background: url('images/clear-' + $size + '.png') 50% 50% no-repeat;
1156 |
1157 | &:hover {
1158 | background-image: url('images/clear-' + $size + '-hover.png');
1159 | opacity: 1;
1160 | }
1161 | }
1162 | ```
1163 |
1164 | Мы понимаем, что параметризировать здесь надо свойство `background-image`, а не составное `background`. Но у нас нет
1165 | цели показать вам максимально оптимизированный `CSS`-код. Мы понимаем, что не нам вас учить, как оптимально писать
1166 | CSS-код :)
1167 |
1168 | Осталось только позвать этот миксин в каждом вью. Добавим вызовы, каждый со своим размером иконки
1169 |
1170 | В `input_small.styl` передаём параметр `$size = 10`
1171 |
1172 | ```css
1173 | .input_small {
1174 | input_skin_base($height = 22px);
1175 | input_skin_clear($size = 10);
1176 |
1177 | /* */
1178 | &__control{
1179 | font-size: 13px;
1180 | }
1181 | }
1182 | ```
1183 |
1184 | А в `input_large.styl` передаём `$size = 14`:
1185 |
1186 | ```css
1187 | .input_large {
1188 | input_skin_base($height = 32px);
1189 | input_skin_clear($size = 14);
1190 |
1191 | /* */
1192 | &__control{
1193 | font-size: 16px;
1194 | }
1195 | }
1196 | ```
1197 |
1198 | Вы столкнулись с неконсистентностью передачи параметров. Почему-то один параметр мы передаём с `px`, а другой без. Но это
1199 | не имеет значения для этой страницы документации. `Stylus` позволяет делать так. Как именно будете писать вы -
1200 | выбор за вами.
1201 |
1202 | Всё. На этом пора закругляться о стилях.
1203 |
1204 | Получилось как-то многословно. Этот мануал требовал от вас внимательности и аккуратности. Мы старались описывать всё
1205 | обстоятельно, по шагам, без спешки, чтобы вы успевали редактировать с нами синхронно.
1206 |
1207 | Вот, что должно было [у вас получиться](https://github.com/bevis-ui/bevis-stub/tree/input-and-form/blocks/input).
1208 |
1209 | Сверьте ваш и наш код сейчас, потому что дальше мы будем программировать поведение текстового поля. Если так случилось,
1210 | что ваш код отличается от нашего - возможно, вы что-то не сделали во время этого урока. Или, что весьма вероятно, мы
1211 | что-то написали не очень понятно или, вообще, с явными ошибками. Если вы нашли ошибку, напишите нам скорее :)
1212 |
1213 | А теперь посмотрим, как мы программируем поведение блока. Нам [сюда](yblock.md).
1214 |
--------------------------------------------------------------------------------
/practice/dependencies.md:
--------------------------------------------------------------------------------
1 | # Зависимости
2 |
3 | Зависимости - это очень просто. На вашем сайте есть форма, а в форме есть текстовое поле. Форма без инпута -
4 | бессмысленная штука. Форма - это блок, который отвечает за отправку данных, а инпут совершенно другой блок,
5 | который только и умеет, что получать данные от пользователя. Корректная работы формы зависит от работы инпута. Это
6 | и есть зависимость :)
7 |
8 | Блоки могут зависеть друг от друга по поведению и (или) по отображению.
9 |
10 | Например, инпут слушает на себе событие `onchange`, и как только оно случилось - триггерит кастомное событие "Меня
11 | заполнили текстом!", которое слушает форма. Как только форма его услышала, она отправляет данные на сервер. Это
12 | простой пример "зависимости по поведению". Такие зависимости мы резолвим с
13 | помощью [модульной системы](https://github.com/ymaps/modules). Если вы не знакомы с модулями,
14 | прочтите [эту статью](modules.md) и всё поймёте.
15 |
16 |
17 | Но бывает так, что один блок использует другой не из поведения, а из отображения. Возьмём ту же форму,
18 | её стили описаны в одном стилевом файле, а стили инпута в другом. Логично где-то указать,
19 | что форма зависит от инпута. Зачем разработчику, подключающему форму на свою страницу, заботиться о том,
20 | какие блоки понадобятся для корректной работы формы? Пусть сама форма про себя волнуется.
21 |
22 | Зависимость описывается простым текстом, почти человеческим языком.
23 |
24 | Например, блок `header` использует блок `input`. Создаем файл `header.deps.yaml` со следующим содержимым:
25 |
26 | ```yaml
27 | - input
28 | ```
29 |
30 | Да, мы используем формат `YAML` для описания зависимостей. Он немногословный, простой и понятный. Хотите разобраться
31 | досконально, прочитайте [руководство](http://en.wikipedia.org/wiki/YAML). А в общих чертах мы и сами вам расскажем :)
32 |
33 | ## Структура
34 |
35 | Каждый `deps.yaml`-файл - это массив. Каждый элемент массива начинается со знака минус (`-`).
36 |
37 | Пример зависимостей шапки от двух блоков:
38 |
39 | ```yaml
40 | - input
41 | - button
42 | ```
43 | Этим кодом мы объявляем: шапка зависит от инпута и кнопки.
44 |
45 | Если вам надо указать не только имя блока, а ещё что-то (например `view` блока), тогда зависимость описываем чуть
46 | детальнее: указываем `block` и `view`, но по-прежнему это делается после служебного знака (`-`). А чтобы парсер
47 | `yaml` смог понять, где заканчивается первый элемент массива и начинается второй, делаем отступ от начала строки,
48 | выравнивая содержимое:
49 |
50 | ```yaml
51 | - block: button
52 | view: active
53 | - block: input
54 | view: icons
55 | ```
56 |
57 | Вот и всё про `yaml`-синтаксис. Просто и интуитивно.
58 |
59 | ## Обязательные зависимости
60 |
61 | Флагом `required` в значении `yes` обозначаются те зависимости, код которых при сборке должен предшествовать коду
62 | текущего блока.
63 |
64 | Пример использования `required`:
65 |
66 | ```yaml
67 | - block: design
68 | required: yes
69 | ```
70 |
71 | Если этот кода написан для шапки, в результате сборки код блока `design` окажется в собранном файле раньше,
72 | чем код самой шапки.
73 |
--------------------------------------------------------------------------------
/practice/enb.md:
--------------------------------------------------------------------------------
1 | # Настраиваем сборку проекта
2 |
3 | в разработке
4 |
--------------------------------------------------------------------------------
/practice/i18n.md:
--------------------------------------------------------------------------------
1 | # Как настроить локализацию?
2 |
3 | Если под локализацией вы подразумеваете то же, что и мы — перевод сайта на другие языки, — тогда вы, как и мы,
4 | заблуждаетесь.
5 |
6 | Читаем Википедию.
7 |
8 | **Локализация** — процесс адаптации веб-приложения к культуре какой-либо страны.
9 |
10 | **Интернационализация** — технологические приёмы разработки, упрощающие адаптацию продукта к языковым и культурным
11 | особенностям регионов, отличных от того, в котором разрабатывался продукт.
12 |
13 | Важное отличие одного от другого можно сформулировать совсем коротко. Интернационализация — это адаптация продукта
14 | для использования _в разных странах_, в то время как локализация — это добавление специальных функций для
15 | использования _в какой-то определённой стране_.
16 |
17 | Мы, разработчики сайтов, говорим "локализация", но обычно подразумеваем только лишь перевод сайта на разные языки.
18 | А так как этот документ именно о переводах, придётся его переозаглавить.
19 |
20 | # Как настроить интернационализацию?
21 |
22 | В английском языке для слова `internationalization` принято сокращение `i18n`. При этом число `18` означает количество
23 | пропущенных между `i` и `n` букв. Для Локализации `Localization` применяют сокращение `L10n`. Заглавная буква `L`
24 | используется чтобы не путать с `i` в `L10n`, число `10` — количество букв между `L` и `n`.
25 |
26 | Неудивительно, что инструменты, которые так или иначе связаны с интернационализацией, называются с использованием
27 | сокращения `i18n`. В `BEViS` мы не стали ломать привычки. Наши инструменты для интернационализации называются так же.
28 |
29 | Освоить их проще простого, если вы вместе с нами освоили задания из последнего занятия про [yblock](yblock.md).
30 |
31 | Теперь у вас есть страница `/pages/test-page/test-page.page.js`, на которой к этому моменту задекларирован один
32 | единственный блок `form`. Добавим в блок новую опцию `titleText`, в которую передадим произвольный текст. Его-то мы и
33 | будем переводить на разные языки.
34 |
35 | У вас должна получиться такая структура:
36 |
37 | ```javascript
38 | module.exports = function (pages) {
39 | pages.declare('test-page', function (params) {
40 | var options = params.options;
41 | return {
42 | block: 'page',
43 | title: 'test page',
44 | styles: [
45 | {url: options.assetsPath + '.css'}
46 | ],
47 | scripts: [
48 | {url: options.assetsPath + '.' + params.lang + '.js'}
49 | ],
50 | body: [
51 | {
52 | block: 'form',
53 | titleText: 'Лучший кофе на дороге' // <---------- Новая опция
54 | }
55 | ]
56 | };
57 | });
58 | }
59 | ```
60 |
61 | Допишем шаблон для формы. Будем принимать опцию `titleText` и создавать элемент блока `head`, который в `DOM`-дереве
62 | будет представлен заголовком `h1` с переданным текстом.
63 |
64 | Было:
65 | ```javascript
66 | module.exports = function (bt) {
67 |
68 | bt.match('form', function (ctx) {
69 | ctx.enableAutoInit();
70 |
71 | ctx.setTag('span');
72 |
73 | ctx.setContent('Содержимое блока');
74 | });
75 |
76 | };
77 | ```
78 |
79 | Стало:
80 | ```javascript
81 | module.exports = function (bt) {
82 |
83 | bt.match('form', function (ctx) {
84 | ctx.enableAutoInit();
85 |
86 | ctx.setTag('span');
87 |
88 | var title = ctx.getParam('titleText');
89 | ctx.setContent(
90 | {
91 | elem: 'head',
92 | text: title
93 | }
94 | );
95 | });
96 |
97 | bt.match('form__head', function (ctx) {
98 | ctx.setTag('h1');
99 |
100 | var headText = ctx.getParam('text');
101 | ctx.setContent(headText);
102 | });
103 |
104 | };
105 | ```
106 |
107 | _Я нарочно дал совершенно разные имена опциям шаблона и локальным переменным, чтобы вам было легче проследить,
108 | как текст "Лучший кофе на дороге" передаётся от опции `ctx.getParam('titleText')` до установки контента в
109 | `ctx.setContent(headText);`. Проследите-проследите :) Освежите свои знания про генерацию `HTML` из `BT`-шаблонов._
110 |
111 | Обновим страницу `localhost:8080/test` в браузере и увидим огромный заголовок про кофе. Я вижу, а вы?
112 |
113 | Очевидно, что в опцию `titleText` передавать готовую строку на русском языке нельзя. Надо передавать какой-то ключ,
114 | по которому нужно _где-то_ получать русский текст для интерфейса на русском языке, английский текст для
115 | интерфейса на английском языке, украинский для украинского, турецкий для турецкого и т.д.
116 |
117 | Слово _где-то_ я выделил курсивом, чтобы сразу о нём поговорить. Невозможно локализировать сайт, не имея некоего
118 | хранилища переведённых текстов. В `BEViS` такое хранилище мы организовали в виде файлов. Создадим его вместе с вами.
119 |
120 | Создайте папку `blocks/i18n/_keyset`.
121 |
122 | Это будет специальное место, где будут храниться переводы всех блоков. Почему внутри папки `blocks/i18n` мы создали
123 | подпапку `_keyset` — об этом не задумывайтесь. Это всё обсудим позже. Сейчас переходим к самому интересному.
124 |
125 | Мы готовим переводы для блока `Form`, поэтому создадим набор ключей для этого блока. Набор ключей будем называть
126 | кисетом (если на ваш взгляд слово "кейсет" звучит лучше, называйте так). Давайте создадим кисет для формы.
127 |
128 | Создаём папку `blocks/i18n/_keyset/i18n_keyset_form.i18n`. Опять же, пока не задумываемся, почему такое странное
129 | название у директории. А внутри неё создаём единственный (пока!) языковой-файл `ru.js` и пишем в него единственный
130 | (пока!) ключ с переводом:
131 |
132 | `blocks/i18n/_keyset/i18n_keyset_form.i18n/ru.js`:
133 | ```javascript
134 | module.exports = {
135 | "form": {
136 | "title-text": "Лучший кофе на дороге"
137 | }
138 | };
139 | ```
140 |
141 | Остановимся на содержимом подробнее.
142 |
143 | Языковой файл `ru.js` — это обычный `Node.js`-модуль, который экспортирует объект с единственным полем, который мы
144 | назвали `form`. Мы назвали его так, чтобы имя кисета перекликалось с именем блока `Form`, который мы видим на
145 | web-странице. Когда имена перекликаются, так легче.
146 |
147 | В этом файле сейчас мы описали только один ключ `title-text` и его значением сделали тот самый текст про кофе. Для
148 | наглядности добавим ещё один ключ. Сейчас он нам не нужен, а позже пригодится.
149 |
150 | `blocks/i18n/_keyset/i18n_keyset_form.i18n/ru.js`:
151 | ```javascript
152 | module.exports = {
153 | "form": {
154 | "title-text": "Лучший кофе на дороге",
155 | "hint-content": "Иллюзионист иллюстрирует иллюзорно"
156 | }
157 | };
158 | ```
159 |
160 | Хранилище готово. Конечно, оно готово только для русского языка. Сразу приготовим тексты для английского
161 | языка. Создадим в той же папке файл с именем `en.js`.
162 |
163 | `blocks/i18n/_keyset/i18n_keyset_form.i18n/en.js`:
164 | ```javascript
165 | module.exports = {
166 | "form": {
167 | "title-text": "The best coffee on the road",
168 | "hint-content": "Illusionist illustrates illusorily"
169 | }
170 | };
171 | ```
172 |
173 | А теперь давайте сделаем так, чтобы приложение ходило за текстом про кофе именно в это хранилище.
174 |
175 | ## Локализация внутри декларации страницы
176 |
177 | Первое, что мы делаем всегда после создания нового блока, указываем зависимость — сообщаем, что такой-то
178 | блок будет использоваться на такой-то странице.
179 |
180 | Редактируем `pages/test-page/test-page.deps.yaml`, добавляемость зависимость к блоку `i18n` и указываем, что на этой
181 | странице используется кисет `form`:
182 | ```
183 | - page
184 | - block: block
185 | elem: auto-init
186 | - input
187 | - super-input
188 | - form
189 | - block: i18n
190 | keyset: form # <----- Вот он
191 | ```
192 |
193 | Всё. Теперь в декларации страницы убираем русский текст и вместо него просим ключ из хранилища:
194 |
195 | ```javascript
196 | module.exports = function (pages) {
197 | pages.declare('test-page', function (params) {
198 | var options = params.options;
199 | return {
200 | block: 'page',
201 | title: 'test page',
202 | styles: [
203 | {url: options.assetsPath + '.css'}
204 | ],
205 | scripts: [
206 | {url: options.assetsPath + '.' + params.lang + '.js'}
207 | ],
208 | body: [
209 | {
210 | block: 'form',
211 | titleText: pages.i18n('form', 'title-text') // <------ Здесь зовём i18n-ключ
212 | }
213 | ]
214 | };
215 | });
216 | };
217 | ```
218 |
219 | Обновляем страницу в браузере, видим текст про кофе. Как понять, а вдруг это кеш, и у нас ничего не получилось? Можно
220 | изменить текст в кисете и проверить. Редактируем?
221 |
222 | `blocks/i18n/_keyset/i18n_keyset_form.i18n/ru.js`:
223 | ```javascript
224 | module.exports = {
225 | "form": {
226 | "title-text": "Лучший кофе на дороге — отхлебнёшь, протянешь ноги! (с)", // <----- Изменили значение
227 | "hint-content": "Иллюзионист иллюстрирует иллюзорно"
228 | }
229 | };
230 | ```
231 |
232 | Обновляем в браузере. Есть новый текст! Оглушительный успех ;)
233 |
234 | Мы позвали перевод, находясь внутри декларации страницы, с помощью метода `pages.i18n`, который умеет принимать два
235 | параметра — имя кисета и имя ключа:
236 | ```javascript
237 | pages.i18n('form', 'title-text')
238 | ```
239 |
240 | Этот метод ходит в хранилище переводов, находит там нужный кисет `form`, а в нём нужный ключ `title-text` и
241 | возвращает значение ключа `Лучший кофе на ...`
242 |
243 | Похожий метод (похожий, но не этот же самый) доступен нам не только в декларации страницы, но и в `bt`-шаблонах
244 | любого блока.
245 |
246 | ## Локализация в `bt`-шаблонах
247 |
248 | Откроем `blocks/form/form.bt.js` и добавим новый элемент `hint`:
249 | ```javascript
250 | module.exports = function (bt) {
251 |
252 | bt.match('form', function (ctx) {
253 | ctx.enableAutoInit();
254 |
255 | ctx.setTag('span');
256 |
257 | var title = ctx.getParam('titleText');
258 | ctx.setContent([
259 | {
260 | elem: 'head',
261 | text: title
262 | },
263 | {
264 | elem: 'hint', // <----- Создали новый элемент
265 | textHint: bt.lib.i18n('form', 'hint-content') // <----- Позвали ключ для него
266 | }
267 | ]);
268 | });
269 |
270 | // Шаблон для элемента hint
271 | bt.match('form__hint', function (ctx) {
272 | ctx.setContent(ctx.getParam('textHint'));
273 | });
274 |
275 | bt.match('form__head', function (ctx) {
276 | ctx.setTag('h1');
277 |
278 | var headText = ctx.getParam('text');
279 | ctx.setContent(headText);
280 | });
281 |
282 | };
283 | ```
284 |
285 | В шаблоне формы мы сгенерили новый элемент `hint` и в его опцию `textHint` передали значение ключа `hint-content`.
286 | Когда обновим браузер, увидим под заголовком текст про иллюзиониста.
287 |
288 | Успех!
289 |
290 | Мы рассмотрели способы, как звать ключи, находясь в декларации страницы или в `bt`-шаблонах блока.
291 |
292 | Декларация страницы работает на сервере, создаёт статический `HTML`, который летит пользователю в браузер.
293 | `BT`-шаблоны умеют работать как на сервере, так и в браузере.
294 |
295 | А как пользоваться `i18n` в клиентском `javascript`?
296 |
297 | ## Локализация в клиентских `js`-модулях
298 |
299 | ```javascript
300 | modules.define(
301 | 'form',
302 | [
303 | 'inherit',
304 | 'block',
305 | 'input',
306 | 'super-input',
307 | 'y-i18n' // <---------- Позвали клиентский модуль y-i18n
308 | ],
309 | function (
310 | provide,
311 | inherit,
312 | YBlock,
313 | Input,
314 | SuperInput,
315 | i18n // <---------- Приняли его в качестве аргумента i18n
316 | ) {
317 | var form = inherit(YBlock, {
318 | __constructor: function () {
319 | this.__base.apply(this, arguments);
320 |
321 | var formDomNode = this.getDomNode();
322 |
323 | // Создаём инпут
324 | this._greetingInput = new Input({
325 | value: 'Привет, Бивис',
326 | name: 'loginField',
327 | placeholder: 'Инпут на сайте',
328 |
329 | parentNode: formDomNode
330 | });
331 | this._greetingInput.on('input-submitted', this._onInputSubmitted, this);
332 |
333 | // Создаём инпут для пароля
334 | this._passwordInput = new SuperInput({
335 | name: 'passwordField',
336 | type: 'password',
337 | placeholder: i18n('form', 'hint-content'), // <---------- Позвали любой ключ
338 |
339 | parentNode: formDomNode
340 | });
341 | this._passwordInput.on('input-submitted', this._onInputSubmitted, this);
342 | },
343 |
344 | /**
345 | * Реагирует на нажатие клавиши Enter в `Input`
346 | * @param {YEventEmitter} e
347 | */
348 | _onInputSubmitted: function (e) {
349 | console.log('Форма поймала событие на Input = ', e);
350 | }
351 | }, {
352 | getBlockName: function () {
353 | return 'form';
354 | }
355 | });
356 |
357 | provide(form);
358 | });
359 | ```
360 |
361 | _Напомню, что классы `Input`, `SuperInput` и класс `Form` мы создавали с вами вместе, когда читали статью про
362 | [клиентское программирование с помощью YBlock](yblock.md). Код этих компонентов можно увидеть в отдельной ветке
363 | [input-and-form](https://github.com/bevis-ui/bevis-stub/tree/input-and-form/blocks)._
364 |
365 | В коде выше мы смотрим на компонент `Form`. Для того, чтобы использовать локализацию в клиентсокм модуле, я минимально
366 | изменил класс `Form`, пометив все изменения комментариями. В прейсхолдер инпута для пароля я позвал строку про
367 | иллюзиониста. Обновите страницу в браузере. Успех?
368 |
369 | Обратите внимание на важный момент.
370 |
371 | В проекте есть две сущности с почти одинаковыми названиями.
372 |
373 | 1. В движке проекта _уже есть_ специальный модуль с именем `y-i18n`, который вы можете звать в своих клиентских
374 | модулях, как в примере выше. Этот модуль создавать не нужно, он просто есть по умолчанию, находится где-то в файлах
375 | `BEViS`-движка. Он умеет умеет ходить в ваше хранилище переводов и забирать оттуда строки на разных языках.
376 | 2. И есть _ваше хранилище_ переводов, которое находится по адресу `blocks/i18n`. Хранилище представлено неким блоком
377 | с именем `i18n`, и его дочерним элементом `_keyset`. Мы специально реализовали хранилище в виде так называемого
378 | `блока`, чтобы им удобно было пользоваться. Напомню, чтобы им пользоваться, нужно в зависимостях страницы указать
379 | имя этого блока и имя нужного кисета, как мы делали это выше в файле `pages/test/test-page.deps.yaml`:
380 | ```
381 | - page
382 | - block: block
383 | elem: auto-init
384 | - input
385 | - super-input
386 | - form
387 | - block: i18n # <----- Позвали на страницу хранилище переводов
388 | keyset: form # <----- Указали, каким кисетом будем пользоваться.
389 | ```
390 |
391 | Можно указывать столько кисетов, сколько хочется, если в `yaml`-файле перечислить их в виде массива:
392 | ```
393 | - block: i18n
394 | keyset: [form, any-other] # <----- Позвали кисеты form и any-other
395 | ```
396 |
397 | Зависимости можно указывать не только в страничных файлах, а в блоках. Например, мы знаем что модуль `Form`
398 | использует внутри себя переводы. Тогда кисеты можно подтягивать не в страничном `pages/test/test-page.deps.yaml`, а в
399 | блочном `blocks/form/form.deps.yaml`. Этого файла ещё нет. Создадим его и напишем в нём:
400 | ```
401 | - block: i18n
402 | keyset: [form, any-other]
403 | ```
404 |
405 | А в страничном `pages/test/test-page.deps.yaml` удалим эту зависимость и обновим страницу в браузере. Ничего не
406 | сломалось, иллюзионист что-то там иллюстрирует в поле для пароля. Ну, отлично. Получается, что любой блок в своём
407 | файле зависимостей может указать, от каких других блоков именно он зависит. Если зависимости указывать таким образом,
408 | гарантирован порядок — на страницу придут только те модули, которые нужно конкретным блокам на текущей странице.
409 |
410 | Обратили внимание, что мы указали зависимость к кисету `any-other`, а его нет на файловой системе? Ну и ничего не
411 | сломалось при обновлении страницы. А что случится, если мы в клиентском модуле попробуем получить ключ из этого
412 | кисета? Пробуем? Давайте отредактируем в модуле `Form` вызов конструктора инпута для пароля:
413 |
414 | ```javascript
415 | // Создаём инпут для пароля
416 | this._passwordInput = new SuperInput({
417 | name: 'passwordField',
418 | type: 'password',
419 | placeholder: i18n('any-other', 'my-key'), // <---- Позвали несуществующий ключ 'my-key' из
420 | // несуществующего кисета 'any-other'
421 |
422 | parentNode: formDomNode
423 | });
424 | ```
425 |
426 | После рефреша страницы в консоли видим ошибку:
427 | ```
428 | Uncaught Error: form init error: Keyset "any-other" was not found.
429 | ```
430 |
431 | Отлично, приложение помогает понять, что мы что-то забыли. Давайте создадим этот кисет и будем заканчивать.
432 |
433 | Кстати, я не сказал, в приложении есть скрипт для создания кисетов из терминала:
434 | ```
435 | make keyset
436 | # Введите имя keyset
437 | ```
438 |
439 | Этот скрипт создаёт в хранилище нужную папку для кисета и два файла с переводами - `ru.js` и `en.js`. В каждом создаёт
440 | один фейковый ключ, который назван незатейливо `my-key`. В английском файле значение ключа установлено в `my value`,
441 | а в русском в `моё значение`. Я же говорю, кисет незатейливый :)
442 |
443 | После рефреша в поле пароля видим плейсхолдер "моё значение".
444 |
445 | Всё, теперь вы знаете всё, чтобы сделать сайт интернационализированным. Осталось самое главное :)
446 |
447 | ## Как переключать интерфейс на другой язык?
448 |
449 | Конечно, мы наделали кучу файлов с переводами, умеем из них получать строки, умеем это делать не только в страничном
450 | `btjson`, но и в `bt`-шаблонах и даже в клиентских `javascript`-модулях. А как переключить приложение на другой язык?
451 | Ведь это же самое главное :)
452 |
453 | Откроем `pages/test/test-page.page.js`:
454 | ```javascript
455 | module.exports = function (pages) {
456 | pages.declare('test-page', function (params) {
457 | var options = params.options;
458 | return {
459 | block: 'page',
460 | title: 'test page',
461 | styles: [
462 | {url: options.assetsPath + '.css'}
463 | ],
464 | scripts: [
465 | {url: options.assetsPath + '.' + params.lang + '.js'} // <----- Здесь!
466 | ],
467 | body: [
468 | {
469 | block: 'form',
470 | titleText: pages.i18n('form', 'title-text')
471 | }
472 | ]
473 | };
474 | });
475 | };
476 | ```
477 |
478 | Видите строку `{url: options.assetsPath + '.' + params.lang + '.js'}`?
479 |
480 | Именно переменная `params.lang` отвечает за то, какой язык будет использоваться в _клиентских `js`-модулях_.
481 | Я не спроста выделил курсивом последние слова. Убедимся на опыте. Сделайте быструю замену прямо сейчас. Замените
482 | `params.lang` на `'en'`
483 |
484 | Было:
485 | ```javascript
486 | {url: options.assetsPath + '.' + params.lang + '.js'}
487 | ```
488 |
489 | Стало:
490 | ```javascript
491 | {url: options.assetsPath + '.en.js'}
492 | ```
493 |
494 | И обновите страницу в браузере. Инпуты не отренедрились? А в консоли вообще ошибка!
495 | ```
496 | GET 404 http://localhost:8080/build/test/_test.en.js
497 | ```
498 |
499 | Файл не загрузился, нет его! Хм, так и должно быть. Ведь файла с расширением `*.en.js` не существует. Убедитесь
500 | сами, загляните в папку для собраннх файлов `build/test`. Там есть только файлы с расширением `*.ru.js`. Дело в том,
501 | что `BEViS` ничего не знает о том, что (оказывается!) он должен собирать ещё и файлы с расширением `*.en.js`. Так
502 | давайте скажем ему.
503 |
504 | Это происходит в файле `.enb/make.js`. Это очень важный файл, в котором мы описываем конфигурацию сборки наших
505 | файлов. Там много разного написано, нас же сейчас интересует одна единственная строка:
506 | ```javascript
507 | config.setLanguages(['ru']);
508 | ```
509 |
510 | Именно в ней мы указываем, с какими языками собирать файлы. Допишите туда `en`, чтобы получилось так:
511 | ```javascript
512 | config.setLanguages(['ru', 'en']);
513 | ```
514 |
515 | Обновим страницу. Так, инпуты появились. Но тексты на русском языке!
516 |
517 | Погодите-погодите, один текст всё-таки на английском - тот, который внутри инпута, который мы устанавливали из
518 | клиентского модуля `form.js`. Убедились?
519 |
520 | Всё верно, так и должно быть — страничный `build/test/_test.en.js` отвечает за локализацию в клиентских модулях и
521 | клиентских `bt`-шаблонах.
522 |
523 | ## Локализация клиентских `bt`-шаблонов и клиентских `js`-модулей
524 |
525 | Запомним. Вся локализация *на клиенте* зависит от того, какой файл прилетел в браузер. Прилетит `test.ru.js` — будет
526 | сайт на русском. Прилетит `test.en.js` — будет сайт на английском. Но! Локализуются только те переменные, которые
527 | использовались в клиентском `javascript` — в клиентских `js`-модулях и в тех `bt`-шаблонах, которые работают в
528 | браузере (не на сервере!). Не очень понятно, да?
529 |
530 |
531 | Смотрите. В теле нашей страницы в конце `` загружается файл `build/test/_test.en.js`. Это собранный файл,
532 | состоящий из множества клиентских блочных модулей, таких как `blocks/form/form.js` и `blocks/input/input.js` (и ещё
533 | множество других файлов). Когда страничный файл загружается в память браузера, он инициализирует все модули, в том
534 | числе и модуль `form.js`. А в его конструкторе в том числе написано такое:
535 |
536 | ```javascript
537 | // Создаём инпут для пароля
538 | this._passwordInput = new SuperInput({
539 | name: 'passwordField',
540 | type: 'password',
541 | placeholder: i18n('any-other', 'my-key'),
542 |
543 | parentNode: formDomNode
544 | });
545 | ```
546 |
547 | Что происходит после `new SuperInput({/*здесь btjson блока*/})`, вы знаете. На лету в памяти браузера (то есть,
548 | "на клиенте") `BEViS` выполняет `bt`-шаблоны, которые из `btjson`-а строят `HTML` — те самые шаблоны, которые
549 | описаны в файле `input.bt.js`. Вы должны уже помнить, что в собранный `build/test/_test.en.js` попадают не только все
550 | клиентские модули, а ещё и все `bt`-шаблоны, которые умеют строить `HTML` не только на сервере, но ещё и налету на
551 | клиенте (то есть, "в памяти браузера").
552 |
553 | Ещё раз.
554 |
555 | Вся локализация *на клиенте* зависит от того, какой файл прилетел в браузер. Прилетит `test.ru.js` — будет сайт на
556 | русском. Прилетит `test.en.js` — будет сайт на английском. Но! Локализуются только те переменные, которые
557 | использовались в клиентском `javascript` — в клиентских `js`-модулях и в тех `bt`-шаблонах, которые работают в
558 | браузере (не на сервере!).
559 |
560 | А как быть с сервером?
561 |
562 | ## Локализация серверных `bt`-шаблонов
563 |
564 | Во-первых, если мы делаем локализацию сайта, странно хардкодить язык в декларацию страницы. Поэтому, давайте откатим
565 | последнее изменение в `pages/test/test-page.page.js`, чтобы в строке снова появилась переменная `params.lang`
566 |
567 | ```javascript
568 | {url: options.assetsPath + '.' + params.lang + '.js'}
569 | ```
570 |
571 | Если сейчас обновим браузер и посмотрим в сгенеренный `HTML`, увидим, что благодаря этой строке запрашивается файл
572 | `build/test/_test.ru.js`. Следовательно в переменной `params.lang` хранится значение `ru`. А где же оно
573 | устанавливается?
574 |
575 | Откройте файл `server/page.js`. Кстати, вам известно, как устроено `Node.js`-приложение? Если известно, вас этот файл
576 | не испугает . А если вы ещё не знакомы с устройством `Node.js`-приложений, не пугайтесь — вам пока не надо понимать,
577 | что происходит в этом файле. Я и сам в нём не всё понимаю ;)
578 |
579 | А если серьёзно, этот файл — часть `Node.js`-приложения, отвечающая за генерацию статического `HTML`. И
580 | непосредственно генерация происходит в методе `handle`:
581 | ```javascript
582 | /**
583 | * Build page
584 | *
585 | * @returns {Promise} promise
586 | */
587 | handle: function () {
588 | return vow.all([
589 | this._getPages(),
590 | this._getTemplate(),
591 | this._getI18n()
592 | ])
593 | .spread(function (pages, bt, buildI18n) {
594 | var i18n = buildI18n();
595 | pages.setI18n(i18n);
596 | bt.lib.i18n = i18n;
597 |
598 | return pages.exec(this._pageName, {
599 | query: this._query,
600 | options: this._getPageOptions(),
601 | lang: this._lang
602 | }).then(function (btJson) {
603 | return this._applyTemplate(btJson, bt);
604 | }.bind(this));
605 | }.bind(this));
606 | }
607 | ```
608 |
609 | Вдумываться в то, что здесь написано, не надо. Вообще! Если вы ещё не знакомы с `Promises`, вам совершенно непонятно,
610 | что тут происходит. Да вообще не важно! Нам бы с языком разобраться и понять, где формируется объект `params` и его
611 | переменная `params.lang`. А смотрите, именно в этом методе они и формируются. Вот где:
612 | ```javascript
613 | return pages.exec(this._pageName, { // ----
614 | query: this._query, // |
615 | options: this._getPageOptions(), // > Вот этот объект!
616 | lang: this._lang // |
617 | }) // ----
618 | ```
619 |
620 | А потом этот самый объект приходит в файл `pages/test/test-page.page.js` в качества параметра `params`:
621 | ```javascript
622 | module.exports = function (pages) {
623 | pages.declare('test-page', function (params) {
624 | var options = params.options; // ^------------- Вот этот же объект!
625 | return {
626 | block: 'page',
627 | title: 'test page',
628 | styles: [
629 | {url: options.assetsPath + '.css'}
630 | ],
631 | scripts: [
632 | {url: options.assetsPath + '.' + params.lang + '.js'} // <----- Здесь!
633 | ],
634 | body: [
635 | {
636 | block: 'form',
637 | titleText: pages.i18n('form', 'title-text')
638 | }
639 | ]
640 | };
641 | });
642 | };
643 | ```
644 |
645 | А в нём есть поле `lang`, которое получает значение из внутренней переменной объекта `Page` (см.строку
646 | `lang:this._lang`). А что хранится в `this._lang`? Смотрим выше, в конструктор `Page` и видим интересное:
647 | ```javascript
648 | /**
649 | * Page
650 | *
651 | * @param {String} id Relative path to page (pages/index, for example)
652 | * @param {String} pageName Page name.
653 | * @param {Request} req Node request Object
654 | * @param {Response} res Node response Object
655 | */
656 | __constructor: function (id, pageName, req, res) {
657 | this._id = id;
658 | this._pageName = pageName;
659 | this._req = req;
660 | this._res = res;
661 | this._lang = req.query.lang || 'ru'; // <-------- Вот оно!
662 | this._query = req.query;
663 | }
664 | ```
665 |
666 | Теперь понятно. Если не определена переменная `req.query.lang`, тогда установи `this._lang` в `ru`!
667 |
668 | Объект `req` — это объект `Node.js`-сервера, в котором собрана вся информация о `HTTP`-запросе в сервер. Там есть о
669 | запросе всё — имя страницы, которую вы запросили в браузере, все параметры запроса (я имею в виду `GET`, `POST`),
670 | все куки, `HTTP`-заголовки, — вообще всё!
671 |
672 | Конкретно параметры запроса хранятся в поле `req.query`. Следовательно, если мы запросим страницу `test` с
673 | `GET`-параметром `lang=en`, тогда наш сайт станет англоязычным, что ли? Ну-ка, попробуем:
674 | ```
675 | http://localhost:8080/test?lang=en
676 | ```
677 |
678 | Да, оглушительный успех! Я вижу страницу с английскими текстами.
679 |
680 | Ну, кроме первого инпута, в котором по-русски написано "Привет, Бивис". Но это не ошибка, мы же сами захардкодили эти
681 | тексты в `blocks/form/form.js`.
682 |
683 | Вот и ответ на вопрос. Чтобы серверные `bt`-шаблоны получили нужный язык, нужно его установить в параметрах генерации
684 | статического `HTML`, то есть здесь:
685 | ```javascript
686 | /**
687 | * Build page
688 | *
689 | * @returns {Promise} promise
690 | */
691 | handle: function () {
692 | return vow.all([
693 | this._getPages(),
694 | this._getTemplate(),
695 | this._getI18n()
696 | ])
697 | .spread(function (pages, bt, buildI18n) {
698 | var i18n = buildI18n();
699 | pages.setI18n(i18n);
700 | bt.lib.i18n = i18n;
701 |
702 | return pages.exec(this._pageName, {
703 | query: this._query,
704 | options: this._getPageOptions(),
705 | lang: this._lang // <----------------------- Здесь!
706 | }).then(function (btJson) {
707 | return this._applyTemplate(btJson, bt);
708 | }.bind(this));
709 | }.bind(this));
710 | }
711 | ```
712 |
713 | Если в `BEViS`-приложении в запросе передавать `GET`-параметр `lang`, тогда можно легко менять язык интерфейса.
714 | Правда, есть ошибка, которую мы исправлять в `bevis-stub` не будем. Сделайте запрос к несуществующему языку, например к
715 | турецкому:
716 | ```
717 | http://localhost:8080/test?lang=tr
718 | ```
719 |
720 | Приложение упадёт с "Internal error", а в терминале вывалится сообщение типа такого:
721 | ```
722 | 00:14:31 - error: Error: There is no tech for target build/test/test.lang.tr.js.
723 | ```
724 |
725 | Нет такого языка, вот вам и ошибка.
726 |
727 | Но исправлять это мы не будем, потому что от проекта к проекту способы получать язык пользователя разнятся. Кто-то
728 | будет читать язык из `GET`-параметра (и ещё не факт, что он будет называться `lang`), кто-то будет читать его из кук, в
729 | каком-то проекте язык интерфейса будет приходить из базы данных. Поэтому это остаётся на ваше усмотрение. Ну и да,
730 | придётся чуть больше узнать про то, как устроено `Node.js`-приложение, почитать мануалы.
731 |
732 | Главное — (и вы это, бесспорно, давно поняли) чтобы серверные `bt`-шаблоны сгенерили `HTML` с нужным языком, следует
733 | в `server/page .js` в методе `handle` установить параметр `lang`.
734 |
735 | Всё, теперь всё. Теперь вы можете перевести свой сайт на разные языки. А когда это сделаете, предлагаю вам продолжить
736 | практикум.
737 |
738 | Я хочу вам рассказать о том, [как использовать `MVC`-паттерн в `BEViS`](mvc-app.md). Хочется порядка в коде проекта,
739 | хочется привычных абстракций в виде контроллеров, моделей и вью. Это делается быстро и несложно. Посмотрим вместе?
740 |
741 | Сделайте вкусного чая и приходите, я жду вас [здесь](mvc-app.md).
742 |
743 | ----
744 |
745 | PS. Переводы проекта хранятся в папке `blocks/i18n/_keyset/*`. В Яндексе мы не создаём руками эти кисеты. У нас есть
746 | специальный внутренний веб-сервис, в котором можно завести кисеты, создать там через веб-интерфейс нужные ключи на
747 | русском языке и "призвать" переводчиков, чтобы они перевели ключи на нужные языки. А потом мы с помощью некоторого
748 | скрипта обращаемся по `HTTP` к этому сервису, как к бекенду, и получаем переводы из него в виде готовых файлов
749 | `ru.js`, `en.js`, `tr.js`. И этот же скрипт сам раскладывает файлы переводов внутри папки `blocks/i18n/_keyset/*`.
750 |
751 | Возможно, в своей команде вы тоже сделаете какой-то такой сервис, с ним удобно. А если по каким-то причинам не
752 | сделаете, не беда, хранилище переводов можно поддерживать и руками.
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
--------------------------------------------------------------------------------
/practice/modules.md:
--------------------------------------------------------------------------------
1 | # Модульная система YMaps Modules
2 |
3 | Что такое Модульная система? Зачем и для кого? Как этим пользоваться?
4 |
5 | На эти вопросы я давно хотел получить ответы, но стеснялся их задать. Если вы такой же робкий, этот документ для вас :)
6 |
7 | Что это?
8 | --------
9 | Вы web-разработчик, пишете сайты; на страницах верстаете формы с контролами,
10 | различные компоненты, большие и маленькие; для большинства из них
11 | пишете клиентский `js`-код. Вот и продолжайте это делать. Пока у вас нет своего клиентского `js`-кода,
12 | модульная система вам не нужна. Никому не нужен пустой фантик без конфеты внутри.
13 |
14 | Модуль - это фантик, в который вы заворачиваете конфету. На фантике написано имя конфеты и больше ничего. Оберните
15 | код пяти своих контролов каждый в свой отдельный фантик, у вас получится 5 модулей — система из пяти **ваших**
16 | модулей. Конфеты ссыпают в корзинку для сладостей, откуда конфеты удобно таскать детям. Модульная система и есть
17 | такая корзинка.
18 |
19 | Зачем это и для кого?
20 | --------------------
21 | Как вы связываете `js`-компоненты между собой? Например, вы объявляете на странице блок с формой авторизации,
22 | пишете для неё клиентский `js`-код, в котором описываете поведение формы: если нажали на кнопку сабмита — отправь
23 | авторизационные данные аяксом, а не через стандартный `form.submit()`.
24 |
25 | И предположим, в вашем проекте уже есть специальный компонент "кнопка", в котором реализовано всё поведение кнопки,
26 | например, "если меня нажали, выкинь вверх некое событие, чтобы форма могла это событие услышать и отреагировать нужным
27 | образом".
28 |
29 | То есть, ваша "форма" должна уметь найти кнопку и слушать на ней событие клика.
30 |
31 | Вопрос: а как вы определяете, что кнопка **уже готова** взаимодействовать с вашей формой? Наверное, в `js`-файле вы
32 | сначала описываете функциональность для кнопки, а ниже - для формы? А если js-код кнопки и формы лежат в разных
33 | файлах? Тогда вы так настраиваете сборку финального `js`-файла, чтобы всё равно код формы оказался описан ниже
34 | кода кнопки?
35 |
36 | Модульная система избавляет нас от этих трудностей. Она избавляет нас от головной боли отслеживать зависимости между
37 | компонентами, поддерживать руками эти зависимости в актуальном состоянии.
38 |
39 | Если вы оформляете форму в виде `ymaps-модуля`, вы просто декларируете зависимость от кнопки, и знаете,
40 | что код формы будет активирован _только тогда_, когда активирован код кнопки. Вам не нужно самому отслеживать
41 | готовность ваших компонентов взаимодействовать друг с другом. Это делает за вас модульная система.
42 |
43 |
44 | Как этим пользоваться?
45 | ---------------------
46 | Это болванка `ymaps-модуля`:
47 |
48 | ````javascript
49 | modules.define(
50 | 'A',
51 | ['B', 'C'],
52 | function(provide, b, c) {
53 | var a = {};
54 |
55 | provide(a);
56 | }
57 | );
58 | ````
59 |
60 | В этом примере ваш собственный код - единственная строка:
61 | ````javascript
62 | var a = {};
63 | ````
64 |
65 | В реальности, конечно, вместо этой строчки вы напишете намного больше кода, но для знакомства с модульной системой
66 | этого достаточно.
67 |
68 | Всё остальное кроме этой строки и есть модуль. Модуль - это своего рода обёртка, фантик — несколько специальных
69 | инструкций, которые и делают из вашего кода модуль. Внутрь фантика вы складываете нужные ингредиенты (зависимости от
70 | других модулей), перемешиваете, как вам хочется (пишете код своего компонента), фантик скручиваете и втыкаете
71 | в бок зубочистку-флажок с именем модуля. Вы сделали модуль `А`.
72 |
73 | Давайте посмотрим на конфету внимательно.
74 |
75 | Так объявляется модуль. Позовите метод `define()`
76 | ````javascript
77 | modules.define(
78 | );
79 | ````
80 |
81 | Скажите модулю: "Тебя зовут `А`"
82 | ````javascript
83 | modules.define(
84 | 'A'
85 | );
86 | ````
87 |
88 | Потом добавьте: "Вообще-то ты зависишь от двух модулей, их зовут`В` и `С`"
89 | ````javascript
90 | modules.define(
91 | 'A',
92 | ['B', 'C'],
93 | );
94 | ````
95 |
96 | Опишите код модуля в анонимной функции. В него параметрами `b` и `c` придёт код модулей `B` и `С`, и вы сможете
97 | использовать их внутри модуля.
98 | ````javascript
99 | modules.define(
100 | 'A',
101 | ['B', 'C'],
102 | function(provide, b, c) {
103 |
104 | }
105 | );
106 | ````
107 |
108 | Чтобы другие модули могли использовать ваш модуль, пусть он громко произнесёт вслух:
109 | "Модуль `А` - это я!". Это делается вызовом функции `provide(а)`, в которую аргументом передаётся ссылка на ваш класс
110 | или объект, то есть на сам программный код вашего модуля.
111 | ````javascript
112 | modules.define(
113 | 'A',
114 | ['B', 'C'],
115 | function(provide, b, c) {
116 | var a = {};
117 |
118 | provide(a);
119 | }
120 | );
121 | ````
122 |
123 | И, наконец, напишите свой компонент. Пусть это будет что-нибудь хорошее ;)
124 | ````javascript
125 | modules.define(
126 | 'A',
127 | ['B', 'C'],
128 | function(provide, b, c) {
129 | var a = 'Yandex is a good company';
130 |
131 | provide(a);
132 | }
133 | );
134 | ````
135 |
136 | Всё. Сложности закончились :)
137 |
138 | А вот так, к примеру, может выглядеть код модуля, отвечающий за форму логина. Создадим фантик,
139 | позовём в него кнопку и что-нибудь запрограммируем внутри:
140 | ````javascript
141 | modules.define(
142 | 'form',
143 | ['button'],
144 | function(provide, button) {
145 | var form = getElementById('my-form');
146 | form.on('submit', onSubmited);
147 |
148 | function onSubmited() {
149 | if ($(button).css('disabled')) {
150 | return false;
151 | }
152 | form.submit();
153 | }
154 |
155 | provide(form);
156 | }
157 | );
158 | ````
159 |
160 | Это выдуманный пример, скорее всего он нерабочий, но для иллюстрации годится. В теле анонимной функции мы программируем
161 | поведение формы в зависимости от того, активна или неактивна кнопка. А после этого методом `provide(form)`
162 | выбрасываем вверх флаг: "Форма — это я". Теперь и этот модуль может быть вызван другими модулями. Метод `provide` -
163 | это выкинутая вверх рука и всемирно известное: "Свободная касса!"
164 |
165 | Возвращаясь к аналогии с конфетой, фантик — это метод `define()` с анонимной функцией внутри. В фантик воткнута
166 | зубочистка, за которую конфетку удобно взять — это метод `provide()`. Модуль независим и работоспособен, потому что
167 | он будет запущен только после того, как все зависимости будут предоставлены (правильные программисты используют
168 | термины "разрезолвлены" или "разрешены") внутрь модуля.
169 |
170 | Осталось сказать, что зависимости резолвятся асинхронно, дерево зависимостей строится в рантайме,
171 | модули можно переопределять и доопределять.
172 |
173 | Если захотите заглянуть в репозиторий модульной системы, проследуйте сюда:
174 | [https://github.com/ymaps/modules](https://github.com/ymaps/modules/blob/master/README.ru.md), и возвращайтесь к
175 | документации про `BEViS`, я вас там жду.
176 |
--------------------------------------------------------------------------------
/practice/new-block.md:
--------------------------------------------------------------------------------
1 | Первый шаг мы [сделали](new-page.md), теперь у нас есть страница `test-page.page.js`.
2 |
3 | Блок, который мы создадим в этом руководстве, будем проверять именно на ней. Если вы её создали,
4 | тогда она выглядит точно как у нас:
5 |
6 | ```javascript
7 | module.exports = function (pages) {
8 | pages.declare('index-page', function (params) {
9 | var options = params.options;
10 | return {
11 | block: 'page',
12 | title: 'index page',
13 | styles: [
14 | {url: options.assetsPath + '.css'}
15 | ],
16 | scripts: [
17 | {url: options.assetsPath + '.' + params.lang + '.js'}
18 | ],
19 | body: [
20 | // здесь ваши блоки
21 | ]
22 | };
23 | });
24 | }
25 | ```
26 |
27 | Если такой страницы у вас ещё нет, [создайте](new-page.md) её и возвращайтесь.
28 |
29 | # Как создать новый блок
30 |
31 | Сделаем несложный блок, например, `input`. Перед тем, как его делать, попробуем его позвать. А вдруг?
32 |
33 | ```javascript
34 | module.exports = function (pages) {
35 | pages.declare('test-page', function (params) {
36 | var options = params.options;
37 | return {
38 | block: 'page',
39 | title: 'test page',
40 | styles: [
41 | {url: options.assetsPath + '.css'}
42 | ],
43 | scripts: [
44 | {url: options.assetsPath + '.' + params.lang + '.js'}
45 | ],
46 | body: [
47 | {block: 'input'} // Добавьте вызов блока
48 | ]
49 | };
50 | });
51 | }
52 | ```
53 |
54 | Запустим приложение командой `make` и запросим в браузере нашу страницу `localhost:8080/test/`. Успех? В браузере
55 | появился блок, хотя мы его даже не создавали?
56 |
57 | ```
58 |
59 |
60 |
61 |
62 | test page
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | ```
71 |
72 | Шаблонизатор сам создал блок. Где-то внутри bt-шаблонизатора есть дефолтный шаблон,
73 | который генерит html-тег для вашего блока, даже если вы сами не писали никакого шаблона.
74 |
75 | Это бывает удобно, но не в нашем случае. Блок `input` должен быть представлен в `HTML` как тег `input`,
76 | поэтому нам без своего шаблона не обойтись. И без стилей и без js-поведения нам не обойтись. Поэтому создадим сразу
77 | нужные файлы блока.
78 |
79 | ## Создаём файлы
80 |
81 |
82 | В консоли прервём приложение сочетанием клавиш `Ctrl-C` и воспользуемся инструментом для создания нового блока. Это небольшой bash-скрипт, который задаст вам один
83 | вопрос, а после создаст сам блок:
84 | ```
85 | make block
86 | # Введите имя блока: input
87 | ```
88 |
89 | На файловой системе появится директория `blocks/input`, а в ней несколько файлов.
90 |
91 | Откройте в редакторе файл `blocks/input/input.bt.js`:
92 |
93 | ```javascript
94 | module.exports = function (bt) {
95 |
96 | bt.match('input', function (ctx) {
97 | ctx.setTag('span');
98 |
99 | ctx.setContent('Содержимое блока');
100 | });
101 |
102 | };
103 | ```
104 |
105 | У блока появился шаблон, который придумали мы, не вы. Это всё исправим, но сначала убедимся,
106 | что новый шаблон наложился на блок `input`. Как это проверить? Обратите внимание,
107 | в результате успешного наложения шаблона в `HTML` должен измениться тег, потому что в шаблоне есть два метода - один
108 | меняет тег блоку, второй задаёт конент.
109 |
110 | Сейчас там такой код:
111 | ```html
112 |
113 | ```
114 |
115 | После наложения шаблона должен стать таким:
116 | ```html
117 | 'Содержимое блока'
118 | ```
119 |
120 | Опять запустим проект `make`, обновим в браузере `localhost:8080/test/` и откроем `HTML`-код страницы. Ничего не
121 | произошло — тег `div` не исчез, `span` вместо него не пояился, и контента нет и в помине.
122 |
123 | Ошибка ли?
124 |
125 | Смотрим в консоль. Нет сообщений об ошибке. Значит, не ошибка, это мы что-то не доделали.
126 |
127 | Мы не описали зависимость. Что такое зависимости, [прочтите здесь](dependencies.md), сейчас для этого самое время.
128 | А если уже читали, тогда самое время добавить зависимость.
129 |
130 | На странице `test` должен появиться блок `input`, следовательно страница "зависит" от блока.
131 | Пока мы не сообщим странице эту зависимость, странице неоткуда брать информацию об этом.
132 |
133 | Откройте файл `pages/test-page/test-page.deps.yaml`. В нём только одна строка
134 |
135 | ```
136 | - page
137 | ```
138 |
139 | Вы уже понимаете, это означает, что страница зависит от блока `page`.
140 |
141 | И правда, в декларации страницы мы раньше звали только блок `page`, а теперь внутрь `page` мы позовём блок `input`,
142 | поэтому опишем дополнительную зависимость:
143 |
144 | ```
145 | - page
146 | - input
147 | ```
148 |
149 | Обновите в браузере страницу и загляните в `HTML`. Успех? Мы видим, что шаблон отработал,
150 | потому что html-тег у блока изменился так, как мы того и хотели:
151 |
152 | ```html
153 | Содержимое блока
154 | ```
155 |
156 | Теперь напишем актуальный шаблон, который должен уметь...
157 |
158 | а) генерить блок в виде тега `input`
159 |
160 | б) устанавливать в тег значение, которые мы задали ему при декларации блока.
161 |
162 |
163 | # Пишем актуальный шаблон
164 |
165 | Попробуем набросать первый вариант, открываем файл `blocks/input/input.bt.js`. Пока он выглядит как заготовка:
166 |
167 | ```javascript
168 | module.exports = function (bt) {
169 | bt.match('input', function (ctx) {
170 | ctx.setTag('span');
171 |
172 | ctx.setContent('Содержимое блока');
173 | });
174 | };
175 | ```
176 |
177 | Редактируем:
178 |
179 | ```javascript
180 | module.exports = function (bt) {
181 | bt.match('input', function (ctx) {
182 | ctx.setTag('input');
183 |
184 | var currentValue = ctx.getParam('value');
185 | ctx.setAttr('value', currentValue);
186 | });
187 | };
188 | ```
189 |
190 | Разберём по-строчно.
191 |
192 | ```javascript
193 | ctx.setTag('input');
194 | ```
195 | Метод `setTag()` генерит в конечном `HTML` тег, заданный единственным параметром.
196 |
197 |
198 | ```javascript
199 | var currentValue = ctx.getParam('value');
200 | ```
201 | Метод `getParam()` принимает некий параметр "value" (откуда он, что за параметр - пока не важно) и сохраняет его
202 | значение в локальную переменную `currentValue`.
203 |
204 | ```javascript
205 | ctx.setAttr('value', currentValue);
206 | ```
207 | Метод `setAttr()` генерит в конечном `HTML` атрибут, заданный первым параметром и устанавливает в атрибут строку,
208 | переданную вторым параметром.
209 |
210 | Сохраняем, обновляем страницу, смотрим в `HTML`. Сработало, но не всё. Нет атрибута `value`
211 |
212 | ```html
213 |
214 | ```
215 |
216 | Всё правильно, потому что метод `setAttr(attrName, attrValue)` создаёт атрибут только если второй параметр
217 | `attrValue` не равен null. А у нас он как раз и равен null, потому что шаблон готов принять параметр,
218 | а мы такой параметр ему не передали.
219 |
220 | Передадим параметр. Смотрите, как у блока `input` я объявил кастомный параметр `value: 'Привет, Бивис'`
221 |
222 | ```javascript
223 | module.exports = function (pages) {
224 | pages.declare('test-page', function (params) {
225 | var options = params.options;
226 | return {
227 | block: 'page',
228 | title: 'test page',
229 | styles: [
230 | {url: options.assetsPath + '.css'}
231 | ],
232 | scripts: [
233 | {url: options.assetsPath + '.' + params.lang + '.js'}
234 | ],
235 | body: [
236 | {
237 | block: 'input',
238 | value: 'Привет, Бивис'
239 | }
240 | ]
241 | };
242 | });
243 | }
244 | ```
245 |
246 | Теперь обновите страницу в браузере. Успех? :)
247 |
248 | И в html оно выглядит так:
249 | ```html
250 |
251 | ```
252 |
253 | _Обратите внимание, в бивисе нет зарезервированных полей, кроме `block` и `view`. Вместо `value: 'Привет,
254 | Бивис'` можно было указать любое другое, например, не знаю... `inputText: 'Привет,
255 | Бивис'`. Важно лишь в шаблоне принимать этот параметр методом `ctx.getParam('inputText')`_
256 |
257 | И вот структура нашего блока почти готова. Стоп. Текстовое поле формы без атрибута `name`? Как форма передаст его
258 | значение на сервер? Ок, выльем и этот атрибут. Вы уже знаете, как это делается,
259 | мы могли бы и не показывать:
260 |
261 | ```javascript
262 | module.exports = function (bt) {
263 | bt.match('input', function (ctx) {
264 | ctx.setTag('input');
265 |
266 | var currentValue = ctx.getParam('value');
267 | ctx.setAttr('value', currentValue);
268 | ctx.setAttr('name', 'loginField');
269 | });
270 | };
271 | ```
272 |
273 | И в `HTML` оно выглядит так:
274 | ```html
275 |
276 | ```
277 |
278 | Мы захардкодили имя будущего атрибута. Не зазорно, если мы точно знаем, что имя у инпута никогда не поменяется.
279 | В таком случае мы думаем так: "Почему бы и не прибить гвоздём"?
280 |
281 | А если серьёзно, этим шагом мы не столько "хардкодим", сколько изолируем от внешнего мира внутреннее
282 | устройство блока, никому не разрешаем извне менять значение атрибута `name`. Мы сознательно ограничиваем API
283 | этого блока.
284 |
285 | Вы можете делать API блока более открытым или... менее открытым. Это решать конкретно вам. Мы стараемся открывать
286 | блок наружу только самым минимальным образом, чтобы сохранить максимальный контроль над
287 | внутренним устройством блока.
288 |
289 | ## Стилизация
290 |
291 | Откройте в редакторе файл `blocks/input/input.styl`, там написано:
292 |
293 | ```css
294 | .input {
295 | /* здесь стили блока */
296 | }
297 | ```
298 |
299 | Напишите какое-нибудь свойство и обновите страницу в браузере. Я только что написал `border: 1px solid red;` и
300 | проверил — у меня в браузере вокруг блока появилась рамка. У вас тоже успех?
301 |
302 | В Stylus можно делать много разных вещей. Некоторые из них мы сознательно не используем. Точнее,
303 | мы используем лишь некоторые, которые не смогут повредить стабильности стилей. Но это тема
304 | [отдельного документа](css.md), там мы опишем подходы, которые мы себе разрешаем, и опишем вредные возможности,
305 | которые мы себе строго запрещаем.
306 |
307 | А вообще про Stylus стоит почитать на официальном
308 | сайте [http://learnboost.github.io/stylus/](http://learnboost.github.io/stylus/)
309 |
310 | Пока сделаем вид, что в `styl`-файлах мы пишем чистый `CSS` и перейдём к самому интересному. К программированию
311 | поведения блока.
312 |
313 | ## Проектирование
314 |
315 | Перед программированием поведения очень полезно спроектировать блок.
316 | Искусство проектирования может показаться нетривиальным и избыточным.
317 | Но проектирование — это то, чем мы занимаемся в процессе разработки осознанно или неосознанно,
318 | хотим мы того или нет. Делаем мы это в голове в виде мыслей или на бумаге в виде схем и диаграмм,
319 | но каждый из нас этим занимается постоянно. Займёмся и сейчас.
320 |
321 | Изобразим UML-диаграмму будущего блока. Это необязательно для вас, но мы сделаем,
322 | чтобы явно показать ход наших мыслей.
323 |
324 | Что умеет блок `input`? Пользователь вводит в него текст для того, чтобы форма отправила это значение на сервер.
325 | Следовательно, инпут должен уметь отдавать введённое значение.
326 |
327 | Отобразим это в PlantUML-диаграмме с помощью простого описания:
328 |
329 | ```puml
330 | class Input {
331 | +getValue(): String
332 | }
333 | ```
334 |
335 | Чтобы сделать из этого красивую картинку, отправляем этот код в http://www.plantuml.com/plantuml/form :
336 |
337 |
338 |
339 | Теперь красиво и наглядно :)
340 |
341 | Двигаемся дальше. Инпут ещё должен уметь принимать в себя значение, следовательно добавим в него метод-сеттер и
342 | сделаем ещё одну красивую картинку:
343 |
344 |
345 |
346 | На этом остановимся, не будем раньше времени усложнять блок.
347 |
348 | ----
349 |
350 | _Скорее всего вы не проектируете свои приложения с помощью `UML`-схем. Честно говоря, мы бы тоже с удовольствием не
351 | использовали эти схемы ;)_
352 |
353 | _Нам трудно без формальных схем проектировать большие приложения, поэтому мы этим и занимаемся. Если вам
354 | интересно, мы читаем про `UML` на сайте [http://plantuml.sourceforge.net/](http://plantuml.sourceforge.net/),
355 | а красивые картинки генерим здесь: [http://www.plantuml.com/plantuml/form](http://www.plantuml.com/plantuml/form)_
356 |
357 | ----
358 |
359 | Что мы понимаем, когда смотрим на составленные `UML`-схемы? Мы понимаем, что наш блок пока довольно простой и умеет
360 | он только лишь значение отдавать или получать. Удерживая в голове эту информацию, как вектор,
361 | мы начинаем программировать поведение.
362 |
363 | ## Поведение
364 |
365 | Чтобы блок мог взаимодействовать с пользователем (или с другими блоками без участия пользователя),
366 | Бивису необходимо сообщить, что блок интерактивный, что для него написано `js`-поведение.
367 |
368 | Для этого мы помечаем блок, устанавливаем на нём "метку", по которой бивис-движок понимает,
369 | что для блока написано поведение.
370 |
371 | Такую метку ставит метод `enableAutoInit()`.
372 |
373 | ```javascript
374 | module.exports = function (bt) {
375 | bt.match('input', function (ctx) {
376 | ctx.enableAutoInit();
377 | ctx.setTag('input');
378 | ctx.setAttr('value', ctx.getParam('value'));
379 | ctx.setAttr('name', 'loginField');
380 | });
381 | };
382 | ```
383 |
384 | Он создаёт в `HTML`-коде блока специальный класс `_init`, по которому Бивис понимает,
385 | что блок интерактивный и его нужно инициализировать.
386 |
387 | ```html
388 |
389 | ```
390 |
391 | Теперь запрограммируем поведение. Создайте файл `input.js` в директории `blocks/input` и скопируйте в него заготовку:
392 |
393 | ```javascript
394 | modules.define(
395 | 'input',
396 | ['inherit', 'block'],
397 | function (provide, inherit, YBlock) {
398 | var Input = inherit(YBlock, {
399 | // инстанс-методы
400 | }, {
401 | // статические методы
402 | });
403 |
404 | provide(Input);
405 | }
406 | );
407 | ```
408 |
409 | Это модуль. Модульных систем много разных, Бивис использует
410 | [Ymaps Modules](modules.md), которые придумали и разработали
411 | разработчики из Яндекс.Карт. Пройдите по ссылке, прочитайте.
412 | В основе модулей лежит очень простая идея, и в документации к `Ymaps Modules` простым языком описывается,
413 | зачем модульная система нужна, как её использовать, и что конкретно означает каждая строчка из кода,
414 | который мы привели выше.
415 |
416 | Не стали читать? ;) Там чтения на пять минут. Мы всё таки настаиваем, вам понравится.
417 | [Прочитайте](modules.md).
418 |
419 |
420 | Прочитали? Тогда вы понимаете, что код внутри анонимной функции - это и есть код,
421 | который программирует поведение вашего `input`. Можно было бы программировать его по старинке,
422 | навешивая события на DOM-элементы самим или через jQuery-хелперы. Но мы предпочитаем работать с блоками через
423 | абстракции, чтобы не зависеть от HTML-реализации блока.
424 |
425 | Поэтому мы в зависимостях модуля `input` указали два других модуля — `inherit` и `block`.
426 | Первый модуль возвращает функцию `inherit`, которая используется для создания классов и наследования (подробное
427 | описание читайте на странице автора:
428 | [https://github.com/dfilatov/node-inherit](https://github.com/dfilatov/node-inherit).
429 |
430 | Второй модуль - это класс, который и реализует механизмы абстрагирования от DOM-узлов. Это базовый визуальный блок.
431 |
432 | _Все блоки, которые вы создаёте в проекте и которые пользователь может увидеть на страницах
433 | вашего сайта (мы такие блоки называем визуальными), должны наследоваться от
434 | модуля `block` с помощью модуля `inherit`._
435 |
436 | В этом коде мы создаём класс `Input`, как наследника от класса `Yblock`, и пока больше ничего полезного не делаем.
437 |
438 | ```javascript
439 | var Input = inherit(YBlock, {
440 | // инстанс-методы
441 | }, {
442 | // статические методы
443 | });
444 | ```
445 |
446 | А этой строкой мы "провайдим" наш класс `Input` наружу. Как бы высовываем из модуля ручку наружу,
447 | за которую другие модули могут схватиться и позвать его к себе через зависимости.
448 |
449 | ```javascript
450 | provide(Input);
451 | ```
452 |
453 | Теперь добавим пустой конструктор в блок инстанс-методов:
454 |
455 | ```javascript
456 | var Input = inherit(YBlock, {
457 | __constructor: function () {
458 |
459 | }
460 |
461 | // инстанс-методы
462 | }, {
463 | // статические методы
464 | });
465 | ```
466 |
467 | Метод `__constructor()` запускается автоматически, когда создаётся экземпляр класса `Input`.
468 |
469 | Точно такой же метод есть в базовом классе `YBlock`, от которого `Input` наследуется.
470 | То есть внутри `Input` мы перезаписали конструктор, а после обязательно нужно вызвать базовый конструктор,
471 | чтобы он сделал всё, "что нужно". Сейчас не так важно, что базовый констуруктор такого "нужного" там делает. Важно
472 | запомнить, что вызов базового метода происходит так: `this.__base.apply(this, arguments)`:
473 |
474 | ```javascript
475 | var Input = inherit(YBlock, {
476 | __constructor: function () {
477 | this.__base.apply(this, arguments);
478 | }
479 |
480 | // инстанс-методы
481 | }, {
482 | // статические методы
483 | });
484 | ```
485 |
486 | Теперь переопределим статический метод, который будет возвращать имя конкретно этого блока:
487 |
488 | ```javascript
489 | var Input = inherit(YBlock, {
490 | __constructor: function () {
491 | this.__base.apply(this, arguments);
492 | }
493 |
494 | // инстанс-методы
495 | }, {
496 | // статические методы
497 |
498 | getBlockName: function () {
499 | return 'input'; // вернуть необходимо имя блока
500 | }
501 | });
502 | ```
503 |
504 | В некоторых ситуациях вам нужно будет спросить имя блока, этот метод вернёт вам актуальное имя. Следует относиться
505 | к этому методу, как к обязательному.
506 |
507 | И теперь посмотрим на нашу заготовку целиком:
508 |
509 | ```javascript
510 | modules.define(
511 | 'input', // создали модуль, дали ему имя
512 | [
513 | 'inherit', // зависимость от inherit
514 | 'block' // зависимость от YBlock
515 | ],
516 | function (
517 | provide,
518 | inherit, // получаем функцию inherit
519 | YBlock // получаем блок YBlock
520 | ) {
521 | var Input = inherit(YBlock, {
522 | // Создаём свой конструктор
523 | __constructor: function () {
524 | // Вызываем базовый конструктор
525 | this.__base.apply(this, arguments);
526 |
527 | // здесь описываем то, что происходит сразу после создания инстанса класса
528 | }
529 |
530 | // здесь опишем инстанс-методы класса Input
531 | }, {
532 | // здесь опишем статические методы
533 |
534 | // при наследовании от YBlock, необходимо переопределить статический метод getBlockName
535 | getBlockName: function () {
536 | return 'input'; // вернуть имя блока
537 | }
538 | });
539 |
540 | provide(Input); // Выставить из модуля наружу "ручку"
541 | }
542 | );
543 | ```
544 |
545 | А теперь напишем те два метода для нашего блока, ради которых всё задумано - один отдаёт значение,
546 | второй устанавливает.
547 |
548 | ```javascript
549 | modules.define(
550 | 'input',
551 | ['inherit', 'block'],
552 | function (provide, inherit, YBlock) {
553 | var Input = inherit(YBlock, {
554 | __constructor: function () {
555 | this.__base.apply(this, arguments);
556 |
557 | console.log(this.getValue());
558 | },
559 |
560 | getValue: function() {
561 | return this.getDomNode().val();
562 | },
563 |
564 |
565 | setValue: function(value) {
566 | this.getDomNode().val(value);
567 | }
568 | }, {
569 | getBlockName: function () {
570 | return 'input';
571 | }
572 | });
573 |
574 | provide(Input);
575 | });
576 | ```
577 |
578 | А чтобы проверить, что эта красота работает, в конструкторе вызовем один из методов, например `this.getValue()`,
579 | и будем ожидать, что в момент загрузки страницы в браузерной консоли появится текстовое сообщение.
580 |
581 | Проверяем? Открываем на странице firebug-консоль,
582 | обновляем страницу и... ничего не происходит. Конструктор не выполнился? А почему?
583 |
584 | А потому что по дефолту в Бивисе отключена автоматическая инициализация всех блоков. Это, главным образом,
585 | экономит процессорное время, чтобы не делать лишних действий. Решение о том, нужно ли инициализировать все блоки
586 | сразу во время загрузки страницы (или только какие-то блоки "по мере их готовности") принимаете вы — разработчик проекта.
587 |
588 | Если вы хотите, чтобы бивис инициализировал все блоки, у которых в шаблоне указано ctx.enableAutoInit(),
589 | автоматически во время загрузки страницы (а вы именно этого в данном примере хотите),
590 | вам нужно указать дополнительную зависимость для страницы.
591 |
592 | Снова откройте файл `pages/test-page/test-page.deps.yaml` и допишите:
593 |
594 | ```
595 | - page
596 | - input
597 | - block: block
598 | elem: auto-init
599 | ```
600 |
601 | Вы добавили зависимость от блока по имени `block` (случайная тавтология),
602 | а точнее от его элемента `auto-init`, который и занимается тем, что инициализирует все ваши блоки при загрузке
603 | страницы.
604 |
605 | Теперь обновите страницу в браузере. Успех? Я вижу в консоли тот самый текст,
606 | который есть сейчас в текстовом поле. Вы тоже видите? Значит, мы написали поведение для блока.
607 |
608 | # Что дальше?
609 |
610 | К этому моменту вы получили 90% всех знаний и том, как пользоваться бивисом. Вы умеете создавать структуру,
611 | писать стили и программировать поведение.
612 |
613 | Оставшиеся 10% - узкие прикладные задачи, которые мы с вами решим в отдельных руководствах:
614 |
615 | * Как обращаться за данными и передавать в блоки?
616 | * Как сделать такой же блок, но чуть-чуть другой
617 | * Как настроить локализацию в проекте
618 |
619 | В [документе про CSS](css.md) мы расскажем:
620 | * про то, как написать разные css-представления для одного блока
621 | * как описывать состояния блока
622 | * про опасные и безопасные приемы в Stylus (например, почему глобальные миксины это плохо,
623 | а глобальные переменные - это не плохо)
624 |
625 | В [документе про JS](yblock.md) мы расскажем:
626 | * про два способа инициализации блока
627 | * про доступ и операции над внутренностями блока
628 | * про навешивание событий
629 |
630 | В [документе про BT](bt.md) мы расскажем:
631 | * как строить внутреннюю структуру блока
632 | * как можно параметрами менять внутреннее устройство блока
633 | * как видозменять контекст (на примере DOCTYPE и layout-for-page из bevis-blog)
634 |
635 | А если вы не нашли ответа на свой вопрос, создайте issue прямо здесь и задайте этот вопрос,
636 | не стесняйтесь. Мы ответим так быстро, как только сможем :)
637 |
638 | Успехов вам!
639 |
--------------------------------------------------------------------------------
/practice/new-page.md:
--------------------------------------------------------------------------------
1 | # Как создать новую страницу
2 |
3 | Вы уже склонировали `bevis-stub`, запустили в корне проекта команду `make`, увидели,
4 | как качаются какие-то библиотеки (возможно ждали долго, но это только первый раз).
5 |
6 | Но вот в консоль перестали сыпаться сообщения, и последнее, что вы видите, это нечто такое:
7 |
8 | ```
9 | ENV= node_modules/.bin/enb make
10 | 19:16:25.764 - build started
11 | 19:16:25.982 - [rebuild] [build/index/index.levels] levels
12 | 19:16:25.982 - [rebuild] [build/index/index.bemdecl.js] file-provider
13 | 19:16:26.093 - [rebuild] [build/index/index.deps.js] deps-with-modules
14 | 19:16:26.093 - [rebuild] [build/index/index.files] files
15 | 19:16:26.093 - [rebuild] [build/index/index.dirs] files
16 | 19:16:26.093 - [isValid] [build/index/index.js] js
17 | 19:16:26.093 - [isValid] [build/index/index.css] css-stylus-with-autoprefixer
18 | 19:16:26.094 - [isValid] [build/index/_index.js] file-copy
19 | 19:16:26.094 - [isValid] [build/index/_index.css] file-copy
20 | 19:16:26.094 - build finished - 330ms
21 |
22 | DEBUG: Running node-supervisor with
23 | DEBUG: program 'server/boot.js'
24 | DEBUG: --watch 'server,configs'
25 | DEBUG: --ignore 'undefined'
26 | DEBUG: --extensions 'node|js'
27 | DEBUG: --exec 'node'
28 |
29 | DEBUG: Starting child process with 'node server/boot.js'
30 | DEBUG: Watching directory '~/bevis-stub/server' for changes.
31 | DEBUG: Watching directory '~/bevis-stub/configs' for changes.
32 | 19:16:26 - info: worker 23454 started
33 | 19:16:26 - info: app started on 8080
34 | 19:16:26 - info: worker 23455 started
35 | 19:16:26 - info: app started on 8080
36 | ```
37 |
38 | Это означает, что проект собран, сервер запущен на 8080 порту (но это можно и поменять), и если теперь вы в браузере
39 | запросите `localhost:8080`, то увидите нашу страничку "Привет, BEViS!".
40 |
41 | ----
42 |
43 | Всё ли так? Если нет, заведите issue, мы вам поможем :)
44 |
45 | Не понимаете всего, что написано в консоли? Не важно. Я тоже слабо понимаю ;)
46 |
47 | ----
48 |
49 | Теперь создадим страницу `test`, которая будет отвечать пользователю по адресу `localhost:8080/test/`.
50 |
51 |
52 | Кстати, сразу проверим, что нам ответит браузер, если мы прямо сейчас сделаем запрос к несуществующей странице. Пишем
53 | в адресную строку браузера `localhost:8080/test/` и видим ответ "Internal error". Это нормально, так и должно быть :)
54 |
55 | Проверяем консоль, так и есть - консоль подтверждает, что всё идёт, как задумано:
56 |
57 | ```
58 | 19:27:05 - error: Error: Target not found: build/test/test.page.js
59 | at Error ()
60 | at TargetNotFoundError (~/bevis-stub/node_modules/enb/lib/errors/target-not-found-error.js:11:23)
61 | at module.exports.inherit._resolveTarget (~/bevis-stub/node_modules/enb/lib/make.js:383:15)
62 | at ~/bevis-stub/node_modules/enb/lib/make.js:400:36
63 | at Array.forEach (native)
64 | at module.exports.inherit._resolveTargets (~/bevis-stub/node_modules/enb/lib/make.js:399:21)
65 | at module.exports.inherit.buildTargets (~/bevis-stub/node_modules/enb/lib/make.js:433:35)
66 | at ~/bevis-stub/node_modules/enb/lib/server/server-middleware.js:65:33
67 | at Array.0 (~/bevis-stub/node_modules/vow/lib/vow.js:194:56)
68 | at callFns (~/bevis-stub/node_modules/vow/lib/vow.js:452:35)
69 | ```
70 |
71 | ----
72 |
73 | Забегая вперёд, ответим на вопрос тех из вас, кто внимательно-превнимательно прочитал статьи и готов поймать нас на
74 | лжи :) Мы говорили, что разработчику нужно думать только о директориях `blocks` и `pages`. Почему же в логах
75 | фигурирует странный путь `build/test`, а не `pages/test`?
76 |
77 | Потому что папка 'pages' - это директория для живых людей, а `build` - директория для робота сборщика и для
78 | http-сервера. Сборщик складывает туда собранные файлы, а http-сервер читает их там и пересылает пользователю в
79 | браузера. Пока думать о папке `build` не надо, наше дело - папка `pages`. О собираемых файлах
80 | мы будем думать, когда захотим кастомизировать сборку. Тогда же разберемся с консольным выводом — что сообщает
81 | каждая строчка. Сейчас не надо :)
82 |
83 | ----
84 |
85 | Всё. Теперь создаём страницу `test`. В консоли прервём приложение сочетанием клавиш `Ctrl-C`. И воспользуемся инструментом для создания новой страницы. Это небольшой bash-скрипт, который задаст вам один
86 | вопрос, а после создаст саму страницу:
87 | ```
88 | make page
89 | # Введите имя страницы: test
90 | ```
91 |
92 | На файловой системе появится файл `pages/test-page/test-page.page.js` (рядом с ним ещё какой-то файл с расширением
93 | `yaml`, он вам понадобится, но не сейчас, не обращайте внимания).
94 |
95 | Откройте файл `pages/test-page/test-page.page.js` в редакторе:
96 |
97 | ```javascript
98 | module.exports = function (pages) {
99 | pages.declare('test-page', function (params) {
100 | var options = params.options;
101 | return {
102 | block: 'page',
103 | title: 'test page',
104 | styles: [
105 | {url: options.assetsPath + '.css'}
106 | ],
107 | scripts: [
108 | {url: options.assetsPath + '.' + params.lang + '.js'}
109 | ],
110 | body: [
111 | // здесь ваши блоки
112 | ]
113 | };
114 | });
115 | }
116 | ```
117 |
118 | Вот такая страница.
119 |
120 | Опять запустим проект
121 | ```
122 | make
123 | ```
124 |
125 | Обратите внимание на сообщения в консоли, они изменились. Теперь сборщик сообщает о том,
126 | что он собирает не только `build/index/*`, но ещё и `build/test/*`
127 |
128 | ```
129 | 19:40:18.739 - build started
130 | 19:40:18.954 - [rebuild] [build/index/index.levels] levels
131 | 19:40:18.954 - [rebuild] [build/test/test.levels] levels
132 | 19:40:18.954 - [rebuild] [build/index/index.bemdecl.js] file-provider
133 | 19:40:18.955 - [isValid] [build/index/index.deps.js]
134 | 19:40:18.956 - [rebuild] [build/index/index.deps.js] deps-with-modules
135 | 19:40:18.956 - [rebuild] [build/index/index.files] files
136 | 19:40:18.956 - [rebuild] [build/index/index.dirs] files
137 | 19:40:18.957 - [isValid] [build/index/index.js] js
138 | 19:40:18.957 - [isValid] [build/index/index.css] css-stylus-with-autoprefixer
139 | 19:40:18.957 - [isValid] [build/index/_index.js] file-copy
140 | 19:40:18.957 - [isValid] [build/index/_index.css] file-copy
141 | 19:40:18.958 - [rebuild] [build/test/test.bemdecl.js] file-provider
142 | 19:40:18.958 - [isValid] [build/test/test.deps.js]
143 | 19:40:18.959 - [rebuild] [build/test/test.deps.js] deps-with-modules
144 | 19:40:18.959 - [rebuild] [build/test/test.files] files
145 | 19:40:18.959 - [rebuild] [build/test/test.dirs] files
146 | 19:40:18.959 - [isValid] [build/test/test.js] js
147 | 19:40:18.959 - [isValid] [build/test/test.css] css-stylus-with-autoprefixer
148 | 19:40:18.959 - [isValid] [build/test/_test.js] file-copy
149 | 19:40:18.959 - [isValid] [build/test/_test.css] file-copy
150 | 19:40:18.960 - build finished - 221ms
151 |
152 | DEBUG: Running node-supervisor with
153 | DEBUG: program 'server/boot.js'
154 | DEBUG: --watch 'server,configs'
155 | DEBUG: --ignore 'undefined'
156 | DEBUG: --extensions 'node|js'
157 | DEBUG: --exec 'node'
158 |
159 | DEBUG: Starting child process with 'node server/boot.js'
160 | DEBUG: Watching directory '~/bevis-stub/server' for changes.
161 | DEBUG: Watching directory '~/bevis-stub/configs' for changes.
162 | 19:40:19 - info: worker 23707 started
163 | 19:40:19 - info: app started on 8080
164 | 19:40:19 - info: worker 23708 started
165 | 19:40:19 - info: app started on 8080
166 | ```
167 |
168 | Смотрим в браузере запрос к `localhost:8080/test/`
169 |
170 | Сообщения об ошибке нет, появился серый аккуратный фоновый рисунок, а если заглянуть в html-код страницы,
171 | увидим там такое:
172 |
173 | ```
174 |
175 |
176 |
177 |
178 | test page
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 | ```
187 |
188 | Это успех, у нас есть новая страница.
189 |
190 | ## Откуда взялся весь этот HTML?
191 |
192 | Ещё раз посмотрим на декларацию файла `pages/test-page/test-page.page.js`.
193 |
194 | ```javascript
195 | module.exports = function (pages) {
196 | pages.declare('index-page', function (params) {
197 | var options = params.options;
198 | return {
199 | block: 'page',
200 | title: 'index page',
201 | styles: [
202 | {url: options.assetsPath + '.css'}
203 | ],
204 | scripts: [
205 | {url: options.assetsPath + '.' + params.lang + '.js'}
206 | ],
207 | body: [
208 | // здесь ваши блоки
209 | ]
210 | };
211 | });
212 | }
213 | ```
214 |
215 | В статьях мы обращали ваше внимание, что метод `pages.declare()` возвращает бивис-блоки:
216 |
217 | ```javascript
218 | return [
219 | { block: 'header' },
220 | { block: 'authorization' }
221 | ];
222 |
223 | ```
224 |
225 | и утверждали, что этого кода достаточно, чтобы Бивис сгенерил такой `html`:
226 |
227 | ```html
228 |
238 |
248 | ```
249 |
250 | Мы не обманули, так и есть. Но если вы вдумчивый и въедливый, то уже задались вопросом,
251 | как именно генерятся обязательные для любой веб-страницы теги `html`, `head`, `body`.
252 |
253 | Мы нарочно опустили этот момент, потому что он не предсталяет отдельной ценности и сложности. Потому что структура
254 | страницы генерится отдельным блоком `page`, который поставляется сразу с бивисом,
255 | и автоматически генерится при создании заготовки новой страницы.
256 |
257 | Когда мы хотим наполнить страницу какими-то блоками, мы делаем это через декларацию этих блоков в
258 | блоке `page`, например так:
259 |
260 | ```javascript
261 | module.exports = function (pages) {
262 | pages.declare('test-page', function (params) {
263 | var options = params.options;
264 | return {
265 | block: 'page',
266 | title: 'test page',
267 | styles: [
268 | {url: options.assetsPath + '.css'}
269 | ],
270 | scripts: [
271 | {url: options.assetsPath + '.' + params.lang + '.js'}
272 | ],
273 | body: [
274 | { block: 'ВАШ БЛОК НОМЕР 1' },
275 | { block: 'ВАШ БЛОК НОМЕР 2' }
276 | ]
277 | };
278 | });
279 | }
280 | ```
281 |
282 | Блок `page`, который здесь объявлен, есть в папке `/blocks`. Он формирует минимальную `html` структуру
283 | любой страницы и имеет несколько параметров, назначение которых легко понять из имён.
284 |
285 | В `title` мы передаём заголовок страницы, который окажется в теге `` секции ``.
286 | Параметры `styles` и `scripts` принимают массив стилей и скриптов соответственно. Они генерят теги `` или
287 | `` и `