Commit ad99dd76 authored by webchick's avatar webchick

#242962 by Heine: Add a 'tablesort' Form API element to unify various places...

#242962 by Heine: Add a 'tablesort' Form API element to unify various places we use a big table with JS-enabled checkboxes next to each item (with tests).
parent f16eccbe
......@@ -3354,7 +3354,10 @@ function drupal_render(&$elements) {
}
$prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
$suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
return $prefix . $content . $suffix;
$content = $prefix . $content . $suffix;
// Store the rendered content, so higher level elements can reuse it.
$elements['#content'] = $content;
return $content;
}
}
......@@ -3609,6 +3612,9 @@ function drupal_common_theme() {
'file' => array(
'arguments' => array('element' => NULL),
),
'tableselect' => array(
'arguments' => array('element' => NULL),
),
'form_element' => array(
'arguments' => array('element' => NULL, 'value' => NULL),
),
......
......@@ -2036,6 +2036,117 @@ function form_process_checkboxes($element) {
return $element;
}
/**
* Format a table with radio buttons or checkboxes.
*
* @param $element
* An associative array containing the properties and children of the
* tableselect element.
* Properties used: header, options, empty, js_select.
*
* @return
* A themed HTML string representing the table.
*
* @ingroup themeable
*/
function theme_tableselect($element) {
$rows = array();
if (!empty($element['#options'])) {
// Generate a table row for each selectable item in #options.
foreach ($element['#options'] as $key => $value) {
$row = array();
// Render the checkbox / radio element.
$row[] = $element[$key]['#content'];
// As theme_table only maps header and row columns by order, create the
// correct order by iterating over the header fields.
foreach ($element['#header'] as $fieldname => $title) {
$row[] = $element['#options'][$key][$fieldname];
}
$rows[] = $row;
}
// Add an empty header or a "Select all" checkbox to provide room for the
// checkboxes/radios in the first table column.
$first_col = $element['#js_select'] ? array(theme('table_select_header_cell')) : array('');
$header = array_merge($first_col, $element['#header']);
}
else {
// If there are no selectable options, display the empty text over the
// entire width of the table.
$header = $element['#header'];
$rows[] = array(array('data' => $element['#empty'], 'colspan' => count($header)));
}
return theme('table', $header, $rows);
}
/**
* Create the correct amount of checkbox or radio elements to populate the table.
*
* @param $element
* An associative array containing the properties and children of the
* tableselect element.
*
* @return
* The processed element.
*/
function form_process_tableselect($element) {
if ($element['#multiple']) {
$value = is_array($element['#value']) ? $element['#value'] : array();
}
else {
// Advanced selection behaviour make no sense for radios.
$element['#js_select'] = FALSE;
}
$element['#tree'] = TRUE;
if (count($element['#options']) > 0) {
if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
$element['#default_value'] = array();
}
// Create a checkbox or radio for each item in #options in such a way that
// the value of the tableselect element behaves as if it had been of type
// checkboxes or radios.
foreach ($element['#options'] as $key => $choice) {
// Do not overwrite manually created children.
if (!isset($element[$key])) {
if ($element['#multiple']) {
$element[$key] = array(
'#type' => 'checkbox',
'#title' => '',
'#return_value' => $key,
'#default_value' => isset($value[$key]),
'#attributes' => $element['#attributes'],
'#ahah' => isset($element['#ahah']) ? $element['#ahah'] : NULL,
);
}
else {
// Generate the parents as the autogenerator does, so we will have a
// unique id for each radio button.
$parents_for_id = array_merge($element['#parents'], array($key));
$element[$key] = array(
'#type' => 'radio',
'#title' => '',
'#return_value' => $key,
'#default_value' => ($element['#default_value'] == $key) ? $key : NULL,
'#attributes' => $element['#attributes'],
'#parents' => $element['#parents'],
'#id' => form_clean_id('edit-' . implode('-', $parents_for_id)),
'#ahah' => isset($element['#ahah']) ? $element['#ahah'] : NULL,
);
}
}
}
}
else {
$element['#value'] = array();
}
return $element;
}
/**
* Theme a form submit button.
*
......
......@@ -165,13 +165,14 @@ function tablesort_get_order($headers) {
return $default;
}
else {
// The first column specified is initial 'order by' field unless otherwise specified
if (is_array($headers[0])) {
$headers[0] += array('data' => NULL, 'field' => NULL);
return array('name' => $headers[0]['data'], 'sql' => $headers[0]['field']);
// The first column specified is the initial 'order by' field unless otherwise specified.
$first = current($headers);
if (is_array($first)) {
$first += array('data' => NULL, 'field' => NULL);
return array('name' => $first['data'], 'sql' => $first['field']);
}
else {
return array('name' => $headers[0]);
return array('name' => $first, 'sql' => '');
}
}
}
......
......@@ -58,43 +58,37 @@ function comment_admin_overview($type = 'new', $arg) {
// Load the comments that need to be displayed.
$status = ($arg == 'approval') ? COMMENT_NOT_PUBLISHED : COMMENT_PUBLISHED;
$form['header'] = array(
'#type' => 'value',
'#value' => array(
theme('table_select_header_cell'),
array('data' => t('Subject'), 'field' => 'subject'),
array('data' => t('Author'), 'field' => 'name'),
array('data' => t('Posted in'), 'field' => 'node_title'),
array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'),
array('data' => t('Operations')),
));
$result = pager_query('SELECT c.subject, c.nid, c.cid, c.comment, c.timestamp, c.status, c.name, c.homepage, u.name AS registered_name, u.uid, n.title as node_title FROM {comment} c INNER JOIN {users} u ON u.uid = c.uid INNER JOIN {node} n ON n.nid = c.nid WHERE c.status = %d' . tablesort_sql($form['header']['#value']), 50, 0, NULL, $status);
$header = array(
'subject' => array('data' => t('Subject'), 'field' => 'subject'),
'author' => array('data' => t('Author'), 'field' => 'name'),
'posted_in' => array('data' => t('Posted in'), 'field' => 'node_title'),
'time' => array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'),
'operations' => array('data' => t('Operations')),
);
$result = pager_query('SELECT c.subject, c.nid, c.cid, c.comment, c.timestamp, c.status, c.name, c.homepage, u.name AS registered_name, u.uid, n.title as node_title FROM {comment} c INNER JOIN {users} u ON u.uid = c.uid INNER JOIN {node} n ON n.nid = c.nid WHERE c.status = %d' . tablesort_sql($header), 50, 0, NULL, $status);
// Build a table listing the appropriate comments.
$options = array();
$destination = drupal_get_destination();
while ($comment = db_fetch_object($result)) {
$comments[$comment->cid] = '';
$comment->name = $comment->uid ? $comment->registered_name : $comment->name;
$form['subject'][$comment->cid] = array(
'#markup' => l($comment->subject, 'node/' . $comment->nid, array('attributes' => array('title' => truncate_utf8($comment->comment, 128)), 'fragment' => 'comment-' . $comment->cid))
);
$form['username'][$comment->cid] = array(
'#markup' => theme('username', $comment)
);
$form['node_title'][$comment->cid] = array(
'#markup' => l($comment->node_title, 'node/' . $comment->nid)
);
$form['timestamp'][$comment->cid] = array(
'#markup' => format_date($comment->timestamp, 'small')
);
$form['operations'][$comment->cid] = array(
'#markup' => l(t('edit'), 'comment/edit/' . $comment->cid, array('query' => $destination))
$options[$comment->cid] = array(
'subject' => l($comment->subject, 'node/' . $comment->nid, array('attributes' => array('title' => truncate_utf8($comment->comment, 128)), 'fragment' => 'comment-' . $comment->cid)),
'author' => theme('username', $comment),
'posted_in' => l($comment->node_title, 'node/' . $comment->nid),
'time' => format_date($comment->timestamp, 'small'),
'operations' => l(t('edit'), 'comment/edit/' . $comment->cid, array('query' => $destination)),
);
}
$form['comments'] = array(
'#type' => 'checkboxes',
'#options' => isset($comments) ? $comments: array()
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#empty' => t('No comments available.'),
);
$form['pager'] = array(
'#markup' => theme('pager', NULL, 50, 0)
);
......@@ -145,41 +139,6 @@ function comment_admin_overview_submit($form, &$form_state) {
}
}
/**
* Theme the comment admin form.
*
* @param $form
* An associative array containing the structure of the form.
* @ingroup themeable
*/
function theme_comment_admin_overview($form) {
$output = drupal_render($form['options']);
if (isset($form['subject']) && is_array($form['subject'])) {
foreach (element_children($form['subject']) as $key) {
$row = array();
$row[] = drupal_render($form['comments'][$key]);
$row[] = drupal_render($form['subject'][$key]);
$row[] = drupal_render($form['username'][$key]);
$row[] = drupal_render($form['node_title'][$key]);
$row[] = drupal_render($form['timestamp'][$key]);
$row[] = drupal_render($form['operations'][$key]);
$rows[] = $row;
}
}
else {
$rows[] = array(array('data' => t('No comments available.'), 'colspan' => '6'));
}
$output .= theme('table', $form['header']['#value'], $rows);
if ($form['pager']['#markup']) {
$output .= drupal_render($form['pager']);
}
$output .= drupal_render($form);
return $output;
}
/**
* List the selected comments and verify that the admin wants to delete them.
*
......
......@@ -118,9 +118,6 @@ function comment_theme() {
'comment_block' => array(
'arguments' => array(),
),
'comment_admin_overview' => array(
'arguments' => array('form' => NULL),
),
'comment_preview' => array(
'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
),
......
......@@ -110,3 +110,206 @@ class FormsTestTypeCase extends DrupalWebTestCase {
}
}
/**
* Test the tableselect form element for expected behavior.
*/
class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
function getInfo() {
return array(
'name' => t('Tableselect form element type test'),
'description' => t('Test the tableselect element for expected behavior'),
'group' => t('Form API'),
);
}
function setUp() {
parent::setUp('form_test');
}
/**
* Test the display of checkboxes when #multiple is TRUE.
*/
function testMultipleTrue() {
$this->drupalGet('form_test/tableselect/multiple-true');
$this->assertNoText(t('Empty text.'), t('Empty text should not be displayed.'));
// Test for the presence of the Select all rows tableheader.
$this->assertFieldByXPath('//th[@class="select-all"]', NULL, t('Presence of the "Select all" checkbox.'));
$rows = array('row1', 'row2', 'row3');
foreach($rows as $row) {
$this->assertFieldByXPath('//input[@type="checkbox"]', $row, t('Checkbox for value @row.', array('@row' => $row)));
}
}
/**
* Test the display of radios when #multiple is FALSE.
*/
function testMultipleFalse() {
$this->drupalGet('form_test/tableselect/multiple-false');
$this->assertNoText(t('Empty text.'), t('Empty text should not be displayed.'));
// Test for the absence of the Select all rows tableheader.
$this->assertNoFieldByXPath('//th[@class="select-all"]', '', t('Absence of the "Select all" checkbox.'));
$rows = array('row1', 'row2', 'row3');
foreach($rows as $row) {
$this->assertFieldByXPath('//input[@type="radio"]', $row, t('Radio button for value @row.', array('@row' => $row)));
}
}
/**
* Test the display of the #empty text when #options is an empty array.
*/
function testEmptyText() {
$this->drupalGet('form_test/tableselect/empty-text');
$this->assertText(t('Empty text.'), t('Empty text should be displayed.'));
}
/**
* Test the submission of single and multiple values when #multiple is TRUE.
*/
function testMultipleTrueSubmit() {
// Test a submission with one checkbox checked.
$edit = array();
$edit['tableselect[row1]'] = TRUE;
$this->drupalPost('form_test/tableselect/multiple-true', $edit, 'Submit');
$this->assertText(t('Submitted: row1 = row1'), t('Checked checkbox row1'));
$this->assertText(t('Submitted: row2 = 0'), t('Unchecked checkbox row2.'));
$this->assertText(t('Submitted: row3 = 0'), t('Unchecked checkbox row3.'));
// Test a submission with multiple checkboxes checked.
$edit['tableselect[row1]'] = TRUE;
$edit['tableselect[row3]'] = TRUE;
$this->drupalPost('form_test/tableselect/multiple-true', $edit, 'Submit');
$this->assertText(t('Submitted: row1 = row1'), t('Checked checkbox row1.'));
$this->assertText(t('Submitted: row2 = 0'), t('Unchecked checkbox row2.'));
$this->assertText(t('Submitted: row3 = row3'), t('Checked checkbox row3.'));
}
/**
* Test submission of values when #multiple is FALSE.
*/
function testMultipleFalseSubmit() {
$edit['tableselect'] = 'row1';
$this->drupalPost('form_test/tableselect/multiple-false', $edit, 'Submit');
$this->assertText(t('Submitted: row1'), t('Selected radio button'));
}
/**
* Test the #js_select property.
*/
function testAdvancedSelect() {
// When #multiple = TRUE a Select all checkbox should be displayed by default.
$this->drupalGet('form_test/tableselect/advanced-select/multiple-true-default');
$this->assertFieldByXPath('//th[@class="select-all"]', NULL, t('Display a "Select all" checkbox by default when #multiple is TRUE.'));
// When #js_select is set to FALSE, a "Select all" checkbox should not be displayed.
$this->drupalGet('form_test/tableselect/advanced-select/multiple-true-no-advanced-select');
$this->assertNoFieldByXPath('//th[@class="select-all"]', NULL, t('Do not display a "Select all" checkbox when #js_select is FALSE.'));
// A "Select all" checkbox never makes sense when #multiple = FALSE, regardless of the value of #js_select.
$this->drupalGet('form_test/tableselect/advanced-select/multiple-false-default');
$this->assertNoFieldByXPath('//th[@class="select-all"]', NULL, t('Do not display a "Select all" checkbox when #multiple is FALSE.'));
$this->drupalGet('form_test/tableselect/advanced-select/multiple-false-advanced-select');
$this->assertNoFieldByXPath('//th[@class="select-all"]', NULL, t('Do not display a "Select all" checkbox when #multiple is FALSE, even when #js_select is TRUE.'));
}
/**
* Test the whether the option checker gives an error on invalid tableselect values for checkboxes.
*/
function testMultipleTrueOptionchecker() {
list($header, $options) = _form_test_tableselect_get_data();
$form['tableselect'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
);
// Test with a valid value.
list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => 'row1'));
$this->assertFalse(isset($errors['tableselect']), t('Option checker allows valid values for checkboxes.'));
// Test with an invalid value.
list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => 'non_existing_value'));
$this->assertTrue(isset($errors['tableselect']), t('Option checker disallows invalid values for checkboxes.'));
}
/**
* Test the whether the option checker gives an error on invalid tableselect values for radios.
*/
function testMultipleFalseOptionchecker() {
list($header, $options) = _form_test_tableselect_get_data();
$form['tableselect'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#multiple' => FALSE,
);
// Test with a valid value.
list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => 'row1'));
$this->assertFalse(isset($errors['tableselect']), t('Option checker allows valid values for radio buttons.'));
// Test with an invalid value.
list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => 'non_existing_value'));
$this->assertTrue(isset($errors['tableselect']), t('Option checker disallows invalid values for radio buttons.'));
}
/**
* Helper function for the option check test to submit a form while collecting errors.
*
* @param $form_element
* A form element to test.
* @param $edit
* An array containing post data.
*
* @return
* An array containing the processed form, the form_state and any errors.
*/
private function formSubmitHelper($form_element, $edit) {
$form_id = $this->randomName();
$form = $form_state = array();
$form = array_merge($form, $form_element);
$form['op'] = array('#type' => 'submit', '#value' => t('Submit'));
$form['#post'] = $edit;
$form['#post']['form_id'] = $form_id;
drupal_prepare_form($form_id, $form, $form_state);
drupal_process_form($form_id, $form, $form_state);
$errors = form_get_errors();
// Clear errors and messages.
drupal_get_messages();
form_set_error(NULL, '', TRUE);
// Return the processed form together with form_state and errors
// to allow the caller lowlevel access to the form.
return array($form, $form_state, $errors);
}
}
; $Id$
name = "FormAPI Test"
description = "Support module for Form API tests."
package = Testing
version = VERSION
core = 7.x
files[] = form_test.module
hidden = TRUE
<?php
// $Id$
/**
* @file
* Helper module for the form API tests.
*/
/**
* Implementation of hook_menu().
*/
function form_test_menu() {
$items = array();
$items['form_test/tableselect/multiple-true'] = array(
'title' => 'Tableselect checkboxes test',
'page callback' => 'drupal_get_form',
'page arguments' => array('_form_test_tableselect_multiple_true_form'),
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
$items['form_test/tableselect/multiple-false'] = array(
'title' => 'Tableselect radio button test',
'page callback' => 'drupal_get_form',
'page arguments' => array('_form_test_tableselect_multiple_false_form'),
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
$items['form_test/tableselect/empty-text'] = array(
'title' => 'Tableselect empty text test',
'page callback' => 'drupal_get_form',
'page arguments' => array('_form_test_tableselect_empty_form'),
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
$items['form_test/tableselect/advanced-select'] = array(
'title' => 'Tableselect js_select tests',
'page callback' => 'drupal_get_form',
'page arguments' => array('_form_test_tableselect_js_select_form'),
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Create a header and options array. Helper function for callbacks.
*/
function _form_test_tableselect_get_data() {
$header = array(
'one' => t('One'),
'two' => t('Two'),
'three' => t('Three'),
'four' => t('Four'),
);
$options['row1'] = array(
'one' => 'row1col1',
'two' => t('row1col2'),
'three' => t('row1col3'),
'four' => t('row1col4'),
);
$options['row2'] = array(
'one' => 'row2col1',
'two' => t('row2col2'),
'three' => t('row2col3'),
'four' => t('row2col4'),
);
$options['row3'] = array(
'one' => 'row3col1',
'two' => t('row3col2'),
'three' => t('row3col3'),
'four' => t('row3col4'),
);
return array($header, $options);
}
/**
* Build a form to test the tableselect element.
*
* @param $form_state
* The form_state
* @param $element_properties
* An array of element properties for the tableselect element.
*
* @return
* A form with a tableselect element and a submit button.
*/
function _form_test_tableselect_form_builder($form_state, $element_properties) {
$form = array();
list($header, $options) = _form_test_tableselect_get_data();
$form['tableselect'] = $element_properties;
$form['tableselect'] += array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#multiple' => FALSE,
'#empty' => t('Empty text.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Test the tableselect #multiple = TRUE functionality.
*/
function _form_test_tableselect_multiple_true_form($form_state) {
return _form_test_tableselect_form_builder($form_state, array('#multiple' => TRUE));
}
/**
* Process the tableselect #multiple = TRUE submitted values.
*/
function _form_test_tableselect_multiple_true_form_submit($form, &$form_state) {
$selected = $form_state['values']['tableselect'];
foreach ($selected as $key => $value) {
drupal_set_message(t('Submitted: @key = @value', array('@key' => $key, '@value' => $value)));
}
}
/**
* Test the tableselect #multiple = FALSE functionality.
*/
function _form_test_tableselect_multiple_false_form($form_state) {
return _form_test_tableselect_form_builder($form_state, array('#multiple' => FALSE));
}
/**
* Process the tableselect #multiple = FALSE submitted values.
*/
function _form_test_tableselect_multiple_false_form_submit($form, &$form_state) {
drupal_set_message(t('Submitted: @value', array('@value' => $form_state['values']['tableselect'])));
}
/**
* Test functionality of the tableselect #empty property.
*/
function _form_test_tableselect_empty_form($form_state) {
return _form_test_tableselect_form_builder($form_state, array('#options' => array()));
}
/**
* Test functionality of the tableselect #js_select property.
*/
function _form_test_tableselect_js_select_form($form_state, $action) {
switch ($action) {
case 'multiple-true-default':
$options = array('#multiple' => TRUE);
break;
case 'multiple-false-default':
$options = array('#multiple' => FALSE);
break;
case 'multiple-true-no-advanced-select':
$options = array('#multiple' => TRUE, '#js_select' => FALSE);
break;
case 'multiple-false-advanced-select':
$options = array('#multiple' => FALSE, '#js_select' => TRUE);
break;
}
return _form_test_tableselect_form_builder($form_state, $options);
}
......@@ -354,7 +354,14 @@ function system_elements() {
'#input' => TRUE,
'#size' => 60,
);
$type['tableselect'] = array(
'#input' => TRUE,
'#js_select' => TRUE,
'#multiple' => TRUE,
'#process' => array('form_process_tableselect'),
'#options' => array(),
'#empty' => '',
);
/**
* Form structure.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment