Problem/Motivation
Our understanding of Twig practices has improved since the first release of UI Patterns:
- 2017. At the beginning, a pattern template was considered as a regular Twig template, with a robust pattern() function instead of the block, include, sandbox, use tags and include, source and blocks functions.
- Then, we considered that hardcoding such a nesting from the templates was bad practice, and even pattern() must be avoided most of the time. It is better inject them from outside.
- October 2018. Patterns Variants were introduced, cleaning the fields list by removing the "weird" one.
- June 2020. ui_patterns_settings was released and we were able to move the variables with "controlled values" (list of values, boolean, strong typing...) out of the fields list.
- October 2022. We introduced during a Monthly the "slots" (UI Patterns Fields) VS "props" (UI patterns Settings), which provide us a mental framework to build our patterns definitions.
- February 2023. We have noticed that we are never using Twig filters with slots, and we can promote this as a good practice
Today, the front dev has a very good tool to build its pattern in a safe and efficient way.
However, there is still an issue: looping on a slot is a risky thing.
For example, this will break if 'slides' value is not a list but a single renderable. :
<div class="carousel-inner" role="listbox">
{% for slide in slides %}
<div class="carousel-item">
{{ slide }}
</div>
{% endfor %}
</div>
See also this comment: https://www.drupal.org/project/drupal/issues/3301853#comment-14901185
Proposed resolution
It is not possible to use a iterable test because render arrays are considered as iterable :
{% set slides = (slides is iterable) ? slides : [slides] %}
{% for slide in slides %}
function twig_test_iterable($value)
{
return $value instanceof \Traversable || \is_array($value);
}
It may not be advisable to create a new filter (which will wrap the value in a list if its not a list) if we said before they are bad practices on slots:
{% for slide in slides|as_list %}
And this may leverage a PHP 8.1 function not compatible with Drupal 9: https://www.php.net/manual/en/function.array-is-list.php
Remaining tasks
So, someone has an idea? ;)
Issue fork ui_patterns-3342390
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3342390-make-the-twig
changes, plain diff MR !26
Comments
Comment #2
pdureau commentedComment #3
pdureau commentedProposal with a filter.
I know we are considering filters on slots a bad practice, because most or all of the Twig filters are too typed and don't fit with slot's free types.
However, we can introduce a list of "slots filters", filters compatible with slots.
Introduce a new one “seq_wrap” (lists are called sequences in Twig):
If the value is a sequence, do nothing. If not, wrap into a single item sequence.
A level parameter:
|seq_wrap(2)(default value: 1) can be useful when slots are wrapped in 2 levels of loops, for example the cells of a table.Proposal with a "sequence" test
Like in Jinja: https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-tests.sequence
Comment #4
pdureau commentedComment #6
pdureau commentedProposal with a "sequence" test: https://git.drupalcode.org/project/ui_patterns/-/merge_requests/26
That doesn't mean we will pick this proposal instead of the other. Let's discuss.
Comment #7
pdureau commentedComment #8
pdureau commentedLet's try to target upstream : https://github.com/twigphp/Twig/issues/3828
Comment #9
duaelfrAs you know, I don't like having to add things in templates to handle Drupal problems. IMHO, Twig templates should be Drupal agnostic to help integrating with templates made for other systems and ease frontenders on-boarding.
The way I suggest to handle this is by preprocessing data coming from Drupal. I wish we had processing plugins but as we don't, this is how I do it in my projects (it's probably missing some edge cases but it's working so far):
In my_module.module:
In src/Helpers/PatternsHelper.php:
In a pattern.yml file:
In pattern-my-pattern.html.twig:
(This helper also have a
handleOptionalFieldsmethod to be able to use a Twig if on slots and ahandleBooleanFieldsthat I don't use that much now we have settings.)Comment #10
pdureau commentedThanks for your feedback @Duaelfr
I 100% agree with your sentence: "Twig templates should be Drupal agnostic to help integrating with templates made for other systems and ease frontenders on-boarding."
That's the reason why:
You are proposing to introduce a new "multiple" boolean property. This duality "new definition property" VS "twig extensions" was already in #3311480: Reduce preprocess hooks usage by adding add_class() & set_attributes() filters where we were hesitating between "set_attributes" & "add_class" properties VS Twig filters.
IMO both way are valid. However, some issues with the "multiple" boolean property are:
Comment #11
pdureau commentedProposal using only what is already provided by Twig:
Result:
Result:
If accepted, there is no code to merge, only documentation ("good practices") to update.
Comment #12
grimreaperHi,
I have added some review comments.
But I have not read the previous discussion, so I may be outdated, and finally the MR is useless?
Comment #13
pdureau commentedHard to tell. In comment #11 I have proposed a solution without MR, but Duaelfr remembered me that some render arrays are starting with "0" (example: facets), so this naive test will not work.
I don't know what to do. No solution seems perfect.
In order of preference:
Comment #14
pdureau commentedPostponed until we get some news from Twig project.
Comment #15
pdureau commentedPull Request was done for Twig: https://github.com/twigphp/Twig/pull/3859/files
Comment #16
pdureau commentedEnough is enough :)
Let's add those 2 tests in UI Patterns 1.x and 2.x now instead of waiting https://github.com/twigphp/Twig/issues/3828 is ready, let's implement
is sequenceandis mappingtests directly in UI Patterns 1.x & 2.x. We will remove them once implemented upstream.We already sure about the API to add, because it is already available in Jinja and other template engines.
However,
is sequencetest is not enough when a list of renderables has mapping keys (non consecutive, strings) instead of sequence (integer, consecutive) keys.For example a list of blocks from page layout or layout builder: each block is keyed by its UUID.
In other words:
So, normalize this list of renderable to be a proper Twig sequence from the
pattern(orcomponentin UI Patterns 2.x) render element itself, as a prerender function, on the first level of each slots, no recursivity.I will do the 2.x implementation in an other issue: #3422265: [2.0.0-beta1] Normalize list of renderables in slots
Someone has to take care of 1.x implementation here.
Comment #17
grimreaperComment #18
pdureau commentedhttps://github.com/twigphp/Twig/issues/3828 has resumed!
So, if you want to wait the upstream, this issue will only be for the prerender.
Comment #19
grimreaperComment #20
pdureau commentedMerged: https://github.com/twigphp/Twig/commit/045f5adec773f6430676fdda078b2fa39...
So, we keep this issue for this remaining task.
However,
is sequencetest is not enough when a list of renderables has mapping keys (non consecutive, strings) instead of sequence (integer, consecutive) keys.For example a list of blocks from page layout or layout builder: each block is keyed by its UUID.
In other words:
So, normalize this list of renderable to be a proper Twig sequence from the
patternrender element itself, as a prerender function, on the first level of each slots, no recursivity.Be careful to not break layout builder :)
2.x implementation has its own specific issue : #3422265: [2.0.0-beta1] Normalize list of renderables in slots
Comment #21
grimreaperComment #22
pdureau commentedDone for UI Patterns 2.x: https://git.drupalcode.org/project/ui_patterns/-/commit/c66fc5d0e4eb9a59...
Comment #24
grimreaperDiscussed with @pdureau, possible in UI Patterns 2 because data structure is will defined. But UI Patterns 1 is too permissive and we cannot be sure of what we are manipulating.