Within this tutorial we are going to develop a project using BEM platform. This tutorial requires JavaScript programming language knowledge.
We will create an online shop web page using BEM principles in CSS-writing, JavaScript, i-bem.js and BEMHTML.
All tools that we are going to use work crossplatform.
Be aware that you use suitable versions of BEM tools and libraries to run through this tutorial:
To get started with BEM-based project you need to install:
- Node.js 4+.
- Git Bash if you use Windows OS.
The quickest and easiest way to start with your own BEM project is to use an existing template project repository — project-stub. It contains the minimal configuration files and folders you will need for quick start from scratch.
We need to create a local copy of a project-stub
. You can choose any of your favorite tools to clone the project. We are going to use Git.
If your operating system is Windows, you must run the following commands in Git Bash with administrator rights. Make sure that you launch Git Bash as an administrator.
git clone https://github.com/bem/project-stub.git --depth 1 --branch v1.6.0 test-project
Go to a new project directory:
cd test-project
Remove the versions history of the origin:
rm -rf .git
Create a git repository from that directory:
git init
Install dependencies:
Note Do not use root
rights to install npm dependencies.
npm install
Build the project using ENB:
npm run build
Project build process configuration is determined in .enb/make.js
file. It defines all the technologies of blocks implementation (templates, dependencies, CSS rules and JavaScript functionality) that have to be connected to the project pages by ENB.
Run a server mode for development:
npm start
As a result, the following message appears:
Server started at 0.0.0.0:8080
This means that the ENB server is up and running. From this point on a solicited part of your project will be rebuilt automatically every time you reload a web page. The result is available on http://localhost:8080/desktop.bundles/index/index.html.
Getting stuck?
If 8080
port is already in use by another program, you can redefine it using -p
option.
npm start -- -p 8081
HTML layout and CSS rules of each page depend on its BEMJSON description in a pageName.bemjson.js
file. In BEM terms it is called declaration.
A BEMJSON declaration describes a page structure in BEM terms: blocks, elements and modifiers. BEMHTML template engine processes BEMJSON declaration to create HTML layout of a web page. BEMJSON file describes the web page as a BEM tree that provides all dependencies for creation of technology bundles.
Blocks are our building materials for each page. You can use the already created blocks from the libraries or create a new one by yourself.
Every block can be implemented in the following technologies: css
/styl
, js
, bemhtml.js
, deps.js
, bemjson.js
. We will call them block implementation technology files. Blocks implementation sets are stored in one directory. In BEM terms it is called redefinition level.
Project structure presumes that all newly created and redefined blocks are stored in a desktop.blocks
directory. The web pages blocks and all blocks that are mentioned in BEMJSON declarations are stored in a desktop.bundles
directory.
This section provides you with a step-by-step diving into BEM-based development of a web page. Previewing the steps of this tutorial will give you a clear insight into the developing process.
First of all the two main parts of our web page will be defined: a head and its main part — body.
-
We will create a head section on the page. In BEM terms we will call it a
head
block. We will declare thehead
block in a BEMJSON file of a page, create it and assign the first CSS rules to provide a correct markup within this block. -
The
head
block will be extended by a search form and a logo, created using blocks from the library. Alogo
block will be rendered as a link to bem.info site. Using CSS and BEMHTML technologies we will redefine some blocks from a library. -
A list of goods will be added to a page body using corresponding BEMHTML template. We will declare it in BEMJSON as a
goods
block. New CSS rules for this block will provide correct layout for a list of goods. -
For correct appliance of CSS rules and JavaScript implementation of a block we will provide additional dependencies in a
deps.js
file. -
We will link a third-party library to the project and extend JavaScript functionality of the block we are going to use.
-
We will find out how to extend the block functionality using a mix of blocks and elements.
-
And finally we will show you how to create a new page and start a whole project build procedure.
You have just one page in your project to begin with: index.html. Try and open it in your browser: http://localhost:8080/desktop.bundles/index/index.html.
Initially index.html page contains sample blocks which demonstrate the diversity of the bem-components libraries, connected to the project-stub.
Note! Make sure that you specify the full path to the index.html page. Otherwise some problems with relative paths could occur.
Let's add a head
block to the page. To do this declare it in a BEMJSON file of a page.
{ block: 'head' }
module.exports = {
block: 'page',
title: 'Title of the page',
favicon: '/favicon.ico',
head: [
{ elem: 'meta', attrs: { name: 'description', content: '' }},
{ elem: 'css', url: 'index.min.css' }
],
scripts: [{ elem: 'js', url: 'index.min.js' }],
content: [
{ block : 'head' }
]
};
Refresh the page to see the corresponding <div>
with a head
class.
<!DOCTYPE html>
<html class="ua_js_yes">
<head>...</head>
<body class="page">
<div class="head"></div>
<script src="index.min.js"></script>
</body>
</html>
A head
block consists of a search form, a logo and a layout block that provides correct markup within the head.
First of all put a layout
block along with its two elements (left
and right
) inside the head
block.
{
block: 'head',
content: {
block: 'layout',
content: [
{
elem: 'left',
content: 'left here'
},
{
elem: 'right',
content: 'right here'
}
]
}
}
Code sample index.bemjson.js.
Refresh the page to view the new corresponding HTML layout.
<!DOCTYPE html>
<html class="ua_js_yes">
<head>...</head>
<body class="page">
<div class="head">
<div class="layout">
<div class="layout__left">left here</div>
<div class="layout__right">right here</div>
</div>
</div>
<script src="index.min.js"></script>
</body>
</html>
This markup requires CSS rules for the layout
. In BEM terms you have to implement a block in CSS.
Note PostCSS — is linked to the project-stub
by default.
To implement a block using CSS technology you have to create a CSS file for this block in a corresponding block directory using bem create
command of bem-tools-create.
bem create -l desktop.blocks -b layout -T css
where
-l directoryName
— defines a redefinition level;-b blockName
— defines a name of the block directory for which a technology file will be created. If there is no any directory with the defined name, creates it;-T technologyName
— creates technology file for block implementation.
Running this command creates a desktop.blocks/layout/layout.css
file in a layout
directory on a desktop.blocks
redefinition level. Inside you will find a CSS selector that matches the layout block.
It is where you step in and fill the selector up with CSS properties. Or just copy and paste from Gist.
You can create the blocks manually. To do so just create a desktop.blocks/layout
directory and put there all required block implementation technology files.
A logo
block will consist of an icon with a slogan. To insert it into the head
block we have to declare a logo in an index.bemjson.js
file and add CSS rules for it.
You can use our cute BEM image for the logo or pick any other image you like.
{
elem: 'right',
content: {
block: 'logo',
content: [{
block: 'image',
url: '//varya.me/online-shop-dummy/desktop.blocks/b-logo/b-logo.png'
},
{
elem: 'slogan',
content: 'A new way of thinking'
}]
}
}
Code sample index.bemjson.js.
You do not need to implement an input
and a button
blocks yourself. They are provided by the bem-components library which is linked to the project-stub by default. So you can just declare these blocks in a desktop.bundles/index/index.bemjson.js
file.
{
elem: 'left',
content: [
{
block: 'input',
name: 'text',
val: 'Find'
},
{
block: 'button',
mods: { type: 'submit' },
content: 'Search'
}
]
}
Code sample index.bemjson.js.
Let's add Yandex search results to the search form:
{
elem: 'left',
content: {
tag: 'form',
attrs: { action: 'https://yandex.com/yandsearch' },
content: [
{
block: 'input',
name: 'text',
val: 'Find'
},
{
block: 'button',
mods: { type: 'submit' },
content: 'Search'
}
]
}
}
Code sample index.bemjson.js.
Use a link
block from the same library to render an icon with a slogan as a link to bem.info site.
{
elem: 'right',
content: {
block: 'logo',
content: [
{
block: 'link',
url: 'https://en.bem.info',
content: [
{
block: 'image',
url: 'http://varya.me/online-shop-dummy/desktop.blocks/b-logo/b-logo.png'
},
{
elem: 'slogan',
content: 'A new way of thinking'
}
]
}
]
}
}
Code sample index.bemjson.js.
The blocks input
and button
can be modified using additional CSS rules.
The CSS files for an input
block have to be stored in a desktop.blocks
redefinition level:
bem create -l desktop.blocks -b input -T css
Code sample input.css.
Run the same command for a button
block:
bem create -l desktop.blocks -b button -T css
Code sample button.css.
The same can be done for a link
block.
bem create -l desktop.blocks -b link -T css
Code sample link.css.
You need an additional HTML element — a container — to center the page. It is not necessary to create a specific block for it. The more correct way rather be to modify a page
block template at a desktop.blocks
redefinition level. This template will generate an output HTML for the entire page.
We are going to use BEMHTML as a template language.
bem create -l desktop.blocks -b page -T bemhtml.js
You can use BEMHTML templates not only to declare HTML tags to output but also to generate additional markup depending on view.
Add some code to wrap the page contents in additional container node; put it into a newly created desktop.blocks/page/page.bemhtml.js
file.
block('page')(
content()(function() {
return {
elem: 'inner',
content: applyNext()
};
})
);
Code sample page.bemhtml.js.
<!DOCTYPE html>
<html class="ua_js_yes">
<head>...</head>
<body class="page">
<div class="page__inner">
<div class="head">
<div class="layout">...</div>
</div>
<script src="index.min.js"></script>
</div>
</body>
</html>
Then implement the page
block in CSS technology to apply style to resulting markup:
bem create -l desktop.blocks -b page -T css
Copy CSS code for a newborn desktop.blocks/page/page.css
file from here.
Define a border property for the head to make it visible on a page. To do this create CSS rules fir the head
block.
bem create -l desktop.blocks -b head -T css
Once again, you can borrow contents for a desktop.blocks/head/head.css
file from here.
A web page we are going to develop contains a list of some goods. To be able to add it to the page you have to declare a goods
block in a BEMJSOM file. There are the following goods data available: title, image, price and link for each item.
{
block: 'goods',
goods: [
{
title: 'Apple iPhone 4S 32Gb',
image: 'https://mdata.yandex.net/i?path=b1004232748_img_id8368283111385023010.jpg',
price: '259',
url: '/'
},
{
title: 'Samsung Galaxy Ace S5830',
image: 'https://mdata.yandex.net/i?path=b0206005907_img_id5777488190397681906.jpg',
price: '73',
url: '/'
},
//...
}
Code sample index.bemjson.js.
This block has to be implemented in BEMHTML technology in order to be turned into an appropriate piece of HTML. It needs to be styled with CSS as well. So you can create this block with two types of technologies at once using bem create
command:
bem create -l desktop.blocks -b goods -T bemhtml.js -T css
Then write BEMHTML code in a desktop.blocks/goods/goods.bemhtml.js
file that processes BEMJSON input data into the block elements. Use a tag
mode to define an HTML representation of a goods block and its elements as well.
block('goods')(
tag()('ul'),
//...
elem('item')(
tag()('li')
),
elem('title')(
tag()('h3')
),
elem('image')(
tag()('img'),
attrs()(function() {
return { src: this.ctx.url };
})
),
elem('price')(
tag()('span')
)
);
Code sample goods.bemhtml.js.
<!DOCTYPE html>
<html class="ua_js_yes">
<head>...</head>
<body class="page">
<div class="page__inner">
<div class="head">...</div>
<ul class="goods">
<li class="goods__item">
<h3 class="goods__title">Apple iPhone 4S 32Gb</h3>
<img class="goods__image" src="http://mdata.yandex.net/
i?path=b1004232748_img_id8368283111385023010.jpg"/>
<span class="goods__price">259</span>
</li>
<li class="goods__item">...</li>
<li class="goods__item">...</li>
</ul>
<script src="index.min.js"></script>
</div>
</body>
</html>
Templates can produce not only HTML elements of a block but nested blocks as well. The example below shows you how to render a price element as a link
block of bem-components library.
An extra trick: if you would like to avoid cascade when styling the block, mark this link as an element of a goods
block.
{
elem: 'price',
content: {
block: 'link',
mix: [{ block: 'goods', elem: 'link' }],
url: item.url,
content: item.price
}
}
Code sample goods.bemhtml.js.
<ul class="goods">
<li class="goods__item">
<h3 class="goods__title">Apple iPhone 4S 32Gb</h3>
<img class="goods__image" src="http://mdata.yandex.net/
i?path=b1004232748_img_id8368283111385023010.jpg"/>
<span class="goods__price">
<a class="link goods__link" href="/">259</a>
</span>
</li>
//...
<li class="goods__item">...</li>
<li class="goods__item">...</li>
</ul>
You need to identify new goods on a page. To implement this add a verification of a new
modifier to the template: code sample.
Use this code snapshot for CSS rules.
Notice that you do not need to create CSS file for this block because it had already been generated by bem create
.
Besides declaring blocks within input BEMJSON data you need to make sure that the corresponding templates, CSS and JavaScript are linked to the page. To do this, describe block dependencies.
BEM provides a special deps.js
block technology for this.
bem create -l desktop.blocks -b goods -T deps.js
The link
block is declared in BEMHTML, not in BEMJSON, so you need to add dependencies of the link
block to the goods
.
You can use moderate dependency type codenamed shouldDeps
and declare that you need a link
block.
({
shouldDeps: [
'link'
]
})
Code sample goods.deps.js.
It would be nice to have each item in the list of goods rendered as a rectangle with a shadow. We can borrow a block from a third-party block library called j
.
It provides just one block box
that does all we need.
Install a new library. For this run the following command:
npm install tadatuta/j --save-dev
Next make your pages take blocks from the block level provided by the library . Do this by tuning a bundle configuration in a .enb/make.js
file:
levels = [
{ path: 'node_modules/bem-core/common.blocks', check: false },
{ path: 'node_modules/bem-core/desktop.blocks', check: false },
{ path: 'node_modules/bem-components/common.blocks', check: false },
{ path: 'node_modules/bem-components/desktop.blocks', check: false },
{ path: 'node_modules/bem-components/design/common.blocks', check: false },
{ path: 'node_modules/bem-components/design/desktop.blocks', check: false },
{ path: 'node_modules/j/blocks', check: false },
'common.blocks',
'desktop.blocks'
];
Code sample .enb/make.js.
You need to restart the server after changing the configuration to apply all changes. Kill the current process (Ctrl + C
) and run the server again.
Having linked the library you can use a box
block. We will use it to add a white background with a shadow to the head of the page. To do this, you need to mix the head
block with the box
block, and declare this mix
method in BEMJSON.
One of the possible ways to mix blocks is to declare this mix
in BEMJSON input data.
Here you can mix head
and box
blocks:
{
block: 'head',
mix: [{ block: 'box' }],
content: ...
}
Code sample index.bemjson.js.
<!DOCTYPE html>
<html class="ua_js_yes">
<head>...</head>
<body class="page">
<div class="page__inner">
<div class="head box">
<div class="layout">...</div>
</div>
<ul class="goods">...</ul>
<script src="index.min.js"></script>
</div>
</body>
</html>
You can also mix an element with a block. Mix could be declared in BEMJSON or in BEMHTML templates of a block.
Let's specify that each item element from a goods list has the same formatting as a head of the page. For this you need to mix each item
from the goods
block with the box
block from the already linked library.
elem: 'item',
elemMods: { new: item.new && 'yes' },
mix: [{ block: 'box' }],
content: ...
Code sample goods.bemhtml.js.
<!DOCTYPE html>
<html class="i-ua_js_yes">
<head>...</head>
<body class="page">
<div class="page__inner">
<div class="head box">...</div>
<ul class="goods">
<li class="goods__item box">...</li>
<li class="goods__item box">...</li>
<li class="goods__item box">...</li>
<li class="goods__item goods__item_new_yes box">...</li>
<li class="goods__item box">...</li>
//...
</ul>
<script src="index.min.js"></script>
</div>
</body>
</html>
Do not forget to define that a goods
block requires the box
block.
({
shouldDeps: [
'link',
'box'
]
})
Code sample goods.deps.js.
The box
block borrowed from a third-party library supports roll up animation implemented in JavaScript.
To use this functionality in a head
block you need to change the a block BEMJSON declaration and set JavaScript property to true
for the mixed box
block.
mix: [{ block: 'box', js: true }]
Code sample index.bemjson.js.
It is required to have a switcher
element in the block:
block: 'head',
mix: [{ block: 'box', js: true }],
content: [
{
block: 'layout',
//...
},
{
block: 'box',
elem: 'switcher'
}
]
Code sample index.bemjson.js.
With that you have a block with a clickable arrow-shaped element which rolls the block up in the head
block.
If you are not satisfied with the dynamic functionality provided by the box
block, you could change it. Maybe you would like it to roll up and left. Usually you cannot alter the code of the library you borrowed your block from as it is not yours. But thanks to using the i-bem.js declarative framework you can change JavaScript implementation of a block.
bem create -l desktop.blocks -b box -T js
You need to use the onSetMod
property in a desktop.blocks/box/box.js
file to specify a block reaction depending on modifier state change.
In this example the block is told to respond to setting up and removing a closed
modifier:
modules.define('box', ['i-bem-dom'], function(provide, bemDom) {
provide(bemDom.declBlock(this.name, {
onSetMod : {
'closed': {
'yes': function() {
// some functionality here
},
'': function() {
// some functionality here
}
}
}
}));
});
For example, animation can be added, like in the example linked below.
Code sample box.js.
In a BEM world a page is also a block but at the desktop.bundles
redefinition level. So you can use bem create
command for pages as well.
Create a new page of the project called contact
:
bem create -l desktop.bundles -b contact -T bemjson.js
Load a new page in a browser: http://localhost:8080/desktop.bundles/contact/contact.html.
Server builds it for us upon the first access.
While developing every time you reload a page in a browser the server rebuilds what has to be rebuilt following your changes.
To rebuild the entire project you can use the following command:
npm run build
This tutorial just lets us to open a door to the BEM world.
So, in this article, we learned how to quickly and easily get started with your own project, deployed from templates repository project-stub.
Based on the BEM principles we got to know how to create new blocks and use the existing one from the libraries, how to change blocks functionality, styles and templates.
We started use some of the BEM tools, in particular the ENB
. We found out how to start working with the BEMHTML template engine and the i-bem.js
declarative framework.