Skip to main content

Standard Drupal Render Array Properties

Front-end Development
Drupal

In a previous article, we looked at what the Drupal render system does, and the basic structure of a render array. To understand the capabilities of this system better, here we'll dive into the render array properties supported by nearly every render element.

HTML attributes

One of the most common modifications we want to make to the HTML that the render system outputs is to inject or change HTML attributes such as IDs, classes, or ARIA declarations. Nearly every render element has broad support for this using the standard #attributes property.

[
  '#type' => 'html_tag',
  '#tag' => 'h1',
  '#value' => 'Standard properties',
  '#attributes' => [
    'id' => 'standard-properties',
    'title' => 'Standard properties right here!',
    'class' => ['foo', 'bar'],
  ],
]

The value of #attributes is expected to be an associative array, where each key is the name of an HTML attribute, and its value is a string containing the corresponding HTML attribute value. The exception is class, which instead of a string takes an array of individual class names.

The above example produces:

<h1 id="standard-properties" title="Standard properties right here!" class="foo bar">Standard properties</h1>

We can see that the class names have been placed together with a space separating them.

Why are classes specified in an array? This goes back to one of the reasons the render array system exists in the first place. We want other modules and themes to be able to reach in and tweak the contents later as needed. If classes were specified as a space-separated string, then we'd need to do a lot of text searching and handle special cases whenever we wanted to add or remove a class from something. But since this is already an array, we can do:

$render['#attributes']['class'][] = 'baz';

This sticks an extra class into the element without worrying about what's already there.

Weights

Render array elements can also have weights assigned to them, just like the weights you see on things like taxonomy terms in the Drupal UI. These will influence the output ordering.

public function page() {
  return [
    '#type' => 'container',
    'header' => [
      '#type' => 'html_tag',
      '#tag' => 'h1',
      '#value' => 'Standard properties',
      '#attributes' => [
        'id' => 'standard-properties',
        'title' => 'Standard properties right here!',
        'class' => ['foo', 'bar'],
      ],
    ],
    'content' => [
      [
        '#type' => 'html_tag',
        '#tag' => 'p',
        '#value' => 'This one is second',
        '#weight' => 52,
      ],
      [
        '#type' => 'html_tag',
        '#tag' => 'p',
        '#value' => 'This one, on the other hand, is first.',
        '#weight' => -10,
      ],
    ],
  ];
}

This example has two paragraph tags defined, one with a weight of 52 and the other of -10. The heading tag has no weight listed, so it gets the default of 0. Weighted items are ordered from low to high: the heaviest items sink to the bottom of the page.

Render element items in order of weight.

The paragraphs here are displayed in the order opposite to how they were defined, because of their weights. The heading is still first, though, because weights are only considered with respect to siblings in the render array structure. The header element is outside the array with the weighted paragraphs, so it is presented in the normal ordering.

Attachments

So far we have only looked at properties that affect the HTML element currently being constructed, in rather a direct fashion. Some properties have a broader reach, however, and "bubble up" the hierarchy of render arrays to where they are needed. One of these is #attached, which lets us attach a variety of metadata to an array element.

The most common use of this capability is to reference a Drupal library.

public function page() {
  return [
    '#type' => 'html_tag',
    '#tag' => 'p',
    '#value' => 'This text is modified with an attached library.',
    '#attributes' => ['class' => ['attachment-example']],
    '#attached' => ['library' => ['render_demo/styling']],
  ];
}

This code produces a paragraph element with a particular class name attached. All of that we have seen before. However, it also declares the name of a library to be attached to this element. This library in turn defines a CSS file that tells the browser how to handle items with the attachment-example class on them.

.attachment-example {
  background: black;
  color: lightgreen;
  padding: 1em;
}

When the page is rendered, we see that the styling rules are in place.

Text in green with a black background

Why attach CSS and JS in this way, rather than putting them right in the theme? There can be several motivations for this. One is that it couples behaviors more tightly to the place the elements are defined, so it is easy to find and maintain CSS and JS rules related to the code you are changing.

A bigger reason, though, is that this strategy ensures that the relevant library is in place whenever this content is present on the page, and not included when it's not needed. Drupal can then use this information when aggregating and compressing assets for a page, efficiently delivering only what's necessary. The render system will even handle duplicates elegantly, so you don't have to worry about what will happen if the same library is requested elsewhere.

Placeholders

Sometimes most of your content's structure is known in advance, but some part must be calculated later. The render system's placeholder capability caters to this.

public function page() {
  return [
    '#type' => 'html_tag',
    '#tag' => 'p',
    '#value' => 'The current time is @time',
    '#attached' => [
      'placeholders' => [
        '@time' => [
          '#plain_text' => date('h:i:s A'),
        ],
      ],
    ],
  ];
}

Text within a render array can have placeholders embedded in it, using much the same structure as translatable text. The contents of the placeholders array are injected to replace these text strings at the last minute. Each of the replacements must itself be a render array.

Text containing the current time

Caching

When we try the previous example, we might notice a small problem. Refreshing the page will not update the embedded time we specified. This is because of Drupal's caching behavior.

A full exploration of caching is beyond the scope of this article, but in brief, Drupal comes bundled with two caching modules: the Internal Page Cache and the Internal Dynamic Page Cache. The former is a very simple snapshot of the whole page output that is calculated for anonymous users and not very configurable. If our site contains information that is personalized to the user in some way (logged in or not) or that changes frequently, we will need to disable this module and rely on the much more sophisticated dynamic cache.

The dynamic cache interacts with the render system by allowing us to attach caching metadata to any array. This data bubbles up as needed, providing Drupal with the information required to make the correct decisions about which things can stay cached and which need to be refreshed.

The #cache property lets us attach this metadata to any layer of our render array structure:

public function page() {
  return [
    '#type' => 'html_tag',
    '#tag' => 'p',
    '#value' => 'The current time is @time',
    '#attached' => [
      'placeholders' => [
        '@time' => [
          '#plain_text' => date('h:i:s A'),
        ],
      ],
    ],
    '#cache' => [
      'max-age' => 0,
    ],
  ];
}

Here we are simply setting the maximum age of this piece of content to 0, meaning it can never be cached. We'll cover the more sophisticated options, cache tags and context, in a future article.

Next steps

In our next dive into the render system, we will explore how render arrays intersect with the theme system to make easily-modifiable templated layouts. But for now, enjoy exploring the power of these common properties!

To read more about common properties in the render system, consult the Drupal API documentation.

Need a fresh perspective on a tough project?

Let’s talk about how RDG can help.

Contact Us