📜 ⬆️ ⬇️

Fractal of element names

Hello, my name is ... Man. The number of hands is 2. The number of legs is 2. Blood type is 1. Rhesus is true.


It may seem to you that only by this information, without a name, surname or even a nickname, it is difficult to distinguish me from many other article authors. And you will be right. However, in the front end, I often see how the name of the element is replaced by its description. And nobody cares.


Three men and a girl


Have a seat, a fascinating journey awaits you in front of serious problems of serious projects, which, however, are often underestimated.


Tests


Imagine yourself as a writer of automatic end-to-end tests. You need to test that, for example, in Yandex in the upper right corner the correct name of the logged in user is displayed.


Yandex muzzle through the eyes of the admin


Give this page to a typical typesetter and he will give you something like this:


<body> <aside> <div class="card"> <div class="username">admin</div> </div> <!-- -  --> </aside> <section> <!-- -  --> </section> </body> 

Backfill question: how to find the house-element where the username is displayed?


Two ways, but the essence is one


Here the tester has a choice of two chairs:


  1. Write a css or xpath selector of the form aside > .card > .username and pray that no other cards will ever appear in the sidebar. And no other user names appeared in the card. And so that no one changes it to any button. And he did not wrap it in any socket. In short, this is a very fragile selector, the use of which will break the tests at the slightest changes on the page.
  2. Ask the developer to add a unique identifier for the page . This is the right way to incur the anger of the developer. After all, he has everything on the components. And the components are displayed a lot where, and do not know (and should not know) anything about the application. It is naive to believe that any component will always be on the page in a single copy, which means that the identifier cannot be sewn into the component itself. But at the application level, there is only the use of the LegoSidebar component. And throwing identifiers through several levels of component nesting is also an opportunity.

As you can see, one option is worse than the other - either developers or testers suffer. Moreover, as a rule, a bias towards the latter. Because they enter the business as a rule, when the first have already completed the development of this feature, and they are cutting the other with might and main.


Let's see how layout designers from Yandex dealt with the layout of this simple block (I had to remove 90% of the garbage so that the essence was visible):


 <div class="desk-notif-card"> <div class="desk-notif-card__card"> <div class="desk-notif-card__domik-user usermenu-link"> <a class="home-link usermenu-link__control" href="https://passport.yandex.ru"> <span class="username desk-notif-card__user-name">admin</span> </a> <!-- -  --> </div> </div> </div> <!-- -  --> 

Thanks to BEM, the element we need has information on the context 1 level up in the class name (some username in some card). But can you be sure that such a card will always be alone on the page? A bold assumption. So, again, you have to choose between two stools.


Oh what a difficult choice


And here is another example with a search field where the developer was forced to put an identifier. Well, he put:


 <input class="input__control input__input" id="text" name="text" /> 

Is the developer bad? No, bad architecture that does not help generate globally unique identifiers.


Statistics


Imagine yourself as an analyst. You need to understand why users often click to open the account menu in the Yandex sidebar: by user name or by profile picture.


Base clicks on faces


You need information right now, so you have only one bench - to work with the statistics that are already collected. Even if the developers have taken care that all clicks on all pages are already recorded while preserving the entire hierarchy of elements at the time of the click, you still can’t make the right selector to filter out the necessary clicks. And even if you ask the developer to do this, he, too, is likely to make a mistake somewhere. Especially if the layout has changed over time.


That is, you need global unique identifiers for the elements you need. Moreover, affixed very well in advance. But since the majority of developers cope poorly with accurate prediction of the future, it usually looks like this: the analyst comes to the developers and asks for the identifier, and receives the information in the best case in a month.


I feel I need an identifier here


In the case of Yandex, the fruit of the heroic efforts of the developers looks like this:


 <div class="desk-notif-card__domik-user usermenu-link"> <a class="home-link usermenu-link__control" href="https://passport.yandex.ru" data-statlog="notifications.mail.login.usermenu.toggle" > <!-- -  --> </a> <a class="home-link desk-notif-card__user-icon-link usermenu-link__control avatar" href="https://passport.yandex.ru" data-statlog="notifications.mail.login.usermenu.toggle-icon" > <!-- -  --> </a> </div> 

Oh, and how cool it would be for any element to always have these identifiers. So that they are also understandable to man. And at the same time they were stable, and did not change with any change in layout. But with manual transmission, happiness cannot be achieved.


Styles


Imagine yourself as a layout designer. You need to specialize in styling the username in the card in the Yandex sidebar. We will make the first approach to the projectile, without using BEM. Here is your component:


 const Morda = ()=> <div class="morda"> {/* - */} <LegoSidebar /> </div> 

