Theming form elements
This chapter explains how to do customize the display of form elements.
Table of contents
Introduction
The majority of the work theming a Drupal 7 form can be done using the default output of the form from Drupal 7. In some cases, you may want to tweak some CSS for the layout and form element sizes.
Sometimes, however, you need better control over the HTML output of your form.
See also: Drupal SE: How do I theme a form element? and H. Bensalem: Theming the login form.
Use #prefix and #suffix wrappers
You may use #prefix
and #suffix wrappers
in your form defintion to provide additional classes:
form['submit'] = array( '#type' => 'submit', '#value' => t('Submit'), '#attributes' => array('style' => 'padding: 0 10px;'), '#theme' => "submit", '#prefix' => '<div class="submit-button-custom">', '#suffix' => '</div>', );
Alter the form
You may provide a form-specific alteration (instead of the global hook_form_alter()).
/** * Implements hook_form_FORM_ID_alter(). */ function YOUR_THEME_form_FORM_ID_alter(&$form, &$form_state, $form_id) { $form['submit']['#prefix'] = '<div class="submit-button-custom">'; $form['submit']['#sufix'] = '</div>'; }
Theming form elements as a table
For example, how can an input form use a table to lay out the fields? This section will describe how to do this with a simple 2×2 layout.
You can download the code of this project from Drupal.org:
$ git clone --branch 7.x-1.x http://git.drupal.org/sandbox/gisleh/2226115.git inf5272 cd inf5272
The code is in the subdirectory formtable
.
Create the .info
-file
Since this is going to be a demonstration module, there should be a .info
-file.
Put the following into formtable.info
.
name = Formtable description = A demonstration of how to theme form elements as a table. core = 7.x
Create the schema for the data
Before we can start building the module, we need to create a schema
to hold the data. We are going to provide for a heading, and a
2×2 layout. The following should be
in formtable.install
:
/** * Implements hook_schema(). */ function formtable_schema() { $schema['formtable'] = array( 'fields' => array( 'ftid' => array( 'description' => 'primary key', 'type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, ), 'title' => array( 'description' => 'The title field', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => '', ), 'r1c1' => array( 'description' => 'Row 1 Column 1', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => '', ), 'r1c2' => array( 'description' => 'Row 1 Column 2', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => '', ), 'r2c1' => array( 'description' => 'Row 2 Column 1', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => '', ), 'r2c2' => array( 'description' => 'Row 2 Column 2', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => '', ), ), 'primary key' => array('ftid'), ); return $schema; }
Build the form
Drupal 7 makes extensive use of render arrays to separate the
content of a form from its presentation. In addition to the
renderable elements in a form, you may add properties (starting with
the character #
). In the form below, these are used to
identify the theme function to use, the items that go into the table
headers, and the type and title of the fields.
/** * Form builder for the formtable demo form. */ function formtable_form($form = array(), &$form_state) { $form['title'] = array( '#type' => 'textfield', '#title' => t('Title'), '#required' => TRUE, '#description' => t('This field is not part of the table.'), ); $form['table'] = array( // The next line specify form what theme function to use. '#theme' => 'formtable_tablepart', // Pass the header information to the theme function. '#header' => array(t('Column 1'), t('Column 2')), // Rows in the form table. 'rows' => array( // Make it a tree for easy traversing of the values on submission. '#tree' => TRUE, // First row. 'r1' => array( 'c1' => array( '#type' => 'textfield', '#title' => t('Row 1 Column 1') ), 'c2' => array( '#type' => 'textfield', '#title' => t('Row 1 Column 2'), ), ), // Second row. 'r2' => array( 'c1' => array( '#type' => 'textfield', '#title' => t('Row 2 Column 1') ), 'c2' => array( '#type' => 'textfield', '#title' => t('Row 2 Column 2'), ), ), ), ); // Add the submit button. $form['submit'] = array( '#type' => 'submit', '#value' => t('Submit'), ); return $form; }
Add validation and submit handlers
As always, we need to add a valdation handler and a submit handler
to process form data. To be discovered they must be named with the
name of the module (formtable
) followed by the name of
the form (form
) followed by (validate
) and
(submit
) respectively.
This is the form validation handler. It just makes sure that no field is empty.
/** * Form validatation handler. */ function formtable_form_validate( $form, &$form_state) { if (empty($form_state['values']['rows']['r1']['c1'])) { form_set_error('rows][r1][c1', t('Field cannot be empty.')); } if (empty($form_state['values']['rows']['r1']['c2'])) { form_set_error('rows][r1][c2', t('Field cannot be empty.')); } if (empty($form_state['values']['rows']['r2']['c1'])) { form_set_error('rows][r2][c1', t('Field cannot be empty.')); } if (empty($form_state['values']['rows']['r2']['c2'])) { form_set_error('rows][r2][c2', t('Field cannot be empty.')); } }
The form submission handler just inserts the content of the form
into the table formtable
in the database, and prints a
message on the screen.
/** * Form submission handler. */ function formtable_form_submit($form, &$form_state) { $key = db_insert('formtable') ->fields(array( 'title' => $form_state['values']['title'], 'r1c1' => $form_state['values']['rows']['r1']['c1'], 'r1c2' => $form_state['values']['rows']['r1']['c2'], 'r2c1' => $form_state['values']['rows']['r2']['c1'], 'r2c2' => $form_state['values']['rows']['r2']['c2'], )) ->execute(); drupal_set_message(t('Form submitted.')); drupal_goto('formtable/' . $key); }
Create and register the theme callback
The theme callback is a functions that actually themes the table
part of the form. Its name must start with theme_
. The
rest of the name (here formtable_tablepart
) is used
in hook_theme
to identify this function.
We're going to use the built-in theme_table
theme
function to render the themed table. This function is used to build
that render array.
From the documentation of
theme_table
,
we see that this function accepts an associative array where the table
header and rows are both arrays. Both simple arrays with just the
text to show, and more complex arrays listing attributes are allowed.
This example shows how these arrays may look like:
$header = array('Header 1 ', 'Header 2', 'Header 3'); $rows = array( // Simple row array( 'Cell 1', 'Cell 2', 'Cell 3', ), // Row with attributes on Cell 4 and the row. array( 'data' => array( array('data' => 'Cell 4', 'class' => 'formtable-td', 'colspan' => 2), 'Cell 5', ), 'class' => array('formtable-tr'), ) );
In this demonstration module, we are just going to construct simple
arrays for both arrays, based upon the the form defined in the
function formtable_form
above:
/** * Theme callback to theme part of the booking form in a table format. */ function theme_formtable_tablepart($variables) { // Get the useful values. $form = $variables['form']; $rows = $form['rows']; $header = $form['#header']; // Setup the structure to be rendered and returned. $content = array( '#theme' => 'table', '#header' => $header, '#rows' => array(), ); // Traverse each row. @see element_chidren(). foreach (element_children($rows) as $row_index) { $row = array(); // Traverse each column in the row. @see element_children(). foreach (element_children($rows[$row_index]) as $col_index) { // Render the column form element. $row[] = drupal_render($rows[$row_index][$col_index]); } // Add the row to the table. $content['#rows'][] = $row; } // Render the table and return. return drupal_render($content); }
Note that to be discoverable, the name of this function need to be
registered with hook_theme
as the theme for the
form. Otherwise, it will be ignored:
/** * Implements hook_theme($existing, $type, $theme, $path). */ function formtable_theme() { return array( 'formtable_tablepart' => array( 'render element' => 'form', ), ); }
Add a menu router
To make this a working module that can be tested, we need to add a menu router. The menu router provides access to the path with the form, as well as a router to a function that displays the form data.
/** * Implements hook_menu(). */ function formtable_menu() { $items = array(); // A page to demonstrate theming form elements in a table. $items['formtable'] = array( 'title' => 'FormTable demonstrator', 'page callback' => 'drupal_get_form', 'page arguments' => array('formtable_form'), 'access callback' => TRUE, ); $items['formtable/%'] = array( 'title' => 'FormTable item', 'page callback' => 'formtable_showitem', 'page arguments' => array(1), 'access callback' => TRUE, ); return $items; } function formtable_showitem($ftid) { $item = db_query("SELECT * from {formtable} WHERE ftid = :ftid LIMIT 1", array(":ftid" => $ftid))->fetchObject(); dpm($item, 'item'); $content = '<h3>' . $item->title . '</h3>'; $content .= '<table><tr><td>' . $item->r1c1 . '</td>'; $content .= '<td>' . $item->r1c2 . '</td></tr>'; $content .= '<tr><td>' . $item->r2c1 . '</td>'; $content .= '<td>' . $item->r2c2 . '</td></tr></table'; return $content; }
Add hook_help
Having a hook_help
in the module is not strictly
necessary. However, the implementation of hook_help
below
makes it simple to test the module by providing a clickable link to
the form's path.
/** * Implements hook_help(). */ function formtable_help($path, $arg) { switch ($path) { case 'admin/help#formtable': $output = t('<p>A demonstrator that shows how theme form elements as a table. To test, navigate to the path !path.</p>', array('!path' => l('formtable','formtable'))); return $output; } }
Note that we can also test the module by typing this path by hand in the web browser's address field.
Deploy and test
After installing and enabling the module, to see the module in
action, navigate to the path formtable
just below the
siteroot. As noted in the previous section, there is also a
clickable link to this path on the module's help page.
Final word
You may also create a multiple field widget that allows widgets containing multiple fields to be embedded in any Drupal entity. An example of this can be found in the phase 2 blog. Unfortunately, this example is rather buggy, so use it as inspiration, not as a recipe.
Last update: 2015-08-24 [gh].