📜 ⬆️ ⬇️

HTML generation: more convenient than helpers and pure HTML

Writing pure HTML is often inconvenient, especially if you need to make dynamic inserts.

Template engines partially solve this problem, but their bizarre syntax needs to be studied, put up with restrictions, put some templates into others for reuse, in general the attempt is good, but something is not right.

Some frameworks have helpers; in particular, Aura.Html forced me to write this article. Helpers have a different story - they were originally conceived for real simplification, since one team can generate a good piece of HTML code, but most of them are sharpened for a certain use, and something beyond that looks too crooked.
')
As a more universal solution, it would not be bad not to invent a bizarre syntax, but to use the most common PHP and all the familiar primitive CSS selectors.

Thinking about it some time ago, I began to saw my bicycle. The bike turned out, was used as part of another bike, then separated, updated many times, and now I would like to share it with the community.

How does it work?


The idea was to make it as simple as possible:

h::div('Content') 

what will give the output

 <div> Content </div> 

This is the simplest example. The name of the method is a tag, the value is passed inside. If you need to add attributes - no problem:

 h::div( 'Content', [ 'class' => 'some-content' ] ) 

 <div class="some-content"> Content </div> 

And one would think that it’s already easier in any way, but here CSS selectors come to the rescue, and a bit of street magic:

 h::{'div.some-content'}('Content') 

The output will be the same. At first glance it may seem a bit strange, but in practice it is very convenient.

In comparison with Aura.Html


In the beginning I mentioned Aura.Html, it's worth comparing how HTML is generated there, and here.
Aura.Html (example from documentation):

 $helper->input(array( 'type' => 'search', 'name' => 'foo', 'value' => 'bar', 'attribs' => array() )); 

Our option:

 h::{'input[type=search][name=foo][value=bar]'}() 

Any of the parameters could be transferred to an array.
At the exit:

 <input name="foo" type="search" value="bar"> 

And another option is more serious.

Aura.Html (example from documentation):

 $helper->input(array( 'type' => 'select', 'name' => 'foo', 'value' => 'bar', 'attribs' => array( 'placeholder' => 'Please pick one', ), 'options' => array( 'baz' => 'Baz Label', 'dib' => 'Dib Label', 'bar' => 'Bar Label', 'zim' => 'Zim Label', ), )) 

Our option:

 h::{'select[name=foo]'}([ 'in' => [ 'Please pick one', 'Baz Label', 'Dib Label', 'Bar Label', 'Zim Label' ], 'value' => [ '', 'baz', 'dib', 'bar', 'zim' ], 'selected' => 'bar', 'disabled' => '' ]) 

Here in is used explicitly, it can be used to pass the tag internals, like Content in the example with the div above. Both general rules and some special ones are used, a little more about which further.
The output is the same:

 <select name="foo"> <option disabled value="">Please pick one</option> <option value="baz">Baz Label</option> <option value="dib">Dib Label</option> <option selected value="bar">Bar Label</option> <option value="zim">Zim Label</option> </select> 

Special treatment


All tags follow the general rules of processing, but there are some tags that have additional constructs for convenience.
For example:

 h::{'input[name=agree][type=checkbox][value=1][checked=1]'}() 

 <input name="agree" checked type="checkbox" value="1"> 

It works like with select , in the value of the value, and checked will be put down when the same element of the transmitted array matches.

Another example of the use of in and special processing input [type = radio] :

 h::{'input[type=radio]'}([ 'checked' => 1, 'value' => [0, 1], 'in' => ['Off', 'On'] ]) 

 <input type="radio" value="0"> Off <input checked type="radio" value="1"> On 

No label wrappers are added specifically to make the code as general and predictable as possible.

If you need to process an array


This is probably the most frequently used feature along with nesting control, since the data and the truth often come from somewhere in the form of an array.
To process an array, you can pass it directly instead of the value:

 h::{'tr td'}([ 'First cell', 'Second cell', 'Third cell' ]) 

Or even omit the extra brackets in the simplest case.

 h::{'tr td'}( 'First cell', 'Second cell', 'Third cell' ) 

At the exit:

 <tr> <td> First cell </td> <td> Second cell </td> <td> Third cell </td> </tr> 


