Commit 5e32593f authored by Dries's avatar Dries

- Patch #716602 by effulgentsia: refactor ajax_render() and clean up 'ajax' element type.

parent 1106db64
......@@ -166,9 +166,10 @@
* $commands[] = ajax_command_replace('#object-1', 'some html here');
* // Add a visual "changed" marker to the '#object-1' element.
* $commands[] = ajax_command_changed('#object-1');
* // Output new markup to the browser and end the request.
* // Note: Only custom AJAX paths/page callbacks need to do this manually.
* ajax_render($commands);
* // Menu 'page callback' and #ajax['callback'] functions are supposed to
* // return render arrays. If returning an AJAX commands array, it must be
* // encapsulated in a render array structure.
* return array('#type' => 'ajax', '#commands' => $commands);
* @endcode
*
* When returning an AJAX command array, it is often useful to have
......@@ -178,28 +179,20 @@
* $commands = array();
* $commands[] = ajax_command_replace(NULL, $output);
* $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
* return $commands;
* return array('#type' => 'ajax', '#commands' => $commands);
* @endcode
*
* See @link ajax_commands AJAX framework commands @endlink
*/
/**
* Render a commands array into JSON and exit.
*
* Commands are immediately handed back to the AJAX requester. This function
* will render and immediately exit.
* Render a commands array into JSON.
*
* @param $commands
* A list of macro commands generated by the use of ajax_command_*()
* functions.
* @param $header
* If set to FALSE the 'text/javascript' header used by drupal_json_output()
* will not be used, which is necessary when using an IFRAME. If set to
* 'multipart' the output will be wrapped in a textarea, which can also be
* used as an alternative method when uploading files.
*/
function ajax_render($commands = array(), $header = TRUE) {
function ajax_render($commands = array()) {
// Automatically extract any 'settings' added via drupal_add_js() and make
// them the first command.
$scripts = drupal_add_js(NULL, NULL);
......@@ -210,36 +203,7 @@ function ajax_render($commands = array(), $header = TRUE) {
// Allow modules to alter any AJAX response.
drupal_alter('ajax_render', $commands);
// Use === here so that bool TRUE doesn't match 'multipart'.
if ($header === 'multipart') {
// We do not use drupal_json_output() here because the header is not true.
// We are not really returning JSON, strictly-speaking, but rather JSON
// content wrapped in a textarea as per the "file uploads" example here:
// http://malsup.com/jquery/form/#code-samples
print '<textarea>' . drupal_json_encode($commands) . '</textarea>';
}
elseif ($header) {
drupal_json_output($commands);
}
else {
print drupal_json_encode($commands);
}
drupal_exit();
}
/**
* Send an error response back via AJAX and immediately exit.
*
* This function can be used to quickly create a command array with an error
* string and send it, short-circuiting the error handling process.
*
* @param $error
* A string to display in an alert.
*/
function ajax_render_error($error = '') {
$commands = array();
$commands[] = ajax_command_alert(empty($error) ? t('An error occurred while handling the request: The server received invalid input.') : $error);
ajax_render($commands);
return drupal_json_encode($commands);
}
/**
......@@ -368,6 +332,10 @@ function ajax_form_callback() {
*/
function ajax_deliver($page_callback_result) {
$commands = array();
$header = TRUE;
// Normalize whatever was returned by the page callback to an AJAX commands
// array.
if (!isset($page_callback_result)) {
// Simply delivering an empty commands array is sufficient. This results
// in the AJAX request being completed, but nothing being done to the page.
......@@ -388,11 +356,20 @@ function ajax_deliver($page_callback_result) {
break;
}
}
elseif (is_array($page_callback_result) && isset($page_callback_result['#type']) && ($page_callback_result['#type'] == 'ajax_commands')) {
// Complex AJAX callbacks can return a result that contains a specific
// set of commands to send to the browser.
if (isset($page_callback_result['#ajax_commands'])) {
$commands = $page_callback_result['#ajax_commands'];
elseif (is_array($page_callback_result) && isset($page_callback_result['#type']) && ($page_callback_result['#type'] == 'ajax')) {
// Complex AJAX callbacks can return a result that contains an error message
// or a specific set of commands to send to the browser.
$page_callback_result += element_info('ajax');
$header = $page_callback_result['#header'];
$error = $page_callback_result['#error'];
if (isset($error) && $error !== FALSE) {
if ((empty($error) || $error === TRUE)) {
$error = t('An error occurred while handling the request: The server received invalid input.');
}
$commands[] = ajax_command_alert($error);
}
else {
$commands = $page_callback_result['#commands'];
}
}
else {
......@@ -405,7 +382,49 @@ function ajax_deliver($page_callback_result) {
$commands[] = ajax_command_replace(NULL, $html);
$commands[] = ajax_command_prepend(NULL, theme('status_messages'));
}
ajax_render($commands);
// This function needs to do the same thing that drupal_deliver_html_page()
// does: add any needed http headers, print rendered output, and perform
// end-of-request tasks. By default, $header=TRUE, and we add a
// 'text/javascript' header. The page callback can override $header by
// returning an 'ajax' element with a #header property. This can be set to
// FALSE to prevent the 'text/javascript' header from being output, necessary
// when outputting to an IFRAME. This can also be set to 'multipart', in which
// case, we don't output JSON, but JSON content wrapped in a textarea, making
// a 'text/javascript' header incorrect.
if ($header && $header !== 'multipart') {
drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8');
}
$output = ajax_render($commands);
if ($header === 'multipart') {
// jQuery file uploads: http://malsup.com/jquery/form/#code-samples
$output = '<textarea>' . $output . '</textarea>';
}
print $output;
ajax_footer();
}
/**
* Perform end-of-AJAX-request tasks.
*
* This function is the equivalent of drupal_page_footer(), but for AJAX
* requests.
*
* @see drupal_page_footer()
*/
function ajax_footer() {
// Even for AJAX requests, invoke hook_exit() implementations. There may be
// modules that need very fast AJAX responses, and therefore, run AJAX
// requests with an early bootstrap.
if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')) {
module_invoke_all('exit');
}
// Commit the user session. See above comment about the possibility of this
// function running without session.inc loaded.
if (function_exists('drupal_session_commit')) {
drupal_session_commit();
}
}
/**
......
......@@ -237,7 +237,6 @@ function book_form_update() {
// Load the form based upon the $_POST data sent via the ajax call.
list($form, $form_state) = ajax_get_form();
$commands = array();
$bid = $_POST['book']['bid'];
// Validate the bid.
......@@ -248,15 +247,9 @@ function book_form_update() {
$form['book']['plid'] = _book_parent_select($book_link);
form_set_cache($form['values']['form_build_id'], $form, $form_state);
// Build and render the new select element, then return it in JSON format.
// Build the new select element and return it.
$form_state = array();
$form = form_builder($form['form_id']['#value'], $form, $form_state);
$commands[] = ajax_command_replace(NULL, drupal_render($form['book']['plid']));
return $form['book']['plid'];
}
// @todo: We could and should just return $form['book']['plid'] and skip the
// ajax_command_replace() above. But for now, this provides a test case of
// returning an AJAX commands array.
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
}
......@@ -397,7 +397,7 @@ function field_add_more_js($form, $form_state) {
$field = $field_info['field'];
if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
ajax_render(array());
return;
}
// Navigate to the right element in the the form.
......
......@@ -214,7 +214,7 @@ function file_ajax_upload() {
drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
$commands = array();
$commands[] = ajax_command_replace(NULL, theme('status_messages'));
ajax_render($commands, FALSE);
return array('#type' => 'ajax', '#commands' => $commands, '#header' => FALSE);
}
list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
......@@ -224,7 +224,7 @@ function file_ajax_upload() {
drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
$commands = array();
$commands[] = ajax_command_replace(NULL, theme('status_messages'));
ajax_render($commands, FALSE);
return array('#type' => 'ajax', '#commands' => $commands, '#header' => FALSE);
}
// Get the current element and count the number of files.
......@@ -261,7 +261,7 @@ function file_ajax_upload() {
$commands = array();
$commands[] = ajax_command_replace(NULL, $output, $settings);
ajax_render($commands, FALSE);
return array('#type' => 'ajax', '#commands' => $commands, '#header' => FALSE);
}
/**
......
......@@ -67,7 +67,7 @@ function ajax_forms_test_simple_form_select_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_html('#ajax_selected_color', $form_state['values']['select']);
$commands[] = ajax_command_data('#ajax_selected_color', 'form_state_value_select', $form_state['values']['select']);
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -77,7 +77,7 @@ function ajax_forms_test_simple_form_checkbox_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_html('#ajax_checkbox_value', (int)$form_state['values']['checkbox']);
$commands[] = ajax_command_data('#ajax_checkbox_value', 'form_state_value_select', (int)$form_state['values']['checkbox']);
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
......@@ -233,7 +233,7 @@ function ajax_forms_test_advanced_commands_after_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_after($selector, "This will be placed after");
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -242,7 +242,7 @@ function ajax_forms_test_advanced_commands_after_callback($form, $form_state) {
function ajax_forms_test_advanced_commands_alert_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_alert("Alert");
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -252,7 +252,7 @@ function ajax_forms_test_advanced_commands_append_callback($form, $form_state) {
$selector = '#append_div';
$commands = array();
$commands[] = ajax_command_append($selector, "Appended text");
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -263,7 +263,7 @@ function ajax_forms_test_advanced_commands_before_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_before($selector, "Before text");
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -271,7 +271,7 @@ function ajax_forms_test_advanced_commands_before_callback($form, $form_state) {
*/
function ajax_forms_test_advanced_commands_changed_callback($form, $form_state) {
$commands[] = ajax_command_changed('#changed_div');
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* AJAX callback for 'changed' with asterisk marking inner div.
......@@ -279,7 +279,7 @@ function ajax_forms_test_advanced_commands_changed_callback($form, $form_state)
function ajax_forms_test_advanced_commands_changed_asterisk_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_changed('#changed_div', '#changed_div_mark_this');
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -291,7 +291,7 @@ function ajax_forms_test_advanced_commands_css_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_css($selector, array('background-color' => $color));
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -302,7 +302,7 @@ function ajax_forms_test_advanced_commands_data_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_data($selector, 'testkey', 'testvalue');
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -311,7 +311,7 @@ function ajax_forms_test_advanced_commands_data_callback($form, $form_state) {
function ajax_forms_test_advanced_commands_html_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_html('#html_div', 'replacement text');
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -320,7 +320,7 @@ function ajax_forms_test_advanced_commands_html_callback($form, $form_state) {
function ajax_forms_test_advanced_commands_prepend_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_prepend('#prepend_div', "prepended text");
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -329,7 +329,7 @@ function ajax_forms_test_advanced_commands_prepend_callback($form, $form_state)
function ajax_forms_test_advanced_commands_remove_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_remove('#remove_text');
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
......@@ -338,5 +338,5 @@ function ajax_forms_test_advanced_commands_remove_callback($form, $form_state) {
function ajax_forms_test_advanced_commands_restripe_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_restripe('#restripe_table');
return array('#type' => 'ajax_commands', '#ajax_commands' => $commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
......@@ -13,12 +13,14 @@ function ajax_test_menu() {
$items['ajax-test/render'] = array(
'title' => 'ajax_render',
'page callback' => 'ajax_test_render',
'delivery callback' => 'ajax_deliver',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ajax-test/render-error'] = array(
'title' => 'ajax_render_error',
'page callback' => 'ajax_test_render_error',
'page callback' => 'ajax_test_error',
'delivery callback' => 'ajax_deliver',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
......@@ -26,7 +28,7 @@ function ajax_test_menu() {
}
/**
* Menu callback; Copies $_GET['commands'] into $commands and ajax_render()s that.
* Menu callback; Returns $_GET['commands'] suitable for use by ajax_deliver().
*
* Additionally ensures that ajax_render() incorporates JavaScript settings
* by invoking drupal_add_js() with a dummy setting.
......@@ -40,20 +42,16 @@ function ajax_test_render() {
// Add a dummy JS setting.
drupal_add_js(array('ajax' => 'test'), 'setting');
// Output AJAX commands and end the request.
ajax_render($commands);
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Menu callback; Invokes ajax_render_error().
*
* Optionally passes $_GET['message'] to ajax_render_error().
* Menu callback; Returns AJAX element with #error property set.
*/
function ajax_test_render_error() {
function ajax_test_error() {
$message = '';
if (!empty($_GET['message'])) {
$message = $_GET['message'];
}
ajax_render_error($message);
return array('#type' => 'ajax', '#error' => $message);
}
......@@ -290,8 +290,10 @@ function system_element_info() {
// HTML page, so we don't provide defaults for #theme or #theme_wrappers.
// However, modules can set these properties (for example, to provide an HTML
// debugging page that displays rather than executes AJAX commands).
$types['ajax_commands'] = array(
'#ajax_commands' => array(),
$types['ajax'] = array(
'#header' => TRUE,
'#commands' => array(),
'#error' => NULL,
);
$types['html_tag'] = array(
'#theme' => 'html_tag',
......
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