-
Notifications
You must be signed in to change notification settings - Fork 11.6k
[12.x] Add @maybe Blade directive for conditional HTML attributes
#57235
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
Conversation
|
To me, the naming is not intuitive. I would call it |
I appreciate the feedback, but I'll leave the naming to Taylor. |
|
It'd be fine with |
|
Good one! One problem is the naming, I think |
|
I updated the naming section of the PR description. |
|
Thanks a lot 👍🏻 |
|
I think this should mirror |
It is different in the sense that the For this use case here it makes it longer by 6 chars for the majority of situations where we have one data attribute. Don't really like it because the reason for the PR is to make things less verbose. That said, I'll have a look if we can support both. Edit: Happy to implement. For now I'll leave it to Taylor to decide first. {{-- Attributes: 1 --}}
<a @if($link->title) title="{{ $link->title }} @endif">
<a @maybe('title', $link->title)>
<a @maybe(['title' => $link->title])>
{{-- Attributes: 2 --}}
<a @if($link->title) title="{{ $link->title }} @endif" @if($link->rel) rel="{{ $link->rel }} @endif>
<a @maybe('title', $link->title) @maybe('rel', $link->rel)>
<a @maybe(['title' => $link->title, 'rel' => $link->rel])>
{{-- Attributes: 3 --}}
<a @if($link->title) title="{{ $link->title }} @endif" @if($link->rel) rel="{{ $link->rel }} @endif @if($link->clickId) data-tracker="{{ $link->clickId }} @endif">
<a @maybe('title', $link->title) @maybe('rel', $link->rel) @maybe('data-tracker', $link->clickId)>
<a @maybe(['title' => $link->title, 'rel' => $link->rel, 'data-tracker' => $link->clickId])> |
|
fyi, previous unsuccessful attempt at |
Similar. Mine is minus the complexity. Though, apparently high demand for a solution. |
|
I tried using This directive is quite valid. It could be expanded to include conditions like the |
|
I haven't dug deep into how this renders attributes, but I would expect the following to happen for these different attribute types. [
'crossorigin', // crossorigin
'data-persistent-across-pages' => 'YES', // data-persistent-across-pages="YES"
'remove-me' => false, // [removed]
'keep-me' => true, // keep-me
'null' => null, // [removed]
'empty-string' => '', // empty-string=""
'spaced-string' => ' ', // empty-string=" "
'zero' => 0, // zero="0"
'one' => 1, // zero="1"
];<div
crossorigin
data-persistent-across-pages="YES"
keep-me
empty-string=""
spaced-string=" "
zero="0"
one="1"
/>This will keep it inline with how Vite handles attribute types and values. |
|
See arbitrary attributes in #43442 for more on these decisions. |
Hey Tim! I already read that in your comment to the PR linked above. But I kindly disagree. Personally I don't care what Vite does, what I care about is how I can add my own attributes in a non-verbose way. This PR seeks to handle the majority case we deal with every single day, not to be "aligned" with the Differences:
As I mentioned in my PR description, I would name this Both concepts are valid, but they cannot be merged in one (one wants to render false, one doesn't). Hence, what you are asking for is unfortunately nothing for this PR. 🙏 |
|
Appreciate you pushing back, @NickSdot! Always appreciated. To clarify, when I say Vite, I mean what we do in Laravel itself. Having different attribute rendering mechanics for two Laravel features seems like a footgun. But even taking the stand that we don't want to be inline with Laravel's attribute handling in the Vite space, I would still push back on the current rendering proposal. If nothing else, we should respect HTML itself. It explicitly mentions that
|
|
I think my brain goes to tooling like jQuery when I see that we don't render empty strings and whitespace only strings. I probably need to think on that some more to come up with a compelling argument as my brain is deep in other stuff right now. None of this is a hill I wanna die on, btw. Just sharing a perspective and prior art in Laravel related to the feature to ensure we keep the framework cohesive when and where it makes sense. |
@timacdonald well, as I mentioned above, there is no way to satisfy two completely contrary concepts in one solution. Below I make a point why both are valid (+ spec conform) and why we need two mechanisms.
I knew that this will be the next argument. :) For the follwing reasons I need to push back again: 1) There are more than one relevant spec here. The ARIA spec for instance. Accessibility APIs expect explicit tokens. E.g: 2) Only valid for presence indicators (boolean attributes) What you are quoting is a separate concept with explicit behaviour. This, however, does not mean that I cannot use true/false in enumerated attributes (next section after the one on your screenshot: 2.3.3 Keywords and enumerated attributes). True/False are not forbidden values for enumerated attributes. We can do whatever we want, just cannot expect "boolean attributes" behaviour from it. To bring a simple example; you will agree that the folowing is neater foo.active = foo.active === 'true' ? 'false' : 'true';than if (foo.hasAttribute('data-active')) {
foo.removeAttribute('data-active');
} else {
foo.setAttribute('data-active', '');
}Enumerated attributes allow us excactly that. And here we are also back to the previous point: ARIA attributes are enumerated attributes, not boolean ones. Both ways are HTML spec conform, even if we ignore that ARIA is a separate spec. 3) Third Party Expectations I am all for following specs. And I also have proven above that we are aligned with the spec. However, I still would like to throw in third party stuff. If a third party expects explicit
Yes, again, I don't object. Both concepts have their place. That's why we need two solutions for it. I believe we shouldn't try too hard to unify something that cannot be unified. It's like in the real world, we have a Slotted screwdriver and a Phillips screwdriver. Both aren't footguns, but solutions for different problems. 4) Last but not least You get the point: this is not only for custom data attributes.
Unfortunately, it (subjectively) feels like Taylor tends to close PRs when you hop in with objections. So if your objections are not fully thougt out... it's demotivating to be closed just because. No offense, of course! ❤️ I hope my arguments above are compelling enough to "book a win" here. |
No offense taken. It is my job to offer opinions here and there. Sorry if I've put you off here or on other PRs. I can see utility in a feature of this shape. FWIW, I hope some form of this gets merged. |
No, it's also not like that. Wasn't put off myself. So far you luckily been supportive to all my PRs. Sorry if I didn't express myself clear enough. 🙏 |
|
Awesome! |
|
I fully agree with @timacdonald and would rather see a single |
|
Adding to @willrowe's comment, the @attrs([
'aria-hidden' => $hidden ? 'true' : 'false',
'aria-expanded' => $expanded ? 'true' : 'false',
])One can easily add a helper to their code base if wanted. Or just use @attr([
// json_encode will output booleans, numbers and null as a unquoted strings
'aria-hidden' => json_encode($hidden),
'aria-expanded' => json_encode($expanded),
])But attribute toggling, as @timacdonald described, would be very awkward to accomplish with the current proposal. Also, calling the directive Mind that Blade's directives can also be used for non-HTML content, like markdown emails and Envoy tasks. I'd prefer, if added, for it to be called something like If different behavior due to Insert "Why not both?" meme here We could add a A |
This is Blade. Just saying. Y'all keep discussing things that this PR doesn't seek to solve. As we now already know there must be two different solutions because both concepts are diametrical to each other. This isn't "tailored" to one persons requirements, this is the majority use case. We all set title and rel attributes all the time. The alternative examples proposed above are hilarious. You realise that they are longer than writing the actual control flows this PR attempts to get rid off? About naming, I repeat, I leave that to Taylor. Guys, keep on bike shedding unrelated stuff instead of working on a complementing PR to add the other missing piece. I am sure that's how we will get good things! ✌️❤️ Edit: Edit 2:
|
Of course not. Those would be covered in the behavior everyone would expect a With sane rendering rules that follow the HTML spec, minus
Sure, mate. It is such an odd case it got a custom directive, a wrapper class, and a section on docs. https://laravel.com/docs/12.x/blade#rendering-json
Yes, it is. Or at least when one prioritizes The gripe is not on the value of the directive, as I, and I am sure others who commented out, like the proposed shorthand syntax in general. The gripe is on the proposed esoteric rendering rules. This argument doesn't make any sense on the But whatever, you do you. Good luck with your PR. I like the idea, just not the oddities, such as treating booleans as strings (which is perplexing). If not merged, consider publishing it as a package. I am sure many other developers would benefit from it for Have a nice day =) |
|
I answered the HTML spec question above in detail. This is spec conform. Read up enumeration attributes instead of ignoring it and liking comments which also got it wrong. Enumeration example from ARIA: Enumeration example from app: These have nothing to do with boolean attributes. Unfortunately, I cannot do more to help you to understand the difference.
Thanks for making the argument for getting this merged. And yet you don't want to see it merged. Because it isn't tailored to your use case? ;) I wish you the same! |
I do want to see it merged. Just not with such esoteric rendering rules. I find the syntax great: <a href="..." @attr('title', $title)>{{ $label }}</a>Where the The proposed syntax is very handy. Just not your particular use case for rendering attribute values in such a manner no one would expect.
Not my use case. HTML attributes spec. Imagine someone using Web Components opening issue after issue as they expect boolean attributes to behave conforming to the spec. Enumeration attributes are another spec suitable to specific use cases. It is not the general use case. And as such, IMO, it could be subject to a future addition, as I believe the general use case would benefit more developers. Or even provided by a 3rd-party package. I am sorry. I won't spend more of my time trying to help you bring this addition to the framework. I wish you the best luck. |
|
HTML spec is too broad. While others can be functionally like |
Agree on this.
This defeats the goal of the PR because it would too long. Multiple directives solve that. |
|
It's not about what either 4 or 15 people want. It's about what's best and most maintainable for the framework as a whole. I'm making my argument for what I think is best, and you make your argument for what you think is best. Then the BDFL comes in, passes judgement, and we all move on with our lives. Of course 2 directives could exist. Or 3, or 4, or 10. But that adds a maintenance cost. If we choose to pay it, great. I've made my case for a single directive I believe best covers the use cases. |
Now, that looks smart 😲 Very intriguing solution 🤔 I'm usually not in favor of multiple information within one string, but on one hand, it's pretty common in the framework already, and on the other, it's both quite short and addresses some problems from this discussion fairly well 👍🏻 |
About the
I disagree with this take. From what I can see, the last PR that generated this level of discussion was at least 6 years ago. This PR is already in the top 10 most-commented PRs in the entire framework. That tells me the feature itself is valuable and people actually care about it. |
I'd be totally down to that—good point 👍🏻
And it's a discussion needed to make a PR result in well-implemented features 👍🏻 |
|
Personally, I still believe the boolean case as the opposition here wants it is the minority; we use it less. I'd keep it as is and don't invert. However, I'd not die from it being inverted. If it would be decided to invert, I'd to say this would be more nice: @attr('foo=', true) -> foo="true"
@attr('foo', true) -> foo
@attr('foo=', false) -> foo="false"
@attr('foo', false) -> nothingBasically it says, if the |
|
Makes sense 👍🏻 |
Since we start complicating things. 😂😂😂 |
|
Released this as a package for the time being: The special @taylorotwell, I still believe such common functionality should be part of core.
The package takes all feedback from this discussion into account. Potentially, this is the "great path forward" you've been looking for. If that's the case I am keen to update the PR. Please lmk. Otherwise, just close -- no hard feelings. 🙏 |
That PR was reverted here: #57151
From this criterion, we can exclude the following classes of attributes from the discussion, as they would be "so subset" for having their own spec.
With such a narrowed-down set of attributes, what is really left? Regular HTML attributes? I guess everyone agrees that the first example on the first post is one of the common goals: Going from this: <a href="#" @if($title) title="{{ $title }}" @endif>Link</a>To this: <a href="#" @attr('title', $title)>Link</a>And I guess everyone would agree this outcome is undesirable: <a href="#" title="false">Link</a>How to prevent this from happening if the newly introduced directive gets merged? In some part the discussion derailed from spelling out boolean values (e.g., spelling Now that we agree boolean attributes are, in your own words, "are the subset; so subset that they have their very own spec.", along, by the same criteria, with enumerated attributes, custom Such as the case of the Do we want booleans spelled out for these attributes that are not on a subset to have their very own spec? Do we want Or do we want these attributes to be hidden when they are provided with a dynamic variable with a falsy value? Another thing that made me wonder is how common is the case to spell out boolean values, that they would be treated as first-class citizens? Do we have any stats on how often we need to reach for these attributes? Even if boolean attributes are to be considered a subset for having their own section on spec, what is more common: to toggle boolean attributes dynamically or to spell out boolean values on arbitrary attributes? And we didn't touch on the issue of 3rd-party integration, such as <div @attr('wire:poll', $shouldPoll)>...</div>On the other hand, there is a very common case where we would not want booleans spelled out. When using the For example: <input type="radio" @attr('value', $value)>or <input type="checkbox" @attr('value', $value)>We wouldn't want them to be spelled out, as the From a maintenance perspective, imagine a user using the newly introduced To make it work as expected, a user would need to do something like this: <input type="checkbox" @attr('value', $value ? 1 : 0)>And that is also precisely what I think one should do if they need to spell out boolean attributes. Of course, if we knew how common it is to need to spell out boolean values when using regular HTML attributes (not boolean attributes, not enumerated, not But we don't know. At least no one provided a clue on this need. If anyone has an estimate, please share so we can enrich the discussion with actual data. In summary:
Based on this, I still think spelling out boolean values by default is not the best option for any class of attributes, being a subset or not. I also agree that usage on subset cases that need their very own spec, such as boolean attributes, enumerated attributes, custom |
Bro is so focused on being against something that he didn't even realise that this isn't even the case anymore. I agree; changed my mind on it. It's fixed, sir. (didn't read further after this) @rodrigopedra, big fan of your contributions, code- and opinions-wise, here. I believe you are smart and that you add value. Now, however, I believe you maybe need to have a quick reset. 🤞🙏✌️ |
I know. Does that change the general acceptance of the syntax? No. |
|
Hey folks, I have not been keeping up with the thread here, sorry. I very much appreciate the pushing, pulling, and challenging things. I can feel how much we all want thing in Laravel to be the best they can, even when we don't all see things the same way. I was asked my take on the new proposal. I see that this new proposal with the After taking all this in and seeing the back and forth, I've personally changed my mind on how I think this should work. I know this is Blade but I think we should take inspiration from what React / Vue are doing. I reckon someone like Evan You, creator of Vue, has thought much more deeply and for a far longer time about these things than anyone in this thread and has likely thought of a lot of tradeoffs as well. I'm sorry for the flip in opinions here, but strong opinions loosely held. My original proposalI compared my original proposal to some common frameworks in the Laravel space:
My new proposalI now feel our defaults should better match Vue and React which also start to align better with Nick's original proposal. Still has some deviations.
What about booleans?I think we should also take inspiration from Vue, React, etc. We maintain an internal list similar to how Vue handles things. If we encounter any of these attributes, we render the attribute as a HTML boolean attribute. This list is based on the current specification.
Those with a keen eye will see this does not match exactly what Vue has. I have removed a few attributes that are no longer part of the spec, including:
It also has some additional attributes that Vue has not yet included. Unfortunately, rendering these boolean attributes is not consistent across frameworks. Here are how some popular libraries render boolean attributes:
My feelings here are that we should do what Vue does. It feels closest from a PHP variable -> HTML boolean conversion. I'm not really sure what Alpine is doing with those last couple; See this comment on MDN:
What about non-standard attributes like
|
|
@timacdonald thanks for the feedback! One issue I see with not having the special syntax is that we would have no way to differentiate in situations where we want to not rendering instead of
I'd argue that having Did you see the behaviour matrix here and what directives I added? It allows for everything.
The behaviour surely could be adapted to what you propose here (which basically would be inverting the special syntax cases, I believe?), but should we really drop the special syntax in favour of having this pretty complex |
|
Personally, I'd like to push for a single way to do the thing that just works. I don't love that the overhead exists while authoring the template, choosing between directives or @attributes([
'foo' => $v,
'data-foo' => $v,
'aria-foo' => $v,
'wire:poll' => $v,
])
For this PR? Yes. For consuming applications? No. I wouldn't even imagine that we would document this hook. I see I love that the framework hides complexity in itself, 3rd party packages, or application service providers, to then surfaces a simple API to the main consuming user. That is just my perspective, though, and I totally see there is no clear answer here and a lot of the suggestions here for API and values are valid. |
|
@timacdonald Ironically, you changed your mind to what I proposed initally and I changed mine to what you proposed initally. 🫠 Please note, inverting the behaviour would mean that this: <a href="#" @attr('title', $title)>Link</a>renders as <a href="#" title="false">Link</a>Rodrigo is IMO correct here, this is a rather undesirable outcome (at least in the majority of cases). Initially I was thinking that it is better, but I eventually changed my mind on it. While I don't want to make this to 1:1 work like Blade component attributes, I think it is woth noting that <x-phone :data-something="false">Does not render the attribute. <span>While <x-phone :data-something="true">renders as <span data-something="data-something">Wouldn't it be confusing if
I agree that one directive would be nice. But I really had to give up on it. There is no single "intended", unfortunately. Aria works slightly different than others, a dedicated directive can account for that. Also, having <button @attr('aria-label', $label) @attr('aria-hidden', $hidden)></button>
<button @aria('label', $label) @aria('hidden', $hidden)></button>The
Yeah, that's probably right. Alright, so the open questions are now:
|
|
Here in comparison with Blade component behaviour.
Since So again, not sure if we really should do the opposite approach as you propose. |
|
So, after tackling empty/whitespace strings (#57467) for a few hours, I am back to booleans now. I understand that Tim likes the Vue way better. I am also a big Vue fan and believe Evan is a giga brain. But, stricly speaking, Alpine is more correct (spec) here. And Blade Component attributes work in the same way as Alpine. However, I don't see a reason how the verbose way would make sense. Since Blade, as well as Alpine (somewhat), are close to Laravel I feel we should try to get (eg) I am not very familiar with Alpine. Maybe someone could PR this to Alpine? I opened #57469 to handle Blade components side of things. Edit: "more correct" would probably better be phrased "more clean". The verbose way is accepted, while the non-verbose way is the standard.
|
|
A) Booleans B) Arrays @attributes([
'foo' => $v,
'data-foo' => $v,
'aria-foo' => $v,
'wire:poll' => $v,
])at all yet. Yes, that is easy to add. I don't really see why we need it from a 'length'-wise point of view. But I assume you would want it because how other things in the framework work? Keen to add it when we are at a point where it was signaled that there is a chance to get this merged. |
|
Personally I think we should just follow what @timacdonald suggested on this, while leaning towards Vue for behavior where there are differences. 👍 |
|
Let me know if you would like a hand making any changes here. I've made a lot of noise here, so I'm happy to put my code where my mouth is, so to speak. As a side note, regarding the accidental incorrect rendering of tags, one interesting thing the hook approach could provide is a 3rd party library that validates common attributes with known allowed values, e.g., Blade::compileAttribute('/^dir$/', function ($value) {
if (empty($value)) {
return '';
}
if (! in_array($value, ['ltr', 'rtl', 'auto'])) {
Log::warning("Unexpected value [{$value}] for rtl attribute encountered.", [
'url' => request()->fullUrl(),
]);
return '';
}
return 'rtl="'.$value.'"';
});There could be a hook that ensures title and alt tags are not Just a thought I had overnight while I was pondering on this one. |
|
@timacdonald That reminds me of the example, I posted above:
Seems to be something worth looking into. As you have shown, there's even more potential than meets the eye 👍🏻 |
|
@timacdonald not sure you are talking to me or Taylor? Just to make sure I understand correctly, we are going here with the exact opposite to what Blade Component attributes do (regarding booleans)? |
You literally above argued that using the hook should be a very rare case to counter my concerns. Now you are saying the very standard case should be handled by a 3rd party lib. It doesn't make sense, I am afraid. It also doesn't make sense that we here do the exact opposite of how Blade component attributes work. The modifier solution I propose perhaps still isn't it. But what is proposed now is also not. Given that my feedback to it, that is based on 19 days of very hard discussions, is completely ignored I am afraid I cannot support how this played out. |
|
Hey @NickSdot, Thanks for the PR and the discussions it has initiated. Appreciate you and your time. I'll follow up with Taylor and see if we are keen to pursue this feature in whatever form it may take. Thanks again. |






