3

I am trying to add some attributes to a render array. In Drupal 7, we can do this by adding a property in an array like the following.

$items[] = array('data' => $item_data, 'id' => 'someId'); // attribute = 'id'
$variables['subpanels'] = theme('item_list', array('items' => $items, 'title' => NULL, 'type' => 'ul'));

In Drupal 8, there's no #attributes property documented in Render Arrays in Drupal 8. How can I add this id as attribute in a render array, with Drupal 8?

This is the code I am using so far.

mymoduleController.php

public static function _mymodule_f1($users_online) {
  $user = \Drupal::currentUser();

  $render_array_1 = array();
  $render_array_1['name'] = 'test-name';
  $render_array_1['header'] = t('test-header');
  $render_array_1['contents'] = array('#value' => '<div class="test_options">' . '<a class="test_loading" href="#"></a>'. '</div>');

  $items = array();
  foreach ($users_online as $key => $value) {
    if ($key != 'total') {
      $items[] = array('#value' => '<a class="' . $key . '" href="#">' .   $value['name'] . '</a>', 'class' => array('status-' . $value['status']));
    }
  }
  //print_r($items);
  if ($items) {
    $item_list = array(
      '#theme' => 'item_list',
      '#items' => $items,
    );
    $render_array_1['footer'] = $item_list;
  }
  $render_array_1['text'] = array('#value' => t('T1') . ' (<span class="online-count">' . count($items) . '</span>)');
  $image = [
   '#theme' => 'image',
   '#uri' => drupal_get_path('module', 'mymodule') . '/css/themes/images/loading.gif',
   '#width' => NULL,
   '#height' => NULL,
   '#class' => 'icon'
  ];

  $render_array_1['icon'] = $image;
  $mymodule_subpanel = array(
    '#theme' => 'mymodule_subpanel',
    '#subpanel' => $render_array_1
  );
  return $mymodule_subpanel;
}

mymodule.module

function mymodule_theme() {
  return array(
    'mymodule' => array(
      'variables' => array('mymodule' => NULL),
      'template' => 'mymodule',
    ),
   'mymodule_subpanel' => array(
     'variables' => array('subpanel' => NULL),
     'template' => 'mymodule-subpanel',
   ),
 );
}

function mymodule_preprocess_mymodule(&$variables) {
  $items = array();
  $users_online = mymoduleController::f2();
  $render_items = mymoduleController::_mymodule_f1($users_online);
  $render_items['#attributes'] = array('id' => 'some_id'); //not working; unable to add id='some_id' in the <li> tag.
  //print_r($render_items);

  $items[] = $render_items;
  //print_r($items);

  $item_list = array(
    '#theme' => 'item_list',
    '#items' => $items, // array of render arrays.
    '#title' => NULL,
    '#list_type' => 'ul',
    '#attributes' => array('id' => 'mainpanel')
  );
  $variables['subpanels'] = $item_list;
}
  • 4
    The statement about no #attributes isn't true. You can look up various Render Elements that use it, and there are even ways of adding to attributes in Twig. That documentation page is describing the basic render arrays like theme wrappers or types. You can see more about Render Elements in the API: api.drupal.org/api/drupal/… – mradcliffe Dec 18 '16 at 18:08
  • @mradcliffe Is there any example that I can refer to. I tried using #attributes in my custom render array but it doesn't add the desired attribute. Also, I need to add attributes programatically and not in a Twig. – raghav Dec 18 '16 at 18:17
  • @mradcliffe I used #attribute property like this $render_array['#attributes'] = array('id' => 'someId'); – raghav Dec 18 '16 at 18:22
  • 2
    Can you update your question with the actual code you're trying? That will probably help everyone here. – mradcliffe Dec 18 '16 at 21:59
  • It could be that the id attribute is being overridden. Try putting it outside of the #attributes array. $render_array[#id] = 'some_id'. – Eyal Dec 18 '16 at 23:09
5

Render arrays can contain render elements or theme templates.

Example for a render element:

'#type' => 'link'

and a theme template:

'#theme' => 'item_list'

Render elements have the parameter #attributes. This is defined in the base class Drupal\Core\Render\Element\RenderElement that all render and form elements are extended from.

That is not the case for templates in general.

The template links for example has #attributes and handles this in template_preprocess_links() in /core/includes/theme.inc.

The example from the question is item-list. This template has a variable #wrapper_attributes. You can see in the first line of the preprocess function how wrapper-attributes is transformed in an attributes object, which is then passed on to the twig template where it is placed in the list wrapper:

