From 40bd10898e4a98b81c79f9b77abb1f6e7982882f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= <splendidnoise@gmail.com>
Date: Tue, 12 Mar 2013 17:28:43 -0400
Subject: [PATCH] Issue #1847084 by trawekp, tarekdj, jessebeach: Vertical
 toolbar doesn't work with overlay
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>
---
 core/misc/displace.js                     |  113 +++++++++++++++++++++++++++++
 core/misc/tableheader.js                  |   50 +++++--------
 core/modules/overlay/overlay-child.css    |   12 +--
 core/modules/overlay/overlay-child.js     |   20 ++---
 core/modules/overlay/overlay-parent.js    |   29 ++++++--
 core/modules/overlay/overlay.module       |    1 +
 core/modules/system/system.module         |   16 ++++
 core/modules/toolbar/css/toolbar.base.css |    2 +-
 core/modules/toolbar/js/toolbar.js        |   19 ++++-
 core/modules/toolbar/toolbar.module       |    5 +-
 core/themes/bartik/css/style.css          |    4 +-
 core/themes/seven/style.css               |    4 +-
 12 files changed, 212 insertions(+), 63 deletions(-)
 create mode 100644 core/misc/displace.js

diff --git a/core/misc/displace.js b/core/misc/displace.js
new file mode 100644
index 0000000..5f83ebb
--- /dev/null
+++ b/core/misc/displace.js
@@ -0,0 +1,113 @@
+/**
+ *
+ */
+(function (window, Drupal, $) {
+  var offsets = {
+    top: 0,
+    right: 0,
+    bottom: 0,
+    left: 0
+  };
+
+  var displacingElements = {
+    top: [],
+    right: [],
+    bottom: [],
+    left: []
+  }
+  var displacingDimension = {
+    top: 'height',
+    right: 'width',
+    bottom: 'height',
+    left: 'width'
+  }
+
+  /**
+   * Registers a resize hanlder on the window.
+   */
+  Drupal.behaviors.drupalDisplace = {
+    attach: function (context, settings) {
+      // once() returns a jQuery set. It will be empty if no unprocessed
+      // elements are found. window and window.parent are equivalent unless the
+      // Drupal page is itself wrapped in an iframe.
+      $(window.parent.document.body).once('drupalDisplace', function () {
+        // If this window is itself in an iframe it must be marked as processed.
+        // Its parent window will have been processed above.
+        // When attach() is called again for the preview iframe, it will check
+        // its parent window and find it has been processed. In most cases, the
+        // following code will have no effect.
+        $(window.document.body).once('drupalDisplace');
+
+        findDisplacingElements();
+        // Displace attaches a resize handler to the parent window, not to
+        // a window inside of an overlay element.
+        $(window).on('resize.drupalDisplace', displace);
+      });
+    }
+  };
+
+  /**
+   * Finds and stores elements with a .data-offset-{edge} class.
+   */
+  function findDisplacingElements () {
+    // Find the displacing elements.
+    for (var edge in displacingElements) {
+      if (displacingElements.hasOwnProperty(edge)) {
+        displacingElements[edge] = $('[data-offset-' + edge + ']:visible');
+      }
+    }
+  }
+
+  /**
+   * Determines the viewport offsets.
+   */
+  function calculateOffsets () {
+    // Go through each edge and add up the displacements.
+    for (var edge in displacingElements) {
+      var displacement = 0;
+      if (displacingElements.hasOwnProperty(edge)) {
+        for (var i = 0; i < displacingElements[edge].length; i++) {
+          var $el = displacingElements[edge].eq(i);
+          // If the offset data attribute contains a displacing value, use it.
+          var value = parseInt($el.attr('data-offset-' + edge), 10);
+          if (typeof value === 'number') {
+            displacement += value;
+          }
+          // If the element's offset data attribute does not contain a value,
+          // try to get the displacing dimension from the element directly.
+          else {
+            displacement += $el[displacingDimension[edge]]();
+          }
+        }
+        // Store the displacement vlaue in the closure's offset variable.
+        offsets[edge] = displacement;
+      }
+    }
+  }
+
+  /**
+   * Informs listeners of the current offset dimensions.
+   */
+  function displace() {
+    findDisplacingElements();
+    calculateOffsets();
+    $(document).trigger('drupalViewportOffsetChange', offsets);
+  }
+
+  /**
+   * Returns the current offset dimensions.
+   */
+  displace.getOffsets = function (refresh) {
+      if (refresh) {
+        findDisplacingElements();
+        calculateOffsets();
+      }
+      return offsets;
+  };
+
+  /**
+   * API
+   */
+  Drupal.displace = displace;
+
+}(window, Drupal, jQuery));
diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js
index 0d4f7cd..633dc6e 100644
--- a/core/misc/tableheader.js
+++ b/core/misc/tableheader.js
@@ -1,4 +1,4 @@
-(function ($, Drupal) {
+(function ($, Drupal, window, document) {
 
 "use strict";
 
@@ -17,10 +17,13 @@ function scrollValue(position) {
 
 // Select and initilize sticky table headers.
 function tableHeaderInitHandler(e) {
+  TableHeader.displacements = window.parent.Drupal.displace.getOffsets(true);
   var $tables = $(e.data.context).find('table.sticky-enabled').once('tableheader');
   for (var i = 0, il = $tables.length; i < il; i++) {
     TableHeader.tables.push(new TableHeader($tables[i]));
   }
+  // Trigger a scroll event to prime sticky headers that may be applicable.
+  $(window).trigger('scroll.TableHeader');
 }
 
 // Helper method to loop through tables and execute a method.
@@ -39,16 +42,16 @@ function tableHeaderOnScrollHandler(e) {
   forTables('onScroll');
 }
 
-function tableHeaderOffsetChangeHandler(e) {
+function tableHeaderOffsetChangeHandler(e, offsets) {
   // Compute the new offset value.
-  TableHeader.computeOffsetTop();
-  forTables('stickyPosition', TableHeader.offsetTop);
+  TableHeader.displacements = offsets;
+  forTables('stickyPosition', offsets.top);
 }
 
 // Bind event that need to change all tables.
 $(window).on({
   /**
-   * When resizing table width and offset top can change, recalculate everything.
+   * When resizing table width can change, recalculate everything.
    */
   'resize.TableHeader': tableHeaderResizeHandler,
 
@@ -66,9 +69,9 @@ $(document).on({
   'columnschange.TableHeader': tableHeaderResizeHandler,
 
   /**
-   * Offset value vas changed by a third party script.
+   * Recalculate TableHeader.topOffset when viewport is resized
    */
-  'offsettopchange.TableHeader': tableHeaderOffsetChangeHandler
+  'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler
 });
 
 /**
@@ -77,9 +80,6 @@ $(document).on({
  * TableHeader will make the current table header stick to the top of the page
  * if the table is very long.
  *
- * Fire a custom "topoffsetchange" event to make TableHeader compute the
- * new offset value from the "data-offset-top" attributes of relevant elements.
- *
  * @param table
  *   DOM object for the table to add a sticky header to.
  *
@@ -122,24 +122,15 @@ $.extend(TableHeader, {
   tables: [],
 
   /**
-   * Cache of computed offset value.
+   * Cache of computed viewport displacement values.
    *
    * @type {Number}
    */
-  offsetTop: 0,
-
-  /**
-   * Sum all [data-offset-top] values and cache it.
-   */
-  computeOffsetTop: function () {
-    var $offsets = $('[data-offset-top]');
-    var value, sum = 0;
-    for (var i = 0, il = $offsets.length; i < il; i++) {
-      value = parseInt($offsets[i].getAttribute('data-offset-top'), 10);
-      sum += !isNaN(value) ? value : 0;
-    }
-    this.offsetTop = sum;
-    return sum;
+  displacements: {
+    top: 0,
+    right: 0,
+    bottom: 0,
+    left: 0
   }
 });
 
@@ -211,7 +202,7 @@ $.extend(TableHeader.prototype, {
    */
   checkStickyVisible: function () {
     var scrollTop = scrollValue('scrollTop');
-    var tableTop = this.tableOffset.top - TableHeader.offsetTop;
+    var tableTop = this.tableOffset.top - TableHeader.displacements.top;
     var tableBottom = tableTop + this.tableHeight;
     var visible = false;
 
@@ -230,7 +221,7 @@ $.extend(TableHeader.prototype, {
    *
    * @param event
    */
-  onScroll: function (e) {
+  onScroll: function (event) {
     this.checkStickyVisible();
     // Track horizontal positioning relative to the viewport.
     this.stickyPosition(null, scrollValue('scrollLeft'));
@@ -248,9 +239,8 @@ $.extend(TableHeader.prototype, {
     this.tableHeight = this.$originalTable[0].clientHeight;
 
     // Update offset top.
-    TableHeader.computeOffsetTop();
     this.tableOffset = this.$originalTable.offset();
-    this.stickyPosition(TableHeader.offsetTop);
+    this.stickyPosition(TableHeader.displacements.top, scrollValue('scrollLeft'));
 
     // Update columns width.
     var $that = null;
@@ -277,4 +267,4 @@ $.extend(TableHeader.prototype, {
 // Expose constructor in the public space.
 Drupal.TableHeader = TableHeader;
 
-}(jQuery, Drupal));
+}(jQuery, Drupal, window, window.document));
diff --git a/core/modules/overlay/overlay-child.css b/core/modules/overlay/overlay-child.css
index ecfa4cb..66f07bc 100644
--- a/core/modules/overlay/overlay-child.css
+++ b/core/modules/overlay/overlay-child.css
@@ -16,15 +16,17 @@
 }
 
 #overlay {
-  display: table;
+  box-sizing: border-box;
+  display: block;
   margin: 0 auto;
+  max-width: 80em;
+  max-width: 80rem;
   min-height: 100px;
-  min-width: 700px;
   position: relative;
-  padding: .2em;
   padding-bottom: 2em;
-  padding-right: 26px; /* LTR */
-  width: 88%;
+  padding-left: 40px;
+  padding-right: 40px;
+  width: 100%;
 }
 #overlay-titlebar {
   padding: 0 20px;
diff --git a/core/modules/overlay/overlay-child.js b/core/modules/overlay/overlay-child.js
index 80a8a2f..2de73e0 100644
--- a/core/modules/overlay/overlay-child.js
+++ b/core/modules/overlay/overlay-child.js
@@ -3,7 +3,7 @@
  * Attaches the behaviors for the Overlay child pages.
  */
 
-(function ($) {
+(function ($, window, document) {
 
 "use strict";
 
@@ -54,6 +54,15 @@ Drupal.behaviors.overlayChild = {
       parent.Drupal.overlay.refreshRegions(settings.refreshRegions);
     }
 
+    // Workaround because of the way jQuery events works.
+    // jQuery from the parent frame needs to be used to catch this event.
+    parent.jQuery(document).on({
+      'drupalViewportOffsetChange': function (event, offsets) {
+        // Fires an event that the child iframe can listen to.
+        $(document).trigger('drupalViewportOffsetChange', offsets);
+      }
+    });
+
     // Ok, now we can tell the parent window we're ready.
     parent.Drupal.overlay.bindChild(window);
 
@@ -179,11 +188,4 @@ Drupal.overlayChild.behaviors.shortcutAddLink = function (context, settings) {
   });
 };
 
-// Workaround because of the way jQuery events works.
-// jQuery from the parent frame needs to be used to catch this event.
-parent.jQuery(document).bind('offsettopchange', function () {
-  // Fires an event that the child iframe can listen to.
-  $(document).trigger('offsettopchange');
-});
-
-})(jQuery);
+})(jQuery, window, window.document);
diff --git a/core/modules/overlay/overlay-parent.js b/core/modules/overlay/overlay-parent.js
index caf9336..5d1088b 100644
--- a/core/modules/overlay/overlay-parent.js
+++ b/core/modules/overlay/overlay-parent.js
@@ -34,6 +34,10 @@ Drupal.behaviors.overlayParent = {
       return;
     }
 
+    // The overlay's positioning can be affected by other elements on the page.
+    // Get the current viewpor offset values.
+    Drupal.overlay.viewportOffsets = Drupal.displace.getOffsets(true);
+
     $(document)
       // Instead of binding a click event handler to every link we bind one to
       // the document and only handle events that bubble up. This allows other
@@ -131,6 +135,7 @@ Drupal.overlay.create = function () {
   $(window)
     .bind('resize' + eventClass, $.proxy(this, 'eventhandlerOuterResize'));
   $(document)
+    .on('drupalViewportOffsetChange' + eventClass, $.proxy(this, 'eventhandlerViewportOffsetChange'))
     .bind('drupalOverlayLoad' + eventClass, $.proxy(this, 'eventhandlerOuterResize'))
     .bind('drupalOverlayReady' + eventClass +
           ' drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerSyncURLFragment'))
@@ -398,6 +403,14 @@ Drupal.overlay.isExternalLink = function (url) {
 };
 
 /**
+ *
+ */
+Drupal.overlay.eventhandlerViewportOffsetChange = function (event, offsets) {
+  Drupal.overlay.viewportOffsets = offsets;
+  Drupal.overlay.eventhandlerOuterResize();
+}
+
+/**
  * Event handler: resizes overlay according to the size of the parent window.
  *
  * @param event
@@ -435,17 +448,17 @@ Drupal.overlay.eventhandlerAlterDisplacedElements = function (event) {
     return;
   }
 
+  var offsets = Drupal.overlay.viewportOffsets;
+
   $(this.iframeWindow.document.body).css({
-    marginTop: Drupal.overlay.getDisplacement('top'),
-    marginBottom: Drupal.overlay.getDisplacement('bottom')
-  }).attr('data-offset-top', Drupal.overlay.getDisplacement('top'));
-
-  $(document).bind('offsettopchange', function () {
-    var iframeDocument = Drupal.overlay.iframeWindow.document;
-    $(iframeDocument.body).attr('data-offset-top', Drupal.overlay.getDisplacement('top'));
-    $(iframeDocument).trigger('offsettopchange');
+    'margin-top': offsets.top,
+    'margin-right': offsets.right,
+    'margin-bottom': offsets.bottom,
+    'margin-left': offsets.left
   });
 
+  $(Drupal.overlay.iframeWindow.document).trigger('drupalViewportOffsetChange', Drupal.overlay.viewportOffsets);
+
   var documentHeight = this.iframeWindow.document.body.clientHeight;
   var documentWidth = this.iframeWindow.document.body.clientWidth;
   // IE6 doesn't support maxWidth, use width instead.
diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module
index cc29255..197d006 100644
--- a/core/modules/overlay/overlay.module
+++ b/core/modules/overlay/overlay.module
@@ -225,6 +225,7 @@ function overlay_library_info() {
       array('system', 'jquery'),
       array('system', 'drupal'),
       array('system', 'drupalSettings'),
+      array('system', 'drupal.displace'),
       array('system', 'jquery.ui.core'),
       array('system', 'jquery.bbq'),
     ),
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 2c51ae0..0523006 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1380,6 +1380,21 @@ function system_library_info() {
     ),
   );
 
+  // A utility that measures elements like the toolbar that can potentially
+  // displace the positioning of display elements like the overlay.
+  $libraries['drupal.displace'] = array(
+    'title' => 'Drupal displace',
+    'version' => VERSION,
+    'js' => array(
+      'core/misc/displace.js' => array('group' => JS_LIBRARY),
+    ),
+    'dependencies' => array(
+      array('system', 'jquery'),
+      array('system', 'drupal'),
+      array('system', 'drupal.debounce'),
+    ),
+  );
+
   // A utility function to limit calls to a function with a given time.
   $libraries['drupal.debounce'] = array(
     'title' => 'Drupal debounce',
@@ -2076,6 +2091,7 @@ function system_library_info() {
       array('system', 'drupal'),
       array('system', 'drupalSettings'),
       array('system', 'jquery.once'),
+      array('system', 'drupal.displace'),
     ),
   );
   $libraries['drupal.timezone'] = array(
diff --git a/core/modules/toolbar/css/toolbar.base.css b/core/modules/toolbar/css/toolbar.base.css
index 58a2c2b..64e6468 100644
--- a/core/modules/toolbar/css/toolbar.base.css
+++ b/core/modules/toolbar/css/toolbar.base.css
@@ -91,7 +91,7 @@ html.js .toolbar {
   position: absolute;
 }
 .toolbar .tray {
-  z-index: 250;
+  z-index: 1200;
 }
 .toolbar .horizontal {
   width: 100%;
diff --git a/core/modules/toolbar/js/toolbar.js b/core/modules/toolbar/js/toolbar.js
index 45e91ad..095f5b7 100644
--- a/core/modules/toolbar/js/toolbar.js
+++ b/core/modules/toolbar/js/toolbar.js
@@ -158,9 +158,9 @@ Drupal.toolbar.toggleTray = function (event) {
       // Set aria-pressed to false.
       .attr('aria-pressed', 'false');
     $toolbar.find('.tray').not($activateTray).removeClass('active');
-    // Update the page and toolbar dimension indicators.
-    updatePeripherals();
   }
+  // Update the page and toolbar dimension indicators.
+  updatePeripherals();
 };
 
 /**
@@ -192,8 +192,17 @@ Drupal.toolbar.setHeight = function () {
     $toolbar.attr('data-offset-top', height);
     // Alter the padding on the top of the body element.
     $('body').css('padding-top', height);
-    $(document).trigger('offsettopchange', height);
-    $(window).trigger('resize');
+    Drupal.displace();
+  }
+};
+
+Drupal.toolbar.setTrayWidth = function () {
+  var $tray = $toolbar.find('.tray.active');
+  var trayWidth = $tray.hasClass('vertical') ? $tray.width() : 0;
+  var offset = $tray.attr('data-offset-left');
+  if (offset !== trayWidth) {
+    $tray.attr('data-offset-left', trayWidth);
+    Drupal.displace();
   }
 };
 
@@ -296,6 +305,8 @@ function updatePeripherals () {
   setBodyState();
   // Adjust the height of the toolbar.
   Drupal.toolbar.setHeight();
+  // Adjust the tray width for vertical trays.
+  Drupal.toolbar.setTrayWidth();
 }
 
 /**
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index e4996bc..3292242 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -93,7 +93,7 @@ function toolbar_element_info() {
       'id' => 'toolbar-administration',
       // The 'overlay-displace-top' class pushes the overlay down, so it appears
       // below the toolbar.
-      'class' => array('toolbar', 'overlay-displace-top'),
+      'class' => array('toolbar',),
       'role' => 'navigation',
     ),
     // Metadata for the administration bar.
@@ -101,7 +101,7 @@ function toolbar_element_info() {
       '#heading' => t('Toolbar items'),
       '#attributes' => array(
         'id' => 'toolbar-bar',
-        'class' => array('bar', 'overlay-displace-top', 'clearfix'),
+        'class' => array('bar', 'clearfix'),
       ),
     ),
   );
@@ -631,6 +631,7 @@ function toolbar_library_info() {
       array('system', 'matchmedia'),
       array('system', 'jquery.once'),
       array('system', 'drupal.debounce'),
+      array('system', 'drupal.displace'),
       array('toolbar', 'toolbar.menu'),
     ),
   );
diff --git a/core/themes/bartik/css/style.css b/core/themes/bartik/css/style.css
index 44deab1..67c37d3 100644
--- a/core/themes/bartik/css/style.css
+++ b/core/themes/bartik/css/style.css
@@ -1623,7 +1623,7 @@ div.admin-panel .description {
 /**
  * Responsive tables.
  */
-@media screen and (max-width:28.125em) { /* 450px */
+@media screen and (max-width: 37.5em) { /* 600px */
   th.priority-low,
   td.priority-low,
   th.priority-medium,
@@ -1631,7 +1631,7 @@ div.admin-panel .description {
     display: none;
   }
 }
-@media screen and (max-width:45em) { /* 720px */
+@media screen and (max-width: 60em) { /* 920px */
   th.priority-low,
   td.priority-low {
     display: none;
diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css
index 72ce5e6..6440294 100644
--- a/core/themes/seven/style.css
+++ b/core/themes/seven/style.css
@@ -522,7 +522,7 @@ table.system-status-report tr.error {
 /**
  * Responsive tables.
  */
-@media screen and (max-width:28.125em) { /* 450px */
+@media screen and (max-width: 37.5em) { /* 600px */
   th.priority-low,
   td.priority-low,
   th.priority-medium,
@@ -530,7 +530,7 @@ table.system-status-report tr.error {
     display: none;
   }
 }
-@media screen and (max-width:45em) { /* 720px */
+@media screen and (max-width: 60em) { /* 920px */
   th.priority-low,
   td.priority-low {
     display: none;
-- 
1.7.10.4

