-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add an attr
function to make outputting HTML attributes easier
#3930
base: 3.x
Are you sure you want to change the base?
Conversation
b680b5f
to
9c29c4d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it a lot. Let's continue the work. Tell me of you need help @mpdude
extra/html-extra/HtmlExtension.php
Outdated
|
||
$result = ''; | ||
foreach ($attr as $name => $value) { | ||
$result .= twig_escape_filter($env, $name, 'html_attr').'="'.htmlspecialchars($value).'" '; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's consider false
as a way to disable the attribute? And true
would not generate the value part?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that makes sense. I'd have false
omit the attribute output altogether. For true
, I'd use something like name="name"
for backwards compat with (X)HTML.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost everywhere else, indeed, false
should remove/hide the attribute.
""
should not happen, i guess, but should be dealt with attention, because the following have all the same meaning:
disabled=""
disabled
disabled="false"
disabled="disabled"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the aria cases, if you want, there is some classic cases + docs here
with some test cases if you need too https://github.com/symfony/ux/blob/647532ab688f79acfbcb1c895e88a8b2f1a502f6/src/Icons/tests/Unit/IconTest.php#L226
(other similar in the TwigComponent / ComponentAttributes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except for aria-*
attributes that support keyword and enumerated values. In some cases aria-*
attributes support true
and false
string values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fortunately, Twig and PHP have separate types for strings and booleans. Passing the string true
or false
will not trigger the special logic for boolean values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stof I guess what I was trying to say is that yes you could pass 'true'
or 'false
' as parameters but this would require string coercion when setting the value.
The previous comment stated that:
Almost everywhere else, indeed, false should remove/hide the attribute.
I was pointing out that aria-*
attributes do accept a string value of 'false'
and in those cases a coercion of bool false
to string 'false'
might be warranted. This could also apply to data-*
attributes.
IMO:
null
value always removes the attribute from the final string rendering.true
andfalse
values foraria-*
attributes should coerce boolean to their string values'true'
and'false'
true
andfalse
values for all other attributes should act as boolean html attributes. ie true renders the attribute, false removes the attribute (as per @fabpot comment).
f65ee8f
to
c9acca6
Compare
f7c3b7a
to
c7bacfd
Compare
c7bacfd
to
03a5de1
Compare
@fabpot Regarding tests: I suppose the fixture-based tests ( What's the best way to cover lots of scenarios in the fixture-style tests? It's easy to get lost when there are lots of cases but just one |
You can write both, but
That's indeed a current limitation. |
@@ -124,4 +130,85 @@ public static function htmlCva(array|string $base = [], array $variants = [], ar | |||
{ | |||
return new Cva($base, $variants, $compoundVariants, $defaultVariant); | |||
} | |||
|
|||
public static function htmlAttrMerge(...$arrays): array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can maybe have a @param
to better describe $arrays
?
return $result; | ||
} | ||
|
||
public static function htmlAttr(Environment $env, ...$args): string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a @param
for $args
?
{ | ||
$result = []; | ||
|
||
foreach ($arrays as $argNumber => $array) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not guaranteed that we have a number here.
extra/html-extra/HtmlExtension.php
Outdated
|
||
$value = CoreExtension::toArray($value); | ||
|
||
$result[$deepMergeKey] = array_merge($result[$deepMergeKey] ?? [], $value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use [... ]
when we can ? As it's slightly more performant than array_merge
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (is_numeric($name)) { | ||
$style .= $value.'; '; | ||
} else { | ||
$style .= $name.': '.$value.'; '; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why this specific case for style.. i may have missed the reason earlier in the discussion ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case style
is something like ['color: red', 'font-weight: bold']
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have two other questions then (sorry)
What if "aria" is something like ['aria-disabled', 'aria-current']
?
Does that mean i can do something like this ?
{{ html_attr(
{ style: ['color: red;'] },
{ style: {'color': 'blue'} },
{ style: {'color': 'green'} },
{ style: ['', 'color: black;'] },
) }}
I'm shared between "i understand each of these usages" and "this could be very un-predictable when/if args are assembled from different layers / templates :|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, definetly many edge cases I haven't thought through. Make sure you don't miss the inital idea in #3907, fwiw.
I think we should aim for behaviour similar to array_merge
and not try to do any kind of parsing/interpretation. For example:
html_attr_merge( { style: {'color': 'blue'} }, { style: {'color': 'green'} } } ) }}
would be{ style: {'color': 'green'} }
html_attr_merge( { style: ['color: blue'] }, { style: { ['color: green']} } } ) }}
would be{ style: ['color: blue', 'color: green'] }
– if you want "real" overrides, use semantic keyshtml_attr_merge( { aria: { 'aria-label': 'that' }, {'label': 'this'} } )
would be{ 'aria-label': 'this' }
– I'd rewrite keys from thearia
anddata
sub-array to the "flat" values first and then merge those.
I hope I can come up with reasonable test cases for all those situations so we can discuss them before merge.
cf673c1
to
9722108
Compare
Added a first load of tests for the |
Co-authored-by: Fabien Potencier <fabien@potencier.org>
For reference Yii2 has a similar function: https://github.com/yiisoft/yii2/blob/master/framework/helpers/BaseHtml.php#L1966-L2046 The renderTagAttributes method has the following rules:
CraftCMS uses twig and provides an Vuejs v2 -> v3 also went through some changes for |
} | ||
|
||
if (!is_iterable($array)) { | ||
throw new RuntimeError(sprintf('The "attr_merge" filter only works with mappings or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be " The html_attr_merge
filter only…" . What if the exception is thrown when using the html_attr
function.
How will merging of attributes that support multiple values work? Eg Example: <div {{ attr(
{'aria-labelledby': 'id1 id2'},
{'aria-labelledby': 'id3'},
) }}></div> Should the result be:
This could also apply to |
Closes #3907.
First, it adds a
attr_merge
filter. This filter is intended to be used with arrays that represent HTML attribute name-value pairs. Basically, it works like|merge
, but for the special key namesclass
,style
anddata
it performs merging on thevalue
level.This is intended for the use case where you'd like to add to the attributes for an HTML element based on conditions, e. g. multiple subsequent
{% if ... %}
blocks.Example:
{ class: 'foo' }|attr_merge({ class: 'bar' })
will be{ class: ['foo', 'bar'] }
{ class: 'foo' }|attr_merge({ class: ['bar', 'baz'] })
will be{ class: ['foo', 'bar', 'baz'] }
{ class: { special: 'foo' } }|attr_merge({ class: ['bar', 'baz'] })|attr_merge({ class: {special: 'qux' } })
will be{ class: { special: 'qux', 0: 'bar', 1: 'baz' } }
or in Twig code:
Second, it adds a
attr
function. This function takes one or multipleattr
arrays like above, and print them as a series of HTML attribute markup. All values fromclass
will be concatenated with spaces. Key/value pairs fromstyle
will be treated as CSS property/value pairs. Fordata
, keys will be used to constructdata-{keyname}
attributes.Example:
will generate HTML markup:
TODO: