Problem/Motivation

We've introduced the Attributes object to make developer's lives easier, and while this does make things clearer for themers too, it doesn't necessarily make it easier to quickly pick up and use. It introduces a new special object (Drupalism) that does special things (more Drupalisms) which do make it easier to print the contents of the object inside a template. However, themers also need an easy way to add, remove, and modify the Attributes they are given, via some special modifier functions.

The original proposal to simplify settled on in March was to make it easy for themers to *add* classes, however this proposal is designed to make it easier for themers to either add or remove classes, or any other attribute, without needing to resort to a preprocessor function.

This is also inline with the larger goal to move as much "front end" stuff into the template as possible, and if we embrace this concept, we will be able to move a fair number of class assignments directly into the template, making it easier for to people unfamiliar to Drupal on how to add a class to the Attribute object inside a template.

Proposed resolution

The current proposed solution requires adding a set of modifier functions for the Attributes object that can be used inside the Twig templates.

The final syntax has not been set in stone, but one could imagine seeing something not unlike the following:

attributes|add(attribute_name,value)
attributes|remove(attribute_name)

Special handling will be used in the case of really common elements, like classes and ids (maybe others as well?)

attributes.class|add(classname1, classname2, ...)
attributes.class|remove(classname1, classname2, ...)
attributes|setId(id)
attributes|addClass(classname1, classname2, ...)
attributes|removeClass(classname1, classname2, ...)
...

Once implemented, we should update all core twig templates to use these helpers where it makes sense, so that it is self-documenting, and beginners will not have a problem. We will see Twig templates change from:

<tag class="{{ attributes.class }}" {{- attributes }}>{{content}}</tag>

to

<tag {{- attributes }}>{{content}}</tag>

This will bring consistency to the way we add, remove, hide, and modify classes.

This will provide an easy way for core and contrib to resolve issues where classes are hardcoded into a template: #1286530: Meta: Templates hard code class attribute, and shouldn't.

This will have the benefit of never printing empty class="" strings on tags (discussed as bugs here here and here).

Finally, this will allow themers to do things like this:

{# Add a custom class, and remove zebra striping because we're using css3 #}
{% attributes.class|add('custom-class') %}
{% attributes.class|remove('odd','even') %}
<tag{{ attributes }}>

Or in a single line:

<tag{{ attributes|addClass('custom-class')|removeClass('odd','even') }}>

Instead of today's status quo:

// in template.php
function mytheme_sometemplate_preprocess(&$vars) {
  // pseudo-code, not sure if this is even the right way to do this ;)
  unset($vars['attributes']->class['odd']);
  unset($vars['attributes']->class['even']);
}
// in templates/sometemplate.twig
<tag class="custom-class {{ attributes.class }}" {{- attributes }}>

Remaining tasks

Explore this in code, to verify if the functions as proposed above will work or not. In particular, the two uses of "add" and "remove" classes have different arguments based on which method they are called against: one for classes and the other for adding entire attributes. Not sure if this will work or not.

User interface changes

None.

API changes

This will affect how we write twig templates; specifically, it would change the status quo of explicitly printing the classes on a tag separately from the other attributes, which was settled upon in March 2012 here: #1808254: Standardize and simplify the attribute syntax in Twig template files.

Comments

jwilson3’s picture

The "remove" function proposed here was also proposed as "unset" here: #1696912: Provide unset() method in twig templates

jwilson3’s picture

Issue summary: View changes

Allow

jwilson3’s picture

Issue summary: View changes

Added reference to empty class discussed as a drupal bug.

jwilson3’s picture

Issue summary: View changes

Updated issue summary.

cosmicdreams’s picture

This sounds like a such a commonly useful addition that I wonder if anyone has every proposed including it into Twig itself. A simple query on Google did not return any useful results as searching for twig class extensions tends to return documentation about how to extend Twig's classes.

cosmicdreams’s picture

Issue summary: View changes

Clarify sentence

jwilson3’s picture

Issue summary: View changes

Add a statement about how embracing this will make things easier for noobs too.

jwilson3’s picture

Well the thing is that Attributes object is a Drupalism, not a Twigism. Does twig have something related to an object that represents a tag's attributes?

Granted, it does make sense that there would be functions that allow you to easily add a key to an array (eg attributes.class|add(x)) or add a property to an object (eg attributes|add(property, value) )

jwilson3’s picture

Issue summary: View changes

more easier ;)

jwilson3’s picture

Maybe that is *just* the abstraction we are looking for a couple of generic twig filters for adding and removing things.

an add Twig filter:

If the thing you're working on is an object, add a property with the given value (if no second argument is provided, not sure what to do); if the thing being filtered is an array, just add a key; If the thing you are working on is a string, append to the string (?). Maybe provide special cases where things like "attributes.class" can be introspected and handled differently if needed. Then, there remains the question on how to "add" to a multi-dimensional array or adding new objects. Maybe just attributes.x|add(y, z) where "x" can be introspected and created on the fly if it doesnt exist as an array containing "y" and "z"? Does twig already do anything like this?

