diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc index 45bb706..e05ac74 100644 --- a/core/includes/ajax.inc +++ b/core/includes/ajax.inc @@ -76,7 +76,6 @@ * 'wrapper' => 'replace_textfield_div', * ), * ); - * // This entire form element will be replaced with an updated value. * $form['replace_textfield'] = array( * '#type' => 'textfield', @@ -948,6 +947,38 @@ function ajax_command_remove($selector) { } /** + * Creates a Drupal Ajax 'redirect' command. + * + * The 'redirect' command instructs the browser to redirect to the give url. + * + * This command is implemented by Drupal.ajax.prototype.commands.redirect() + * defined in misc/ajax.js. + * + * @param $path + * An internal drupal path. + * @param (optional) $options + * An associative array of additional URL options to pass to url(). Defaults + * to array(). + * + * @return + * An array suitable for use with the ajax_render() function. + */ +function ajax_command_redirect($path, $options = array()) { + // Similar logic to drupal_goto. + $destination = drupal_container()->get('request')->query->get('destination'); + if (!empty($destination) && !url_is_external($destination)) { + $parsed_destination = drupal_parse_url($destination); + $path = $parsed_destination['path']; + $options['query'] = $parsed_destination['query']; + $options['fragment'] = $parsed_destination['fragment']; + } + return array( + 'command' => 'redirect', + 'href' => url($path, $options), + ); +} + +/** * Creates a Drupal Ajax 'changed' command. * * This command instructs the client to mark each of the elements matched by the @@ -1140,3 +1171,183 @@ function ajax_command_add_css($styles) { 'data' => $styles, ); } + +/** + * Wrapper around drupal_build_form to handle rendering forms in a jquery.ui + * dialog. Automatically handles a number of AJAX tasks. + * + * @param $form_id + * The unique string identifying the desired form. + * @param $args + * An array of arguments for the form builder. + * @param ... + * Any additional arguments are passed on to drupal_build_form(), including + * the unique form constructor function. + * + * @return + * An array of ajax commands. + * + * @see drupal_get_form() + * @see drupal_build_form() + * @see ajax_deliver() + */ +function ajax_render_modal_form($form_id, $args) { + // Check for the content-type. + $container = drupal_container(); + $type = FALSE; + if ($container->has('request') && $container->has('content_negotiation')) { + $type = $container->get('content_negotiation')->getContentType($container->get('request')); + } + // Set sensible defaults. + $form_state = array( + 'rerender' => FALSE, + 'ajax' => ($type == 'ajax'), + 'no_redirect' => ($type == 'ajax'), + 'build_info' => array( + 'args' => $args, + ) + ); + // Build and render the form. + $form = drupal_build_form($form_id, $form_state); + $output = drupal_render($form); + // Some forms may have the title built in, if so set the title here (for non + // javascript users): + if (empty($form_state['ajax']) && !empty($form_state['title'])) { + drupal_set_title($form_state['title']); + } + + if (!empty($form_state['ajax']) && (empty($form_state['executed']) || !empty($form_state['rerender']))) { + // If the form didn't execute and we're using ajax, build up a + // Ajax command list to execute. + $commands = array(); + + $display = ''; + if ($messages = theme('status_messages__ajax__' . $form_id)) { + $display = $messages; + } + $display .= $output; + + $title = empty($form_state['title']) ? drupal_get_title() : $form_state['title']; + // @todo document hook + drupal_alter(array('ajax_form_title', 'ajax_form_title_' . $form_id), $title, $form_state); + + $url = empty($form_state['url']) ? url(current_path(), array('absolute' => TRUE, 'query' => drupal_container()->get('request')->query->all())) : $form_state['url']; + + $commands[] = ajax_command_set_modal($display, $title, $url); + + // @todo document hook + drupal_alter(array('ajax_form_commands', 'ajax_form_commands_' . $form_id), $commands, $form_state); + + return array('#type' => 'ajax', '#commands' => $commands); + } + elseif ($form_state['submitted'] && empty($form_state['rerender'])) { + if (!empty($form_state['ajax'])) { + // Form has submitted, dismiss it. + $commands = array(); + $commands[] = ajax_command_dismiss_modal(); + $redirect = current_path(); + if (isset($form_state['redirect'])) { + if (is_array($form_state['redirect'])) { + $redirect = end($form_state['redirect']); + } + else { + $redirect = $form_state['redirect']; + } + } + $commands[] = ajax_command_redirect($redirect); + drupal_alter(array('ajax_form_commands', 'ajax_form_commands_' . $form_id), $commands, $form_state); + return array('#type' => 'ajax', '#commands' => $commands); + } + // Degrade to non-javascript behaviour. + drupal_redirect_form($form_state); + } + + // Non javascript invocation - degrade to standard drupal_build_form + // behaviour. Forms can have the title built in, if so set the title here: + if (empty($form_state['ajax']) && !empty($form_state['title'])) { + drupal_set_title($form_state['title']); + } + + return $output; +} + +/** + * Creates a Drupal AJAX 'modalOpen' command. + * + * @param $html + * The html to display in the modal. + * @param $title + * The title. + * @param $url + * An optional URL. + * + * @return + * An array suitable for use with the ajax_render() function. + */ +function ajax_command_set_modal($html, $title, $url = NULL) { + $command = array( + 'command' => 'modalOpen', + 'data' => $html, + 'title' => $title, + ); + if (isset($url)) { + $command['url'] = $url; + } + return $command; +} + +/** + * Creates a Drupal AJAX 'modalClose' command. + * + * @return + * An array suitable for use with the ajax_render() function. + */ +function ajax_command_dismiss_modal() { + $command = array( + 'command' => 'modalClose', + ); + return $command; +} + +/** + * Form builder wrapper for displaying ajax forms in modals. + * + * Modules should use this as a menu callback to handle displaying forms in + * modals. Example hook_menu () item + * @code + * $items['node/%node/delete'] = array( + * 'title' => 'Delete', + * 'page callback' => 'ajax_modal_form', + * 'page arguments' => array('node_delete_confirm', 1), + * 'access callback' => 'node_access', + * 'access arguments' => array('delete', 1), + * 'weight' => 1, + * 'type' => MENU_LOCAL_TASK, + * 'context' => MENU_CONTEXT_INLINE, + * 'file' => 'node.pages.inc', + * ); + * @endcode + * In addition modules should ensure their links to ajax modals contain the + * 'modal' flag. + * + * @param $form_id + * The unique string identifying the desired form. + * @param ... + * Any additional arguments are passed on to drupal_build_form(), including + * the unique form constructor function. + * + * @return mixed + * If request Content-Type is ajax, returns array of ajax commands, otherwise + * returns markup from drupal_get_form() + * + * @see l() + */ +function ajax_modal_form($form_id) { + // Get the form build arguments. + $args = func_get_args(); + // Remove the $form_id. + array_splice($args, 0, 1); + $output = ajax_render_modal_form($form_id, $args); + + return $output; +} diff --git a/core/includes/common.inc b/core/includes/common.inc index 7fa79d1..7823412 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2306,6 +2306,9 @@ function drupal_http_header_attributes(array $attributes = array()) { * internal to the site, $options['language'] is used to determine whether * the link is "active", or pointing to the current page (the language as * well as the path must match). This element is also used by url(). + * - 'modal': Pass TRUE if the link should display the form in a modal, note + * that the page callback should be ajax_modal_form() in order for this to + * work. * - Additional $options elements used by the url() function. * * @return string @@ -2323,6 +2326,11 @@ function l($text, $path, array $options = array()) { 'html' => FALSE, ); + if (!empty($options['modal'])) { + $options['attributes']['class'][] = 'use-ajax'; + drupal_add_library('system', 'drupal.ajax.modal'); + } + // Append active class. // The link is only active, if its path corresponds to the current path, the // language of the linked path is equal to the current language, and if the diff --git a/core/misc/ajax.js b/core/misc/ajax.js index 58db308..e83db7c 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -40,6 +40,12 @@ Drupal.behaviors.AJAX = { } } + // Prevent ajax links to move page. + $('body').once('ajax-link').on('click', '.use-ajax', function (e) { + e.preventDefault(); + e.stopPropagation(); + }); + // Bind Ajax behaviors to all items showing the class. $('.use-ajax').once('ajax', function () { var element_settings = {}; @@ -547,6 +553,13 @@ Drupal.ajax.prototype.commands = { }, /** + * Command to redirect a user. + */ + redirect: function (ajax, response, status) { + window.location.href = response.href; + }, + + /** * Command to remove a chunk from the page. */ remove: function (ajax, response, status) { diff --git a/core/misc/ajax.modal.js b/core/misc/ajax.modal.js new file mode 100644 index 0000000..a8171d7 --- /dev/null +++ b/core/misc/ajax.modal.js @@ -0,0 +1,54 @@ +(function ($, Drupal, drupalSettings) { + + "use strict"; + + var $modal; + var modalConfig = { + //autoOpen: false, + modal: true, + dialogClass: '', + close: function (e) { + Drupal.detachBehaviors(e.target, null, 'unload'); + // remove everything from the DOM like the previous modal dialog. + $(e.target).dialog('destroy').remove(); + } + }; + + $.extend(Drupal.ajax.prototype.commands, { + /** + * + */ + modalOpen: function (ajax, response, status) { + var modalSettings = $.extend({title: response.title}, modalConfig, drupalSettings.modal); + // Clean up all Modal behaviors. + $modal = $('#drupal-modal'); + if ($modal.length) { + Drupal.detachBehaviors($modal[0], null, 'unload'); + $modal.dialog('destroy').remove(); + } + $modal = $('