/**
 * Prepares variables for item list templates.
 *
 * Default template: item-list.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - items: An array of items to be displayed in the list. Each item can be
 *     either a string or a render array. If #type, #theme, or #markup
 *     properties are not specified for child render arrays, they will be
 *     inherited from the parent list, allowing callers to specify larger
 *     nested lists without having to explicitly specify and repeat the
 *     render properties for all nested child lists.
 *   - title: A title to be prepended to the list.
 *   - list_type: The type of list to return (e.g. "ul", "ol").
 *   - wrapper_attributes: HTML attributes to be applied to the list wrapper.
 *
 * @see https://www.drupal.org/node/1842756
 */
function template_preprocess_item_list(&$variables) {
  $variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']);
    foreach ($variables['items'] as &$item) {
    $attributes = array();
    // If the item value is an array, then it is a render array.
    if (is_array($item)) {
      // List items support attributes via the '#wrapper_attributes' property.
      if (isset($item['#wrapper_attributes'])) {
        $attributes = $item['#wrapper_attributes'];
      }

The template item_list has also the variable attributes, which is passed on to the twig template. Here you can see how this all ends up in the html markup, including the attributes of the items, which are collected for each item from wrapper-attributes in preprocess:

item-list.html.twig

{#
/**
 * @file
 * Theme override for an item list.
 *
 * Available variables:
 * - items: A list of items. Each item contains:
 *   - attributes: HTML attributes to be applied to each list item.
 *   - value: The content of the list element.
 * - title: The title of the list.
 * - list_type: The tag for list element ("ul" or "ol").
 * - wrapper_attributes: HTML attributes to be applied to the list wrapper.
 * - attributes: HTML attributes to be applied to the list.
 * - empty: A message to display when there are no items. Allowed value is a
 *   string or render array.
 * - context: A list of contextual data associated with the list. May contain:
 *   - list_style: The custom list style.
 *
 * @see template_preprocess_item_list()
 */
#}
{% if context.list_style %}
  {%- set wrapper_attributes = wrapper_attributes.addClass('item-list--' ~ context.list_style) %}
  {%- set attributes = attributes.addClass('item-list__' ~ context.list_style) %}
{% endif %}
{% if items or empty -%}
  <div{{ wrapper_attributes.addClass('item-list') }}>
    {%- if title is not empty -%}
      <h3>{{ title }}</h3>
    {%- endif -%}
    {%- if items -%}
      <{{ list_type }}{{ attributes }}>
        {%- for item in items -%}
          <li{{ item.attributes }}>{{ item.value }}</li>
        {%- endfor -%}
      </{{ list_type }}>
    {%- else -%}
      {{- empty -}}
    {%- endif -%}
  </div>
{%- endif %}

Btw. the wrapper is only placed in the classy theme, the base theme doesn't use this.

Conclusion:

Templates don't have attributes by default. As the examples show, they can handle them like render elements, under a different variable name or not at all.

-1

I hope this example may help you.

Step 1. Create your module and Block (http://www.drupal8.ovh/tutoriels/107/create-a-simple-block-programmatically)

Step 2. Add your template usin hook_theme() (here module name = clock)

Example:

function clock_theme($existing, $type, $theme, $path) {
  return array(
    'clock' => array(
      'variables' => array(
        'icon' => NULL,
        'title' => NULL,
        'time' => NULL,
      ),
    ),
  );
}

You cal also use template_preprocess to handle your template Example:

function template_preprocess_clock(&$variables) {
  //Add global classes
  $variables['attributes']['class'][] = 'my_clock';
}

Step 3. Create the twig template in clock/templates/clock.html.twig Example:

<div {{ attributes }}>
  {% if icon %}
    <img class='clock_block_icon' src='{{ icon }}' alt='{{ title }}'/>
  {% endif %}
  <div class="clock_block_title" >{{ title }}</div>
  <div class="clock_block" data-time="{{ time }}"></div>
</div>

Step 4. Add the template to your block or page, using the render array Example :

return array(
      '#theme' => 'clock',
      '#icon' => $icon,
      '#title' => 'The Title',
      '#time' => "The Time is : ".date('H:i:s'),
    );

http://www.drupal8.ovh/en/tutoriels/140/add-twig-template-to-a-custom-module-block-or-page

And also this one

$output[] = array(
  '#type' => 'inline_template',
  '#template' => '{{ yourvar }}',
  '#context' => array(
    'yourvar' => 'The Value',
  ),
);
return $output;

http://www.drupal8.ovh/en/tutoriels/154/inline-template-drupal-8-and-twig

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.