diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc index 6d21c98..af6e347 100644 --- a/core/includes/ajax.inc +++ b/core/includes/ajax.inc @@ -948,6 +948,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 $options (optional) + * An associative array of additional URL options to pass to url(). + * + * @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 +1172,205 @@ 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 $form_state + * An array which stores information about the form. + * @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_form_wrapper($form_id, &$form_state) { + // 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(array('status_messages', 'status_messages__ajax', '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_form($display, $title, $url); + + // @todo document hook + drupal_alter(array('ajax_form_commands', 'ajax_form_commands_' . $form_id), $commands, $form_state); + + return $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_form(); + $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 $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 'setForm' command. + * + * @param $output + * The form 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_form($output, $title, $url = NULL) { + $command = array( + 'command' => 'setForm', + 'output' => $output, + 'title' => $title, + ); + if (isset($url)) { + $command['url'] = $url; + } + return $command; +} + +/** + * Creates a Drupal AJAX 'dismissForm' command. + * + * @return + * An array suitable for use with the ajax_render() function. + */ +function ajax_command_dismiss_form() { + $command = array( + 'command' => 'dismissForm', + ); + 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); + // 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 if they don't already exist. + $form_state = array( + 'rerender' => FALSE, + 'ajax' => ($type == 'ajax'), + 'no_redirect' => ($type == 'ajax'), + 'build_info' => array( + 'args' => $args, + ) + ); + $output = ajax_form_wrapper($form_id, $form_state); + + return ($type == 'ajax') ? array('#type' => 'ajax', '#commands' => $output) : $output; +} + +/** + * Utililty to add libraries and JavaScript required for ajax modal forms. + * + * Adds jquery.ui.dialog, jquery.form, ajax.js and required settings to the + * page. +*/ +function ajax_modal_form_prepare() { + $added = drupal_static(__FUNCTION__); + if (!$added) { + drupal_add_js('core/misc/ajax.js'); + drupal_add_library('system', 'jquery.ui.dialog'); + drupal_add_library('system', 'jquery.form'); + drupal_add_js(array( + 'ajaxForm' => array( + 'popup' => 'ajax-modal-popup', + 'body' => 'ajax-modal-body' + ) + ), 'setting'); + $added = TRUE; + } +} diff --git a/core/includes/common.inc b/core/includes/common.inc index c47494e..53e0a22 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2284,6 +2284,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 @@ -2292,7 +2295,7 @@ function drupal_http_header_attributes(array $attributes = array()) { * @see url() */ function l($text, $path, array $options = array()) { - static $use_theme = NULL; + static $use_theme = NULL, $modal_added = FALSE; // Merge in defaults. $options += array( @@ -2300,6 +2303,14 @@ function l($text, $path, array $options = array()) { 'html' => FALSE, ); + if (!empty($options['modal'])) { + $options['attributes']['class'][] = 'use-ajax'; + if (!$modal_added) { + ajax_modal_form_prepare(); + $modal_added = TRUE; + } + } + // Append active class. if (($path == current_path() || ($path == '' && drupal_is_front_page())) && (empty($options['language']) || $options['language']->langcode == language(LANGUAGE_TYPE_URL)->langcode)) { diff --git a/core/misc/ajax.js b/core/misc/ajax.js index 94bfb89..02a795e 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -465,6 +465,101 @@ Drupal.ajax.prototype.error = function (response, uri) { }; /** + * Resize the open jquery ui modal. + */ +Drupal.ajax.prototype.resizeModal = function(e, no_shrink) { + var $ = jQuery; + var $modal = $('.ajax-form-dialog'); //@todo + var $scroll = $('.scroll', $modal); //@todo + if ($modal.size() == 0 || $modal.css('display') == 'none') { + return; + } + + var maxWidth = parseInt($(window).width() * .85); // 85% of window + var minWidth = parseInt($(window).width() * .6); // 60% of window + + // Set the modal to the minwidth so that our width calculation of + // children works. + $modal.css('width', minWidth); + var width = minWidth; + + // Don't let the window get more than 80% of the display high. + var maxHeight = parseInt($(window).height() * .8); + var minHeight = 200; + if (no_shrink) { + minHeight = $modal.height(); + } + + if (minHeight > maxHeight) { + minHeight = maxHeight; + } + + var height = 0; + + // Calculate the height of the 'scroll' region. + var scrollHeight = 0; + + scrollHeight += parseInt($scroll.css('padding-top')); + scrollHeight += parseInt($scroll.css('padding-bottom')); + + $scroll.children().each(function() { + var w = $(this).innerWidth(); + if (w > width) { + width = w; + } + scrollHeight += $(this).outerHeight(true); + }); + + // Now, calculate what the difference between the scroll and the modal + // will be. + + var difference = 0; + difference += parseInt($scroll.css('padding-top')); + difference += parseInt($scroll.css('padding-bottom')); + // @todo is any of this relevant? + /*difference += $('.views-override').outerHeight(true); + difference += $('.views-messages').outerHeight(true); + difference += $('#views-ajax-title').outerHeight(true); + difference += $('.views-add-form-selected').outerHeight(true); + difference += $('.form-buttons', $modal).outerHeight(true);*/ + + height = scrollHeight + difference; + + if (height > maxHeight) { + height = maxHeight; + scrollHeight = maxHeight - difference; + } + else if (height < minHeight) { + height = minHeight; + scrollHeight = minHeight - difference; + } + + if (width > maxWidth) { + width = maxWidth; + } + + // Get where we should move content to + var top = ($(window).height() / 2) - (height / 2); + var left = ($(window).width() / 2) - (width / 2); + + $modal.css({ + 'top': top + 'px', + 'left': left + 'px', + 'width': width + 'px', + 'height': height + 'px' + }); + + // Ensure inner popup height matches. + $(Drupal.settings.ajaxForm.popup).css('height', height + 'px'); + + $scroll.css({ + 'height': scrollHeight + 'px', + 'max-height': scrollHeight + 'px' + }); + +}; + +/** * Provide a series of commands that the server can request the client perform. */ Drupal.ajax.prototype.commands = { @@ -542,6 +637,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) { @@ -635,6 +737,68 @@ Drupal.ajax.prototype.commands = { document.styleSheets[0].addImport(match[1]); } while (match); } + }, + + /** + * Command to set a form in a modal. + */ + setForm: function (ajax, response, status) { + Drupal.settings.ajaxForm = Drupal.settings.ajaxForm || {}; + if (!Drupal.settings.ajaxForm.popup || typeof Drupal.settings.ajaxForm.popup == 'string') { + // Initalise the dialog. Modules can nominate specific ids by overriding + // Drupal.settings.ajaxForm.popup|title|body. + var popup = Drupal.settings.ajaxForm.popup = $('
').attr('id', Drupal.settings.ajaxForm.popup || 'ajax-form-popup'); + Drupal.settings.ajaxForm.body = $('
') + .attr('id', Drupal.settings.ajaxForm.body || 'ajax-form-body') + .appendTo(popup); + // Create a jQuery UI dialog, but leave it closed. + popup.dialog({ + 'autoOpen': false, + 'dialogClass': 'ajax-form-dialog', + 'modal': true, + 'position': 'center', + 'resizable': false, + 'width': 750 + }); + } + // Get the selectors for our title, body and popup elements. + var ajax_body = Drupal.settings.ajaxForm.body || '
'; + var ajax_popup = Drupal.settings.ajaxForm.popup || '
'; + // Update our elements with the command response. + Drupal.settings.ajaxForm.body.html(response.output); + Drupal.settings.ajaxForm.popup.dialog('option', 'title', response.title); + // Open the jQuery.ui dialog. + Drupal.settings.ajaxForm.popup.dialog('open'); + Drupal.attachBehaviors(Drupal.settings.ajaxForm.popup, ajax.settings); + if (response.url) { + // Identify the button that was clicked so that .ajaxSubmit() can use it. + // We need to do this for both .click() and .mousedown() since JavaScript + // code might trigger either behavior. + var $submit_buttons = $('input[type=submit], button', Drupal.settings.ajaxForm.body); + $submit_buttons.click(function(event) { + this.form.clk = this; + }); + $submit_buttons.mousedown(function(event) { + this.form.clk = this; + }); + $('.dismiss', Drupal.settings.ajaxForm.body).click(function( event ) { + event.preventDefault(); + Drupal.settings.ajaxForm.popup.dialog('close'); + }) + + $('form', ajax_body).once('ajax-submit-processed').each(function() { + var element_settings = { 'url': response.url, 'event': 'submit', 'progress': { 'type': 'throbber' } }; + var $form = $(this); + var id = $form.attr('id'); + Drupal.ajax[id] = new Drupal.ajax(id, this, element_settings); + Drupal.ajax[id].form = $form; + }); + } + }, + + dismissForm: function (ajax, response, status) { + Drupal.ajax.prototype.commands.setForm({}, {'title': '', 'output': ''}); + $(Drupal.settings.ajaxForm.popup).dialog('close'); } }; diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc index 5cbe4ef..945a7bc 100644 --- a/core/modules/node/node.admin.inc +++ b/core/modules/node/node.admin.inc @@ -513,6 +513,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 bfba2fd..759d098 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -1989,7 +1989,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 e5dc768..11e64e4 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -3340,6 +3340,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( + 'dismiss' + ) + ), '#options' => $options, ); // By default, render the form using theme_confirm_form().