dojo.provide("dijit._base.scroll"); dijit.scrollIntoView = function(/* DomNode */node){ // summary: // Scroll the passed node into view, if it is not. // don't rely on that node.scrollIntoView works just because the function is there // it doesnt work in Konqueror or Opera even though the function is there and probably // not safari either // native scrollIntoView() causes FF3's whole window to scroll if there is no scroll bar // on the immediate parent // dont like browser sniffs implementations but sometimes you have to use it // It's not enough just to scroll the menu node into view if // node.scrollIntoView hides part of the parent's scrollbar, // so just manage the parent scrollbar ourselves //var testdir="H"; //debug try{ // catch unexpected/unrecreatable errors (#7808) since we can recover using a semi-acceptable native method node = dojo.byId(node); var doc = dojo.doc; var body = dojo.body(); var html = body.parentNode; // if FF2 (which is perfect) or an untested browser, then use the native method if((!(dojo.isFF >= 3 || dojo.isIE || dojo.isWebKit) || node == body || node == html) && (typeof node.scrollIntoView == "function")){ // FF2 is perfect, too bad FF3 is not node.scrollIntoView(false); // short-circuit to native if possible return; } var ltr = dojo._isBodyLtr(); var isIE8strict = dojo.isIE >= 8 && !compatMode; var rtl = !ltr && !isIE8strict; // IE8 flips scrolling so pretend it's ltr // body and html elements are all messed up due to browser bugs and inconsistencies related to doctype // normalize the values before proceeding (FF2 is not listed since its native behavior is perfect) // for computation simplification, client and offset width and height are the same for body and html // strict: html: | body: | compatMode: // width height | width height |------------ // ie*: clientW clientH | scrollW clientH | CSS1Compat // ff3: clientW clientH |HscrollW clientH | CSS1Compat // sf3: clientW clientH | clientW HclientH | CSS1Compat // op9: clientW clientH |HscrollW clientH | CSS1Compat // ---------------------------------------------|----------- // none: html: | body: | // width height | width height | // ie*: BclientW BclientH | clientW clientH | BackCompat // ff3: BclientW BclientH | clientW clientH | BackCompat // sf3: clientW clientH | clientW HclientH | CSS1Compat // op9: BclientW BclientH | clientW clientH | BackCompat // ---------------------------------------------|----------- // loose: html: | body: | // width height | width height | // ie*: clientW clientH | scrollW clientH | CSS1Compat // ff3: BclientW BclientH | clientW clientH | BackCompat // sf3: clientW clientH | clientW HclientH | CSS1Compat // op9: clientW clientH |HscrollW clientH | CSS1Compat var scrollRoot = body; var compatMode = doc.compatMode == 'BackCompat'; if(compatMode){ // BODY is scrollable, HTML has same client size // body client values already OK html._offsetWidth = html._clientWidth = body._offsetWidth = body.clientWidth; html._offsetHeight = html._clientHeight = body._offsetHeight = body.clientHeight; }else{ if(dojo.isWebKit){ body._offsetWidth = body._clientWidth = html.clientWidth; body._offsetHeight = body._clientHeight = html.clientHeight; }else{ scrollRoot = html; } html._offsetHeight = html.clientHeight; html._offsetWidth = html.clientWidth; } function isFixedPosition(element){ var ie = dojo.isIE; return ((ie <= 6 || (ie >= 7 && compatMode))? false : (dojo.style(element, 'position').toLowerCase() == "fixed")); } function addPseudoAttrs(element){ var parent = element.parentNode; var offsetParent = element.offsetParent; if(offsetParent == null || isFixedPosition(element)){ // position:fixed has no real offsetParent offsetParent = html; // prevents exeptions parent = (element == body)? html : null; } // all the V/H object members below are to reuse code for both directions element._offsetParent = offsetParent; element._parent = parent; //console.debug('parent = ' + (element._parentTag = element._parent?element._parent.tagName:'NULL')); //console.debug('offsetParent = ' + (element._offsetParentTag = element._offsetParent.tagName)); var bp = dojo._getBorderExtents(element); element._borderStart = { H:(isIE8strict && !ltr)? (bp.w-bp.l):bp.l, V:bp.t }; element._borderSize = { H:bp.w, V:bp.h }; element._scrolledAmount = { H:element.scrollLeft, V:element.scrollTop }; element._offsetSize = { H: element._offsetWidth||element.offsetWidth, V: element._offsetHeight||element.offsetHeight }; //console.debug('element = ' + element.tagName + ', '+testdir+' size = ' + element[testdir=='H'?"offsetWidth":"offsetHeight"] + ', parent = ' + element._parentTag); // IE8 flips everything in rtl mode except offsetLeft and borderLeft - so manually change offsetLeft to offsetRight here element._offsetStart = { H:(isIE8strict && !ltr)? offsetParent.clientWidth-element.offsetLeft-element._offsetSize.H:element.offsetLeft, V:element.offsetTop }; //console.debug('element = ' + element.tagName + ', initial _relativeOffset = ' + element._offsetStart[testdir]); element._clientSize = { H:element._clientWidth||element.clientWidth, V:element._clientHeight||element.clientHeight }; if(element != body && element != html && element != node){ for(var dir in element._offsetSize){ // for both x and y directions var scrollBarSize = element._offsetSize[dir] - element._clientSize[dir] - element._borderSize[dir]; //if(dir==testdir)console.log('element = ' + element.tagName + ', scrollBarSize = ' + scrollBarSize + ', clientSize = ' + element._clientSize[dir] + ', offsetSize = ' + element._offsetSize[dir] + ', border size = ' + element._borderSize[dir]); var hasScrollBar = element._clientSize[dir] > 0 && scrollBarSize > 0; // can't check for a specific scrollbar size since it changes dramatically as you zoom //if(dir==testdir)console.log('element = ' + element.tagName + ', hasScrollBar = ' + hasScrollBar); if(hasScrollBar){ element._offsetSize[dir] -= scrollBarSize; if(dojo.isIE && rtl && dir=="H"){ element._offsetStart[dir] += scrollBarSize; } } } } } var element = node; while(element != null){ if(isFixedPosition(element)){ node.scrollIntoView(false); return; } //TODO: handle without native call addPseudoAttrs(element); element = element._parent; } if(dojo.isIE && node._parent){ // if no parent, then offsetParent._borderStart may not tbe set var offsetParent = node._offsetParent; //console.debug('adding offsetParent borderStart = ' + offsetParent._borderStart.H + ' to node offsetStart'); node._offsetStart.H += offsetParent._borderStart.H; node._offsetStart.V += offsetParent._borderStart.V; } if(dojo.isIE >= 7 && scrollRoot == html && rtl && body._offsetStart && body._offsetStart.H == 0){ // IE7 bug var scroll = html.scrollWidth - html._offsetSize.H; if(scroll > 0){ //console.debug('adjusting html scroll by ' + -scroll + ', scrollWidth = ' + html.scrollWidth + ', offsetSize = ' + html._offsetSize.H); body._offsetStart.H = -scroll; } } if(dojo.isIE <= 6 && !compatMode){ html._offsetSize.H += html._borderSize.H; html._offsetSize.V += html._borderSize.V; } // eliminate offsetLeft/Top oddities by tweaking scroll for ease of computation if(rtl && body._offsetStart && scrollRoot == html && html._scrolledAmount){ var ofs = body._offsetStart.H; if(ofs < 0){ html._scrolledAmount.H += ofs; body._offsetStart.H = 0; } } element = node; while(element){ var parent = element._parent; if(!parent){ break; } //console.debug('element = ' + element.tagName + ', parent = ' + parent.tagName + ', parent offsetSize = ' + parent._offsetSize[testdir] + ' clientSize = ' + parent._clientSize[testdir]); if(parent.tagName == "TD"){ var table = parent._parent._parent._parent; // point to TABLE if(parent != element._offsetParent && parent._offsetParent != element._offsetParent){ parent = table; // child of TD has the same offsetParent as TABLE, so skip TD, TR, and TBODY (ie. verticalslider) } } // check if this node and its parent share the same offsetParent var relative = element._offsetParent == parent; //console.debug('element = ' + element.tagName + ', offsetParent = ' + element._offsetParent.tagName + ', parent = ' + parent.tagName + ', relative = ' + relative); for(var dir in element._offsetStart){ // for both x and y directions var otherDir = dir=="H"? "V" : "H"; if(rtl && dir=="H" && (parent != html) && (parent != body) && (dojo.isIE || dojo.isWebKit) && parent._clientSize.H > 0 && parent.scrollWidth > parent._clientSize.H){ // scroll starts on the right var delta = parent.scrollWidth - parent._clientSize.H; //console.debug('rtl scroll delta = ' + delta + ', changing ' + parent.tagName + ' scroll from ' + parent._scrolledAmount.H + ' to ' + (parent._scrolledAmount.H - delta) + ', parent.scrollWidth = ' + parent.scrollWidth + ', parent._clientSize.H = ' + parent._clientSize.H); if(delta > 0){ parent._scrolledAmount.H -= delta; } // match FF3 which has cool negative scrollLeft values } if(parent._offsetParent.tagName == "TABLE"){ // make it consistent if(dojo.isIE){ // make it consistent with Safari and FF3 and exclude the starting TABLE border of TABLE children parent._offsetStart[dir] -= parent._offsetParent._borderStart[dir]; parent._borderStart[dir] = parent._borderSize[dir] = 0; } else{ parent._offsetStart[dir] += parent._offsetParent._borderStart[dir]; } } //if(dir==testdir)console.debug('border start = ' + parent._borderStart[dir] + ', border size = ' + parent._borderSize[dir]); if(dojo.isIE){ //if(dir==testdir)console.debug('changing parent offsetStart from ' + parent._offsetStart[dir] + ' by adding offsetParent ' + parent._offsetParent.tagName + ' border start = ' + parent._offsetParent._borderStart[dir]); parent._offsetStart[dir] += parent._offsetParent._borderStart[dir]; } //if(dir==testdir)console.debug('subtracting border start = ' + parent._borderStart[dir]); // underflow = visible gap between parent and this node taking scrolling into account // if negative, part of the node is obscured by the parent's beginning and should be scrolled to become visible var underflow = element._offsetStart[dir] - parent._scrolledAmount[dir] - (relative? 0 : parent._offsetStart[dir]) - parent._borderStart[dir]; // if overflow is positive, number of pixels obscured by the parent's end var overflow = underflow + element._offsetSize[dir] - parent._offsetSize[dir] + parent._borderSize[dir]; //if(dir==testdir)console.debug('element = ' + element.tagName + ', offsetStart = ' + element._offsetStart[dir] + ', relative = ' + relative + ', parent offsetStart = ' + parent._offsetStart[dir] + ', scroll = ' + parent._scrolledAmount[dir] + ', parent border start = ' + parent._borderStart[dir] + ', parent border size = ' + parent._borderSize[dir] + ', underflow = ' + underflow + ', overflow = ' + overflow + ', element offsetSize = ' + element._offsetSize[dir] + ', parent offsetSize = ' + parent._offsetSize[dir]); var scrollAttr = (dir=="H")? "scrollLeft" : "scrollTop"; // see if we should scroll forward or backward var reverse = dir=="H" && rtl; // flip everything var underflowScroll = reverse? -overflow : underflow; var overflowScroll = reverse? -underflow : overflow; // don't scroll if the over/underflow signs are opposite since that means that // the node extends beyond parent's boundary in both/neither directions var scrollAmount = (underflowScroll*overflowScroll <= 0)? 0 : Math[(underflowScroll < 0)? "max" : "min"](underflowScroll, overflowScroll); //if(dir==testdir)console.debug('element = ' + element.tagName + ' dir = ' + dir + ', scrollAmount = ' + scrollAmount); if(scrollAmount != 0){ var oldScroll = parent[scrollAttr]; parent[scrollAttr] += (reverse)? -scrollAmount : scrollAmount; // actually perform the scroll var scrolledAmount = parent[scrollAttr] - oldScroll; // in case the scroll failed //if(dir==testdir)console.debug('scrolledAmount = ' + scrolledAmount); } if(relative){ element._offsetStart[dir] += parent._offsetStart[dir]; } element._offsetStart[dir] -= parent[scrollAttr]; } element._parent = parent._parent; element._offsetParent = parent._offsetParent; } parent = node; var next; while(parent && parent.removeAttribute){ next = parent.parentNode; parent.removeAttribute('_offsetParent'); parent.removeAttribute('_parent'); parent = next; } }catch(error){ console.error('scrollIntoView: ' + error); node.scrollIntoView(false); } };