Commit c327b4d4 authored by Dries's avatar Dries

- Patch #684846 by effulgentsia, rfay, quicksketch, aspilicious: AJAX...

- Patch #684846 by effulgentsia, rfay, quicksketch, aspilicious: AJAX triggered by non-submit element fails if any elements are validated.
parent a7d001f7
......@@ -257,28 +257,12 @@ function ajax_get_form() {
* The Form API #ajax property can be set both for buttons and other input
* elements.
*
* ajax_process_form() defines an additional 'formPath' JavaScript setting
* that is used by Drupal.ajax.prototype.beforeSubmit() to automatically inject
* an additional field 'ajax_triggering_element' to the submitted form values,
* which contains the array #parents of the element in the form structure.
* This additional field allows ajax_form_callback() to determine which
* element triggered the action, as non-submit form elements do not
* provide this information in $form_state['clicked_button'], which can
* also be used to determine triggering element, but only submit-type
* form elements.
*
* This function is also the canonical example of how to implement
* #ajax['path']. If processing is required that cannot be accomplished with
* a callback, re-implement this function and set #ajax['path'] to the
* enhanced function.
*/
function ajax_form_callback() {
// Find the triggering element, which was set up for us on the client side.
if (!empty($_REQUEST['ajax_triggering_element'])) {
$triggering_element_path = $_REQUEST['ajax_triggering_element'];
// Remove the value for form validation.
unset($_REQUEST['ajax_triggering_element']);
}
list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
// Build, validate and if possible, submit the form.
......@@ -288,32 +272,11 @@ function ajax_form_callback() {
// drupal_process_form() set up.
$form = drupal_rebuild_form($form_id, $form_state, $form_build_id);
// $triggering_element_path in a simple form might just be 'myselect', which
// would mean we should use the element $form['myselect']. For nested form
// elements we need to recurse into the form structure to find the triggering
// element, so we can retrieve the #ajax['callback'] from it.
if (!empty($triggering_element_path)) {
if (!isset($form['#access']) || $form['#access']) {
$triggering_element = $form;
foreach (explode('/', $triggering_element_path) as $key) {
if (!empty($triggering_element[$key]) && (!isset($triggering_element[$key]['#access']) || $triggering_element[$key]['#access'])) {
$triggering_element = $triggering_element[$key];
}
else {
// We did not find the $triggering_element or do not have #access,
// so break out and do not provide it.
$triggering_element = NULL;
break;
}
}
}
}
if (empty($triggering_element)) {
$triggering_element = $form_state['clicked_button'];
}
// Now that we have the element, get a callback if there is one.
if (!empty($triggering_element)) {
$callback = $triggering_element['#ajax']['callback'];
// As part of drupal_process_form(), the element that triggered the form
// submission is determined, and in the case of AJAX, it might not be a
// button. This lets us route to the appropriate callback.
if (!empty($form_state['triggering_element'])) {
$callback = $form_state['triggering_element']['#ajax']['callback'];
}
if (!empty($callback) && function_exists($callback)) {
return $callback($form, $form_state);
......@@ -499,13 +462,40 @@ function ajax_process_form($element, &$form_state) {
'speed' => 'none',
'method' => 'replace',
'progress' => array('type' => 'throbber'),
'formPath' => implode('/', $element['#array_parents']),
);
// Process special settings.
// Change path to url.
$settings['url'] = isset($settings['path']) ? url($settings['path']) : url('system/ajax');
unset($settings['path']);
$settings['button'] = isset($element['#executes_submit_callback']) ? array($element['#name'] => $element['#value']) : FALSE;
// Add special data to $settings['submit'] so that when this element
// triggers an AJAX submission, Drupal's form processing can determine which
// element triggered it.
// @see _form_element_triggered_scripted_submission()
if (isset($settings['trigger_as'])) {
// An element can add a 'trigger_as' key within #ajax to make the element
// submit as though another one (for example, a non-button can use this
// to submit the form as though a button were clicked). When using this,
// the 'name' key is always required to identify the element to trigger
// as. The 'value' key is optional, and only needed when multiple elements
// share the same name, which is commonly the case for buttons.
$settings['submit']['_triggering_element_name'] = $settings['trigger_as']['name'];
if (isset($settings['trigger_as']['value'])) {
$settings['submit']['_triggering_element_value'] = $settings['trigger_as']['value'];
}
unset($settings['trigger_as']);
}
else {
// Most of the time, elements can submit as themselves, in which case the
// 'trigger_as' key isn't needed, and the element's name is used.
$settings['submit']['_triggering_element_name'] = $element['#name'];
// If the element is a (non-image) button, its name may not identify it
// uniquely, in which case a match on value is also needed.
// @see _form_button_was_clicked()
if (isset($element['#button_type']) && empty($element['#has_garbage_value'])) {
$settings['submit']['_triggering_element_value'] = $element['#value'];
}
}
// Convert a simple #ajax['progress'] string into an array.
if (is_string($settings['progress'])) {
......
This diff is collapsed.
......@@ -98,7 +98,7 @@ Drupal.ajax = function (base, element, element_settings) {
type: 'bar',
message: 'Please wait...'
},
button: {}
submit: {}
};
$.extend(this, defaults, element_settings);
......@@ -121,7 +121,7 @@ Drupal.ajax = function (base, element, element_settings) {
var ajax = this;
var options = {
url: ajax.url,
data: ajax.button,
data: ajax.submit,
beforeSerialize: function (element_settings, options) {
return ajax.beforeSerialize(element_settings, options);
},
......@@ -200,10 +200,6 @@ Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
// Disable the element that received the change.
$(this.element).addClass('progress-disabled').attr('disabled', true);
// Server-side code needs to know what element triggered the call, so it can
// find the #ajax binding.
form_values.push({ name: 'ajax_triggering_element', value: this.formPath });
// Insert progressbar or throbber.
if (this.progress.type == 'bar') {
var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback));
......
......@@ -385,12 +385,12 @@ function file_managed_file_process($element, &$form_state, $form) {
'#weight' => -5,
);
// Because the output of this field changes depending on the button clicked,
// we need to ask FAPI immediately if the remove button was clicked.
// It's not good that we call this private function, but
// $form_state['clicked_button'] is only available after this #process
// callback is finished.
if (_form_button_was_clicked($element['remove_button'], $form_state)) {
// @todo It is not good to call these private functions. This should be
// refactored so that the file deletion happens during a submit handler,
// and form changes affected by that (such as toggling the upload and remove
// buttons) happens during the 2nd run of this function that is triggered by
// a form rebuild: http://drupal.org/node/736298.
if (_form_button_was_clicked($element['remove_button'], $form_state) || _form_element_triggered_scripted_submission($element['remove_button'], $form_state)) {
// If it's a temporary file we can safely remove it immediately, otherwise
// it's up to the implementing module to clean up files that are in use.
if ($element['#file'] && $element['#file']->status == 0) {
......
......@@ -317,8 +317,6 @@ function overlay_preprocess_toolbar(&$variables) {
* processing, so that it's possible to close the overlay after submitting
* a form.
*
* @see _form_builder_handle_input_element()
* @see _form_builder_ie_cleanup()
* @see form_execute_handlers()
* @see form_builder()
* @see overlay_form_submit()
......@@ -327,17 +325,6 @@ function overlay_preprocess_toolbar(&$variables) {
*/
function overlay_form_after_build($form, &$form_state) {
if (overlay_get_mode() == 'child') {
// Form API may have already captured submit handlers from the submitted
// button before after_build callback is invoked. This may have been done
// by _form_builder_handle_input_element(). If so, the list of submit
// handlers is stored in the $form_state array, which is something we can
// also alter from here, luckily. Rememeber: our goal here is to set
// $form_state['redirect'] to FALSE if the API function
// overlay_request_dialog_close() has been invoked. That's because we want
// to tell the parent window to close the overlay.
if (!empty($form_state['submit_handlers']) && !in_array('overlay_form_submit', $form_state['submit_handlers'])) {
$form_state['submit_handlers'][] = 'overlay_form_submit';
}
// If this element has submit handlers, then append our own.
if (isset($form['#submit'])) {
$form['#submit'][] = 'overlay_form_submit';
......
......@@ -341,7 +341,7 @@ class PollJSAddChoice extends DrupalWebTestCase {
// Press 'add choice' button through AJAX, and place the expected HTML result
// as the tested content.
$commands = $this->drupalPostAJAX(NULL, $edit, 'poll_more');
$commands = $this->drupalPostAJAX(NULL, $edit, array('op' => t('More choices')));
$this->content = $commands[1]['data'];
$this->assertFieldByName('choice[chid:0][chtext]', $edit['choice[new:0][chtext]'], t('Field !i found', array('!i' => 0)));
......
......@@ -1576,11 +1576,18 @@ protected function drupalGetAJAX($path, array $options = array(), array $headers
* which is likely different than the $path parameter used for retrieving
* the initial form. Defaults to 'system/ajax'.
* - triggering_element: If the value for the 'path' key is 'system/ajax' or
* another generic AJAX processing path, this needs to be set to the '/'
* separated path to the element within the server's cached $form array.
* The callback for the generic AJAX processing path uses this to find
* the #ajax information for the element, including which specific
* callback to use for processing the request.
* another generic AJAX processing path, this needs to be set to the name
* of the element. If the name doesn't identify the element uniquely, then
* this should instead be an array with a single key/value pair,
* corresponding to the element name and value. The callback for the
* generic AJAX processing path uses this to find the #ajax information
* for the element, including which specific callback to use for
* processing the request.
*
* This can also be set to NULL in order to emulate an Internet Explorer
* submission of a form with a single text field, and pressing ENTER in that
* textfield: under these conditions, no button information is added to the
* POST data.
* @param $options
* Options to be forwarded to url().
* @param $headers
......@@ -1622,7 +1629,7 @@ protected function drupalPost($path, $edit, $submit, array $options = array(), a
// We post only if we managed to handle every field in edit and the
// submit button matches.
if (!$edit && $submit_matches) {
if (!$edit && ($submit_matches || !isset($submit))) {
$post_array = $post;
if ($upload) {
// TODO: cURL handles file uploads for us, but the implementation
......@@ -1643,7 +1650,14 @@ protected function drupalPost($path, $edit, $submit, array $options = array(), a
$post[$key] = urlencode($key) . '=' . urlencode($value);
}
if ($ajax && isset($submit['triggering_element'])) {
$post['ajax_triggering_element'] = 'ajax_triggering_element=' . urlencode($submit['triggering_element']);
if (is_array($submit['triggering_element'])) {
// Get the first key/value pair in the array.
$post['_triggering_element_value'] = '_triggering_element_value=' . urlencode(reset($submit['triggering_element']));
$post['_triggering_element_name'] = '_triggering_element_name=' . urlencode(key($submit['triggering_element']));
}
else {
$post['_triggering_element_name'] = '_triggering_element_name=' . urlencode($submit['triggering_element']);
}
}
$post = implode('&', $post);
}
......@@ -1666,7 +1680,7 @@ protected function drupalPost($path, $edit, $submit, array $options = array(), a
foreach ($edit as $name => $value) {
$this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value)));
}
if (!$ajax) {
if (!$ajax && isset($submit)) {
$this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit)));
}
$this->fail(t('Found the requested form fields at @path', array('@path' => $path)));
......@@ -1856,7 +1870,7 @@ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
break;
case 'submit':
case 'image':
if ($submit == $value) {
if (isset($submit) && $submit == $value) {
$post[$name] = $value;
$submit_matches = TRUE;
}
......
......@@ -98,62 +98,62 @@ class AJAXCommandsTestCase extends AJAXTestCase {
$edit = array();
// Tests the 'after' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'after_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'After': Click to put something after the div"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'insert' && $command['method'] == 'after' && $command['data'] == 'This will be placed after', "'after' AJAX command issued with correct data");
// Tests the 'alert' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'alert_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Alert': Click to alert"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'alert' && $command['text'] == 'Alert', "'alert' AJAX Command issued with correct text");
// Tests the 'append' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'append_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Append': Click to append something"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'insert' && $command['method'] == 'append' && $command['data'] == 'Appended text', "'append' AJAX command issued with correct data");
// Tests the 'before' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'before_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'before': Click to put something before the div"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'insert' && $command['method'] == 'before' && $command['data'] == 'Before text', "'before' AJAX command issued with correct data");
// Tests the 'changed' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'changed_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed."))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'changed' && $command['selector'] == '#changed_div', "'changed' AJAX command issued with correct selector");
// Tests the 'changed' command using the second argument.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'changed_command_asterisk_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed with asterisk."))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'changed' && $command['selector'] == '#changed_div' && $command['asterisk'] == '#changed_div_mark_this', "'changed' AJAX command (with asterisk) issued with correct selector");
// Tests the 'css' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'css_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the the '#box' div to be blue."))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'css' && $command['selector'] == '#css_div' && $command['argument']['background-color'] == 'blue', "'css' AJAX command issued with correct selector");
// Tests the 'data' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'data_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX data command: Issue command."))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'data' && $command['name'] == 'testkey' && $command['value'] == 'testvalue', "'data' AJAX command issued with correct key and value");
// Tests the 'html' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'html_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX html: Replace the HTML in a selector."))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'insert' && $command['method'] == 'html' && $command['data'] == 'replacement text', "'html' AJAX command issued with correct data");
// Tests the 'prepend' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'prepend_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'prepend': Click to prepend something"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'insert' && $command['method'] == 'prepend' && $command['data'] == 'prepended text', "'prepend' AJAX command issued with correct data");
// Tests the 'remove' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'remove_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'remove': Click to remove text"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'remove' && $command['selector'] == '#remove_text', "'remove' AJAX command issued with correct command and selector");
// Tests the 'restripe' command.
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, 'restripe_command_example'));
$commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'restripe' command"))));
$command = $commands[0];
$this->assertTrue($command['command'] == 'restripe' && $command['selector'] == '#restripe_table', "'restripe' AJAX command issued with correct selector");
}
......@@ -203,3 +203,37 @@ class AJAXFormValuesTestCase extends AJAXTestCase {
}
}
}
/**
* Miscellaneous AJAX tests using ajax_test module.
*/
class AJAXElementValidation extends AJAXTestCase {
public static function getInfo() {
return array(
'name' => 'Miscellaneous AJAX tests',
'description' => 'Various tests of AJAX behavior',
'group' => 'AJAX',
);
}
/**
* Try to post an AJAX change to a form that has a validated element.
*
* The drivertext field is AJAX-enabled. An additional field is not, but
* is set to be a required field. In this test the required field is not
* filled in, and we want to see if the activation of the "drivertext"
* AJAX-enabled field fails due to the required field being empty.
*/
function testAJAXElementValidation() {
$web_user = $this->drupalCreateUser();
$edit = array('drivertext' => t('some dumb text'));
// Post with 'drivertext' as the triggering element.
$post_result = $this->drupalPostAJAX('ajax_validation_test', $edit, 'drivertext');
// Look for a validation failure in the resultant JSON.
$this->assertNoText(t('Error message'), t("No error message in resultant JSON"));
$this->assertText('ajax_forms_test_validation_form_callback invoked', t('The correct callback was invoked'));
}
}
......@@ -24,6 +24,12 @@ function ajax_forms_test_menu() {
'page arguments' => array('ajax_forms_test_ajax_commands_form'),
'access callback' => TRUE,
);
$items['ajax_validation_test'] = array(
'title' => 'AJAX Validation Test',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_forms_test_validation_form'),
'access callback' => TRUE,
);
return $items;
}
......@@ -340,3 +346,59 @@ function ajax_forms_test_advanced_commands_restripe_callback($form, $form_state)
$commands[] = ajax_command_restripe('#restripe_table');
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* This form and its related submit and callback functions demonstrate
* not validating another form element when a single AJAX element is triggered.
*
* The "drivertext" element is an AJAX-enabled textfield, free-form.
* The "required_field" element is a textfield marked required.
*
* The correct behavior is that the AJAX-enabled drivertext element should
* be able to trigger without causing validation of the "required_field".
*/
function ajax_forms_test_validation_form($form, &$form_state) {
$form['drivertext'] = array(
'#title' => t('AJAX-enabled textfield.'),
'#description' => t("When this one AJAX-triggers and the spare required field is empty, you should not get an error."),
'#type' => 'textfield',
'#default_value' => !empty($form_state['values']['drivertext']) ? $form_state['values']['drivertext'] : "",
'#ajax' => array(
'callback' => 'ajax_forms_test_validation_form_callback',
'wrapper' => 'message_area',
'method' => 'replace',
),
'#suffix' => '<div id="message_area"></div>',
);
$form['spare_required_field'] = array(
'#title' => t("Spare Required Field"),
'#type' => 'textfield',
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Submit handler for the validation form.
*/
function ajax_forms_test_validation_form_submit($form, $form_state) {
drupal_set_message(t("Validation form submitted"));
}
/**
* AJAX callback for the 'drivertext' element of the validation form.
*/
function ajax_forms_test_validation_form_callback($form, $form_state) {
drupal_set_message("ajax_forms_test_validation_form_callback invoked");
drupal_set_message(t("Callback: drivertext=%drivertext, spare_required_field=%spare_required_field", array('%drivertext' => $form_state['values']['drivertext'], '%spare_required_field' => $form_state['values']['spare_required_field'])));
return '<div id="message_area">ajax_forms_test_validation_form_callback at ' . date('c') . '</div>';
}
......@@ -850,6 +850,77 @@ class FormsProgrammaticTestCase extends DrupalWebTestCase {
}
}
/**
* Test that FAPI correctly determines $form_state['clicked_button'].
*/
class FormsClickedButtonTestCase extends DrupalWebTestCase {
function getInfo() {
return array(
'name' => 'Form clicked button determination',
'description' => 'Test the determination of $form_state[\'clicked_button\'].',
'group' => 'Form API',
);
}
function setUp() {
parent::setUp('form_test');
}
/**
* Test the determination of $form_state['clicked_button'] when no button
* information is included in the POST data, as is sometimes the case when
* the ENTER key is pressed in a textfield in Internet Explorer.
*/
function testNoButtonInfoInPost() {
$path = 'form-test/clicked-button';
$edit = array();
$form_id = 'form-test-clicked-button';
// Ensure submitting a form with no buttons results in no
// $form_state['clicked_button'] and the form submit handler not running.
drupal_static_reset('drupal_html_id');
$this->drupalPost($path, $edit, NULL, array(), array(), $form_id);
$this->assertText('There is no clicked button.', t('$form_state[\'clicked_button\'] set to NULL.'));
$this->assertNoText('Submit handler for form_test_clicked_button executed.', t('Form submit handler did not execute.'));
// Ensure submitting a form with one or more submit buttons results in
// $form_state['clicked_button'] being set to the first one the user has
// access to. An argument with 'r' in it indicates a restricted
// (#access=FALSE) button.
drupal_static_reset('drupal_html_id');
$this->drupalPost($path . '/s', $edit, NULL, array(), array(), $form_id);
$this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to only button.'));
$this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.'));
drupal_static_reset('drupal_html_id');
$this->drupalPost($path . '/s/s', $edit, NULL, array(), array(), $form_id);
$this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.'));
$this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.'));
drupal_static_reset('drupal_html_id');
$this->drupalPost($path . '/rs/s', $edit, NULL, array(), array(), $form_id);
$this->assertText('The clicked button is button2.', t('$form_state[\'clicked_button\'] set to first available button.'));
$this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.'));
// Ensure submitting a form with buttons of different types results in
// $form_state['clicked_button'] being set to the first button, regardless
// of type. For the FAPI 'button' type, this should result in the submit
// handler not executing. The types are 's'(ubmit), 'b'(utton), and
// 'i'(mage_button).
drupal_static_reset('drupal_html_id');
$this->drupalPost($path . '/s/b/i', $edit, NULL, array(), array(), $form_id);
$this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.'));
$this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.'));
drupal_static_reset('drupal_html_id');
$this->drupalPost($path . '/b/s/i', $edit, NULL, array(), array(), $form_id);
$this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.'));
$this->assertNoText('Submit handler for form_test_clicked_button executed.', t('Form submit handler did not execute.'));
drupal_static_reset('drupal_html_id');
$this->drupalPost($path . '/i/s/b', $edit, NULL, array(), array(), $form_id);
$this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.'));
$this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.'));
}
}
/**
* Tests rebuilding of arbitrary forms by altering them.
......
......@@ -118,6 +118,14 @@ function form_test_menu() {
'type' => MENU_CALLBACK,
);
$items['form-test/clicked-button'] = array(
'title' => 'Clicked button test',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_test_clicked_button'),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
......@@ -923,6 +931,84 @@ function form_test_programmatic_form_submit($form, &$form_state) {
$form_state['storage']['programmatic_form_submit'] = $form_state['values']['submitted_field'];
}
/**
* Form builder to test button click detection.
*/
function form_test_clicked_button($form, &$form_state) {
// A single text field. In IE, when a form has only one non-button input field
// and the ENTER key is pressed while that field has focus, the form is
// submitted without any information identifying the button responsible for
// the submission. In other browsers, the form is submitted as though the
// first button were clicked.
$form['text'] = array(
'#title' => 'Text',
'#type' => 'textfield',
);
// Loop through each path argument, addding buttons based on the information
// in the argument. For example, if the path is
// form-test/clicked-button/s/i/rb, then 3 buttons are added: a 'submit', an
// 'image_button', and a 'button' with #access=FALSE. This enables form.test
// to test a variety of combinations.
$i=0;
$args = array_slice(arg(), 2);
foreach ($args as $arg) {
$name = 'button' . ++$i;
// 's', 'b', or 'i' in the argument define the button type wanted.
if (strpos($arg, 's') !== FALSE) {
$type = 'submit';
}
elseif (strpos($arg, 'b') !== FALSE) {
$type = 'button';
}
elseif (strpos($arg, 'i') !== FALSE) {
$type = 'image_button';
}
else {
$type = NULL;
}
if (isset($type)) {
$form[$name] = array(
'#type' => $type,
'#name' => $name,
);
// Image buttons need a #src; the others need a #value.
if ($type == 'image_button') {
$form[$name]['#src'] = 'misc/druplicon.png';
}
else {
$form[$name]['#value'] = $name;
}
// 'r' for restricted, so we can test that button click detection code
// correctly takes #access security into account.
if (strpos($arg, 'r') !== FALSE) {
$form[$name]['#access'] = FALSE;
}
}
}
return $form;
}
/**
* Form validation handler for the form_test_clicked_button() form.
*/
function form_test_clicked_button_validate($form, &$form_state) {
if (isset($form_state['clicked_button'])) {
drupal_set_message(t('The clicked button is %name.', array('%name' => $form_state['clicked_button']['#name'])));
}
else {
drupal_set_message('There is no clicked button.');
}
}
/**
* Form submit handler for the form_test_clicked_button() form.
*/
function form_test_clicked_button_submit($form, &$form_state) {
drupal_set_message('Submit handler for form_test_clicked_button executed.');
}
/**
* Implements hook_form_FORM_ID_alter() for the registration form.
......
......@@ -318,6 +318,7 @@ function system_element_info() {
'#name' => 'op',
'#button_type' => 'submit',
'#executes_submit_callback' => TRUE,
'#limit_validation_errors' => FALSE,
'#process' => array('ajax_process_form'),
'#theme_wrappers' => array('button'),
);
......@@ -326,6 +327,7 @@ function system_element_info() {
'#name' => 'op',
'#button_type' => 'submit',
'#executes_submit_callback' => FALSE,
'#limit_validation_errors' => FALSE,
'#process' => array('ajax_process_form'),
'#theme_wrappers' => array('button'),
);
......@@ -333,6 +335,7 @@ function system_element_info() {
'#input' => TRUE,
'#button_type' => 'submit',
'#executes_submit_callback' => TRUE,
'#limit_validation_errors' => FALSE,
'#process' => array('ajax_process_form'),
'#return_value' => TRUE,
'#has_garbage_value' => TRUE,
......
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