EN
Forum

Methodology

Toolbox

Platform

Dynamic projects with BEM

Introduction

Many of today's applications require dynamic capabilities, such as data exchange in real time followed by complete or partial reloading of the current page.

The purpose of this document is to show how to develop dynamic BEM projects using full stack development.

We'll look at the process of creating a dynamic app called Social Services Search Robot (SSSR). This is an app for searching for recent tweets and videos by keyword.

The project development uses:

After reading this, you will be able to develop your own BEM projects for use with dynamic data.

Note: To be able to use the examples given here, you need basic skills in:

Important: This document doesn't cover issues with layout and the client JavaScript.

You will need to install:

Important: Windows users must additionally install Git Bash.

All the code samples provided in this document were tested in the following versions:

Note: npm is the package manager that is included in Node.js.

Notation

This document uses the following notation:

Technologies

The full BEM technology stack includes:

More information about the BEMJSON format for input data.

BEMDECL

Forms a list of BEM entities that are used on the page.

In BEM, this list is called a declaration. The purpose of the declaration is to define what to include in the build and in what order.

Declarations are defined in files with the .bemdecl.js extension.

Example of a declaration from the Hello World application:

// `desktop.bundles/index/index.bemdecl.js`
exports.blocks = [
    { name: 'root' }
];  

As the example shows, the index.bemdecl.js file only defines the root block.

When using DEPS, the declaration defines the BEM entity to start the project build on.

You should treat the root block as the central "entry point" when working with a project. All the other BEM entities are included in the build by dependencies.

Example of a project build from dependencies:

root(DECL)
|
└──> root(DEPS)
     |
     └──> page(DEPS)
          |
          ├──> header(DEPS)
          |    |
          |    └──> ...
          |
          ├──> body(DEPS)
          |    |
          |    └──> ...
          |
          └──> footer(DEPS)
               |
               └──> ...

More information about the BEMDECL technology.

DEPS

Defines the dependencies between BEM entities that are spread out across the project's file system and not listed in the declaration.

Dependencies are defined as JavaScript objects in files with the .deps.js extension.

Example of dependencies for the root block from the Hello World application:

// `common.blocks/root/root.deps.js`
({
    shouldDeps: 'page'
})

More information about DEPS.

BEMTREE

Part of the bem-xjst template engine that transforms data to BEMJSON.

Templates are defined in BEMJSON format in files with the .bemtree.js extension.

Input and output of the template engine:

BEMTREE

More information about BEMTREE.

BEMHTML

Part of the bem-xjst template engine that transforms BEMJSON to HTML.

Templates are defined in files with the .bemhtml.js extension.

Input and output of the template engine:

BEMHTML

More information about BEMHTML.

i-bem.js

Client JavaScript framework for web development using the BEM methodology.

The JavaScript code is described in files with the .js extension.

It allows you to:

More information about i-bem.js.

Hello World application

Programmers have a tradition of starting out programming in a new language or framework with the Hello, World! program. The program usually prints the words "Hello, World!" to the output stream to show that it runs and can perform input/output operations.

You cancreate this application and then extend it to the desired SSSR.

To do this, you'll need a local copy of the bem-express template repository. You can use Git to make a copy.

Note: For OS X or Linux users, all commands are run in the terminal. Windows users need Git Bash. Make sure that you run Git Bash as the administrator.

Template repository

The bem-express template repository was created for tasks involving the development of dynamic applications using BEM. It contains the essential config files and solves an entire set of problems such as building a project, configuring linters, connecting libraries, and others.

The main BEM libraries are integrated in bem-express by default:

Quick start

To create the Hello World application, follow these steps:

The Hello, World application is ready.

Did something go wrong?

If you had trouble creating the application, look for a solution in the forum. If you can't find an answer there, submit a question to the forum experts.

File system

After setting all the dependencies, the file system of the Hello World application should look like this:

