📜 ⬆️ ⬇️

Isomorphic BEM

When node.js appeared, many web developers began to think about the possibility of using the same code both on the client and on the server. Now there are several frameworks that put the “write code once, use everywhere” approach at the center, from time to time new ones appear. So I could not pass by, writing a similar micro-framework - bnsf . It is intended for those who prefer to create the front-end of their applications using the BEM methodology, using the appropriate set of technologies and tools.

Let's try to start writing a front-end for a simple one-page web application using bnsf. In order not to be distracted by the creation of a back-end part, we will use vk.com API as a back-end. Our application will consist of only two pages, the main one - with the user search form by identifier - and the secondary one, we will display information about the selected user.

To get started, you need node.js, yeoman and gulp. I recommend using * nix OS, as the code was not tested under Windows, although, theoretically, it should work. I proceed from the assumption that you already have node.js installed. If not, I advise you to use nvm .

Install gulp, yeoman and the corresponding generator:
')
npm install -g gulp yo generator-bnsf 

We create our project:

 yo bnsf vk-test-app cd vk-test-app 

You can see which files and folders were generated:

 ls 

It will output approximately the following set of files (the order may differ on different operating systems):

 README.md desktop.blocks gulpfile.js node_modules bower.json desktop.bundles libs package.json 

The project is already possible to try to collect:

 gulp 

gulp will not only build the project, but also start the server, begin to monitor changes in the project and, if necessary, rebuild it.

Check that everything works. We try to open in the browser http: // localhost: 3000 - we should see a page with the text page-index and the title of the main page.

We already have one page, let's create a second one to output entries from the user's wall. To do this, we again need a generator. Since it works from the command line, you will need another terminal session in order not to interrupt the gulp. At this stage, you can simply agree with everything that yeoman will ask about. It will warn about conflicts - this is standard practice, when a file is not created new, but an existing one is being edited, so just press “enter” in response to all yo questions. So, let's execute from the project root:

 yo bnsf:page user 

Once again I will remind you that we answer all questions by agreement - that is, we press the input.

gulp should notice the appearance of a new page and rebuild the project. Checking: the request for http: // localhost: 3000 / user should give a page with the text page-user.

Let's now place a search form on the main page by editing the file desktop.blocks/page-index/page-index.bemtree as follows:

 block('page-index')( content()(function () { return [ { block: 'search-form', content: [ { block: 'input', mods: { theme: 'simple' } }, { block: 'button', mods: { type: 'submit', theme: 'simple' }, content: 'search' } ] }, { block: 'search-results' } ]; }) ); block('page-index').elem('title').content()('main page'); 

And change the dependencies in page-index.deps.js:

 ({ mustDeps: ['i-page'], shouldDeps: [ { elem: 'title' }, 'search-form', { block: 'input', mods: { theme: 'simple' } }, { block: 'button', mods: { theme: 'simple' } }, 'search-results' ] }) 