a remove Twig filter:

If the thing you're working on is an object, delete the property; if the thing being filtered is an array, just delete the specified key. If the thing you are working on is a string, remove the specified substring (?) Then have special introspection that can detect if your working on an "Attribute object, don't *delete* the key or property, but somehow set an internal setting that removes it from __toString() ?

markabur’s picture

Nice writeup! These methods/filters would be super-nice to have as a themer, and I think the syntax is actually less confusing.

Speaking for myself, when I first saw this:

<tag class="{{ attributes.class }}" {{ attributes }}>{{content}}</tag>

I was really confused, and I thought it was a mistake. "Does this mean 'Print the class part of attributes, then print the whole attributes thing?' Won't class get printed twice?" I dug around the Twig docs looking for an example of this pattern and of course didn't find it because it is a Drupalism. Eventually I discovered some Drupal issues that talked about "drillability" and I was able to understand what was happening.

There is dark magic about how {{ attributes.class }} automatically suppresses class from being printed subsequently in {{ attributes }}. So, {{...}} does more than just print, it actually affects the original data in some circumstances. Strange! I guess if we go {{ attributes.class }} a second time in the template, it would be blank?

I wonder if it would be more useful and less confusing to treat {{...}} strictly to mean "print" instead? If we have the remove filter available, then we could do this:

<tag class="{{ attributes.class }}" {{ attributes|remove("class") }}>{{content}}</tag>

To me that actually reads better than the first example, even though there's more to grok. "Print the class part of attributes, then print the attributes thing without the class part." And the full, original attributes object is still available later in the template.

jwilson3’s picture

Now that twig engine is in core should this be moved to core issue queue? What's the process going forward?

jwilson3’s picture

Now that twig engine is in core should this be moved to core issue queue? What's the process going forward?

jwilson3’s picture

Now that twig engine is in core should this be moved to core issue queue? What's the process going forward?

jenlampton’s picture

Component: Twig engine » Twig engine (twig_engine branch)

No, this should probably be done in the twig_engine branch.

Fabianx’s picture

Component: Twig engine (twig_engine branch) » Twig templates conversion (front-end branch)

* Make patch for sandbox
* Test
* Push to core

Fabianx’s picture

Category: feature » task

And its a task (if there is a need)

psynaptic’s picture

Why are we trying to promote logic in template files? I would much prefer it if we didn't allow template files this ability to over-complicate and instead promote adding classes via theme logic.

DamienMcKenna’s picture

-1 for adding more logic functions to the template files.

+1 for having super short tags, e.g. <tag {{- attributes }}>{{content}}</tag>

We should not be promoting adding custom logic to template files, we should be promoting succinct template files.

Fabianx’s picture

succinct template files

Uh, what is that? Just curious ...

markabur’s picture

These modifier functions aren't really doing anything new in Twig, they're just making the attributes object modifiable at the tpl level like strings already are. E.g.

{% attributes.class|remove('odd','even') %}

is just an easier-to-understand version of this (untested, but should work?):

{% attributes.class|split('odd')|join|split('even')|join %}

However, it would be nice to have a similar level of access for the object itself, since this won't work:

{% attributes|split('id')|join %}
DamienMcKenna’s picture

succinct template files

Uh, what is that? Just curious ...

Template files that don't need lots of custom logic other than simple loops.

I guess it boils down to questioning whether you should be adding and removing classes, etc, within a template and whether that logic shouldn't be handled elsewhere instead?

DamienMcKenna’s picture

If the goal really is to boil tag output down to the bare minimum of <tag {{- attributes }}>{{content}}</tag> then my concerns are moot.

jwilson3’s picture

I guess it boils down to questioning whether you should be adding and removing classes, etc, within a template and whether that logic shouldn't be handled elsewhere instead?

Yes, that is exactly the issue, as stated in the Problem/Motivation. The resolution in #1808254: Standardize and simplify the attribute syntax in Twig template files explicitly pushed for getting people to *add* classes easily in template files, so this issue assumes that resolution is in place, and argues that making it easy to *add* a class in the template, but hard to remove or modify a class (read: ugly and difficult to remember) is a net fail at the end of the day for themers.

Additionally, if we still have the hide() functionality (which suspends content provided from lower system layers from being printed), it follows that it should be easy to hide classes provided by lower layers as well.

Fabianx’s picture

#18: @jwilson3: That is an interesting point.

c4rl’s picture

Related: In case anyone hasn't seen it yet, check out my latest comment here #1808254-50: Standardize and simplify the attribute syntax in Twig template files

IMHO, preprocessors seem like a fine API for doing this sort of thing, I don't think we have to reimplement it in Twig. :)

c4rl’s picture

Issue summary: View changes

Remove dodo.

jwilson3’s picture

This is superseded by #2285451: Create addClass() and removeClass() methods on Attribute object for merging css class names., which uses a similar syntax, but instead of working on the attributes.class member directly, it affects the attributes object directly.