Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

if(!dojo._hasResource["dojo._base.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo._base.html"] = true;
dojo.require("dojo._base.lang");
dojo.provide("dojo._base.html");

// FIXME: need to add unit tests for all the semi-public methods

try{
        document.execCommand("BackgroundImageCache", false, true);
}catch(e){
        // sane browsers don't have cache "issues"
}

// =============================
// DOM Functions
// =============================

/*=====
dojo.byId = function(id, doc){
        //      summary:
        //              similar to other library's "$" function, takes a
        //              string representing a DOM id or a DomNode
        //              and returns the corresponding DomNode. If a Node is
        //              passed, this function is a no-op. Returns a
        //              single DOM node or null, working around several
        //              browser-specific bugs to do so.
        //      id: String|DOMNode
        //              DOM id or DOM Node
        //      doc: DocumentElement
        //              Document to work in. Defaults to the current value of
        //              dojo.doc.  Can be used to retreive
        //              node references from other documents.
=====*/
if(dojo.isIE || dojo.isOpera){
        dojo.byId = function(id, doc){
                if(dojo.isString(id)){
                        var _d = doc || dojo.doc;
                        var te = _d.getElementById(id);
                        // attributes.id.value is better than just id in case the 
                        // user has a name=id inside a form
                        if(te && te.attributes.id.value == id){
                                return te;
                        }else{
                                var eles = _d.all[id];
                                if(!eles){ return; }
                                if(!eles.length){ return eles; }
                                // if more than 1, choose first with the correct id
                                var i=0;
                                while((te=eles[i++])){
                                        if(te.attributes.id.value == id){ return te; }
                                }
                        }
                }else{
                        return id; // DomNode
                }
        }
}else{
        dojo.byId = function(id, doc){
                if(dojo.isString(id)){
                        return (doc || dojo.doc).getElementById(id);
                }else{
                        return id; // DomNode
                }
        }
}
/*=====
}
=====*/

(function(){
        /*
        dojo.createElement = function(obj, parent, position){
                // TODO: need to finish this!
        }
        */

        var _destroyContainer = null;
        dojo._destroyElement = function(/*String||DomNode*/node){
                // summary:
                //              removes node from its parent, clobbers it and all of its
                //              children.
                //      node:
                //              the element to be destroyed, either as an ID or a reference

                node = dojo.byId(node);
                try{
                        if(!_destroyContainer){
                                _destroyContainer = document.createElement("div");
                        }
                        _destroyContainer.appendChild(node.parentNode ? node.parentNode.removeChild(node) : node);
                        // NOTE: see http://trac.dojotoolkit.org/ticket/2931. This may be a bug and not a feature
                        _destroyContainer.innerHTML = ""; 
                }catch(e){
                        /* squelch */
                }
        };

        dojo.isDescendant = function(/*DomNode|String*/node, /*DomNode|String*/ancestor){
                //      summary:
                //              Returns true if node is a descendant of ancestor
                //      node: id or node reference to test
                //      ancestor: id or node reference of potential parent to test against
                try{
                        node = dojo.byId(node);
                        ancestor = dojo.byId(ancestor);
                        while(node){
                                if(node === ancestor){
                                        return true; // Boolean
                                }
                                node = node.parentNode;
                        }
                }catch(e){ return -1; /* squelch */ }
                return false; // Boolean
        };

        dojo.setSelectable = function(/*DomNode|String*/node, /*Boolean*/selectable){
                //      summary: enable or disable selection on a node
                //      node:
                //              id or reference to node
                //      selectable:
                node = dojo.byId(node);
                if(dojo.isMozilla){
                        node.style.MozUserSelect = selectable ? "" : "none";
                }else if(dojo.isKhtml){
                        node.style.KhtmlUserSelect = selectable ? "auto" : "none";
                }else if(dojo.isIE){
                        node.unselectable = selectable ? "" : "on";
                        dojo.query("*", node).forEach(function(descendant){
                                descendant.unselectable = selectable ? "" : "on";
                        });
                }
                //FIXME: else?  Opera?
        };

        var _insertBefore = function(/*Node*/node, /*Node*/ref){
                ref.parentNode.insertBefore(node, ref);
                return true;    //      boolean
        }

        var _insertAfter = function(/*Node*/node, /*Node*/ref){
                //      summary:
                //              Try to insert node after ref
                var pn = ref.parentNode;
                if(ref == pn.lastChild){
                        pn.appendChild(node);
                }else{
                        return _insertBefore(node, ref.nextSibling);    //      boolean
                }
                return true;    //      boolean
        }

        dojo.place = function(/*String|DomNode*/node, /*String|DomNode*/refNode, /*String|Number*/position){
                //      summary:
                //              attempt to insert node in relation to ref based on position
                //      node: 
                //              id or reference to node to place relative to refNode
                //      refNode: 
                //              id or reference of node to use as basis for placement
                //      position:
                //              string noting the position of node relative to refNode or a
                //              number indicating the location in the childNodes collection of
                //              refNode. Accepted string values are:
                //                      * before
                //                      * after
                //                      * first
                //                      * last
                //              "first" and "last" indicate positions as children of refNode.

                // FIXME: need to write tests for this!!!!
                if(!node || !refNode || position === undefined){ 
                        return false;   //      boolean 
                }
                node = dojo.byId(node);
                refNode = dojo.byId(refNode);
                if(typeof position == "number"){
                        var cn = refNode.childNodes;
                        if((position == 0 && cn.length == 0) ||
                                cn.length == position){
                                refNode.appendChild(node); return true;
                        }
                        if(position == 0){
                                return _insertBefore(node, refNode.firstChild);
                        }
                        return _insertAfter(node, cn[position-1]);
                }
                switch(position.toLowerCase()){
                        case "before":
                                return _insertBefore(node, refNode);    //      boolean
                        case "after":
                                return _insertAfter(node, refNode);             //      boolean
                        case "first":
                                if(refNode.firstChild){
                                        return _insertBefore(node, refNode.firstChild); //      boolean
                                }else{
                                        refNode.appendChild(node);
                                        return true;    //      boolean
                                }
                                break;
                        default: // aka: last
                                refNode.appendChild(node);
                                return true;    //      boolean
                }
        }

        // Box functions will assume this model.
        // On IE/Opera, BORDER_BOX will be set if the primary document is in quirks mode.
        // Can be set to change behavior of box setters.
        
        // can be either:
        //      "border-box"
        //      "content-box" (default)
        dojo.boxModel = "content-box";
        
        // We punt per-node box mode testing completely.
        // If anybody cares, we can provide an additional (optional) unit 
        // that overrides existing code to include per-node box sensitivity.

        // Opera documentation claims that Opera 9 uses border-box in BackCompat mode.
        // but experiments (Opera 9.10.8679 on Windows Vista) indicate that it actually continues to use content-box.
        // IIRC, earlier versions of Opera did in fact use border-box.
        // Opera guys, this is really confusing. Opera being broken in quirks mode is not our fault.

        if(dojo.isIE /*|| dojo.isOpera*/){
                var _dcm = document.compatMode;
                // client code may have to adjust if compatMode varies across iframes
                dojo.boxModel = (_dcm=="BackCompat")||(_dcm=="QuirksMode")||(dojo.isIE<6) ? "border-box" : "content-box";
        }

        // =============================
        // Style Functions
        // =============================
        
        // getComputedStyle drives most of the style code.
        // Wherever possible, reuse the returned object.
        //
        // API functions below that need to access computed styles accept an 
        // optional computedStyle parameter.
        //
        // If this parameter is omitted, the functions will call getComputedStyle themselves.
        //
        // This way, calling code can access computedStyle once, and then pass the reference to 
        // multiple API functions. 
        //
        // This is a faux declaration to take pity on the doc tool

/*=====
        dojo.getComputedStyle = function(node){
                //      summary:
                //              Returns a "computed style" object.
                //      description:
                //              get "computed style" object which can be used to gather
                //              information about the current state of the rendered node. 
                //
                //              Note that this may behave differently on different browsers.
                //              Values may have different formats and value encodings across
                //              browsers. 
                //
                //              Use the dojo.style() method for more consistent (pixelized)
                //              return values.
                //      node: DOMNode
                //              a reference to a DOM node. Does NOT support taking an
                //              ID string for speed reasons.
                //      example:
                //      |       dojo.getComputedStyle(dojo.byId('foo')).borderWidth;
                return; // CSS2Properties
        }
=====*/

        var gcs, dv = document.defaultView;
        if(dojo.isSafari){
                gcs = function(/*DomNode*/node){
                        var s = dv.getComputedStyle(node, null);
                        if(!s && node.style){ 
                                node.style.display = ""; 
                                s = dv.getComputedStyle(node, null);
                        }
                        return s || {};
                }; 
        }else if(dojo.isIE){
                gcs = function(node){
                        return node.currentStyle;
                };
        }else{
                gcs = function(node){
                        return dv.getComputedStyle(node, null);
                };
        }
        dojo.getComputedStyle = gcs;

        if(!dojo.isIE){
                dojo._toPixelValue = function(element, value){
                        // style values can be floats, client code may want
                        // to round for integer pixels.
                        return parseFloat(value) || 0; 
                }
        }else{
                dojo._toPixelValue = function(element, avalue){
                        if(!avalue){ return 0; }
                        // on IE7, medium is usually 4 pixels
                        if(avalue=="medium"){ return 4; }
                        // style values can be floats, client code may
                        // want to round this value for integer pixels.
                        if(avalue.slice && (avalue.slice(-2)=='px')){ return parseFloat(avalue); }
                        with(element){
                                var sLeft = style.left;
                                var rsLeft = runtimeStyle.left;
                                runtimeStyle.left = currentStyle.left;
                                try{
                                        // 'avalue' may be incompatible with style.left, which can cause IE to throw
                                        // this has been observed for border widths using "thin", "medium", "thick" constants
                                        // those particular constants could be trapped by a lookup
                                        // but perhaps there are more
                                        style.left = avalue;
                                        avalue = style.pixelLeft;
                                }catch(e){
                                        avalue = 0;
                                }
                                style.left = sLeft;
                                runtimeStyle.left = rsLeft;
                        }
                        return avalue;
                }
        }

        // FIXME: there opacity quirks on FF that we haven't ported over. Hrm.
        /*=====
        dojo._getOpacity = function(node){
                        //      summary:
                        //              Returns the current opacity of the passed node as a
                        //              floating-point value between 0 and 1.
                        //      node: DomNode
                        //              a reference to a DOM node. Does NOT support taking an
                        //              ID string for speed reasons.
                        //      return: Number between 0 and 1
        }
        =====*/

        dojo._getOpacity = (dojo.isIE ? function(node){
                        try{
                                return (node.filters.alpha.opacity / 100); // Number
                        }catch(e){
                                return 1; // Number
                        }
                } : function(node){
                        return dojo.getComputedStyle(node).opacity;
                }
        );

        /*=====
        dojo._setOpacity = function(node, opacity){
                        //      summary:
                        //              set the opacity of the passed node portably. Returns the
                        //              new opacity of the node.
                        //      node: DOMNode
                        //              a reference to a DOM node. Does NOT support taking an
                        //              ID string for performance reasons.
                        //      opacity: Number
                        //              A Number between 0 and 1. 0 specifies transparent.
                        //      return: Number between 0 and 1
        }
        =====*/

        dojo._setOpacity = (dojo.isIE ? function(/*DomNode*/node, /*Number*/opacity){
                        if(opacity == 1){
                                // on IE7 Alpha(Filter opacity=100) makes text look fuzzy so remove it altogether (bug #2661)
                                node.style.cssText = node.style.cssText.replace(/FILTER:[^;]*;/i, "");
                                if(node.nodeName.toLowerCase() == "tr"){
                                        dojo.query("> td", node).forEach(function(i){
                                                i.style.cssText = i.style.cssText.replace(/FILTER:[^;]*;/i, "");
                                        });
                                }
                        }else{
                                var o = "Alpha(Opacity="+(opacity*100)+")";
                                node.style.filter = o;
                        }
                        if(node.nodeName.toLowerCase() == "tr"){
                                dojo.query("> td", node).forEach(function(i){
                                        i.style.filter = o;
                                });
                        }
                        return opacity;
                } : function(node, opacity){
                        return node.style.opacity = opacity;
                }
        );

        var _pixelNamesCache = {
                width: true, height: true, left: true, top: true
        };
        var _toStyleValue = function(node, type, value){
                type = type.toLowerCase();
                if(_pixelNamesCache[type] === true){
                        return dojo._toPixelValue(node, value)
                }else if(_pixelNamesCache[type] === false){
                        return value;
                }else{
                        if(dojo.isOpera && type == "cssText"){
                                // FIXME: add workaround for #2855 here
                        }
                        if(
                                (type.indexOf("margin") >= 0) ||
                                // (type.indexOf("border") >= 0) ||
                                (type.indexOf("padding") >= 0) ||
                                (type.indexOf("width") >= 0) ||
                                (type.indexOf("height") >= 0) ||
                                (type.indexOf("max") >= 0) ||
                                (type.indexOf("min") >= 0) ||
                                (type.indexOf("offset") >= 0)
                        ){
                                _pixelNamesCache[type] = true;
                                return dojo._toPixelValue(node, value)
                        }else{
                                _pixelNamesCache[type] = false;
                                return value;
                        }
                }
        }

        // public API
        
        dojo.style = function(/*DomNode|String*/ node, /*String*/style, /*String?*/value){
                //      summary:
                //              gets or sets a style property on node. If 2 arguments are
                //              passed, acts as a getter. If value is passed, acts as a setter
                //              for the property.
                //      node:
                //              id or reference to node to get/set style for
                //      style:
                //              the style property to set in DOM-accessor format
                //              ("borderWidth", not "border-width").
                //      value:
                //              optional. If passed, sets value on the node for style, handling
                //              cross-browser concerns.
                var n=dojo.byId(node), args=arguments.length, op=(style=="opacity");
                if(args==3){
                        return op ? dojo._setOpacity(n, value) : n.style[style] = value; /*Number*/
                }
                if(args==2 && op){
                        return dojo._getOpacity(n);
                }
                var s = dojo.getComputedStyle(n);
                return (args == 1) ? s : _toStyleValue(n, style, s[style]); /* CSS2Properties||String||Number */
        }

        // =============================
        // Box Functions
        // =============================

        dojo._getPadExtents = function(/*DomNode*/n, /*Object*/computedStyle){
                //      summary:
                //              Returns object with special values specifically useful for node
                //              fitting.
                //                      l/t = left/top padding (respectively)
                //                      w = the total of the left and right padding 
                //                      h = the total of the top and bottom padding
                //              If 'node' has position, l/t forms the origin for child nodes. 
                //              The w/h are used for calculating boxes.
                //              Normally application code will not need to invoke this
                //              directly, and will use the ...box... functions instead.
                var 
                        s=computedStyle||gcs(n), 
                        px=dojo._toPixelValue,
                        l=px(n, s.paddingLeft), 
                        t=px(n, s.paddingTop);
                return { 
                        l: l,
                        t: t,
                        w: l+px(n, s.paddingRight),
                        h: t+px(n, s.paddingBottom)
                };
        }

        dojo._getBorderExtents = function(/*DomNode*/n, /*Object*/computedStyle){
                //      summary:
                //              returns an object with properties useful for noting the border
                //              dimensions.
                //                      l/t = the sum of left/top border (respectively)
                //                      w = the sum of the left and right border
                //                      h = the sum of the top and bottom border
                //              The w/h are used for calculating boxes.
                //              Normally application code will not need to invoke this
                //              directly, and will use the ...box... functions instead.
                var 
                        ne='none',
                        px=dojo._toPixelValue, 
                        s=computedStyle||gcs(n), 
                        bl=(s.borderLeftStyle!=ne ? px(n, s.borderLeftWidth) : 0),
                        bt=(s.borderTopStyle!=ne ? px(n, s.borderTopWidth) : 0);
                return { 
                        l: bl,
                        t: bt,
                        w: bl + (s.borderRightStyle!=ne ? px(n, s.borderRightWidth) : 0),
                        h: bt + (s.borderBottomStyle!=ne ? px(n, s.borderBottomWidth) : 0)
                };
        }

        dojo._getPadBorderExtents = function(/*DomNode*/n, /*Object*/computedStyle){
                //      summary:
                //              returns object with properties useful for box fitting with
                //              regards to padding.
                //                      l/t = the sum of left/top padding and left/top border (respectively)
                //                      w = the sum of the left and right padding and border
                //                      h = the sum of the top and bottom padding and border
                //              The w/h are used for calculating boxes.
                //              Normally application code will not need to invoke this
                //              directly, and will use the ...box... functions instead.
                var 
                        s=computedStyle||gcs(n), 
                        p=dojo._getPadExtents(n, s),
                        b=dojo._getBorderExtents(n, s);
                return { 
                        l: p.l + b.l,
                        t: p.t + b.t,
                        w: p.w + b.w,
                        h: p.h + b.h
                };
        }

        dojo._getMarginExtents = function(n, computedStyle){
                //      summary:
                //              returns object with properties useful for box fitting with
                //              regards to box margins (i.e., the outer-box).
                //                      l/t = marginLeft, marginTop, respectively
                //                      w = total width, margin inclusive
                //                      h = total height, margin inclusive
                //              The w/h are used for calculating boxes.
                //              Normally application code will not need to invoke this
                //              directly, and will use the ...box... functions instead.
                var 
                        s=computedStyle||gcs(n), 
                        px=dojo._toPixelValue,
                        l=px(n, s.marginLeft),
                        t=px(n, s.marginTop),
                        r=px(n, s.marginRight),
                        b=px(n, s.marginBottom);
                if (dojo.isSafari && (s.position != "absolute")){
                        // FIXME: Safari's version of the computed right margin
                        // is the space between our right edge and the right edge 
                        // of our offsetParent. 
                        // What we are looking for is the actual margin value as 
                        // determined by CSS.
                        // Hack solution is to assume left/right margins are the same.
                        r = l;
                }
                return { 
                        l: l,
                        t: t,
                        w: l+r,
                        h: t+b
                };
        }

        // Box getters work in any box context because offsetWidth/clientWidth
        // are invariant wrt box context
        //
        // They do *not* work for display: inline objects that have padding styles
        // because the user agent ignores padding (it's bogus styling in any case)
        //
        // Be careful with IMGs because they are inline or block depending on 
        // browser and browser mode.

        // Although it would be easier to read, there are not separate versions of 
        // _getMarginBox for each browser because:
        // 1. the branching is not expensive
        // 2. factoring the shared code wastes cycles (function call overhead)
        // 3. duplicating the shared code wastes bytes
        
        dojo._getMarginBox = function(/*DomNode*/node, /*Object*/computedStyle){
                // summary:
                //              returns an object that encodes the width, height, left and top
                //              positions of the node's margin box.
                var s = computedStyle||gcs(node), me = dojo._getMarginExtents(node, s);
                var     l = node.offsetLeft - me.l,     t = node.offsetTop - me.t;
                if(dojo.isMoz){
                        // Mozilla:
                        // If offsetParent has a computed overflow != visible, the offsetLeft is decreased
                        // by the parent's border.
                        // We don't want to compute the parent's style, so instead we examine node's
                        // computed left/top which is more stable.
                        var sl = parseFloat(s.left), st = parseFloat(s.top);
                        if(!isNaN(sl) && !isNaN(st)){
                                l = sl, t = st;
                        }else{
                                // If child's computed left/top are not parseable as a number (e.g. "auto"), we
                                // have no choice but to examine the parent's computed style.
                                var p = node.parentNode;
                                if(p && p.style){
                                        var pcs = gcs(p);
                                        if(pcs.overflow != "visible"){
                                                var be = dojo._getBorderExtents(p, pcs);
                                                l += be.l, t += be.t;
                                        }
                                }
                        }
                }else if(dojo.isOpera){
                        // On Opera, offsetLeft includes the parent's border
                        var p = node.parentNode;
                        if(p){
                                var be = dojo._getBorderExtents(p);
                                l -= be.l, t -= be.t;
                        }
                }
                return { 
                        l: l, 
                        t: t, 
                        w: node.offsetWidth + me.w, 
                        h: node.offsetHeight + me.h 
                };
        }
        
        dojo._getContentBox = function(node, computedStyle){
                // summary:
                //              Returns an object that encodes the width, height, left and top
                //              positions of the node's content box, irrespective of the
                //              current box model.

                // clientWidth/Height are important since the automatically account for scrollbars
                // fallback to offsetWidth/Height for special cases (see #3378)
                var s=computedStyle||gcs(node), pe=dojo._getPadExtents(node, s), be=dojo._getBorderExtents(node, s), w=node.clientWidth, h;
                if(!w){
                        w=node.offsetWidth, h=node.offsetHeight;
                }else{
                        h=node.clientHeight, be.w = be.h = 0; 
                }
                // On Opera, offsetLeft includes the parent's border
                if(dojo.isOpera){ pe.l += be.l; pe.t += be.t; };
                return { 
                        l: pe.l, 
                        t: pe.t, 
                        w: w - pe.w - be.w, 
                        h: h - pe.h - be.h
                };
        }

        dojo._getBorderBox = function(node, computedStyle){
                var s=computedStyle||gcs(node), pe=dojo._getPadExtents(node, s), cb=dojo._getContentBox(node, s);
                return { 
                        l: cb.l - pe.l, 
                        t: cb.t - pe.t, 
                        w: cb.w + pe.w, 
                        h: cb.h + pe.h
                };
        }

        // Box setters depend on box context because interpretation of width/height styles
        // vary wrt box context.
        //
        // The value of dojo.boxModel is used to determine box context.
        // dojo.boxModel can be set directly to change behavior.
        //
        // Beware of display: inline objects that have padding styles
        // because the user agent ignores padding (it's a bogus setup anyway)
        //
        // Be careful with IMGs because they are inline or block depending on 
        // browser and browser mode.
        // 
        // Elements other than DIV may have special quirks, like built-in
        // margins or padding, or values not detectable via computedStyle.
        // In particular, margins on TABLE do not seems to appear 
        // at all in computedStyle on Mozilla.
        
        dojo._setBox = function(/*DomNode*/node, /*Number?*/l, /*Number?*/t, /*Number?*/w, /*Number?*/h, /*String?*/u){
                //      summary:
                //              sets width/height/left/top in the current (native) box-model
                //              dimentions. Uses the unit passed in u.
                //      node: DOM Node reference. Id string not supported for performance reasons.
                //      l: optional. left offset from parent.
                //      t: optional. top offset from parent.
                //      w: optional. width in current box model.
                //      h: optional. width in current box model.
                //      u: optional. unit measure to use for other measures. Defaults to "px".
                u = u || "px";
                with(node.style){
                        if(!isNaN(l)){ left = l+u; }
                        if(!isNaN(t)){ top = t+u; }
                        if(w>=0){ width = w+u; }
                        if(h>=0){ height = h+u; }
                }
        }

        dojo._usesBorderBox = function(/*DomNode*/node){
                //      summary: 
                //              True if the node uses border-box layout.

                // We could test the computed style of node to see if a particular box
                // has been specified, but there are details and we choose not to bother.
                var n = node.tagName;
                // For whatever reason, TABLE and BUTTON are always border-box by default.
                // If you have assigned a different box to either one via CSS then
                // box functions will break.
                return dojo.boxModel=="border-box" || n=="TABLE" || n=="BUTTON"; // boolean
        }

        dojo._setContentSize = function(/*DomNode*/node, /*Number*/widthPx, /*Number*/heightPx, /*Object*/computedStyle){
                //      summary:
                //              Sets the size of the node's contents, irrespective of margins,
                //              padding, or borders.
                var bb = dojo._usesBorderBox(node);
                if(bb){
                        var pb = dojo._getPadBorderExtents(node, computedStyle);
                        if(widthPx>=0){ widthPx += pb.w; }
                        if(heightPx>=0){ heightPx += pb.h; }
                }
                dojo._setBox(node, NaN, NaN, widthPx, heightPx);
        }

        dojo._setMarginBox = function(/*DomNode*/node,  /*Number?*/leftPx, /*Number?*/topPx, 
                                                                                                        /*Number?*/widthPx, /*Number?*/heightPx, 
                                                                                                        /*Object*/computedStyle){
                //      summary:
                //              sets the size of the node's margin box and palcement
                //              (left/top), irrespective of box model. Think of it as a
                //              passthrough to dojo._setBox that handles box-model vagaries for
                //              you.

                var s = computedStyle || dojo.getComputedStyle(node);
                // Some elements have special padding, margin, and box-model settings. 
                // To use box functions you may need to set padding, margin explicitly.
                // Controlling box-model is harder, in a pinch you might set dojo.boxModel.
                var bb=dojo._usesBorderBox(node),
                                pb=bb ? _nilExtents : dojo._getPadBorderExtents(node, s),
                                mb=dojo._getMarginExtents(node, s);
                if(widthPx>=0){ widthPx = Math.max(widthPx - pb.w - mb.w, 0); }
                if(heightPx>=0){ heightPx = Math.max(heightPx - pb.h - mb.h, 0); }
                dojo._setBox(node, leftPx, topPx, widthPx, heightPx);
        }
        
        var _nilExtents = { l:0, t:0, w:0, h:0 };

        // public API
        
        dojo.marginBox = function(/*DomNode|String*/node, /*Object?*/box){
                //      summary:
                //              getter/setter for the margin-box of node.
                //      description: 
                //              Returns an object in the expected format of box (regardless
                //              if box is passed). The object might look like:
                //                      { l: 50, t: 200, w: 300: h: 150 }
                //              for a node offset from its parent 50px to the left, 200px from
                //              the top with a margin width of 300px and a margin-height of
                //              150px.
                //      node:
                //              id or reference to DOM Node to get/set box for
                //      box:
                //              optional. If passed, denotes that dojo.marginBox() should
                //              update/set the margin box for node. Box is an object in the
                //              above format. All properties are optional if passed.
                var n=dojo.byId(node), s=gcs(n), b=box;
                return !b ? dojo._getMarginBox(n, s) : dojo._setMarginBox(n, b.l, b.t, b.w, b.h, s); // Object
        }

        dojo.contentBox = function(/*DomNode|String*/node, /*Object?*/box){
                //      summary:
                //              getter/setter for the content-box of node.
                //      description:
                //              Returns an object in the expected format of box (regardless if box is passed).
                //              The object might look like:
                //                      { l: 50, t: 200, w: 300: h: 150 }
                //              for a node offset from its parent 50px to the left, 200px from
                //              the top with a content width of 300px and a content-height of
                //              150px. Note that the content box may have a much larger border
                //              or margin box, depending on the box model currently in use and
                //              CSS values set/inherited for node.
                //      node:
                //              id or reference to DOM Node to get/set box for
                //      box:
                //              optional. If passed, denotes that dojo.contentBox() should
                //              update/set the content box for node. Box is an object in the
                //              above format. All properties are optional if passed.
                var n=dojo.byId(node), s=gcs(n), b=box;
                return !b ? dojo._getContentBox(n, s) : dojo._setContentSize(n, b.w, b.h, s); // Object
        }
        
        // =============================
        // Positioning 
        // =============================
        
        var _sumAncestorProperties = function(node, prop){
                if(!(node = (node||0).parentNode)){return 0};
                var val, retVal = 0, _b = dojo.body();
                while(node && node.style){
                        if(gcs(node).position == "fixed"){
                                return 0;
                        }
                        val = node[prop];
                        if(val){
                                retVal += val - 0;
                                // opera and khtml #body & #html has the same values, we only
                                // need one value
                                if(node == _b){ break; }
                        }
                        node = node.parentNode;
                }
                return retVal;  //      integer
        }

        dojo._docScroll = function(){
                var _b = dojo.body();
                var _w = dojo.global;
                var de = dojo.doc.documentElement;
                return {
                        y: (_w.pageYOffset || de.scrollTop || _b.scrollTop || 0),
                        x: (_w.pageXOffset || dojo._fixIeBiDiScrollLeft(de.scrollLeft) || _b.scrollLeft || 0)
                };
        };
        
        dojo._isBodyLtr = function(){
                //FIXME: could check html and body tags directly instead of computed style?  need to ignore case, accept empty values
                return !("_bodyLtr" in dojo) ? 
                        dojo._bodyLtr = dojo.getComputedStyle(dojo.body()).direction == "ltr" :
                        dojo._bodyLtr; // Boolean 
        }
        
        dojo._getIeDocumentElementOffset = function(){
                // summary
                // The following values in IE contain an offset:
                //     event.clientX 
                //     event.clientY 
                //     node.getBoundingClientRect().left
                //     node.getBoundingClientRect().top
                // But other position related values do not contain this offset, such as
                // node.offsetLeft, node.offsetTop, node.style.left and node.style.top.
                // The offset is always (2, 2) in LTR direction. When the body is in RTL
                // direction, the offset counts the width of left scroll bar's width.
                // This function computes the actual offset.

                //NOTE: assumes we're being called in an IE browser

                var de = dojo.doc.documentElement;
                if(dojo.isIE >= 7){
                        return {x: de.getBoundingClientRect().left, y: de.getBoundingClientRect().top}; // Object
                }else{
                        // IE 6.0
                        return {x: dojo._isBodyLtr() || window.parent == window ?
                                de.clientLeft : de.offsetWidth - de.clientWidth - de.clientLeft, 
                                y: de.clientTop}; // Object
                }
        };
        
        dojo._fixIeBiDiScrollLeft = function(/*Integer*/ scrollLeft){
                // In RTL direction, scrollLeft should be a negative value, but IE 
                // returns a positive one. All codes using documentElement.scrollLeft
                // must call this function to fix this error, otherwise the position
                // will offset to right when there is a horizonal scrollbar.
                if(dojo.isIE && !dojo._isBodyLtr()){
                        var de = dojo.doc.documentElement;
                        return scrollLeft + de.clientWidth - de.scrollWidth; // Integer
                }
                return scrollLeft; // Integer
        }
        
        dojo._abs = function(/*DomNode*/node, /*Boolean?*/includeScroll){
                //      summary:
                //              Gets the absolute position of the passed element based on the
                //              document itself. Returns an object of the form:
                //                      { x: 100, y: 300 }
                //              if includeScroll is passed, the x and y values will include any
                //              document offsets that may affect the position relative to the
                //              viewport.

                // FIXME: need to decide in the brave-new-world if we're going to be
                // margin-box or border-box.
                var ownerDocument = node.ownerDocument;
                var ret = {
                        x: 0,
                        y: 0
                };
                var hasScroll = false;

                // targetBoxType == "border-box"
                var db = dojo.body();
                if(dojo.isIE){
                        var client = node.getBoundingClientRect();
                        var offset = dojo._getIeDocumentElementOffset();
                        ret.x = client.left - offset.x;
                        ret.y = client.top - offset.y;
                }else if(ownerDocument["getBoxObjectFor"]){
                        // mozilla
                        var bo = ownerDocument.getBoxObjectFor(node);
                        ret.x = bo.x - _sumAncestorProperties(node, "scrollLeft");
                        ret.y = bo.y - _sumAncestorProperties(node, "scrollTop");
                }else{
                        if(node["offsetParent"]){
                                hasScroll = true;
                                var endNode;
                                // in Safari, if the node is an absolutely positioned child of
                                // the body and the body has a margin the offset of the child
                                // and the body contain the body's margins, so we need to end
                                // at the body
                                // FIXME: getting contrary results to the above in latest WebKit.
                                if(dojo.isSafari &&
                                        //(node.style.getPropertyValue("position") == "absolute") &&
                                        (gcs(node).position == "absolute") &&
                                        (node.parentNode == db)){
                                        endNode = db;
                                }else{
                                        endNode = db.parentNode;
                                }
                                if(node.parentNode != db){
                                        var nd = node;
                                        if(dojo.isOpera || (dojo.isSafari >= 3)){ nd = db; }
                                        ret.x -= _sumAncestorProperties(nd, "scrollLeft");
                                        ret.y -= _sumAncestorProperties(nd, "scrollTop");
                                }
                                var curnode = node;
                                do{
                                        var n = curnode["offsetLeft"];
                                        //FIXME: ugly hack to workaround the submenu in 
                                        //popupmenu2 does not shown up correctly in opera. 
                                        //Someone have a better workaround?
                                        if(!dojo.isOpera || n>0){
                                                ret.x += isNaN(n) ? 0 : n;
                                        }
                                        var m = curnode["offsetTop"];
                                        ret.y += isNaN(m) ? 0 : m;
                                        curnode = curnode.offsetParent;
                                }while((curnode != endNode)&&curnode);
                        }else if(node["x"]&&node["y"]){
                                ret.x += isNaN(node.x) ? 0 : node.x;
                                ret.y += isNaN(node.y) ? 0 : node.y;
                        }
                }
                // account for document scrolling
                // if offsetParent is used, ret value already includes scroll position
                // so we may have to actually remove that value if !includeScroll
                if(hasScroll || includeScroll){
                        var scroll = dojo._docScroll();
                        var m = hasScroll ? (!includeScroll ? -1 : 0) : 1;
                        ret.y += m*scroll.y;
                        ret.x += m*scroll.x;
                }

                return ret; // object
        }

        // FIXME: need a setter for coords or a moveTo!!
        dojo.coords = function(/*DomNode|String*/node, /*Boolean?*/includeScroll){
                //      summary:
                //              Returns an object that measures margin box width/height and
                //              absolute positioning data from dojo._abs(). Return value will
                //              be in the form:
                //                      { l: 50, t: 200, w: 300: h: 150, x: 100, y: 300 }
                //              does not act as a setter. If includeScroll is passed, the x and
                //              y params are affected as one would expect in dojo._abs().
                var n=dojo.byId(node), s=gcs(n), mb=dojo._getMarginBox(n, s);
                var abs = dojo._abs(n, includeScroll);
                mb.x = abs.x;
                mb.y = abs.y;
                return mb;
        }
})();

// =============================
// (CSS) Class Functions
// =============================

dojo.hasClass = function(/*DomNode|String*/node, /*String*/classStr){
        //      summary:
        //              Returns whether or not the specified classes are a portion of the
        //              class list currently applied to the node. 
        return ((" "+dojo.byId(node).className+" ").indexOf(" "+classStr+" ") >= 0);  // Boolean
};

dojo.addClass = function(/*DomNode|String*/node, /*String*/classStr){
        //      summary:
        //              Adds the specified classes to the end of the class list on the
        //              passed node.
        node = dojo.byId(node);
        var cls = node.className;
        if((" "+cls+" ").indexOf(" "+classStr+" ") < 0){
                node.className = cls + (cls ? ' ' : '') + classStr;
        }
};

dojo.removeClass = function(/*DomNode|String*/node, /*String*/classStr){
        // summary: Removes the specified classes from node.
        node = dojo.byId(node);
        var t = dojo.trim((" " + node.className + " ").replace(" " + classStr + " ", " "));
        if(node.className != t){ node.className = t; }
};

dojo.toggleClass = function(/*DomNode|String*/node, /*String*/classStr, /*Boolean?*/condition){
        //      summary:        
        //              Adds a class to node if not present, or removes if present.
        //              Pass a boolean condition if you want to explicitly add or remove.
        //      condition:
        //              If passed, true means to add the class, false means to remove.
        if(condition === undefined){
                condition = !dojo.hasClass(node, classStr);
        }
        dojo[condition ? "addClass" : "removeClass"](node, classStr);
};

}