Problem/Motivation
#2285451: Create addClass() and removeClass() methods on Attribute object for merging css class names. added methods on the Attribute class to add and remove CSS classes. This is super useful and a key part of #2322163: [meta] Consensus Banana Phase 1, move CSS classes from preprocess to twig templates..
It would also be useful to have methods for adding/removing non-class attributes so that this could be done cleanly from within Twig templates.
Proposed resolution
Add methods to the Attribute object for adding/removing attributes, some proposed method names (we would only add one set, we need to pick from these or come up with another set):
set/remove
setAttribute/removeAttribute
setAttribute would accept the attribute name and value.
removeAttribute would accept the attribute name only.
Example syntax within a Twig template:
set/remove:
{% set classes = [
'test1',
'test2',
'test3',
'test4',
]
%}
<div {{ attributes.addClass(classes).set('data-foo', 'bar').remove('role') }}></div>
setAttribute/removeAttribute:
{% set classes = [
'test1',
'test2',
'test3',
'test4',
]
%}
<div {{ attributes.addClass(classes).setAttribute('data-foo', 'bar').removeAttribute('role') }}></div>
Remaining tasks
Patch
Tests
User interface changes
n/a
API changes
API addition to be able to manipulate non-class attributes on Attribute objects.
Comment | File | Size | Author |
---|---|---|---|
#63 | interdiff.txt | 1.05 KB | lauriii |
#63 | add_methods_for-2325517-63.patch | 5.17 KB | lauriii |
#58 | interdiff-53-58.txt | 477 bytes | Tom Verhaeghe |
Comments
Comment #1
joelpittetI'm in favour of the API addition and just 'set/remove' pair added. addClass/removeClass do array manipulation. set() will add or replace.
Duplicate attributes aren't ideal so, add may give the wrong impression. 2¢
Comment #2
star-szrTo be clear I'm not proposing adding both sets, just thinking about what will be intuitive for people. I don't have a strong opinion right now. Tweaked the issue summary to indicate that we need to pick.
Comment #3
davidhernandezSo it is set/add versus addAttribute/removeAttribute? (or maybe add is just setAttribute) setAttribute/removeAttribute seems clearer, and is following the same naming convention.
At first glance, this line might be confusing:
Is it removing a class?
But this should be clear:
Comment #4
seantwalsh@Cottser I agree with @davidhernandez, I think it is important that the syntax be consistent and also easy to understand. So +1 to addAttribute/removeAttribute.
Comment #5
joelpittetYou need to know that you are working on an Attribute object for those methods to work in the first place so it seems redundant to add that to the method names as well.
Also addClass(class1, class2, class3) takes and array and merges onto one attribute.
@see http://api.jquery.com/addclass/
addAttribute(name, value)/set(name, value) would not merge it would set the attribute to a value or override it's value.
@see http://api.jquery.com/attr/
Which could do this nice bit:
or we could just copy them directly and call it
attr()/removeAttr()
though that doesn't seem normal naming conventions for Drupal.Comment #6
davidhernandezSure, if you have a good understanding of how this works, you would know you are dealing with an Attribute object, but will your average themer know that or even understand what it means? Most people don't think about the behind the scenes, they are only thinking about what is directly exposed to them. My concern is that if the naming convention isn't consistent, it makes it harder to remember and intuit.
I agree that set over add makes sense, if there isn't anything to actually add.
Comment #7
star-szrI like @davidhernandez's setAttribute/removeAttribute suggestion. In this case I think a bit of verbosity is for the best.
Comment #8
star-szrI just remembered that removing attributes can already be done now via the |without filter, but no such luck with |merge of course. Looking at the jQuery stuff I wonder if adding or removing should support arrays/hashes as shown in @joelpittet's #5:
{{ attributes.removeAttribute(['alt', 'title']) }}
Otherwise you'd have to chain a bunch of set/remove to do multiple manipulations.
Edit: For remove you could just pass multiple arguments so this is more about adding but I still think remove should work either way, and I think the addClass/removeClass works like that already.
Comment #9
star-szrAdding banana as related so this is more findable.
Comment #10
Wim LeersWhile working on #1869476: Convert global menus (primary links, secondary links) into blocks, I stumbled upon needing to add an attribute in a Twig template, but it's sadly impossible AFAICT. Hoping this issue will move forward, because it's indeed going to be pretty common.
This will also allow us to remove most of
system_preprocess_block()
for example, which sets therole
attribute on several blocks (amongst others).Comment #11
star-szrThanks @Wim Leers, that's great to know. Bumping this up at least in focus.
Comment #12
star-szrUpdating the issue summary to use replace addAttribute with setAttribute.
Comment #13
lauriiiComment #14
lauriiiComment #15
lauriiiComment #17
lauriiiFixed one documentation problem I found
Comment #18
joelpittetthis is looking really good, it also has tests:) Just a couple of questions @lauriii
This seems like it would prevent something like
<img{{ attributes.setAttribute('alt', '') }}>
Is this ok?
The doc's may need to indicated this only referes to AttributeArray instances because they hold arrays, but otherwise this makes sense.
Comment #19
lauriii#18.1 actually makes sense so now we add even empty Attributes. Not sure if we should test if $value is FALSE and then we wouldnt set attribute. It would make sense but I didnt find any use case for that.
I think it actually makes more sense if setting class, we replace the whole attribute. I dont think setAttribute should be used as a replacement for addClass.
Comment #22
lauriiiComment #23
joelpittet@lauriii could you add a test in there to make sure this works with boolean attributes too?
like checked/selected?
I think that may be the only thing missing from this.
Comment #24
rteijeiro CreditAttribution: rteijeiro commentedWorking on this one from DrupalCamp Novi Sad.
Comment #25
rteijeiro CreditAttribution: rteijeiro commentedThere is still a test that is not passing. Lauri is going to teach me how to fix it using his drunken coding skills.
Comment #27
rteijeiro CreditAttribution: rteijeiro commentedThere is a problem when creating a boolean attribute using the constructor. Maybe we need a follow-up to dig into the problem. Anyway I fixed the test with a workaround.
Comment #28
rteijeiro CreditAttribution: rteijeiro commentedGo testbot, go!!
Comment #31
lauriiiComment #32
drifter CreditAttribution: drifter commentedTested this manually in a TWIG template. Works well. One thing I found confusing was passing an array to setAttribute() - the syntax in #8 does not work (set multiple attributes at once). Passing an array as a value works
and results in joining the value array with spaces, which is basically only useful for the "class" attribute, I can't think of another use case. So maybe needs better documenting of the array passing version? Otherwise RTBC.
Comment #33
drifter CreditAttribution: drifter commentedDiscussed with lauriii. What mislead me was:
If you're using the array syntax, you're not adding multiple attributes (as in alt, title, href), you're adding multiple attribute values (as in class1, class2, class3). Maybe change documentation to say something like:
Comment #34
lauriiiThanks, I missed that. The documentation is completely wrong!
Comment #35
davidhernandezShould get a change record with this API addition.
Comment #36
lauriiiFixed the documentation on setAttribute
Comment #37
drifter CreditAttribution: drifter commentedLooks ready to me.
Comment #38
lauriiiInterdiff was 666 bytes, not sure if its a good thing to but this into core ;)
Comment #39
alexpottThe if does not appear to be tested and i'm not sure why we have it. Do we anticipate code calling this with $attribute set to FALSE or NULL?
Also #35 asks for a CR and we don't have one.
Comment #40
alexpottre #38 and on halloween too!
Comment #41
davidhernandezI added a draft change record.
Comment #42
star-szrThanks all!
I agree we should remove the
if
@alexpott mentioned in #39, it feels like babysitting.s/Attribute/attribute, we are not referring to the Attribute object.
Maybe "Removes an attribute from an Attribute object" here? Internally it's an ArrayObject anyway, not just an array... :)
Comment #43
lauriiiComment #44
Alienpruts CreditAttribution: Alienpruts commentedComment #45
Alienpruts CreditAttribution: Alienpruts commentedAs suggested by Alexpott at #39 : removed the if-check and tested.
Be gentle : first sprinter here :)
Comment #46
Alienpruts CreditAttribution: Alienpruts commentedWorking in #42, sorry Cottser, forgot about it :)
Comment #47
Alienpruts CreditAttribution: Alienpruts commentedChanged lines as commented by Cottser #42, removed a forgotten comment line in previous patch.
Learning as we go :)
Comment #48
roderik@Alienpruts: learning as you go is the first objective :)
The change to a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php fell out of your uploaded patch.
Also, general question:
looking into offsetSet(), it doesn't look to me this actually merges stuff, it completely overwrites any existing value. (Or?)
If so, then the question is: is this supposed to be able to merge attributes into existing ones? My guess is no (i.e. the code is fine and the comment should be amended).
But I don't use this code, so what do I know...
Comment #49
davidhernandez@roderik, setAttribute() should override an existing value. If the attribute already has 'id' defined, it will be changed, which is the intention. The only case where this would really be an issue is array attributes, like 'class'. But class is one of the only attributes that uses multiple values, and we have addClass() to account for that.
Comment #50
Alienpruts CreditAttribution: Alienpruts commentedComment #51
Alienpruts CreditAttribution: Alienpruts commentedOK, bear with me...
Redid patch #36, included all changes since then.
Interdiff is from #36 on.
Problem was an issue with git (or more precisely, my mis-use of the git apply command :) )
Sorry, hope this works...
Comment #52
roderik#49 Cool, thanks. Alienpruts will pick this up.
Comment #53
Tom Verhaeghe CreditAttribution: Tom Verhaeghe commentedComment #54
Tom Verhaeghe CreditAttribution: Tom Verhaeghe commentedComment #55
Alienpruts CreditAttribution: Alienpruts commentedHello Tom,
tnx for the patch, appreciate it.
In the future, may I suggest you put a small comment with it describing what it is you did? Would be super helpful :)
Tnx again :)
Comment #56
Tom Verhaeghe CreditAttribution: Tom Verhaeghe commentedAlienpruts: I will do so. Baby steps ;-)
Comment #57
lauriii@Tom Verhaeghe: Thank you for working on this issue! I found few more things on the issue that could be fixed before moving this forward. Would you like to take look on these?
This row's indentation is wrong
Should we create follow-up issue to fix the problem why we cannot add boolean type of attributes on the initialization of the Attributes object. This is introduced on #27
Comment #58
Tom Verhaeghe CreditAttribution: Tom Verhaeghe commentedHere's a fix where I corrected the indentation.
Comment #59
Alienpruts CreditAttribution: Alienpruts commentedTnx Tom, looks good :)
@laurrii : I'm for creating a follow-up issue.
Comment #60
joelpittetThis looks like it should move to AttributeValueBase so we don't have to add the same method on all 3 child classes.
@lauriii and @rteijeiro I don't see where it's failing on booleans in the constructor in either #27 or #25 can you add it to a test, maybe we can solve it here?
Comment #61
joelpittetI moved the value method to the AttributeValueBase, and tested boolean in constructor which passes locally, and used short array syntax.
Comment #62
joelpittetMoved the boolean assertions to the correct test, the constructor test.
Comment #63
lauriiiI moved testing boolean also to the constructor on
testRemoveAttribute()
.Comment #64
alexpottThis issue is part of enabling the theme improvements of moving logic out of preprocess and into templates and so it allowed as per https://www.drupal.org/core/beta-changes and it's benefits outweigh any disruption. Committed 80c7322 and pushed to 8.0.x. Thanks!
Comment #67
jwilson3Edit: ugh. wrong issue, sorry -- too many tabs open! :(
Anyway, I was trying to use setAttribute() to solve an unrelated issue and it didn't work (i moved the comment to the right issue #293295-11: Increment <ol> numbers on views paged results, i know this is the wrong place to put this, so again I apologize.