And here is a bundle of components supported by completely different guys:


 const LegoSidebar = ( { username } )=> <aside className="lego-sidebar"> <LegoCard> <LegoUsername>{ username }</LegoUsername> </LegoCard> </aside> const LegoCard = ( {} , ... children )=> <div className="lego-card"> { ... children } </div> const LegoUsername = ( {} , ... children )=> <div className="lego-username"> { ... children } </div> 

All in total gives the following result:


 <body class="morda"> <aside class="lego-sidebar"> <div class="lego-card"> <div class="lego-username">admin</div> </div> <!-- -  --> </aside> </body> 

There definitely needs a valerian


If isolation of styles is used, then you simply have nothing to sit down for. Stand and wait for these other guys to add a custom class to their components through LegoSidebar, to LegoUsername:


 const LegoSidebar = ( { username , rootClass , cardClass , usernameClass } )=> <aside className={ "lego-sidebar " + rootClass }> <LegoCard rootClass={ cardClass }> <LegoUsername rootClass={ usernameClass}>{ username }</LegoUsername> </LegoCard> </aside> const LegoCard = ( { rootClass } , ... children )=> <div className={ "lego-card " + rootClass }> { ... children } </div> const LegoUsername = ( { rootClass } , ... children )=> <div className={ "lego-username " + rootClass }> { ... children } </div> 

And it is good if they are embedded directly in each other, and not through a dozen intermediate components. Otherwise, they will die under a load of noodles from copy-paste.


Overcoming the difficulties created by your own hands


If insulation is not used, then welcome to a chair made of fragile selectors:


 .morda .lego-sidebar > .lego-card > .lego-username:first-letter { color : inherit; } 

However, if we had a tool that would take local names:


 const Morda = ()=> <div> {/* - */} <Lego_sidebar id="sidebar" /> </div> const Lego_sidebar = ( { username } )=> <aside> <Lego_card id="profile"> <Lego_username id="username">{ username }</Lego_username> </Lego_card> </aside> const Lego_card = ( {} , ... children )=> <div> { ... children } </div> const Lego_username = ( {} , ... children )=> <div> { ... children } </div> 

I would stick them together taking into account the nesting of components, generating classes right down to the root of the application:


 <body class="morda"> <aside class="lego_sidebar morda_sidebar"> <div class="lego_card lego_sidebar_profile morda_sidebar_profile"> <div class="lego_username lego_sidebar_username morda_sidebar_username">admin</div> </div> <!-- -  --> </aside> </body> 

Then we could stylize any element , no matter how deep it may be:


 .morda_sidebar_username:first-letter { color : inherit; } 

No, it's fantastic. This does not happen.


After 5 years, 99% of the hipster code can only be thrown away


Item Transfer


Imagine yourself as a developer of a rendering library. High-performance reactive algorithms using VirtualDOM, IncrementalDOM, DOM Batching and other WhateverDOM allow you to generate this kind of DOM for a scrum board in just a few seconds:


 <div class="dashboard"> <div class="column-todo"> <div class="task"> <!--  html --> </div> <!--    --> </div> <div class="column-wip"> </div> <div class="column-done"> </div> </div> 

From this type of state:


 { todo : [ { /*   */ }, /*   */ ] , wip : [] , done : [] , } 

And here's the bad luck: the user begins to dragging tasks back and forth and expects that this will happen quickly. It would seem that you just need to take the DOM element of the task and move it to another place in the DOM-e. But you will have to manually work with the DOM and be sure that in all places tasks are always rendered into exactly the same DOM tree, which is often not the case - there are usually minor differences. In short, changing the DOM manually is like sitting on a unicycle: one inadvertent movement and nothing will save you from gravity. It is necessary to somehow explain the rendering system so that it understands where the task was transferred, and where one was deleted and the other was added.


Error transferring identity to the wrong body


To solve this problem, it is necessary to equip the views with identifiers. If the identifiers match, then the rendered view can simply be moved to a new location. If they did not match, then these are different entities and you need to destroy one and create another. It is important that the identifiers are not repeated, and could not coincide by chance.


While you are transferring elements within the same parent DOM element, the key attribute from React , the ngForTrackBy parameter from Angular, and similar things in other frameworks can help you. But these are too private decisions. It is worth moving the task to another column, and all these optimizations stop working.


But if each DOM element had a globally unique identifier, regardless of where this element was rendered, then using getElementById would quickly reuse the existing DOM tree when moving an entity from one place to another. Unlike the crutches for rendering lists mentioned above, global identifiers solve the problem systematically and will not break even if grouped characters or some other game appear in the columns:


 <div id="/dashboard"> <div id="/dashboard/column-todo"> <div id="/dashboard/todo/priority=critical"> <div id="/dashboard/task=y43uy4t6"> <!--  html --> </div> <!--    --> </div> <!--     --> </div> <div id="/dashboard/column-wip"> <div id="/dashboard/wip/assignee=jin"></div> <!--     --> </div> <div id="/dashboard/column-done"> <div id="/dashboard/done/release=0.9.9"></div> <!--     --> </div> </div> 

