diff --git a/core/includes/form.inc b/core/includes/form.inc index b26966f3bf27266f12a454dcf38c67595508419f..2659d06ef2b24f4f8f789c3e0dcb8405a729fd12 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -2347,6 +2347,36 @@ function form_type_checkboxes_value($element, $input = FALSE) { } } +/** + * Determines the value of a table form element. + * + * @param array $element + * The form element whose value is being populated. + * @param array|false $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 form_type_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. + // @todo D8: Remove this inconsistency. + if ($input === FALSE) { + $element += array('#default_value' => array()); + return drupal_map_assoc(array_keys(array_filter($element['#default_value']))); + } + else { + return is_array($input) ? drupal_map_assoc($input) : array(); + } + } +} + /** * Form value callback: Determines the value for a #type radios form element. * @@ -3566,6 +3596,128 @@ function form_process_tableselect($element) { return $element; } +/** + * #process callback for #type 'table' to add tableselect support. + * + * @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. + * + * @return array + * The processed element. + * + * @see form_process_tableselect() + * @see theme_tableselect() + */ +function form_process_table($element, &$form_state) { + if ($element['#tableselect']) { + if ($element['#multiple']) { + $value = is_array($element['#value']) ? $element['#value'] : array(); + } + // Advanced selection behaviour makes no sense for radios. + else { + $element['#js_select'] = FALSE; + } + // Add a "Select all" checkbox column to the header. + // @todo D8: Rename into #select_all? + if ($element['#js_select']) { + $element['#attached']['library'][] = array('system', 'drupal.tableselect'); + array_unshift($element['#header'], array('class' => array('select-all'))); + } + // Add an empty header column for radio buttons or when a "Select all" + // checkbox is not desired. + else { + array_unshift($element['#header'], ''); + } + + if (!isset($element['#default_value']) || $element['#default_value'] === 0) { + $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. + // @todo Consider to add an optional $element[$key]['#title_key'] + // defaulting to 'title'? + $title = ''; + if (!empty($element[$key]['title']['#title'])) { + $title = $element[$key]['title']['#title']; + } + else { + foreach (element_children($element[$key]) as $column) { + if (isset($element[$key][$column]['#title'])) { + $title = $element[$key][$column]['#title']; + break; + } + if (isset($element[$key][$column]['#markup'])) { + $title = $element[$key][$column]['#markup']; + 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; +} + +/** + * #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 form_validate_table($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 (!isset($element['#value']) || $element['#value'] === '') { + form_error($element, t('No item selected.')); + } +} + /** * Processes a machine-readable name form element. * diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 8a35b6ed8b9c446f331ba1effa40dc6b06dfa46b..da8b32807c649ca495b4a555e35ad8ba202af84e 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1852,6 +1852,99 @@ function theme_breadcrumb($variables) { return $output; } +/** + * #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]['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. + * + * @see system_element_info() + * @see theme_table() + * @see drupal_process_attached() + * @see drupal_add_tabledrag() + */ +function drupal_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. + // @todo Do not render a cell for children of #type 'value'. + // @see http://drupal.org/node/1248940 + foreach (element_children($element[$first]) as $second) { + // Assign the element by reference, so any potential changes to the + // original element are taken over. + $row['data'][] = array('data' => &$element[$first][$second]); + } + $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; +} + /** * Returns HTML for a table. * diff --git a/core/modules/filter/filter.admin.inc b/core/modules/filter/filter.admin.inc index c01fb937eae7d55f4251d7b312ed3656cc45026e..a62f8e3c14eb0ff90050aee4077e0407635a6893 100644 --- a/core/modules/filter/filter.admin.inc +++ b/core/modules/filter/filter.admin.inc @@ -18,7 +18,17 @@ function filter_admin_overview($form) { $fallback_format = filter_fallback_format(); $form['#tree'] = TRUE; + $form['formats'] = array( + '#type' => 'table', + '#header' => array(t('Name'), t('Roles'), t('Weight'), t('Operations')), + '#tabledrag' => array( + array('order', 'sibling', 'text-format-order-weight'), + ), + ); foreach ($formats as $id => $format) { + $form['formats'][$id]['#attributes']['class'][] = 'draggable'; + $form['formats'][$id]['#weight'] = $format->weight; + $links = array(); $links['configure'] = array( 'title' => t('configure'), @@ -40,16 +50,20 @@ function filter_admin_overview($form) { 'href' => "admin/config/content/formats/$id/disable", ); } + $form['formats'][$id]['roles'] = array('#markup' => $roles_markup); - $form['formats'][$id]['operations'] = array( - '#type' => 'operations', - '#links' => $links, - ); + $form['formats'][$id]['weight'] = array( '#type' => 'weight', '#title' => t('Weight for @title', array('@title' => $format->name)), '#title_display' => 'invisible', '#default_value' => $format->weight, + '#attributes' => array('class' => array('text-format-order-weight')), + ); + + $form['formats'][$id]['operations'] = array( + '#type' => 'operations', + '#links' => $links, ); } $form['actions'] = array('#type' => 'actions'); @@ -74,41 +88,6 @@ function filter_admin_overview_submit($form, &$form_state) { drupal_set_message(t('The text format ordering has been saved.')); } -/** - * Returns HTML for the text format administration overview form. - * - * @param $variables - * An associative array containing: - * - form: A render element representing the form. - * - * @ingroup themeable - */ -function theme_filter_admin_overview($variables) { - $form = $variables['form']; - - $rows = array(); - foreach (element_children($form['formats']) as $id) { - $form['formats'][$id]['weight']['#attributes']['class'] = array('text-format-order-weight'); - $row = array( - 'data' => array( - drupal_render($form['formats'][$id]['name']), - drupal_render($form['formats'][$id]['roles']), - drupal_render($form['formats'][$id]['weight']), - drupal_render($form['formats'][$id]['operations']), - ), - 'class' => array('draggable'), - ); - $rows[] = $row; - } - $header = array(t('Name'), t('Roles'), t('Weight'), t('Operations')); - $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'text-format-order'))); - $output .= drupal_render_children($form); - - drupal_add_tabledrag('text-format-order', 'order', 'sibling', 'text-format-order-weight'); - - return $output; -} - /** * Page callback: Displays the text format add/edit form. * diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index 1aff07049e7972618957a8734a54f3d8c989d90a..c4b596a4ab4b36373172460f5a1a7ad496f5929c 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -73,10 +73,6 @@ function filter_help($path, $arg) { */ function filter_theme() { return array( - 'filter_admin_overview' => array( - 'render element' => 'form', - 'file' => 'filter.admin.inc', - ), 'filter_admin_format_filter_order' => array( 'render element' => 'element', 'file' => 'filter.admin.inc', diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc index 568149623562aa7f2ddbe50e66bbb5b4c7b2ce39..74f100cac37de44b470867c09de18360e8c0cb21 100644 --- a/core/modules/node/node.admin.inc +++ b/core/modules/node/node.admin.inc @@ -418,7 +418,6 @@ function node_admin_content($form, $form_state) { * Returns the admin form object to node_admin_content(). * * @see node_admin_nodes_submit() - * @see node_admin_nodes_validate() * @see node_filter_form() * @see node_filter_form_submit() * @see node_multiple_delete_confirm() @@ -453,7 +452,7 @@ function node_admin_nodes() { $form['options']['submit'] = array( '#type' => 'submit', '#value' => t('Update'), - '#validate' => array('node_admin_nodes_validate'), + '#tableselect' => TRUE, '#submit' => array('node_admin_nodes_submit'), ); @@ -523,27 +522,39 @@ function node_admin_nodes() { // Prepare the list of nodes. $languages = language_list(LANGUAGE_ALL); $destination = drupal_get_destination(); - $options = array(); + $form['nodes'] = array( + '#type' => 'table', + '#header' => $header, + '#empty' => t('No content available.'), + ); foreach ($nodes as $node) { $l_options = $node->langcode != LANGUAGE_NOT_SPECIFIED && isset($languages[$node->langcode]) ? array('language' => $languages[$node->langcode]) : array(); - $options[$node->nid] = array( - 'title' => array( - 'data' => array( - '#type' => 'link', - '#title' => $node->label(), - '#href' => 'node/' . $node->nid, - '#options' => $l_options, - '#suffix' => ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))), - ), - ), - 'type' => check_plain(node_get_type_label($node)), - 'author' => theme('username', array('account' => $node)), - 'status' => $node->status ? t('published') : t('not published'), - 'changed' => format_date($node->changed, 'short'), + $form['nodes'][$node->nid]['title'] = array( + '#type' => 'link', + '#title' => $node->label(), + '#href' => 'node/' . $node->nid, + '#options' => $l_options, + '#suffix' => ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))), + ); + $form['nodes'][$node->nid]['type'] = array( + '#markup' => check_plain(node_get_type_label($node)), + ); + $form['nodes'][$node->nid]['author'] = array( + '#theme' => 'username', + '#account' => $node, + ); + $form['nodes'][$node->nid]['status'] = array( + '#markup' => $node->status ? t('published') : t('not published'), + ); + $form['nodes'][$node->nid]['changed'] = array( + '#markup' => format_date($node->changed, 'short'), ); if ($multilingual) { - $options[$node->nid]['language_name'] = language_name($node->langcode); + $form['nodes'][$node->nid]['language_name'] = array( + '#markup' => language_name($node->langcode), + ); } + // Build a list of all the accessible operations for the current node. $operations = array(); if (node_access('update', $node)) { @@ -567,27 +578,23 @@ function node_admin_nodes() { 'query' => $destination, ); } - $options[$node->nid]['operations'] = array(); + $form['nodes'][$node->nid]['operations'] = array(); if (count($operations) > 1) { // Render an unordered list of operations links. - $options[$node->nid]['operations'] = array( - 'data' => array( - '#type' => 'operations', - '#subtype' => 'node', - '#links' => $operations, - ), + $form['nodes'][$node->nid]['operations'] = array( + '#type' => 'operations', + '#subtype' => 'node', + '#links' => $operations, ); } elseif (!empty($operations)) { // Render the first and only operation as a link. $link = reset($operations); - $options[$node->nid]['operations'] = array( - 'data' => array( - '#type' => 'link', - '#title' => $link['title'], - '#href' => $link['href'], - '#options' => array('query' => $link['query']), - ), + $form['nodes'][$node->nid]['operations'] = array( + '#type' => 'link', + '#title' => $link['title'], + '#href' => $link['href'], + '#options' => array('query' => $link['query']), ); } } @@ -595,47 +602,13 @@ function node_admin_nodes() { // Only use a tableselect when the current user is able to perform any // operations. if ($admin_access) { - $form['nodes'] = array( - '#type' => 'tableselect', - '#header' => $header, - '#options' => $options, - '#empty' => t('No content available.'), - ); - } - // Otherwise, use a simple table. - else { - $form['nodes'] = array( - '#theme' => 'table', - '#header' => $header, - '#rows' => $options, - '#empty' => t('No content available.'), - ); + $form['nodes']['#tableselect'] = TRUE; } - $form['pager'] = array('#markup' => theme('pager')); + $form['pager'] = array('#theme' => 'pager'); return $form; } -/** - * Form validation handler for node_admin_nodes(). - * - * Checks whether any nodes have been selected to perform the chosen 'Update - * option' on. - * - * @see node_admin_nodes() - * @see node_admin_nodes_submit() - * @see node_filter_form() - * @see node_filter_form_submit() - * @see node_multiple_delete_confirm() - * @see node_multiple_delete_confirm_submit() - */ -function node_admin_nodes_validate($form, &$form_state) { - // Error if there are no items to select. - if (!is_array($form_state['values']['nodes']) || !count(array_filter($form_state['values']['nodes']))) { - form_set_error('', t('No items selected.')); - } -} - /** * Form submission handler for node_admin_nodes(). * diff --git a/core/modules/system/system.module b/core/modules/system/system.module index eaaebc2c583c30dbf971be770f6a962f6a12931b..709fd1f52b1bba4f0a4c0e561fb690c6d6457f9e 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -557,6 +557,30 @@ function system_element_info() { '#theme' => 'hidden', ); + $types['table'] = array( + '#header' => array(), + '#rows' => array(), + '#empty' => '', + // Properties for tableselect support. + '#input' => TRUE, + '#tree' => TRUE, + '#tableselect' => FALSE, + '#multiple' => TRUE, + '#js_select' => TRUE, + '#value_callback' => 'form_type_table_value', + '#process' => array('form_process_table'), + '#element_validate' => array('form_validate_table'), + // Properties for tabledrag support. + // The value is a list of arrays that are passed to drupal_add_tabledrag(). + // drupal_pre_render_table() prepends the HTML ID of the table to each set + // of arguments. + // @see drupal_add_tabledrag() + '#tabledrag' => array(), + // Render properties. + '#pre_render' => array('drupal_pre_render_table'), + '#theme' => 'table', + ); + return $types; }