Now the form is already displayed (you can check again by going to http: // localhost: 3000 ), only the tag is not a form , but a div . To fix this, create the appropriate template file, desktop.blocks/search-form/search-form.bemhtml :

 block('search-form').tag()('form'); 

Now it may seem redundant to create a separate directory with a file that stores only one line of code. But in a real project, this is almost impossible to meet: either a file with styles appears, or with JavaScript, or the block template itself is more complicated. Often - all of the above at once.

Great, we have a form, but she still does not know how to look for anything. Let “search” from the point of view of the form is redirect to the current page with the query parameter. For the form to start doing this, you need the following JS in the file desktop.blocks/search-form/search-form.browser.js :

 /**@module search-form*/ modules.define('search-form', ['i-bem__dom', 'app-navigation'], function (provide, BEMDOM, navigation) { "use strict"; /** * @class SearchForm * @extends BEM.DOM * @exports */ provide(BEMDOM.decl(this.name, /**@lends SearchForm#*/{ onSetMod: { js: { /** * @constructs * @this SearchForm */ inited: function () { this._input = this.findBlockInside('input'); } } }, /** * @param {Event} e * @private */ _onSubmit: function (e) { e.preventDefault(); var query = this._input.getVal(), params = query ? {query: query} : null; navigation.navigate('page-index', params); } }, /**@lends SearchForm*/{ /** * @static */ live: function () { var init = { modName: 'js', modVal: 'inited' }; this .liveInitOnBlockInsideEvent(init, 'button') .liveInitOnBlockInsideEvent(init, 'input') .liveBindTo('submit', function (e) { this._onSubmit(e) }); } })); }); 

We will also have to complicate the template a bit by adding the information that the block has logic, the file desktop.blocks/search-form/search-form.bemhtml :

 block('search-form')( tag()('form'), js()(true) ); 

So now we have a form that can change the get parameter of the page. You can verify this by typing, say, “1” into the text input and pressing enter. It's time to get some data on this parameter. I do not want to use an API that requires authentication, so I will use the method available to anyone via url api.vk.com/method/users.get api.vk.com/method/users.get . Let the form accept the user ID, and the link to its page (to the user page we created above) and to the pages of another 4 users with identifiers obtained by a simple increment will be displayed. As the text of links we will use user names.

The first thing we need to do is add a route to the API route configuration file. This is the file desktop.bundles/index/index.api.routing.yml , and this is how its contents should turn out:

 - host: api.vk.com routes: - id: users path: /method/users.get 

Second - Create a file desktop.blocks/search-results/search-results.bemtree . The basic idea is this: to whom the data should be displayed, the one behind them goes. In our case, the data is needed by the search-results block, it’s for the data and go:

 block('search-results').content()(function () { if (!this.route.parameters.query) { return ''; } var id = parseInt(this.route.parameters.query, 10); return id ? this.get('users', { //      API   user user_ids: [id, id + 1, id + 2, id + 3, id + 4] }, function (data) { //       return data.body.response.map(function (dataItem) { return { block: 'search-results', elem: 'item', content: { block: 'link', url: path('page-user', { id: dataItem.uid }), //  url     page-user content: dataItem.first_name + ' ' + dataItem.last_name } }; }); }) : 'Something goes wrong'; }); 

In this data template, we look at whether the id came to us, if it did, we request data along the API route with the user identifier and the user_ids parameter using the get method. If id is not a number, give the string 'Something goes wrong'. Since the output will need a list, and we love the semantics, we will create desktop.blocks/search-results/search-results.bemhtml :

 block('search-results') .tag()('ul') .elem('item').tag()('li'); 

In addition, we will need a file for the block dependency declaration, desktop.blocks/search-results/search-results.deps.js :

 ({ shouldDeps: ['link'] }) 

Now the page is already able to search for users and display the results. Try it, just do not forget to refresh the page. If you enter "1" - in the delivery of the results must find Pavel Durov. But the trouble is - every time the whole page is redrawn. It is easy to fix by teaching it to update only what is necessary. page-index.bemtree add page-index.bemtree to make it look like this:

 block('page-index')( content()(function () { return [ { block: 'search-form', content: [ { block: 'input', mods: { theme: 'simple' } }, { block: 'button', mods: { type: 'submit', theme: 'simple' }, content: 'search' } ] }, { block: 'search-results' } ]; }), js()({ update: 'search-results' //      JavaScript:  ,    }) ); block('page-index').elem('title').content()('main page'); 

Now, by opening the inspector in the browser, you can make sure that with new requests to the API only the search-results block is updated.

Well, it's time to come to the second page, not for nothing that we created it.
Let's start with desktop.blocks/page-user/page-user.bemtree :

 block('page-user').content()(function () { return [ { block: 'menu', content: { block: 'link', url: path('page-index'), content: 'main page' } }, { block: 'user-card' } ]; }); block('page-user').elem('title').content()('user'); 

We added a fake menu block - just like a wrapper for a link to the main page, the link itself, and a user-card block that will display information about the user.
Do not forget to update the dependencies in desktop.blocks/page-user/page-user.deps.js :

 ({ mustDeps: ['i-page'], shouldDeps: ['link', 'user-card'] }) 

I did not add a menu block depending on it, because I am not going to implement it.

To display a user card, create a file desktop.blocks/user-card/user-card.bemtree :

 block('user-card').content()(function () { return this.get('users', { user_ids: this.route.parameters.id }, function (data) { return data.body.response.map(function (dataItem) { var output = []; for (var key in dataItem) { if (dataItem.hasOwnProperty(key)) { output.push({ elem: 'row', content: [ { elem: 'key', content: key }, { elem: 'value', content: JSON.stringify(dataItem[key]) } ] }); } } return output; }); }); }); 

In this form will already work. You can try to click on the link in the search results, just do not forget to refresh the page before this, to pull up the new code. But let's make the user card a table by defining desktop.blocks/user-card/user-card.bemhtml :

 block('user-card')( tag()('table'), elem('row').tag()('tr'), elem('key').tag()('td'), elem('value').tag()('td') ); 

Now, so much better.

I think this is the time to finish, although one could still add at least the validation of user input, more accurate url, showing the download process, return to the last search ... I will leave this on homework to those who are interested. Well, or the next article, if interested will ask for such.

Useful links:
bnsf is the framework discussed in the article. In fact, just a library of blocks in the terminology of BEM.
bem-core - block library on which bnsf depends
bem-components - library of blocks, which is used in the project created above
bem.info - a site about bem with documentation, in particular, you can read about there:
bemtree is a technology for building input data for a template engine from API data and
bemhtml - declarative template engine
An article in the topic by Nickolas Zackas. There is a translation .

Source: https://habr.com/ru/post/249653/


All Articles