UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

393 lines (332 loc) 13.5 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) ************************************************************************ */ /** * Contains methods to control and query the element's scroll properties */ qx.Class.define("qx.bom.element.Scroll", { /* ***************************************************************************** STATICS ***************************************************************************** */ statics : { /** @type {Integer} The typical native scrollbar size in the environment */ __scrollbarSize : null, /** * Get the typical native scrollbar size in the environment * * @return {Number} The native scrollbar size */ getScrollbarWidth : function() { if (this.__scrollbarSize !== null) { return this.__scrollbarSize; } var Style = qx.bom.element.Style; var getStyleSize = function(el, propertyName) { return parseInt(Style.get(el, propertyName), 10) || 0; }; var getBorderRight = function(el) { return ( Style.get(el, "borderRightStyle") == "none" ? 0 : getStyleSize(el, "borderRightWidth") ); }; var getBorderLeft = function(el) { return ( Style.get(el, "borderLeftStyle") == "none" ? 0 : getStyleSize(el, "borderLeftWidth") ); }; var getInsetRight = qx.core.Environment.select("engine.name", { "mshtml" : function(el) { if ( Style.get(el, "overflowY") == "hidden" || el.clientWidth == 0 ) { return getBorderRight(el); } return Math.max(0, el.offsetWidth - el.clientLeft - el.clientWidth); }, "default" : function(el) { // Alternative method if clientWidth is unavailable // clientWidth == 0 could mean both: unavailable or really 0 if (el.clientWidth == 0) { var ov = Style.get(el, "overflow"); var sbv = ( ov == "scroll" || ov == "-moz-scrollbars-vertical" ? 16 : 0 ); return Math.max(0, getBorderRight(el) + sbv); } return Math.max( 0, (el.offsetWidth - el.clientWidth - getBorderLeft(el)) ); } }); var getScrollBarSizeRight = function(el) { return getInsetRight(el) - getBorderRight(el); }; var t = document.createElement("div"); var s = t.style; s.height = s.width = "100px"; s.overflow = "scroll"; document.body.appendChild(t); var c = getScrollBarSizeRight(t); this.__scrollbarSize = c; document.body.removeChild(t); return this.__scrollbarSize; }, /* --------------------------------------------------------------------------- SCROLL INTO VIEW --------------------------------------------------------------------------- */ /** * The method scrolls the element into view (x-axis only). * * @param element {Element} DOM element to scroll into view * @param stop {Element?null} Any parent element which functions as * outermost element to scroll. Default is the HTML document. * @param align {String?null} Alignment of the element. Allowed values: * <code>left</code> or <code>right</code>. Could also be null. * Without a given alignment the method tries to scroll the widget * with the minimum effort needed. */ intoViewX : function(element, stop, align) { var parent = element.parentNode; var doc = qx.dom.Node.getDocument(element); var body = doc.body; var parentLocation, parentLeft, parentRight; var parentOuterWidth, parentClientWidth, parentScrollWidth; var parentLeftBorder, parentRightBorder, parentScrollBarWidth; var elementLocation, elementLeft, elementRight, elementWidth; var leftOffset, rightOffset, scrollDiff; var alignLeft = align === "left"; var alignRight = align === "right"; // Correcting stop position stop = stop ? stop.parentNode : doc; // Go up the parent chain while (parent && parent != stop) { // "overflow" is always visible for both: document.body and document.documentElement if (parent.scrollWidth > parent.clientWidth && (parent === body || qx.bom.element.Style.get(parent, "overflowY") != "visible")) { // Calculate parent data // Special handling for body element if (parent === body) { parentLeft = parent.scrollLeft; parentRight = parentLeft + qx.bom.Viewport.getWidth(); parentOuterWidth = qx.bom.Viewport.getWidth(); parentClientWidth = parent.clientWidth; parentScrollWidth = parent.scrollWidth; parentLeftBorder = 0; parentRightBorder = 0; parentScrollBarWidth = 0; } else { parentLocation = qx.bom.element.Location.get(parent); parentLeft = parentLocation.left; parentRight = parentLocation.right; parentOuterWidth = parent.offsetWidth; parentClientWidth = parent.clientWidth; parentScrollWidth = parent.scrollWidth; parentLeftBorder = parseInt(qx.bom.element.Style.get(parent, "borderLeftWidth"), 10) || 0; parentRightBorder = parseInt(qx.bom.element.Style.get(parent, "borderRightWidth"), 10) || 0; parentScrollBarWidth = parentOuterWidth - parentClientWidth - parentLeftBorder - parentRightBorder; } // Calculate element data elementLocation = qx.bom.element.Location.get(element); elementLeft = elementLocation.left; elementRight = elementLocation.right; elementWidth = element.offsetWidth; // Relative position from each other leftOffset = elementLeft - parentLeft - parentLeftBorder; rightOffset = elementRight - parentRight + parentRightBorder; // Scroll position rearrangement scrollDiff = 0; // be sure that element is on left edge if (alignLeft) { scrollDiff = leftOffset; } // be sure that element is on right edge else if (alignRight) { scrollDiff = rightOffset + parentScrollBarWidth; } // element must go down // * when current left offset is smaller than 0 // * when width is bigger than the inner width of the parent else if (leftOffset < 0 || elementWidth > parentClientWidth) { scrollDiff = leftOffset; } // element must go up // * when current right offset is bigger than 0 else if (rightOffset > 0) { scrollDiff = rightOffset + parentScrollBarWidth; } parent.scrollLeft += scrollDiff; // Browsers that follow the CSSOM View Spec fire the "scroll" // event asynchronously. See #intoViewY for more details. qx.event.Registration.fireNonBubblingEvent(parent, "scroll"); } if (parent === body) { break; } parent = parent.parentNode; } }, /** * The method scrolls the element into view (y-axis only). * * @param element {Element} DOM element to scroll into view * @param stop {Element?null} Any parent element which functions as * outermost element to scroll. Default is the HTML document. * @param align {String?null} Alignment of the element. Allowed values: * <code>top</code> or <code>bottom</code>. Could also be null. * Without a given alignment the method tries to scroll the widget * with the minimum effort needed. */ intoViewY : function(element, stop, align) { var parent = element.parentNode; var doc = qx.dom.Node.getDocument(element); var body = doc.body; var parentLocation, parentTop, parentBottom; var parentOuterHeight, parentClientHeight, parentScrollHeight; var parentTopBorder, parentBottomBorder, parentScrollBarHeight; var elementLocation, elementTop, elementBottom, elementHeight; var topOffset, bottomOffset, scrollDiff; var alignTop = align === "top"; var alignBottom = align === "bottom"; // Correcting stop position stop = stop ? stop.parentNode : doc; // Go up the parent chain while (parent && parent != stop) { // "overflow" is always visible for both: document.body and document.documentElement if (parent.scrollHeight > parent.clientHeight && (parent === body || qx.bom.element.Style.get(parent, "overflowY") != "visible")) { // Calculate parent data // Special handling for body element if (parent === body) { parentTop = parent.scrollTop; parentBottom = parentTop + qx.bom.Viewport.getHeight(); parentOuterHeight = qx.bom.Viewport.getHeight(); parentClientHeight = parent.clientHeight; parentScrollHeight = parent.scrollHeight; parentTopBorder = 0; parentBottomBorder = 0; parentScrollBarHeight = 0; } else { parentLocation = qx.bom.element.Location.get(parent); parentTop = parentLocation.top; parentBottom = parentLocation.bottom; parentOuterHeight = parent.offsetHeight; parentClientHeight = parent.clientHeight; parentScrollHeight = parent.scrollHeight; parentTopBorder = parseInt(qx.bom.element.Style.get(parent, "borderTopWidth"), 10) || 0; parentBottomBorder = parseInt(qx.bom.element.Style.get(parent, "borderBottomWidth"), 10) || 0; parentScrollBarHeight = parentOuterHeight - parentClientHeight - parentTopBorder - parentBottomBorder; } // Calculate element data elementLocation = qx.bom.element.Location.get(element); elementTop = elementLocation.top; elementBottom = elementLocation.bottom; elementHeight = element.offsetHeight; // Relative position from each other topOffset = elementTop - parentTop - parentTopBorder; bottomOffset = elementBottom - parentBottom + parentBottomBorder; // Scroll position rearrangement scrollDiff = 0; // be sure that element is on top edge if (alignTop) { scrollDiff = topOffset; } // be sure that element is on bottom edge else if (alignBottom) { scrollDiff = bottomOffset + parentScrollBarHeight; } // element must go down // * when current top offset is smaller than 0 // * when height is bigger than the inner height of the parent else if (topOffset < 0 || elementHeight > parentClientHeight) { scrollDiff = topOffset; } // element must go up // * when current bottom offset is bigger than 0 else if (bottomOffset > 0) { scrollDiff = bottomOffset + parentScrollBarHeight; } parent.scrollTop += scrollDiff; // Browsers that follow the CSSOM View Spec fire the "scroll" // event asynchronously. // // The widget layer expects the "scroll" event to be fired before // the "appear" event. Fire non-bubbling "scroll" in all browsers, // since a duplicate "scroll" should not cause any issues and it // is hard to track which version of the browser engine started to // follow the CSSOM Spec. Fixes [BUG #4570]. qx.event.Registration.fireNonBubblingEvent(parent, "scroll"); } if (parent === body) { break; } parent = parent.parentNode; } }, /** * The method scrolls the element into view. * * @param element {Element} DOM element to scroll into view * @param stop {Element?null} Any parent element which functions as * outermost element to scroll. Default is the HTML document. * @param alignX {String} Alignment of the element. Allowed values: * <code>left</code> or <code>right</code>. Could also be undefined. * Without a given alignment the method tries to scroll the widget * with the minimum effort needed. * @param alignY {String} Alignment of the element. Allowed values: * <code>top</code> or <code>bottom</code>. Could also be undefined. * Without a given alignment the method tries to scroll the widget * with the minimum effort needed. */ intoView : function(element, stop, alignX, alignY) { this.intoViewX(element, stop, alignX); this.intoViewY(element, stop, alignY); } } });