1834 lines
58 KiB
JavaScript
1834 lines
58 KiB
JavaScript
/*
|
|
Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
|
|
Available via Academic Free License >= 2.1 OR the modified BSD license.
|
|
see: http://dojotoolkit.org/license for details
|
|
*/
|
|
|
|
|
|
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:
|
|
// Returns DOM node with matching `id` attribute or `null`
|
|
// if not found. If `id` is a DomNode, this function is a no-op.
|
|
//
|
|
// id: String|DOMNode
|
|
// A string to match an HTML id attribute or a reference to a DOM Node
|
|
//
|
|
// doc: Document?
|
|
// Document to work in. Defaults to the current value of
|
|
// dojo.doc. Can be used to retrieve
|
|
// node references from other documents.
|
|
//
|
|
// example:
|
|
// Look up a node by ID:
|
|
// | var n = dojo.byId("foo");
|
|
//
|
|
// example:
|
|
// Check if a node exists, and use it.
|
|
// | var n = dojo.byId("bar");
|
|
// | if(n){ doStuff() ... }
|
|
//
|
|
// example:
|
|
// Allow string or DomNode references to be passed to a custom function:
|
|
// | var foo = function(nodeOrId){
|
|
// | nodeOrId = dojo.byId(nodeOrId);
|
|
// | // ... more stuff
|
|
// | }
|
|
=====*/
|
|
|
|
if(dojo.isIE || dojo.isOpera){
|
|
dojo.byId = function(id, doc){
|
|
if(typeof id != "string"){
|
|
return id;
|
|
}
|
|
var _d = doc || dojo.doc, 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 || te.id == id)){
|
|
return te;
|
|
}else{
|
|
var eles = _d.all[id];
|
|
if(!eles || eles.nodeName){
|
|
eles = [eles];
|
|
}
|
|
// if more than 1, choose first with the correct id
|
|
var i=0;
|
|
while((te=eles[i++])){
|
|
if((te.attributes && te.attributes.id && te.attributes.id.value == id)
|
|
|| te.id == id){
|
|
return te;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}else{
|
|
dojo.byId = function(id, doc){
|
|
// inline'd type check
|
|
return (typeof id == "string") ? (doc || dojo.doc).getElementById(id) : id; // DomNode
|
|
};
|
|
}
|
|
/*=====
|
|
};
|
|
=====*/
|
|
|
|
(function(){
|
|
var d = dojo;
|
|
var byId = d.byId;
|
|
|
|
var _destroyContainer = null,
|
|
_destroyDoc;
|
|
d.addOnWindowUnload(function(){
|
|
_destroyContainer = null; //prevent IE leak
|
|
});
|
|
|
|
/*=====
|
|
dojo._destroyElement = function(node){
|
|
// summary:
|
|
// Existing alias for `dojo.destroy`. Deprecated, will be removed
|
|
// in 2.0
|
|
}
|
|
=====*/
|
|
dojo._destroyElement = dojo.destroy = function(/*String|DomNode*/node){
|
|
// summary:
|
|
// Removes a node from its parent, clobbering it and all of its
|
|
// children.
|
|
//
|
|
// description:
|
|
// Removes a node from its parent, clobbering it and all of its
|
|
// children. Function only works with DomNodes, and returns nothing.
|
|
//
|
|
// node:
|
|
// A String ID or DomNode reference of the element to be destroyed
|
|
//
|
|
// example:
|
|
// Destroy a node byId:
|
|
// | dojo.destroy("someId");
|
|
//
|
|
// example:
|
|
// Destroy all nodes in a list by reference:
|
|
// | dojo.query(".someNode").forEach(dojo.destroy);
|
|
|
|
node = byId(node);
|
|
try{
|
|
var doc = node.ownerDocument;
|
|
// cannot use _destroyContainer.ownerDocument since this can throw an exception on IE
|
|
if(!_destroyContainer || _destroyDoc != doc){
|
|
_destroyContainer = doc.createElement("div");
|
|
_destroyDoc = doc;
|
|
}
|
|
_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: string id or node reference to test
|
|
// ancestor: string id or node reference of potential parent to test against
|
|
//
|
|
// example:
|
|
// Test is node id="bar" is a descendant of node id="foo"
|
|
// | if(dojo.isDescendant("bar", "foo")){ ... }
|
|
try{
|
|
node = byId(node);
|
|
ancestor = byId(ancestor);
|
|
while(node){
|
|
if(node == ancestor){
|
|
return true; // Boolean
|
|
}
|
|
node = node.parentNode;
|
|
}
|
|
}catch(e){ /* squelch, return false */ }
|
|
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:
|
|
// state to put the node in. false indicates unselectable, true
|
|
// allows selection.
|
|
// example:
|
|
// Make the node id="bar" unselectable
|
|
// | dojo.setSelectable("bar");
|
|
// example:
|
|
// Make the node id="bar" selectable
|
|
// | dojo.setSelectable("bar", true);
|
|
node = byId(node);
|
|
if(d.isMozilla){
|
|
node.style.MozUserSelect = selectable ? "" : "none";
|
|
}else if(d.isKhtml || d.isWebKit){
|
|
node.style.KhtmlUserSelect = selectable ? "auto" : "none";
|
|
}else if(d.isIE){
|
|
var v = (node.unselectable = selectable ? "" : "on");
|
|
d.query("*", node).forEach("item.unselectable = '"+v+"'");
|
|
}
|
|
//FIXME: else? Opera?
|
|
};
|
|
|
|
var _insertBefore = function(/*DomNode*/node, /*DomNode*/ref){
|
|
var parent = ref.parentNode;
|
|
if(parent){
|
|
parent.insertBefore(node, ref);
|
|
}
|
|
};
|
|
|
|
var _insertAfter = function(/*DomNode*/node, /*DomNode*/ref){
|
|
// summary:
|
|
// Try to insert node after ref
|
|
var parent = ref.parentNode;
|
|
if(parent){
|
|
if(parent.lastChild == ref){
|
|
parent.appendChild(node);
|
|
}else{
|
|
parent.insertBefore(node, ref.nextSibling);
|
|
}
|
|
}
|
|
};
|
|
|
|
dojo.place = function(node, refNode, position){
|
|
// summary:
|
|
// Attempt to insert node into the DOM, choosing from various positioning options.
|
|
// Returns the first argument resolved to a DOM node.
|
|
//
|
|
// node: String|DomNode
|
|
// id or node reference, or HTML fragment starting with "<" to place relative to refNode
|
|
//
|
|
// refNode: String|DomNode
|
|
// id or node reference to use as basis for placement
|
|
//
|
|
// position: String|Number?
|
|
// 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
|
|
// | * replace
|
|
// | * only
|
|
// | * first
|
|
// | * last
|
|
// "first" and "last" indicate positions as children of refNode, "replace" replaces refNode,
|
|
// "only" replaces all children. position defaults to "last" if not specified
|
|
//
|
|
// returns: DomNode
|
|
// Returned values is the first argument resolved to a DOM node.
|
|
//
|
|
// .place() is also a method of `dojo.NodeList`, allowing `dojo.query` node lookups.
|
|
//
|
|
// example:
|
|
// Place a node by string id as the last child of another node by string id:
|
|
// | dojo.place("someNode", "anotherNode");
|
|
//
|
|
// example:
|
|
// Place a node by string id before another node by string id
|
|
// | dojo.place("someNode", "anotherNode", "before");
|
|
//
|
|
// example:
|
|
// Create a Node, and place it in the body element (last child):
|
|
// | dojo.place("<div></div>", dojo.body());
|
|
//
|
|
// example:
|
|
// Put a new LI as the first child of a list by id:
|
|
// | dojo.place("<li></li>", "someUl", "first");
|
|
|
|
refNode = byId(refNode);
|
|
if(typeof node == "string"){ // inline'd type check
|
|
node = node.charAt(0) == "<" ? d._toDom(node, refNode.ownerDocument) : byId(node);
|
|
}
|
|
if(typeof position == "number"){ // inline'd type check
|
|
var cn = refNode.childNodes;
|
|
if(!cn.length || cn.length <= position){
|
|
refNode.appendChild(node);
|
|
}else{
|
|
_insertBefore(node, cn[position < 0 ? 0 : position]);
|
|
}
|
|
}else{
|
|
switch(position){
|
|
case "before":
|
|
_insertBefore(node, refNode);
|
|
break;
|
|
case "after":
|
|
_insertAfter(node, refNode);
|
|
break;
|
|
case "replace":
|
|
refNode.parentNode.replaceChild(node, refNode);
|
|
break;
|
|
case "only":
|
|
d.empty(refNode);
|
|
refNode.appendChild(node);
|
|
break;
|
|
case "first":
|
|
if(refNode.firstChild){
|
|
_insertBefore(node, refNode.firstChild);
|
|
break;
|
|
}
|
|
// else fallthrough...
|
|
default: // aka: last
|
|
refNode.appendChild(node);
|
|
}
|
|
}
|
|
return node; // DomNode
|
|
}
|
|
|
|
// 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(d.isIE /*|| dojo.isOpera*/){
|
|
// client code may have to adjust if compatMode varies across iframes
|
|
d.boxModel = document.compatMode == "BackCompat" ? "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.
|
|
|
|
/*=====
|
|
dojo.getComputedStyle = function(node){
|
|
// summary:
|
|
// Returns a "computed style" object.
|
|
//
|
|
// description:
|
|
// Gets a "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.
|
|
//
|
|
// Note also that this method is expensive. Wherever possible,
|
|
// reuse the returned object.
|
|
//
|
|
// 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;
|
|
//
|
|
// example:
|
|
// Reusing the returned object, avoiding multiple lookups:
|
|
// | var cs = dojo.getComputedStyle(dojo.byId("someNode"));
|
|
// | var w = cs.width, h = cs.height;
|
|
return; // CSS2Properties
|
|
}
|
|
=====*/
|
|
|
|
// Although we normally eschew argument validation at this
|
|
// level, here we test argument 'node' for (duck)type,
|
|
// by testing nodeType, ecause 'document' is the 'parentNode' of 'body'
|
|
// it is frequently sent to this function even
|
|
// though it is not Element.
|
|
var gcs;
|
|
if(d.isWebKit){
|
|
gcs = function(/*DomNode*/node){
|
|
var s;
|
|
if(node.nodeType == 1){
|
|
var dv = node.ownerDocument.defaultView;
|
|
s = dv.getComputedStyle(node, null);
|
|
if(!s && node.style){
|
|
node.style.display = "";
|
|
s = dv.getComputedStyle(node, null);
|
|
}
|
|
}
|
|
return s || {};
|
|
};
|
|
}else if(d.isIE){
|
|
gcs = function(node){
|
|
// IE (as of 7) doesn't expose Element like sane browsers
|
|
return node.nodeType == 1 /* ELEMENT_NODE*/ ? node.currentStyle : {};
|
|
};
|
|
}else{
|
|
gcs = function(node){
|
|
return node.nodeType == 1 ?
|
|
node.ownerDocument.defaultView.getComputedStyle(node, null) : {};
|
|
};
|
|
}
|
|
dojo.getComputedStyle = gcs;
|
|
|
|
if(!d.isIE){
|
|
d._toPixelValue = function(element, value){
|
|
// style values can be floats, client code may want
|
|
// to round for integer pixels.
|
|
return parseFloat(value) || 0;
|
|
};
|
|
}else{
|
|
d._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;
|
|
}
|
|
}
|
|
var px = d._toPixelValue;
|
|
|
|
// 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.
|
|
// returns: Number between 0 and 1
|
|
return; // Number
|
|
}
|
|
=====*/
|
|
|
|
var astr = "DXImageTransform.Microsoft.Alpha";
|
|
var af = function(n, f){
|
|
try{
|
|
return n.filters.item(astr);
|
|
}catch(e){
|
|
return f ? {} : null;
|
|
}
|
|
};
|
|
|
|
dojo._getOpacity =
|
|
d.isIE ? function(node){
|
|
try{
|
|
return af(node).Opacity / 100; // Number
|
|
}catch(e){
|
|
return 1; // Number
|
|
}
|
|
} :
|
|
function(node){
|
|
return gcs(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.
|
|
// returns: Number between 0 and 1
|
|
return; // Number
|
|
}
|
|
=====*/
|
|
|
|
dojo._setOpacity =
|
|
d.isIE ? function(/*DomNode*/node, /*Number*/opacity){
|
|
var ov = opacity * 100, opaque = opacity == 1;
|
|
node.style.zoom = opaque ? "" : 1;
|
|
|
|
if(!af(node)){
|
|
if(opaque){
|
|
return opacity;
|
|
}
|
|
node.style.filter += " progid:" + astr + "(Opacity=" + ov + ")";
|
|
}else{
|
|
af(node, 1).Opacity = ov;
|
|
}
|
|
|
|
// on IE7 Alpha(Filter opacity=100) makes text look fuzzy so disable it altogether (bug #2661),
|
|
//but still update the opacity value so we can get a correct reading if it is read later.
|
|
af(node, 1).Enabled = !opaque;
|
|
|
|
if(node.nodeName.toLowerCase() == "tr"){
|
|
d.query("> td", node).forEach(function(i){
|
|
d._setOpacity(i, opacity);
|
|
});
|
|
}
|
|
return opacity;
|
|
} :
|
|
function(node, opacity){
|
|
return node.style.opacity = opacity;
|
|
};
|
|
|
|
var _pixelNamesCache = {
|
|
left: true, top: true
|
|
};
|
|
var _pixelRegExp = /margin|padding|width|height|max|min|offset/; // |border
|
|
var _toStyleValue = function(node, type, value){
|
|
type = type.toLowerCase(); // FIXME: should we really be doing string case conversion here? Should we cache it? Need to profile!
|
|
if(d.isIE){
|
|
if(value == "auto"){
|
|
if(type == "height"){ return node.offsetHeight; }
|
|
if(type == "width"){ return node.offsetWidth; }
|
|
}
|
|
if(type == "fontweight"){
|
|
switch(value){
|
|
case 700: return "bold";
|
|
case 400:
|
|
default: return "normal";
|
|
}
|
|
}
|
|
}
|
|
if(!(type in _pixelNamesCache)){
|
|
_pixelNamesCache[type] = _pixelRegExp.test(type);
|
|
}
|
|
return _pixelNamesCache[type] ? px(node, value) : value;
|
|
};
|
|
|
|
var _floatStyle = d.isIE ? "styleFloat" : "cssFloat",
|
|
_floatAliases = { "cssFloat": _floatStyle, "styleFloat": _floatStyle, "float": _floatStyle }
|
|
;
|
|
|
|
// public API
|
|
|
|
dojo.style = function( /*DomNode|String*/ node,
|
|
/*String?|Object?*/ style,
|
|
/*String?*/ value){
|
|
// summary:
|
|
// Accesses styles on a node. If 2 arguments are
|
|
// passed, acts as a getter. If 3 arguments are passed, acts
|
|
// as a setter.
|
|
// description:
|
|
// Getting the style value uses the computed style for the node, so the value
|
|
// will be a calculated value, not just the immediate node.style value.
|
|
// Also when getting values, use specific style names,
|
|
// like "borderBottomWidth" instead of "border" since compound values like
|
|
// "border" are not necessarily reflected as expected.
|
|
// If you want to get node dimensions, use `dojo.marginBox()`,
|
|
// `dojo.contentBox()` or `dojo.position()`.
|
|
// 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") or an object with key/value
|
|
// pairs suitable for setting each property.
|
|
// value:
|
|
// If passed, sets value on the node for style, handling
|
|
// cross-browser concerns. When setting a pixel value,
|
|
// be sure to include "px" in the value. For instance, top: "200px".
|
|
// Otherwise, in some cases, some browsers will not apply the style.
|
|
// example:
|
|
// Passing only an ID or node returns the computed style object of
|
|
// the node:
|
|
// | dojo.style("thinger");
|
|
// example:
|
|
// Passing a node and a style property returns the current
|
|
// normalized, computed value for that property:
|
|
// | dojo.style("thinger", "opacity"); // 1 by default
|
|
//
|
|
// example:
|
|
// Passing a node, a style property, and a value changes the
|
|
// current display of the node and returns the new computed value
|
|
// | dojo.style("thinger", "opacity", 0.5); // == 0.5
|
|
//
|
|
// example:
|
|
// Passing a node, an object-style style property sets each of the values in turn and returns the computed style object of the node:
|
|
// | dojo.style("thinger", {
|
|
// | "opacity": 0.5,
|
|
// | "border": "3px solid black",
|
|
// | "height": "300px"
|
|
// | });
|
|
//
|
|
// example:
|
|
// When the CSS style property is hyphenated, the JavaScript property is camelCased.
|
|
// font-size becomes fontSize, and so on.
|
|
// | dojo.style("thinger",{
|
|
// | fontSize:"14pt",
|
|
// | letterSpacing:"1.2em"
|
|
// | });
|
|
//
|
|
// example:
|
|
// dojo.NodeList implements .style() using the same syntax, omitting the "node" parameter, calling
|
|
// dojo.style() on every element of the list. See: `dojo.query()` and `dojo.NodeList()`
|
|
// | dojo.query(".someClassName").style("visibility","hidden");
|
|
// | // or
|
|
// | dojo.query("#baz > div").style({
|
|
// | opacity:0.75,
|
|
// | fontSize:"13pt"
|
|
// | });
|
|
|
|
var n = byId(node), args = arguments.length, op = (style == "opacity");
|
|
style = _floatAliases[style] || style;
|
|
if(args == 3){
|
|
return op ? d._setOpacity(n, value) : n.style[style] = value; /*Number*/
|
|
}
|
|
if(args == 2 && op){
|
|
return d._getOpacity(n);
|
|
}
|
|
var s = gcs(n);
|
|
if(args == 2 && typeof style != "string"){ // inline'd type check
|
|
for(var x in style){
|
|
d.style(node, x, style[x]);
|
|
}
|
|
return s;
|
|
}
|
|
return (args == 1) ? s : _toStyleValue(n, style, s[style] || n.style[style]); /* CSS2Properties||String||Number */
|
|
}
|
|
|
|
// =============================
|
|
// Box Functions
|
|
// =============================
|
|
|
|
dojo._getPadExtents = function(/*DomNode*/n, /*Object*/computedStyle){
|
|
// summary:
|
|
// Returns object with special values specifically useful for node
|
|
// fitting.
|
|
// description:
|
|
// Returns an object with `w`, `h`, `l`, `t` properties:
|
|
// | 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),
|
|
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.
|
|
// description:
|
|
// * 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",
|
|
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.
|
|
// description:
|
|
// * 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 = d._getPadExtents(n, s),
|
|
b = d._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),
|
|
l = px(n, s.marginLeft),
|
|
t = px(n, s.marginTop),
|
|
r = px(n, s.marginRight),
|
|
b = px(n, s.marginBottom);
|
|
if(d.isWebKit && (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 = d._getMarginExtents(node, s);
|
|
var l = node.offsetLeft - me.l, t = node.offsetTop - me.t, p = node.parentNode;
|
|
if(d.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.
|
|
if(p && p.style){
|
|
var pcs = gcs(p);
|
|
if(pcs.overflow != "visible"){
|
|
var be = d._getBorderExtents(p, pcs);
|
|
l += be.l, t += be.t;
|
|
}
|
|
}
|
|
}
|
|
}else if(d.isOpera || (d.isIE > 7 && !d.isQuirks)){
|
|
// On Opera and IE 8, offsetLeft/Top includes the parent's border
|
|
if(p){
|
|
be = d._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 = d._getPadExtents(node, s),
|
|
be = d._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(d.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 = d._getPadExtents(node, s),
|
|
cb = d._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:
|
|
// left offset from parent.
|
|
// t:
|
|
// top offset from parent.
|
|
// w:
|
|
// width in current box model.
|
|
// h:
|
|
// width in current box model.
|
|
// u:
|
|
// unit measure to use for other measures. Defaults to "px".
|
|
u = u || "px";
|
|
var s = node.style;
|
|
if(!isNaN(l)){ s.left = l + u; }
|
|
if(!isNaN(t)){ s.top = t + u; }
|
|
if(w >= 0){ s.width = w + u; }
|
|
if(h >= 0){ s.height = h + u; }
|
|
}
|
|
|
|
dojo._isButtonTag = function(/*DomNode*/node) {
|
|
// summary:
|
|
// True if the node is BUTTON or INPUT.type="button".
|
|
return node.tagName == "BUTTON"
|
|
|| node.tagName=="INPUT" && (node.getAttribute("type")||'').toUpperCase() == "BUTTON"; // boolean
|
|
}
|
|
|
|
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.
|
|
|
|
// TABLE and BUTTON (and INPUT type=button) are always border-box by default.
|
|
// If you have assigned a different box to either one via CSS then
|
|
// box functions will break.
|
|
|
|
var n = node.tagName;
|
|
return d.boxModel=="border-box" || n=="TABLE" || d._isButtonTag(node); // 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.
|
|
if(d._usesBorderBox(node)){
|
|
var pb = d._getPadBorderExtents(node, computedStyle);
|
|
if(widthPx >= 0){ widthPx += pb.w; }
|
|
if(heightPx >= 0){ heightPx += pb.h; }
|
|
}
|
|
d._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 placement
|
|
// (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 || gcs(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.
|
|
bb = d._usesBorderBox(node),
|
|
pb = bb ? _nilExtents : d._getPadBorderExtents(node, s)
|
|
;
|
|
if(d.isWebKit){
|
|
// on Safari (3.1.2), button nodes with no explicit size have a default margin
|
|
// setting an explicit size eliminates the margin.
|
|
// We have to swizzle the width to get correct margin reading.
|
|
if(d._isButtonTag(node)){
|
|
var ns = node.style;
|
|
if(widthPx >= 0 && !ns.width) { ns.width = "4px"; }
|
|
if(heightPx >= 0 && !ns.height) { ns.height = "4px"; }
|
|
}
|
|
}
|
|
var mb = d._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); }
|
|
d._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:
|
|
// Getter/setter for the margin-box of node.
|
|
// 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:
|
|
// 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.
|
|
// example:
|
|
// Retrieve the marginbox of a passed node
|
|
// | var box = dojo.marginBox("someNodeId");
|
|
// | console.dir(box);
|
|
//
|
|
// example:
|
|
// Set a node's marginbox to the size of another node
|
|
// | var box = dojo.marginBox("someNodeId");
|
|
// | dojo.marginBox("someOtherNode", box);
|
|
|
|
var n = byId(node), s = gcs(n), b = box;
|
|
return !b ? d._getMarginBox(n, s) : d._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.
|
|
// While the getter will return top and left values, the
|
|
// setter only accepts setting the width and height.
|
|
// node:
|
|
// id or reference to DOM Node to get/set box for
|
|
// box:
|
|
// If passed, denotes that dojo.contentBox() should
|
|
// update/set the content box for node. Box is an object in the
|
|
// above format, but only w (width) and h (height) are supported.
|
|
// All properties are optional if passed.
|
|
var n = byId(node), s = gcs(n), b = box;
|
|
return !b ? d._getContentBox(n, s) : d._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 = d.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 n = d.global;
|
|
return "pageXOffset" in n? { x:n.pageXOffset, y:n.pageYOffset } :
|
|
(n=d.doc.documentElement, n.clientHeight? { x:d._fixIeBiDiScrollLeft(n.scrollLeft), y:n.scrollTop } :
|
|
(n=d.body(), { x:n.scrollLeft||0, y:n.scrollTop||0 }));
|
|
};
|
|
|
|
dojo._isBodyLtr = function(){
|
|
return "_bodyLtr" in d? d._bodyLtr :
|
|
d._bodyLtr = (d.body().dir || d.doc.documentElement.dir || "ltr").toLowerCase() == "ltr"; // Boolean
|
|
}
|
|
|
|
dojo._getIeDocumentElementOffset = function(){
|
|
// summary:
|
|
// returns the offset in x and y from the document body to the
|
|
// visual edge of the page
|
|
// description:
|
|
// 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 = d.doc.documentElement; // only deal with HTML element here, _abs handles body/quirks
|
|
|
|
if(d.isIE < 8){
|
|
var r = de.getBoundingClientRect(); // works well for IE6+
|
|
//console.debug('rect left,top = ' + r.left+','+r.top + ', html client left/top = ' + de.clientLeft+','+de.clientTop + ', rtl = ' + (!d._isBodyLtr()) + ', quirks = ' + d.isQuirks);
|
|
var l = r.left,
|
|
t = r.top;
|
|
if(d.isIE < 7){
|
|
l += de.clientLeft; // scrollbar size in strict/RTL, or,
|
|
t += de.clientTop; // HTML border size in strict
|
|
}
|
|
return {
|
|
x: l < 0? 0 : l, // FRAME element border size can lead to inaccurate negative values
|
|
y: t < 0? 0 : t
|
|
};
|
|
}else{
|
|
return {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
}
|
|
|
|
};
|
|
|
|
dojo._fixIeBiDiScrollLeft = function(/*Integer*/ scrollLeft){
|
|
// In RTL direction, scrollLeft should be a negative value, but IE < 8
|
|
// 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 horizontal scrollbar.
|
|
|
|
var dd = d.doc;
|
|
if(d.isIE < 8 && !d._isBodyLtr()){
|
|
var de = d.isQuirks ? dd.body : dd.documentElement;
|
|
return scrollLeft + de.clientWidth - de.scrollWidth; // Integer
|
|
}
|
|
return scrollLeft; // Integer
|
|
}
|
|
|
|
// FIXME: need a setter for coords or a moveTo!!
|
|
dojo._abs = dojo.position = function(/*DomNode*/node, /*Boolean?*/includeScroll){
|
|
// summary:
|
|
// Gets the position and size of the passed element relative to
|
|
// the viewport (if includeScroll==false), or relative to the
|
|
// document root (if includeScroll==true).
|
|
//
|
|
// description:
|
|
// Returns an object of the form:
|
|
// { x: 100, y: 300, w: 20, h: 15 }
|
|
// If includeScroll==true, the x and y values will include any
|
|
// document offsets that may affect the position relative to the
|
|
// viewport.
|
|
// Uses the border-box model (inclusive of border and padding but
|
|
// not margin). Does not act as a setter.
|
|
|
|
var db = d.body(), dh = db.parentNode, ret;
|
|
node = byId(node);
|
|
if(node["getBoundingClientRect"]){
|
|
// IE6+, FF3+, super-modern WebKit, and Opera 9.6+ all take this branch
|
|
ret = node.getBoundingClientRect();
|
|
ret = { x: ret.left, y: ret.top, w: ret.right - ret.left, h: ret.bottom - ret.top };
|
|
if(d.isIE){
|
|
// On IE there's a 2px offset that we need to adjust for, see _getIeDocumentElementOffset()
|
|
var offset = d._getIeDocumentElementOffset();
|
|
|
|
// fixes the position in IE, quirks mode
|
|
ret.x -= offset.x + (d.isQuirks ? db.clientLeft+db.offsetLeft : 0);
|
|
ret.y -= offset.y + (d.isQuirks ? db.clientTop+db.offsetTop : 0);
|
|
}else if(d.isFF == 3){
|
|
// In FF3 you have to subtract the document element margins.
|
|
// Fixed in FF3.5 though.
|
|
var cs = gcs(dh);
|
|
ret.x -= px(dh, cs.marginLeft) + px(dh, cs.borderLeftWidth);
|
|
ret.y -= px(dh, cs.marginTop) + px(dh, cs.borderTopWidth);
|
|
}
|
|
}else{
|
|
// FF2 and older WebKit
|
|
ret = {
|
|
x: 0,
|
|
y: 0,
|
|
w: node.offsetWidth,
|
|
h: node.offsetHeight
|
|
};
|
|
if(node["offsetParent"]){
|
|
ret.x -= _sumAncestorProperties(node, "scrollLeft");
|
|
ret.y -= _sumAncestorProperties(node, "scrollTop");
|
|
|
|
var curnode = node;
|
|
do{
|
|
var n = curnode.offsetLeft,
|
|
t = curnode.offsetTop;
|
|
ret.x += isNaN(n) ? 0 : n;
|
|
ret.y += isNaN(t) ? 0 : t;
|
|
|
|
cs = gcs(curnode);
|
|
if(curnode != node){
|
|
if(d.isMoz){
|
|
// tried left+right with differently sized left/right borders
|
|
// it really is 2xleft border in FF, not left+right, even in RTL!
|
|
ret.x += 2 * px(curnode,cs.borderLeftWidth);
|
|
ret.y += 2 * px(curnode,cs.borderTopWidth);
|
|
}else{
|
|
ret.x += px(curnode, cs.borderLeftWidth);
|
|
ret.y += px(curnode, cs.borderTopWidth);
|
|
}
|
|
}
|
|
// static children in a static div in FF2 are affected by the div's border as well
|
|
// but offsetParent will skip this div!
|
|
if(d.isMoz && cs.position=="static"){
|
|
var parent=curnode.parentNode;
|
|
while(parent!=curnode.offsetParent){
|
|
var pcs=gcs(parent);
|
|
if(pcs.position=="static"){
|
|
ret.x += px(curnode,pcs.borderLeftWidth);
|
|
ret.y += px(curnode,pcs.borderTopWidth);
|
|
}
|
|
parent=parent.parentNode;
|
|
}
|
|
}
|
|
curnode = curnode.offsetParent;
|
|
}while((curnode != dh) && 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(includeScroll){
|
|
var scroll = d._docScroll();
|
|
ret.x += scroll.x;
|
|
ret.y += scroll.y;
|
|
}
|
|
|
|
return ret; // Object
|
|
}
|
|
|
|
dojo.coords = function(/*DomNode|String*/node, /*Boolean?*/includeScroll){
|
|
// summary:
|
|
// Deprecated: Use position() for border-box x/y/w/h
|
|
// or marginBox() for margin-box w/h/l/t.
|
|
// Returns an object representing a node's size and position.
|
|
//
|
|
// description:
|
|
// Returns an object that measures margin-box (w)idth/(h)eight
|
|
// and absolute position x/y of the border-box. Also returned
|
|
// is computed (l)eft and (t)op values in pixels from the
|
|
// node's offsetParent as returned from marginBox().
|
|
// 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.position().
|
|
var n = byId(node), s = gcs(n), mb = d._getMarginBox(n, s);
|
|
var abs = d.position(n, includeScroll);
|
|
mb.x = abs.x;
|
|
mb.y = abs.y;
|
|
return mb;
|
|
}
|
|
|
|
// =============================
|
|
// Element attribute Functions
|
|
// =============================
|
|
|
|
// dojo.attr() should conform to http://www.w3.org/TR/DOM-Level-2-Core/
|
|
|
|
var _propNames = {
|
|
// properties renamed to avoid clashes with reserved words
|
|
"class": "className",
|
|
"for": "htmlFor",
|
|
// properties written as camelCase
|
|
tabindex: "tabIndex",
|
|
readonly: "readOnly",
|
|
colspan: "colSpan",
|
|
frameborder: "frameBorder",
|
|
rowspan: "rowSpan",
|
|
valuetype: "valueType"
|
|
},
|
|
_attrNames = {
|
|
// original attribute names
|
|
classname: "class",
|
|
htmlfor: "for",
|
|
// for IE
|
|
tabindex: "tabIndex",
|
|
readonly: "readOnly"
|
|
},
|
|
_forcePropNames = {
|
|
innerHTML: 1,
|
|
className: 1,
|
|
htmlFor: d.isIE,
|
|
value: 1
|
|
};
|
|
|
|
var _fixAttrName = function(/*String*/ name){
|
|
return _attrNames[name.toLowerCase()] || name;
|
|
};
|
|
|
|
var _hasAttr = function(node, name){
|
|
var attr = node.getAttributeNode && node.getAttributeNode(name);
|
|
return attr && attr.specified; // Boolean
|
|
};
|
|
|
|
// There is a difference in the presence of certain properties and their default values
|
|
// between browsers. For example, on IE "disabled" is present on all elements,
|
|
// but it is value is "false"; "tabIndex" of <div> returns 0 by default on IE, yet other browsers
|
|
// can return -1.
|
|
|
|
dojo.hasAttr = function(/*DomNode|String*/node, /*String*/name){
|
|
// summary:
|
|
// Returns true if the requested attribute is specified on the
|
|
// given element, and false otherwise.
|
|
// node:
|
|
// id or reference to the element to check
|
|
// name:
|
|
// the name of the attribute
|
|
// returns:
|
|
// true if the requested attribute is specified on the
|
|
// given element, and false otherwise
|
|
var lc = name.toLowerCase();
|
|
return _forcePropNames[_propNames[lc] || name] || _hasAttr(byId(node), _attrNames[lc] || name); // Boolean
|
|
}
|
|
|
|
var _evtHdlrMap = {}, _ctr = 0,
|
|
_attrId = dojo._scopeName + "attrid",
|
|
// the next dictionary lists elements with read-only innerHTML on IE
|
|
_roInnerHtml = {col: 1, colgroup: 1,
|
|
// frameset: 1, head: 1, html: 1, style: 1,
|
|
table: 1, tbody: 1, tfoot: 1, thead: 1, tr: 1, title: 1};
|
|
|
|
dojo.attr = function(/*DomNode|String*/node, /*String|Object*/name, /*String?*/value){
|
|
// summary:
|
|
// Gets or sets an attribute on an HTML element.
|
|
// description:
|
|
// Handles normalized getting and setting of attributes on DOM
|
|
// Nodes. If 2 arguments are passed, and a the second argumnt is a
|
|
// string, acts as a getter.
|
|
//
|
|
// If a third argument is passed, or if the second argument is a
|
|
// map of attributes, acts as a setter.
|
|
//
|
|
// When passing functions as values, note that they will not be
|
|
// directly assigned to slots on the node, but rather the default
|
|
// behavior will be removed and the new behavior will be added
|
|
// using `dojo.connect()`, meaning that event handler properties
|
|
// will be normalized and that some caveats with regards to
|
|
// non-standard behaviors for onsubmit apply. Namely that you
|
|
// should cancel form submission using `dojo.stopEvent()` on the
|
|
// passed event object instead of returning a boolean value from
|
|
// the handler itself.
|
|
// node:
|
|
// id or reference to the element to get or set the attribute on
|
|
// name:
|
|
// the name of the attribute to get or set.
|
|
// value:
|
|
// The value to set for the attribute
|
|
// returns:
|
|
// when used as a getter, the value of the requested attribute
|
|
// or null if that attribute does not have a specified or
|
|
// default value;
|
|
//
|
|
// when used as a setter, the DOM node
|
|
//
|
|
// example:
|
|
// | // get the current value of the "foo" attribute on a node
|
|
// | dojo.attr(dojo.byId("nodeId"), "foo");
|
|
// | // or we can just pass the id:
|
|
// | dojo.attr("nodeId", "foo");
|
|
//
|
|
// example:
|
|
// | // use attr() to set the tab index
|
|
// | dojo.attr("nodeId", "tabIndex", 3);
|
|
// |
|
|
//
|
|
// example:
|
|
// Set multiple values at once, including event handlers:
|
|
// | dojo.attr("formId", {
|
|
// | "foo": "bar",
|
|
// | "tabIndex": -1,
|
|
// | "method": "POST",
|
|
// | "onsubmit": function(e){
|
|
// | // stop submitting the form. Note that the IE behavior
|
|
// | // of returning true or false will have no effect here
|
|
// | // since our handler is connect()ed to the built-in
|
|
// | // onsubmit behavior and so we need to use
|
|
// | // dojo.stopEvent() to ensure that the submission
|
|
// | // doesn't proceed.
|
|
// | dojo.stopEvent(e);
|
|
// |
|
|
// | // submit the form with Ajax
|
|
// | dojo.xhrPost({ form: "formId" });
|
|
// | }
|
|
// | });
|
|
//
|
|
// example:
|
|
// Style is s special case: Only set with an object hash of styles
|
|
// | dojo.attr("someNode",{
|
|
// | id:"bar",
|
|
// | style:{
|
|
// | width:"200px", height:"100px", color:"#000"
|
|
// | }
|
|
// | });
|
|
//
|
|
// example:
|
|
// Again, only set style as an object hash of styles:
|
|
// | var obj = { color:"#fff", backgroundColor:"#000" };
|
|
// | dojo.attr("someNode", "style", obj);
|
|
// |
|
|
// | // though shorter to use `dojo.style()` in this case:
|
|
// | dojo.style("someNode", obj);
|
|
|
|
node = byId(node);
|
|
var args = arguments.length, prop;
|
|
if(args == 2 && typeof name != "string"){ // inline'd type check
|
|
// the object form of setter: the 2nd argument is a dictionary
|
|
for(var x in name){
|
|
d.attr(node, x, name[x]);
|
|
}
|
|
return node; // DomNode
|
|
}
|
|
var lc = name.toLowerCase(),
|
|
propName = _propNames[lc] || name,
|
|
forceProp = _forcePropNames[propName],
|
|
attrName = _attrNames[lc] || name;
|
|
if(args == 3){
|
|
// setter
|
|
do{
|
|
if(propName == "style" && typeof value != "string"){ // inline'd type check
|
|
// special case: setting a style
|
|
d.style(node, value);
|
|
break;
|
|
}
|
|
if(propName == "innerHTML"){
|
|
// special case: assigning HTML
|
|
if(d.isIE && node.tagName.toLowerCase() in _roInnerHtml){
|
|
d.empty(node);
|
|
node.appendChild(d._toDom(value, node.ownerDocument));
|
|
}else{
|
|
node[propName] = value;
|
|
}
|
|
break;
|
|
}
|
|
if(d.isFunction(value)){
|
|
// special case: assigning an event handler
|
|
// clobber if we can
|
|
var attrId = d.attr(node, _attrId);
|
|
if(!attrId){
|
|
attrId = _ctr++;
|
|
d.attr(node, _attrId, attrId);
|
|
}
|
|
if(!_evtHdlrMap[attrId]){
|
|
_evtHdlrMap[attrId] = {};
|
|
}
|
|
var h = _evtHdlrMap[attrId][propName];
|
|
if(h){
|
|
d.disconnect(h);
|
|
}else{
|
|
try{
|
|
delete node[propName];
|
|
}catch(e){}
|
|
}
|
|
// ensure that event objects are normalized, etc.
|
|
_evtHdlrMap[attrId][propName] = d.connect(node, propName, value);
|
|
break;
|
|
}
|
|
if(forceProp || typeof value == "boolean"){
|
|
// special case: forcing assignment to the property
|
|
// special case: setting boolean to a property instead of attribute
|
|
node[propName] = value;
|
|
break;
|
|
}
|
|
// node's attribute
|
|
node.setAttribute(attrName, value);
|
|
}while(false);
|
|
return node; // DomNode
|
|
}
|
|
// getter
|
|
// should we access this attribute via a property or
|
|
// via getAttribute()?
|
|
value = node[propName];
|
|
if(forceProp && typeof value != "undefined"){
|
|
// node's property
|
|
return value; // Anything
|
|
}
|
|
if(propName != "href" && (typeof value == "boolean" || d.isFunction(value))){
|
|
// node's property
|
|
return value; // Anything
|
|
}
|
|
// node's attribute
|
|
// we need _hasAttr() here to guard against IE returning a default value
|
|
return _hasAttr(node, attrName) ? node.getAttribute(attrName) : null; // Anything
|
|
}
|
|
|
|
dojo.removeAttr = function(/*DomNode|String*/ node, /*String*/ name){
|
|
// summary:
|
|
// Removes an attribute from an HTML element.
|
|
// node:
|
|
// id or reference to the element to remove the attribute from
|
|
// name:
|
|
// the name of the attribute to remove
|
|
byId(node).removeAttribute(_fixAttrName(name));
|
|
}
|
|
|
|
dojo.getNodeProp = function(/*DomNode|String*/ node, /*String*/ name){
|
|
// summary:
|
|
// Returns an effective value of a property or an attribute.
|
|
// node:
|
|
// id or reference to the element to remove the attribute from
|
|
// name:
|
|
// the name of the attribute
|
|
node = byId(node);
|
|
var lc = name.toLowerCase(),
|
|
propName = _propNames[lc] || name;
|
|
if((propName in node) && propName != "href"){
|
|
// node's property
|
|
return node[propName]; // Anything
|
|
}
|
|
// node's attribute
|
|
var attrName = _attrNames[lc] || name;
|
|
return _hasAttr(node, attrName) ? node.getAttribute(attrName) : null; // Anything
|
|
}
|
|
|
|
dojo.create = function(tag, attrs, refNode, pos){
|
|
// summary:
|
|
// Create an element, allowing for optional attribute decoration
|
|
// and placement.
|
|
//
|
|
// description:
|
|
// A DOM Element creation function. A shorthand method for creating a node or
|
|
// a fragment, and allowing for a convenient optional attribute setting step,
|
|
// as well as an optional DOM placement reference.
|
|
//|
|
|
// Attributes are set by passing the optional object through `dojo.attr`.
|
|
// See `dojo.attr` for noted caveats and nuances, and API if applicable.
|
|
//|
|
|
// Placement is done via `dojo.place`, assuming the new node to be the action
|
|
// node, passing along the optional reference node and position.
|
|
//
|
|
// tag: String|DomNode
|
|
// A string of the element to create (eg: "div", "a", "p", "li", "script", "br"),
|
|
// or an existing DOM node to process.
|
|
//
|
|
// attrs: Object
|
|
// An object-hash of attributes to set on the newly created node.
|
|
// Can be null, if you don't want to set any attributes/styles.
|
|
// See: `dojo.attr` for a description of available attributes.
|
|
//
|
|
// refNode: String?|DomNode?
|
|
// Optional reference node. Used by `dojo.place` to place the newly created
|
|
// node somewhere in the dom relative to refNode. Can be a DomNode reference
|
|
// or String ID of a node.
|
|
//
|
|
// pos: String?
|
|
// Optional positional reference. Defaults to "last" by way of `dojo.place`,
|
|
// though can be set to "first","after","before","last", "replace" or "only"
|
|
// to further control the placement of the new node relative to the refNode.
|
|
// 'refNode' is required if a 'pos' is specified.
|
|
//
|
|
// returns: DomNode
|
|
//
|
|
// example:
|
|
// Create a DIV:
|
|
// | var n = dojo.create("div");
|
|
//
|
|
// example:
|
|
// Create a DIV with content:
|
|
// | var n = dojo.create("div", { innerHTML:"<p>hi</p>" });
|
|
//
|
|
// example:
|
|
// Place a new DIV in the BODY, with no attributes set
|
|
// | var n = dojo.create("div", null, dojo.body());
|
|
//
|
|
// example:
|
|
// Create an UL, and populate it with LI's. Place the list as the first-child of a
|
|
// node with id="someId":
|
|
// | var ul = dojo.create("ul", null, "someId", "first");
|
|
// | var items = ["one", "two", "three", "four"];
|
|
// | dojo.forEach(items, function(data){
|
|
// | dojo.create("li", { innerHTML: data }, ul);
|
|
// | });
|
|
//
|
|
// example:
|
|
// Create an anchor, with an href. Place in BODY:
|
|
// | dojo.create("a", { href:"foo.html", title:"Goto FOO!" }, dojo.body());
|
|
//
|
|
// example:
|
|
// Create a `dojo.NodeList()` from a new element (for syntatic sugar):
|
|
// | dojo.query(dojo.create('div'))
|
|
// | .addClass("newDiv")
|
|
// | .onclick(function(e){ console.log('clicked', e.target) })
|
|
// | .place("#someNode"); // redundant, but cleaner.
|
|
|
|
var doc = d.doc;
|
|
if(refNode){
|
|
refNode = byId(refNode);
|
|
doc = refNode.ownerDocument;
|
|
}
|
|
if(typeof tag == "string"){ // inline'd type check
|
|
tag = doc.createElement(tag);
|
|
}
|
|
if(attrs){ d.attr(tag, attrs); }
|
|
if(refNode){ d.place(tag, refNode, pos); }
|
|
return tag; // DomNode
|
|
}
|
|
|
|
/*=====
|
|
dojo.empty = function(node){
|
|
// summary:
|
|
// safely removes all children of the node.
|
|
// node: DOMNode|String
|
|
// a reference to a DOM node or an id.
|
|
// example:
|
|
// Destroy node's children byId:
|
|
// | dojo.empty("someId");
|
|
//
|
|
// example:
|
|
// Destroy all nodes' children in a list by reference:
|
|
// | dojo.query(".someNode").forEach(dojo.empty);
|
|
}
|
|
=====*/
|
|
|
|
d.empty =
|
|
d.isIE ? function(node){
|
|
node = byId(node);
|
|
for(var c; c = node.lastChild;){ // intentional assignment
|
|
d.destroy(c);
|
|
}
|
|
} :
|
|
function(node){
|
|
byId(node).innerHTML = "";
|
|
};
|
|
|
|
/*=====
|
|
dojo._toDom = function(frag, doc){
|
|
// summary:
|
|
// instantiates an HTML fragment returning the corresponding DOM.
|
|
// frag: String
|
|
// the HTML fragment
|
|
// doc: DocumentNode?
|
|
// optional document to use when creating DOM nodes, defaults to
|
|
// dojo.doc if not specified.
|
|
// returns: DocumentFragment
|
|
//
|
|
// example:
|
|
// Create a table row:
|
|
// | var tr = dojo._toDom("<tr><td>First!</td></tr>");
|
|
}
|
|
=====*/
|
|
|
|
// support stuff for dojo._toDom
|
|
var tagWrap = {
|
|
option: ["select"],
|
|
tbody: ["table"],
|
|
thead: ["table"],
|
|
tfoot: ["table"],
|
|
tr: ["table", "tbody"],
|
|
td: ["table", "tbody", "tr"],
|
|
th: ["table", "thead", "tr"],
|
|
legend: ["fieldset"],
|
|
caption: ["table"],
|
|
colgroup: ["table"],
|
|
col: ["table", "colgroup"],
|
|
li: ["ul"]
|
|
},
|
|
reTag = /<\s*([\w\:]+)/,
|
|
masterNode = {}, masterNum = 0,
|
|
masterName = "__" + d._scopeName + "ToDomId";
|
|
|
|
// generate start/end tag strings to use
|
|
// for the injection for each special tag wrap case.
|
|
for(var param in tagWrap){
|
|
var tw = tagWrap[param];
|
|
tw.pre = param == "option" ? '<select multiple="multiple">' : "<" + tw.join("><") + ">";
|
|
tw.post = "</" + tw.reverse().join("></") + ">";
|
|
// the last line is destructive: it reverses the array,
|
|
// but we don't care at this point
|
|
}
|
|
|
|
d._toDom = function(frag, doc){
|
|
// summary:
|
|
// converts HTML string into DOM nodes.
|
|
|
|
doc = doc || d.doc;
|
|
var masterId = doc[masterName];
|
|
if(!masterId){
|
|
doc[masterName] = masterId = ++masterNum + "";
|
|
masterNode[masterId] = doc.createElement("div");
|
|
}
|
|
|
|
// make sure the frag is a string.
|
|
frag += "";
|
|
|
|
// find the starting tag, and get node wrapper
|
|
var match = frag.match(reTag),
|
|
tag = match ? match[1].toLowerCase() : "",
|
|
master = masterNode[masterId],
|
|
wrap, i, fc, df;
|
|
if(match && tagWrap[tag]){
|
|
wrap = tagWrap[tag];
|
|
master.innerHTML = wrap.pre + frag + wrap.post;
|
|
for(i = wrap.length; i; --i){
|
|
master = master.firstChild;
|
|
}
|
|
}else{
|
|
master.innerHTML = frag;
|
|
}
|
|
|
|
// one node shortcut => return the node itself
|
|
if(master.childNodes.length == 1){
|
|
return master.removeChild(master.firstChild); // DOMNode
|
|
}
|
|
|
|
// return multiple nodes as a document fragment
|
|
df = doc.createDocumentFragment();
|
|
while(fc = master.firstChild){ // intentional assignment
|
|
df.appendChild(fc);
|
|
}
|
|
return df; // DOMNode
|
|
}
|
|
|
|
// =============================
|
|
// (CSS) Class Functions
|
|
// =============================
|
|
var _className = "className";
|
|
|
|
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.
|
|
//
|
|
// node:
|
|
// String ID or DomNode reference to check the class for.
|
|
//
|
|
// classStr:
|
|
// A string class name to look for.
|
|
//
|
|
// example:
|
|
// Do something if a node with id="someNode" has class="aSillyClassName" present
|
|
// | if(dojo.hasClass("someNode","aSillyClassName")){ ... }
|
|
|
|
return ((" "+ byId(node)[_className] +" ").indexOf(" " + classStr + " ") >= 0); // Boolean
|
|
};
|
|
|
|
var spaces = /\s+/, a1 = [""],
|
|
str2array = function(s){
|
|
if(typeof s == "string" || s instanceof String){
|
|
if(s.indexOf(" ") < 0){
|
|
a1[0] = s;
|
|
return a1;
|
|
}else{
|
|
return s.split(spaces);
|
|
}
|
|
}
|
|
// assumed to be an array
|
|
return s || "";
|
|
};
|
|
|
|
dojo.addClass = function(/*DomNode|String*/node, /*String|Array*/classStr){
|
|
// summary:
|
|
// Adds the specified classes to the end of the class list on the
|
|
// passed node. Will not re-apply duplicate classes.
|
|
//
|
|
// node:
|
|
// String ID or DomNode reference to add a class string too
|
|
//
|
|
// classStr:
|
|
// A String class name to add, or several space-separated class names,
|
|
// or an array of class names.
|
|
//
|
|
// example:
|
|
// Add a class to some node:
|
|
// | dojo.addClass("someNode", "anewClass");
|
|
//
|
|
// example:
|
|
// Add two classes at once:
|
|
// | dojo.addClass("someNode", "firstClass secondClass");
|
|
//
|
|
// example:
|
|
// Add two classes at once (using array):
|
|
// | dojo.addClass("someNode", ["firstClass", "secondClass"]);
|
|
//
|
|
// example:
|
|
// Available in `dojo.NodeList` for multiple additions
|
|
// | dojo.query("ul > li").addClass("firstLevel");
|
|
|
|
node = byId(node);
|
|
classStr = str2array(classStr);
|
|
var cls = node[_className], oldLen;
|
|
cls = cls ? " " + cls + " " : " ";
|
|
oldLen = cls.length;
|
|
for(var i = 0, len = classStr.length, c; i < len; ++i){
|
|
c = classStr[i];
|
|
if(c && cls.indexOf(" " + c + " ") < 0){
|
|
cls += c + " ";
|
|
}
|
|
}
|
|
if(oldLen < cls.length){
|
|
node[_className] = cls.substr(1, cls.length - 2);
|
|
}
|
|
};
|
|
|
|
dojo.removeClass = function(/*DomNode|String*/node, /*String|Array?*/classStr){
|
|
// summary:
|
|
// Removes the specified classes from node. No `dojo.hasClass`
|
|
// check is required.
|
|
//
|
|
// node:
|
|
// String ID or DomNode reference to remove the class from.
|
|
//
|
|
// classStr:
|
|
// An optional String class name to remove, or several space-separated
|
|
// class names, or an array of class names. If omitted, all class names
|
|
// will be deleted.
|
|
//
|
|
// example:
|
|
// Remove a class from some node:
|
|
// | dojo.removeClass("someNode", "firstClass");
|
|
//
|
|
// example:
|
|
// Remove two classes from some node:
|
|
// | dojo.removeClass("someNode", "firstClass secondClass");
|
|
//
|
|
// example:
|
|
// Remove two classes from some node (using array):
|
|
// | dojo.removeClass("someNode", ["firstClass", "secondClass"]);
|
|
//
|
|
// example:
|
|
// Remove all classes from some node:
|
|
// | dojo.removeClass("someNode");
|
|
//
|
|
// example:
|
|
// Available in `dojo.NodeList()` for multiple removal
|
|
// | dojo.query(".foo").removeClass("foo");
|
|
|
|
node = byId(node);
|
|
var cls;
|
|
if(classStr !== undefined){
|
|
classStr = str2array(classStr);
|
|
cls = " " + node[_className] + " ";
|
|
for(var i = 0, len = classStr.length; i < len; ++i){
|
|
cls = cls.replace(" " + classStr[i] + " ", " ");
|
|
}
|
|
cls = d.trim(cls);
|
|
}else{
|
|
cls = "";
|
|
}
|
|
if(node[_className] != cls){ node[_className] = cls; }
|
|
};
|
|
|
|
dojo.toggleClass = function(/*DomNode|String*/node, /*String|Array*/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.
|
|
//
|
|
// example:
|
|
// | dojo.toggleClass("someNode", "hovered");
|
|
//
|
|
// example:
|
|
// Forcefully add a class
|
|
// | dojo.toggleClass("someNode", "hovered", true);
|
|
//
|
|
// example:
|
|
// Available in `dojo.NodeList()` for multiple toggles
|
|
// | dojo.query(".toggleMe").toggleClass("toggleMe");
|
|
|
|
if(condition === undefined){
|
|
condition = !d.hasClass(node, classStr);
|
|
}
|
|
d[condition ? "addClass" : "removeClass"](node, classStr);
|
|
};
|
|
|
|
})();
|
|
|
|
}
|