You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
13 KiB
JavaScript
224 lines
13 KiB
JavaScript
![]()
15 years ago
|
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);
|
||
|
}
|
||
|
};
|