Reusable UI components in Ruby on Rails applications
In this article, I would like to talk about an alternative way to organize a frontend for applications on Ruby on Rails. Mostly I work on the backend, but from time to time there are tasks on the front-end and what is often seen there does not inspire any optimism for further work.
Each developer, at their discretion, organizes a file structure for storing CSS and JavaScript code, for HTML representations Rails has some conventions . Perhaps someone had to deal with applications in which CSS and JavaScript are limited to almost the only application
files, then the contents of these files may not even be “footcloths”, but entire “sheets” of code. Presentations are not easy either, especially partials
when they are used to create separate UI components: modal windows, menus, cellars, etc. Sometimes you need to get a little fancy in order to use such a UI component in different representations with different parameters and / or logic.
As a result, it turns out that CSS, JavaScript, HTML representations and UI components (based on partials
) lie in different directories and each technology has its own file structure. With this approach, the front-end in medium and large applications is hard to maintain, all of these technologies produce a very explosive mixture that can simply explode at any inopportune moment.
Ideally, I wanted the frontend to consist of separate UI components and have an understandable file structure. To solve such problems, the BEM methodology has long existed , therefore it was it that was taken as the basis when creating the Bemer gem .
Bemer allows you to:
- create reusable UI components
- use BEMHTML templates for UI components
- develop applications according to BEM methodology
More information can be found here .
The file structure for the UI component is different from that used in the BEM methodology . Each UI component has its own directory, but the technology files are called index
(index files):
Example file structure when using Sprockets .
app/
├── assets/
| ├── javascripts/
| | └── application.coffee
| └── stylesheets/
| └── application.scss
├── bemer_components/
| ├── modal/
| | ├── index.html.slim
| | ├── index.bemhtml.slim
| | ├── index.coffee
| | └── index.scss
| └── ...
└── ...
Example file structure when using Webpacker .
app/
├── javascript/
| ├── bemer_components/
| | ├── modal/
| | | ├── index.html.slim
| | | ├── index.bemhtml.slim
| | | ├── index.coffee
| | | └── index.scss
| | └── ...
| ├── packs/
| | ├── application.js
| | └── ...
| └── ...
└── ...
With Bemer, you can create various kinds of UI components . This example will look at a way in which BEMHTML templates can be used. The basis will be the Modal component from Bootstrap , with the following HTML structure:
In the beginning, you need to create a structure (tree) of the component using a helper define_component
(only for this kind of UI component you can use BEMHTML templates). A possible variant of the structure (tree) of the Modal component:
/ Содержимое файла app/bemer_components/modal/index.html.slim
= define_component bem_cascade: false, tag: :div do |component|
= component.block :modal, cls: 'modal fade', role: :dialog, tabindex: -1 do |modal|
= modal.elem :dialog, role: :document, cls: 'modal-dialog'
= modal.elem :content, cls: 'modal-content'
= modal.elem :header, cls: 'modal-header'
= component.block :close, type: :button, tag: :button, cls: 'close', 'data-dismiss': :modal
span aria-hidden='true' ×
= modal.elem :title, tag: :h4, cls: 'modal-title'
= modal.elem :body, cls: 'modal-body'
= modal.elem :footer, cls: 'modal-footer'
The structure (tree) of the component consists of blocks and elements (nodes), they act as labels, using which you can change the component using BEMHTML templates, as well as get css classes from the BEM methodology. In this case, the generation of data from the BEM methodology is not needed, so it can be disabled using the parameter bem_cascade: false
.
Now, in any view, you can call the Modal component and apply the necessary BEMHTML templates to it:
/ Кнопка для показа модального окна
button.btn.btn-primary.btn-lg data-target="#my-modal" data-toggle="modal" type="button"
| Launch demo modal
/ Вызов компонента Modal и применение BEMHTML шаблонов
= render_component :modal do |template|
= template.block :modal do |modal|
= modal.add_attrs id: 'my-modal'
= modal.specify(elem: :dialog).add_cls 'modal-lg'
= modal.specify(elem: :title).content 'Modal title'
= modal.specify(elem: :body).content
p Modal body
= modal.specify(elem: :footer).content
button.btn.btn-default data-dismiss="modal" type="button" Close
button.btn.btn-primary type="button" Save changes
/ Эквивалентный результат
= render_component :modal do |template|
= template.block(:modal).add_attrs id: 'my-modal'
= template.elem(:dialog).add_cls 'modal-lg'
= template.elem(:title).content 'Modal title'
= template.elem(:body).content
p Modal body
= template.elem(:footer).content
button.btn.btn-default data-dismiss="modal" type="button" Close
button.btn.btn-primary type="button" Save changes
Conclusion
Before using in your projects, carefully read the documentation and source code to understand all the pros and cons of this approach.
Maybe Bemer can help someone:
- clean up front-end in old projects (
Rails >= 3.2.22
) - reorganize the frontend as a UI component
- create separate collections of UI components (like bemer-bootstrap ) and use them in different projects
- make front-end applications more convenient for work
- it is more convenient to develop according to the BEM methodology
In conclusion, I want to say thanks to the entire BEM team for quick feedback, and in particular tadatuta and miripiruni for detailed answers to questions on the BEM methodology and the bem-xjst template engine .