Create new `image_attributes` Twig function to easily expose image attributes (and integrate with Experience Builder)

Created on 31 January 2025, 2 months ago

Problem/Motivation

It's always been really difficult working with images via the Drupal theme system. We typically have to traverse entities, preprocess, etc just to get attributes like src, alt, etc. And God help us if we're using image styles (which we should be), and then getting the calculated width and height values.

Furthermore, Experience Builder builds the image manually by passing in the attributes in an object.

"image": {
      "title": "image",
      "type":  "object",
      "required": ["src"],
      "properties": {
        "src": {
          "title": "Image URL",
          "$ref": "json-schema-definitions://experience_builder.module/image-uri"
        },
        "alt": {
          "title": "Alternative text",
          "type": "string"
        },
        "width": {
          "title": "Image width",
          "type": "integer"
        },
        "height": {
          "title": "Image height",
          "type": "integer"
        }
      }
    },

If we want to invoke a SDC from an existing Drupal template that's compatible with XB, we need to be able to match that structure. Currently this isn't possible without significant custom code. This is a major DX headache.

Steps to reproduce

Proposed resolution

I talked about this issue with both @andyg5000 and @lauriii.

The consensus reached is to create a new image_attributes Twig function in core.

The function would work as follows:

{# image = image: file entity|media entity, key: NULL|int=0 #}
{# return: { src, alt, height, width, loading? } #}

Remaining tasks

User interface changes

โœจ Feature request
Status

Active

Version

11.1 ๐Ÿ”ฅ

Component

theme system

Created by

๐Ÿ‡บ๐Ÿ‡ธUnited States mherchel Gainesville, FL, US

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @mherchel
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States mherchel Gainesville, FL, US
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States mherchel Gainesville, FL, US
  • ๐Ÿ‡ณ๐Ÿ‡ฟNew Zealand quietone
  • First commit to issue fork.
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States andyg5000 North Carolina, USA

    I've pushed up a first stab at this based on the definition above with the following changes.

    Changes to specifications above:
    - Instead of using NULL to return all of the image properties in the image field, I opted to use -1 to match the cardinality unlimited constant used in Field Storage.
    - Allow passing an optional image style to override the configuration provided by the view mode passed to the template. This will invoke createDerivative if the file doesn't exist and includes the itok token.

    Example syntax:

    
    # Resolve the values for the first image in the array.
    {% set atts = image_attributes(content.field_image) %} # or {% set atts = image_attributes(content.field_image, 0) %}
    {{ dump(atts.width) }}
    # Outputs: 100
    
    # Resolve the values for a specific image style of the first image in the array.
    {% set atts = image_attributes(content.field_image, 0, "thumbnail") %}
    {{ dump(atts) }}
    # Outputs:
    #0 array:5 [โ–ผ
    #  "width" => "100"
    #  "height" => "42" 
    #  "alt" => "Home page hero"
    #  "src" => "/sites/default/files/styles/thumbnail/public/2025-01/Home-Hero.png?itok=PnOeIEzU"
    # ]
    
    # Resolve the values for all images in the array. 
    {% set atts = image_attributes(content.field_image, -1, "thumbnail") %}
    {{ dump(atts[0].width) }}
    # Outputs: 100
    

    To Do:
    - Approval of file placement, naming convention, and overall code.
    - Sanitize output (forgot to do this)
    - Determine additional functionality requirements (e.g. ability to pass a media entity?)
    - Write tests

  • ๐Ÿ‡ซ๐Ÿ‡ฎFinland lauriii Finland

    Instead of using NULL to return all of the image properties in the image field, I opted to use -1 to match the cardinality unlimited constant used in Field Storage.

    +1 for using -1.

    Allow passing an optional image style to override the configuration provided by the view mode passed to the template. This will invoke createDerivative if the file doesn't exist and includes the itok token.

    I'm not 100% convinced we should be handling image styles on this level. The problem we'd run into is adding support for responsive images. This is closely related to โœจ Create global 'image' component roughly based on NextJS's image component Active . I believe we should have an easy way to create responsive images in the context of a component so that I don't have to go to the Drupal UI to create these. If we implement this, then we'd want to always retrieve the source image and applying the image style would happen after running this filter.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States mherchel Gainesville, FL, US

    I'm not 100% convinced we should be handling image styles on this level.

    This solves a real need. I'm not sure what other alternatives we are.

    I believe we should have an easy way to create responsive images in the context of a component so that I don't have to go to the Drupal UI to create these.

    ๐Ÿ’ฏ Agree! But the same goes with image styles. I personally use responsive images only on larger images (700px wide). The majority of images are smaller.

    If we implement this, then we'd want to always retrieve the source image and applying the image style would happen after running this filter.

    I'm not sure I understand this. Why do we need the source image?

  • Pipeline finished with Failed
    about 2 months ago
    Total: 115s
    #421310
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States andyg5000 North Carolina, USA

    Hey Lauri,

    Thanks for reviewing this! Supporting derivative generation from the component would be great, but I think we still need backward compatibility with image styles.

    We can remove the image style override option, but this Twig function will still use the image style defined on the view mode to resolve the URL and dimensions. The third argument was just an easier way to get different sizes without modifying the view mode.

    Are you suggesting this function should return the source image instead? Or in addition to src?

    Hereโ€™s an example of how I envisioned it being used in a simple
    component (without responsive_image_styles):

    {% set thumbnail = image_attributes(content.field_image, 0, "thumbnail") %}
    {% set medium = image_attributes(content.field_image, 0, "medium") %}
    {% set large = image_attributes(content.field_image, 0, "large") %}
    
    <picture>
      <source srcset="{{ thumbnail.src }}" width="{{ thumbnail.width }}" height="{{ thumbnail.height }}" media="(min-width: 300px)" />
      <source srcset="{{ medium.src }}" width="{{ medium.width }}" height="{{ medium.height }}" media="(min-width: 600px)" />
      <img src="{{ large.src }}" alt="{{ large.alt }}" width="{{ large.width }}" height="{{ large.height }}" />
    </picture>
    

    This setup still works with responsive_image_styles, but currently, it ignores that config due to the image style overrides. We could easily support responsive image style derivatives by returning a nested array for each image.

    If you and Mike can finalize the core goals for this issue, Iโ€™ll update my merge accordingly.

    Thanks,
    Andy

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States andyg5000 North Carolina, USA

    After discussing with Lauri and Mike on a call, the goals are now:

    1) Create a twig filter (not function) called img_attributes
    2) Return the src, width, height, alt and loading attributes to an array
    3) Only support the image style, if defined, on the array passed to the filter
    4) The caller is responsible for looping over multiple images

    Exceptions:
    If multiple images or an invalid field is passed to the filter, it should return NULL

  • Pipeline finished with Canceled
    about 2 months ago
    Total: 122s
    #421474
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States andyg5000 North Carolina, USA

    Ok, updates pushed to match ideas from our call.

    A new core twig filter called img_attributes will resolve the src, witdh, height, alt in addition to any attributes already available in #item_attributes (e.g. loading="lazy").

    I also include a new filter that would allow you to swap image style on the fly. Lauri had some hesitation on this, but maybe he'll like it as it's own filter :D. If not it can be easily removed from this MR.

    Here's examples of usage:

    {% set img_atts = content.field_image[0]|img_attributes %}
    {{ dump(img_atts.width)}}
    # Outputs: 220
    
    {% set is_img_atts = content.field_image[0]|use_image_style('thumbnail')|img_attributes %}
    {{ dump(is_img_atts.width) }}
    # Outputs: 100
    

    ToDo:
    1) Approve code approach, namespace, etc...
    2) Decide if use_image_style can be accepted
    2) Write tests

  • Pipeline finished with Failed
    about 2 months ago
    Total: 433s
    #421476
Production build 0.71.5 2024