Index: modules/overlay/overlay-child.js =================================================================== RCS file: modules/overlay/overlay-child.js diff -N modules/overlay/overlay-child.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/overlay/overlay-child.js 28 Jul 2009 12:51:05 -0000 @@ -0,0 +1,136 @@ +// $Id: child.js,v 1.1.4.3 2009/06/17 15:16:26 markuspetrux Exp $ + +(function ($) { + +/** + * Overlay object for child windows. + */ +Drupal.overlayChild = Drupal.overlayChild || { processed: false, behaviors: {} }; + +/** + * Drupal will automatically attach the child dialog behavior to new content. + */ +Drupal.behaviors.overlayChild = { + attach: function(context, settings) { + Drupal.overlayChild.attachBehavior(context, settings); + } +}; + +/** + * Attach child dialog behavior. + */ +Drupal.overlayChild.attachBehavior = function(context, settings) { + var self = Drupal.overlayChild; + var settings = settings.overlayChild || {}; + + // Make sure this behavior is not processed more than once. + if (self.processed) { + return; + } + self.processed = true; + + // If we cannot reach the parent window, then we have nothing else to do + // here. + if (!self.isObject(parent.Drupal) || !self.isObject(parent.Drupal.overlay)) { + return; + } + + // If a form has been submitted successfully, then the server side script + // may have decided to tell us the parent window to close the popup dialog. + if (settings.closeOverlay) { + parent.Drupal.overlay.bindChild(window, true); + // Close the child window from a separate thread because the current + // one is busy processing Drupal behaviors. + setTimeout(function() { + // We need to store the parent variable locally because it will + // disappear as soon as we close the iframe. + var p = parent; + p.Drupal.overlay.close(settings.statusMessages); + if (typeof settings.redirect == 'string') { + p.Drupal.overlay.redirect(settings.redirect); + } + }, 1); + return; + } + + // Ok, now we can tell the parent window we're ready. + parent.Drupal.overlay.bindChild(window); + + // Install onBeforeUnload callback, if module is present. + if (self.isObject(Drupal.onBeforeUnload) && !Drupal.onBeforeUnload.callbackExists('overlayChild')) { + Drupal.onBeforeUnload.addCallback('overlayChild', function() { + // Tell the parent window we're unloading. + parent.Drupal.overlay.unbindChild(window); + }); + } + + // Attach child related behaviors to the iframe document. + self.attachBehaviors(context, settings); +}; + +/** + * Add the isObject() method to the overlayChild object for convenience. + */ +Drupal.overlayChild.isObject = parent.Drupal.overlay.isObject; + +/** + * Attach child related behaviors to the iframed document. + */ +Drupal.overlayChild.attachBehaviors = function(context, settings) { + $.each(this.behaviors, function() { + this(context, settings); + }); +}; + +/** + * Add target="_new" to all external URLs. + */ +Drupal.overlayChild.behaviors.parseLinks = function(context, settings) { + $('a:not(.overlay-processed)', context).addClass('overlay-processed').each(function() { + // Do not process links that have the class "overlay-exclude". + if ($(this).hasClass('overlay-exclude')) { + return; + } + // Links that have the class "overlay-escape" should close the overlay and + // open in the main window. + if ($(this).hasClass('overlay-escape')) { + $(this).click(function() { + // We need to store the parent variable locally because it will + // disappear as soon as we close the iframe. + var parentWindow = parent; + parentWindow.Drupal.overlay.close(false); + parentWindow.Drupal.overlay.redirect($(this).attr('href')); + return false; + }); + return; + } + // Obtain the href attribute of the link. + var href = $(this).attr('href'); + // Do not process links with an empty href, or that only have the fragment. + if (href.length <= 0 || href.charAt(0) == '#') { + return; + } + if (href.indexOf('http') != 0 && href.indexOf('https') != 0) { + // Keep internal linked pages in the overlay. + href += (href.indexOf('?') > -1 ? '&' : '?') + 'render=overlay'; + $(this).attr('href', href); + } + else { + $(this).attr('target', '_new'); + } + }); + $('form:not(.overlay-processed)', context).addClass('overlay-processed').each(function() { + // Obtain the action attribute of the form. + var action = $(this).attr('action'); + if (action.indexOf('http') != 0 && action.indexOf('https') != 0) { + // Keep internal forms in the overlay. + action += (action.indexOf('?') > -1 ? '&' : '?') + 'render=overlay'; + $(this).attr('action', action); + } + else { + $(this).attr('target', '_new'); + } + }); +}; + +})(jQuery); Index: modules/overlay/overlay-parent.css =================================================================== RCS file: modules/overlay/overlay-parent.css diff -N modules/overlay/overlay-parent.css --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/overlay/overlay-parent.css 28 Jul 2009 13:55:18 -0000 @@ -0,0 +1,180 @@ +/* $Id$ */ + +/** + * jQuery UI Dialog classes. + */ +.overlay { + color: #000; + padding-right: 30px; +} + +.overlay .ui-dialog-titlebar { + position: relative; + height: 30px; + white-space: nowrap; + z-index: 2; +} + +.overlay .ui-dialog-title { + display: block; + position: absolute; + top: 0; + left: 0; + margin: 0.2em 0.5em; + padding: 0; + color: #fff; + font-size: 20px; +} + +.overlay .ui-dialog-titlebar-close { + display: block; + position: absolute; + right: -26px; + top: 30px; + margin: 0; + padding: 0; + width: 26px; + height: 26px; +} + +.overlay .ui-dialog-titlebar-close-bg { + background: transparent url(images/close.png) no-repeat; + width: 32px; + height: 36px; + position: absolute; + right: -32px; + top: 30px; + opacity: 0; + filter: alpha(opacity=0); +} + +.overlay .ui-dialog-titlebar-close span { + display: none; +} + +.overlay .ui-dialog-content { + color: #292929; + background-color: #f8f8f8; +} + +/** + * ui-dialog overlay. + */ +.ui-widget-overlay { + position: absolute; + top: 0; left: 0; + width: 100%; height: 100%; + background-color: #000; + opacity: 0.8; + filter: alpha(opacity=80); +} + +/** + * Overlay content and shadows. + */ +#overlay-container { + margin: 0; + padding: 0; + background: #fff url(images/loading.gif) no-repeat 50% 50%; +} + +#overlay-container div.overlay-shadow { + position: absolute; + z-index: 1; + opacity: 0; + filter: alpha(opacity=0); +} + +#overlay-container div.overlay-shadow-bottom { + height: 15px; + background: transparent url(images/ovrly-shdw-bt.png) repeat-x; + margin: -5px 0 0 15px; + position: relative; +} + +#overlay-element { + margin: 0; + padding: 0; + border: none; + overflow: hidden; +} + +#overlay-container div.overlay-shadow-right { + top: 30px; + right: 15px; + width: 15px; + background: transparent url(images/ovrly-shdw-rt.png) repeat-y; +} + +#overlay-container div.overlay-shadow-bottom-right { + bottom: -15px; + left: 0px; + width: 15px; + height: 15px; + background: transparent url(images/ovrly-shdw-bt-rt.png) no-repeat; +} + +#overlay-container div.overlay-shadow-bottom-left { + top: 0px; + left: -15px; + width: 15px; + height: 15px; + background: transparent url(images/ovrly-shdw-bt-lt.png) no-repeat; +} + +/** + * Tabs on the overlay. + */ +.ui-dialog-titlebar ul { + float: right; + border-bottom: none; + margin: 0 20px 0 0; + padding: 9px 0 0; + line-height: normal; + text-transform: uppercase; + font-size: 12px; +} + +.ui-dialog-titlebar ul li { + display: inline; + list-style: none; + margin-left: 1px; +} + +.ui-dialog-titlebar ul li { + background: transparent url(images/tab_inactive_rt.png) no-repeat top right; + padding: 4px 8px 7px 0; +} + +.ui-dialog-titlebar ul li a, +.ui-dialog-titlebar ul li a.active, +.ui-dialog-titlebar ul li a:active, +.ui-dialog-titlebar ul li a:visited { + background: transparent url(images/tab_inactive_lt.png) no-repeat; + color: #000; + font-weight: bold; + padding: 4px 15px 7px 20px; + text-decoration: none; +} + +.ui-dialog-titlebar ul li.active { + background: transparent url(images/tab_active_rt.png) no-repeat top right; +} + +.ui-dialog-titlebar ul li.active a, +.ui-dialog-titlebar ul li.active a.active, +.ui-dialog-titlebar ul li.active a:active, +.ui-dialog-titlebar ul li.active a:visited { + background: transparent url(images/tab_active_lt.png) no-repeat; + color: #000; + font-weight: bold; + padding: 4px 15px 7px 20px; +} + +.ui-dialog-titlebar ul li a:hover { + color: #fff; +} + +.ui-dialog-titlebar ul li.active a:hover { + color: #000; +} Index: modules/overlay/overlay-parent.js =================================================================== RCS file: modules/overlay/overlay-parent.js diff -N modules/overlay/overlay-parent.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/overlay/overlay-parent.js 28 Jul 2009 12:57:03 -0000 @@ -0,0 +1,517 @@ +// $Id: parent.js,v 1.1.4.4 2009/06/19 15:32:57 markuspetrux Exp $ + +(function ($) { + +Drupal.behaviors.keepOverlay = { + attach: function(context, settings) { + + // Attach on the .to-overlay class. + $('a.to-overlay:not(.overlay-processed)').addClass('overlay-processed').click(function() { + + // Remove the active class from where it was, and add the active class to + // this link, so the button keeps highlighting where we are. Only + // highlight active items in the shortcuts bar. + $('#toolbar a').each(function() { + $(this).removeClass('active'); + }); + if ($(this).parents('div.toolbar-shortcuts').length) { + $(this).addClass('active'); + } + + // Append render variable, so the server side can choose the right + // rendering and add child modal frame code to the page if needed. + var linkURL = $(this).attr('href'); + linkURL += (linkURL.indexOf('?') > -1 ? '&' : '?') + 'render=overlay'; + + // If the modal frame is already open, replace the loaded document with + // this new one. Keeps browser history. + if (Drupal.overlay.isOpen) { + Drupal.overlay.load(linkURL); + return false; + } + + // There is overlay opened yet, we should open a new one. + var toolbarHeight = $('#toolbar').height(); + var overlayOptions = { + url: linkURL, + width: $(window).width() - 40, + height: $(window).height() - 40 - toolbarHeight, + // Remove active class from all header buttons. + onOverlayClose: function() { $('#toolbar a').each(function() { $(this).removeClass('active'); }); } + }; + Drupal.overlay.open(overlayOptions); + + // Set position and styling to let the admin toolbar work. + $('.overlay').css('top', toolbarHeight + 20); + $('#toolbar').css('z-index', 2000); + + // Prevent default action of the link click event. + return false; + }); + } +}; + +/** + * Overlay object for parent windows. + */ +Drupal.overlay = Drupal.overlay || { + options: {}, + iframe: { $container: null, $element: null }, + isOpen: false +}; + +/** + * Open an overlay. + */ +Drupal.overlay.open = function(options) { + var self = this; + + // Just one overlay is allowed. + if (self.isOpen || $('#overlay-container').size()) { + return false; + } + + // Build overlay frame options structure. + self.options = { + url: options.url, + width: options.width, + height: options.height, + autoFit: (options.autoFit == undefined || options.autoFit), + onOverlayClose: options.onOverlayClose + }; + + // Create the dialog and related DOM elements. + self.create(options); + + // Open the dialog offscreen where we can set its size, etc. + self.iframe.$container.dialog('option', {position: ['-999em', '-999em']}).dialog('open'); + + return true; +}; + +/** + * Create the overlay. + */ +Drupal.overlay.create = function() { + var self = this; + + // Note: We use scrolling="yes" for IE as a workaround to yet another IE bug + // where the horizontal scrollbar is always rendered no matter how wide the + // iframe element is defined. + self.iframe.$element = $(''); + self.iframe.$container = $('
').append(self.iframe.$element); + self.iframe.$element + .after($('').append('')) + .after($('').append('')); + + $('body').append(self.iframe.$container); + + self.iframe.$container.dialog({ + modal: true, + autoOpen: false, + closeOnEscape: true, + resizable: false, + title: Drupal.t('Loading...'), + dialogClass: 'overlay', + open: function() { + // Unbind the keypress handler installed by ui.dialog itself. + // IE does not fire keypress events for some non-alphanumeric keys + // such as the tab character. http://www.quirksmode.org/js/keys.html + // Also, this is not necessary here because we need to deal with an + // iframe element that contains a separate window. + // We'll try to provide our own behavior from bindChild() method. + $('.overlay').unbind('keypress.ui-dialog'); + + // Adjust close button features. + $('.overlay .ui-dialog-titlebar-close:not(.overlay-processed)').addClass('overlay-processed') + .attr('href', 'javascript:void(0)') + .attr('title', Drupal.t('Close')) + .unbind('click') + .bind('click', function() { try { self.close(false); } catch(e) {}; return false; }) + .before(''); + + // Compute initial dialog size. + var dialogSize = self.sanitizeSize({width: self.options.width, height: self.options.height}); + + // Compute frame size and dialog position based on dialog size. + var frameSize = $.extend({}, dialogSize); + frameSize.height -= $('.overlay .ui-dialog-titlebar').outerHeight(true); + var dialogPosition = self.computePosition($('.overlay'), dialogSize); + + // Adjust size of the iframe element and container. + $('.overlay').width(dialogSize.width).height(dialogSize.height); + self.iframe.$container.width(frameSize.width).height(frameSize.height); + self.iframe.$element.width(frameSize.width).height(frameSize.height); + + // Update the dialog size so that UI internals are aware of the change. + self.iframe.$container.dialog('option', {width: dialogSize.width, height: dialogSize.height}); + + // Hide the dialog, position it on the viewport and then fade it in with + // the frame hidden until the child document is loaded. + self.iframe.$element.hide(); + $('.overlay').hide().css({top: dialogPosition.top, left: dialogPosition.left}); + $('.overlay').fadeIn('fast', function() { + // Load the document on hidden iframe (see bindChild method). + self.load(self.options.url); + }); + + // Close the overlay if the background is clicked. + $('.overlay-shadow, .ui-dialog, .ui-widget-overlay, .ui-dialog-titlebar').bind('click', function() { + try { + self.close(false); + } + catch (e) {} + return false; + }); + + self.isOpen = true; + }, + beforeclose: function() { + if (self.beforeCloseEnabled) { + return true; + } + if (!self.beforeCloseIsBusy) { + self.beforeCloseIsBusy = true; + setTimeout(function() { self.close(false); }, 1); + } + return false; + }, + close: function() { + $(document).unbind('keydown.overlay-event'); + $('.overlay .ui-dialog-titlebar-close').unbind('keydown.overlay-event'); + try { + self.iframe.$element.remove(); + self.iframe.$container.dialog('destroy').remove(); + } catch(e) {}; + delete self.iframe.documentSize; + delete self.iframe.Drupal; + delete self.iframe.$element; + delete self.iframe.$container; + if (self.beforeCloseEnabled) { + delete self.beforeCloseEnabled; + } + if (self.beforeCloseIsBusy) { + delete self.beforeCloseIsBusy; + } + self.isOpen = false; + } + }); +}; + +/** + * Load the given URL into the dialog iframe. + */ +Drupal.overlay.load = function(url) { + var self = this; + var iframe = self.iframe.$element.get(0); + // Get the document object of the iframe window. + // @see http://xkr.us/articles/dom/iframe-document/ + var doc = (iframe.contentWindow || iframe.contentDocument); + if (doc.document) { + doc = doc.document; + } + doc.location.replace(url); +}; + +/** + * Check if the dialog can be closed. + */ +Drupal.overlay.canClose = function() { + var self = this; + if (!self.isOpen) { + return false; + } + return true; +}; + +/** + * Close the overlay. + */ +Drupal.overlay.close = function(statusMessages) { + var self = this; + + // Check if the dialog can be closed. + if (!self.canClose()) { + delete self.beforeCloseIsBusy; + return false; + } + + // Hide and destroy the dialog. + function closeDialog() { + // Prevent double execution when close is requested more than once. + if (!self.isObject(self.iframe.$container)) { + return; + } + self.beforeCloseEnabled = true; + self.iframe.$container.dialog('close'); + if ($.isFunction(self.options.onOverlayClose)) { + self.options.onOverlayClose(statusMessages); + } + } + if (!self.isObject(self.iframe.$element) || !self.iframe.$element.size() || !self.iframe.$element.is(':visible')) { + closeDialog(); + } + else { + self.iframe.$element.fadeOut('fast', function() { + $('.overlay').animate({height: 'hide', opacity: 'hide'}, closeDialog); + }); + } + return true; +}; + +Drupal.overlay.redirect = function(link) { + if (link.indexOf('http') != 0 && link.indexOf('https') != 0) { + var absolute = location.href.match(/https?:\/\/[^\/]*/)[0]; + link = absolute + link; + } + location.href = link; + return true; +} + +/** + * Bind the child window. + */ +Drupal.overlay.bindChild = function(iFrameWindow, isClosing) { + var self = this; + var $iFrameWindow = iFrameWindow.jQuery; + var $iFrameDocument = $iFrameWindow(iFrameWindow.document); + self.iframe.Drupal = iFrameWindow.Drupal; + + // We are done if the child window is closing. + if (isClosing) { + return; + } + + // Update the dialog title with the child window title. + $('.overlay .ui-dialog-title').html($iFrameDocument.attr('title')); + + // Remove any existing tabs. + $('.overlay .ui-dialog-titlebar ul').remove(); + + // Setting tabIndex makes the div focusable. + // Setting outline to 0 prevents a border on focus in Mozilla. + // Inspired by ui.dialog initialization code. + $iFrameDocument.attr('tabIndex', -1).css('outline', 0); + + $('.ui-dialog-titlebar-close-bg').animate({opacity: 1}, 'fast'); + + // Perform animation to show the iframe element. + self.iframe.$element.fadeIn('fast', function() { + // @todo: Watch for experience in the way we compute the size of the + // iframed document. There are many ways to do it, and none of them + // seem to be perfect. Note though, that the size of the iframe itself + // may affect the size of the child document, specially on fluid layouts. + // If you get in trouble, then I would suggest to choose a known dialog + // size and disable the option autoFit. + self.iframe.documentSize = {width: $iFrameDocument.width(), height: $iFrameWindow('body').height() + 25 }; + + // Adjust overlay to fit the iframe content? + if (self.options.autoFit) { + self.resize(self.iframe.documentSize); + } + + // Try to enhance keyboard based navigation of the overlay. + // Logic inspired by the open() method in ui.dialog.js, and + // http://wiki.codetalks.org/wiki/index.php/Docs/Keyboard_navigable_JS_widgets + + // Get a reference to the close button. + var $closeButton = $('.overlay .ui-dialog-titlebar-close'); + + // Search tabbable elements on the iframed document to speed up related + // keyboard events. + // @todo: Do we need to provide a method to update these references when + // AJAX requests update the DOM on the child document? + var $iFrameTabbables = $iFrameWindow(':tabbable:not(form)'); + var $firstTabbable = $iFrameTabbables.filter(':first'); + var $lastTabbable = $iFrameTabbables.filter(':last'); + + // Set focus to the first tabbable element in the content area or the + // first button. If there are no tabbable elements, set focus on the + // close button of the dialog itself. + if (!$firstTabbable.focus().size()) { + $iFrameDocument.focus(); + } + + // Unbind keyboard event handlers that may have been enabled previously. + $(document).unbind('keydown.overlay-event'); + $closeButton.unbind('keydown.overlay-event'); + + // When the focus leaves the close button, then we want to jump to the + // first/last inner tabbable element of the child window. + $closeButton.bind('keydown.overlay-event', function(event) { + if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) { + var $target = (event.shiftKey ? $lastTabbable : $firstTabbable); + if (!$target.size()) { + $target = $iFrameDocument; + } + setTimeout(function() { $target.focus(); }, 10); + return false; + } + }); + + // When the focus leaves the child window, then drive the focus to the + // close button of the dialog. + $iFrameDocument.bind('keydown.overlay-event', function(event) { + if (event.keyCode) { + if (event.keyCode == $.ui.keyCode.TAB) { + if (event.shiftKey && event.target == $firstTabbable.get(0)) { + setTimeout(function() { $closeButton.focus(); }, 10); + return false; + } + else if (!event.shiftKey && event.target == $lastTabbable.get(0)) { + setTimeout(function() { $closeButton.focus(); }, 10); + return false; + } + } + else if (event.keyCode == $.ui.keyCode.ESCAPE) { + setTimeout(function() { self.close(false); }, 10); + return false; + } + } + }); + + // When the focus is captured by the parent document, then try + // to drive the focus back to the first tabbable element, or the + // close button of the dialog (default). + $(document).bind('keydown.overlay-event', function(event) { + if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) { + setTimeout(function() { + if (!$iFrameWindow(':tabbable:not(form):first').focus().size()) { + $closeButton.focus(); + } + }, 10); + return false; + } + }); + + var tabs = $iFrameDocument.find('ul.horizontal-tabs-panes, ul.primary').get(0); + + // If there are tabs in the page, move them to the titlebar. + if (typeof tabs != 'undefined') { + $('.ui-dialog-titlebar').append($(tabs).remove().get(0)); + if ($(tabs).is('.primary')) { + $(tabs).find('a').addClass('to-overlay').removeClass('overlay-processed'); + Drupal.attachBehaviors($(tabs)); + } + // Remove any classes from the list element to avoid theme styles + // clashing with our styling. + $(tabs).removeAttr('class'); + } + }); +}; + +/** + * Unbind the child window. + */ +Drupal.overlay.unbindChild = function(iFrameWindow) { + var self = this; + + // Prevent memory leaks by explicitly unbinding keyboard event handler + // on the child document. + iFrameWindow.jQuery(iFrameWindow.document).unbind('keydown.overlay-event'); + + // Change the overlay title. + $('.overlay .ui-dialog-title').html(Drupal.t('Please, wait...')); + + // Hide the iframe element. + self.iframe.$element.fadeOut('fast'); +}; + +/** + * Check if the given variable is an object. + */ +Drupal.overlay.isObject = function(something) { + return (something !== null && typeof something === 'object'); +}; + +/** + * Sanitize dialog size. + */ +Drupal.overlay.sanitizeSize = function(size) { + var width, height; + var $window = $(window); + var minWidth = 300, maxWidth = parseInt($window.width() * .92); + if (typeof size.width != 'number') { + width = maxWidth; + } + else if (size.width < minWidth || size.width > maxWidth) { + width = Math.min(maxWidth, Math.max(minWidth, size.width)); + } + else { + width = size.width; + } + var minHeight = 100, maxHeight = parseInt($window.height() * .92); + if (typeof size.height != 'number') { + height = maxHeight; + } + else if (size.height < minHeight) { + // Do not consider maxHeight, only set up to be at least the minimal height. + height = Math.max(minHeight, size.height); + } + else { + height = size.height; + } + return {width: width, height: height}; +}; + +/** + * Compute position to center horizontally and on viewport top vertically. + */ +Drupal.overlay.computePosition = function($element, elementSize) { + var $window = $(window); + // Consider the possibly displayed admin toolbar. + var $toolbar = $('#toolbar'); + var toolbarHeight = $toolbar ? $toolbar.height() : 0; + var position = { + left: Math.max(0, parseInt(($window.width() - elementSize.width) / 2)), + top: toolbarHeight + 20 + }; + + // Reset the scroll to the top of the window so that the overlay is visible again. + window.scrollTo(0, 0); + return position; +}; + +/** + * Resize overlay. + */ +Drupal.overlay.resize = function(size) { + var self = this; + + // Compute frame and dialog size based on requested document size. + var titleBarHeight = $('.overlay .ui-dialog-titlebar').outerHeight(true); + var frameSize = self.sanitizeSize(size); + var dialogSize = $.extend({}, frameSize); + dialogSize.height += titleBarHeight + 15; + + // Compute position on viewport. + var dialogPosition = self.computePosition($('.overlay'), dialogSize); + + var animationOptions = $.extend(dialogSize, dialogPosition); + + // Perform the resize animation. + $('.overlay').animate(animationOptions, 'fast', function() { + // Proceed only if the dialog still exists. + if (self.isObject(self.iframe.$element) && self.isObject(self.iframe.$container)) { + // Resize the iframe element and container. + $('.overlay').width(dialogSize.width).height(dialogSize.height); + self.iframe.$container.width(frameSize.width).height(frameSize.height); + self.iframe.$element.width(frameSize.width).height(frameSize.height); + $('.overlay-shadow-right').height(frameSize.height); + + // Animate shadows and the close button + $('.overlay-shadow', $(this)).animate({opacity:1}, 'slow'); + + // Update the dialog size so that UI internals are aware of the change. + self.iframe.$container.dialog('option', {width: dialogSize.width, height: dialogSize.height}); + + // Keep the dim background grow or shrink with the dialog. + $('.ui-widget-overlay').height($(document).height()); + + // Animate body opacity, so we fade in the page page as it loads in. + $(self.iframe.$element.get(0)).contents().find('body.overlay').animate({opacity:1}, 'slow'); + } + }); +}; + +})(jQuery); Index: modules/overlay/overlay.info =================================================================== RCS file: modules/overlay/overlay.info diff -N modules/overlay/overlay.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/overlay/overlay.info 7 Jul 2009 12:49:07 -0000 @@ -0,0 +1,7 @@ +; $Id$ +name = Administration overlay +description = Displays the Drupal administration interface in an overlay. +package = Core +version = VERSION +core = 7.x +files[] = overlay.module Index: modules/overlay/overlay.module =================================================================== RCS file: modules/overlay/overlay.module diff -N modules/overlay/overlay.module --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/overlay/overlay.module 28 Jul 2009 12:56:33 -0000 @@ -0,0 +1,227 @@ + $value) { + if (strpos($key, '#') !== 0 && !in_array($key, array('content', 'help'))) { + unset($variables['page'][$key]); + } + } + // Add overlay class, so themes can react to being displayed in the overlay. + $variables['classes_array'][] = 'overlay'; + // Do not include site name or slogan in the overlay title. + $variables['head_title'] = drupal_get_title(); + } +} + +/** + * Form after build callback. + * + * Ok, all hook_form_alter() have been processed. Now, if someone has enabled + * the global variable $GLOBALS['overlay_page_template'], then we want to + * scan the form structure in search of elements with submit handlers. + * + * @see _form_builder_handle_input_element() + * @see _form_builder_ie_cleanup() + * @see form_execute_handlers() + * @see form_builder() + * + * @ingroup forms + */ +function overlay_form_after_build($form, &$form_state) { + #if (isset($_GET['render']) && $_GET['render'] == 'overlay') { + // 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 set $form_state['redirect'] is set to FALSE + // if the API overlay_close_dialog() 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'; + } + #} + return $form; +} + +/** + * Generic form submit handler. + * + * When we are requested to close an overlay, we don't want Form API to + * perform any redirection once the submitted form has been processed. + * + * When $form_state['redirect'] is set to FALSE, then Form API will simply + * re-render the form with the values still in its fields. And this is all + * we need to output the javascript that will tell the parent window to close + * the child dialog. + * + * @ingroup forms + */ +function overlay_form_submit($form, &$form_state) { + // Close the overlay in the lack of a redirect, or if specifically requested + // to do so. + if (overlay_close_dialog() || empty($form_state['redirect'])) { + $settings = array( + 'overlayChild' => array( + 'closeOverlay' => TRUE, + 'statusMessages' => theme('status_messages'), + 'redirect' => $form_state['redirect'] ? url($form_state['redirect']) : $form_state['redirect'], + ), + ); + drupal_add_js($settings, array('type' => 'setting')); + // Print the page with no content to avoid a flash of the refilled form. + print theme('page', ''); + drupal_page_footer(); + exit; + } + elseif (is_array($form_state['redirect'])) { + if (!empty($form_state['redirect'][1])) { + if (is_array($form_state['redirect'][1])) { + // Query is an array, add our marker as a key. + $form_state['redirect'][1]['render'] = 'overlay'; + } + else { + // Query is not an array, add our marker as string. + $form_state['redirect'][1] .= '&render=overlay'; + } + } + else { + // Empty or is not set, but parent is array. Add array. + $form_state['redirect'][1] = array('render' => 'overlay'); + } + } + else { + // Not an array. Save the path value and add our own marker. + $form_state['redirect'] = array($form_state['redirect'], array('render' => 'overlay')); + } +} + +/** + * Set overlay mode and add proper Javascript and styles to the page. + * + * @ingroup overlay_api + */ +function overlay_mode($mode = NULL) { + global $base_path; + $overlay_mode = &drupal_static(__FUNCTION__); + + // Make sure external resources are not included more than once. Also return + // current mode, if no mode was specified. + if (isset($overlay_mode) || !isset($mode)) { + return $overlay_mode; + } + $overlay_mode = $mode; + $module_path = drupal_get_path('module', 'overlay'); + + switch($mode) { + case OVERLAY_PARENT: + // Add required jQuery UI elements. Note that we don't use + // drupal_add_library() here, since we have no use for the CSS files added + // by the library. + drupal_add_js('misc/ui/ui.core.js', array('weight' => JS_LIBRARY + 5)); + drupal_add_js('misc/ui/ui.dialog.js', array('weight' => JS_LIBRARY + 6)); + + drupal_add_css($module_path . '/overlay-parent.css'); + drupal_add_js($module_path . '/overlay-parent.js'); + + $settings = array( + 'overlay' => array( + 'shadowPath' => $base_path . drupal_get_path('module', 'overlay') . '/images/ovrly-shdw-bt-lt.png', + ), + ); + drupal_add_js($settings, array('type' => 'setting')); + break; + + case OVERLAY_CHILD: + // Disable admin toolbar, which is something child windows don't need. + module_invoke('toolbar', 'suppress', TRUE); + + // This is required to get access to jQuery UI extensions to jQuery itself, + // such as the ':focusable' and ':tabbable' selectors. No need for the whole + // library, so not using drupal_add_library(). + drupal_add_js('misc/ui/ui.core.js', array('weight' => JS_LIBRARY + 5)); + + // Add javascript to the child page. + drupal_add_js($module_path . '/overlay-child.js'); + break; + } + return $overlay_mode; +} + +/** + * Callback to close the overlay dialog. + * + * @param $set + * If set, will set the current close dialog mode to the given boolean. + * @return + * The current overlay close dialog mode, TRUE if the overlay should close or + * FALSE if it should not (default). + */ +function overlay_close_dialog($set = NULL) { + $close = &drupal_static(__FUNCTION__, FALSE); + if (isset($set)) { + $close = $set; + } + return $close; +} Index: modules/node/node.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.admin.inc,v retrieving revision 1.59 diff -u -p -r1.59 node.admin.inc --- modules/node/node.admin.inc 21 Jul 2009 01:36:51 -0000 1.59 +++ modules/node/node.admin.inc 28 Jul 2009 11:15:25 -0000 @@ -451,6 +451,9 @@ function node_admin_nodes() { foreach ($result as $node) { $nodes[$node->nid] = ''; $options = empty($node->language) ? array() : array('language' => $languages[$node->language]); + // Set a class to flag to the overlay, if present, not to open the link in + // the overlay. + $options['attributes']['class'] = 'overlay-escape'; $form['title'][$node->nid] = array('#markup' => l($node->title, 'node/' . $node->nid, $options) . ' ' . theme('mark', node_mark($node->nid, $node->changed))); $form['name'][$node->nid] = array('#markup' => check_plain(node_type_get_name($node))); $form['username'][$node->nid] = array('#markup' => theme('username', $node)); Index: modules/node/node.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v retrieving revision 1.71 diff -u -p -r1.71 node.pages.inc --- modules/node/node.pages.inc 20 Jul 2009 22:18:53 -0000 1.71 +++ modules/node/node.pages.inc 28 Jul 2009 12:47:21 -0000 @@ -395,6 +395,15 @@ function theme_node_preview($node) { function node_form_submit($form, &$form_state) { global $user; + // Run all submit callbacks except the overlay, which we require to run later + // if present. + if ($key = array_search('overlay_form_submit', $form['#submit'])) { + unset($form['#submit'][$key]); + } + if ($key = array_search('overlay_form_submit', $form_state['submit_handlers'])) { + unset($form['submit_handlers'][$key]); + } + $node = node_form_submit_build_node($form, $form_state); $insert = empty($node->nid); node_save($node); @@ -414,6 +423,11 @@ function node_form_submit($form, &$form_ unset($form_state['rebuild']); $form_state['nid'] = $node->nid; $form_state['redirect'] = 'node/' . $node->nid; + // If the overlay module is enabled, use the overlay module's API to close + // the overlay. + if (module_exists('overlay')) { + overlay_close_dialog(TRUE); + } } else { // In the unlikely case something went wrong on save, the node will be Index: profiles/default/default.info =================================================================== RCS file: /cvs/drupal/drupal/profiles/default/default.info,v retrieving revision 1.1 diff -u -p -r1.1 default.info --- profiles/default/default.info 15 Jul 2009 02:08:41 -0000 1.1 +++ profiles/default/default.info 28 Jul 2009 09:50:36 -0000 @@ -14,3 +14,4 @@ dependencies[] = taxonomy dependencies[] = dblog dependencies[] = search dependencies[] = toolbar +dependencies[] = overlay