Sep 2, 2014

Twig inline_template render elements

Updating to D8 alpha 14 resulted in one of the module's pages showing a form field of url paths literally like this,

  <ul><li>admin/config/system</li></ul>

In other words, there was HTML markup that was being escaped too many times and being rendered as content. Somehow, this issue appeared because of changes in alpha 14.

Researching this, I found two things. First, the core maintainers have been grappling with the same problem in the core UI itself. That's a bit of consolation for a newcomer like me swimming in the ocean of Drupal.

Second, for render arrays there is a new recognized value for the #type property. That value is inline_template. The idea is that when you don't want to create a full-blown Twig template file, this #type enables you to define a snippet of template in the render array itself.

♦  Here's a simplistic example.

  $render_title = array(
      '#type' => 'inline_template',
      '#template' => '{{ title }}',
      '#context' => array('title' => 'My Project'),
     );

  // Insert this array into the full form.
  $form['#project_title'] = $render_title;

The #template property provides the template using Twig syntax. The double braces mean print the value of the enclosed Twig variable, in this case, title.

The #context property provides a mapping from Twig variables to their values.

This example doesn't do anything except print the value of a variable. Depending on what might be in the value, you could just implement that without the use of a template.

♦  Here's something more useful and interesting.

  $render_link = array(
    '#type' => 'inline_template',
    '#template' => '<a href="{{ url }}">{{ update }}</a>',
    '#context' => array(
        'update' => $this->t('Update'),
        'url' => url('/admin/config/system/optimizely/add_update/'),
    ),
  );

  $form['#admin_link'] = $render_link;

The template consists of markup for a link to be rendered, using a translated word and a string that is built dynamically.

♦  Lastly, Twig templates allow for looping so that you can iterate over an array.

  $paths = array( ... );

  $render_paths = array(
      '#type' => 'inline_template',
      '#template' => '<ul>' .
        '{% for p in paths %}' .
          '<li>{{ p }}</li>' .
        '{% endfor %}' .
        '</ul>',
      '#context' => array('paths' => $paths),
     );

  $form['#paths'] = $render_paths;

The Twig for construct is like foreach in PHP. This builds an HTML unordered list with list items that are determined at runtime.

♦  Getting back to the issue that started me down this road, the original code was building snippets of markup and inserting the strings directly into the form render array. What was intended as markup would be escaped and displayed as content.

  $form['#paths'] = '<ul><li>some path</li></ul>';

With use of an inline template, the markup is handled correctly.

  $form['#paths'] = array(
    '#type' => 'inline_template',
    '#template' => '<ul><li>some path</li></ul>',
  );

♦  Another way: calling the t() function on a string that contains markup also produces the correct result. For example,

  $project_code = 
    t('Set ID in <a href="@url">@acct_info</a> page to enable',
      array('@url' => url('optimizely/settings'),
            '@acct_info' => t('Account Info'),
        )
      );


  $form['#project_code'] = $project_code;

Sources:

New inline_template render element for HTML code in PHP
https://www.drupal.org/node/2311123

Double escaping on /admin/modules/uninstall
https://www.drupal.org/node/2305831

No comments:

Post a Comment