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 = $('
'); + $('body').append($modal); + + // trigger a global event to allow scripts to bind events to the dialog + $(window).trigger('modal:beforecreate', [$modal, modalSettings]); + $modal.dialog(modalSettings); + $(window).trigger('modal:aftercreate', [$modal, modalSettings]); + + // Let scripts use Drupal.behaviors to do things to the content. + ajax.wrapper = $modal; + Drupal.ajax.prototype.commands.insert(ajax, { + method: 'html', + data: response.data + }, status); + + $modal.on('click', '.modal-dismiss', function (e) { + $modal.dialog('close'); + }); + }, + + modalClose: function (ajax, response, status) { + $modal.dialog('close'); + } + }); + +})(jQuery, Drupal, drupalSettings); diff --git a/core/modules/forum/forum.admin.inc b/core/modules/forum/forum.admin.inc index e3c564a..dae6604 100644 --- a/core/modules/forum/forum.admin.inc +++ b/core/modules/forum/forum.admin.inc @@ -75,7 +75,16 @@ function forum_form_forum($form, &$form_state, $edit = array()) { $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save')); if ($edit['tid']) { - $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete')); + $form['actions']['delete'] = array( + '#type' => 'submit', + '#value' => t('Delete'), + '#attached' => array( + 'library' => array(array('system', 'drupal.ajax.modal')) + ), + '#ajax' => array( + 'callback' => 'forum_confirm_delete_ajax' + ) + ); $form['tid'] = array('#type' => 'hidden', '#value' => $edit['tid']); } $form['#submit'][] = 'forum_form_submit'; @@ -190,7 +199,13 @@ function forum_form_container($form, &$form_state, $edit = array()) { '#value' => t('Save') ); if ($edit['tid']) { - $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete')); + $form['actions']['delete'] = array( + '#type' => 'submit', + '#value' => t('Delete'), + '#ajax' => array( + 'callback' => 'forum_confirm_delete_ajax' + ) + ); $form['tid'] = array('#type' => 'value', '#value' => $edit['tid']); } $form['#submit'][] = 'forum_form_submit'; @@ -200,6 +215,15 @@ function forum_form_container($form, &$form_state, $edit = array()) { } /** + * Ajax callback for deleting a forum. + */ +function forum_confirm_delete_ajax($form, &$form_state) { + return ajax_render_modal_form('forum_confirm_delete', array( + $form_state['values']['tid'] + )); +} + +/** * Form constructor for confirming deletion of a forum taxonomy term. * * @param $tid @@ -209,6 +233,7 @@ function forum_form_container($form, &$form_state, $edit = array()) { * @ingroup forms */ function forum_confirm_delete($form, &$form_state, $tid) { + $form['#action'] = url('admin/structure/forum/edit/forum/' . $tid); $term = taxonomy_term_load($tid); $form['tid'] = array('#type' => 'value', '#value' => $tid); diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc index bca4dc2..6f79b94 100644 --- a/core/modules/node/node.admin.inc +++ b/core/modules/node/node.admin.inc @@ -557,6 +557,7 @@ function node_admin_nodes() { $operations['delete'] = array( 'title' => t('delete'), 'href' => 'node/' . $node->nid . '/delete', + 'modal' => TRUE, 'query' => $destination, ); } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index a6a64ae..c1d2c43 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -1793,7 +1793,7 @@ function node_menu() { ); $items['node/%node/delete'] = array( 'title' => 'Delete', - 'page callback' => 'drupal_get_form', + 'page callback' => 'ajax_modal_form', 'page arguments' => array('node_delete_confirm', 1), 'access callback' => 'node_access', 'access arguments' => array('delete', 1), diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 843ed78..5f1123d 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1170,6 +1170,23 @@ function system_library_info() { ), ); + // Drupal's Ajax framework, modal component. + $libraries['drupal.ajax.modal'] = array( + 'title' => 'Drupal AJAX modals', + 'website' => 'http://api.drupal.org/api/group/ajax/8', + 'version' => VERSION, + 'js' => array( + 'core/misc/ajax.modal.js' => array('group' => JS_LIBRARY, 'weight' => 2), + ), + 'dependencies' => array( + array('system', 'jquery'), + array('system', 'drupal'), + array('system', 'drupalSettings'), + array('system', 'drupal.ajax'), + array('system', 'jquery.ui.dialog') + ), + ); + // Drupal's batch API. $libraries['drupal.batch'] = array( 'title' => 'Drupal batch API', @@ -3370,6 +3387,11 @@ function confirm_form($form, $question, $path, $description = NULL, $yes = NULL, '#type' => 'link', '#title' => $no ? $no : t('Cancel'), '#href' => $options['path'], + '#attributes' => array( + 'class' => array( + 'modal-dismiss' + ) + ), '#options' => $options, ); // By default, render the form using theme_confirm_form().