sssr-project/
    .enb/                 # Config files for the ENB compiler
    common.blocks/        # Basic implementations of blocks
    desktop.bundles/      # Directories of project bundles
    development.blocks/   # Blocks that are integrated during development
    node_modules/         # Installed Node modules (packages)
    server/               # Directory with server code
    static/               # Root directory for distribution of static files
    .bemhint.js           # Bemhint linter configuration
    .borschik             # Borschik compiler configuration
    .eslintignore         # Excluding files and directories in ESLint
    .eslintrc             # ESLint configuration
    .gitignore            # Excluding files and directories in Git
    .stylelintrc          # Stylelint configuration
    .travis.yml           # Automatically starting linters in Continuous Integration
    nodemon.json          # Nodemon package configuration
    package.json          # Describing a project for npm
    README.md             # Text description of the project

Let's look at some of the main directories in more detail:

.enb

Contains the configuration of the ENB compiler.

The build performs the following tasks:

The build algorithm is described in the .enb/make.js file.

More information about building BEM projects.

common.blocks

Contains implementations of all the project's BEM entities.

The names of files and directories follow the naming convention. The code is divided into independent parts for ease of working with individual blocks.

common.blocks/
    body/                 # Directory for the body block
    footer/               # Directory for the footer block
    header/               # Directory for the header block
    page/                 # Directory for the page block
        _view/            # Subdirectory for the page_view modifier
        page.bemtree.js   # BEMTREE implementation of the page block
        page.deps.js      # DEPS implementation of the page block
    page-index/           # Directory for the page-index block
    root/                 # Directory for the root block

Before being sent to the browser, the files are compiled and optimized.

desktop.bundles

Contains the files received as the result of the build. These files are called bundles in the BEM methodology.

Each bundle directory corresponds to a single page of the project:

desktop.bundles/
    index/                # Bundles for the index page
        index.bemdecl.js  # Declaration for the index page
        index.bemhtml.js  # BEMHTML bundle for the index page
        index.bemtree.js  # BEMTREE bundle for the index page
        index.css         # CSS bundle for the index page
        index.deps.js     # DEPS bundle for the index page
        index.js          # JS bundle for the index page
        ...

Note: The only file in the index directory that isn't generated automatically is the index.bemdecl.js file. More information about the BEMDECL technology is provided below.

server

Contains the Node modules that listen for web requests and generate the page.

File system for the directory:

server/
    config.js             # Application configuration
    index.js              # Application entry point
    rebuild.js            # Rebuilding the application
    render.js             # HTML rendering

Modules and their functions:

static

Contains static files that can be accessed externally:

static/
    favicon.ico           # Favicon
    index.min.css         # Symbolic link to desktop.bundles/index/index.min.css
    index.min.js          # Symbolic link to desktop.bundles/index/index.min.js

More information about symbolic links.

Social Services Search Robot application

Demo

SSSR is a service for searching for tweets and videos that match a specified set of parameters. The search parameters are transmitted to the Twitter Search API and the YouTube Data API in the form of an HTTP GET request. The APIs form a response as a JSON document.

The purpose of developing this application is to show you:

You can use this infrastructure (the SSSR application) as the basis of a wide variety of dynamic BEM projects for solving specific tasks.

Note. To develop the application, you need to install some specific Node modules.

Application flow

The application flow can be visualized as follows:

Chart of Social Services Search Robot

Step 1. Submitting a query

The user sends a query to the server.

Step 2. Receiving data

The application sends a request for data to the Twitter Search API and the YouTube Data API according to the query received from the user.

Note: Generating the request and preparing the received data for templating are covered in detail below.

Step 3. BEMTREE templating

The application passes the received data to the BEMTREE template engine, which transforms the data to BEMJSON.

Step 4. BEMHTML templating

The application passes the BEMJSON to the BEMHTML template engine, which transforms the BEMJSON to HTML.

Step 5. Sending results to the user

The application returns the results (the HTML page) to the user.

Note: Either the entire page can be updated, or just the content of a specific block.

Node modules used