Each element of the array will be processed separately, that is, it is legal to transfer not only strings, but also some attributes, though, sometimes it looks too monstrous:

 h::{'tr.row td.cs-left[style=text-align:left;][colspan=2]'}( 'First cell', [ 'Second cell', [ 'class' => 'middle-cell', 'style' => 'color:red;', 'colspan' => 1 ] ], [ 'Third cell', [ 'colspan' => false ] ] ) 

If attributes were also specified in the call - the class and style will be expanded, the rest will be overwritten, the attributes with the logical value false will be deleted.

 <tr class="row"> <td class="cs-left" colspan="2" style="text-align:left;"> First cell </td> <td class="cs-left middle-cell" colspan="1" style="text-align:left;color:red;"> Second cell </td> <td class="cs-left" style="text-align:left;"> Third cell </td> </tr> 

With the help of a magic wand, which is not a familiar part of the CSS selector (this is the only exception that can be avoided), you can control how nesting levels are handled:

 h::{'tr| td'}([ [ 'First row, first column', 'First row, second column' ], [ 'Second row, first column', 'Second row, second column' ] ]) 

 <tr> <td> First row, first column </td> <td> First row, second column </td> <tr> <tr> <td> Second row, first column </td> <td> Second row, second column </td> <tr> 

If an array is obtained from a database or other storage, it is convenient to use such an array directly, and this can be done by passing it into a special insert attribute:

 $array = [ [ 'text' => 'Text1', 'id' => 10 ], [ 'text' => 'Text2', 'id' => 20 ] ]; h::a( '$i[text]', [ 'href' => 'Page/$i[id]', 'insert' => $array ] ) 

 <a href="Page/10"> Text1 </a> <a href="Page/20"> Text2 </a> 

It is possible to write all the attributes in one line:

 $array = [ [ 'id' => 'first_checkbox', 'value' => 1 ], [ 'id' => 'second_checkbox', 'value' => 0 ], [ 'id' => 'third_checkbox', 'value' => 1 ] ]; h::{'input[id=$i[id]][type=checkbox][checked=$i[value]][value=1]'}([ 'insert' => $array ]) 

 <input id="first_checkbox" checked type="checkbox" value="1"> <input id="second_checkbox" type="checkbox" value="1"> <input id="third_checkbox" checked type="checkbox" value="1"> 

And all this can be expanded


This class represents only general, non-binding HTML generation rules that can be used regardless of the environment.
But sometimes you want to simplify the implementation of more complex routine operations.
For example, I use many UIkit elements in the frontend, and, for example, the switch needs specially prepared HTML.
By copying the original input processing code and slightly editing, you can get the following result:

 h::radio([ 'checked' => 1, 'value' => [0, 1], 'in' => ['Off', 'On'] ]) 

 <span class="uk-button-group" data-uk-button-radio=""> <label class="uk-button uk-active" for="input_544f4ae475f58"> <input checked="" id="input_544f4ae475f58" type="radio" value="1"> On </label> <label class="uk-button" for="input_544f4ae475feb"> <input id="input_544f4ae475feb" type="radio" value="0"> Off </label> </span> 

You can also override the pre_processing method, and implement arbitrary attribute processing just before the tag is rendered, for example, if the data-title attribute is present, I hang the class, and thus get a pop-up hint over the element when hovering.

Advantage of use


HTML is generated without a chance to leave the tag unclosed, or something like that.
Everywhere, common processing rules are used, which are logical, very quickly remembered, and are much more often convenient than vice versa.
You can use it with absolutely any tags, even with web components (I will not write an example, and so many examples).
There are no dependencies, it is possible to inherit and redefine / expand anything at will, since this is just one static class and nothing else.
The output is a regular line that can be easily used with absolutely any code, used as input for the next class call.

Where to take and read


On this, perhaps, enough examples.
GitHub source code
There is also documentation with a detailed explanation of all the nuances of use and all supported constructions.
You can put it through the composer , or simply by connecting a file with the class.
Example of inheritance with added functionality

Plans


It’s still necessary to refactor __callStatic () without breaking anything)
It would be cool to rewrite to Zephir, and make an extension for PHP (this is more of a dream, but maybe I’ll take it once).

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


All Articles