Grouped speakers


Semantics


Imagine yourself as a layout designer. And you need to add a div over there. Presented? Now kill yourself. You are already spoiled by html.


In fact, you do not need to add a div , but a card name block. title - the name of this block, reflecting its semantics in the place of use. div is a type of block that reflects its appearance and behavior, regardless of where it is used. If we were typesetting on TypeScript, then it would be expressed like this:


 const Title : DIV 

We can instantly create an instance of the type:


 const Title : DIV = new DIV({ children : [ taskName ] }) 

And let the time script print the type automatically:


 const Title = new DIV({ children : [ taskName ] }) 

Well, here, and not far from HTML:


 const Title = <div>{ taskName }</div> 

Note that Title is not just a random variable name that is used and thrown away. This is the primary semantics of this element. And in order not to lose it, it must be reflected as a result of:


 const Title = <div id="title">{ taskName }</div> 

And again we get rid of the tautology:


 <div id="title">{ taskName }</div> 

Add the remaining elements:


 <div id="task"> <div id="title">{ taskName }</div> <div id="deadline">{ taskDue }</div> <div id="description">{ taskDescription }</div> </div> 

Note that in addition to the title of the task card, there can be many other headers, including the card headers of other tasks:


 <div id="/dashboar/column-todo"> <div id="/dashboard/column-todo/title">To Do</div> <div id="/dashboard/task=fh5yfp6e"> <div id="/dashboard/task=fh5yfp6e/title">{ taskName }</div> <div id="/dashboard/task=fh5yfp6e/deadline">{ taskDue }</div> <div id="/dashboard/task=fh5yfp6e/description">{ taskDescription }</div> </div> <div id="/dashboard/task=fhty50or"> <div id="/dashboard/task=fhty50or/title">{ taskName }</div> <div id="/dashboard/task=fhty50or/deadline">{ taskDue }</div> <div id="/dashboard/task=fhty50or/description">{ taskDescription }</div> </div> </div> 

Thus, for each element human-readable identifiers are formed that accurately reflect their semantics and therefore are globally unique.


Please note that semantics are determined by membership, not location. Although the task card /dashboard/task=fh5yfp6e is located in the /dashboard/todo column, it belongs to the /dashboard . It was he who created it. He set it up. He gave her a name and ensured the uniqueness of her identifier. He completely controls it. He will destroy her.


Know who your daddy is


But the use of "correct html tags" is not semantics, it is typification:


 <section id="/dashboard/column-todo"> <h4 id="/dashboard/column-todo/title">To Do</h4> <figure id="/dashboard/task=fh5yfp6e"> <h5 id="/dashboard/task=fh5yfp6e/title">{ taskName }</h5> <time id="/dashboard/task=fh5yfp6e/created">{ taskCreated }</time> <time id="/dashboard/task=fh5yfp6e/deadline">{ taskDue }</time> <div id="/dashboard/task=fh5yfp6e/description">{ taskDescription }</div> </figure> </section> 

Pay attention to two time tags with completely different semantics.


Localization


Imagine yourself as a translator. You have an extremely simple task - to translate a line of text from English into Russian. You got the string "Done". If this action, then it is necessary to translate as "Finish", and if the state, then as "Finished", but if this is the state of the task, then "Complete". Without information about the context of use, it is impossible to correctly translate the text. And here the developer again has two shops:


  1. Provide texts with comments with context information. And comments are too lazy to write. And how complete the information is needed is not clear. And more code is already obtained than when receiving texts by key.
  2. Receive texts by key, having read which you can understand the context of use. Manually set, it does not guarantee the completeness of information, but at least it will be unique.

Again the choice of two evils


But what if we do not want anyhow how to sit, but want to stand proudly on a firm basis from best practices? Then we need to use a combination of the name of the type (component, template), the local name of the element within this type, and the name of its property as a key. For the text "Done" as the column name, this key would be github_issues_dashboard:column-done:title . And for the text "Done" on the task completion button in the task card, the identifier will already be github_issues_task-card:button-done:label ). These, of course, are not the identifiers that we talked about earlier, but these keys are formed from the same names that we explicitly or implicitly give to the constituent elements. If we name them explicitly, then we have the opportunity to automate the generation of various keys and identifiers. But if implicitly, then you have to set these keys and identifiers manually and hope that the mess with the name of the same entity in different places in different ways does not break out.


Debugging


Imagine yourself as an application developer. A bug report arrives to you:


   !  !   ,   : Uncaught TypeError: f is not a function at <Button> at <Panel> at <App> 