Let's take a closer look at the concept of a Node module and the main modules that the application needs.

Important: This section doesn't cover all the modules that are used. For more information about a specific module, see the npm site. It includes a list of all the Node modules and a search feature.

The basic Node implementation is kept as simple as possible. Rather than directly embedding all the possible components in Node, the develpers provide additional functionality as separate modules (packages).

The modular system for Node is modeled after the CommonJS system, which is a solution for creating interactive modules. The system relies on a contract that must be fulfilled by developers in order for their modules to interact correctly with each other.

All packages that are installed using the npm package manager are located in the node_modules directory.

Use the require command to enable modules. If a package is installed using npm, you don't need to specify the path. Just specify the name:

var express = require('express');

If you are enabling a custom local module, specify the path:

var someModule = require('./somefolder/somemodule');

Any module that you enable must be designed for Node interaction. To meet this requirement, the module must be exported using module.exports:

module.exports = {
    // some module
};

The application requires the following modules:

Note: You can install the required modules using a single command:

npm install express passport passport-youtube-v3 twitter googleapis moment --save

express

Provides most of the functionality that the developer needs for building a web application.

Install:

npm install express --save

The Express documentation provides a basic application called Hello World Express. It demonstrates the basic steps:

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.send('Hello World!')
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
});

passport

Provides various authentication strategies for Node.js applications.

Install:

npm install passport --save

Example of OAuth 2.0 authorization:

var passport = require('passport'),
    OAuth2Strategy = require('passport-oauth').OAuth2Strategy;

/**
 * This function integrates the appropriate authorization strategy
 * @function
 * @param {string} provider — For example, Facebook, Twitter, Google, or others
 * @param {object} strategy — Authorization strategy
 */
passport.use('provider', new OAuth2Strategy({
    authorizationURL: 'https://www.provider.com/oauth2/authorize',
    tokenURL: 'https://www.provider.com/oauth2/token',
    clientID: SERVICE_APP_ID,
    clientSecret: SERVICE_APP_SECRET,
    callbackURL: 'https://www.example.com/auth/provider/callback'
}));

Note: OAuth 2.0 is an open authorization protocol that makes it possible to grant a third party limited access to a user's protected resources, without sharing the user's credentials.

passport-youtube-v3

Provides a mechanism for authentication on YouTube via the YouTube account and OAuth 2.0 tokens.

Install:

npm install passport-youtube-v3 --save

Example

var passport = require('passport'),
    YoutubeV3Strategy = require('passport-youtube-v3').Strategy;
/**
 * This function integrates the YoutubeV3Strategy
 * @function
 * @param {object} strategy — Strategy
 */
passport.use(new YoutubeV3Strategy({
    clientID: YOUTUBE_APP_ID,
    clientSecret: YOUTUBE_APP_SECRET,
    callbackURL: '/auth/youtube/callback',
    scope: ['https://www.googleapis.com/auth/youtube.readonly']
}, verify));

More information about how to get OAuth tokens.

twitter

Client library for working with the Twitter REST API.

Install:

npm install twitter --save

Example

var Twitter = require('twitter');
// Creating an instance of the Twitter object
var client = new Twitter({
  consumer_key: '',
  consumer_secret: '',
  bearer_token: ''
});

var params = {q: 'bem'};
/**
 * Search function. Searches for tweets that match the specified parameters.
 * @function
 * @param {object} params - Search parameters.
 * @param {function} callback - Receives the tweet results.
 */
client.get('search/tweets', params, function(error, tweets, response) {
  if (!error) {
    console.log(tweets);
  }
});

googleapis

Client library for working with the Google REST API.

Install:

npm install googleapis --save

Example

var google = require('googleapis'),
    OAuth2 = google.auth.OAuth2;
// Creating an instance of the OAuth2 object
var oauth2Client = new OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);
// Setting credentials for outgoing calls
oauth2Client.setCredentials({
  access_token: 'ACCESS TOKEN HERE',
  refresh_token: 'REFRESH TOKEN HERE'
});
// Logging in
var youtube = google.youtube({
    version: 'v3',
    auth: this.oauth2Client
});

