Index: modules/overlay/overlay-child.js =================================================================== RCS file: /cvs/drupal/drupal/modules/overlay/overlay-child.js,v retrieving revision 1.10 diff -u -r1.10 overlay-child.js --- modules/overlay/overlay-child.js 8 Jul 2010 12:20:23 -0000 1.10 +++ modules/overlay/overlay-child.js 24 Jul 2010 02:15:39 -0000 @@ -159,22 +159,23 @@ }; /** - * Use displacement from parent window. + * Allow resize event handlers to recalculate sizes/positions when parent window + * is being resized. */ -Drupal.overlayChild.behaviors.alterTableHeaderOffset = function (context, settings) { - if (Drupal.settings.tableHeaderOffset) { - Drupal.overlayChild.prevTableHeaderOffset = Drupal.settings.tableHeaderOffset; - } - Drupal.settings.tableHeaderOffset = 'Drupal.overlayChild.tableHeaderOffset'; +Drupal.overlayChild.behaviors.parentResize = function (context, settings) { + $(document).bind('drupalOverlayResize.drupal-overlay', function () { + $(window).triggerHandler('resize'); + }); }; -/** - * Callback for Drupal.settings.tableHeaderOffset. - */ -Drupal.overlayChild.tableHeaderOffset = function () { - var topOffset = Drupal.overlayChild.prevTableHeaderOffset ? eval(Drupal.overlayChild.prevTableHeaderOffset + '()') : 0; - - return topOffset + parseInt($(document.body).css('marginTop')); +// Include displacement of parent document. +Drupal.displace.forceHandlers = true; +var _getDisplacement = Drupal.displace.getDisplacement; +Drupal.displace.getDisplacement = function (region) { + if (parent && parent.Drupal && parent.Drupal.displace) { + Drupal.displace.initialOffset = parent.Drupal.displace.getDisplacement(region); + } + return _getDisplacement.apply(Drupal.displace, arguments); }; })(jQuery); Index: modules/overlay/overlay.module =================================================================== RCS file: /cvs/drupal/drupal/modules/overlay/overlay.module,v retrieving revision 1.24 diff -u -r1.24 overlay.module --- modules/overlay/overlay.module 17 Jul 2010 02:12:36 -0000 1.24 +++ modules/overlay/overlay.module 24 Jul 2010 02:15:39 -0000 @@ -166,7 +166,8 @@ 'website' => 'http://drupal.org/handbook/modules/overlay', 'version' => '1.0', 'js' => array( - $module_path . '/overlay-child.js' => array(), + 'misc/displace.js' => array('weight' => JS_LIBRARY + 1), + $module_path . '/overlay-child.js' => array('weight' => JS_LIBRARY + 1), ), 'css' => array( $module_path . '/overlay-child.css' => array(), Index: modules/overlay/overlay-parent.js =================================================================== RCS file: /cvs/drupal/drupal/modules/overlay/overlay-parent.js,v retrieving revision 1.49 diff -u -r1.49 overlay-parent.js --- modules/overlay/overlay-parent.js 8 Jul 2010 12:20:23 -0000 1.49 +++ modules/overlay/overlay-parent.js 24 Jul 2010 02:15:39 -0000 @@ -357,14 +357,6 @@ return; } - $(this.iframeWindow.document.body).css({ - marginTop: Drupal.overlay.getDisplacement('top'), - marginBottom: Drupal.overlay.getDisplacement('bottom') - }) - // IE7 isn't reflowing the document immediately. - // @todo This might be fixed in a cleaner way. - .addClass('overlay-trigger-reflow').removeClass('overlay-trigger-reflow'); - var documentHeight = this.iframeWindow.document.body.clientHeight; var documentWidth = this.iframeWindow.document.body.clientWidth; // IE6 doesn't support maxWidth, use width instead. @@ -647,8 +639,13 @@ * - event.currentTarget: any */ Drupal.overlay.eventhandlerDispatchEvent = function (event) { + var self = this; if (this.iframeWindow && this.iframeWindow.document) { - this.iframeWindow.jQuery(this.iframeWindow.document).trigger(event); + window.setTimeout(function() { + if (self.iframeWindow && self.iframeWindow.document) { + self.iframeWindow.jQuery(self.iframeWindow.document).trigger(event); + } + }, 1); } }; Index: modules/toolbar/toolbar.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/modules/toolbar/toolbar.tpl.php,v retrieving revision 1.11 diff -u -r1.11 toolbar.tpl.php --- modules/toolbar/toolbar.tpl.php 23 May 2010 18:23:32 -0000 1.11 +++ modules/toolbar/toolbar.tpl.php 24 Jul 2010 02:15:39 -0000 @@ -22,7 +22,7 @@ * @see template_preprocess_toolbar() */ ?> -
+
Index: modules/toolbar/toolbar.js =================================================================== RCS file: /cvs/drupal/drupal/modules/toolbar/toolbar.js,v retrieving revision 1.19 diff -u -r1.19 toolbar.js --- modules/toolbar/toolbar.js 8 Jun 2010 05:16:29 -0000 1.19 +++ modules/toolbar/toolbar.js 24 Jul 2010 02:15:39 -0000 @@ -48,7 +48,6 @@ .removeClass('toggle-active') .attr('title', toggle_text) .html(toggle_text); - $('body').removeClass('toolbar-drawer').css('paddingTop', Drupal.toolbar.height()); $.cookie( 'Drupal.toolbar.collapsed', 1, @@ -70,7 +69,6 @@ .addClass('toggle-active') .attr('title', toggle_text) .html(toggle_text); - $('body').addClass('toolbar-drawer').css('paddingTop', Drupal.toolbar.height()); $.cookie( 'Drupal.toolbar.collapsed', 0, @@ -94,14 +92,4 @@ } }; -Drupal.toolbar.height = function() { - var height = $('#toolbar').outerHeight(); - // In IE, Shadow filter adds some extra height, so we need to remove it from - // the returned height. - if ($('#toolbar').css('filter').match(/DXImageTransform\.Microsoft\.Shadow/)) { - height -= $('#toolbar').get(0).filters.item("DXImageTransform.Microsoft.Shadow").strength; - } - return height; -}; - })(jQuery); Index: modules/toolbar/toolbar.module =================================================================== RCS file: /cvs/drupal/drupal/modules/toolbar/toolbar.module,v retrieving revision 1.43 diff -u -r1.43 toolbar.module --- modules/toolbar/toolbar.module 2 Jul 2010 02:22:26 -0000 1.43 +++ modules/toolbar/toolbar.module 24 Jul 2010 02:15:39 -0000 @@ -187,12 +187,9 @@ '#theme' => 'toolbar', '#attached'=> array( 'js' => array( - $module_path . '/toolbar.js', + array('data' => 'misc/displace.js', 'weight' => JS_LIBRARY + 1), array('data' => 'misc/jquery.cookie.js', 'weight' => JS_LIBRARY + 2), - array( - 'data' => array('tableHeaderOffset' => 'Drupal.toolbar.height'), - 'type' => 'setting' - ), + $module_path . '/toolbar.js', ), 'css' => array( $module_path . '/toolbar.css', Index: modules/toolbar/toolbar.css =================================================================== RCS file: /cvs/drupal/drupal/modules/toolbar/toolbar.css,v retrieving revision 1.25 diff -u -r1.25 toolbar.css --- modules/toolbar/toolbar.css 21 Jul 2010 00:26:21 -0000 1.25 +++ modules/toolbar/toolbar.css 24 Jul 2010 02:15:39 -0000 @@ -1,13 +1,5 @@ /* $Id: toolbar.css,v 1.25 2010/07/21 00:26:21 dries Exp $ */ -body.toolbar { - padding-top: 2.2em; -} - -body.toolbar-drawer { - padding-top: 5.3em; -} - /** * Aggressive resets so we can achieve a consistent look in hostile CSS * environments. @@ -35,19 +27,24 @@ font: normal small "Lucida Grande", Verdana, sans-serif; background: #666; color: #ccc; - position: fixed; - top: 0; - left: 0; - right: 0; +} +.displace-processed #toolbar { margin: 0 -20px; padding: 0 20px; - z-index: 600; + z-index: 9999; box-shadow: 0 3px 20px #000; -moz-box-shadow: 0 3px 20px #000; -webkit-box-shadow: 0 3px 20px #000; filter: progid:DXImageTransform.Microsoft.Shadow(color=#000000, direction='180', strength='10'); -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(color=#000000, direction='180', strength='10')"; } +.displace-unsupported #toolbar { + margin: 0; + padding-right: 0; + left: -20px; + right: 0; + width: 100%; +} #toolbar div.collapsed { display: none; Index: misc/tableheader.js =================================================================== RCS file: /cvs/drupal/drupal/misc/tableheader.js,v retrieving revision 1.31 diff -u -r1.31 tableheader.js --- misc/tableheader.js 23 May 2010 18:23:32 -0000 1.31 +++ misc/tableheader.js 24 Jul 2010 02:15:39 -0000 @@ -1,118 +1,111 @@ // $Id: tableheader.js,v 1.31 2010/05/23 18:23:32 dries Exp $ (function ($) { -Drupal.tableHeaderDoScroll = function () { - if ($.isFunction(Drupal.tableHeaderOnScroll)) { - Drupal.tableHeaderOnScroll(); - } -}; - +/** + * Attaches sticky table headers. + */ Drupal.behaviors.tableHeader = { attach: function (context, settings) { - // This breaks in anything less than IE 7. Prevent it from running. - if ($.browser.msie && parseInt($.browser.version, 10) < 7) { + if (!$.support.positionFixed) { return; } - $('table.sticky-enabled thead', context).once('tableheader', function () { - // Clone the table header so it inherits original jQuery properties. Hide - // the table to avoid a flash of the header clone upon page load. - var headerClone = $(this).clone(true).hide().insertBefore(this.parentNode).wrap('').parent().css({ - position: 'fixed', - top: '0px' - }); - - headerClone = $(headerClone)[0]; - - // Store parent table. - var table = $(this).parent('table')[0]; - headerClone.table = table; - // Finish initializing header positioning. - tracker(headerClone); - // We hid the header to avoid it showing up erroneously on page load; - // we need to unhide it now so that it will show up when expected. - $(headerClone).children('thead').show(); - - $(table).addClass('sticky-table'); + $('table.sticky-enabled', context).once('tableheader', function () { + $(this).data("drupal-tableheader", new Drupal.tableHeader(this)); }); + } +}; - // Define the anchor holding var. - var prevAnchor = ''; - - // Track positioning and visibility. - function tracker(e) { - // Reset top position of sticky table headers to the current top offset. - var topOffset = Drupal.settings.tableHeaderOffset ? eval(Drupal.settings.tableHeaderOffset + '()') : 0; - $('.sticky-header').css('top', topOffset + 'px'); - // Save positioning data. - var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight; - if (e.viewHeight != viewHeight) { - e.viewHeight = viewHeight; - e.vPosition = $(e.table).offset().top - 4 - topOffset; - e.hPosition = $(e.table).offset().left; - e.vLength = e.table.clientHeight - 100; - // Resize header and its cell widths. - var parentCell = $('th', e.table); - $('th', e).each(function (index) { - var cellWidth = parentCell.eq(index).css('width'); - // Exception for IE7. - if (cellWidth == 'auto') { - cellWidth = parentCell.get(index).clientWidth + 'px'; - } - $(this).css('width', cellWidth); - }); - $(e).css('width', $(e.table).css('width')); +/** + * Constructor for the tableHeader object. Provides sticky table headers. + * + * @param table + * DOM object for the table to add a sticky header to. + */ +Drupal.tableHeader = function (table) { + var self = this; + + this.originalTable = $(table); + this.originalHeader = $(table).children('thead'); + this.originalHeaderCells = this.originalHeader.find('> tr > th'); + + // Clone the table header so it inherits original jQuery properties. Hide + // the table to avoid a flash of the header clone upon page load. + this.stickyTable = $('') + .insertBefore(this.originalTable) + .css({ position: 'fixed', top: '0px' }); + this.stickyHeader = this.originalHeader.clone(true) + .hide() + .appendTo(this.stickyTable); + this.stickyHeaderCells = this.stickyHeader.find('> tr > th'); + + this.originalTable.addClass('sticky-table'); + $(window) + .bind('scroll.drupal-tableheader', $.proxy(this, 'eventhandlerRecalculateStickyHeader')) + .bind('resize.drupal-tableheader', { calculateWidth: true }, $.proxy(this, 'eventhandlerRecalculateStickyHeader')) + // Make sure the anchor being scrolled into view is not hidden beneath the + // sticky table header. Adjust the scrollTop if it does. + .bind('drupalDisplaceAnchor.drupal-tableheader', function () { + window.scrollBy(0, -self.stickyTable.outerHeight()); + }) + // Make sure the element being focused is not hidden beneath the sticky + // table header. Adjust the scrollTop if it does. + .bind('drupalDisplaceFocus.drupal-tableheader', function (event) { + if (self.stickyVisible && event.clientY < (self.stickyOffsetTop + self.stickyTable.outerHeight()) && event.$target.closest('sticky-header').length === 0) { + window.scrollBy(0, -self.stickyTable.outerHeight()); } + }) + .triggerHandler('resize.drupal-tableheader'); - // Track horizontal positioning relative to the viewport and set visibility. - var hScroll = document.documentElement.scrollLeft || document.body.scrollLeft; - var vOffset = (document.documentElement.scrollTop || document.body.scrollTop) - e.vPosition; - var visState = (vOffset > 0 && vOffset < e.vLength) ? 'visible' : 'hidden'; - $(e).css({ left: -hScroll + e.hPosition + 'px', visibility: visState }); - - // Check the previous anchor to see if we need to scroll to make room for the header. - // Get the height of the header table and scroll up that amount. - if (prevAnchor != location.hash) { - if (location.hash != '') { - var scrollLocation = $('td' + location.hash).offset().top - $(e).height(); - $('body, html').scrollTop(scrollLocation); - } - prevAnchor = location.hash; - } - } + // We hid the header to avoid it showing up erroneously on page load; + // we need to unhide it now so that it will show up when expected. + this.stickyHeader.show(); +}; - // Only attach to scrollbars once, even if Drupal.attachBehaviors is called - // multiple times. - $('body').once(function () { - $(window).scroll(Drupal.tableHeaderDoScroll); - $(document.documentElement).scroll(Drupal.tableHeaderDoScroll); - }); +/** + * Event handler: recalculates position of the sticky table header. + * + * @param event + * Event being triggered. + */ +Drupal.tableHeader.prototype.eventhandlerRecalculateStickyHeader = function (event) { + var self = this; + var calculateWidth = event.data && event.data.calculateWidth; + + // Reset top position of sticky table headers to the current top offset. + this.stickyOffsetTop = Drupal.displace ? Drupal.displace.getDisplacement('top') : 0; + this.stickyTable.css('top', this.stickyOffsetTop + 'px'); + + // Save positioning data. + var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight; + if (calculateWidth || this.viewHeight !== viewHeight) { + this.viewHeight = viewHeight; + this.vPosition = this.originalTable.offset().top - 4 - this.stickyOffsetTop; + this.hPosition = this.originalTable.offset().left; + this.vLength = this.originalTable[0].clientHeight - 100; + calculateWidth = true; + } - // Track scrolling. - Drupal.tableHeaderOnScroll = function () { - $('table.sticky-header').each(function () { - tracker(this); - }); - }; - - // Track resizing. - var time = null; - var resize = function () { - // Ensure minimum time between adjustments. - if (time) { - return; + // Track horizontal positioning relative to the viewport and set visibility. + var hScroll = document.documentElement.scrollLeft || document.body.scrollLeft; + var vOffset = (document.documentElement.scrollTop || document.body.scrollTop) - this.vPosition; + this.stickyVisible = vOffset > 0 && vOffset < this.vLength; + this.stickyTable.css({ left: (-hScroll + this.hPosition) + 'px', visibility: this.stickyVisible ? 'visible' : 'hidden' }); + + // Only perform expensive calculations if the sticky header is actually + // visible or when forced. + if (this.stickyVisible && (calculateWidth || !this.widthCalculated)) { + this.widthCalculated = true; + // Resize header and its cell widths. + this.stickyHeaderCells.each(function (index) { + var cellWidth = self.originalHeaderCells.eq(index).css('width'); + // Exception for IE7. + if (cellWidth == 'auto') { + cellWidth = self.originalHeaderCells.get(index).clientWidth + 'px'; } - time = setTimeout(function () { - $('table.sticky-header').each(function () { - // Force cell width calculation. - this.viewHeight = 0; - tracker(this); - }); - // Reset timer. - time = null; - }, 250); - }; - $(window).resize(resize); + $(this).css('width', cellWidth); + }); + this.stickyTable.css('width', this.originalTable.css('width')); } }; Index: misc/drupal.js =================================================================== RCS file: /cvs/drupal/drupal/misc/drupal.js,v retrieving revision 1.68 diff -u -r1.68 drupal.js --- misc/drupal.js 24 May 2010 07:22:12 -0000 1.68 +++ misc/drupal.js 24 Jul 2010 02:15:39 -0000 @@ -331,7 +331,21 @@ // 'js enabled' cookie. document.cookie = 'has_js=1; path=/'; -// Attach all behaviors. +/** + * Additions to jQuery.support. + */ +$(function () { + /** + * Boolean indicating whether or not position:fixed is supported. + */ + if (jQuery.support.positionFixed === undefined) { + var el = $('
').appendTo(document.body); + jQuery.support.positionFixed = el[0].offsetTop === 10; + el.remove(); + } +}); + +//Attach all behaviors. $(function () { Drupal.attachBehaviors(document, Drupal.settings); }); Index: modules/system/system.css =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.css,v retrieving revision 1.77 diff -u -r1.77 system.css --- modules/system/system.css 23 May 2010 18:23:32 -0000 1.77 +++ modules/system/system.css 24 Jul 2010 02:15:39 -0000 @@ -257,41 +257,31 @@ /* ** To be used with displace.js */ +.displace-processed body { + /*position: relative;*/ +} .displace-top, .displace-bottom { position: relative; width: 100%; } .displace-processed .displace-top, -.displace-processed .displace-bottom { +.displace-processed .displace-bottom, +.displace-processed .displace-absolute.displace-absolute-fixed { position: fixed; + z-index: 9999; width: auto; left: 0; right: 0; } -.displace-unsupported .displace-top, -.displace-unsupported .displace-bottom { +.displace-processed .displace-absolute { position: absolute; -} - -/* -** To be used with displace.js -*/ -.displace-top, -.displace-bottom { - position: relative; - width: 100%; -} -.displace-processed .displace-top, -.displace-processed .displace-bottom { - position: fixed; - width: auto; - left: 0; - right: 0; + z-index: 500; } .displace-unsupported .displace-top, .displace-unsupported .displace-bottom { position: absolute; + width: 100%; } /* Index: misc/displace.js =================================================================== RCS file: misc/displace.js diff -N misc/displace.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/displace.js 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,348 @@ +// $Id$ +(function ($) { + +/** + * Attaches the displace behavior. + */ +Drupal.behaviors.displace = { + + attach: function (context, settings) { + Drupal.displace.refresh(); + + $(document.documentElement).once('displace', function () { + // Detect when an anchor is being scrolled into view. + $(window).bind('scroll.drupal-displace-anchor', Drupal.displaceFocus.eventhandlerDetectAnchorScrolling); + // Detect when the focus is being set to an element. + $(document).bind('focusin.drupal-displace-focus', Drupal.displaceFocus.eventhandlerDetectFocusMovement); + }); + + $(window).triggerHandler('scroll.drupal-displace-anchor'); + }, + + detach: function (context, settings, trigger) { + if (trigger == 'unload' || trigger == 'move') { + Drupal.displace.clearCache(true); + } + } +}; + +/** + * Provides a generic method to act on focus movement. + */ +Drupal.displaceFocus = Drupal.displaceFocus || {}; +Drupal.displaceFocus.prototype = {}; + +/** + * Creates an event object containing position data. + * + * @param type + * Describes the nature of the event. + * @param target + * The DOM element that initiated the event. + */ +Drupal.displaceFocus.createEvent = function (type, target) { + var event = jQuery.Event(type), + $target = $(target), + targetOffset = $target.offset(), + $window = $(window); + + event.target = target; + event.$target = $target; + // The amount of pixels the entire document has been scrolled. + event.pageXOffset = $window.scrollLeft(); + event.pageYOffset = $window.scrollTop(); + // The position of the target element relative to the document. + event.pageX = Math.round(targetOffset.left); + event.pageY = Math.round(targetOffset.top); + // The position of the target element relative to the viewport. + event.clientX = event.pageX - event.pageXOffset; + event.clientY = event.pageY - event.pageYOffset; + + return event; +}; + +/** + * Event handler: detects when an anchor is being scrolled into view and + * triggers the custom event "drupalDisplaceFocus". + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: scroll + * - event.currentTarget: window + */ +Drupal.displaceFocus.eventhandlerDetectAnchorScrolling = function (event) { + if (location.hash && location.hash != '#') { + // Delay the timeout as long as events are triggered. + clearTimeout(Drupal.displaceFocus.displaceAnchorTimeout); + Drupal.displaceFocus.displaceAnchorTimeout = setTimeout(function () { + var $anchor = $(location.hash), $window = $(window); + if ($anchor.length && Math.round($anchor.offset().top) == $window.scrollTop()) { + $window.trigger(Drupal.displaceFocus.createEvent('drupalDisplaceFocus', $anchor[0])); + } + }, 100); + } +}; + +/** + * Event handler: detects when the focus is being set to an element and + * triggers the custom event "drupalDisplaceFocus". + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: focusin + * - event.currentTarget: document + */ +Drupal.displaceFocus.eventhandlerDetectFocusMovement = function (event) { + if (event.target.nodeType === 1) { + // Make sure target element is not fixed positioned. + var offsetParent = $(event.target).offsetParent(); + while (offsetParent.length && offsetParent[0] !== document.body) { + if (offsetParent.css('position') == 'fixed') { + return; + } + offsetParent = $(offsetParent[0]).offsetParent(); + } + + $(window).trigger(Drupal.displaceFocus.createEvent('drupalDisplaceFocus', event.target)); + } +}; + +/** + * Provides a generic method to position elements fixed to the viewport. + * + * Fixed positioning (CSS declaration position:fixed) is done relative to the + * viewport. This makes it hard to position multiple fixed positioned element + * relative to each other (e.g. multiple toolbars should come after each other, + * not on top of each other). + * + * To position an element fixed at the top of the viewport add the class + * "displace-top" to that element, and to position it to the bottom of the view- + * port add the class "displace-bottom". + * + * To position an element absolute at the top of the viewport also add the + * class "displace-absolute". + * + * When a displacement region contains both absolute and fixed positioned + * elements, the absolute positioned elements will be positioned fixed too, but + * will only be visible when they would be visible if they were positioned + * absolute. This is done to improve the look & feel. + * + * When a browser does not support position:fixed (like IE6) the displaced + * elements automaticly receive the class "displace-unsupported" and are + * positioned absolute. + */ +Drupal.displace = Drupal.displace || {}; +Drupal.displace.prototype = {}; + +/** + * Get all displaced elements of given region. + * + * @see modules/overlay/overlay-child.js + */ +Drupal.displace.refresh = function () { + var $window = $(window); + + // Test for position:fixed support. + if (!$.support.positionFixed) { + $(document.documentElement).addClass('displace-unsupported'); + } + + Drupal.displace.clearCache(true); + + if (Drupal.displace.forceHandlers || Drupal.displace.getDisplacedElements('top').length || Drupal.displace.getDisplacedElements('bottom').length) { + var events = 'resize.drupal-displace'; + // If there are both absolute and fixed displaced elements also bind the + // handler to scroll events. + if (Drupal.displace.mixedDisplacement('top') || Drupal.displace.mixedDisplacement('bottom')) { + events += ' scroll.drupal-displace'; + } + $window + .bind(events, Drupal.displace.eventhandlerDisplaceDocument) + .bind('drupalDisplaceFocus.drupal-displace', Drupal.displace.eventhandlerDisplaceFocus); + } + // Unbind any previously bound event handlers as there are no displaced + // elements to be taken into account. + else { + $window.unbind('.drupal-displace'); + } + + $window.triggerHandler('resize.drupal-displace'); +}; + +/** + * Get all displaced elements of given region. + * + * @param region + * Region name. Either "top" or "bottom". + * + * @return + * jQuery object containing all displaced elements of given region. + */ +Drupal.displace.getDisplacedElements = function (region) { + if (!this._elements[region]) { + this._elements[region] = $('.displace-' + region); + } + return this._elements[region]; +}; + +/** + * Checks whether there are both absolute and fixed displaced elements. + * + * @param region + * Region name. Either "top" or "bottom". + * + * @return + * TRUE if there are both absolute and fixed displaced elements in given + * region, FALSE otherwise. + */ +Drupal.displace.mixedDisplacement = function (region) { + if (!this._mixedDisplacement[region]) { + this._mixedDisplacement[region] = Drupal.displace.getDisplacedElements(region).is('.displace-absolute') && Drupal.displace.getDisplacedElements(region).is(':not(.displace-absolute)'); + } + return this._mixedDisplacement[region]; +}; + +/** + * Get the total displacement of given region. + * + * @param region + * Region name. Either "top" or "bottom". + * + * @return + * The total displacement of given region in pixels. + * + * @see modules/overlay/overlay-child.js + */ +Drupal.displace.getDisplacement = function (region) { + if (!this._displacement[region]) { + var offset = Drupal.displace.initialOffset || 0; + var height = 0; + var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + this.getDisplacedElements(region).each(function () { + offset = offset + height; + height = $(this).css(region, offset).outerHeight(); + + // Make sure absolute and fixed displaced elements work together. + if (Drupal.displace.mixedDisplacement(region) && $(this).hasClass('displace-absolute')) { + if ((region == 'top' && scrollTop < offset + height) || (region == 'bottom' && scrollTop + $(window).height() > document.body.scrollHeight - offset + height)) { + $(this).addClass('displace-absolute-fixed'); + } + // Skip this absolute displaced element as it is outside the viewport. + else { + $(this).removeClass('displace-absolute-fixed'); + height = 0; + } + } + + // In IE, Shadow filter adds some extra height, so we need to remove it + // from the returned height. + if (this.filters && this.filters.length && this.filters.item('DXImageTransform.Microsoft.Shadow')) { + height -= this.filters.item('DXImageTransform.Microsoft.Shadow').strength; + height = Math.max(0, height); + } + }); + + // Use offset of latest displaced element as the total displacement. + this._displacement[region] = offset + height; + } + + return this._displacement[region]; +}; + +/** + * Clear cache. + * + * @param selectorCache + * Boolean whether to also clear the selector cache. + */ +Drupal.displace.clearCache = function (selectorCache) { + if (selectorCache) { + this._elements = []; + this._mixedDisplacement = []; + } + this._displacement = []; +}; + +/** + * Event handler: makes sure there is enough space for displaced elements at the + * top and bottom of the document. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: resize, scroll + * - event.currentTarget: window + */ +Drupal.displace.eventhandlerDisplaceDocument = function (event) { + Drupal.displace.clearCache(); + + // Adjust paddingTop and paddingBottom of document root element to make sure + // nothing is hidden beneath any displaced elements. + var adjustment = { + marginTop: Drupal.displace.getDisplacement('top') || null, + marginBottom: Drupal.displace.getDisplacement('bottom') || null + }; + + // If position:fixed is not supported, displaced elements are positioned + // absolute; their positions are relative to their offset parent (BODY) and + // will therefor be moved along with the padding; move them back to the top + // or bottom of the window. + if (!$.support.positionFixed && (adjustment.marginTop || adjustment.marginBottom)) { + var regions = [ 'top', 'bottom' ]; + for (var i in regions) { + Drupal.displace.getDisplacedElements(regions[i]).each(function () { + $(this).css(regions[i], (parseInt($(this).css(regions[i])) - Drupal.displace.getDisplacement(regions[i]))); + }); + } + } + + // Apply adjustments to the document. + $(document.body).css(adjustment); +}; + +/** + * Event handler: makes sure the element being focused is not hidden beneath any + * displaced elements. Adjusts the scrollTop if it does. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: drupalDisplaceFocus + * - event.currentTarget: window + */ +Drupal.displace.eventhandlerDisplaceFocus = function (event) { + var displacement = Drupal.displace.getDisplacement('top'); + if (event.clientY < displacement) { + window.scrollBy(0, -(displacement - event.clientY)); + } +}; + +// Adjust positions when beneath a displaced element. +var _position = $.fn.position; +$.fn.position = function(options) { + var ret = _position.apply(this, arguments); + if (ret.each) { + ret.each(function() { + var elem = $(this), offset = elem.offset(); + + if (offset.top < Drupal.displace.getDisplacement('top')) { + offset.top = Drupal.displace.getDisplacement('top'); + elem.offset(offset); + } + }); + } + return ret; +}; + +// If an draggable's containment is set to document or window adjust it with +// the displacement. +if ($.ui && $.ui.draggable) { + var _setContainment= $.ui.draggable.prototype._setContainment; + $.ui.draggable.prototype._setContainment = function() { + _setContainment.apply(this, arguments); + if (this.options.containment == 'document' || this.options.containment == 'window') { + this.containment[1] += Drupal.displace.getDisplacement('top'); + this.containment[3] += Drupal.displace.getDisplacement('bottom'); + } + }; +} + +})(jQuery);