Sep 30, 2014

FormStateInterface, and drupal_valid_path()

As we move into Drupal 8, alpha 15, a few more API changes have come along for the Optimizely module.

(1)  For objects whose class implements interface FormStateInterface, you can no longer index into the object as though it were an array as in D7.

Instead, there are a number of methods to get and set values. In our case, we only need to retrieve values from simple form text fields.

So, for example,

function validateForm(array &$form, FormStateInterface $form_state)
{
  $proj_code = $form_state['values']['optimizely_project_code'];


is replaced with,

function validateForm(array &$form, FormStateInterface $form_state)
{
  $proj_code = $form_state->getValue('optimizely_project_code');


This change broke the code in a lot of the methods for validating and for submitting forms.


(2)  Regarding drupal_valid_path(), the function has been removed. So,

  if (drupal_valid_path($path, TRUE) == FALSE) {

is replaced with,

  if (\Drupal::pathValidator()->isValid($path) == FALSE) {


Sources:

Replace instances of deprecated drupal_valid_path with PathValidator
https://www.drupal.org/node/2303361






Sep 7, 2014

url() function in Twig templates

As of Drupal 8 alpha 14, several relative links that had been hard-coded in Twig templates were no longer working.

<li>Add the account ID to the 
<a href="/admin/config/system/optimizely/settings">
Account Info</a> settings page</li>

The url constructed by the theming system did not include the root of my development site, which is opti. Instead, it would come out as

  localhost/admin/config/system/optimizely/settings

Dropping the leading slash character in the href also produced an incorrect result. Here, add_update is the last part of the path for the page that is rendered using the template.

  localhost/opti/admin/config/system/optimizely/add_update/admin/config/system/optimizely/settings

After some poking around, I found the Twig url() function, which does the trick.

<li>Add the account ID to the 
<a href="{{ url('optimizely.settings') }}">
Account Info</a> settings page</li>

This produces the desired url, which is

  localhost/opti/admin/config/system/optimizely/settings

But note that the parameter to url() is the name of a route, not a path. In the optimizely.routing.yml file, the route is defined as

  optimizely.settings:
    path: /admin/config/system/optimizely/settings
    defaults:
      _form: \Drupal\optimizely\AccountSettingsForm
      _title: Optimizely
    requirements:
      _permission: administer optimizely


The path() and the l() functions might also work, but I haven't tried them. See the source articles.

Sources:

Creating and Using Templates
http://symfony.com/doc/current/book/templating.html

Create best practices about use of the l() function and the url() function in templates
https://www.drupal.org/node/1812562

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