cms/drupal/modules/overlay/overlay-parent.js
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 /**
       
     2  * @file
       
     3  * Attaches the behaviors for the Overlay parent pages.
       
     4  */
       
     5 
       
     6 (function ($) {
       
     7 
       
     8 /**
       
     9  * Open the overlay, or load content into it, when an admin link is clicked.
       
    10  */
       
    11 Drupal.behaviors.overlayParent = {
       
    12   attach: function (context, settings) {
       
    13     if (Drupal.overlay.isOpen) {
       
    14       Drupal.overlay.makeDocumentUntabbable(context);
       
    15     }
       
    16 
       
    17     if (this.processed) {
       
    18       return;
       
    19     }
       
    20     this.processed = true;
       
    21 
       
    22     $(window)
       
    23       // When the hash (URL fragment) changes, open the overlay if needed.
       
    24       .bind('hashchange.drupal-overlay', $.proxy(Drupal.overlay, 'eventhandlerOperateByURLFragment'))
       
    25       // Trigger the hashchange handler once, after the page is loaded, so that
       
    26       // permalinks open the overlay.
       
    27       .triggerHandler('hashchange.drupal-overlay');
       
    28 
       
    29     $(document)
       
    30       // Instead of binding a click event handler to every link we bind one to
       
    31       // the document and only handle events that bubble up. This allows other
       
    32       // scripts to bind their own handlers to links and also to prevent
       
    33       // overlay's handling.
       
    34       .bind('click.drupal-overlay mouseup.drupal-overlay', $.proxy(Drupal.overlay, 'eventhandlerOverrideLink'));
       
    35   }
       
    36 };
       
    37 
       
    38 /**
       
    39  * Overlay object for parent windows.
       
    40  *
       
    41  * Events
       
    42  * Overlay triggers a number of events that can be used by other scripts.
       
    43  * - drupalOverlayOpen: This event is triggered when the overlay is opened.
       
    44  * - drupalOverlayBeforeClose: This event is triggered when the overlay attempts
       
    45  *   to close. If an event handler returns false, the close will be prevented.
       
    46  * - drupalOverlayClose: This event is triggered when the overlay is closed.
       
    47  * - drupalOverlayBeforeLoad: This event is triggered right before a new URL
       
    48  *   is loaded into the overlay.
       
    49  * - drupalOverlayReady: This event is triggered when the DOM of the overlay
       
    50  *   child document is fully loaded.
       
    51  * - drupalOverlayLoad: This event is triggered when the overlay is finished
       
    52  *   loading.
       
    53  * - drupalOverlayResize: This event is triggered when the overlay is being
       
    54  *   resized to match the parent window.
       
    55  */
       
    56 Drupal.overlay = Drupal.overlay || {
       
    57   isOpen: false,
       
    58   isOpening: false,
       
    59   isClosing: false,
       
    60   isLoading: false
       
    61 };
       
    62 
       
    63 Drupal.overlay.prototype = {};
       
    64 
       
    65 /**
       
    66  * Open the overlay.
       
    67  *
       
    68  * @param url
       
    69  *   The URL of the page to open in the overlay.
       
    70  *
       
    71  * @return
       
    72  *   TRUE if the overlay was opened, FALSE otherwise.
       
    73  */
       
    74 Drupal.overlay.open = function (url) {
       
    75   // Just one overlay is allowed.
       
    76   if (this.isOpen || this.isOpening) {
       
    77     return this.load(url);
       
    78   }
       
    79   this.isOpening = true;
       
    80   // Store the original document title.
       
    81   this.originalTitle = document.title;
       
    82 
       
    83   // Create the dialog and related DOM elements.
       
    84   this.create();
       
    85 
       
    86   this.isOpening = false;
       
    87   this.isOpen = true;
       
    88   $(document.documentElement).addClass('overlay-open');
       
    89   this.makeDocumentUntabbable();
       
    90 
       
    91   // Allow other scripts to respond to this event.
       
    92   $(document).trigger('drupalOverlayOpen');
       
    93 
       
    94   return this.load(url);
       
    95 };
       
    96 
       
    97 /**
       
    98  * Create the underlying markup and behaviors for the overlay.
       
    99  */
       
   100 Drupal.overlay.create = function () {
       
   101   this.$container = $(Drupal.theme('overlayContainer'))
       
   102     .appendTo(document.body);
       
   103 
       
   104   // Overlay uses transparent iframes that cover the full parent window.
       
   105   // When the overlay is open the scrollbar of the parent window is hidden.
       
   106   // Because some browsers show a white iframe background for a short moment
       
   107   // while loading a page into an iframe, overlay uses two iframes. By loading
       
   108   // the page in a hidden (inactive) iframe the user doesn't see the white
       
   109   // background. When the page is loaded the active and inactive iframes
       
   110   // are switched.
       
   111   this.activeFrame = this.$iframeA = $(Drupal.theme('overlayElement'))
       
   112     .appendTo(this.$container);
       
   113 
       
   114   this.inactiveFrame = this.$iframeB = $(Drupal.theme('overlayElement'))
       
   115     .appendTo(this.$container);
       
   116 
       
   117   this.$iframeA.bind('load.drupal-overlay', { self: this.$iframeA[0], sibling: this.$iframeB }, $.proxy(this, 'loadChild'));
       
   118   this.$iframeB.bind('load.drupal-overlay', { self: this.$iframeB[0], sibling: this.$iframeA }, $.proxy(this, 'loadChild'));
       
   119 
       
   120   // Add a second class "drupal-overlay-open" to indicate these event handlers
       
   121   // should only be bound when the overlay is open.
       
   122   var eventClass = '.drupal-overlay.drupal-overlay-open';
       
   123   $(window)
       
   124     .bind('resize' + eventClass, $.proxy(this, 'eventhandlerOuterResize'));
       
   125   $(document)
       
   126     .bind('drupalOverlayLoad' + eventClass, $.proxy(this, 'eventhandlerOuterResize'))
       
   127     .bind('drupalOverlayReady' + eventClass +
       
   128           ' drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerSyncURLFragment'))
       
   129     .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRefreshPage'))
       
   130     .bind('drupalOverlayBeforeClose' + eventClass +
       
   131           ' drupalOverlayBeforeLoad' + eventClass +
       
   132           ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent'));
       
   133 
       
   134   if ($('.overlay-displace-top, .overlay-displace-bottom').length) {
       
   135     $(document)
       
   136       .bind('drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerAlterDisplacedElements'))
       
   137       .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRestoreDisplacedElements'));
       
   138   }
       
   139 };
       
   140 
       
   141 /**
       
   142  * Load the given URL into the overlay iframe.
       
   143  *
       
   144  * Use this method to change the URL being loaded in the overlay if it is
       
   145  * already open.
       
   146  *
       
   147  * @return
       
   148  *   TRUE if URL is loaded into the overlay, FALSE otherwise.
       
   149  */
       
   150 Drupal.overlay.load = function (url) {
       
   151   if (!this.isOpen) {
       
   152     return false;
       
   153   }
       
   154 
       
   155   // Allow other scripts to respond to this event.
       
   156   $(document).trigger('drupalOverlayBeforeLoad');
       
   157 
       
   158   $(document.documentElement).addClass('overlay-loading');
       
   159 
       
   160   // The contentDocument property is not supported in IE until IE8.
       
   161   var iframeDocument = this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document;
       
   162 
       
   163   // location.replace doesn't create a history entry. location.href does.
       
   164   // In this case, we want location.replace, as we're creating the history
       
   165   // entry using URL fragments.
       
   166   iframeDocument.location.replace(url);
       
   167 
       
   168   return true;
       
   169 };
       
   170 
       
   171 /**
       
   172  * Close the overlay and remove markup related to it from the document.
       
   173  *
       
   174  * @return
       
   175  *   TRUE if the overlay was closed, FALSE otherwise.
       
   176  */
       
   177 Drupal.overlay.close = function () {
       
   178   // Prevent double execution when close is requested more than once.
       
   179   if (!this.isOpen || this.isClosing) {
       
   180     return false;
       
   181   }
       
   182 
       
   183   // Allow other scripts to respond to this event.
       
   184   var event = $.Event('drupalOverlayBeforeClose');
       
   185   $(document).trigger(event);
       
   186   // If a handler returned false, the close will be prevented.
       
   187   if (event.isDefaultPrevented()) {
       
   188     return false;
       
   189   }
       
   190 
       
   191   this.isClosing = true;
       
   192   this.isOpen = false;
       
   193   $(document.documentElement).removeClass('overlay-open');
       
   194   // Restore the original document title.
       
   195   document.title = this.originalTitle;
       
   196   this.makeDocumentTabbable();
       
   197 
       
   198   // Allow other scripts to respond to this event.
       
   199   $(document).trigger('drupalOverlayClose');
       
   200 
       
   201   // When the iframe is still loading don't destroy it immediately but after
       
   202   // the content is loaded (see Drupal.overlay.loadChild).
       
   203   if (!this.isLoading) {
       
   204     this.destroy();
       
   205     this.isClosing = false;
       
   206   }
       
   207   return true;
       
   208 };
       
   209 
       
   210 /**
       
   211  * Destroy the overlay.
       
   212  */
       
   213 Drupal.overlay.destroy = function () {
       
   214   $([document, window]).unbind('.drupal-overlay-open');
       
   215   this.$container.remove();
       
   216 
       
   217   this.$container = null;
       
   218   this.$iframeA = null;
       
   219   this.$iframeB = null;
       
   220 
       
   221   this.iframeWindow = null;
       
   222 };
       
   223 
       
   224 /**
       
   225  * Redirect the overlay parent window to the given URL.
       
   226  *
       
   227  * @param url
       
   228  *   Can be an absolute URL or a relative link to the domain root.
       
   229  */
       
   230 Drupal.overlay.redirect = function (url) {
       
   231   // Create a native Link object, so we can use its object methods.
       
   232   var link = $(url.link(url)).get(0);
       
   233 
       
   234   // If the link is already open, force the hashchange event to simulate reload.
       
   235   if (window.location.href == link.href) {
       
   236     $(window).triggerHandler('hashchange.drupal-overlay');
       
   237   }
       
   238 
       
   239   window.location.href = link.href;
       
   240   return true;
       
   241 };
       
   242 
       
   243 /**
       
   244  * Bind the child window.
       
   245  *
       
   246  * Note that this function is fired earlier than Drupal.overlay.loadChild.
       
   247  */
       
   248 Drupal.overlay.bindChild = function (iframeWindow, isClosing) {
       
   249   this.iframeWindow = iframeWindow;
       
   250 
       
   251   // We are done if the child window is closing.
       
   252   if (isClosing || this.isClosing || !this.isOpen) {
       
   253     return;
       
   254   }
       
   255 
       
   256   // Allow other scripts to respond to this event.
       
   257   $(document).trigger('drupalOverlayReady');
       
   258 };
       
   259 
       
   260 /**
       
   261  * Event handler: load event handler for the overlay iframe.
       
   262  *
       
   263  * @param event
       
   264  *   Event being triggered, with the following restrictions:
       
   265  *   - event.type: load
       
   266  *   - event.currentTarget: iframe
       
   267  */
       
   268 Drupal.overlay.loadChild = function (event) {
       
   269   var iframe = event.data.self;
       
   270   var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
       
   271   var iframeWindow = iframeDocument.defaultView || iframeDocument.parentWindow;
       
   272   if (iframeWindow.location == 'about:blank') {
       
   273     return;
       
   274   }
       
   275 
       
   276   this.isLoading = false;
       
   277   $(document.documentElement).removeClass('overlay-loading');
       
   278   event.data.sibling.removeClass('overlay-active').attr({ 'tabindex': -1 });
       
   279 
       
   280   // Only continue when overlay is still open and not closing.
       
   281   if (this.isOpen && !this.isClosing) {
       
   282     // And child document is an actual overlayChild.
       
   283     if (iframeWindow.Drupal && iframeWindow.Drupal.overlayChild) {
       
   284       // Replace the document title with title of iframe.
       
   285       document.title = iframeWindow.document.title;
       
   286 
       
   287       this.activeFrame = $(iframe)
       
   288         .addClass('overlay-active')
       
   289         // Add a title attribute to the iframe for accessibility.
       
   290         .attr('title', Drupal.t('@title dialog', { '@title': iframeWindow.jQuery('#overlay-title').text() })).removeAttr('tabindex');
       
   291       this.inactiveFrame = event.data.sibling;
       
   292 
       
   293       // Load an empty document into the inactive iframe.
       
   294       (this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document).location.replace('about:blank');
       
   295 
       
   296       // Move the focus to just before the "skip to main content" link inside
       
   297       // the overlay.
       
   298       this.activeFrame.focus();
       
   299       var skipLink = iframeWindow.jQuery('a:first');
       
   300       Drupal.overlay.setFocusBefore(skipLink, iframeWindow.document);
       
   301 
       
   302       // Allow other scripts to respond to this event.
       
   303       $(document).trigger('drupalOverlayLoad');
       
   304     }
       
   305     else {
       
   306       window.location = iframeWindow.location.href.replace(/([?&]?)render=overlay&?/g, '$1').replace(/\?$/, '');
       
   307     }
       
   308   }
       
   309   else {
       
   310     this.destroy();
       
   311   }
       
   312 };
       
   313 
       
   314 /**
       
   315  * Creates a placeholder element to receive document focus.
       
   316  *
       
   317  * Setting the document focus to a link will make it visible, even if it's a
       
   318  * "skip to main content" link that should normally be visible only when the
       
   319  * user tabs to it. This function can be used to set the document focus to
       
   320  * just before such an invisible link.
       
   321  *
       
   322  * @param $element
       
   323  *   The jQuery element that should receive focus on the next tab press.
       
   324  * @param document
       
   325  *   The iframe window element to which the placeholder should be added. The
       
   326  *   placeholder element has to be created inside the same iframe as the element
       
   327  *   it precedes, to keep IE happy. (http://bugs.jquery.com/ticket/4059)
       
   328  */
       
   329 Drupal.overlay.setFocusBefore = function ($element, document) {
       
   330   // Create an anchor inside the placeholder document.
       
   331   var placeholder = document.createElement('a');
       
   332   var $placeholder = $(placeholder).addClass('element-invisible').attr('href', '#');
       
   333   // Put the placeholder where it belongs, and set the document focus to it.
       
   334   $placeholder.insertBefore($element);
       
   335   $placeholder.focus();
       
   336   // Make the placeholder disappear as soon as it loses focus, so that it
       
   337   // doesn't appear in the tab order again.
       
   338   $placeholder.one('blur', function () {
       
   339     $(this).remove();
       
   340   });
       
   341 };
       
   342 
       
   343 /**
       
   344  * Check if the given link is in the administrative section of the site.
       
   345  *
       
   346  * @param url
       
   347  *   The URL to be tested.
       
   348  *
       
   349  * @return boolean
       
   350  *   TRUE if the URL represents an administrative link, FALSE otherwise.
       
   351  */
       
   352 Drupal.overlay.isAdminLink = function (url) {
       
   353   if (!Drupal.urlIsLocal(url)) {
       
   354     return false;
       
   355   }
       
   356 
       
   357   var path = this.getPath(url);
       
   358 
       
   359   // Turn the list of administrative paths into a regular expression.
       
   360   if (!this.adminPathRegExp) {
       
   361     var prefix = '';
       
   362     if (Drupal.settings.overlay.pathPrefixes.length) {
       
   363       // Allow path prefixes used for language negatiation followed by slash,
       
   364       // and the empty string.
       
   365       prefix = '(' + Drupal.settings.overlay.pathPrefixes.join('/|') + '/|)';
       
   366     }
       
   367     var adminPaths = '^' + prefix + '(' + Drupal.settings.overlay.paths.admin.replace(/\s+/g, '|') + ')$';
       
   368     var nonAdminPaths = '^' + prefix + '(' + Drupal.settings.overlay.paths.non_admin.replace(/\s+/g, '|') + ')$';
       
   369     adminPaths = adminPaths.replace(/\*/g, '.*');
       
   370     nonAdminPaths = nonAdminPaths.replace(/\*/g, '.*');
       
   371     this.adminPathRegExp = new RegExp(adminPaths);
       
   372     this.nonAdminPathRegExp = new RegExp(nonAdminPaths);
       
   373   }
       
   374 
       
   375   return this.adminPathRegExp.exec(path) && !this.nonAdminPathRegExp.exec(path);
       
   376 };
       
   377 
       
   378 /**
       
   379  * Determine whether a link is external to the site.
       
   380  *
       
   381  * Deprecated. Use Drupal.urlIsLocal() instead.
       
   382  *
       
   383  * @param url
       
   384  *   The URL to be tested.
       
   385  *
       
   386  * @return boolean
       
   387  *   TRUE if the URL is external to the site, FALSE otherwise.
       
   388  */
       
   389 Drupal.overlay.isExternalLink = function (url) {
       
   390   return !Drupal.urlIsLocal(url);
       
   391 };
       
   392 
       
   393 /**
       
   394  * Constructs an internal URL (relative to this site) from the provided path.
       
   395  *
       
   396  * For example, if the provided path is 'admin' and the site is installed at
       
   397  * http://example.com/drupal, this function will return '/drupal/admin'.
       
   398  *
       
   399  * @param path
       
   400  *   The internal path, without any leading slash.
       
   401  *
       
   402  * @return
       
   403  *   The internal URL derived from the provided path, or null if a valid
       
   404  *   internal path cannot be constructed (for example, if an attempt to create
       
   405  *   an external link is detected).
       
   406  */
       
   407 Drupal.overlay.getInternalUrl = function (path) {
       
   408   var url = Drupal.settings.basePath + path;
       
   409   if (Drupal.urlIsLocal(url)) {
       
   410     return url;
       
   411   }
       
   412 };
       
   413 
       
   414 /**
       
   415  * Event handler: resizes overlay according to the size of the parent window.
       
   416  *
       
   417  * @param event
       
   418  *   Event being triggered, with the following restrictions:
       
   419  *   - event.type: any
       
   420  *   - event.currentTarget: any
       
   421  */
       
   422 Drupal.overlay.eventhandlerOuterResize = function (event) {
       
   423   // Proceed only if the overlay still exists.
       
   424   if (!(this.isOpen || this.isOpening) || this.isClosing || !this.iframeWindow) {
       
   425     return;
       
   426   }
       
   427 
       
   428   // IE6 uses position:absolute instead of position:fixed.
       
   429   if (typeof document.body.style.maxHeight != 'string') {
       
   430     this.activeFrame.height($(window).height());
       
   431   }
       
   432 
       
   433   // Allow other scripts to respond to this event.
       
   434   $(document).trigger('drupalOverlayResize');
       
   435 };
       
   436 
       
   437 /**
       
   438  * Event handler: resizes displaced elements so they won't overlap the scrollbar
       
   439  * of overlay's iframe.
       
   440  *
       
   441  * @param event
       
   442  *   Event being triggered, with the following restrictions:
       
   443  *   - event.type: any
       
   444  *   - event.currentTarget: any
       
   445  */
       
   446 Drupal.overlay.eventhandlerAlterDisplacedElements = function (event) {
       
   447   // Proceed only if the overlay still exists.
       
   448   if (!(this.isOpen || this.isOpening) || this.isClosing || !this.iframeWindow) {
       
   449     return;
       
   450   }
       
   451 
       
   452   $(this.iframeWindow.document.body).css({
       
   453     marginTop: Drupal.overlay.getDisplacement('top'),
       
   454     marginBottom: Drupal.overlay.getDisplacement('bottom')
       
   455   })
       
   456   // IE7 isn't reflowing the document immediately.
       
   457   // @todo This might be fixed in a cleaner way.
       
   458   .addClass('overlay-trigger-reflow').removeClass('overlay-trigger-reflow');
       
   459 
       
   460   var documentHeight = this.iframeWindow.document.body.clientHeight;
       
   461   var documentWidth = this.iframeWindow.document.body.clientWidth;
       
   462   // IE6 doesn't support maxWidth, use width instead.
       
   463   var maxWidthName = (typeof document.body.style.maxWidth == 'string') ? 'maxWidth' : 'width';
       
   464 
       
   465   if (Drupal.overlay.leftSidedScrollbarOffset === undefined && $(document.documentElement).attr('dir') === 'rtl') {
       
   466     // We can't use element.clientLeft to detect whether scrollbars are placed
       
   467     // on the left side of the element when direction is set to "rtl" as most
       
   468     // browsers dont't support it correctly.
       
   469     // http://www.gtalbot.org/BugzillaSection/DocumentAllDHTMLproperties.html
       
   470     // There seems to be absolutely no way to detect whether the scrollbar
       
   471     // is on the left side in Opera; always expect scrollbar to be on the left.
       
   472     if ($.browser.opera) {
       
   473       Drupal.overlay.leftSidedScrollbarOffset = document.documentElement.clientWidth - this.iframeWindow.document.documentElement.clientWidth + this.iframeWindow.document.documentElement.clientLeft;
       
   474     }
       
   475     else if (this.iframeWindow.document.documentElement.clientLeft) {
       
   476       Drupal.overlay.leftSidedScrollbarOffset = this.iframeWindow.document.documentElement.clientLeft;
       
   477     }
       
   478     else {
       
   479       var el1 = $('<div style="direction: rtl; overflow: scroll;"></div>').appendTo(document.body);
       
   480       var el2 = $('<div></div>').appendTo(el1);
       
   481       Drupal.overlay.leftSidedScrollbarOffset = parseInt(el2[0].offsetLeft - el1[0].offsetLeft);
       
   482       el1.remove();
       
   483     }
       
   484   }
       
   485 
       
   486   // Consider any element that should be visible above the overlay (such as
       
   487   // a toolbar).
       
   488   $('.overlay-displace-top, .overlay-displace-bottom').each(function () {
       
   489     var data = $(this).data();
       
   490     var maxWidth = documentWidth;
       
   491     // In IE, Shadow filter makes element to overlap the scrollbar with 1px.
       
   492     if (this.filters && this.filters.length && this.filters.item('DXImageTransform.Microsoft.Shadow')) {
       
   493       maxWidth -= 1;
       
   494     }
       
   495 
       
   496     if (Drupal.overlay.leftSidedScrollbarOffset) {
       
   497       $(this).css('left', Drupal.overlay.leftSidedScrollbarOffset);
       
   498     }
       
   499 
       
   500     // Prevent displaced elements overlapping window's scrollbar.
       
   501     var currentMaxWidth = parseInt($(this).css(maxWidthName));
       
   502     if ((data.drupalOverlay && data.drupalOverlay.maxWidth) || isNaN(currentMaxWidth) || currentMaxWidth > maxWidth || currentMaxWidth <= 0) {
       
   503       $(this).css(maxWidthName, maxWidth);
       
   504       (data.drupalOverlay = data.drupalOverlay || {}).maxWidth = true;
       
   505     }
       
   506 
       
   507     // Use a more rigorous approach if the displaced element still overlaps
       
   508     // window's scrollbar; clip the element on the right.
       
   509     var offset = $(this).offset();
       
   510     var offsetRight = offset.left + $(this).outerWidth();
       
   511     if ((data.drupalOverlay && data.drupalOverlay.clip) || offsetRight > maxWidth) {
       
   512       if (Drupal.overlay.leftSidedScrollbarOffset) {
       
   513         $(this).css('clip', 'rect(auto, auto, ' + (documentHeight - offset.top) + 'px, ' + (Drupal.overlay.leftSidedScrollbarOffset + 2) + 'px)');
       
   514       }
       
   515       else {
       
   516         $(this).css('clip', 'rect(auto, ' + (maxWidth - offset.left) + 'px, ' + (documentHeight - offset.top) + 'px, auto)');
       
   517       }
       
   518       (data.drupalOverlay = data.drupalOverlay || {}).clip = true;
       
   519     }
       
   520   });
       
   521 };
       
   522 
       
   523 /**
       
   524  * Event handler: restores size of displaced elements as they were before
       
   525  * overlay was opened.
       
   526  *
       
   527  * @param event
       
   528  *   Event being triggered, with the following restrictions:
       
   529  *   - event.type: any
       
   530  *   - event.currentTarget: any
       
   531  */
       
   532 Drupal.overlay.eventhandlerRestoreDisplacedElements = function (event) {
       
   533   var $displacedElements = $('.overlay-displace-top, .overlay-displace-bottom');
       
   534   try {
       
   535     $displacedElements.css({ maxWidth: '', clip: '' });
       
   536   }
       
   537   // IE bug that doesn't allow unsetting style.clip (http://dev.jquery.com/ticket/6512).
       
   538   catch (err) {
       
   539     $displacedElements.attr('style', function (index, attr) {
       
   540       return attr.replace(/clip\s*:\s*rect\([^)]+\);?/i, '');
       
   541     });
       
   542   }
       
   543 };
       
   544 
       
   545 /**
       
   546  * Event handler: overrides href of administrative links to be opened in
       
   547  * the overlay.
       
   548  *
       
   549  * This click event handler should be bound to any document (for example the
       
   550  * overlay iframe) of which you want links to open in the overlay.
       
   551  *
       
   552  * @param event
       
   553  *   Event being triggered, with the following restrictions:
       
   554  *   - event.type: click, mouseup
       
   555  *   - event.currentTarget: document
       
   556  *
       
   557  * @see Drupal.overlayChild.behaviors.addClickHandler
       
   558  */
       
   559 Drupal.overlay.eventhandlerOverrideLink = function (event) {
       
   560   // In some browsers the click event isn't fired for right-clicks. Use the
       
   561   // mouseup event for right-clicks and the click event for everything else.
       
   562   if ((event.type == 'click' && event.button == 2) || (event.type == 'mouseup' && event.button != 2)) {
       
   563     return;
       
   564   }
       
   565 
       
   566   var $target = $(event.target);
       
   567 
       
   568   // Only continue if clicked target (or one of its parents) is a link.
       
   569   if (!$target.is('a')) {
       
   570     $target = $target.closest('a');
       
   571     if (!$target.length) {
       
   572       return;
       
   573     }
       
   574   }
       
   575 
       
   576   // Never open links in the overlay that contain the overlay-exclude class.
       
   577   if ($target.hasClass('overlay-exclude')) {
       
   578     return;
       
   579   }
       
   580 
       
   581   // Close the overlay when the link contains the overlay-close class.
       
   582   if ($target.hasClass('overlay-close')) {
       
   583     // Clearing the overlay URL fragment will close the overlay.
       
   584     $.bbq.removeState('overlay');
       
   585     return;
       
   586   }
       
   587 
       
   588   var target = $target[0];
       
   589   var href = target.href;
       
   590   // Only handle links that have an href attribute and use the HTTP(S) protocol.
       
   591   if (href != undefined && href != '' && target.protocol.match(/^https?\:/)) {
       
   592     var anchor = href.replace(target.ownerDocument.location.href, '');
       
   593     // Skip anchor links.
       
   594     if (anchor.length == 0 || anchor.charAt(0) == '#') {
       
   595       return;
       
   596     }
       
   597     // Open admin links in the overlay.
       
   598     else if (this.isAdminLink(href)) {
       
   599       // If the link contains the overlay-restore class and the overlay-context
       
   600       // state is set, also update the parent window's location.
       
   601       var parentLocation = ($target.hasClass('overlay-restore') && typeof $.bbq.getState('overlay-context') == 'string')
       
   602         ? this.getInternalUrl($.bbq.getState('overlay-context'))
       
   603         : null;
       
   604       href = this.fragmentizeLink($target.get(0), parentLocation);
       
   605       // Only override default behavior when left-clicking and user is not
       
   606       // pressing the ALT, CTRL, META (Command key on the Macintosh keyboard)
       
   607       // or SHIFT key.
       
   608       if (event.button == 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
       
   609         // Redirect to a fragmentized href. This will trigger a hashchange event.
       
   610         this.redirect(href);
       
   611         // Prevent default action and further propagation of the event.
       
   612         return false;
       
   613       }
       
   614       // Otherwise alter clicked link's href. This is being picked up by
       
   615       // the default action handler.
       
   616       else {
       
   617         $target
       
   618           // Restore link's href attribute on blur or next click.
       
   619           .one('blur mousedown', { target: target, href: target.href }, function (event) { $(event.data.target).attr('href', event.data.href); })
       
   620           .attr('href', href);
       
   621       }
       
   622     }
       
   623     // Non-admin links should close the overlay and open in the main window,
       
   624     // which is the default action for a link. We only need to handle them
       
   625     // if the overlay is open and the clicked link is inside the overlay iframe.
       
   626     else if (this.isOpen && target.ownerDocument === this.iframeWindow.document) {
       
   627       // Open external links in the immediate parent of the frame, unless the
       
   628       // link already has a different target.
       
   629       if (target.hostname != window.location.hostname) {
       
   630         if (!$target.attr('target')) {
       
   631           $target.attr('target', '_parent');
       
   632         }
       
   633       }
       
   634       else {
       
   635         // Add the overlay-context state to the link, so "overlay-restore" links
       
   636         // can restore the context.
       
   637         if ($target[0].hash) {
       
   638           // Leave links with an existing fragment alone. Adding an extra
       
   639           // parameter to a link like "node/1#section-1" breaks the link.
       
   640         }
       
   641         else {
       
   642           // For links with no existing fragment, add the overlay context.
       
   643           $target.attr('href', $.param.fragment(href, { 'overlay-context': this.getPath(window.location) + window.location.search }));
       
   644         }
       
   645 
       
   646         // When the link has a destination query parameter and that destination
       
   647         // is an admin link we need to fragmentize it. This will make it reopen
       
   648         // in the overlay.
       
   649         var params = $.deparam.querystring(href);
       
   650         if (params.destination && this.isAdminLink(params.destination)) {
       
   651           var fragmentizedDestination = $.param.fragment(this.getPath(window.location), { overlay: params.destination });
       
   652           $target.attr('href', $.param.querystring(href, { destination: fragmentizedDestination }));
       
   653         }
       
   654 
       
   655         // Make the link open in the immediate parent of the frame, unless the
       
   656         // link already has a different target.
       
   657         if (!$target.attr('target')) {
       
   658           $target.attr('target', '_parent');
       
   659         }
       
   660       }
       
   661     }
       
   662   }
       
   663 };
       
   664 
       
   665 /**
       
   666  * Event handler: opens or closes the overlay based on the current URL fragment.
       
   667  *
       
   668  * @param event
       
   669  *   Event being triggered, with the following restrictions:
       
   670  *   - event.type: hashchange
       
   671  *   - event.currentTarget: document
       
   672  */
       
   673 Drupal.overlay.eventhandlerOperateByURLFragment = function (event) {
       
   674   // If we changed the hash to reflect an internal redirect in the overlay,
       
   675   // its location has already been changed, so don't do anything.
       
   676   if ($.data(window.location, window.location.href) === 'redirect') {
       
   677     $.data(window.location, window.location.href, null);
       
   678     return;
       
   679   }
       
   680 
       
   681   // Get the overlay URL from the current URL fragment.
       
   682   var internalUrl = null;
       
   683   var state = $.bbq.getState('overlay');
       
   684   if (state) {
       
   685     internalUrl = this.getInternalUrl(state);
       
   686   }
       
   687   if (internalUrl) {
       
   688     // Append render variable, so the server side can choose the right
       
   689     // rendering and add child frame code to the page if needed.
       
   690     var url = $.param.querystring(internalUrl, { render: 'overlay' });
       
   691 
       
   692     this.open(url);
       
   693     this.resetActiveClass(this.getPath(Drupal.settings.basePath + state));
       
   694   }
       
   695   // If there is no overlay URL in the fragment and the overlay is (still)
       
   696   // open, close the overlay.
       
   697   else if (this.isOpen && !this.isClosing) {
       
   698     this.close();
       
   699     this.resetActiveClass(this.getPath(window.location));
       
   700   }
       
   701 };
       
   702 
       
   703 /**
       
   704  * Event handler: makes sure the internal overlay URL is reflected in the parent
       
   705  * URL fragment.
       
   706  *
       
   707  * Normally the parent URL fragment determines the overlay location. However, if
       
   708  * the overlay redirects internally, the parent doesn't get informed, and the
       
   709  * parent URL fragment will be out of date. This is a sanity check to make
       
   710  * sure we're in the right place.
       
   711  *
       
   712  * The parent URL fragment is also not updated automatically when overlay's
       
   713  * open, close or load functions are used directly (instead of through
       
   714  * eventhandlerOperateByURLFragment).
       
   715  *
       
   716  * @param event
       
   717  *   Event being triggered, with the following restrictions:
       
   718  *   - event.type: drupalOverlayReady, drupalOverlayClose
       
   719  *   - event.currentTarget: document
       
   720  */
       
   721 Drupal.overlay.eventhandlerSyncURLFragment = function (event) {
       
   722   if (this.isOpen) {
       
   723     var expected = $.bbq.getState('overlay');
       
   724     // This is just a sanity check, so we're comparing paths, not query strings.
       
   725     if (this.getPath(Drupal.settings.basePath + expected) != this.getPath(this.iframeWindow.document.location)) {
       
   726       // There may have been a redirect inside the child overlay window that the
       
   727       // parent wasn't aware of. Update the parent URL fragment appropriately.
       
   728       var newLocation = Drupal.overlay.fragmentizeLink(this.iframeWindow.document.location);
       
   729       // Set a 'redirect' flag on the new location so the hashchange event handler
       
   730       // knows not to change the overlay's content.
       
   731       $.data(window.location, newLocation, 'redirect');
       
   732       // Use location.replace() so we don't create an extra history entry.
       
   733       window.location.replace(newLocation);
       
   734     }
       
   735   }
       
   736   else {
       
   737     $.bbq.removeState('overlay');
       
   738   }
       
   739 };
       
   740 
       
   741 /**
       
   742  * Event handler: if the child window suggested that the parent refresh on
       
   743  * close, force a page refresh.
       
   744  *
       
   745  * @param event
       
   746  *   Event being triggered, with the following restrictions:
       
   747  *   - event.type: drupalOverlayClose
       
   748  *   - event.currentTarget: document
       
   749  */
       
   750 Drupal.overlay.eventhandlerRefreshPage = function (event) {
       
   751   if (Drupal.overlay.refreshPage) {
       
   752     window.location.reload(true);
       
   753   }
       
   754 };
       
   755 
       
   756 /**
       
   757  * Event handler: dispatches events to the overlay document.
       
   758  *
       
   759  * @param event
       
   760  *   Event being triggered, with the following restrictions:
       
   761  *   - event.type: any
       
   762  *   - event.currentTarget: any
       
   763  */
       
   764 Drupal.overlay.eventhandlerDispatchEvent = function (event) {
       
   765   if (this.iframeWindow && this.iframeWindow.document) {
       
   766     this.iframeWindow.jQuery(this.iframeWindow.document).trigger(event);
       
   767   }
       
   768 };
       
   769 
       
   770 /**
       
   771  * Make a regular admin link into a URL that will trigger the overlay to open.
       
   772  *
       
   773  * @param link
       
   774  *   A JavaScript Link object (i.e. an <a> element).
       
   775  * @param parentLocation
       
   776  *   (optional) URL to override the parent window's location with.
       
   777  *
       
   778  * @return
       
   779  *   A URL that will trigger the overlay (in the form
       
   780  *   /node/1#overlay=admin/config).
       
   781  */
       
   782 Drupal.overlay.fragmentizeLink = function (link, parentLocation) {
       
   783   // Don't operate on links that are already overlay-ready.
       
   784   var params = $.deparam.fragment(link.href);
       
   785   if (params.overlay) {
       
   786     return link.href;
       
   787   }
       
   788 
       
   789   // Determine the link's original destination. Set ignorePathFromQueryString to
       
   790   // true to prevent transforming this link into a clean URL while clean URLs
       
   791   // may be disabled.
       
   792   var path = this.getPath(link, true);
       
   793   // Preserve existing query and fragment parameters in the URL, except for
       
   794   // "render=overlay" which is re-added in Drupal.overlay.eventhandlerOperateByURLFragment.
       
   795   var destination = path + link.search.replace(/&?render=overlay/, '').replace(/\?$/, '') + link.hash;
       
   796 
       
   797   // Assemble and return the overlay-ready link.
       
   798   return $.param.fragment(parentLocation || window.location.href, { overlay: destination });
       
   799 };
       
   800 
       
   801 /**
       
   802  * Refresh any regions of the page that are displayed outside the overlay.
       
   803  *
       
   804  * @param data
       
   805  *   An array of objects with information on the page regions to be refreshed.
       
   806  *   For each object, the key is a CSS class identifying the region to be
       
   807  *   refreshed, and the value represents the section of the Drupal $page array
       
   808  *   corresponding to this region.
       
   809  */
       
   810 Drupal.overlay.refreshRegions = function (data) {
       
   811   $.each(data, function () {
       
   812     var region_info = this;
       
   813     $.each(region_info, function (regionClass) {
       
   814       var regionName = region_info[regionClass];
       
   815       var regionSelector = '.' + regionClass;
       
   816       // Allow special behaviors to detach.
       
   817       Drupal.detachBehaviors($(regionSelector));
       
   818       $.get(Drupal.settings.basePath + Drupal.settings.overlay.ajaxCallback + '/' + regionName, function (newElement) {
       
   819         $(regionSelector).replaceWith($(newElement));
       
   820         Drupal.attachBehaviors($(regionSelector), Drupal.settings);
       
   821       });
       
   822     });
       
   823   });
       
   824 };
       
   825 
       
   826 /**
       
   827  * Reset the active class on links in displaced elements according to
       
   828  * given path.
       
   829  *
       
   830  * @param activePath
       
   831  *   Path to match links against.
       
   832  */
       
   833 Drupal.overlay.resetActiveClass = function(activePath) {
       
   834   var self = this;
       
   835   var windowDomain = window.location.protocol + window.location.hostname;
       
   836 
       
   837   $('.overlay-displace-top, .overlay-displace-bottom')
       
   838   .find('a[href]')
       
   839   // Remove active class from all links in displaced elements.
       
   840   .removeClass('active')
       
   841   // Add active class to links that match activePath.
       
   842   .each(function () {
       
   843     var linkDomain = this.protocol + this.hostname;
       
   844     var linkPath = self.getPath(this);
       
   845 
       
   846     // A link matches if it is part of the active trail of activePath, except
       
   847     // for frontpage links.
       
   848     if (linkDomain == windowDomain && (activePath + '/').indexOf(linkPath + '/') === 0 && (linkPath !== '' || activePath === '')) {
       
   849       $(this).addClass('active');
       
   850     }
       
   851   });
       
   852 };
       
   853 
       
   854 /**
       
   855  * Helper function to get the (corrected) Drupal path of a link.
       
   856  *
       
   857  * @param link
       
   858  *   Link object or string to get the Drupal path from.
       
   859  * @param ignorePathFromQueryString
       
   860  *   Boolean whether to ignore path from query string if path appears empty.
       
   861  *
       
   862  * @return
       
   863  *   The Drupal path.
       
   864  */
       
   865 Drupal.overlay.getPath = function (link, ignorePathFromQueryString) {
       
   866   if (typeof link == 'string') {
       
   867     // Create a native Link object, so we can use its object methods.
       
   868     link = $(link.link(link)).get(0);
       
   869   }
       
   870 
       
   871   var path = link.pathname;
       
   872   // Ensure a leading slash on the path, omitted in some browsers.
       
   873   if (path.charAt(0) != '/') {
       
   874     path = '/' + path;
       
   875   }
       
   876   path = path.replace(new RegExp(Drupal.settings.basePath + '(?:index.php)?'), '');
       
   877   if (path == '' && !ignorePathFromQueryString) {
       
   878     // If the path appears empty, it might mean the path is represented in the
       
   879     // query string (clean URLs are not used).
       
   880     var match = new RegExp('([?&])q=(.+)([&#]|$)').exec(link.search);
       
   881     if (match && match.length == 4) {
       
   882       path = match[2];
       
   883     }
       
   884   }
       
   885 
       
   886   return path;
       
   887 };
       
   888 
       
   889 /**
       
   890  * Get the total displacement of given region.
       
   891  *
       
   892  * @param region
       
   893  *   Region name. Either "top" or "bottom".
       
   894  *
       
   895  * @return
       
   896  *   The total displacement of given region in pixels.
       
   897  */
       
   898 Drupal.overlay.getDisplacement = function (region) {
       
   899   var displacement = 0;
       
   900   var lastDisplaced = $('.overlay-displace-' + region + ':last');
       
   901   if (lastDisplaced.length) {
       
   902     displacement = lastDisplaced.offset().top + lastDisplaced.outerHeight();
       
   903 
       
   904     // In modern browsers (including IE9), when box-shadow is defined, use the
       
   905     // normal height.
       
   906     var cssBoxShadowValue = lastDisplaced.css('box-shadow');
       
   907     var boxShadow = (typeof cssBoxShadowValue !== 'undefined' && cssBoxShadowValue !== 'none');
       
   908     // In IE8 and below, we use the shadow filter to apply box-shadow styles to
       
   909     // the toolbar. It adds some extra height that we need to remove.
       
   910     if (!boxShadow && /DXImageTransform\.Microsoft\.Shadow/.test(lastDisplaced.css('filter'))) {
       
   911       displacement -= lastDisplaced[0].filters.item('DXImageTransform.Microsoft.Shadow').strength;
       
   912       displacement = Math.max(0, displacement);
       
   913     }
       
   914   }
       
   915   return displacement;
       
   916 };
       
   917 
       
   918 /**
       
   919  * Makes elements outside the overlay unreachable via the tab key.
       
   920  *
       
   921  * @param context
       
   922  *   The part of the DOM that should have its tabindexes changed. Defaults to
       
   923  *   the entire page.
       
   924  */
       
   925 Drupal.overlay.makeDocumentUntabbable = function (context) {
       
   926   // Manipulating tabindexes for the entire document is unacceptably slow in IE6
       
   927   // and IE7, so in those browsers, the underlying page will still be reachable
       
   928   // via the tab key. However, we still make the links within the Disable
       
   929   // message unreachable, because the same message also exists within the
       
   930   // child document. The duplicate copy in the underlying document is only for
       
   931   // assisting screen-reader users navigating the document with reading commands
       
   932   // that follow markup order rather than tab order.
       
   933   if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) {
       
   934     $('#overlay-disable-message a', context).attr('tabindex', -1);
       
   935     return;
       
   936   }
       
   937 
       
   938   context = context || document.body;
       
   939   var $overlay, $tabbable, $hasTabindex;
       
   940 
       
   941   // Determine which elements on the page already have a tabindex.
       
   942   $hasTabindex = $('[tabindex] :not(.overlay-element)', context);
       
   943   // Record the tabindex for each element, so we can restore it later.
       
   944   $hasTabindex.each(Drupal.overlay._recordTabindex);
       
   945   // Add the tabbable elements from the current context to any that we might
       
   946   // have previously recorded.
       
   947   Drupal.overlay._hasTabindex = $hasTabindex.add(Drupal.overlay._hasTabindex);
       
   948 
       
   949   // Set tabindex to -1 on everything outside the overlay and toolbars, so that
       
   950   // the underlying page is unreachable.
       
   951 
       
   952   // By default, browsers make a, area, button, input, object, select, textarea,
       
   953   // and iframe elements reachable via the tab key.
       
   954   $tabbable = $('a, area, button, input, object, select, textarea, iframe');
       
   955   // If another element (like a div) has a tabindex, it's also tabbable.
       
   956   $tabbable = $tabbable.add($hasTabindex);
       
   957   // Leave links inside the overlay and toolbars alone.
       
   958   $overlay = $('.overlay-element, #overlay-container, .overlay-displace-top, .overlay-displace-bottom').find('*');
       
   959   $tabbable = $tabbable.not($overlay);
       
   960   // We now have a list of everything in the underlying document that could
       
   961   // possibly be reachable via the tab key. Make it all unreachable.
       
   962   $tabbable.attr('tabindex', -1);
       
   963 };
       
   964 
       
   965 /**
       
   966  * Restores the original tabindex value of a group of elements.
       
   967  *
       
   968  * @param context
       
   969  *   The part of the DOM that should have its tabindexes restored. Defaults to
       
   970  *   the entire page.
       
   971  */
       
   972 Drupal.overlay.makeDocumentTabbable = function (context) {
       
   973   // Manipulating tabindexes is unacceptably slow in IE6 and IE7. In those
       
   974   // browsers, the underlying page was never made unreachable via tab, so
       
   975   // there is no work to be done here.
       
   976   if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) {
       
   977     return;
       
   978   }
       
   979 
       
   980   var $needsTabindex;
       
   981   context = context || document.body;
       
   982 
       
   983   // Make the underlying document tabbable again by removing all existing
       
   984   // tabindex attributes.
       
   985   var $tabindex = $('[tabindex]', context);
       
   986   if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8) {
       
   987     // removeAttr('tabindex') is broken in IE6-7, but the DOM function
       
   988     // removeAttribute works.
       
   989     var i;
       
   990     var length = $tabindex.length;
       
   991     for (i = 0; i < length; i++) {
       
   992       $tabindex[i].removeAttribute('tabIndex');
       
   993     }
       
   994   }
       
   995   else {
       
   996     $tabindex.removeAttr('tabindex');
       
   997   }
       
   998 
       
   999   // Restore the tabindex attributes that existed before the overlay was opened.
       
  1000   $needsTabindex = $(Drupal.overlay._hasTabindex, context);
       
  1001   $needsTabindex.each(Drupal.overlay._restoreTabindex);
       
  1002   Drupal.overlay._hasTabindex = Drupal.overlay._hasTabindex.not($needsTabindex);
       
  1003 };
       
  1004 
       
  1005 /**
       
  1006  * Record the tabindex for an element, using $.data.
       
  1007  *
       
  1008  * Meant to be used as a jQuery.fn.each callback.
       
  1009  */
       
  1010 Drupal.overlay._recordTabindex = function () {
       
  1011   var $element = $(this);
       
  1012   var tabindex = $(this).attr('tabindex');
       
  1013   $element.data('drupalOverlayOriginalTabIndex', tabindex);
       
  1014 };
       
  1015 
       
  1016 /**
       
  1017  * Restore an element's original tabindex.
       
  1018  *
       
  1019  * Meant to be used as a jQuery.fn.each callback.
       
  1020  */
       
  1021 Drupal.overlay._restoreTabindex = function () {
       
  1022   var $element = $(this);
       
  1023   var tabindex = $element.data('drupalOverlayOriginalTabIndex');
       
  1024   $element.attr('tabindex', tabindex);
       
  1025 };
       
  1026 
       
  1027 /**
       
  1028  * Theme function to create the overlay iframe element.
       
  1029  */
       
  1030 Drupal.theme.prototype.overlayContainer = function () {
       
  1031   return '<div id="overlay-container"><div class="overlay-modal-background"></div></div>';
       
  1032 };
       
  1033 
       
  1034 /**
       
  1035  * Theme function to create an overlay iframe element.
       
  1036  */
       
  1037 Drupal.theme.prototype.overlayElement = function (url) {
       
  1038   return '<iframe class="overlay-element" frameborder="0" scrolling="auto" allowtransparency="true"></iframe>';
       
  1039 };
       
  1040 
       
  1041 })(jQuery);