Commit 8aedb8ba authored by webchick's avatar webchick

Issue #1847084 by jessebeach, trawekp, LewisNyman, nod_, tarekdj: Fixed...

Issue #1847084 by jessebeach, trawekp, LewisNyman, nod_, tarekdj: Fixed Vertical toolbar doesn't work with overlay (measure/track displacing elements better + provide change events for them).
parent 57669aab
/**
* Manages elements that can offset the size of the viewport.
*/
(function ($, Drupal, debounce) {
"use strict";
var offsets = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
/**
* Registers a resize hanlder on the window.
*/
Drupal.behaviors.drupalDisplace = {
attach: function () {
// Do not process the window of the overlay.
if (parent.Drupal.overlay && parent.Drupal.overlay.iframeWindow === window) {
return;
}
// Mark this behavior as processed on the first pass.
if (this.displaceProcessed) {
return;
}
this.displaceProcessed = true;
$(window).on('resize.drupalDisplace', debounce(displace, 200));
}
};
/**
* Informs listeners of the current offset dimensions.
*
* @param {boolean} broadcast
* (optional) When true or undefined, causes the recalculated offsets values to be
* broadcast to listeners.
*
* @return {object}
* An object whose keys are the for sides an element -- top, right, bottom
* and left. The value of each key is the viewport displacement distance for
* that edge.
*/
function displace (broadcast) {
offsets = Drupal.displace.offsets = calculateOffsets();
if (typeof broadcast === 'undefined' || broadcast) {
$(document).trigger('drupalViewportOffsetChange', offsets);
}
return offsets;
}
/**
* Determines the viewport offsets.
*
* @return {object}
* An object whose keys are the for sides an element -- top, right, bottom
* and left. The value of each key is the viewport displacement distance for
* that edge.
*/
function calculateOffsets () {
return {
top: calculateOffset('top'),
right: calculateOffset('right'),
bottom: calculateOffset('bottom'),
left: calculateOffset('left')
};
}
/**
* Gets a specific edge's offset.
*
* Any element with the attribute data-offset-{edge} e.g. data-offset-top will
* be considered in the viewport offset calculations. If the attribute has a
* numeric value, that value will be used. If no value is provided, one will
* be calculated using the element's dimensions and placement.
*
* @param {string} edge
* The name of the edge to calculate. Can be 'top', 'right',
* 'bottom' or 'left'.
*
* @return {number}
* The viewport displacement distance for the requested edge.
*/
function calculateOffset (edge) {
var edgeOffset = 0;
var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']');
for (var i = 0, n = displacingElements.length; i < n; i++) {
var el = displacingElements[i];
// If the element is not visble, do consider its dimensions.
if (el.style.display === 'none') {
continue;
}
// If the offset data attribute contains a displacing value, use it.
var displacement = parseInt(el.getAttribute('data-offset-' + edge), 10);
// If the element's offset data attribute exits
// but is not a valid number then get the displacement
// dimensions directly from the element.
if (isNaN(displacement)) {
displacement = getRawOffset(el, edge);
}
// If the displacement value is larger than the current value for this
// edge, use the displacement value.
edgeOffset = Math.max(edgeOffset, displacement);
}
return edgeOffset;
}
/**
* Calculates displacement for element based on its dimensions and placement.
*
* @param {jQuery} $el
* The jQuery element whose dimensions and placement will be measured.
*
* @param {string} edge
* The name of the edge of the viewport that the element is associated
* with.
*
* @return {number}
* The viewport displacement distance for the requested edge.
*/
function getRawOffset (el, edge) {
var $el = $(el);
var documentElement = document.documentElement;
var displacement = 0;
var horizontal = (edge === 'left' || edge === 'right');
// Get the offset of the element itself.
var placement = $el.offset()[ horizontal ? 'left' : 'top'];
// Subtract scroll distance from placement to get the distance
// to the edge of the viewport.
placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal) ? 'Left' : 'Top'] || 0;
// Find the displacement value according to the edge.
switch (edge) {
// Left and top elements displace as a sum of their own offset value
// plus their size.
case 'top':
// Total displacment is the sum of the elements placement and size.
displacement = placement + $el.outerHeight();
break;
case 'left':
// Total displacment is the sum of the elements placement and size.
displacement = placement + $el.outerWidth();
break;
// Right and bottom elements displace according to their left and
// top offset. Their size isn't important.
case 'bottom':
displacement = documentElement.clientHeight - placement;
break;
case 'right':
displacement = documentElement.clientWidth - placement;
break;
default:
displacement = 0;
}
return displacement;
}
/**
* Assign the displace function to a property of the Drupal global object.
*/
Drupal.displace = displace;
$.extend(Drupal.displace, {
/**
* Expose offsets to other scripts to avoid having to recalculate offsets
*/
offsets: offsets,
/**
* Expose method to compute a single edge offsets.
*/
calculateOffset: calculateOffset
});
})(jQuery, Drupal, Drupal.debounce);
(function ($, Drupal) {
(function ($, Drupal, displace) {
"use strict";
......@@ -39,16 +39,14 @@ function tableHeaderOnScrollHandler(e) {
forTables('onScroll');
}
function tableHeaderOffsetChangeHandler(e) {
// Compute the new offset value.
TableHeader.computeOffsetTop();
forTables('stickyPosition', TableHeader.offsetTop);
function tableHeaderOffsetChangeHandler(e, 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 +64,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 +75,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.
*
......@@ -119,28 +114,7 @@ $.extend(TableHeader, {
*
* @type {Array}
*/
tables: [],
/**
* Cache of computed offset value.
*
* @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;
}
tables: []
});
/**
......@@ -211,7 +185,7 @@ $.extend(TableHeader.prototype, {
*/
checkStickyVisible: function () {
var scrollTop = scrollValue('scrollTop');
var tableTop = this.tableOffset.top - TableHeader.offsetTop;
var tableTop = this.tableOffset.top - displace.offsets.top;
var tableBottom = tableTop + this.tableHeight;
var visible = false;
......@@ -248,9 +222,9 @@ $.extend(TableHeader.prototype, {
this.tableHeight = this.$originalTable[0].clientHeight;
// Update offset top.
TableHeader.computeOffsetTop();
displace.offsets.top = displace.calculateOffset('top');
this.tableOffset = this.$originalTable.offset();
this.stickyPosition(TableHeader.offsetTop);
this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft'));
// Update columns width.
var $that = null;
......@@ -277,4 +251,4 @@ $.extend(TableHeader.prototype, {
// Expose constructor in the public space.
Drupal.TableHeader = TableHeader;
}(jQuery, Drupal));
}(jQuery, Drupal, window.parent.Drupal.displace));
/**
* @file
* RTL styling for Overlay child pages.
......@@ -12,26 +11,13 @@ html {
float: right;
left: auto;
}
#overlay {
padding: 0.2em;
padding-left: 26px;
}
#overlay-close-wrapper {
left: 0;
right: auto;
}
#overlay-close,
#overlay-close:hover {
background: transparent url(images/close.png) no-repeat;
border-top-right-radius: 0;
-webkit-border-top-left-radius: 12px;
-webkit-border-bottom-left-radius: 12px;
-moz-border-radius-topleft: 12px;
-moz-border-radius-bottomleft: 12px;
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
background-color: #ffffff;
border-radius: 12px 0 0 12px;
}
/**
......
/**
* @file
* Basic styling for the Overlay child pages.
......@@ -16,15 +15,15 @@
}
#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: 2em 40px;
width: 100%;
}
#overlay-titlebar {
padding: 0 20px;
......@@ -68,8 +67,8 @@
}
#overlay-close,
#overlay-close:hover {
background: transparent url(images/close.png) no-repeat; /* LTR */
border-top-left-radius: 0; /* LTR */
background: #ffffff url(images/close.png) no-repeat;
border-radius: 0 12px 12px 0; /* LTR */
display: block;
height: 26px;
margin: 0;
......@@ -77,14 +76,6 @@
/* Replace with position:fixed to get a scrolling close button. */
position: absolute;
width: 26px;
-webkit-border-top-right-radius: 12px;
-webkit-border-bottom-right-radius: 12px;
-moz-border-radius-topright: 12px;
-moz-border-radius-bottomright: 12px;
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
background-color: #ffffff;
}
/**
......
......@@ -179,11 +179,13 @@ 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');
});
Drupal.overlayChild.behaviors.bindDrupalViewportOffsetChangeEvent = 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(parent.document).on('drupalViewportOffsetChange', function (event, offsets) {
// Fires an event that the child iframe can listen to.
$(document).trigger('drupalViewportOffsetChange', offsets);
});
};
})(jQuery);
......@@ -3,7 +3,7 @@
* Attaches the behaviors for the Overlay parent pages.
*/
(function ($) {
(function ($, Drupal, displace) {
"use strict";
......@@ -106,6 +106,10 @@ Drupal.overlay.open = function (url) {
* Create the underlying markup and behaviors for the overlay.
*/
Drupal.overlay.create = function () {
// Update offsets values on the page.
displace(false);
// Build the overlay container.
this.$container = $(Drupal.theme('overlayContainer'))
.appendTo(document.body);
......@@ -131,6 +135,7 @@ Drupal.overlay.create = function () {
$(window)
.bind('resize' + eventClass, $.proxy(this, 'eventhandlerOuterResize'));
$(document)
.bind('drupalViewportOffsetChange' + eventClass, $.proxy(this, 'eventhandlerViewportOffsetChange'))
.bind('drupalOverlayLoad' + eventClass, $.proxy(this, 'eventhandlerOuterResize'))
.bind('drupalOverlayReady' + eventClass +
' drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerSyncURLFragment'))
......@@ -139,11 +144,9 @@ Drupal.overlay.create = function () {
' drupalOverlayBeforeLoad' + eventClass +
' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent'));
if ($('.overlay-displace-top, .overlay-displace-bottom').length) {
$(document)
.bind('drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerAlterDisplacedElements'))
.bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRestoreDisplacedElements'));
}
$(document)
.bind('drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerAlterDisplacedElements'))
.bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRestoreDisplacedElements'));
};
/**
......@@ -397,6 +400,22 @@ Drupal.overlay.isExternalLink = function (url) {
return re.test(url);
};
/**
* Responds to the drupalViewportOffsetChange event.
*
* @param object event
* A jQuery event object.
*
* @param object offsets
* An object whose keys are the for sides an element -- top, right, bottom
* and left. The value of each key is the viewport displacement distance for
* that edge.
*/
Drupal.overlay.eventhandlerViewportOffsetChange = function (event, offsets) {
// Allow other scripts to respond to this event.
$(document).trigger('drupalOverlayResize');
};
/**
* Event handler: resizes overlay according to the size of the parent window.
*
......@@ -435,78 +454,29 @@ Drupal.overlay.eventhandlerAlterDisplacedElements = function (event) {
return;
}
$(this.iframeWindow.document.body).css({
marginTop: Drupal.overlay.getDisplacement('top'),
marginBottom: Drupal.overlay.getDisplacement('bottom')
}).attr('data-offset-top', Drupal.overlay.getDisplacement('top'));
var offsets = displace.offsets;
$(document).bind('offsettopchange', function () {
var iframeDocument = Drupal.overlay.iframeWindow.document;
$(iframeDocument.body).attr('data-offset-top', Drupal.overlay.getDisplacement('top'));
$(iframeDocument).trigger('offsettopchange');
// Move the body of the iframe contentDocument inward a sufficient distance
// to prevent it from appearing underneath displacing elements like the
// toolbar.
var iframeBody = this.iframeWindow.document.body;
$(iframeBody).css({
'padding-top': offsets.top,
'padding-right': offsets.right,
'padding-bottom': offsets.bottom,
'padding-left': offsets.left
});
// Trigger a repaint.
iframeBody.style.display = 'none';
var _tmp = iframeBody.offsetHeight;
iframeBody.style.display = 'block';
var documentHeight = this.iframeWindow.document.body.clientHeight;
var documentWidth = this.iframeWindow.document.body.clientWidth;
// IE6 doesn't support maxWidth, use width instead.
var maxWidthName = 'maxWidth';
if (Drupal.overlay.leftSidedScrollbarOffset === undefined && $(document.documentElement).attr('dir') === 'rtl') {
// We can't use element.clientLeft to detect whether scrollbars are placed
// on the left side of the element when direction is set to "rtl" as most
// browsers dont't support it correctly.
// http://www.gtalbot.org/BugzillaSection/DocumentAllDHTMLproperties.html
// There seems to be absolutely no way to detect whether the scrollbar
// is on the left side in Opera; always expect scrollbar to be on the left.
if ($.browser.opera) {
Drupal.overlay.leftSidedScrollbarOffset = document.documentElement.clientWidth - this.iframeWindow.document.documentElement.clientWidth + this.iframeWindow.document.documentElement.clientLeft;
}
else if (this.iframeWindow.document.documentElement.clientLeft) {
Drupal.overlay.leftSidedScrollbarOffset = this.iframeWindow.document.documentElement.clientLeft;
}
else {
var el1 = $('<div style="direction: rtl; overflow: scroll;"></div>').appendTo(document.body);
var el2 = $('<div></div>').appendTo(el1);
Drupal.overlay.leftSidedScrollbarOffset = parseInt(el2[0].offsetLeft - el1[0].offsetLeft, 10);
el1.remove();
}
// Constrain the width of offsetting top and bottom elements, such as the
// toolbar, so that a scroll in the overlay iframe won't be occluded.
var iframeBodyWidth = iframeBody.clientWidth;
if (iframeBodyWidth > 0 && iframeBodyWidth < document.documentElement.clientWidth) {
$('[data-offset-top], [data-offset-bottom]').css('max-width', iframeBodyWidth);
}
// Consider any element that should be visible above the overlay (such as
// a toolbar).
$('.overlay-displace-top, .overlay-displace-bottom').each(function () {
var data = $(this).data();
var maxWidth = documentWidth;
// In IE, Shadow filter makes element to overlap the scrollbar with 1px.
if (this.filters && this.filters.length && this.filters.item('DXImageTransform.Microsoft.Shadow')) {
maxWidth -= 1;
}
if (Drupal.overlay.leftSidedScrollbarOffset) {
$(this).css('left', Drupal.overlay.leftSidedScrollbarOffset);
}
// Prevent displaced elements overlapping window's scrollbar.
var currentMaxWidth = parseInt($(this).css(maxWidthName), 10);
if ((data.drupalOverlay && data.drupalOverlay.maxWidth) || isNaN(currentMaxWidth) || currentMaxWidth > maxWidth || currentMaxWidth <= 0) {
$(this).css(maxWidthName, maxWidth);
(data.drupalOverlay = data.drupalOverlay || {}).maxWidth = true;
}
// Use a more rigorous approach if the displaced element still overlaps
// window's scrollbar; clip the element on the right.
var offset = $(this).offset();
var offsetRight = offset.left + $(this).outerWidth();
if ((data.drupalOverlay && data.drupalOverlay.clip) || offsetRight > maxWidth) {
if (Drupal.overlay.leftSidedScrollbarOffset) {
$(this).css('clip', 'rect(auto, auto, ' + (documentHeight - offset.top) + 'px, ' + (Drupal.overlay.leftSidedScrollbarOffset + 2) + 'px)');
}
else {
$(this).css('clip', 'rect(auto, ' + (maxWidth - offset.left) + 'px, ' + (documentHeight - offset.top) + 'px, auto)');
}
(data.drupalOverlay = data.drupalOverlay || {}).clip = true;
}
});
};
/**
......@@ -519,16 +489,7 @@ Drupal.overlay.eventhandlerAlterDisplacedElements = function (event) {
* - event.currentTarget: any
*/
Drupal.overlay.eventhandlerRestoreDisplacedElements = function (event) {
var $displacedElements = $('.overlay-displace-top, .overlay-displace-bottom');
try {
$displacedElements.css({ maxWidth: '', clip: '' });
}
// IE bug that doesn't allow unsetting style.clip (http://dev.jquery.com/ticket/6512).
catch (err) {
$displacedElements.attr('style', function (index, attr) {
return attr.replace(/clip\s*:\s*rect\([^)]+\);?/i, '');
});
}
$('[data-offset-top], [data-offset-bottom]').css('max-width', 'none');
};
/**
......@@ -828,7 +789,7 @@ Drupal.overlay.resetActiveClass = function(activePath) {
var self = this;
var windowDomain = window.location.protocol + window.location.hostname;
$('.overlay-displace-top, .overlay-displace-bottom')
$('#toolbar-administration')
.find('a[href]')
// Remove active class from all links in displaced elements.
.removeClass('active')
......@@ -870,24 +831,6 @@ Drupal.overlay.getPath = function (link) {
return path;
};
/**
* Get the total displacement of given region.
*
* @param region
* Region name. Either "top" or "bottom".
*
* @return
* The total displacement of given region in pixels.
*/
Drupal.overlay.getDisplacement = function (region) {
var displacement = 0;
var lastDisplaced = $('[data-offset-' + region + ']');
if (lastDisplaced.length) {
displacement = parseInt(lastDisplaced.attr('data-offset-' + region));
}
return displacement;
};
/**
* Makes elements outside the overlay unreachable via the tab key.
*
......@@ -916,7 +859,7 @@ Drupal.overlay.makeDocumentUntabbable = function (context) {
// If another element (like a div) has a tabindex, it's also tabbable.
$tabbable = $tabbable.add($hasTabindex);
// Leave links inside the overlay and toolbars alone.
$overlay = $('.overlay-element, #overlay-container, .overlay-displace-top, .overlay-displace-bottom').find('*');
$overlay = $('.overlay-element, #overlay-container, #toolbar-administration').find('*');
$tabbable = $tabbable.not($overlay);
// We now have a list of everything in the underlying document that could
// possibly be reachable via the tab key. Make it all unreachable.
......@@ -982,4 +925,4 @@ $.extend(Drupal.theme, {
}
});
})(jQuery);
})(jQuery, Drupal, Drupal.displace);
......@@ -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'),
),
......
......@@ -1399,6 +1399,22 @@ function system_library_info() {
),
);
// A utility that measures and reports viewport offset dimensions from
// elements like the toolbar that can potentially displace the positioning of
// 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',
......@@ -2095,6 +2111,7 @@ function system_library_info() {
array('system', 'drupal'),
array('system', 'drupalSettings'),
array('system', 'jquery.once'),
array('system', 'drupal.displace'),
),
);
$libraries['drupal.timezone'] = array(
......
......@@ -91,7 +91,7 @@ html.js .toolbar {
position: absolute;
}
.toolbar .tray {
z-index: 250;
z-index: 1200;
}
.toolbar .horizontal {
width: 100%;
......
......@@ -2,9 +2,18 @@
* @file toolbar.theme-rtl.css
*/
/**
* Toolbar bar.
*/
.toolbar .bar {
box-shadow: 1px 0 3px 1px rgba(0, 0, 0, 0.3333);
}
/**
* Toolbar tray.
*/
.toolbar .horizontal {
box-shadow: 2px 1px 3px 1px rgba(0, 0, 0, 0.3333);
}
.toolbar .horizontal > .lining {
padding-right: 0;
padding-left: 5em;
......
......@@ -28,7 +28,7 @@
*/
.toolbar .bar {
background-color: #0f0f0f;
box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.3333);
box-shadow: -1px 0 3px 1px rgba(0, 0, 0, 0.3333); /* LTR */
color: #dddddd;
}
.toolbar .bar a {
......@@ -66,7 +66,7 @@
}
.toolbar .horizontal {
border-bottom: 1px solid #aaaaaa;
box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.3333);
box-shadow: -2px 1px 3px 1px rgba(0, 0, 0, 0.3333); /* LTR */
}
.toolbar .horizontal .tray {
background-color: #f5f5f5;
......
......@@ -81,12 +81,9 @@ Drupal.behaviors.toolbar = {
changeOrientation((locked) ? 'vertical' : ((mql.wide.matches) ? 'horizontal' : 'vertical'), locked);
// Render the main menu as a nested, collapsible accordion.
$toolbar.find('.toolbar-menu-administration > .menu').toolbarMenu();
// Call setHeight on screen resize. Wrap it in debounce to prevent
// setHeight from being called too frequently.
var setHeight = Drupal.debounce(Drupal.toolbar.setHeight, 200);
// Attach behavior to the window.
$(window)
.on('resize.toolbar', setHeight);
// Attach behaviors to the document.
$(document)
.on('drupalViewportOffsetChange.toolbar', Drupal.toolbar.adjustPlacement);
// Attach behaviors to the toolbar.
$toolbar
.on('click.toolbar', '.bar a', Drupal.toolbar.toggleTray)
......@@ -99,6 +96,8 @@ Drupal.behaviors.toolbar = {
// Update the page and toolbar dimension indicators.
updatePeripherals();
}
// Call displace to get the initial placement of offset elements.
Drupal.displace();