“Yeah, that same famous f from some button, on some panel,” the sofa expert would say, “Everything is clear.”


Here and the fool


Or not? And if it were just one unique identifier:


 /morda/sidebar/close 

- Yeah, the sidebar close button is broken. VasyaPas, this is for you, they moved the rolls.


Vasya sits down for the app, drives the received identifier into the development console, and then he receives an instance of the component, where it is immediately clear that someone smart passed the line to the button as a click handler:


This doesn't seem to be quite HTML.


It’s good that each component has an identifier. And by this identifier, this component is easy to get , without dancing around the debugger. True, we need a tool to find a component by identifier. But what if the identifier itself is program code to get the component?


 <button id="Components['/morda/sidebar/close']">X</button> 

Then this code can be copied directly to the console for quick access to the state of the component, no matter how many times we reload the page and change the code.


What to do?


If you use $ mol, then you do not need to do anything - just sit down and get a vibration massage in full:


 $ya_morda $mol_view sub / <= Sidebar $ya_lego_sidebar $ya_lego_sidebar $mol_view sub / <= Profile $ya_lego_card sub / <= Username $ya_lego_username sub / <= username \ $ya_lego_card $mol_view $ya_lego_username $mol_view 

The programmer simply cannot syntactically fail to give the component a unique name. The following DOM is generated from this component description:


 <body id="$ya_morda.Root(0)" ya_morda mol_view > <ya_lego_sidebar id="$ya_morda.Root(0).Sidebar()" ya_lego_sidebar mol_view ya_morda_sidebar > <ya_lego_card id="$ya_morda.Root(0).Sidebar().Profile()" ya_lego_card mol_view ya_lego_sidebar_profile ya_morda_sidebar_profile > <ya_lego_username id="$ya_morda.Root(0).Sidebar().Username()" ya_lego_username mol_view ya_lego_sidebar_username ya_morda_sidebar_username > admin </ya_lego_username> </ya_lego_card> </ya_lego_sidebar> </body> 

The code in identifiers is not only globally unique, but also represents an API through which you can access any component. Well, stektrays is just a fairy tale:


 Uncaught (in promise) Error: Test error at $mol_state_local.value("mol-todos-85").calculate at $mol_state_local.value("mol-todos-85").pull at $mol_state_local.value("mol-todos-85").update at $mol_state_local.value("mol-todos-85").get at $mol_app_todomvc.Root(0).task at $mol_app_todomvc.Root(0).task_title at $mol_app_todomvc.Root(0).task_title(85).calculate at $mol_app_todomvc.Root(0).task_title(85).pull at $mol_app_todomvc.Root(0).task_title(85).update at $mol_app_todomvc.Root(0).task_title(85).get at $mol_app_todomvc.Root(0).Task_row(85).title at $mol_app_todomvc.Root(0).Task_row(85).Title().value at $mol_app_todomvc.Root(0).Task_row(85).Title().event_change 

Not those who want to make the world a better place


If you are addicted to React, you can transfer to a custom JSX transformer to generate globally unique identifiers by local names of elements embedded in the component. You can do the same with the generation of classes for styling. For an example with a dashboard, the templates look something like this:


 const Dashboard = ()=> ( <div> <Column id="/column-todo" title="To Do"> <Task id="/task=fh5yfp6e" title="foobar" deadline="yesterday" content="Do it fast!" /> </Column> <Column id="/column-wip" title="WIP" /> <Column id="/column-done" title="Done" /> </div> ) const Column = ( { title } , ... tasks )=> ( <div> <div id="/title">{ title }</div> { tasks } </div> ) const Task = ({ title , deadline , description })=> ( <div> <div id="/title">{ title }</div> <div id="/deadline">{ deadline }</div> <div id="/description">{ description }</div> </div> ) const App = ()=> <Dashboard id="/dashboard" /> 

At the output generating:


 <div id="/dashboar"> <div id="/dashboar/column-todo"> <div id="/dashboard/column-todo/title">To Do</div> <div id="/dashboard/task=fh5yfp6e"> <div id="/dashboard/task=fh5yfp6e/title">foobar</div> <div id="/dashboard/task=fh5yfp6e/deadline">yesterday</div> <div id="/dashboard/task=fh5yfp6e/description">Do it fast!</div> </div> </div> <div id="/dashboar/wip"> <div id="/dashboard/column-wip/title">WIP</div> </div> <div id="/dashboar/done"> <div id="/dashboard/column-done/title">Done</div> </div> </div> 

Oh how many copy-paste


If you are hostage to any other framework, then create it for the authors of the issue to add the optional ability to generate identifiers and classes. Or at least to add an API through which such features could be implemented independently.


Summarizing, I remind you why you need to give unique names to all elements:



')

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


All Articles