Previously, for every form element #type, there was a corresponding theme function, responsible for theming the render element into an INPUT element.
In Drupal 8, each form element #type that results in an INPUT element goes through a single theme_input() theme hook. Only the HTML element attributes differ.
The entire processing from render element #properties into HTML attributes happened in individual theme functions previously (which had to be retained by themers when overriding a theme hook). This preparation and translation from render element into theme variables happens in a dedicated #pre_render callback for each form element #type now, completely separated from the theme hook.
For example, the element #type 'textfield' is now registered as:
$types['textfield'] = array(
'#pre_render' => array('form_pre_render_textfield'),
'#theme' => 'input__textfield',
'#theme_wrappers' => array('form_element'),
);
And there is a new, generic theme hook registered in hook_theme() for INPUT markup, which replaces all type-specific theme hooks that previously produced one and the same thing, an INPUT element:
$theme['input'] = array(
'render element' => 'element',
);
The markup for a particular theme hook can still be targeted in a specific override, since each element type specifies a theme hook suggestion for the respective type; e.g., here: 'input__textfield'.
The new flow is:
-
The
#pre_rendercallbackform_pre_render_textfield()prepares the render element for theming and adds the required #attributes.Before #pre_render After #pre_render $element['foo'] = array( '#type' => 'textfield', '#id' => 'edit-foo', '#name' => 'foo', '#value' => 'Bar', '#maxlength' => 64, '#attributes' => array( 'class' => array('foo', 'baz'), ), );$element['foo'] = array( // ... '#theme' => 'input__textfield', '#attributes' => array( 'type' => 'textfield', 'id' => 'edit-foo', 'name' => 'foo', 'value' => 'Bar', 'maxlength' => 64, 'class' => 'foo baz', ), ); -
template_preprocess_input(&$variables)prepares the theme variables fortheme_input(); e.g., it turns the #attributes into an Attribute object. -
theme_input__textfield()is invoked, if it exists, otherwisetheme_input(). And all it does is to produce the markup:function theme_input($variables) { $element = $variables['element']; $attributes = $variables['attributes']; return '<input' . $attributes . ' />' . drupal_render_children($element); }
Due to this change, it is now also possible to theme an INPUT element on its own, without going through Form API or Render API; e.g.:
$output = theme('input', array(
'attributes' => array(
'type' => 'text',
'value' => 'foo',
),
);
Theme hook changes
| Drupal 7 | Drupal 8 |
|---|---|
| — | input |
| button | input__button |
| checkbox | input__checkbox |
| color | input__color |
| input__email | |
| file | input__file |
| hidden | input__hidden |
| image_button | input__image_button |
| number | input__number |
| password | input__password |
| radio | input__radio |
| range | input__range |
| search | input__search |
| tel | input__tel |
| textfield | input__textfield |
| url | input__url |
Module developers
Due to the way default element #type properties are merged into an element definition, a manually specified #pre_render definition on a form element essentially removes the default #pre_render, which translates the render element into theme variables.
Drupal 7
$form['foo'] = array(
'#type' => 'textfield',
'#pre_render' => array('mymodule_pre_render_foo'),
);
Drupal 8
$form['foo'] = array(
'#type' => 'textfield',
'#pre_render' => array_merge(array('mymodule_pre_render_foo'), element_info_property('textfield', '#pre_render', array())),
);
See element_info_property() for usage details.