Edit: Based on the discusstion here, I released an advanced version as a package for the time being:
https://github.com/NickSdot/blade-html-attributes
This PR adds a new
@maybedirective that conditionally renders HTML attributes with values, complementing the existing boolean attribute directives like@checked,@selected,@disabled,@required, and@readonly.Problem
While we have directives for boolean attributes, we still need verbose
@if ... @endifblocks for attributes with values:We cannot keep adding specific directives for every possible attribute, so we need a dynamic solution.
Solution
The
@maybedirective renders an attribute with a value only when the value is notnull, not an empty string, and not whitespace-only:Before/After
Behaviour Matrix
The directive intentionally differs from a simple
@if()check by treating0andfalseas valid values, since these are common in data attributes for counts, flags, and boolean states.'foo'data-attribute="foo"0data-attribute="0"falsedata-attribute="false"truedata-attribute="true"''null' 'Naming
I considered several alternatives:
@when(too generic, likely better for other future use cases),@flag(implies boolean values only, whereas this handles strings, numbers, bools),@attributeand@optional(too long),@attrand@set(don’t make the conditional nature clear).@haswas tempting as it reads well: “has$title, then rendertitle”. However, the parameter order would need reversing to@has($title, 'title'), which breaks the pattern of other Blade directives where the static value comes first.@optis appealingly terse but perhaps too cryptic.@maybehas the right balance. It’s short, clearly conditional, and reads naturally with the attribute name first: “maybe render title if$title”.