You are here

table_element.module in Table Element 7

Provides a table element for Drupal 7.

The table element for Drupal 8 was written by @sun. This module was derived from his patch {@link http://drupal.org/node/80855#comment-6792864}

@license GPL v2 http://www.fsf.org/licensing/licenses/gpl.html

File

table_element.module
View source
<?php

/**
 * @file
 * Provides a table element for Drupal 7.
 *
 * The table element for Drupal 8 was written by @sun. This module was derived
 * from his patch {@link http://drupal.org/node/80855#comment-6792864}
 *
 * @license GPL v2 http://www.fsf.org/licensing/licenses/gpl.html
 */

/**
 * Implements hook_element_info().
 */
function table_element_element_info() {
  $types = array();
  $types['table'] = array(
    '#header' => array(),
    '#rows' => array(),
    '#empty' => '',
    '#input' => TRUE,
    '#tree' => TRUE,
    '#tableselect' => FALSE,
    '#multiple' => TRUE,
    '#js_select' => TRUE,
    '#theme' => 'table',
    '#process' => array(
      'table_element_process_table',
    ),
    '#element_validate' => array(
      'table_element_validate_table',
    ),
    '#pre_render' => array(
      'table_element_pre_render_table',
    ),
    '#value_callback' => 'table_element_table_value',
  );
  return $types;
}

/**
 * Determines the value of a table form element.
 *
 * @param array $element
 *   The form element whose value is being populated.
 * @param array|bool $input
 *   The incoming input to populate the form element. If this is FALSE,
 *   the element's default value should be returned.
 *
 * @return array
 *   The data that will appear in the $form_state['values'] collection
 *   for this element. Return nothing to use the default.
 */
function table_element_table_value(array $element, $input = FALSE) {

  // If #multiple is FALSE, the regular default value of radio buttons is used.
  if (!empty($element['#tableselect']) && !empty($element['#multiple'])) {

    // Contrary to #type 'checkboxes', the default value of checkboxes in a
    // table is built from the array keys (instead of array values) of the
    // #default_value property.
    if (FALSE === $input) {
      $element += array(
        '#default_value' => array(),
      );
      return drupal_map_assoc(array_keys(array_filter($element['#default_value'])));
    }
    elseif (is_array($input)) {
      return drupal_map_assoc($input);
    }
  }
  return array();
}

/**
 * A #process callback for #type 'table' to add tableselect support.
 *
 * @param array $element
 *   An associative array containing the properties and children of the
 *   table element.
 *
 * @return array
 *   The processed element.
 *
 * @see form_process_tableselect()
 * @see theme_tableselect()
 */
function table_element_process_table(array $element) {
  if (!empty($element['#tableselect'])) {
    if ($element['#multiple']) {
      $value = is_array($element['#value']) ? $element['#value'] : array();
    }
    else {
      $element['#js_select'] = FALSE;
    }

    // Add a "Select all" checkbox column to the header.
    if ($element['#js_select']) {
      $element['#attached']['library'][] = array(
        'system',
        'drupal.tableselect',
      );
      array_unshift($element['#header'], array(
        'class' => array(
          'select-all',
        ),
      ));
    }
    else {
      array_unshift($element['#header'], '');
    }
    if (empty($element['#default_value'])) {
      $element['#default_value'] = array();
    }

    // Create a checkbox or radio for each row in a way that the value of the
    // tableselect element behaves as if it had been of #type checkboxes or
    // radios.
    foreach (element_children($element) as $key) {

      // Do not overwrite manually created children.
      if (!isset($element[$key]['select'])) {

        // Determine option label; either an assumed 'title' column, or the
        // first available column containing a #title or #markup.
        $title = '';
        if (!empty($element[$key]['title']['#title'])) {
          $title = $element[$key]['title']['#title'];
        }
        else {
          foreach (element_children($element[$key]) as $column) {
            foreach (array(
              '#title',
              '#markup',
            ) as $property) {
              if (isset($element[$key][$column][$property])) {
                $title = $element[$key][$column][$property];
                break;
              }
            }
          }
        }
        if ('' !== $title) {
          $title = t('Update !title', array(
            '!title' => $title,
          ));
        }

        // Prepend the select column to existing columns.
        $element[$key] = array(
          'select' => array(),
        ) + $element[$key];
        $element[$key]['select'] += array(
          '#type' => $element['#multiple'] ? 'checkbox' : 'radio',
          '#title' => $title,
          '#title_display' => 'invisible',
          // @todo If rows happen to use numeric indexes instead of string keys,
          // this results in a first row with $key === 0, which is always FALSE.
          '#return_value' => $key,
          '#attributes' => $element['#attributes'],
        );
        $element_parents = array_merge($element['#parents'], array(
          $key,
        ));
        if ($element['#multiple']) {
          $element[$key]['select']['#default_value'] = isset($value[$key]) ? $key : NULL;
          $element[$key]['select']['#parents'] = $element_parents;
        }
        else {
          $element[$key]['select']['#default_value'] = $element['#default_value'] == $key ? $key : NULL;
          $element[$key]['select']['#parents'] = $element['#parents'];
          $element[$key]['select']['#id'] = drupal_html_id('edit-' . implode('-', $element_parents));
        }
      }
    }
  }
  return $element;
}

/**
 * An #element_validate callback for #type 'table'.
 *
 * @param array $element
 *   An associative array containing the properties and children of the
 *   table element.
 * @param array $form_state
 *   The current state of the form.
 */
function table_element_validate_table(array $element, &$form_state) {

  // Skip this validation if the button to submit the form does not require
  // selected table row data.
  if (empty($form_state['triggering_element']['#tableselect'])) {
    return;
  }
  if ($element['#multiple']) {
    if (!is_array($element['#value']) || !count(array_filter($element['#value']))) {
      form_error($element, t('No items selected.'));
    }
  }
  elseif (empty($element['#value'])) {
    form_error($element, t('No item selected.'));
  }
}

/**
 * A #pre_render callback to transform children of an element into #rows.
 *
 * Suitable for theme_table().
 *
 * This function converts sub-elements of an element of #type 'table' to be
 * suitable for theme_table():
 * - The first level of sub-elements are table rows. Only the #attributes
 *   property is taken into account.
 * - The second level of sub-elements is converted into columns for the
 *   corresponding first-level table row.
 *
 * Simple example usage:
 * @code
 * $form['table'] = array(
 *   '#type' => 'table',
 *   '#header' => array(
 *      t('Title'),
 *      array('data' => t('Operations'), 'colspan' => '1'),
 *   ),
 *   // Optionally, to add tableDrag support:
 *   '#tabledrag' => array(
 *     array('order', 'sibling', 'thing-weight'),
 *   ),
 * );
 * foreach ($things as $row => $thing) {
 *   $form['table'][$row]['#weight'] = $thing['weight'];
 *
 *   $form['table'][$row]['title'] = array(
 *     '#type' => 'textfield',
 *     '#default_value' => $thing['title'],
 *   );
 *
 *   // Optionally, to add tableDrag support:
 *   $form['table'][$row]['#attributes']['class'][] = 'draggable';
 *   $form['table'][$row]['#cell']['colspan'] = '2';
 *   $form['table'][$row]['#cell']['class'][] = 'td-element-class';
 *   $form['table'][$row]['weight'] = array(
 *     '#type' => 'textfield',
 *     '#title' => t('Weight for @title', array('@title' => $thing['title'])),
 *     '#title_display' => 'invisible',
 *     '#size' => 4,
 *     '#default_value' => $thing['weight'],
 *     '#attributes' => array('class' => array('thing-weight')),
 *   );
 *
 *   // The amount of link columns should be identical to the 'colspan'
 *   // attribute in #header above.
 *   $form['table'][$row]['edit'] = array(
 *     '#type' => 'link',
 *     '#title' => t('Edit'),
 *     '#href' => 'thing/' . $row . '/edit',
 *   );
 * }
 * @endcode
 *
 * @param array $element
 *   A structured array containing two sub-levels of elements. Properties used:
 *   - #tabledrag: The value is a list of arrays that are passed to
 *     drupal_add_tabledrag(). The HTML ID of the table is prepended to each set
 *     of arguments.
 *
 * @return array
 *   An array of row information.
 *
 * @see system_element_info()
 * @see theme_table()
 * @see drupal_process_attached()
 * @see drupal_add_tabledrag()
 */
function table_element_pre_render_table(array $element) {
  foreach (element_children($element) as $first) {
    $row = array(
      'data' => array(),
    );

    // Apply attributes of first-level elements as table row attributes.
    if (isset($element[$first]['#attributes'])) {
      $row += $element[$first]['#attributes'];
    }

    // Turn second-level elements into table row columns.
    foreach (element_children($element[$first]) as $second) {
      $cell =& $element[$first][$second];
      $cell += array(
        // If an access is not configured then will assume that is
        // granted. As Drupal does.
        '#access' => TRUE,
        // If attributes for table cell is not set then initialize
        // an empty value for them.
        '#cell' => array(),
      );

      // Element with granted access and without "value" type could
      // be rendered as table cell.
      if (!empty($cell['#access']) && !(isset($cell['#type']) && 'value' === $cell['#type'])) {

        // Assign the element by reference, so any potential changes to the
        // original element are taken over.
        $row['data'][] = array(
          'data' => &$cell,
        ) + $cell['#cell'];
      }
    }
    $element['#rows'][] = $row;
  }

  // Take over $element['#id'] as HTML ID attribute, if not already set.
  element_set_attributes($element, array(
    'id',
  ));

  // If the custom #tabledrag is set and there is a HTML ID, inject the table's
  // HTML ID as first callback argument and attach the behavior.
  if (!empty($element['#tabledrag']) && isset($element['#attributes']['id'])) {
    foreach ($element['#tabledrag'] as &$args) {
      array_unshift($args, $element['#attributes']['id']);
    }
    $element['#attached']['drupal_add_tabledrag'] = $element['#tabledrag'];
  }
  return $element;
}

Functions

Namesort descending Description
table_element_element_info Implements hook_element_info().
table_element_pre_render_table A #pre_render callback to transform children of an element into #rows.
table_element_process_table A #process callback for #type 'table' to add tableselect support.
table_element_table_value Determines the value of a table form element.
table_element_validate_table An #element_validate callback for #type 'table'.