var params = {q: 'bem'};
/**
 * Search function. Searches for videos that match the specified parameters.
 * @function
 * @param {object} params - Search parameters.
 * @param {function} callback - Receives the video results.
 */
youtube.search.list(params, function(error, video, response) {
  if (!error) {
    console.log(video);
  }
});

moment

JavaScript library for parsing, validating, and formatting dates.

Install:

npm install moment --save

Example

var moment = require('moment');

moment().startOf('day').fromNow();             // 17 hours ago

Preparing the project structure

Before you begin writing code, you need to make a few changes to the structure that you got from the Hello World application.

Changes for:

Changes for static files

static

static directory

common.blocks directory

server directory

When all these steps have been completed, the file structure in the static directory should look like this:

static/
    images/
        favicon.ico
index.min.css
index.min.js

Changes for server code

server-changes

server directory

controllers directory

helpers directory

middleware directory

When all these steps have been completed, the file structure in the server directory should look like this:

server/
    controllers/
        index.js          # Controller for processing requests and rendering HTML
    helpers/
        index.js          # Entry point for helper modules (empty)
        twitter.js        # Helper module for working with the Twitter Search API (empty)
        youtube.js        # Helper module for working with the YouTube Data API (empty)
    middleware/
        auth.js           # Module for checking authentication on YouTube (empty)
    app.js                # Module for middleware integration
    auth.js               # Module for YouTube authentication (empty)
    config.json           # Application configuration
    index.js              # Starting the application and listening for requests on the port
    rebuild.js            # Module for tracking changes and retstarting the server
    render.js             # HTML rendering
    routes.js             # Router

Obtaining OAuth tokens

Twitter and Google store various user data such as tweets, YouTube videos, email messages, photos, and so on. To make it easy to access this data from other apps or third-party services, they use the OAuth 2.0 open authorization protocol.

The protocol requires the developer to register the application on the OAuth server and request access to specific data. The authorized user either grants or denies access.

Obtaining an OAuth token for Twitter

Twitter allows applications to make authenticated requests on behalf of the application itself.

How to get started

Note: You need Postman in order to make the POST request to receive the OAuth token in exchange for the code obtained using the Base64 method.

How do I encode a string?

To encode a string using Base64:

Note: If you run into problems, consult the online resource base64encode.org.

How do I exchange the code for an OAuth token?

To receive a token in exchange for the code:

Obtaining an OAuth token for Google

Google allows applications to make authenticated requests on behalf of the application itself.

Note: The passport-youtube-v3 module is responsible for receiving and updating the OAuth token in exchange for the authorization code using a POST request.

How to get started

Important: Save the keys that you receive (Client ID and Client Secret). You need them for the application's config file.

Application configuration

After you have received all the keys and tokens, you need to add them to the application's config file:

Working with the Twitter Search API

The Twitter Search API lets you find recent or popular tweets that have been published on Twitter.com over the last 7 days.

More information:

API access

To successfully make calls to the API, you need:

API calls

Changes for working with the Twitter Search API:

twitter-changes

controllers directory

helpers directory

Working with the YouTube Data API

The YouTube Data API allows you to find videos that are published on Youtube.com. By default, the search results include videos, channels, and playlists.

More information:

API access

To successfully make calls to the API, you need:

API calls

Changes for working with the YouTube Data API:

youtube-changes

server directory

controllers directory

helpers directory

middleware directory

Layout

This document focuses primarily on interaction between BEM technologies, so it doesn't cover layout and the JavaScript client. Describing layout issues would make this document unreasonably large.

To prepare the layout, follow these steps:

The Social Services Search Robot application is ready.

Did something go wrong?

If you had trouble creating the application, look for a solution in the forum. If you can't find an answer there, submit a question to the forum experts.

Rate the article
Report an error on GitHub or correct using prose.io.