diff -r 07239de796bb -r e756a8c72c3d cms/drupal/modules/overlay/overlay-parent.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cms/drupal/modules/overlay/overlay-parent.js Fri Sep 08 12:04:06 2017 +0200 @@ -0,0 +1,1041 @@ +/** + * @file + * Attaches the behaviors for the Overlay parent pages. + */ + +(function ($) { + +/** + * Open the overlay, or load content into it, when an admin link is clicked. + */ +Drupal.behaviors.overlayParent = { + attach: function (context, settings) { + if (Drupal.overlay.isOpen) { + Drupal.overlay.makeDocumentUntabbable(context); + } + + if (this.processed) { + return; + } + this.processed = true; + + $(window) + // When the hash (URL fragment) changes, open the overlay if needed. + .bind('hashchange.drupal-overlay', $.proxy(Drupal.overlay, 'eventhandlerOperateByURLFragment')) + // Trigger the hashchange handler once, after the page is loaded, so that + // permalinks open the overlay. + .triggerHandler('hashchange.drupal-overlay'); + + $(document) + // Instead of binding a click event handler to every link we bind one to + // the document and only handle events that bubble up. This allows other + // scripts to bind their own handlers to links and also to prevent + // overlay's handling. + .bind('click.drupal-overlay mouseup.drupal-overlay', $.proxy(Drupal.overlay, 'eventhandlerOverrideLink')); + } +}; + +/** + * Overlay object for parent windows. + * + * Events + * Overlay triggers a number of events that can be used by other scripts. + * - drupalOverlayOpen: This event is triggered when the overlay is opened. + * - drupalOverlayBeforeClose: This event is triggered when the overlay attempts + * to close. If an event handler returns false, the close will be prevented. + * - drupalOverlayClose: This event is triggered when the overlay is closed. + * - drupalOverlayBeforeLoad: This event is triggered right before a new URL + * is loaded into the overlay. + * - drupalOverlayReady: This event is triggered when the DOM of the overlay + * child document is fully loaded. + * - drupalOverlayLoad: This event is triggered when the overlay is finished + * loading. + * - drupalOverlayResize: This event is triggered when the overlay is being + * resized to match the parent window. + */ +Drupal.overlay = Drupal.overlay || { + isOpen: false, + isOpening: false, + isClosing: false, + isLoading: false +}; + +Drupal.overlay.prototype = {}; + +/** + * Open the overlay. + * + * @param url + * The URL of the page to open in the overlay. + * + * @return + * TRUE if the overlay was opened, FALSE otherwise. + */ +Drupal.overlay.open = function (url) { + // Just one overlay is allowed. + if (this.isOpen || this.isOpening) { + return this.load(url); + } + this.isOpening = true; + // Store the original document title. + this.originalTitle = document.title; + + // Create the dialog and related DOM elements. + this.create(); + + this.isOpening = false; + this.isOpen = true; + $(document.documentElement).addClass('overlay-open'); + this.makeDocumentUntabbable(); + + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayOpen'); + + return this.load(url); +}; + +/** + * Create the underlying markup and behaviors for the overlay. + */ +Drupal.overlay.create = function () { + this.$container = $(Drupal.theme('overlayContainer')) + .appendTo(document.body); + + // Overlay uses transparent iframes that cover the full parent window. + // When the overlay is open the scrollbar of the parent window is hidden. + // Because some browsers show a white iframe background for a short moment + // while loading a page into an iframe, overlay uses two iframes. By loading + // the page in a hidden (inactive) iframe the user doesn't see the white + // background. When the page is loaded the active and inactive iframes + // are switched. + this.activeFrame = this.$iframeA = $(Drupal.theme('overlayElement')) + .appendTo(this.$container); + + this.inactiveFrame = this.$iframeB = $(Drupal.theme('overlayElement')) + .appendTo(this.$container); + + this.$iframeA.bind('load.drupal-overlay', { self: this.$iframeA[0], sibling: this.$iframeB }, $.proxy(this, 'loadChild')); + this.$iframeB.bind('load.drupal-overlay', { self: this.$iframeB[0], sibling: this.$iframeA }, $.proxy(this, 'loadChild')); + + // Add a second class "drupal-overlay-open" to indicate these event handlers + // should only be bound when the overlay is open. + var eventClass = '.drupal-overlay.drupal-overlay-open'; + $(window) + .bind('resize' + eventClass, $.proxy(this, 'eventhandlerOuterResize')); + $(document) + .bind('drupalOverlayLoad' + eventClass, $.proxy(this, 'eventhandlerOuterResize')) + .bind('drupalOverlayReady' + eventClass + + ' drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerSyncURLFragment')) + .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRefreshPage')) + .bind('drupalOverlayBeforeClose' + eventClass + + ' drupalOverlayBeforeLoad' + eventClass + + ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent')); + + if ($('.overlay-displace-top, .overlay-displace-bottom').length) { + $(document) + .bind('drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerAlterDisplacedElements')) + .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRestoreDisplacedElements')); + } +}; + +/** + * Load the given URL into the overlay iframe. + * + * Use this method to change the URL being loaded in the overlay if it is + * already open. + * + * @return + * TRUE if URL is loaded into the overlay, FALSE otherwise. + */ +Drupal.overlay.load = function (url) { + if (!this.isOpen) { + return false; + } + + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayBeforeLoad'); + + $(document.documentElement).addClass('overlay-loading'); + + // The contentDocument property is not supported in IE until IE8. + var iframeDocument = this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document; + + // location.replace doesn't create a history entry. location.href does. + // In this case, we want location.replace, as we're creating the history + // entry using URL fragments. + iframeDocument.location.replace(url); + + return true; +}; + +/** + * Close the overlay and remove markup related to it from the document. + * + * @return + * TRUE if the overlay was closed, FALSE otherwise. + */ +Drupal.overlay.close = function () { + // Prevent double execution when close is requested more than once. + if (!this.isOpen || this.isClosing) { + return false; + } + + // Allow other scripts to respond to this event. + var event = $.Event('drupalOverlayBeforeClose'); + $(document).trigger(event); + // If a handler returned false, the close will be prevented. + if (event.isDefaultPrevented()) { + return false; + } + + this.isClosing = true; + this.isOpen = false; + $(document.documentElement).removeClass('overlay-open'); + // Restore the original document title. + document.title = this.originalTitle; + this.makeDocumentTabbable(); + + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayClose'); + + // When the iframe is still loading don't destroy it immediately but after + // the content is loaded (see Drupal.overlay.loadChild). + if (!this.isLoading) { + this.destroy(); + this.isClosing = false; + } + return true; +}; + +/** + * Destroy the overlay. + */ +Drupal.overlay.destroy = function () { + $([document, window]).unbind('.drupal-overlay-open'); + this.$container.remove(); + + this.$container = null; + this.$iframeA = null; + this.$iframeB = null; + + this.iframeWindow = null; +}; + +/** + * Redirect the overlay parent window to the given URL. + * + * @param url + * Can be an absolute URL or a relative link to the domain root. + */ +Drupal.overlay.redirect = function (url) { + // Create a native Link object, so we can use its object methods. + var link = $(url.link(url)).get(0); + + // If the link is already open, force the hashchange event to simulate reload. + if (window.location.href == link.href) { + $(window).triggerHandler('hashchange.drupal-overlay'); + } + + window.location.href = link.href; + return true; +}; + +/** + * Bind the child window. + * + * Note that this function is fired earlier than Drupal.overlay.loadChild. + */ +Drupal.overlay.bindChild = function (iframeWindow, isClosing) { + this.iframeWindow = iframeWindow; + + // We are done if the child window is closing. + if (isClosing || this.isClosing || !this.isOpen) { + return; + } + + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayReady'); +}; + +/** + * Event handler: load event handler for the overlay iframe. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: load + * - event.currentTarget: iframe + */ +Drupal.overlay.loadChild = function (event) { + var iframe = event.data.self; + var iframeDocument = iframe.contentDocument || iframe.contentWindow.document; + var iframeWindow = iframeDocument.defaultView || iframeDocument.parentWindow; + if (iframeWindow.location == 'about:blank') { + return; + } + + this.isLoading = false; + $(document.documentElement).removeClass('overlay-loading'); + event.data.sibling.removeClass('overlay-active').attr({ 'tabindex': -1 }); + + // Only continue when overlay is still open and not closing. + if (this.isOpen && !this.isClosing) { + // And child document is an actual overlayChild. + if (iframeWindow.Drupal && iframeWindow.Drupal.overlayChild) { + // Replace the document title with title of iframe. + document.title = iframeWindow.document.title; + + this.activeFrame = $(iframe) + .addClass('overlay-active') + // Add a title attribute to the iframe for accessibility. + .attr('title', Drupal.t('@title dialog', { '@title': iframeWindow.jQuery('#overlay-title').text() })).removeAttr('tabindex'); + this.inactiveFrame = event.data.sibling; + + // Load an empty document into the inactive iframe. + (this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document).location.replace('about:blank'); + + // Move the focus to just before the "skip to main content" link inside + // the overlay. + this.activeFrame.focus(); + var skipLink = iframeWindow.jQuery('a:first'); + Drupal.overlay.setFocusBefore(skipLink, iframeWindow.document); + + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayLoad'); + } + else { + window.location = iframeWindow.location.href.replace(/([?&]?)render=overlay&?/g, '$1').replace(/\?$/, ''); + } + } + else { + this.destroy(); + } +}; + +/** + * Creates a placeholder element to receive document focus. + * + * Setting the document focus to a link will make it visible, even if it's a + * "skip to main content" link that should normally be visible only when the + * user tabs to it. This function can be used to set the document focus to + * just before such an invisible link. + * + * @param $element + * The jQuery element that should receive focus on the next tab press. + * @param document + * The iframe window element to which the placeholder should be added. The + * placeholder element has to be created inside the same iframe as the element + * it precedes, to keep IE happy. (http://bugs.jquery.com/ticket/4059) + */ +Drupal.overlay.setFocusBefore = function ($element, document) { + // Create an anchor inside the placeholder document. + var placeholder = document.createElement('a'); + var $placeholder = $(placeholder).addClass('element-invisible').attr('href', '#'); + // Put the placeholder where it belongs, and set the document focus to it. + $placeholder.insertBefore($element); + $placeholder.focus(); + // Make the placeholder disappear as soon as it loses focus, so that it + // doesn't appear in the tab order again. + $placeholder.one('blur', function () { + $(this).remove(); + }); +}; + +/** + * Check if the given link is in the administrative section of the site. + * + * @param url + * The URL to be tested. + * + * @return boolean + * TRUE if the URL represents an administrative link, FALSE otherwise. + */ +Drupal.overlay.isAdminLink = function (url) { + if (!Drupal.urlIsLocal(url)) { + return false; + } + + var path = this.getPath(url); + + // Turn the list of administrative paths into a regular expression. + if (!this.adminPathRegExp) { + var prefix = ''; + if (Drupal.settings.overlay.pathPrefixes.length) { + // Allow path prefixes used for language negatiation followed by slash, + // and the empty string. + prefix = '(' + Drupal.settings.overlay.pathPrefixes.join('/|') + '/|)'; + } + var adminPaths = '^' + prefix + '(' + Drupal.settings.overlay.paths.admin.replace(/\s+/g, '|') + ')$'; + var nonAdminPaths = '^' + prefix + '(' + Drupal.settings.overlay.paths.non_admin.replace(/\s+/g, '|') + ')$'; + adminPaths = adminPaths.replace(/\*/g, '.*'); + nonAdminPaths = nonAdminPaths.replace(/\*/g, '.*'); + this.adminPathRegExp = new RegExp(adminPaths); + this.nonAdminPathRegExp = new RegExp(nonAdminPaths); + } + + return this.adminPathRegExp.exec(path) && !this.nonAdminPathRegExp.exec(path); +}; + +/** + * Determine whether a link is external to the site. + * + * Deprecated. Use Drupal.urlIsLocal() instead. + * + * @param url + * The URL to be tested. + * + * @return boolean + * TRUE if the URL is external to the site, FALSE otherwise. + */ +Drupal.overlay.isExternalLink = function (url) { + return !Drupal.urlIsLocal(url); +}; + +/** + * Constructs an internal URL (relative to this site) from the provided path. + * + * For example, if the provided path is 'admin' and the site is installed at + * http://example.com/drupal, this function will return '/drupal/admin'. + * + * @param path + * The internal path, without any leading slash. + * + * @return + * The internal URL derived from the provided path, or null if a valid + * internal path cannot be constructed (for example, if an attempt to create + * an external link is detected). + */ +Drupal.overlay.getInternalUrl = function (path) { + var url = Drupal.settings.basePath + path; + if (Drupal.urlIsLocal(url)) { + return url; + } +}; + +/** + * Event handler: resizes overlay according to the size of the parent window. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: any + * - event.currentTarget: any + */ +Drupal.overlay.eventhandlerOuterResize = function (event) { + // Proceed only if the overlay still exists. + if (!(this.isOpen || this.isOpening) || this.isClosing || !this.iframeWindow) { + return; + } + + // IE6 uses position:absolute instead of position:fixed. + if (typeof document.body.style.maxHeight != 'string') { + this.activeFrame.height($(window).height()); + } + + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayResize'); +}; + +/** + * Event handler: resizes displaced elements so they won't overlap the scrollbar + * of overlay's iframe. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: any + * - event.currentTarget: any + */ +Drupal.overlay.eventhandlerAlterDisplacedElements = function (event) { + // Proceed only if the overlay still exists. + if (!(this.isOpen || this.isOpening) || this.isClosing || !this.iframeWindow) { + return; + } + + $(this.iframeWindow.document.body).css({ + marginTop: Drupal.overlay.getDisplacement('top'), + marginBottom: Drupal.overlay.getDisplacement('bottom') + }) + // IE7 isn't reflowing the document immediately. + // @todo This might be fixed in a cleaner way. + .addClass('overlay-trigger-reflow').removeClass('overlay-trigger-reflow'); + + var documentHeight = this.iframeWindow.document.body.clientHeight; + var documentWidth = this.iframeWindow.document.body.clientWidth; + // IE6 doesn't support maxWidth, use width instead. + var maxWidthName = (typeof document.body.style.maxWidth == 'string') ? 'maxWidth' : 'width'; + + if (Drupal.overlay.leftSidedScrollbarOffset === undefined && $(document.documentElement).attr('dir') === 'rtl') { + // We can't use element.clientLeft to detect whether scrollbars are placed + // on the left side of the element when direction is set to "rtl" as most + // browsers dont't support it correctly. + // http://www.gtalbot.org/BugzillaSection/DocumentAllDHTMLproperties.html + // There seems to be absolutely no way to detect whether the scrollbar + // is on the left side in Opera; always expect scrollbar to be on the left. + if ($.browser.opera) { + Drupal.overlay.leftSidedScrollbarOffset = document.documentElement.clientWidth - this.iframeWindow.document.documentElement.clientWidth + this.iframeWindow.document.documentElement.clientLeft; + } + else if (this.iframeWindow.document.documentElement.clientLeft) { + Drupal.overlay.leftSidedScrollbarOffset = this.iframeWindow.document.documentElement.clientLeft; + } + else { + var el1 = $('
').appendTo(document.body); + var el2 = $('').appendTo(el1); + Drupal.overlay.leftSidedScrollbarOffset = parseInt(el2[0].offsetLeft - el1[0].offsetLeft); + el1.remove(); + } + } + + // Consider any element that should be visible above the overlay (such as + // a toolbar). + $('.overlay-displace-top, .overlay-displace-bottom').each(function () { + var data = $(this).data(); + var maxWidth = documentWidth; + // In IE, Shadow filter makes element to overlap the scrollbar with 1px. + if (this.filters && this.filters.length && this.filters.item('DXImageTransform.Microsoft.Shadow')) { + maxWidth -= 1; + } + + if (Drupal.overlay.leftSidedScrollbarOffset) { + $(this).css('left', Drupal.overlay.leftSidedScrollbarOffset); + } + + // Prevent displaced elements overlapping window's scrollbar. + var currentMaxWidth = parseInt($(this).css(maxWidthName)); + if ((data.drupalOverlay && data.drupalOverlay.maxWidth) || isNaN(currentMaxWidth) || currentMaxWidth > maxWidth || currentMaxWidth <= 0) { + $(this).css(maxWidthName, maxWidth); + (data.drupalOverlay = data.drupalOverlay || {}).maxWidth = true; + } + + // Use a more rigorous approach if the displaced element still overlaps + // window's scrollbar; clip the element on the right. + var offset = $(this).offset(); + var offsetRight = offset.left + $(this).outerWidth(); + if ((data.drupalOverlay && data.drupalOverlay.clip) || offsetRight > maxWidth) { + if (Drupal.overlay.leftSidedScrollbarOffset) { + $(this).css('clip', 'rect(auto, auto, ' + (documentHeight - offset.top) + 'px, ' + (Drupal.overlay.leftSidedScrollbarOffset + 2) + 'px)'); + } + else { + $(this).css('clip', 'rect(auto, ' + (maxWidth - offset.left) + 'px, ' + (documentHeight - offset.top) + 'px, auto)'); + } + (data.drupalOverlay = data.drupalOverlay || {}).clip = true; + } + }); +}; + +/** + * Event handler: restores size of displaced elements as they were before + * overlay was opened. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: any + * - event.currentTarget: any + */ +Drupal.overlay.eventhandlerRestoreDisplacedElements = function (event) { + var $displacedElements = $('.overlay-displace-top, .overlay-displace-bottom'); + try { + $displacedElements.css({ maxWidth: '', clip: '' }); + } + // IE bug that doesn't allow unsetting style.clip (http://dev.jquery.com/ticket/6512). + catch (err) { + $displacedElements.attr('style', function (index, attr) { + return attr.replace(/clip\s*:\s*rect\([^)]+\);?/i, ''); + }); + } +}; + +/** + * Event handler: overrides href of administrative links to be opened in + * the overlay. + * + * This click event handler should be bound to any document (for example the + * overlay iframe) of which you want links to open in the overlay. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: click, mouseup + * - event.currentTarget: document + * + * @see Drupal.overlayChild.behaviors.addClickHandler + */ +Drupal.overlay.eventhandlerOverrideLink = function (event) { + // In some browsers the click event isn't fired for right-clicks. Use the + // mouseup event for right-clicks and the click event for everything else. + if ((event.type == 'click' && event.button == 2) || (event.type == 'mouseup' && event.button != 2)) { + return; + } + + var $target = $(event.target); + + // Only continue if clicked target (or one of its parents) is a link. + if (!$target.is('a')) { + $target = $target.closest('a'); + if (!$target.length) { + return; + } + } + + // Never open links in the overlay that contain the overlay-exclude class. + if ($target.hasClass('overlay-exclude')) { + return; + } + + // Close the overlay when the link contains the overlay-close class. + if ($target.hasClass('overlay-close')) { + // Clearing the overlay URL fragment will close the overlay. + $.bbq.removeState('overlay'); + return; + } + + var target = $target[0]; + var href = target.href; + // Only handle links that have an href attribute and use the HTTP(S) protocol. + if (href != undefined && href != '' && target.protocol.match(/^https?\:/)) { + var anchor = href.replace(target.ownerDocument.location.href, ''); + // Skip anchor links. + if (anchor.length == 0 || anchor.charAt(0) == '#') { + return; + } + // Open admin links in the overlay. + else if (this.isAdminLink(href)) { + // If the link contains the overlay-restore class and the overlay-context + // state is set, also update the parent window's location. + var parentLocation = ($target.hasClass('overlay-restore') && typeof $.bbq.getState('overlay-context') == 'string') + ? this.getInternalUrl($.bbq.getState('overlay-context')) + : null; + href = this.fragmentizeLink($target.get(0), parentLocation); + // Only override default behavior when left-clicking and user is not + // pressing the ALT, CTRL, META (Command key on the Macintosh keyboard) + // or SHIFT key. + if (event.button == 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { + // Redirect to a fragmentized href. This will trigger a hashchange event. + this.redirect(href); + // Prevent default action and further propagation of the event. + return false; + } + // Otherwise alter clicked link's href. This is being picked up by + // the default action handler. + else { + $target + // Restore link's href attribute on blur or next click. + .one('blur mousedown', { target: target, href: target.href }, function (event) { $(event.data.target).attr('href', event.data.href); }) + .attr('href', href); + } + } + // Non-admin links should close the overlay and open in the main window, + // which is the default action for a link. We only need to handle them + // if the overlay is open and the clicked link is inside the overlay iframe. + else if (this.isOpen && target.ownerDocument === this.iframeWindow.document) { + // Open external links in the immediate parent of the frame, unless the + // link already has a different target. + if (target.hostname != window.location.hostname) { + if (!$target.attr('target')) { + $target.attr('target', '_parent'); + } + } + else { + // Add the overlay-context state to the link, so "overlay-restore" links + // can restore the context. + if ($target[0].hash) { + // Leave links with an existing fragment alone. Adding an extra + // parameter to a link like "node/1#section-1" breaks the link. + } + else { + // For links with no existing fragment, add the overlay context. + $target.attr('href', $.param.fragment(href, { 'overlay-context': this.getPath(window.location) + window.location.search })); + } + + // When the link has a destination query parameter and that destination + // is an admin link we need to fragmentize it. This will make it reopen + // in the overlay. + var params = $.deparam.querystring(href); + if (params.destination && this.isAdminLink(params.destination)) { + var fragmentizedDestination = $.param.fragment(this.getPath(window.location), { overlay: params.destination }); + $target.attr('href', $.param.querystring(href, { destination: fragmentizedDestination })); + } + + // Make the link open in the immediate parent of the frame, unless the + // link already has a different target. + if (!$target.attr('target')) { + $target.attr('target', '_parent'); + } + } + } + } +}; + +/** + * Event handler: opens or closes the overlay based on the current URL fragment. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: hashchange + * - event.currentTarget: document + */ +Drupal.overlay.eventhandlerOperateByURLFragment = function (event) { + // If we changed the hash to reflect an internal redirect in the overlay, + // its location has already been changed, so don't do anything. + if ($.data(window.location, window.location.href) === 'redirect') { + $.data(window.location, window.location.href, null); + return; + } + + // Get the overlay URL from the current URL fragment. + var internalUrl = null; + var state = $.bbq.getState('overlay'); + if (state) { + internalUrl = this.getInternalUrl(state); + } + if (internalUrl) { + // Append render variable, so the server side can choose the right + // rendering and add child frame code to the page if needed. + var url = $.param.querystring(internalUrl, { render: 'overlay' }); + + this.open(url); + this.resetActiveClass(this.getPath(Drupal.settings.basePath + state)); + } + // If there is no overlay URL in the fragment and the overlay is (still) + // open, close the overlay. + else if (this.isOpen && !this.isClosing) { + this.close(); + this.resetActiveClass(this.getPath(window.location)); + } +}; + +/** + * Event handler: makes sure the internal overlay URL is reflected in the parent + * URL fragment. + * + * Normally the parent URL fragment determines the overlay location. However, if + * the overlay redirects internally, the parent doesn't get informed, and the + * parent URL fragment will be out of date. This is a sanity check to make + * sure we're in the right place. + * + * The parent URL fragment is also not updated automatically when overlay's + * open, close or load functions are used directly (instead of through + * eventhandlerOperateByURLFragment). + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: drupalOverlayReady, drupalOverlayClose + * - event.currentTarget: document + */ +Drupal.overlay.eventhandlerSyncURLFragment = function (event) { + if (this.isOpen) { + var expected = $.bbq.getState('overlay'); + // This is just a sanity check, so we're comparing paths, not query strings. + if (this.getPath(Drupal.settings.basePath + expected) != this.getPath(this.iframeWindow.document.location)) { + // There may have been a redirect inside the child overlay window that the + // parent wasn't aware of. Update the parent URL fragment appropriately. + var newLocation = Drupal.overlay.fragmentizeLink(this.iframeWindow.document.location); + // Set a 'redirect' flag on the new location so the hashchange event handler + // knows not to change the overlay's content. + $.data(window.location, newLocation, 'redirect'); + // Use location.replace() so we don't create an extra history entry. + window.location.replace(newLocation); + } + } + else { + $.bbq.removeState('overlay'); + } +}; + +/** + * Event handler: if the child window suggested that the parent refresh on + * close, force a page refresh. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: drupalOverlayClose + * - event.currentTarget: document + */ +Drupal.overlay.eventhandlerRefreshPage = function (event) { + if (Drupal.overlay.refreshPage) { + window.location.reload(true); + } +}; + +/** + * Event handler: dispatches events to the overlay document. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: any + * - event.currentTarget: any + */ +Drupal.overlay.eventhandlerDispatchEvent = function (event) { + if (this.iframeWindow && this.iframeWindow.document) { + this.iframeWindow.jQuery(this.iframeWindow.document).trigger(event); + } +}; + +/** + * Make a regular admin link into a URL that will trigger the overlay to open. + * + * @param link + * A JavaScript Link object (i.e. an element). + * @param parentLocation + * (optional) URL to override the parent window's location with. + * + * @return + * A URL that will trigger the overlay (in the form + * /node/1#overlay=admin/config). + */ +Drupal.overlay.fragmentizeLink = function (link, parentLocation) { + // Don't operate on links that are already overlay-ready. + var params = $.deparam.fragment(link.href); + if (params.overlay) { + return link.href; + } + + // Determine the link's original destination. Set ignorePathFromQueryString to + // true to prevent transforming this link into a clean URL while clean URLs + // may be disabled. + var path = this.getPath(link, true); + // Preserve existing query and fragment parameters in the URL, except for + // "render=overlay" which is re-added in Drupal.overlay.eventhandlerOperateByURLFragment. + var destination = path + link.search.replace(/&?render=overlay/, '').replace(/\?$/, '') + link.hash; + + // Assemble and return the overlay-ready link. + return $.param.fragment(parentLocation || window.location.href, { overlay: destination }); +}; + +/** + * Refresh any regions of the page that are displayed outside the overlay. + * + * @param data + * An array of objects with information on the page regions to be refreshed. + * For each object, the key is a CSS class identifying the region to be + * refreshed, and the value represents the section of the Drupal $page array + * corresponding to this region. + */ +Drupal.overlay.refreshRegions = function (data) { + $.each(data, function () { + var region_info = this; + $.each(region_info, function (regionClass) { + var regionName = region_info[regionClass]; + var regionSelector = '.' + regionClass; + // Allow special behaviors to detach. + Drupal.detachBehaviors($(regionSelector)); + $.get(Drupal.settings.basePath + Drupal.settings.overlay.ajaxCallback + '/' + regionName, function (newElement) { + $(regionSelector).replaceWith($(newElement)); + Drupal.attachBehaviors($(regionSelector), Drupal.settings); + }); + }); + }); +}; + +/** + * Reset the active class on links in displaced elements according to + * given path. + * + * @param activePath + * Path to match links against. + */ +Drupal.overlay.resetActiveClass = function(activePath) { + var self = this; + var windowDomain = window.location.protocol + window.location.hostname; + + $('.overlay-displace-top, .overlay-displace-bottom') + .find('a[href]') + // Remove active class from all links in displaced elements. + .removeClass('active') + // Add active class to links that match activePath. + .each(function () { + var linkDomain = this.protocol + this.hostname; + var linkPath = self.getPath(this); + + // A link matches if it is part of the active trail of activePath, except + // for frontpage links. + if (linkDomain == windowDomain && (activePath + '/').indexOf(linkPath + '/') === 0 && (linkPath !== '' || activePath === '')) { + $(this).addClass('active'); + } + }); +}; + +/** + * Helper function to get the (corrected) Drupal path of a link. + * + * @param link + * Link object or string to get the Drupal path from. + * @param ignorePathFromQueryString + * Boolean whether to ignore path from query string if path appears empty. + * + * @return + * The Drupal path. + */ +Drupal.overlay.getPath = function (link, ignorePathFromQueryString) { + if (typeof link == 'string') { + // Create a native Link object, so we can use its object methods. + link = $(link.link(link)).get(0); + } + + var path = link.pathname; + // Ensure a leading slash on the path, omitted in some browsers. + if (path.charAt(0) != '/') { + path = '/' + path; + } + path = path.replace(new RegExp(Drupal.settings.basePath + '(?:index.php)?'), ''); + if (path == '' && !ignorePathFromQueryString) { + // If the path appears empty, it might mean the path is represented in the + // query string (clean URLs are not used). + var match = new RegExp('([?&])q=(.+)([]|$)').exec(link.search); + if (match && match.length == 4) { + path = match[2]; + } + } + + return path; +}; + +/** + * Get the total displacement of given region. + * + * @param region + * Region name. Either "top" or "bottom". + * + * @return + * The total displacement of given region in pixels. + */ +Drupal.overlay.getDisplacement = function (region) { + var displacement = 0; + var lastDisplaced = $('.overlay-displace-' + region + ':last'); + if (lastDisplaced.length) { + displacement = lastDisplaced.offset().top + lastDisplaced.outerHeight(); + + // In modern browsers (including IE9), when box-shadow is defined, use the + // normal height. + var cssBoxShadowValue = lastDisplaced.css('box-shadow'); + var boxShadow = (typeof cssBoxShadowValue !== 'undefined' && cssBoxShadowValue !== 'none'); + // In IE8 and below, we use the shadow filter to apply box-shadow styles to + // the toolbar. It adds some extra height that we need to remove. + if (!boxShadow && /DXImageTransform\.Microsoft\.Shadow/.test(lastDisplaced.css('filter'))) { + displacement -= lastDisplaced[0].filters.item('DXImageTransform.Microsoft.Shadow').strength; + displacement = Math.max(0, displacement); + } + } + return displacement; +}; + +/** + * Makes elements outside the overlay unreachable via the tab key. + * + * @param context + * The part of the DOM that should have its tabindexes changed. Defaults to + * the entire page. + */ +Drupal.overlay.makeDocumentUntabbable = function (context) { + // Manipulating tabindexes for the entire document is unacceptably slow in IE6 + // and IE7, so in those browsers, the underlying page will still be reachable + // via the tab key. However, we still make the links within the Disable + // message unreachable, because the same message also exists within the + // child document. The duplicate copy in the underlying document is only for + // assisting screen-reader users navigating the document with reading commands + // that follow markup order rather than tab order. + if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) { + $('#overlay-disable-message a', context).attr('tabindex', -1); + return; + } + + context = context || document.body; + var $overlay, $tabbable, $hasTabindex; + + // Determine which elements on the page already have a tabindex. + $hasTabindex = $('[tabindex] :not(.overlay-element)', context); + // Record the tabindex for each element, so we can restore it later. + $hasTabindex.each(Drupal.overlay._recordTabindex); + // Add the tabbable elements from the current context to any that we might + // have previously recorded. + Drupal.overlay._hasTabindex = $hasTabindex.add(Drupal.overlay._hasTabindex); + + // Set tabindex to -1 on everything outside the overlay and toolbars, so that + // the underlying page is unreachable. + + // By default, browsers make a, area, button, input, object, select, textarea, + // and iframe elements reachable via the tab key. + $tabbable = $('a, area, button, input, object, select, textarea, iframe'); + // If another element (like a div) has a tabindex, it's also tabbable. + $tabbable = $tabbable.add($hasTabindex); + // Leave links inside the overlay and toolbars alone. + $overlay = $('.overlay-element, #overlay-container, .overlay-displace-top, .overlay-displace-bottom').find('*'); + $tabbable = $tabbable.not($overlay); + // We now have a list of everything in the underlying document that could + // possibly be reachable via the tab key. Make it all unreachable. + $tabbable.attr('tabindex', -1); +}; + +/** + * Restores the original tabindex value of a group of elements. + * + * @param context + * The part of the DOM that should have its tabindexes restored. Defaults to + * the entire page. + */ +Drupal.overlay.makeDocumentTabbable = function (context) { + // Manipulating tabindexes is unacceptably slow in IE6 and IE7. In those + // browsers, the underlying page was never made unreachable via tab, so + // there is no work to be done here. + if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) { + return; + } + + var $needsTabindex; + context = context || document.body; + + // Make the underlying document tabbable again by removing all existing + // tabindex attributes. + var $tabindex = $('[tabindex]', context); + if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) { + // removeAttr('tabindex') is broken in IE6-7, but the DOM function + // removeAttribute works. + var i; + var length = $tabindex.length; + for (i = 0; i < length; i++) { + $tabindex[i].removeAttribute('tabIndex'); + } + } + else { + $tabindex.removeAttr('tabindex'); + } + + // Restore the tabindex attributes that existed before the overlay was opened. + $needsTabindex = $(Drupal.overlay._hasTabindex, context); + $needsTabindex.each(Drupal.overlay._restoreTabindex); + Drupal.overlay._hasTabindex = Drupal.overlay._hasTabindex.not($needsTabindex); +}; + +/** + * Record the tabindex for an element, using $.data. + * + * Meant to be used as a jQuery.fn.each callback. + */ +Drupal.overlay._recordTabindex = function () { + var $element = $(this); + var tabindex = $(this).attr('tabindex'); + $element.data('drupalOverlayOriginalTabIndex', tabindex); +}; + +/** + * Restore an element's original tabindex. + * + * Meant to be used as a jQuery.fn.each callback. + */ +Drupal.overlay._restoreTabindex = function () { + var $element = $(this); + var tabindex = $element.data('drupalOverlayOriginalTabIndex'); + $element.attr('tabindex', tabindex); +}; + +/** + * Theme function to create the overlay iframe element. + */ +Drupal.theme.prototype.overlayContainer = function () { + return '