Projet

Général

Profil

Paste
Télécharger (36,6 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / jquery_update / replace / misc / 1.9 / overlay-parent.js @ 651307cd

1
/**
2
 * Modified by ericduran for jQuery Update compatibility.
3
 *
4
 * Patches overlay-parent.js for jQuery 1.9.1 compatibility.
5
 */
6

    
7
/**
8
 * @file
9
 * Attaches the behaviors for the Overlay parent pages.
10
 */
11

    
12
(function ($) {
13

    
14
/**
15
 * Open the overlay, or load content into it, when an admin link is clicked.
16
 */
17
Drupal.behaviors.overlayParent = {
18
  attach: function (context, settings) {
19
    if (Drupal.overlay.isOpen) {
20
      Drupal.overlay.makeDocumentUntabbable(context);
21
    }
22

    
23
    if (this.processed) {
24
      return;
25
    }
26
    this.processed = true;
27

    
28
    $(window)
29
      // When the hash (URL fragment) changes, open the overlay if needed.
30
      .bind('hashchange.drupal-overlay', $.proxy(Drupal.overlay, 'eventhandlerOperateByURLFragment'))
31
      // Trigger the hashchange handler once, after the page is loaded, so that
32
      // permalinks open the overlay.
33
      .triggerHandler('hashchange.drupal-overlay');
34

    
35
    $(document)
36
      // Instead of binding a click event handler to every link we bind one to
37
      // the document and only handle events that bubble up. This allows other
38
      // scripts to bind their own handlers to links and also to prevent
39
      // overlay's handling.
40
      .bind('click.drupal-overlay mouseup.drupal-overlay', $.proxy(Drupal.overlay, 'eventhandlerOverrideLink'));
41
  }
42
};
43

    
44
/**
45
 * Overlay object for parent windows.
46
 *
47
 * Events
48
 * Overlay triggers a number of events that can be used by other scripts.
49
 * - drupalOverlayOpen: This event is triggered when the overlay is opened.
50
 * - drupalOverlayBeforeClose: This event is triggered when the overlay attempts
51
 *   to close. If an event handler returns false, the close will be prevented.
52
 * - drupalOverlayClose: This event is triggered when the overlay is closed.
53
 * - drupalOverlayBeforeLoad: This event is triggered right before a new URL
54
 *   is loaded into the overlay.
55
 * - drupalOverlayReady: This event is triggered when the DOM of the overlay
56
 *   child document is fully loaded.
57
 * - drupalOverlayLoad: This event is triggered when the overlay is finished
58
 *   loading.
59
 * - drupalOverlayResize: This event is triggered when the overlay is being
60
 *   resized to match the parent window.
61
 */
62
Drupal.overlay = Drupal.overlay || {
63
  isOpen: false,
64
  isOpening: false,
65
  isClosing: false,
66
  isLoading: false
67
};
68

    
69
Drupal.overlay.prototype = {};
70

    
71
/**
72
 * Open the overlay.
73
 *
74
 * @param url
75
 *   The URL of the page to open in the overlay.
76
 *
77
 * @return
78
 *   TRUE if the overlay was opened, FALSE otherwise.
79
 */
80
Drupal.overlay.open = function (url) {
81
  // Just one overlay is allowed.
82
  if (this.isOpen || this.isOpening) {
83
    return this.load(url);
84
  }
85
  this.isOpening = true;
86
  // Store the original document title.
87
  this.originalTitle = document.title;
88

    
89
  // Create the dialog and related DOM elements.
90
  this.create();
91

    
92
  this.isOpening = false;
93
  this.isOpen = true;
94
  $(document.documentElement).addClass('overlay-open');
95
  this.makeDocumentUntabbable();
96

    
97
  // Allow other scripts to respond to this event.
98
  $(document).trigger('drupalOverlayOpen');
99

    
100
  return this.load(url);
101
};
102

    
103
/**
104
 * Create the underlying markup and behaviors for the overlay.
105
 */
106
Drupal.overlay.create = function () {
107
  this.$container = $(Drupal.theme('overlayContainer'))
108
    .appendTo(document.body);
109

    
110
  // Overlay uses transparent iframes that cover the full parent window.
111
  // When the overlay is open the scrollbar of the parent window is hidden.
112
  // Because some browsers show a white iframe background for a short moment
113
  // while loading a page into an iframe, overlay uses two iframes. By loading
114
  // the page in a hidden (inactive) iframe the user doesn't see the white
115
  // background. When the page is loaded the active and inactive iframes
116
  // are switched.
117
  this.activeFrame = this.$iframeA = $(Drupal.theme('overlayElement'))
118
    .appendTo(this.$container);
119

    
120
  this.inactiveFrame = this.$iframeB = $(Drupal.theme('overlayElement'))
121
    .appendTo(this.$container);
122

    
123
  this.$iframeA.bind('load.drupal-overlay', { self: this.$iframeA[0], sibling: this.$iframeB }, $.proxy(this, 'loadChild'));
124
  this.$iframeB.bind('load.drupal-overlay', { self: this.$iframeB[0], sibling: this.$iframeA }, $.proxy(this, 'loadChild'));
125

    
126
  // Add a second class "drupal-overlay-open" to indicate these event handlers
127
  // should only be bound when the overlay is open.
128
  var eventClass = '.drupal-overlay.drupal-overlay-open';
129
  $(window)
130
    .bind('resize' + eventClass, $.proxy(this, 'eventhandlerOuterResize'));
131
  $(document)
132
    .bind('drupalOverlayLoad' + eventClass, $.proxy(this, 'eventhandlerOuterResize'))
133
    .bind('drupalOverlayReady' + eventClass +
134
          ' drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerSyncURLFragment'))
135
    .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRefreshPage'))
136
    .bind('drupalOverlayBeforeClose' + eventClass +
137
          ' drupalOverlayBeforeLoad' + eventClass +
138
          ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent'));
139

    
140
  if ($('.overlay-displace-top, .overlay-displace-bottom').length) {
141
    $(document)
142
      .bind('drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerAlterDisplacedElements'))
143
      .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRestoreDisplacedElements'));
144
  }
145
};
146

    
147
/**
148
 * Load the given URL into the overlay iframe.
149
 *
150
 * Use this method to change the URL being loaded in the overlay if it is
151
 * already open.
152
 *
153
 * @return
154
 *   TRUE if URL is loaded into the overlay, FALSE otherwise.
155
 */
156
Drupal.overlay.load = function (url) {
157
  if (!this.isOpen) {
158
    return false;
159
  }
160

    
161
  // Allow other scripts to respond to this event.
162
  $(document).trigger('drupalOverlayBeforeLoad');
163

    
164
  $(document.documentElement).addClass('overlay-loading');
165

    
166
  // The contentDocument property is not supported in IE until IE8.
167
  var iframeDocument = this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document;
168

    
169
  // location.replace doesn't create a history entry. location.href does.
170
  // In this case, we want location.replace, as we're creating the history
171
  // entry using URL fragments.
172
  iframeDocument.location.replace(url);
173

    
174
  return true;
175
};
176

    
177
/**
178
 * Close the overlay and remove markup related to it from the document.
179
 *
180
 * @return
181
 *   TRUE if the overlay was closed, FALSE otherwise.
182
 */
183
Drupal.overlay.close = function () {
184
  // Prevent double execution when close is requested more than once.
185
  if (!this.isOpen || this.isClosing) {
186
    return false;
187
  }
188

    
189
  // Allow other scripts to respond to this event.
190
  var event = $.Event('drupalOverlayBeforeClose');
191
  $(document).trigger(event);
192
  // If a handler returned false, the close will be prevented.
193
  if (event.isDefaultPrevented()) {
194
    return false;
195
  }
196

    
197
  this.isClosing = true;
198
  this.isOpen = false;
199
  $(document.documentElement).removeClass('overlay-open');
200
  // Restore the original document title.
201
  document.title = this.originalTitle;
202
  this.makeDocumentTabbable();
203

    
204
  // Allow other scripts to respond to this event.
205
  $(document).trigger('drupalOverlayClose');
206

    
207
  // When the iframe is still loading don't destroy it immediately but after
208
  // the content is loaded (see Drupal.overlay.loadChild).
209
  if (!this.isLoading) {
210
    this.destroy();
211
    this.isClosing = false;
212
  }
213
  return true;
214
};
215

    
216
/**
217
 * Destroy the overlay.
218
 */
219
Drupal.overlay.destroy = function () {
220
  $([document, window]).unbind('.drupal-overlay-open');
221
  this.$container.remove();
222

    
223
  this.$container = null;
224
  this.$iframeA = null;
225
  this.$iframeB = null;
226

    
227
  this.iframeWindow = null;
228
};
229

    
230
/**
231
 * Redirect the overlay parent window to the given URL.
232
 *
233
 * @param url
234
 *   Can be an absolute URL or a relative link to the domain root.
235
 */
236
Drupal.overlay.redirect = function (url) {
237
  // Create a native Link object, so we can use its object methods.
238
  var link = $(url.link(url)).get(0);
239

    
240
  // If the link is already open, force the hashchange event to simulate reload.
241
  if (window.location.href == link.href) {
242
    $(window).triggerHandler('hashchange.drupal-overlay');
243
  }
244

    
245
  window.location.href = link.href;
246
  return true;
247
};
248

    
249
/**
250
 * Bind the child window.
251
 *
252
 * Note that this function is fired earlier than Drupal.overlay.loadChild.
253
 */
254
Drupal.overlay.bindChild = function (iframeWindow, isClosing) {
255
  this.iframeWindow = iframeWindow;
256

    
257
  // We are done if the child window is closing.
258
  if (isClosing || this.isClosing || !this.isOpen) {
259
    return;
260
  }
261

    
262
  // Allow other scripts to respond to this event.
263
  $(document).trigger('drupalOverlayReady');
264
};
265

    
266
/**
267
 * Event handler: load event handler for the overlay iframe.
268
 *
269
 * @param event
270
 *   Event being triggered, with the following restrictions:
271
 *   - event.type: load
272
 *   - event.currentTarget: iframe
273
 */
274
Drupal.overlay.loadChild = function (event) {
275
  var iframe = event.data.self;
276
  var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
277
  var iframeWindow = iframeDocument.defaultView || iframeDocument.parentWindow;
278
  if (iframeWindow.location == 'about:blank') {
279
    return;
280
  }
281

    
282
  this.isLoading = false;
283
  $(document.documentElement).removeClass('overlay-loading');
284
  event.data.sibling.removeClass('overlay-active').attr({ 'tabindex': -1 });
285

    
286
  // Only continue when overlay is still open and not closing.
287
  if (this.isOpen && !this.isClosing) {
288
    // And child document is an actual overlayChild.
289
    if (iframeWindow.Drupal && iframeWindow.Drupal.overlayChild) {
290
      // Replace the document title with title of iframe.
291
      document.title = iframeWindow.document.title;
292

    
293
      this.activeFrame = $(iframe)
294
        .addClass('overlay-active')
295
        // Add a title attribute to the iframe for accessibility.
296
        .attr('title', Drupal.t('@title dialog', { '@title': iframeWindow.jQuery('#overlay-title').text() })).removeAttr('tabindex');
297
      this.inactiveFrame = event.data.sibling;
298

    
299
      // Load an empty document into the inactive iframe.
300
      (this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document).location.replace('about:blank');
301

    
302
      // Move the focus to just before the "skip to main content" link inside
303
      // the overlay.
304
      this.activeFrame.focus();
305
      var skipLink = iframeWindow.jQuery('a:first');
306
      Drupal.overlay.setFocusBefore(skipLink, iframeWindow.document);
307

    
308
      // Allow other scripts to respond to this event.
309
      $(document).trigger('drupalOverlayLoad');
310
    }
311
    else {
312
      window.location = iframeWindow.location.href.replace(/([?&]?)render=overlay&?/g, '$1').replace(/\?$/, '');
313
    }
314
  }
315
  else {
316
    this.destroy();
317
  }
318
};
319

    
320
/**
321
 * Creates a placeholder element to receive document focus.
322
 *
323
 * Setting the document focus to a link will make it visible, even if it's a
324
 * "skip to main content" link that should normally be visible only when the
325
 * user tabs to it. This function can be used to set the document focus to
326
 * just before such an invisible link.
327
 *
328
 * @param $element
329
 *   The jQuery element that should receive focus on the next tab press.
330
 * @param document
331
 *   The iframe window element to which the placeholder should be added. The
332
 *   placeholder element has to be created inside the same iframe as the element
333
 *   it precedes, to keep IE happy. (http://bugs.jquery.com/ticket/4059)
334
 */
335
Drupal.overlay.setFocusBefore = function ($element, document) {
336
  // Create an anchor inside the placeholder document.
337
  var placeholder = document.createElement('a');
338
  var $placeholder = $(placeholder).addClass('element-invisible').attr('href', '#');
339
  // Put the placeholder where it belongs, and set the document focus to it.
340
  $placeholder.insertBefore($element);
341
  $placeholder.focus();
342
  // Make the placeholder disappear as soon as it loses focus, so that it
343
  // doesn't appear in the tab order again.
344
  $placeholder.one('blur', function () {
345
    $(this).remove();
346
  });
347
};
348

    
349
/**
350
 * Check if the given link is in the administrative section of the site.
351
 *
352
 * @param url
353
 *   The URL to be tested.
354
 *
355
 * @return boolean
356
 *   TRUE if the URL represents an administrative link, FALSE otherwise.
357
 */
358
Drupal.overlay.isAdminLink = function (url) {
359
  if (Drupal.overlay.isExternalLink(url)) {
360
    return false;
361
  }
362

    
363
  var path = this.getPath(url);
364

    
365
  // Turn the list of administrative paths into a regular expression.
366
  if (!this.adminPathRegExp) {
367
    var prefix = '';
368
    if (Drupal.settings.overlay.pathPrefixes.length) {
369
      // Allow path prefixes used for language negatiation followed by slash,
370
      // and the empty string.
371
      prefix = '(' + Drupal.settings.overlay.pathPrefixes.join('/|') + '/|)';
372
    }
373
    var adminPaths = '^' + prefix + '(' + Drupal.settings.overlay.paths.admin.replace(/\s+/g, '|') + ')$';
374
    var nonAdminPaths = '^' + prefix + '(' + Drupal.settings.overlay.paths.non_admin.replace(/\s+/g, '|') + ')$';
375
    adminPaths = adminPaths.replace(/\*/g, '.*');
376
    nonAdminPaths = nonAdminPaths.replace(/\*/g, '.*');
377
    this.adminPathRegExp = new RegExp(adminPaths);
378
    this.nonAdminPathRegExp = new RegExp(nonAdminPaths);
379
  }
380

    
381
  return this.adminPathRegExp.exec(path) && !this.nonAdminPathRegExp.exec(path);
382
};
383

    
384
/**
385
 * Determine whether a link is external to the site.
386
 *
387
 * Deprecated. Use Drupal.urlIsLocal() instead.
388
 *
389
 * @param url
390
 *   The URL to be tested.
391
 *
392
 * @return boolean
393
 *   TRUE if the URL is external to the site, FALSE otherwise.
394
 */
395
Drupal.overlay.isExternalLink = function (url) {
396

    
397
  // Drupal.urlIsLocal() was added in Drupal 7.39.
398
  if (typeof Drupal.urlIsLocal === 'function') {
399
    return !Drupal.urlIsLocal(url);
400
  }
401

    
402
  var re = RegExp('^((f|ht)tps?:)?//(?!' + window.location.host + ')');
403
  return re.test(url);
404
};
405

    
406
/**
407
 * Constructs an internal URL (relative to this site) from the provided path.
408
 *
409
 * For example, if the provided path is 'admin' and the site is installed at
410
 * http://example.com/drupal, this function will return '/drupal/admin'.
411
 *
412
 * @param path
413
 *   The internal path, without any leading slash.
414
 *
415
 * @return
416
 *   The internal URL derived from the provided path, or null if a valid
417
 *   internal path cannot be constructed (for example, if an attempt to create
418
 *   an external link is detected).
419
 */
420
Drupal.overlay.getInternalUrl = function (path) {
421
  var url = Drupal.settings.basePath + path;
422
  if (!this.isExternalLink(url)) {
423
    return url;
424
  }
425
};
426

    
427
/**
428
 * Event handler: resizes overlay according to the size of the parent window.
429
 *
430
 * @param event
431
 *   Event being triggered, with the following restrictions:
432
 *   - event.type: any
433
 *   - event.currentTarget: any
434
 */
435
Drupal.overlay.eventhandlerOuterResize = function (event) {
436
  // Proceed only if the overlay still exists.
437
  if (!(this.isOpen || this.isOpening) || this.isClosing || !this.iframeWindow) {
438
    return;
439
  }
440

    
441
  // IE6 uses position:absolute instead of position:fixed.
442
  if (typeof document.body.style.maxHeight != 'string') {
443
    this.activeFrame.height($(window).height());
444
  }
445

    
446
  // Allow other scripts to respond to this event.
447
  $(document).trigger('drupalOverlayResize');
448
};
449

    
450
/**
451
 * Event handler: resizes displaced elements so they won't overlap the scrollbar
452
 * of overlay's iframe.
453
 *
454
 * @param event
455
 *   Event being triggered, with the following restrictions:
456
 *   - event.type: any
457
 *   - event.currentTarget: any
458
 */
459
Drupal.overlay.eventhandlerAlterDisplacedElements = function (event) {
460
  // Proceed only if the overlay still exists.
461
  if (!(this.isOpen || this.isOpening) || this.isClosing || !this.iframeWindow) {
462
    return;
463
  }
464

    
465
  $(this.iframeWindow.document.body).css({
466
    marginTop: Drupal.overlay.getDisplacement('top'),
467
    marginBottom: Drupal.overlay.getDisplacement('bottom')
468
  })
469
  // IE7 isn't reflowing the document immediately.
470
  // @todo This might be fixed in a cleaner way.
471
  .addClass('overlay-trigger-reflow').removeClass('overlay-trigger-reflow');
472

    
473
  var documentHeight = this.iframeWindow.document.body.clientHeight;
474
  var documentWidth = this.iframeWindow.document.body.clientWidth;
475
  // IE6 doesn't support maxWidth, use width instead.
476
  var maxWidthName = (typeof document.body.style.maxWidth == 'string') ? 'maxWidth' : 'width';
477

    
478
  if (Drupal.overlay.leftSidedScrollbarOffset === undefined && $(document.documentElement).attr('dir') === 'rtl') {
479
    // We can't use element.clientLeft to detect whether scrollbars are placed
480
    // on the left side of the element when direction is set to "rtl" as most
481
    // browsers dont't support it correctly.
482
    // http://www.gtalbot.org/BugzillaSection/DocumentAllDHTMLproperties.html
483
    // There seems to be absolutely no way to detect whether the scrollbar
484
    // is on the left side in Opera; always expect scrollbar to be on the left.
485
    if ($.browser.opera) {
486
      Drupal.overlay.leftSidedScrollbarOffset = document.documentElement.clientWidth - this.iframeWindow.document.documentElement.clientWidth + this.iframeWindow.document.documentElement.clientLeft;
487
    }
488
    else if (this.iframeWindow.document.documentElement.clientLeft) {
489
      Drupal.overlay.leftSidedScrollbarOffset = this.iframeWindow.document.documentElement.clientLeft;
490
    }
491
    else {
492
      var el1 = $('<div style="direction: rtl; overflow: scroll;"></div>').appendTo(document.body);
493
      var el2 = $('<div></div>').appendTo(el1);
494
      Drupal.overlay.leftSidedScrollbarOffset = parseInt(el2[0].offsetLeft - el1[0].offsetLeft);
495
      el1.remove();
496
    }
497
  }
498

    
499
  // Consider any element that should be visible above the overlay (such as
500
  // a toolbar).
501
  $('.overlay-displace-top, .overlay-displace-bottom').each(function () {
502
    var data = $(this).data();
503
    var maxWidth = documentWidth;
504
    // In IE, Shadow filter makes element to overlap the scrollbar with 1px.
505
    if (this.filters && this.filters.length && this.filters.item('DXImageTransform.Microsoft.Shadow')) {
506
      maxWidth -= 1;
507
    }
508

    
509
    if (Drupal.overlay.leftSidedScrollbarOffset) {
510
      $(this).css('left', Drupal.overlay.leftSidedScrollbarOffset);
511
    }
512

    
513
    // Prevent displaced elements overlapping window's scrollbar.
514
    var currentMaxWidth = parseInt($(this).css(maxWidthName));
515
    if ((data.drupalOverlay && data.drupalOverlay.maxWidth) || isNaN(currentMaxWidth) || currentMaxWidth > maxWidth || currentMaxWidth <= 0) {
516
      $(this).css(maxWidthName, maxWidth);
517
      (data.drupalOverlay = data.drupalOverlay || {}).maxWidth = true;
518
    }
519

    
520
    // Use a more rigorous approach if the displaced element still overlaps
521
    // window's scrollbar; clip the element on the right.
522
    var offset = $(this).offset();
523
    var offsetRight = offset.left + $(this).outerWidth();
524
    if ((data.drupalOverlay && data.drupalOverlay.clip) || offsetRight > maxWidth) {
525
      if (Drupal.overlay.leftSidedScrollbarOffset) {
526
        $(this).css('clip', 'rect(auto, auto, ' + (documentHeight - offset.top) + 'px, ' + (Drupal.overlay.leftSidedScrollbarOffset + 2) + 'px)');
527
      }
528
      else {
529
        $(this).css('clip', 'rect(auto, ' + (maxWidth - offset.left) + 'px, ' + (documentHeight - offset.top) + 'px, auto)');
530
      }
531
      (data.drupalOverlay = data.drupalOverlay || {}).clip = true;
532
    }
533
  });
534
};
535

    
536
/**
537
 * Event handler: restores size of displaced elements as they were before
538
 * overlay was opened.
539
 *
540
 * @param event
541
 *   Event being triggered, with the following restrictions:
542
 *   - event.type: any
543
 *   - event.currentTarget: any
544
 */
545
Drupal.overlay.eventhandlerRestoreDisplacedElements = function (event) {
546
  var $displacedElements = $('.overlay-displace-top, .overlay-displace-bottom');
547
  try {
548
    $displacedElements.css({ maxWidth: '', clip: '' });
549
  }
550
  // IE bug that doesn't allow unsetting style.clip (http://dev.jquery.com/ticket/6512).
551
  catch (err) {
552
    $displacedElements.attr('style', function (index, attr) {
553
      return attr.replace(/clip\s*:\s*rect\([^)]+\);?/i, '');
554
    });
555
  }
556
};
557

    
558
/**
559
 * Event handler: overrides href of administrative links to be opened in
560
 * the overlay.
561
 *
562
 * This click event handler should be bound to any document (for example the
563
 * overlay iframe) of which you want links to open in the overlay.
564
 *
565
 * @param event
566
 *   Event being triggered, with the following restrictions:
567
 *   - event.type: click, mouseup
568
 *   - event.currentTarget: document
569
 *
570
 * @see Drupal.overlayChild.behaviors.addClickHandler
571
 */
572
Drupal.overlay.eventhandlerOverrideLink = function (event) {
573
  // In some browsers the click event isn't fired for right-clicks. Use the
574
  // mouseup event for right-clicks and the click event for everything else.
575
  if ((event.type == 'click' && event.button == 2) || (event.type == 'mouseup' && event.button != 2)) {
576
    return;
577
  }
578

    
579
  var $target = $(event.target);
580

    
581
  // Only continue if clicked target (or one of its parents) is a link.
582
  if (!$target.is('a')) {
583
    $target = $target.closest('a');
584
    if (!$target.length) {
585
      return;
586
    }
587
  }
588

    
589
  // Never open links in the overlay that contain the overlay-exclude class.
590
  if ($target.hasClass('overlay-exclude')) {
591
    return;
592
  }
593

    
594
  // Close the overlay when the link contains the overlay-close class.
595
  if ($target.hasClass('overlay-close')) {
596
    // Clearing the overlay URL fragment will close the overlay.
597
    $.bbq.removeState('overlay');
598
    return;
599
  }
600

    
601
  var target = $target[0];
602
  var href = target.href;
603
  // Only handle links that have an href attribute and use the HTTP(S) protocol.
604
  if (href != undefined && href != '' && target.protocol.match(/^https?\:/)) {
605
    var anchor = href.replace(target.ownerDocument.location.href, '');
606
    // Skip anchor links.
607
    if (anchor.length == 0 || anchor.charAt(0) == '#') {
608
      return;
609
    }
610
    // Open admin links in the overlay.
611
    else if (this.isAdminLink(href)) {
612
      // If the link contains the overlay-restore class and the overlay-context
613
      // state is set, also update the parent window's location.
614
      var parentLocation = ($target.hasClass('overlay-restore') && typeof $.bbq.getState('overlay-context') == 'string')
615
        ? this.getInternalUrl($.bbq.getState('overlay-context'))
616
        : null;
617
      href = this.fragmentizeLink($target.get(0), parentLocation);
618
      // Only override default behavior when left-clicking and user is not
619
      // pressing the ALT, CTRL, META (Command key on the Macintosh keyboard)
620
      // or SHIFT key.
621
      if (event.button == 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
622
        // Redirect to a fragmentized href. This will trigger a hashchange event.
623
        this.redirect(href);
624
        // Prevent default action and further propagation of the event.
625
        return false;
626
      }
627
      // Otherwise alter clicked link's href. This is being picked up by
628
      // the default action handler.
629
      else {
630
        $target
631
          // Restore link's href attribute on blur or next click.
632
          .one('blur mousedown', { target: target, href: target.href }, function (event) { $(event.data.target).attr('href', event.data.href); })
633
          .attr('href', href);
634
      }
635
    }
636
    // Non-admin links should close the overlay and open in the main window,
637
    // which is the default action for a link. We only need to handle them
638
    // if the overlay is open and the clicked link is inside the overlay iframe.
639
    else if (this.isOpen && target.ownerDocument === this.iframeWindow.document) {
640
      // Open external links in the immediate parent of the frame, unless the
641
      // link already has a different target.
642
      if (target.hostname != window.location.hostname) {
643
        if (!$target.attr('target')) {
644
          $target.attr('target', '_parent');
645
        }
646
      }
647
      else {
648
        // Add the overlay-context state to the link, so "overlay-restore" links
649
        // can restore the context.
650
        if ($target[0].hash) {
651
          // Leave links with an existing fragment alone. Adding an extra
652
          // parameter to a link like "node/1#section-1" breaks the link.
653
        }
654
        else {
655
          // For links with no existing fragment, add the overlay context.
656
          $target.attr('href', $.param.fragment(href, { 'overlay-context': this.getPath(window.location) + window.location.search }));
657
        }
658

    
659
        // When the link has a destination query parameter and that destination
660
        // is an admin link we need to fragmentize it. This will make it reopen
661
        // in the overlay.
662
        var params = $.deparam.querystring(href);
663
        if (params.destination && this.isAdminLink(params.destination)) {
664
          var fragmentizedDestination = $.param.fragment(this.getPath(window.location), { overlay: params.destination });
665
          $target.attr('href', $.param.querystring(href, { destination: fragmentizedDestination }));
666
        }
667

    
668
        // Make the link open in the immediate parent of the frame, unless the
669
        // link already has a different target.
670
        if (!$target.attr('target')) {
671
          $target.attr('target', '_parent');
672
        }
673
      }
674
    }
675
  }
676
};
677

    
678
/**
679
 * Event handler: opens or closes the overlay based on the current URL fragment.
680
 *
681
 * @param event
682
 *   Event being triggered, with the following restrictions:
683
 *   - event.type: hashchange
684
 *   - event.currentTarget: document
685
 */
686
Drupal.overlay.eventhandlerOperateByURLFragment = function (event) {
687
  // If we changed the hash to reflect an internal redirect in the overlay,
688
  // its location has already been changed, so don't do anything.
689
  if ($.data(window.location, window.location.href) === 'redirect') {
690
    $.data(window.location, window.location.href, null);
691
    return;
692
  }
693

    
694
  // Get the overlay URL from the current URL fragment.
695
  var internalUrl = null;
696
  var state = $.bbq.getState('overlay');
697
  if (state) {
698
    internalUrl = this.getInternalUrl(state);
699
  }
700
  if (internalUrl) {
701
    // Append render variable, so the server side can choose the right
702
    // rendering and add child frame code to the page if needed.
703
    var url = $.param.querystring(internalUrl, { render: 'overlay' });
704

    
705
    this.open(url);
706
    this.resetActiveClass(this.getPath(Drupal.settings.basePath + state));
707
  }
708
  // If there is no overlay URL in the fragment and the overlay is (still)
709
  // open, close the overlay.
710
  else if (this.isOpen && !this.isClosing) {
711
    this.close();
712
    this.resetActiveClass(this.getPath(window.location));
713
  }
714
};
715

    
716
/**
717
 * Event handler: makes sure the internal overlay URL is reflected in the parent
718
 * URL fragment.
719
 *
720
 * Normally the parent URL fragment determines the overlay location. However, if
721
 * the overlay redirects internally, the parent doesn't get informed, and the
722
 * parent URL fragment will be out of date. This is a sanity check to make
723
 * sure we're in the right place.
724
 *
725
 * The parent URL fragment is also not updated automatically when overlay's
726
 * open, close or load functions are used directly (instead of through
727
 * eventhandlerOperateByURLFragment).
728
 *
729
 * @param event
730
 *   Event being triggered, with the following restrictions:
731
 *   - event.type: drupalOverlayReady, drupalOverlayClose
732
 *   - event.currentTarget: document
733
 */
734
Drupal.overlay.eventhandlerSyncURLFragment = function (event) {
735
  if (this.isOpen) {
736
    var expected = $.bbq.getState('overlay');
737
    // This is just a sanity check, so we're comparing paths, not query strings.
738
    if (this.getPath(Drupal.settings.basePath + expected) != this.getPath(this.iframeWindow.document.location)) {
739
      // There may have been a redirect inside the child overlay window that the
740
      // parent wasn't aware of. Update the parent URL fragment appropriately.
741
      var newLocation = Drupal.overlay.fragmentizeLink(this.iframeWindow.document.location);
742
      // Set a 'redirect' flag on the new location so the hashchange event handler
743
      // knows not to change the overlay's content.
744
      $.data(window.location, newLocation, 'redirect');
745
      // Use location.replace() so we don't create an extra history entry.
746
      window.location.replace(newLocation);
747
    }
748
  }
749
  else {
750
    $.bbq.removeState('overlay');
751
  }
752
};
753

    
754
/**
755
 * Event handler: if the child window suggested that the parent refresh on
756
 * close, force a page refresh.
757
 *
758
 * @param event
759
 *   Event being triggered, with the following restrictions:
760
 *   - event.type: drupalOverlayClose
761
 *   - event.currentTarget: document
762
 */
763
Drupal.overlay.eventhandlerRefreshPage = function (event) {
764
  if (Drupal.overlay.refreshPage) {
765
    window.location.reload(true);
766
  }
767
};
768

    
769
/**
770
 * Event handler: dispatches events to the overlay document.
771
 *
772
 * @param event
773
 *   Event being triggered, with the following restrictions:
774
 *   - event.type: any
775
 *   - event.currentTarget: any
776
 */
777
Drupal.overlay.eventhandlerDispatchEvent = function (event) {
778
  if (this.iframeWindow && this.iframeWindow.document) {
779
    this.iframeWindow.jQuery(this.iframeWindow.document).trigger(event);
780
  }
781
};
782

    
783
/**
784
 * Make a regular admin link into a URL that will trigger the overlay to open.
785
 *
786
 * @param link
787
 *   A JavaScript Link object (i.e. an <a> element).
788
 * @param parentLocation
789
 *   (optional) URL to override the parent window's location with.
790
 *
791
 * @return
792
 *   A URL that will trigger the overlay (in the form
793
 *   /node/1#overlay=admin/config).
794
 */
795
Drupal.overlay.fragmentizeLink = function (link, parentLocation) {
796
  // Don't operate on links that are already overlay-ready.
797
  var params = $.deparam.fragment(link.href);
798
  if (params.overlay) {
799
    return link.href;
800
  }
801

    
802
  // Determine the link's original destination. Set ignorePathFromQueryString to
803
  // true to prevent transforming this link into a clean URL while clean URLs
804
  // may be disabled.
805
  var path = this.getPath(link, true);
806
  // Preserve existing query and fragment parameters in the URL, except for
807
  // "render=overlay" which is re-added in Drupal.overlay.eventhandlerOperateByURLFragment.
808
  var destination = path + link.search.replace(/&?render=overlay/, '').replace(/\?$/, '') + link.hash;
809

    
810
  // Assemble and return the overlay-ready link.
811
  return $.param.fragment(parentLocation || window.location.href, { overlay: destination });
812
};
813

    
814
/**
815
 * Refresh any regions of the page that are displayed outside the overlay.
816
 *
817
 * @param data
818
 *   An array of objects with information on the page regions to be refreshed.
819
 *   For each object, the key is a CSS class identifying the region to be
820
 *   refreshed, and the value represents the section of the Drupal $page array
821
 *   corresponding to this region.
822
 */
823
Drupal.overlay.refreshRegions = function (data) {
824
  $.each(data, function () {
825
    var region_info = this;
826
    $.each(region_info, function (regionClass) {
827
      var regionName = region_info[regionClass];
828
      var regionSelector = '.' + regionClass;
829
      // Allow special behaviors to detach.
830
      Drupal.detachBehaviors($(regionSelector));
831
      $.get(Drupal.settings.basePath + Drupal.settings.overlay.ajaxCallback + '/' + regionName, function (newElement) {
832
        $(regionSelector).replaceWith($(newElement));
833
        Drupal.attachBehaviors($(regionSelector), Drupal.settings);
834
      });
835
    });
836
  });
837
};
838

    
839
/**
840
 * Reset the active class on links in displaced elements according to
841
 * given path.
842
 *
843
 * @param activePath
844
 *   Path to match links against.
845
 */
846
Drupal.overlay.resetActiveClass = function(activePath) {
847
  var self = this;
848
  var windowDomain = window.location.protocol + window.location.hostname;
849

    
850
  $('.overlay-displace-top, .overlay-displace-bottom')
851
  .find('a[href]')
852
  // Remove active class from all links in displaced elements.
853
  .removeClass('active')
854
  // Add active class to links that match activePath.
855
  .each(function () {
856
    var linkDomain = this.protocol + this.hostname;
857
    var linkPath = self.getPath(this);
858

    
859
    // A link matches if it is part of the active trail of activePath, except
860
    // for frontpage links.
861
    if (linkDomain == windowDomain && (activePath + '/').indexOf(linkPath + '/') === 0 && (linkPath !== '' || activePath === '')) {
862
      $(this).addClass('active');
863
    }
864
  });
865
};
866

    
867
/**
868
 * Helper function to get the (corrected) Drupal path of a link.
869
 *
870
 * @param link
871
 *   Link object or string to get the Drupal path from.
872
 * @param ignorePathFromQueryString
873
 *   Boolean whether to ignore path from query string if path appears empty.
874
 *
875
 * @return
876
 *   The Drupal path.
877
 */
878
Drupal.overlay.getPath = function (link, ignorePathFromQueryString) {
879
  if (typeof link == 'string') {
880
    // Create a native Link object, so we can use its object methods.
881
    link = $(link.link(link)).get(0);
882
  }
883

    
884
  var path = link.pathname;
885
  // Ensure a leading slash on the path, omitted in some browsers.
886
  if (path.charAt(0) != '/') {
887
    path = '/' + path;
888
  }
889
  path = path.replace(new RegExp(Drupal.settings.basePath + '(?:index.php)?'), '');
890
  if (path == '' && !ignorePathFromQueryString) {
891
    // If the path appears empty, it might mean the path is represented in the
892
    // query string (clean URLs are not used).
893
    var match = new RegExp('([?&])q=(.+)([&#]|$)').exec(link.search);
894
    if (match && match.length == 4) {
895
      path = match[2];
896
    }
897
  }
898

    
899
  return path;
900
};
901

    
902
/**
903
 * Get the total displacement of given region.
904
 *
905
 * @param region
906
 *   Region name. Either "top" or "bottom".
907
 *
908
 * @return
909
 *   The total displacement of given region in pixels.
910
 */
911
Drupal.overlay.getDisplacement = function (region) {
912
  var displacement = 0;
913
  var lastDisplaced = $('.overlay-displace-' + region + ':last');
914
  if (lastDisplaced.length) {
915
    displacement = lastDisplaced.offset().top + lastDisplaced.outerHeight();
916

    
917
    // In modern browsers (including IE9), when box-shadow is defined, use the
918
    // normal height.
919
    var cssBoxShadowValue = lastDisplaced.css('box-shadow');
920
    var boxShadow = (typeof cssBoxShadowValue !== 'undefined' && cssBoxShadowValue !== 'none');
921
    // In IE8 and below, we use the shadow filter to apply box-shadow styles to
922
    // the toolbar. It adds some extra height that we need to remove.
923
    if (!boxShadow && /DXImageTransform\.Microsoft\.Shadow/.test(lastDisplaced.css('filter'))) {
924
      displacement -= lastDisplaced[0].filters.item('DXImageTransform.Microsoft.Shadow').strength;
925
      displacement = Math.max(0, displacement);
926
    }
927
  }
928
  return displacement;
929
};
930

    
931
/**
932
 * Makes elements outside the overlay unreachable via the tab key.
933
 *
934
 * @param context
935
 *   The part of the DOM that should have its tabindexes changed. Defaults to
936
 *   the entire page.
937
 */
938
Drupal.overlay.makeDocumentUntabbable = function (context) {
939

    
940
  context = context || document.body;
941
  var $overlay, $tabbable, $hasTabindex;
942

    
943
  // Determine which elements on the page already have a tabindex.
944
  $hasTabindex = $('[tabindex] :not(.overlay-element)', context);
945
  // Record the tabindex for each element, so we can restore it later.
946
  $hasTabindex.each(Drupal.overlay._recordTabindex);
947
  // Add the tabbable elements from the current context to any that we might
948
  // have previously recorded.
949
  Drupal.overlay._hasTabindex = $hasTabindex.add(Drupal.overlay._hasTabindex);
950

    
951
  // Set tabindex to -1 on everything outside the overlay and toolbars, so that
952
  // the underlying page is unreachable.
953

    
954
  // By default, browsers make a, area, button, input, object, select, textarea,
955
  // and iframe elements reachable via the tab key.
956
  $tabbable = $('a, area, button, input, object, select, textarea, iframe');
957
  // If another element (like a div) has a tabindex, it's also tabbable.
958
  $tabbable = $tabbable.add($hasTabindex);
959
  // Leave links inside the overlay and toolbars alone.
960
  $overlay = $('.overlay-element, #overlay-container, .overlay-displace-top, .overlay-displace-bottom').find('*');
961
  $tabbable = $tabbable.not($overlay);
962
  // We now have a list of everything in the underlying document that could
963
  // possibly be reachable via the tab key. Make it all unreachable.
964
  $tabbable.attr('tabindex', -1);
965
};
966

    
967
/**
968
 * Restores the original tabindex value of a group of elements.
969
 *
970
 * @param context
971
 *   The part of the DOM that should have its tabindexes restored. Defaults to
972
 *   the entire page.
973
 */
974
Drupal.overlay.makeDocumentTabbable = function (context) {
975

    
976
  var $needsTabindex;
977
  context = context || document.body;
978

    
979
  // Make the underlying document tabbable again by removing all existing
980
  // tabindex attributes.
981
  var $tabindex = $('[tabindex]', context);
982
  $tabindex.removeAttr('tabindex');
983

    
984
  // Restore the tabindex attributes that existed before the overlay was opened.
985
  $needsTabindex = $(Drupal.overlay._hasTabindex, context);
986
  $needsTabindex.each(Drupal.overlay._restoreTabindex);
987
  Drupal.overlay._hasTabindex = Drupal.overlay._hasTabindex.not($needsTabindex);
988
};
989

    
990
/**
991
 * Record the tabindex for an element, using $.data.
992
 *
993
 * Meant to be used as a jQuery.fn.each callback.
994
 */
995
Drupal.overlay._recordTabindex = function () {
996
  var $element = $(this);
997
  var tabindex = $(this).attr('tabindex');
998
  $element.data('drupalOverlayOriginalTabIndex', tabindex);
999
};
1000

    
1001
/**
1002
 * Restore an element's original tabindex.
1003
 *
1004
 * Meant to be used as a jQuery.fn.each callback.
1005
 */
1006
Drupal.overlay._restoreTabindex = function () {
1007
  var $element = $(this);
1008
  var tabindex = $element.data('drupalOverlayOriginalTabIndex');
1009
  $element.attr('tabindex', tabindex);
1010
};
1011

    
1012
/**
1013
 * Theme function to create the overlay iframe element.
1014
 */
1015
Drupal.theme.prototype.overlayContainer = function () {
1016
  return '<div id="overlay-container"><div class="overlay-modal-background"></div></div>';
1017
};
1018

    
1019
/**
1020
 * Theme function to create an overlay iframe element.
1021
 */
1022
Drupal.theme.prototype.overlayElement = function (url) {
1023
  return '<iframe class="overlay-element" frameborder="0" scrolling="auto" allowtransparency="true"></iframe>';
1024
};
1025

    
1026
})(jQuery);