summaryrefslogtreecommitdiff
path: root/lib/dojo/tt-rss-layer.js.uncompressed.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dojo/tt-rss-layer.js.uncompressed.js')
-rw-r--r--lib/dojo/tt-rss-layer.js.uncompressed.js8906
1 files changed, 4938 insertions, 3968 deletions
diff --git a/lib/dojo/tt-rss-layer.js.uncompressed.js b/lib/dojo/tt-rss-layer.js.uncompressed.js
index fa2e12157..81d4302df 100644
--- a/lib/dojo/tt-rss-layer.js.uncompressed.js
+++ b/lib/dojo/tt-rss-layer.js.uncompressed.js
@@ -1,5 +1,5 @@
/*
- Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
+ Copyright (c) 2004-2011, 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
*/
@@ -16,6 +16,8 @@ if(!dojo._hasResource["dojo.date.stamp"]){ //_hasResource checks added by build.
dojo._hasResource["dojo.date.stamp"] = true;
dojo.provide("dojo.date.stamp");
+dojo.getObject("date.stamp", true, dojo);
+
// Methods to convert dates to or from a wire (string) format using well-known conventions
dojo.date.stamp.fromISOString = function(/*String*/formattedString, /*Number?*/defaultTime){
@@ -94,7 +96,7 @@ dojo.date.stamp.fromISOString = function(/*String*/formattedString, /*Number?*/d
}
return result; // Date or null
-}
+};
/*=====
dojo.date.stamp.__Options = function(){
@@ -144,13 +146,13 @@ dojo.date.stamp.toISOString = function(/*Date*/dateObject, /*dojo.date.stamp.__O
}else if(options.selector != "time"){
var timezoneOffset = dateObject.getTimezoneOffset();
var absOffset = Math.abs(timezoneOffset);
- time += (timezoneOffset > 0 ? "-" : "+") +
+ time += (timezoneOffset > 0 ? "-" : "+") +
_(Math.floor(absOffset/60)) + ":" + _(absOffset%60);
}
formattedDate.push(time);
}
return formattedDate.join('T'); // String
-}
+};
}
@@ -159,14 +161,14 @@ dojo._hasResource["dojo.parser"] = true;
dojo.provide("dojo.parser");
+
new Date("X"); // workaround for #11279, new Date("") == NaN
dojo.parser = new function(){
- // summary: The Dom/Widget parsing package
+ // summary:
+ // The Dom/Widget parsing package
var d = dojo;
- this._attrName = d._scopeName + "Type";
- this._query = "[" + this._attrName + "]";
function val2type(/*Object*/ value){
// summary:
@@ -191,13 +193,13 @@ dojo.parser = new function(){
case "number":
return value.length ? Number(value) : NaN;
case "boolean":
- // for checked/disabled value might be "" or "checked". interpret as true.
+ // for checked/disabled value might be "" or "checked". interpret as true.
return typeof value == "boolean" ? value : !(value.toLowerCase()=="false");
case "function":
if(d.isFunction(value)){
// IE gives us a function, even when we say something like onClick="foo"
- // (in which case it gives us an invalid function "function(){ foo }").
- // Therefore, convert to string
+ // (in which case it gives us an invalid function "function(){ foo }").
+ // Therefore, convert to string
value=value.toString();
value=d.trim(value.substring(value.indexOf('{')+1, value.length-1));
}
@@ -226,7 +228,7 @@ dojo.parser = new function(){
}
}
- var instanceClasses = {
+ var dummyClass = {}, instanceClasses = {
// map from fully qualified name (like "dijit.Button") to structure like
// { cls: dijit.Button, params: {label: "string", disabled: "boolean"} }
};
@@ -234,45 +236,70 @@ dojo.parser = new function(){
// Widgets like BorderContainer add properties to _Widget via dojo.extend().
// If BorderContainer is loaded after _Widget's parameter list has been cached,
// we need to refresh that parameter list (for _Widget and all widgets that extend _Widget).
- dojo.connect(dojo, "extend", function(){
+ // TODO: remove this in 2.0, when we stop caching parameters.
+ d.connect(d, "extend", function(){
instanceClasses = {};
});
- function getClassInfo(/*String*/ className){
+ function getProtoInfo(cls, params){
+ // cls: A prototype
+ // The prototype of the class to check props on
+ // params: Object
+ // The parameters object to mix found parameters onto.
+ for(var name in cls){
+ if(name.charAt(0)=="_"){ continue; } // skip internal properties
+ if(name in dummyClass){ continue; } // skip "constructor" and "toString"
+ params[name] = val2type(cls[name]);
+ }
+ return params;
+ }
+
+ function getClassInfo(/*String*/ className, /*Boolean*/ skipParamsLookup){
+ // summary:
+ // Maps a widget name string like "dijit.form.Button" to the widget constructor itself,
+ // and a list of that widget's parameters and their types
// className:
// fully qualified name (like "dijit.form.Button")
// returns:
// structure like
- // {
- // cls: dijit.Button,
+ // {
+ // cls: dijit.Button,
// params: { label: "string", disabled: "boolean"}
// }
- if(!instanceClasses[className]){
+ var c = instanceClasses[className];
+ if(!c){
// get pointer to widget class
- var cls = d.getObject(className);
+ var cls = d.getObject(className), params = null;
if(!cls){ return null; } // class not defined [yet]
-
- var proto = cls.prototype;
-
- // get table of parameter names & types
- var params = {}, dummyClass = {};
- for(var name in proto){
- if(name.charAt(0)=="_"){ continue; } // skip internal properties
- if(name in dummyClass){ continue; } // skip "constructor" and "toString"
- var defVal = proto[name];
- params[name]=val2type(defVal);
+ if(!skipParamsLookup){ // from fastpath, we don't need to lookup the attrs on the proto because they are explicit
+ params = getProtoInfo(cls.prototype, {})
}
-
- instanceClasses[className] = { cls: cls, params: params };
+ c = { cls: cls, params: params };
+
+ }else if(!skipParamsLookup && !c.params){
+ // if we're calling getClassInfo and have a cls proto, but no params info, scan that cls for params now
+ // and update the pointer in instanceClasses[className]. This happens when a widget appears in another
+ // widget's template which still uses dojoType, but an instance of the widget appears prior with a data-dojo-type,
+ // skipping this lookup the first time.
+ c.params = getProtoInfo(c.cls.prototype, {});
}
- return instanceClasses[className];
+
+ return c;
}
- this._functionFromScript = function(script){
+ this._functionFromScript = function(script, attrData){
+ // summary:
+ // Convert a <script type="dojo/method" args="a, b, c"> ... </script>
+ // into a function
+ // script: DOMNode
+ // The <script> DOMNode
+ // attrData: String
+ // For HTML5 compliance, searches for attrData + "args" (typically
+ // "data-dojo-args") instead of "args"
var preamble = "";
var suffix = "";
- var argsStr = script.getAttribute("args");
+ var argsStr = (script.getAttribute(attrData + "args") || script.getAttribute("args"));
if(argsStr){
d.forEach(argsStr.split(/\s*,\s*/), function(part, idx){
preamble += "var "+part+" = arguments["+idx+"]; ";
@@ -286,7 +313,7 @@ dojo.parser = new function(){
});
}
return new Function(preamble+script.innerHTML+suffix);
- }
+ };
this.instantiate = function(/* Array */nodes, /* Object? */mixin, /* Object? */args){
// summary:
@@ -307,69 +334,106 @@ dojo.parser = new function(){
// exist.
// args: Object?
// An object used to hold kwArgs for instantiation.
- // Supports 'noStart' and inherited.
- var thelist = [], dp = dojo.parser;
+ // See parse.args argument for details.
+
+ var thelist = [],
mixin = mixin||{};
args = args||{};
-
+
+ // TODO: for 2.0 default to data-dojo- regardless of scopeName (or maybe scopeName won't exist in 2.0)
+ var attrName = (args.scope || d._scopeName) + "Type", // typically "dojoType"
+ attrData = "data-" + (args.scope || d._scopeName) + "-"; // typically "data-dojo-"
+
d.forEach(nodes, function(obj){
if(!obj){ return; }
- // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc.s
- var node, type, clsInfo, clazz, scripts;
+ // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc.
+ var node, type, clsInfo, clazz, scripts, fastpath;
if(obj.node){
// new format of nodes[] array, object w/lots of properties pre-computed for me
node = obj.node;
type = obj.type;
- clsInfo = obj.clsInfo || (type && getClassInfo(type));
+ fastpath = obj.fastpath;
+ clsInfo = obj.clsInfo || (type && getClassInfo(type, fastpath));
clazz = clsInfo && clsInfo.cls;
scripts = obj.scripts;
}else{
- // old (backwards compatible) format of nodes[] array, simple array of DOMNodes
+ // old (backwards compatible) format of nodes[] array, simple array of DOMNodes. no fastpath/data-dojo-type support here.
node = obj;
- type = dp._attrName in mixin ? mixin[dp._attrName] : node.getAttribute(dp._attrName);
+ type = attrName in mixin ? mixin[attrName] : node.getAttribute(attrName);
clsInfo = type && getClassInfo(type);
clazz = clsInfo && clsInfo.cls;
- scripts = (clazz && (clazz._noScript || clazz.prototype._noScript) ? [] :
+ scripts = (clazz && (clazz._noScript || clazz.prototype._noScript) ? [] :
d.query("> script[type^='dojo/']", node));
}
if(!clsInfo){
throw new Error("Could not load class '" + type);
}
- // Setup hash to hold parameter settings for this widget. Start with the parameter
+ // Setup hash to hold parameter settings for this widget. Start with the parameter
// settings inherited from ancestors ("dir" and "lang").
// Inherited setting may later be overridden by explicit settings on node itself.
- var params = {},
- attributes = node.attributes;
+ var params = {};
+
if(args.defaults){
// settings for the document itself (or whatever subtree is being parsed)
- dojo.mixin(params, args.defaults);
+ d._mixin(params, args.defaults);
}
if(obj.inherited){
// settings from dir=rtl or lang=... on a node above this node
- dojo.mixin(params, obj.inherited);
+ d._mixin(params, obj.inherited);
}
+
+ // mix things found in data-dojo-props into the params
+ if(fastpath){
+ var extra = node.getAttribute(attrData + "props");
+ if(extra && extra.length){
+ try{
+ extra = d.fromJson.call(args.propsThis, "{" + extra + "}");
+ d._mixin(params, extra);
+ }catch(e){
+ // give the user a pointer to their invalid parameters. FIXME: can we kill this in production?
+ throw new Error(e.toString() + " in data-dojo-props='" + extra + "'");
+ }
+ }
- // read parameters (ie, attributes) specified on DOMNode
- // clsInfo.params lists expected params like {"checked": "boolean", "n": "number"}
- for(var name in clsInfo.params){
- var item = name in mixin?{value:mixin[name],specified:true}:attributes.getNamedItem(name);
- if(!item || (!item.specified && (!dojo.isIE || name.toLowerCase()!="value"))){ continue; }
- var value = item.value;
- // Deal with IE quirks for 'class' and 'style'
- switch(name){
- case "class":
- value = "className" in mixin?mixin.className:node.className;
- break;
- case "style":
- value = "style" in mixin?mixin.style:(node.style && node.style.cssText); // FIXME: Opera?
+ // For the benefit of _Templated, check if node has data-dojo-attach-point/data-dojo-attach-event
+ // and mix those in as though they were parameters
+ var attachPoint = node.getAttribute(attrData + "attach-point");
+ if(attachPoint){
+ params.dojoAttachPoint = attachPoint;
}
- var _type = clsInfo.params[name];
- if(typeof value == "string"){
- params[name] = str2obj(value, _type);
- }else{
- params[name] = value;
+ var attachEvent = node.getAttribute(attrData + "attach-event");
+ if(attachEvent){
+ params.dojoAttachEvent = attachEvent;
+ }
+ dojo.mixin(params, mixin);
+ }else{
+ // FIXME: we need something like "deprecateOnce()" to throw dojo.deprecation for something.
+ // remove this logic in 2.0
+ // read parameters (ie, attributes) specified on DOMNode
+
+ var attributes = node.attributes;
+
+ // clsInfo.params lists expected params like {"checked": "boolean", "n": "number"}
+ for(var name in clsInfo.params){
+ var item = name in mixin ? { value:mixin[name], specified:true } : attributes.getNamedItem(name);
+ if(!item || (!item.specified && (!dojo.isIE || name.toLowerCase()!="value"))){ continue; }
+ var value = item.value;
+ // Deal with IE quirks for 'class' and 'style'
+ switch(name){
+ case "class":
+ value = "className" in mixin ? mixin.className : node.className;
+ break;
+ case "style":
+ value = "style" in mixin ? mixin.style : (node.style && node.style.cssText); // FIXME: Opera?
+ }
+ var _type = clsInfo.params[name];
+ if(typeof value == "string"){
+ params[name] = str2obj(value, _type);
+ }else{
+ params[name] = value;
+ }
}
}
@@ -384,9 +448,10 @@ dojo.parser = new function(){
d.forEach(scripts, function(script){
node.removeChild(script);
- var event = script.getAttribute("event"),
+ // FIXME: drop event="" support in 2.0. use data-dojo-event="" instead
+ var event = (script.getAttribute(attrData + "event") || script.getAttribute("event")),
type = script.getAttribute("type"),
- nf = d.parser._functionFromScript(script);
+ nf = d.parser._functionFromScript(script, attrData);
if(event){
if(type == "dojo/connect"){
connects.push({event: event, func: nf});
@@ -404,7 +469,8 @@ dojo.parser = new function(){
thelist.push(instance);
// map it to the JS namespace if that makes sense
- var jsname = node.getAttribute("jsId");
+ // FIXME: in 2.0, drop jsId support. use data-dojo-id instead
+ var jsname = (node.getAttribute(attrData + "id") || node.getAttribute("jsId"));
if(jsname){
d.setObject(jsname, instance);
}
@@ -428,9 +494,9 @@ dojo.parser = new function(){
// ContentPane is the parent widget (so that the parse doesn't call startup() on the
// ContentPane's children)
d.forEach(thelist, function(instance){
- if( !args.noStart && instance &&
- instance.startup &&
- !instance._started &&
+ if( !args.noStart && instance &&
+ dojo.isFunction(instance.startup) &&
+ !instance._started &&
(!instance.getParent || !instance.getParent())
){
instance.startup();
@@ -440,34 +506,57 @@ dojo.parser = new function(){
return thelist;
};
- this.parse = function(/*DomNode?*/ rootNode, /* Object? */ args){
+ this.parse = function(rootNode, args){
// summary:
// Scan the DOM for class instances, and instantiate them.
//
// description:
// Search specified node (or root node) recursively for class instances,
- // and instantiate them Searches for
- // dojoType="qualified.class.name"
+ // and instantiate them. Searches for either data-dojo-type="Class" or
+ // dojoType="Class" where "Class" is a a fully qualified class name,
+ // like `dijit.form.Button`
+ //
+ // Using `data-dojo-type`:
+ // Attributes using can be mixed into the parameters used to instantitate the
+ // Class by using a `data-dojo-props` attribute on the node being converted.
+ // `data-dojo-props` should be a string attribute to be converted from JSON.
+ //
+ // Using `dojoType`:
+ // Attributes are read from the original domNode and converted to appropriate
+ // types by looking up the Class prototype values. This is the default behavior
+ // from Dojo 1.0 to Dojo 1.5. `dojoType` support is deprecated, and will
+ // go away in Dojo 2.0.
//
// rootNode: DomNode?
// A default starting root node from which to start the parsing. Can be
// omitted, defaulting to the entire document. If omitted, the `args`
- // object can be passed in this place. If the `args` object has a
+ // object can be passed in this place. If the `args` object has a
// `rootNode` member, that is used.
//
- // args:
+ // args: Object
// a kwArgs object passed along to instantiate()
- //
+ //
// * noStart: Boolean?
// when set will prevent the parser from calling .startup()
- // when locating the nodes.
+ // when locating the nodes.
// * rootNode: DomNode?
// identical to the function's `rootNode` argument, though
- // allowed to be passed in via this `args object.
+ // allowed to be passed in via this `args object.
+ // * template: Boolean
+ // If true, ignores ContentPane's stopParser flag and parses contents inside of
+ // a ContentPane inside of a template. This allows dojoAttachPoint on widgets/nodes
+ // nested inside the ContentPane to work.
// * inherited: Object
// Hash possibly containing dir and lang settings to be applied to
// parsed widgets, unless there's another setting on a sub-node that overrides
- //
+ // * scope: String
+ // Root for attribute names to search for. If scopeName is dojo,
+ // will search for data-dojo-type (or dojoType). For backwards compatibility
+ // reasons defaults to dojo._scopeName (which is "dojo" except when
+ // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
+ // * propsThis: Object
+ // If specified, "this" referenced from data-dojo-props will refer to propsThis.
+ // Intended for use from the widgets-in-template feature of `dijit._Templated`
//
// example:
// Parse all widgets on a page:
@@ -475,10 +564,10 @@ dojo.parser = new function(){
//
// example:
// Parse all classes within the node with id="foo"
- // | dojo.parser.parse(dojo.byId(foo));
+ // | dojo.parser.parse(dojo.byId('foo'));
//
// example:
- // Parse all classes in a page, but do not call .startup() on any
+ // Parse all classes in a page, but do not call .startup() on any
// child
// | dojo.parser.parse({ noStart: true })
//
@@ -486,7 +575,7 @@ dojo.parser = new function(){
// Parse all classes in a node, but do not call .startup()
// | dojo.parser.parse(someNode, { noStart:true });
// | // or
- // | dojo.parser.parse({ noStart:true, rootNode: someNode });
+ // | dojo.parser.parse({ noStart:true, rootNode: someNode });
// determine the root node based on the passed arguments.
var root;
@@ -496,8 +585,12 @@ dojo.parser = new function(){
}else{
root = rootNode;
}
+ root = root ? dojo.byId(root) : dojo.body();
+ args = args || {};
+
+ var attrName = (args.scope || d._scopeName) + "Type", // typically "dojoType"
+ attrData = "data-" + (args.scope || d._scopeName) + "-"; // typically "data-dojo-"
- var attrName = this._attrName;
function scan(parent, list){
// summary:
// Parent is an Object representing a DOMNode, with or without a dojoType specified.
@@ -506,7 +599,7 @@ dojo.parser = new function(){
// parent: Object
// Object representing the parent node, like
// | {
- // | node: DomNode, // scan children of this node
+ // | node: DomNode, // scan children of this node
// | inherited: {dir: "rtl"}, // dir/lang setting inherited from above node
// |
// | // attributes only set if node has dojoType specified
@@ -519,6 +612,7 @@ dojo.parser = new function(){
// Effective dir and lang settings on parent node, either set directly or inherited from grandparent
var inherited = dojo.clone(parent.inherited);
dojo.forEach(["dir", "lang"], function(name){
+ // TODO: what if this is a widget and dir/lang are declared in data-dojo-props?
var val = parent.node.getAttribute(name);
if(val){
inherited[name] = val;
@@ -526,20 +620,31 @@ dojo.parser = new function(){
});
// if parent is a widget, then search for <script type=dojo/*> tags and put them in scripts[].
- var scripts = parent.scripts;
+ var scripts = parent.clsInfo && !parent.clsInfo.cls.prototype._noScript ? parent.scripts : null;
// unless parent is a widget with the stopParser flag set, continue search for dojoType, recursively
- var recurse = !parent.clsInfo || !parent.clsInfo.cls.prototype.stopParser;
+ var recurse = (!parent.clsInfo || !parent.clsInfo.cls.prototype.stopParser) || (args && args.template);
// scan parent's children looking for dojoType and <script type=dojo/*>
for(var child = parent.node.firstChild; child; child = child.nextSibling){
if(child.nodeType == 1){
- var type = recurse && child.getAttribute(attrName);
+ // FIXME: desupport dojoType in 2.0. use data-dojo-type instead
+ var type, html5 = recurse && child.getAttribute(attrData + "type");
+ if(html5){
+ type = html5;
+ }else{
+ // fallback to backward compatible mode, using dojoType. remove in 2.0
+ type = recurse && child.getAttribute(attrName);
+ }
+
+ var fastpath = html5 == type;
+
if(type){
- // if dojoType specified, add to output array of nodes to instantiate
+ // if dojoType/data-dojo-type specified, add to output array of nodes to instantiate
var params = {
"type": type,
- clsInfo: getClassInfo(type), // note: won't find classes declared via dojo.Declaration
+ fastpath: fastpath,
+ clsInfo: getClassInfo(type, fastpath), // note: won't find classes declared via dojo.Declaration
node: child,
scripts: [], // <script> nodes that are parent's children
inherited: inherited // dir & lang attributes inherited from parent
@@ -552,7 +657,7 @@ dojo.parser = new function(){
}else if(scripts && child.nodeName.toLowerCase() == "script"){
// if <script type="dojo/...">, save in scripts[]
type = child.getAttribute("type");
- if (type && /^dojo\//i.test(type)) {
+ if (type && /^dojo\/\w/i.test(type)) {
scripts.push(child);
}
}else if(recurse){
@@ -566,17 +671,24 @@ dojo.parser = new function(){
}
}
+ // Ignore bogus entries in inherited hash like {dir: ""}
+ var inherited = {};
+ if(args && args.inherited){
+ for(var key in args.inherited){
+ if(args.inherited[key]){ inherited[key] = args.inherited[key]; }
+ }
+ }
+
// Make list of all nodes on page w/dojoType specified
var list = [];
scan({
- node: root ? dojo.byId(root) : dojo.body(),
- inherited: (args && args.inherited) || {
- dir: dojo._isBodyLtr() ? "ltr" : "rtl"
- }
+ node: root,
+ inherited: inherited
}, list);
// go build the object instances
- return this.instantiate(list, null, args); // Array
+ var mixin = args && args.template ? {template: true} : null;
+ return this.instantiate(list, mixin, args); // Array
};
}();
@@ -584,14 +696,14 @@ dojo.parser = new function(){
//after the a11y test.
(function(){
- var parseRunner = function(){
+ var parseRunner = function(){
if(dojo.config.parseOnLoad){
- dojo.parser.parse();
+ dojo.parser.parse();
}
};
// FIXME: need to clobber cross-dependency!!
- if(dojo.exists("dijit.wai.onload") && (dijit.wai.onload === dojo._loaders[0])){
+ if(dojo.getObject("dijit.wai.onload") === dojo._loaders[0]){
dojo._loaders.splice(1, 0, parseRunner);
}else{
dojo._loaders.unshift(parseRunner);
@@ -604,6 +716,8 @@ if(!dojo._hasResource["dojo.window"]){ //_hasResource checks added by build. Do
dojo._hasResource["dojo.window"] = true;
dojo.provide("dojo.window");
+dojo.getObject("window", true, dojo);
+
dojo.window.getBox = function(){
// summary:
// Returns the dimensions and scroll position of the viewable area of a browser window
@@ -657,7 +771,9 @@ dojo.window.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
return;
}
var backCompat = doc.compatMode == 'BackCompat',
- clientAreaRoot = backCompat? body : html,
+ clientAreaRoot = (isIE >= 9 && node.ownerDocument.parentWindow.frameElement)
+ ? ((html.clientHeight > 0 && html.clientWidth > 0 && (body.clientHeight == 0 || body.clientWidth == 0 || body.clientHeight > html.clientHeight || body.clientWidth > html.clientWidth)) ? html : body)
+ : (backCompat ? body : html),
scrollRoot = isWK ? body : clientAreaRoot,
rootWidth = clientAreaRoot.clientWidth,
rootHeight = clientAreaRoot.clientHeight,
@@ -682,14 +798,11 @@ dojo.window.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
}else{
var pb = dojo._getPadBorderExtents(el);
elPos.w -= pb.w; elPos.h -= pb.h; elPos.x += pb.l; elPos.y += pb.t;
- }
-
- if(el != scrollRoot){ // body, html sizes already have the scrollbar removed
var clientSize = el.clientWidth,
scrollBarSize = elPos.w - clientSize;
if(clientSize > 0 && scrollBarSize > 0){
elPos.w = clientSize;
- if(isIE && rtl){ elPos.x += scrollBarSize; }
+ elPos.x += (rtl && (isIE || el.clientLeft > pb.l/*Chrome*/)) ? scrollBarSize : 0;
}
clientSize = el.clientHeight;
scrollBarSize = elPos.h - clientSize;
@@ -718,8 +831,9 @@ dojo.window.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
bot = t + nodePos.h - elPos.h; // beyond bottom: > 0
if(r * l > 0){
var s = Math[l < 0? "max" : "min"](l, r);
+ if(rtl && ((isIE == 8 && !backCompat) || isIE >= 9)){ s = -s; }
nodePos.x += el.scrollLeft;
- el.scrollLeft += (isIE >= 8 && !backCompat && rtl)? -s : s;
+ el.scrollLeft += s;
nodePos.x -= el.scrollLeft;
}
if(bot * t > 0){
@@ -728,7 +842,7 @@ dojo.window.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
nodePos.y -= el.scrollTop;
}
el = (el != scrollRoot) && !fixedPos && el.parentNode;
- }
+ }
}catch(error){
console.error('scrollIntoView: ' + error);
node.scrollIntoView(false);
@@ -741,6 +855,7 @@ if(!dojo._hasResource["dijit._base.manager"]){ //_hasResource checks added by bu
dojo._hasResource["dijit._base.manager"] = true;
dojo.provide("dijit._base.manager");
+
dojo.declare("dijit.WidgetSet", null, {
// summary:
// A set of widgets indexed by id. A default instance of this class is
@@ -996,7 +1111,10 @@ dojo.declare("dijit.WidgetSet", null, {
if(node.nodeType == 1){
var widgetId = node.getAttribute("widgetId");
if(widgetId){
- outAry.push(hash[widgetId]);
+ var widget = hash[widgetId];
+ if(widget){ // may be null on page w/multiple dojo's loaded
+ outAry.push(widget);
+ }
}else{
getChildrenHelper(node);
}
@@ -1086,29 +1204,25 @@ dojo.declare("dijit.WidgetSet", null, {
return true;
case "iframe":
// If it's an editor <iframe> then it's tab navigable.
- //TODO: feature detect "designMode" in elem.contentDocument?
- if(dojo.isMoz){
- try{
- return elem.contentDocument.designMode == "on";
- }catch(err){
- return false;
+ var body;
+ try{
+ // non-IE
+ var contentDocument = elem.contentDocument;
+ if("designMode" in contentDocument && contentDocument.designMode == "on"){
+ return true;
}
- }else if(dojo.isWebKit){
- var doc = elem.contentDocument,
- body = doc && doc.body;
- return body && body.contentEditable == 'true';
- }else{
+ body = contentDocument.body;
+ }catch(e1){
// contentWindow.document isn't accessible within IE7/8
// if the iframe.src points to a foreign url and this
// page contains an element, that could get focus
try{
- doc = elem.contentWindow.document;
- body = doc && doc.body;
- return body && body.firstChild && body.firstChild.contentEditable == 'true';
- }catch(e){
+ body = elem.contentWindow.document.body;
+ }catch(e2){
return false;
}
}
+ return body.contentEditable == 'true' || (body.firstChild && body.firstChild.contentEditable == 'true');
default:
return elem.contentEditable == 'true';
}
@@ -1144,7 +1258,13 @@ dojo.declare("dijit.WidgetSet", null, {
// positive tabIndex value
// * the last element in document order with the highest
// positive tabIndex value
- var first, last, lowest, lowestTabindex, highest, highestTabindex;
+ var first, last, lowest, lowestTabindex, highest, highestTabindex, radioSelected = {};
+ function radioName(node) {
+ // If this element is part of a radio button group, return the name for that group.
+ return node && node.tagName.toLowerCase() == "input" &&
+ node.type && node.type.toLowerCase() == "radio" &&
+ node.name && node.name.toLowerCase();
+ }
var walkTree = function(/*DOMNode*/parent){
dojo.query("> *", parent).forEach(function(child){
// Skip hidden elements, and also non-HTML elements (those in custom namespaces) in IE,
@@ -1168,6 +1288,10 @@ dojo.declare("dijit.WidgetSet", null, {
highest = child;
}
}
+ var rn = radioName(child);
+ if(dojo.attr(child, "checked") && rn) {
+ radioSelected[rn] = child;
+ }
}
if(child.nodeName.toUpperCase() != 'SELECT'){
walkTree(child);
@@ -1175,7 +1299,11 @@ dojo.declare("dijit.WidgetSet", null, {
});
};
if(shown(root)){ walkTree(root) }
- return { first: first, last: last, lowest: lowest, highest: highest };
+ function rs(node) {
+ // substitute checked radio button for unchecked one, if there is a checked one with the same name.
+ return radioSelected[radioName(node)] || node;
+ }
+ return { first: rs(first), last: rs(last), lowest: rs(lowest), highest: rs(highest) };
}
dijit.getFirstInTabbingOrder = function(/*String|DOMNode*/ root){
// summary:
@@ -1215,7 +1343,7 @@ dojo._hasResource["dijit._base.focus"] = true;
dojo.provide("dijit._base.focus");
- // for dijit.isTabNavigable()
+
// summary:
// These functions are used to query or set the focus and selection.
@@ -1271,6 +1399,9 @@ dojo.mixin(dijit, {
}
}
bm = {isCollapsed:true};
+ if(sel.rangeCount){
+ bm.mark = sel.getRangeAt(0).cloneRange();
+ }
}else{
rg = sel.getRangeAt(0);
bm = {isCollapsed: false, mark: rg.cloneRange()};
@@ -1518,7 +1649,7 @@ dojo.mixin(dijit, {
var doc = dojo.isIE ? targetWindow.document.documentElement : targetWindow.document;
if(doc){
if(dojo.isIE){
- doc.attachEvent('onmousedown', mousedownListener);
+ targetWindow.document.body.attachEvent('onmousedown', mousedownListener);
var activateListener = function(evt){
// IE reports that nodes like <body> have gotten focus, even though they have tabIndex=-1,
// Should consider those more like a mouse-click than a focus....
@@ -1536,13 +1667,13 @@ dojo.mixin(dijit, {
doc.attachEvent('ondeactivate', deactivateListener);
return function(){
- doc.detachEvent('onmousedown', mousedownListener);
+ targetWindow.document.detachEvent('onmousedown', mousedownListener);
doc.detachEvent('onactivate', activateListener);
doc.detachEvent('ondeactivate', deactivateListener);
doc = null; // prevent memory leak (apparent circular reference via closure)
};
}else{
- doc.addEventListener('mousedown', mousedownListener, true);
+ doc.body.addEventListener('mousedown', mousedownListener, true);
var focusListener = function(evt){
dijit._onFocusNode(effectiveNode || evt.target);
};
@@ -1553,7 +1684,7 @@ dojo.mixin(dijit, {
doc.addEventListener('blur', blurListener, true);
return function(){
- doc.removeEventListener('mousedown', mousedownListener, true);
+ doc.body.removeEventListener('mousedown', mousedownListener, true);
doc.removeEventListener('focus', focusListener, true);
doc.removeEventListener('blur', blurListener, true);
doc = null; // prevent memory leak (apparent circular reference via closure)
@@ -1694,6 +1825,7 @@ dojo.mixin(dijit, {
widget = dijit.byId(oldStack[i]);
if(widget){
widget._focused = false;
+ widget.set("focused", false);
widget._hasBeenBlurred = true;
if(widget._onBlur){
widget._onBlur(by);
@@ -1707,6 +1839,7 @@ dojo.mixin(dijit, {
widget = dijit.byId(newStack[i]);
if(widget){
widget._focused = true;
+ widget.set("focused", true);
if(widget._onFocus){
widget._onFocus(by);
}
@@ -1733,6 +1866,7 @@ if(!dojo._hasResource["dojo.AdapterRegistry"]){ //_hasResource checks added by b
dojo._hasResource["dojo.AdapterRegistry"] = true;
dojo.provide("dojo.AdapterRegistry");
+
dojo.AdapterRegistry = function(/*Boolean?*/ returnWrappers){
// summary:
// A registry to make contextual calling/searching easier.
@@ -1764,11 +1898,11 @@ dojo.AdapterRegistry = function(/*Boolean?*/ returnWrappers){
this.pairs = [];
this.returnWrappers = returnWrappers || false; // Boolean
-}
+};
dojo.extend(dojo.AdapterRegistry, {
register: function(/*String*/ name, /*Function*/ check, /*Function*/ wrap, /*Boolean?*/ directReturn, /*Boolean?*/ override){
- // summary:
+ // summary:
// register a check function to determine if the wrap function or
// object gets selected
// name:
@@ -1836,7 +1970,6 @@ dojo.provide("dijit._base.place");
-
dijit.getViewport = function(){
// summary:
// Returns the dimensions and scroll position of the viewable area of a browser window
@@ -1896,16 +2029,22 @@ dijit.placeOnScreen = function(
return dijit._place(node, choices);
}
-dijit._place = function(/*DomNode*/ node, /* Array */ choices, /* Function */ layoutNode){
+dijit._place = function(/*DomNode*/ node, choices, layoutNode, /*Object*/ aroundNodeCoords){
// summary:
// Given a list of spots to put node, put it at the first spot where it fits,
// of if it doesn't fit anywhere then the place with the least overflow
// choices: Array
// Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} }
// Above example says to put the top-left corner of the node at (10,20)
- // layoutNode: Function(node, aroundNodeCorner, nodeCorner)
+ // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size)
// for things like tooltip, they are displayed differently (and have different dimensions)
// based on their orientation relative to the parent. This adjusts the popup based on orientation.
+ // It also passes in the available size for the popup, which is useful for tooltips to
+ // tell them that their width is limited to a certain amount. layoutNode() may return a value expressing
+ // how much the popup had to be modified to fit into the available space. This is used to determine
+ // what the best placement is.
+ // aroundNodeCoords: Object
+ // Size of aroundNode, ex: {w: 200, h: 50}
// get {x: 10, y: 10, w: 100, h:100} type obj representing position of
// viewport over document
@@ -1922,12 +2061,20 @@ dijit._place = function(/*DomNode*/ node, /* Array */ choices, /* Function */ la
dojo.some(choices, function(choice){
var corner = choice.corner;
var pos = choice.pos;
+ var overflow = 0;
+
+ // calculate amount of space available given specified position of node
+ var spaceAvailable = {
+ w: corner.charAt(1) == 'L' ? (view.l + view.w) - pos.x : pos.x - view.l,
+ h: corner.charAt(1) == 'T' ? (view.t + view.h) - pos.y : pos.y - view.t
+ };
// configure node to be displayed in given position relative to button
// (need to do this in order to get an accurate size for the node, because
- // a tooltips size changes based on position, due to triangle)
+ // a tooltip's size changes based on position, due to triangle)
if(layoutNode){
- layoutNode(node, choice.aroundCorner, corner);
+ var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords);
+ overflow = typeof res == "undefined" ? 0 : res;
}
// get node's size
@@ -1947,8 +2094,9 @@ dijit._place = function(/*DomNode*/ node, /* Array */ choices, /* Function */ la
endX = Math.min(view.l + view.w, corner.charAt(1) == 'L' ? (startX + mb.w) : pos.x),
endY = Math.min(view.t + view.h, corner.charAt(0) == 'T' ? (startY + mb.h) : pos.y),
width = endX - startX,
- height = endY - startY,
- overflow = (mb.w - width) + (mb.h - height);
+ height = endY - startY;
+
+ overflow += (mb.w - width) + (mb.h - height);
if(best == null || overflow < best.overflow){
best = {
@@ -1958,17 +2106,32 @@ dijit._place = function(/*DomNode*/ node, /* Array */ choices, /* Function */ la
y: startY,
w: width,
h: height,
- overflow: overflow
+ overflow: overflow,
+ spaceAvailable: spaceAvailable
};
}
+
return !overflow;
});
- node.style.left = best.x + "px";
- node.style.top = best.y + "px";
+ // In case the best position is not the last one we checked, need to call
+ // layoutNode() again.
if(best.overflow && layoutNode){
- layoutNode(node, best.aroundCorner, best.corner);
+ layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords);
}
+
+ // And then position the node. Do this last, after the layoutNode() above
+ // has sized the node, due to browser quirks when the viewport is scrolled
+ // (specifically that a Tooltip will shrink to fit as though the window was
+ // scrolled to the left).
+ //
+ // In RTL mode, set style.right rather than style.left so in the common case,
+ // window resizes move the popup along with the aroundNode.
+ var l = dojo._isBodyLtr(),
+ s = node.style;
+ s.top = best.y + "px";
+ s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px";
+
return best;
}
@@ -2015,11 +2178,7 @@ dijit.placeOnScreenAroundNode = function(
// get coordinates of aroundNode
aroundNode = dojo.byId(aroundNode);
- var oldDisplay = aroundNode.style.display;
- aroundNode.style.display="";
- // #3172: use the slightly tighter border box instead of marginBox
var aroundNodePos = dojo.position(aroundNode, true);
- aroundNode.style.display=oldDisplay;
// place the node around the calculated rectangle
return dijit._placeOnScreenAroundRect(node,
@@ -2090,7 +2249,7 @@ dijit._placeOnScreenAroundRect = function(
});
}
- return dijit._place(node, choices, layoutNode);
+ return dijit._place(node, choices, layoutNode, {w: width, h: height});
};
dijit.placementRegistry= new dojo.AdapterRegistry();
@@ -2150,11 +2309,17 @@ dijit.getPopupAroundAlignment = function(/*Array*/ position, /*Boolean*/ leftToR
case "before":
align[leftToRight ? "BL" : "BR"] = leftToRight ? "BR" : "BL";
break;
+ case "below-alt":
+ leftToRight = !leftToRight;
+ // fall through
case "below":
// first try to align left borders, next try to align right borders (or reverse for RTL mode)
align[leftToRight ? "BL" : "BR"] = leftToRight ? "TL" : "TR";
align[leftToRight ? "BR" : "BL"] = leftToRight ? "TR" : "TL";
break;
+ case "above-alt":
+ leftToRight = !leftToRight;
+ // fall through
case "above":
default:
// first try to align left borders, next try to align right borders (or reverse for RTL mode)
@@ -2259,48 +2424,77 @@ dijit.popup = {
_idGen: 1,
- moveOffScreen: function(/*DomNode*/ node){
+ _createWrapper: function(/*Widget || DomNode*/ widget){
// summary:
- // Initialization for nodes that will be used as popups
- //
- // description:
- // Puts node inside a wrapper <div>, and
- // positions wrapper div off screen, but not display:none, so that
- // the widget doesn't appear in the page flow and/or cause a blank
- // area at the bottom of the viewport (making scrollbar longer), but
- // initialization of contained widgets works correctly
-
- var wrapper = node.parentNode;
-
- // Create a wrapper widget for when this node (in the future) will be used as a popup.
- // This is done early because of IE bugs where creating/moving DOM nodes causes focus
- // to go wonky, see tests/robot/Toolbar.html to reproduce
- if(!wrapper || !dojo.hasClass(wrapper, "dijitPopup")){
+ // Initialization for widgets that will be used as popups.
+ // Puts widget inside a wrapper DIV (if not already in one),
+ // and returns pointer to that wrapper DIV.
+
+ var wrapper = widget.declaredClass ? widget._popupWrapper : (widget.parentNode && dojo.hasClass(widget.parentNode, "dijitPopup")),
+ node = widget.domNode || widget;
+
+ if(!wrapper){
+ // Create wrapper <div> for when this widget [in the future] will be used as a popup.
+ // This is done early because of IE bugs where creating/moving DOM nodes causes focus
+ // to go wonky, see tests/robot/Toolbar.html to reproduce
wrapper = dojo.create("div",{
"class":"dijitPopup",
- style:{
- visibility:"hidden",
- top: "-9999px"
- }
+ style:{ display: "none"},
+ role: "presentation"
}, dojo.body());
- dijit.setWaiRole(wrapper, "presentation");
wrapper.appendChild(node);
+
+ var s = node.style;
+ s.display = "";
+ s.visibility = "";
+ s.position = "";
+ s.top = "0px";
+
+ if(widget.declaredClass){ // TODO: in 2.0 change signature to always take widget, then remove if()
+ widget._popupWrapper = wrapper;
+ dojo.connect(widget, "destroy", function(){
+ dojo.destroy(wrapper);
+ delete widget._popupWrapper;
+ });
+ }
}
+
+ return wrapper;
+ },
+ moveOffScreen: function(/*Widget || DomNode*/ widget){
+ // summary:
+ // Moves the popup widget off-screen.
+ // Do not use this method to hide popups when not in use, because
+ // that will create an accessibility issue: the offscreen popup is
+ // still in the tabbing order.
- var s = node.style;
- s.display = "";
- s.visibility = "";
- s.position = "";
- s.top = "0px";
+ // Create wrapper if not already there
+ var wrapper = this._createWrapper(widget);
dojo.style(wrapper, {
visibility: "hidden",
- // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
- top: "-9999px"
+ top: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
+ display: ""
});
},
+ hide: function(/*dijit._Widget*/ widget){
+ // summary:
+ // Hide this popup widget (until it is ready to be shown).
+ // Initialization for widgets that will be used as popups
+ //
+ // Also puts widget inside a wrapper DIV (if not already in one)
+ //
+ // If popup widget needs to layout it should
+ // do so when it is made visible, and popup._onShow() is called.
+
+ // Create wrapper if not already there
+ var wrapper = this._createWrapper(widget);
+
+ dojo.style(wrapper, "display", "none");
+ },
+
getTopPopup: function(){
// summary:
// Compute the closest ancestor popup that's *not* a child of another popup.
@@ -2337,14 +2531,17 @@ dijit.popup = {
around = args.around,
id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++);
-
- // The wrapper may have already been created, but in case it wasn't, create here
- var wrapper = widget.domNode.parentNode;
- if(!wrapper || !dojo.hasClass(wrapper, "dijitPopup")){
- this.moveOffScreen(widget.domNode);
- wrapper = widget.domNode.parentNode;
+ // If we are opening a new popup that isn't a child of a currently opened popup, then
+ // close currently opened popup(s). This should happen automatically when the old popups
+ // gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198].
+ while(stack.length && (!args.parent || !dojo.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){
+ dijit.popup.close(stack[stack.length-1].widget);
}
+ // Get pointer to popup wrapper, and create wrapper if it doesn't exist
+ var wrapper = this._createWrapper(widget);
+
+
dojo.attr(wrapper, {
id: id,
style: {
@@ -2355,9 +2552,9 @@ dijit.popup = {
});
if(dojo.isIE || dojo.isMoz){
- var iframe = wrapper.childNodes[1];
- if(!iframe){
- iframe = new dijit.BackgroundIframe(wrapper);
+ if(!widget.bgIframe){
+ // setting widget.bgIframe triggers cleanup in _Widget.destroy()
+ widget.bgIframe = new dijit.BackgroundIframe(wrapper);
}
}
@@ -2366,6 +2563,7 @@ dijit.popup = {
dijit.placeOnScreenAroundElement(wrapper, around, orient, widget.orient ? dojo.hitch(widget, "orient") : null) :
dijit.placeOnScreen(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding);
+ wrapper.style.display = "";
wrapper.style.visibility = "visible";
widget.domNode.style.visibility = "visible"; // counteract effects from _HasDropDown
@@ -2400,8 +2598,6 @@ dijit.popup = {
}));
stack.push({
- wrapper: wrapper,
- iframe: iframe,
widget: widget,
parent: args.parent,
onExecute: args.onExecute,
@@ -2418,9 +2614,10 @@ dijit.popup = {
return best;
},
- close: function(/*dijit._Widget*/ popup){
+ close: function(/*dijit._Widget?*/ popup){
// summary:
- // Close specified popup and any popups that it parented
+ // Close specified popup and any popups that it parented.
+ // If no popup is specified, closes all popups.
var stack = this._stack;
@@ -2429,10 +2626,9 @@ dijit.popup = {
// a popup would cause others to close too. Thus if we are trying to close B in [A,B,C]
// closing C might close B indirectly and then the while() condition will run where stack==[A]...
// so the while condition is constructed defensively.
- while(dojo.some(stack, function(elem){return elem.widget == popup;})){
+ while((popup && dojo.some(stack, function(elem){return elem.widget == popup;})) ||
+ (!popup && stack.length)){
var top = stack.pop(),
- wrapper = top.wrapper,
- iframe = top.iframe,
widget = top.widget,
onClose = top.onClose;
@@ -2442,11 +2638,9 @@ dijit.popup = {
}
dojo.forEach(top.handlers, dojo.disconnect);
- // Move the widget plus it's wrapper off screen, unless it has already been destroyed in above onClose() etc.
+ // Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc.
if(widget && widget.domNode){
- this.moveOffScreen(widget.domNode);
- }else{
- dojo.destroy(wrapper);
+ this.hide(widget);
}
if(onClose){
@@ -2456,9 +2650,12 @@ dijit.popup = {
}
};
+// TODO: remove dijit._frames, it isn't being used much, since popups never release their
+// iframes (see [22236])
dijit._frames = new function(){
// summary:
// cache of iframes
+
var queue = [];
this.pop = function(){
@@ -2467,7 +2664,7 @@ dijit._frames = new function(){
iframe = queue.pop();
iframe.style.display="";
}else{
- if(dojo.isIE){
+ if(dojo.isIE < 9){
var burl = dojo.config["dojoBlankHtmlUrl"] || (dojo.moduleUrl("dojo", "resources/blank.html")+"") || "javascript:\"\"";
var html="<iframe src='" + burl + "'"
+ " style='position: absolute; left: 0px; top: 0px;"
@@ -2479,7 +2676,7 @@ dijit._frames = new function(){
iframe.className = "dijitBackgroundIframe";
dojo.style(iframe, "opacity", 0.1);
}
- iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didnt work.
+ iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didn't work.
dijit.setWaiRole(iframe,"presentation");
}
return iframe;
@@ -2492,7 +2689,7 @@ dijit._frames = new function(){
}();
-dijit.BackgroundIframe = function(/* DomNode */node){
+dijit.BackgroundIframe = function(/*DomNode*/ node){
// summary:
// For IE/FF z-index schenanigans. id attribute is required.
//
@@ -2503,9 +2700,9 @@ dijit.BackgroundIframe = function(/* DomNode */node){
if(!node.id){ throw new Error("no id"); }
if(dojo.isIE || dojo.isMoz){
- var iframe = dijit._frames.pop();
+ var iframe = (this.iframe = dijit._frames.pop());
node.appendChild(iframe);
- if(dojo.isIE<7){
+ if(dojo.isIE<7 || dojo.isQuirks){
this.resize(node);
this._conn = dojo.connect(node, 'onresize', this, function(){
this.resize(node);
@@ -2516,19 +2713,15 @@ dijit.BackgroundIframe = function(/* DomNode */node){
height: '100%'
});
}
- this.iframe = iframe;
}
};
dojo.extend(dijit.BackgroundIframe, {
resize: function(node){
// summary:
- // resize the iframe so its the same size as node
- // description:
- // this function is a no-op in all browsers except
- // IE6, which does not support 100% width/height
- // of absolute positioned iframes
- if(this.iframe && dojo.isIE<7){
+ // Resize the iframe so it's the same size as node.
+ // Needed on IE6 and IE/quirks because height:100% doesn't work right.
+ if(this.iframe){
dojo.style(this.iframe, {
width: node.offsetWidth + 'px',
height: node.offsetHeight + 'px'
@@ -2571,6 +2764,7 @@ if(!dojo._hasResource["dojo.uacss"]){ //_hasResource checks added by build. Do n
dojo._hasResource["dojo.uacss"] = true;
dojo.provide("dojo.uacss");
+
(function(){
// summary:
// Applies pre-set CSS classes to the top-level HTML node, based on:
@@ -2595,6 +2789,7 @@ dojo.provide("dojo.uacss");
dj_ie6: maj(ie) == 6,
dj_ie7: maj(ie) == 7,
dj_ie8: maj(ie) == 8,
+ dj_ie9: maj(ie) == 9,
dj_quirks: d.isQuirks,
dj_iequirks: ie && d.isQuirks,
@@ -2623,7 +2818,7 @@ dojo.provide("dojo.uacss");
html.className = d.trim(html.className + " " + classStr);
// If RTL mode, then add dj_rtl flag plus repeat existing classes with -rtl extension.
- // We can't run the code below until the <body> tag has loaded (so we can check for dir=rtl).
+ // We can't run the code below until the <body> tag has loaded (so we can check for dir=rtl).
// Unshift() is to run sniff code before the parser.
dojo._loaders.unshift(function(){
if(!dojo._isBodyLtr()){
@@ -2637,6 +2832,10 @@ dojo.provide("dojo.uacss");
if(!dojo._hasResource["dijit._base.sniff"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit._base.sniff"] = true;
+dojo.provide("dijit._base.sniff");
+
+
+
// summary:
// Applies pre-set CSS classes to the top-level HTML node, see
// `dojo.uacss` for details.
@@ -2644,16 +2843,13 @@ dojo._hasResource["dijit._base.sniff"] = true;
// Simply doing a require on this module will
// establish this CSS. Modified version of Morris' CSS hack.
-dojo.provide("dijit._base.sniff");
-
-
-
}
if(!dojo._hasResource["dijit._base.typematic"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit._base.typematic"] = true;
dojo.provide("dijit._base.typematic");
+
dijit.typematic = {
// summary:
// These functions are used to repetitively call a user specified callback
@@ -2838,6 +3034,7 @@ if(!dojo._hasResource["dijit._base.wai"]){ //_hasResource checks added by build.
dojo._hasResource["dijit._base.wai"] = true;
dojo.provide("dijit._base.wai");
+
dijit.wai = {
onload: function(){
// summary:
@@ -2883,26 +3080,24 @@ if(dojo.isIE || dojo.isMoz){ // NOTE: checking in Safari messes things up
}
dojo.mixin(dijit, {
- _XhtmlRoles: /banner|contentinfo|definition|main|navigation|search|note|secondary|seealso/,
-
- hasWaiRole: function(/*Element*/ elem, /*String*/ role){
+ hasWaiRole: function(/*Element*/ elem, /*String?*/ role){
// summary:
- // Determines if an element has a particular non-XHTML role.
+ // Determines if an element has a particular role.
// returns:
- // True if elem has the specific non-XHTML role attribute and false if not.
+ // True if elem has the specific role attribute and false if not.
// For backwards compatibility if role parameter not provided,
- // returns true if has non XHTML role
+ // returns true if has a role
var waiRole = this.getWaiRole(elem);
return role ? (waiRole.indexOf(role) > -1) : (waiRole.length > 0);
},
getWaiRole: function(/*Element*/ elem){
// summary:
- // Gets the non-XHTML role for an element (which should be a wai role).
+ // Gets the role for an element (which should be a wai role).
// returns:
- // The non-XHTML role of elem or an empty string if elem
+ // The role of elem or an empty string if elem
// does not have a role.
- return dojo.trim((dojo.attr(elem, "role") || "").replace(this._XhtmlRoles,"").replace("wairole:",""));
+ return dojo.trim((dojo.attr(elem, "role") || "").replace("wairole:",""));
},
setWaiRole: function(/*Element*/ elem, /*String*/ role){
@@ -2910,24 +3105,13 @@ dojo.mixin(dijit, {
// Sets the role on an element.
// description:
// Replace existing role attribute with new role.
- // If elem already has an XHTML role, append this role to XHTML role
- // and remove other ARIA roles.
- var curRole = dojo.attr(elem, "role") || "";
- if(!this._XhtmlRoles.test(curRole)){
dojo.attr(elem, "role", role);
- }else{
- if((" "+ curRole +" ").indexOf(" " + role + " ") < 0){
- var clearXhtml = dojo.trim(curRole.replace(this._XhtmlRoles, ""));
- var cleanRole = dojo.trim(curRole.replace(clearXhtml, ""));
- dojo.attr(elem, "role", cleanRole + (cleanRole ? ' ' : '') + role);
- }
- }
},
removeWaiRole: function(/*Element*/ elem, /*String*/ role){
// summary:
- // Removes the specified non-XHTML role from an element.
+ // Removes the specified role from an element.
// Removes role attribute if no specific role provided (for backwards compat.)
var roleValue = dojo.attr(elem, "role");
@@ -2999,73 +3183,157 @@ dojo.provide("dijit._base");
-}
-if(!dojo._hasResource["dijit._Widget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
-dojo._hasResource["dijit._Widget"] = true;
-dojo.provide("dijit._Widget");
+}
-dojo.require( "dijit._base" );
+if(!dojo._hasResource["dojo.Stateful"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dojo.Stateful"] = true;
+dojo.provide("dojo.Stateful");
-// This code is to assist deferring dojo.connect() calls in widgets (connecting to events on the widgets'
-// DOM nodes) until someone actually needs to monitor that event.
-dojo.connect(dojo, "_connect",
- function(/*dijit._Widget*/ widget, /*String*/ event){
- if(widget && dojo.isFunction(widget._onConnect)){
- widget._onConnect(event);
+dojo.declare("dojo.Stateful", null, {
+ // summary:
+ // Base class for objects that provide named properties with optional getter/setter
+ // control and the ability to watch for property changes
+ // example:
+ // | var obj = new dojo.Stateful();
+ // | obj.watch("foo", function(){
+ // | console.log("foo changed to " + this.get("foo"));
+ // | });
+ // | obj.set("foo","bar");
+ postscript: function(mixin){
+ if(mixin){
+ dojo.mixin(this, mixin);
}
- });
-
-dijit._connectOnUseEventHandler = function(/*Event*/ event){};
-
-// Keep track of where the last keydown event was, to help avoid generating
-// spurious ondijitclick events when:
-// 1. focus is on a <button> or <a>
-// 2. user presses then releases the ENTER key
-// 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler
-// 4. onkeyup event fires, causing the ondijitclick handler to fire
-dijit._lastKeyDownNode = null;
-if(dojo.isIE){
- (function(){
- var keydownCallback = function(evt){
- dijit._lastKeyDownNode = evt.srcElement;
+ },
+
+ get: function(/*String*/name){
+ // summary:
+ // Get a property on a Stateful instance.
+ // name:
+ // The property to get.
+ // description:
+ // Get a named property on a Stateful object. The property may
+ // potentially be retrieved via a getter method in subclasses. In the base class
+ // this just retrieves the object's property.
+ // For example:
+ // | stateful = new dojo.Stateful({foo: 3});
+ // | stateful.get("foo") // returns 3
+ // | stateful.foo // returns 3
+
+ return this[name];
+ },
+ set: function(/*String*/name, /*Object*/value){
+ // summary:
+ // Set a property on a Stateful instance
+ // name:
+ // The property to set.
+ // value:
+ // The value to set in the property.
+ // description:
+ // Sets named properties on a stateful object and notifies any watchers of
+ // the property. A programmatic setter may be defined in subclasses.
+ // For example:
+ // | stateful = new dojo.Stateful();
+ // | stateful.watch(function(name, oldValue, value){
+ // | // this will be called on the set below
+ // | }
+ // | stateful.set(foo, 5);
+ //
+ // set() may also be called with a hash of name/value pairs, ex:
+ // | myObj.set({
+ // | foo: "Howdy",
+ // | bar: 3
+ // | })
+ // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
+ if(typeof name === "object"){
+ for(var x in name){
+ this.set(x, name[x]);
+ }
+ return this;
+ }
+ var oldValue = this[name];
+ this[name] = value;
+ if(this._watchCallbacks){
+ this._watchCallbacks(name, oldValue, value);
+ }
+ return this;
+ },
+ watch: function(/*String?*/name, /*Function*/callback){
+ // summary:
+ // Watches a property for changes
+ // name:
+ // Indicates the property to watch. This is optional (the callback may be the
+ // only parameter), and if omitted, all the properties will be watched
+ // returns:
+ // An object handle for the watch. The unwatch method of this object
+ // can be used to discontinue watching this property:
+ // | var watchHandle = obj.watch("foo", callback);
+ // | watchHandle.unwatch(); // callback won't be called now
+ // callback:
+ // The function to execute when the property changes. This will be called after
+ // the property has been changed. The callback will be called with the |this|
+ // set to the instance, the first argument as the name of the property, the
+ // second argument as the old value and the third argument as the new value.
+
+ var callbacks = this._watchCallbacks;
+ if(!callbacks){
+ var self = this;
+ callbacks = this._watchCallbacks = function(name, oldValue, value, ignoreCatchall){
+ var notify = function(propertyCallbacks){
+ if(propertyCallbacks){
+ propertyCallbacks = propertyCallbacks.slice();
+ for(var i = 0, l = propertyCallbacks.length; i < l; i++){
+ try{
+ propertyCallbacks[i].call(self, name, oldValue, value);
+ }catch(e){
+ console.error(e);
+ }
+ }
+ }
+ };
+ notify(callbacks['_' + name]);
+ if(!ignoreCatchall){
+ notify(callbacks["*"]); // the catch-all
+ }
+ }; // we use a function instead of an object so it will be ignored by JSON conversion
+ }
+ if(!callback && typeof name === "function"){
+ callback = name;
+ name = "*";
+ }else{
+ // prepend with dash to prevent name conflicts with function (like "name" property)
+ name = '_' + name;
+ }
+ var propertyCallbacks = callbacks[name];
+ if(typeof propertyCallbacks !== "object"){
+ propertyCallbacks = callbacks[name] = [];
+ }
+ propertyCallbacks.push(callback);
+ return {
+ unwatch: function(){
+ propertyCallbacks.splice(dojo.indexOf(propertyCallbacks, callback), 1);
+ }
};
- dojo.doc.attachEvent('onkeydown', keydownCallback);
- dojo.addOnWindowUnload(function(){
- dojo.doc.detachEvent('onkeydown', keydownCallback);
- });
- })();
-}else{
- dojo.doc.addEventListener('keydown', function(evt){
- dijit._lastKeyDownNode = evt.target;
- }, true);
+ }
+
+});
+
}
-(function(){
+if(!dojo._hasResource["dijit._WidgetBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dijit._WidgetBase"] = true;
+dojo.provide("dijit._WidgetBase");
+
-var _attrReg = {}, // cached results from getSetterAttributes
- getSetterAttributes = function(widget){
- // summary:
- // Returns list of attributes with custom setters for specified widget
- var dc = widget.declaredClass;
- if(!_attrReg[dc]){
- var r = [],
- attrs,
- proto = widget.constructor.prototype;
- for(var fxName in proto){
- if(dojo.isFunction(proto[fxName]) && (attrs = fxName.match(/^_set([a-zA-Z]*)Attr$/)) && attrs[1]){
- r.push(attrs[1].charAt(0).toLowerCase() + attrs[1].substr(1));
- }
- }
- _attrReg[dc] = r;
- }
- return _attrReg[dc] || []; // String[]
- };
-dojo.declare("dijit._Widget", null, {
+
+(function(){
+
+dojo.declare("dijit._WidgetBase", dojo.Stateful, {
// summary:
- // Base class for all Dijit widgets.
+ // Future base class for all Dijit widgets.
+ // _Widget extends this class adding support for various features needed by desktop.
// id: [const] String
// A unique, opaque ID string that can be assigned by users or by the
@@ -3163,7 +3431,7 @@ dojo.declare("dijit._Widget", null, {
// Changes to widget attributes listed in attributeMap will be
// reflected into the DOM.
//
- // For example, calling attr('title', 'hello')
+ // For example, calling set('title', 'hello')
// on a TitlePane will automatically cause the TitlePane's DOM to update
// with the new title.
//
@@ -3195,158 +3463,6 @@ dojo.declare("dijit._Widget", null, {
// - "" --> { node: "domNode", type: "attribute" }
attributeMap: {id:"", dir:"", lang:"", "class":"", style:"", title:""},
- // _deferredConnects: [protected] Object
- // attributeMap addendum for event handlers that should be connected only on first use
- _deferredConnects: {
- onClick: "",
- onDblClick: "",
- onKeyDown: "",
- onKeyPress: "",
- onKeyUp: "",
- onMouseMove: "",
- onMouseDown: "",
- onMouseOut: "",
- onMouseOver: "",
- onMouseLeave: "",
- onMouseEnter: "",
- onMouseUp: ""
- },
-
- onClick: dijit._connectOnUseEventHandler,
- /*=====
- onClick: function(event){
- // summary:
- // Connect to this function to receive notifications of mouse click events.
- // event:
- // mouse Event
- // tags:
- // callback
- },
- =====*/
- onDblClick: dijit._connectOnUseEventHandler,
- /*=====
- onDblClick: function(event){
- // summary:
- // Connect to this function to receive notifications of mouse double click events.
- // event:
- // mouse Event
- // tags:
- // callback
- },
- =====*/
- onKeyDown: dijit._connectOnUseEventHandler,
- /*=====
- onKeyDown: function(event){
- // summary:
- // Connect to this function to receive notifications of keys being pressed down.
- // event:
- // key Event
- // tags:
- // callback
- },
- =====*/
- onKeyPress: dijit._connectOnUseEventHandler,
- /*=====
- onKeyPress: function(event){
- // summary:
- // Connect to this function to receive notifications of printable keys being typed.
- // event:
- // key Event
- // tags:
- // callback
- },
- =====*/
- onKeyUp: dijit._connectOnUseEventHandler,
- /*=====
- onKeyUp: function(event){
- // summary:
- // Connect to this function to receive notifications of keys being released.
- // event:
- // key Event
- // tags:
- // callback
- },
- =====*/
- onMouseDown: dijit._connectOnUseEventHandler,
- /*=====
- onMouseDown: function(event){
- // summary:
- // Connect to this function to receive notifications of when the mouse button is pressed down.
- // event:
- // mouse Event
- // tags:
- // callback
- },
- =====*/
- onMouseMove: dijit._connectOnUseEventHandler,
- /*=====
- onMouseMove: function(event){
- // summary:
- // Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget.
- // event:
- // mouse Event
- // tags:
- // callback
- },
- =====*/
- onMouseOut: dijit._connectOnUseEventHandler,
- /*=====
- onMouseOut: function(event){
- // summary:
- // Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget.
- // event:
- // mouse Event
- // tags:
- // callback
- },
- =====*/
- onMouseOver: dijit._connectOnUseEventHandler,
- /*=====
- onMouseOver: function(event){
- // summary:
- // Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget.
- // event:
- // mouse Event
- // tags:
- // callback
- },
- =====*/
- onMouseLeave: dijit._connectOnUseEventHandler,
- /*=====
- onMouseLeave: function(event){
- // summary:
- // Connect to this function to receive notifications of when the mouse moves off of this widget.
- // event:
- // mouse Event
- // tags:
- // callback
- },
- =====*/
- onMouseEnter: dijit._connectOnUseEventHandler,
- /*=====
- onMouseEnter: function(event){
- // summary:
- // Connect to this function to receive notifications of when the mouse moves onto this widget.
- // event:
- // mouse Event
- // tags:
- // callback
- },
- =====*/
- onMouseUp: dijit._connectOnUseEventHandler,
- /*=====
- onMouseUp: function(event){
- // summary:
- // Connect to this function to receive notifications of when the mouse button is released.
- // event:
- // mouse Event
- // tags:
- // callback
- },
- =====*/
-
- // Constants used in templates
-
// _blankGif: [protected] String
// Path to a blank 1x1 image.
// Used by <img> nodes in templates that really get their image via CSS background-image.
@@ -3397,25 +3513,11 @@ dojo.declare("dijit._Widget", null, {
// The handle returned from Widget.subscribe() is the handle returned from dojo.subscribe()
this._subscribes = [];
- // To avoid double-connects, remove entries from _deferredConnects
- // that have been setup manually by a subclass (ex, by dojoAttachEvent).
- // If a subclass has redefined a callback (ex: onClick) then assume it's being
- // connected to manually.
- this._deferredConnects = dojo.clone(this._deferredConnects);
- for(var attr in this.attributeMap){
- delete this._deferredConnects[attr]; // can't be in both attributeMap and _deferredConnects
- }
- for(attr in this._deferredConnects){
- if(this[attr] !== dijit._connectOnUseEventHandler){
- delete this._deferredConnects[attr]; // redefined, probably dojoAttachEvent exists
- }
- }
-
- //mixin our passed parameters
+ // mix in our passed parameters
if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; }
if(params){
this.params = params;
- dojo.mixin(this,params);
+ dojo._mixin(this, params);
}
this.postMixInProperties();
@@ -3431,22 +3533,22 @@ dojo.declare("dijit._Widget", null, {
if(this.domNode){
// Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
+ // Also calls custom setters for all attributes with custom setters.
this._applyAttributes();
+ // If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree.
+ // For 2.0, move this after postCreate(). postCreate() shouldn't depend on the
+ // widget being attached to the DOM since it isn't when a widget is created programmatically like
+ // new MyWidget({}). See #11635.
var source = this.srcNodeRef;
- if(source && source.parentNode){
+ if(source && source.parentNode && this.domNode !== source){
source.parentNode.replaceChild(this.domNode, source);
}
-
- // If the developer has specified a handler as a widget parameter
- // (ex: new Button({onClick: ...})
- // then naturally need to connect from DOM node to that handler immediately,
- for(attr in this.params){
- this._onConnect(attr);
- }
}
if(this.domNode){
+ // Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId",
+ // assuming that dojo._scopeName even exists in 2.0
this.domNode.setAttribute("widgetId", this.id);
}
this.postCreate();
@@ -3485,13 +3587,30 @@ dojo.declare("dijit._Widget", null, {
}
// And also any attributes with custom setters
- dojo.forEach(getSetterAttributes(this), function(a){
+ dojo.forEach(this._getSetterAttributes(), function(a){
if(!(a in this.attributeMap)){
condAttrApply(a, this);
}
}, this);
},
+ _getSetterAttributes: function(){
+ // summary:
+ // Returns list of attributes with custom setters for this widget
+ var ctor = this.constructor;
+ if(!ctor._setterAttrs){
+ var r = (ctor._setterAttrs = []),
+ attrs,
+ proto = ctor.prototype;
+ for(var fxName in proto){
+ if(dojo.isFunction(proto[fxName]) && (attrs = fxName.match(/^_set([a-zA-Z]*)Attr$/)) && attrs[1]){
+ r.push(attrs[1].charAt(0).toLowerCase() + attrs[1].substr(1));
+ }
+ }
+ }
+ return ctor._setterAttrs; // String[]
+ },
+
postMixInProperties: function(){
// summary:
// Called after the parameters to the widget have been read-in,
@@ -3510,7 +3629,22 @@ dojo.declare("dijit._Widget", null, {
// method.
// tags:
// protected
- this.domNode = this.srcNodeRef || dojo.create('div');
+
+ if(!this.domNode){
+ // Create root node if it wasn't created by _Templated
+ this.domNode = this.srcNodeRef || dojo.create('div');
+ }
+
+ // baseClass is a single class name or occasionally a space-separated list of names.
+ // Add those classes to the DOMNode. If RTL mode then also add with Rtl suffix.
+ // TODO: make baseClass custom setter
+ if(this.baseClass){
+ var classes = this.baseClass.split(" ");
+ if(!this.isLeftToRight()){
+ classes = classes.concat( dojo.map(classes, function(name){ return name+"Rtl"; }));
+ }
+ dojo.addClass(this.domNode, classes);
+ }
},
postCreate: function(){
@@ -3522,16 +3656,6 @@ dojo.declare("dijit._Widget", null, {
// node dimensions or placement.
// tags:
// protected
-
- // baseClass is a single class name or occasionally a space-separated list of names.
- // Add those classes to the DOMNod. If RTL mode then also add with Rtl suffix.
- if(this.baseClass){
- var classes = this.baseClass.split(" ");
- if(!this.isLeftToRight()){
- classes = classes.concat( dojo.map(classes, function(name){ return name+"Rtl"; }));
- }
- dojo.addClass(this.domNode, classes);
- }
},
startup: function(){
@@ -3647,7 +3771,6 @@ dojo.declare("dijit._Widget", null, {
});
},
-
uninitialize: function(){
// summary:
// Stub function. Override to implement custom widget tear-down
@@ -3657,60 +3780,7 @@ dojo.declare("dijit._Widget", null, {
return false;
},
- ////////////////// MISCELLANEOUS METHODS ///////////////////
-
- onFocus: function(){
- // summary:
- // Called when the widget becomes "active" because
- // it or a widget inside of it either has focus, or has recently
- // been clicked.
- // tags:
- // callback
- },
-
- onBlur: function(){
- // summary:
- // Called when the widget stops being "active" because
- // focus moved to something outside of it, or the user
- // clicked somewhere outside of it, or the widget was
- // hidden.
- // tags:
- // callback
- },
-
- _onFocus: function(e){
- // summary:
- // This is where widgets do processing for when they are active,
- // such as changing CSS classes. See onFocus() for more details.
- // tags:
- // protected
- this.onFocus();
- },
-
- _onBlur: function(){
- // summary:
- // This is where widgets do processing for when they stop being active,
- // such as changing CSS classes. See onBlur() for more details.
- // tags:
- // protected
- this.onBlur();
- },
-
- _onConnect: function(/*String*/ event){
- // summary:
- // Called when someone connects to one of my handlers.
- // "Turn on" that handler if it isn't active yet.
- //
- // This is also called for every single initialization parameter
- // so need to do nothing for parameters like "id".
- // tags:
- // private
- if(event in this._deferredConnects){
- var mapNode = this[this._deferredConnects[event] || 'domNode'];
- this.connect(mapNode, event.toLowerCase(), event);
- delete this._deferredConnects[event];
- }
- },
+ ////////////////// GET/SET, CUSTOM SETTERS, ETC. ///////////////////
_setClassAttr: function(/*String*/ value){
// summary:
@@ -3718,14 +3788,13 @@ dojo.declare("dijit._Widget", null, {
// tags:
// protected
var mapNode = this[this.attributeMap["class"] || 'domNode'];
- dojo.removeClass(mapNode, this["class"])
- this["class"] = value;
- dojo.addClass(mapNode, value);
+ dojo.replaceClass(mapNode, value, this["class"]);
+ this._set("class", value);
},
_setStyleAttr: function(/*String||Object*/ value){
// summary:
- // Sets the style attribut of the widget according to value,
+ // Sets the style attribute of the widget according to value,
// which is either a hash like {height: "5px", width: "3px"}
// or a plain string
// description:
@@ -3749,25 +3818,13 @@ dojo.declare("dijit._Widget", null, {
}
}
- this.style = value;
- },
-
- setAttribute: function(/*String*/ attr, /*anything*/ value){
- // summary:
- // Deprecated. Use set() instead.
- // tags:
- // deprecated
- dojo.deprecated(this.declaredClass+"::setAttribute(attr, value) is deprecated. Use set() instead.", "", "2.0");
- this.set(attr, value);
+ this._set("style", value);
},
_attrToDom: function(/*String*/ attr, /*String*/ value){
// summary:
// Reflect a widget attribute (title, tabIndex, duration etc.) to
// the widget DOM, as specified in attributeMap.
- //
- // description:
- // Also sets this["attr"] to the new value.
// Note some attributes like "type"
// cannot be processed this way as they are not mutable.
//
@@ -3803,46 +3860,12 @@ dojo.declare("dijit._Widget", null, {
mapNode.innerHTML = value;
break;
case "class":
- dojo.removeClass(mapNode, this[attr]);
- dojo.addClass(mapNode, value);
+ dojo.replaceClass(mapNode, value, this[attr]);
break;
}
}, this);
- this[attr] = value;
},
- attr: function(/*String|Object*/name, /*Object?*/value){
- // summary:
- // Set or get properties on a widget instance.
- // name:
- // The property to get or set. If an object is passed here and not
- // a string, its keys are used as names of attributes to be set
- // and the value of the object as values to set in the widget.
- // value:
- // Optional. If provided, attr() operates as a setter. If omitted,
- // the current value of the named property is returned.
- // description:
- // This method is deprecated, use get() or set() directly.
-
- // Print deprecation warning but only once per calling function
- if(dojo.config.isDebug){
- var alreadyCalledHash = arguments.callee._ach || (arguments.callee._ach = {}),
- caller = (arguments.callee.caller || "unknown caller").toString();
- if(!alreadyCalledHash[caller]){
- dojo.deprecated(this.declaredClass + "::attr() is deprecated. Use get() or set() instead, called from " +
- caller, "", "2.0");
- alreadyCalledHash[caller] = true;
- }
- }
-
- var args = arguments.length;
- if(args >= 2 || typeof name === "object"){ // setter
- return this.set.apply(this, arguments);
- }else{ // getter
- return this.get(name);
- }
- },
-
get: function(name){
// summary:
// Get a property from a widget.
@@ -3851,7 +3874,7 @@ dojo.declare("dijit._Widget", null, {
// description:
// Get a named property from a widget. The property may
// potentially be retrieved via a getter method. If no getter is defined, this
- // just retrieves the object's property.
+ // just retrieves the object's property.
// For example, if the widget has a properties "foo"
// and "bar" and a method named "_getFooAttr", calling:
// | myWidget.get("foo");
@@ -3869,11 +3892,11 @@ dojo.declare("dijit._Widget", null, {
// summary:
// Set a property on a widget
// name:
- // The property to set.
+ // The property to set.
// value:
// The value to set in the property.
// description:
- // Sets named properties on a widget which may potentially be handled by a
+ // Sets named properties on a widget which may potentially be handled by a
// setter in the widget.
// For example, if the widget has a properties "foo"
// and "bar" and a method named "_setFooAttr", calling:
@@ -3894,7 +3917,7 @@ dojo.declare("dijit._Widget", null, {
if(typeof name === "object"){
for(var x in name){
- this.set(x, name[x]);
+ this.set(x, name[x]);
}
return this;
}
@@ -3907,9 +3930,7 @@ dojo.declare("dijit._Widget", null, {
if(name in this.attributeMap){
this._attrToDom(name, value);
}
- var oldValue = this[name];
- // FIXME: what about function assignments? Any way to connect() here?
- this[name] = value;
+ this._set(name, value);
}
return result || this;
},
@@ -3932,6 +3953,17 @@ dojo.declare("dijit._Widget", null, {
});
},
+ _set: function(/*String*/ name, /*anything*/ value){
+ // summary:
+ // Helper function to set new value for specified attribute, and call handlers
+ // registered with watch() if the value has changed.
+ var oldValue = this[name];
+ this[name] = value;
+ if(this._watchCallbacks && this._created && value !== oldValue){
+ this._watchCallbacks(name, oldValue, value);
+ }
+ },
+
toString: function(){
// summary:
// Returns a string that represents the widget
@@ -3958,11 +3990,6 @@ dojo.declare("dijit._Widget", null, {
return this.containerNode ? dijit.findWidgets(this.containerNode) : []; // dijit._Widget[]
},
- // nodesWithKeyClick: [private] String[]
- // List of nodes that correctly handle click events via native browser support,
- // and don't need dijit's help
- nodesWithKeyClick: ["input", "button"],
-
connect: function(
/*Object|null*/ obj,
/*String|Function*/ event,
@@ -3973,8 +4000,8 @@ dojo.declare("dijit._Widget", null, {
// description:
// Provide widget-specific analog to dojo.connect, except with the
// implicit use of this widget as the target object.
- // This version of connect also provides a special "ondijitclick"
- // event which triggers on a click or space or enter keyup
+ // Events connected with `this.connect` are disconnected upon
+ // destruction.
// returns:
// A handle that can be passed to `disconnect` in order to disconnect before
// the widget is destroyed.
@@ -3988,41 +4015,7 @@ dojo.declare("dijit._Widget", null, {
// tags:
// protected
- var d = dojo,
- dc = d._connect,
- handles = [];
- if(event == "ondijitclick"){
- // add key based click activation for unsupported nodes.
- // do all processing onkey up to prevent spurious clicks
- // for details see comments at top of this file where _lastKeyDownNode is defined
- if(dojo.indexOf(this.nodesWithKeyClick, obj.nodeName.toLowerCase()) == -1){ // is NOT input or button
- var m = d.hitch(this, method);
- handles.push(
- dc(obj, "onkeydown", this, function(e){
- //console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
- if((e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) &&
- !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){
- // needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
- dijit._lastKeyDownNode = e.target;
- e.preventDefault(); // stop event to prevent scrolling on space key in IE
- }
- }),
- dc(obj, "onkeyup", this, function(e){
- //console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
- if( (e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) &&
- e.target === dijit._lastKeyDownNode &&
- !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){
- //need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
- dijit._lastKeyDownNode = null;
- return m(e);
- }
- })
- );
- }
- event = "onclick";
- }
- handles.push(dc(obj, event, this, method));
-
+ var handles = [dojo._connect(obj, event, this, method)];
this._connects.push(handles);
return handles; // _Widget.Handle
},
@@ -4058,8 +4051,7 @@ dojo.declare("dijit._Widget", null, {
// | btn.subscribe("/my/topic", function(v){
// | this.set("label", v);
// | });
- var d = dojo,
- handle = d.subscribe(topic, this, method);
+ var handle = dojo.subscribe(topic, this, method);
// return handles for Any widget that may need them
this._subscribes.push(handle);
@@ -4087,13 +4079,6 @@ dojo.declare("dijit._Widget", null, {
return this.dir ? (this.dir == "ltr") : dojo._isBodyLtr(); //Boolean
},
- isFocusable: function(){
- // summary:
- // Return true if this widget can currently be focused
- // and false if not
- return this.focus && (dojo.style(this.domNode, "display") != "none");
- },
-
placeAt: function(/* String|DomNode|_Widget */reference, /* String?|Int? */position){
// summary:
// Place this widget's domNode reference somewhere in the DOM based
@@ -4149,8 +4134,451 @@ dojo.declare("dijit._Widget", null, {
dojo.place(this.domNode, reference, position);
}
return this;
+ }
+});
+
+})();
+
+}
+
+if(!dojo._hasResource["dijit._Widget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dijit._Widget"] = true;
+dojo.provide("dijit._Widget");
+
+
+
+
+
+////////////////// DEFERRED CONNECTS ///////////////////
+
+// This code is to assist deferring dojo.connect() calls in widgets (connecting to events on the widgets'
+// DOM nodes) until someone actually needs to monitor that event.
+dojo.connect(dojo, "_connect",
+ function(/*dijit._Widget*/ widget, /*String*/ event){
+ if(widget && dojo.isFunction(widget._onConnect)){
+ widget._onConnect(event);
+ }
+ });
+
+dijit._connectOnUseEventHandler = function(/*Event*/ event){};
+
+////////////////// ONDIJITCLICK SUPPORT ///////////////////
+
+// Keep track of where the last keydown event was, to help avoid generating
+// spurious ondijitclick events when:
+// 1. focus is on a <button> or <a>
+// 2. user presses then releases the ENTER key
+// 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler
+// 4. onkeyup event fires, causing the ondijitclick handler to fire
+dijit._lastKeyDownNode = null;
+if(dojo.isIE){
+ (function(){
+ var keydownCallback = function(evt){
+ dijit._lastKeyDownNode = evt.srcElement;
+ };
+ dojo.doc.attachEvent('onkeydown', keydownCallback);
+ dojo.addOnWindowUnload(function(){
+ dojo.doc.detachEvent('onkeydown', keydownCallback);
+ });
+ })();
+}else{
+ dojo.doc.addEventListener('keydown', function(evt){
+ dijit._lastKeyDownNode = evt.target;
+ }, true);
+}
+
+(function(){
+
+dojo.declare("dijit._Widget", dijit._WidgetBase, {
+ // summary:
+ // Base class for all Dijit widgets.
+ //
+ // Extends _WidgetBase, adding support for:
+ // - deferred connections
+ // A call like dojo.connect(myWidget, "onMouseMove", func)
+ // will essentially do a dojo.connect(myWidget.domNode, "onMouseMove", func)
+ // - ondijitclick
+ // Support new dojoAttachEvent="ondijitclick: ..." that is triggered by a mouse click or a SPACE/ENTER keypress
+ // - focus related functions
+ // In particular, the onFocus()/onBlur() callbacks. Driven internally by
+ // dijit/_base/focus.js.
+ // - deprecated methods
+ // - onShow(), onHide(), onClose()
+ //
+ // Also, by loading code in dijit/_base, turns on:
+ // - browser sniffing (putting browser id like .dj_ie on <html> node)
+ // - high contrast mode sniffing (add .dijit_a11y class to <body> if machine is in high contrast mode)
+
+
+ ////////////////// DEFERRED CONNECTS ///////////////////
+
+ // _deferredConnects: [protected] Object
+ // attributeMap addendum for event handlers that should be connected only on first use
+ _deferredConnects: {
+ onClick: "",
+ onDblClick: "",
+ onKeyDown: "",
+ onKeyPress: "",
+ onKeyUp: "",
+ onMouseMove: "",
+ onMouseDown: "",
+ onMouseOut: "",
+ onMouseOver: "",
+ onMouseLeave: "",
+ onMouseEnter: "",
+ onMouseUp: ""
+ },
+
+ onClick: dijit._connectOnUseEventHandler,
+ /*=====
+ onClick: function(event){
+ // summary:
+ // Connect to this function to receive notifications of mouse click events.
+ // event:
+ // mouse Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onDblClick: dijit._connectOnUseEventHandler,
+ /*=====
+ onDblClick: function(event){
+ // summary:
+ // Connect to this function to receive notifications of mouse double click events.
+ // event:
+ // mouse Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onKeyDown: dijit._connectOnUseEventHandler,
+ /*=====
+ onKeyDown: function(event){
+ // summary:
+ // Connect to this function to receive notifications of keys being pressed down.
+ // event:
+ // key Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onKeyPress: dijit._connectOnUseEventHandler,
+ /*=====
+ onKeyPress: function(event){
+ // summary:
+ // Connect to this function to receive notifications of printable keys being typed.
+ // event:
+ // key Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onKeyUp: dijit._connectOnUseEventHandler,
+ /*=====
+ onKeyUp: function(event){
+ // summary:
+ // Connect to this function to receive notifications of keys being released.
+ // event:
+ // key Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onMouseDown: dijit._connectOnUseEventHandler,
+ /*=====
+ onMouseDown: function(event){
+ // summary:
+ // Connect to this function to receive notifications of when the mouse button is pressed down.
+ // event:
+ // mouse Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onMouseMove: dijit._connectOnUseEventHandler,
+ /*=====
+ onMouseMove: function(event){
+ // summary:
+ // Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget.
+ // event:
+ // mouse Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onMouseOut: dijit._connectOnUseEventHandler,
+ /*=====
+ onMouseOut: function(event){
+ // summary:
+ // Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget.
+ // event:
+ // mouse Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onMouseOver: dijit._connectOnUseEventHandler,
+ /*=====
+ onMouseOver: function(event){
+ // summary:
+ // Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget.
+ // event:
+ // mouse Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onMouseLeave: dijit._connectOnUseEventHandler,
+ /*=====
+ onMouseLeave: function(event){
+ // summary:
+ // Connect to this function to receive notifications of when the mouse moves off of this widget.
+ // event:
+ // mouse Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onMouseEnter: dijit._connectOnUseEventHandler,
+ /*=====
+ onMouseEnter: function(event){
+ // summary:
+ // Connect to this function to receive notifications of when the mouse moves onto this widget.
+ // event:
+ // mouse Event
+ // tags:
+ // callback
+ },
+ =====*/
+ onMouseUp: dijit._connectOnUseEventHandler,
+ /*=====
+ onMouseUp: function(event){
+ // summary:
+ // Connect to this function to receive notifications of when the mouse button is released.
+ // event:
+ // mouse Event
+ // tags:
+ // callback
+ },
+ =====*/
+
+ create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){
+ // To avoid double-connects, remove entries from _deferredConnects
+ // that have been setup manually by a subclass (ex, by dojoAttachEvent).
+ // If a subclass has redefined a callback (ex: onClick) then assume it's being
+ // connected to manually.
+ this._deferredConnects = dojo.clone(this._deferredConnects);
+ for(var attr in this.attributeMap){
+ delete this._deferredConnects[attr]; // can't be in both attributeMap and _deferredConnects
+ }
+ for(attr in this._deferredConnects){
+ if(this[attr] !== dijit._connectOnUseEventHandler){
+ delete this._deferredConnects[attr]; // redefined, probably dojoAttachEvent exists
+ }
+ }
+
+ this.inherited(arguments);
+
+ if(this.domNode){
+ // If the developer has specified a handler as a widget parameter
+ // (ex: new Button({onClick: ...})
+ // then naturally need to connect from DOM node to that handler immediately,
+ for(attr in this.params){
+ this._onConnect(attr);
+ }
+ }
+ },
+
+ _onConnect: function(/*String*/ event){
+ // summary:
+ // Called when someone connects to one of my handlers.
+ // "Turn on" that handler if it isn't active yet.
+ //
+ // This is also called for every single initialization parameter
+ // so need to do nothing for parameters like "id".
+ // tags:
+ // private
+ if(event in this._deferredConnects){
+ var mapNode = this[this._deferredConnects[event] || 'domNode'];
+ this.connect(mapNode, event.toLowerCase(), event);
+ delete this._deferredConnects[event];
+ }
+ },
+
+ ////////////////// FOCUS RELATED ///////////////////
+ // _onFocus() and _onBlur() are called by the focus manager
+
+ // focused: [readonly] Boolean
+ // This widget or a widget it contains has focus, or is "active" because
+ // it was recently clicked.
+ focused: false,
+
+ isFocusable: function(){
+ // summary:
+ // Return true if this widget can currently be focused
+ // and false if not
+ return this.focus && (dojo.style(this.domNode, "display") != "none");
+ },
+
+ onFocus: function(){
+ // summary:
+ // Called when the widget becomes "active" because
+ // it or a widget inside of it either has focus, or has recently
+ // been clicked.
+ // tags:
+ // callback
+ },
+
+ onBlur: function(){
+ // summary:
+ // Called when the widget stops being "active" because
+ // focus moved to something outside of it, or the user
+ // clicked somewhere outside of it, or the widget was
+ // hidden.
+ // tags:
+ // callback
+ },
+
+ _onFocus: function(e){
+ // summary:
+ // This is where widgets do processing for when they are active,
+ // such as changing CSS classes. See onFocus() for more details.
+ // tags:
+ // protected
+ this.onFocus();
+ },
+
+ _onBlur: function(){
+ // summary:
+ // This is where widgets do processing for when they stop being active,
+ // such as changing CSS classes. See onBlur() for more details.
+ // tags:
+ // protected
+ this.onBlur();
+ },
+
+ ////////////////// DEPRECATED METHODS ///////////////////
+
+ setAttribute: function(/*String*/ attr, /*anything*/ value){
+ // summary:
+ // Deprecated. Use set() instead.
+ // tags:
+ // deprecated
+ dojo.deprecated(this.declaredClass+"::setAttribute(attr, value) is deprecated. Use set() instead.", "", "2.0");
+ this.set(attr, value);
+ },
+
+ attr: function(/*String|Object*/name, /*Object?*/value){
+ // summary:
+ // Set or get properties on a widget instance.
+ // name:
+ // The property to get or set. If an object is passed here and not
+ // a string, its keys are used as names of attributes to be set
+ // and the value of the object as values to set in the widget.
+ // value:
+ // Optional. If provided, attr() operates as a setter. If omitted,
+ // the current value of the named property is returned.
+ // description:
+ // This method is deprecated, use get() or set() directly.
+
+ // Print deprecation warning but only once per calling function
+ if(dojo.config.isDebug){
+ var alreadyCalledHash = arguments.callee._ach || (arguments.callee._ach = {}),
+ caller = (arguments.callee.caller || "unknown caller").toString();
+ if(!alreadyCalledHash[caller]){
+ dojo.deprecated(this.declaredClass + "::attr() is deprecated. Use get() or set() instead, called from " +
+ caller, "", "2.0");
+ alreadyCalledHash[caller] = true;
+ }
+ }
+
+ var args = arguments.length;
+ if(args >= 2 || typeof name === "object"){ // setter
+ return this.set.apply(this, arguments);
+ }else{ // getter
+ return this.get(name);
+ }
+ },
+
+ ////////////////// ONDIJITCLICK SUPPORT ///////////////////
+
+ // nodesWithKeyClick: [private] String[]
+ // List of nodes that correctly handle click events via native browser support,
+ // and don't need dijit's help
+ nodesWithKeyClick: ["input", "button"],
+
+ connect: function(
+ /*Object|null*/ obj,
+ /*String|Function*/ event,
+ /*String|Function*/ method){
+ // summary:
+ // Connects specified obj/event to specified method of this object
+ // and registers for disconnect() on widget destroy.
+ // description:
+ // Provide widget-specific analog to dojo.connect, except with the
+ // implicit use of this widget as the target object.
+ // This version of connect also provides a special "ondijitclick"
+ // event which triggers on a click or space or enter keyup.
+ // Events connected with `this.connect` are disconnected upon
+ // destruction.
+ // returns:
+ // A handle that can be passed to `disconnect` in order to disconnect before
+ // the widget is destroyed.
+ // example:
+ // | var btn = new dijit.form.Button();
+ // | // when foo.bar() is called, call the listener we're going to
+ // | // provide in the scope of btn
+ // | btn.connect(foo, "bar", function(){
+ // | console.debug(this.toString());
+ // | });
+ // tags:
+ // protected
+
+ var d = dojo,
+ dc = d._connect,
+ handles = this.inherited(arguments, [obj, event == "ondijitclick" ? "onclick" : event, method]);
+
+ if(event == "ondijitclick"){
+ // add key based click activation for unsupported nodes.
+ // do all processing onkey up to prevent spurious clicks
+ // for details see comments at top of this file where _lastKeyDownNode is defined
+ if(d.indexOf(this.nodesWithKeyClick, obj.nodeName.toLowerCase()) == -1){ // is NOT input or button
+ var m = d.hitch(this, method);
+ handles.push(
+ dc(obj, "onkeydown", this, function(e){
+ //console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
+ if((e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) &&
+ !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){
+ // needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
+ dijit._lastKeyDownNode = e.target;
+
+ // Stop event to prevent scrolling on space key in IE.
+ // But don't do this for _HasDropDown because it surpresses the onkeypress
+ // event needed to open the drop down when the user presses the SPACE key.
+ if(!("openDropDown" in this && obj == this._buttonNode)){
+ e.preventDefault();
+ }
+ }
+ }),
+ dc(obj, "onkeyup", this, function(e){
+ //console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
+ if( (e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) &&
+ e.target == dijit._lastKeyDownNode && // === breaks greasemonkey
+ !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){
+ //need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
+ dijit._lastKeyDownNode = null;
+ return m(e);
+ }
+ })
+ );
+ }
+ }
+
+ return handles; // _Widget.Handle
},
+ ////////////////// MISCELLANEOUS METHODS ///////////////////
+
_onShow: function(){
// summary:
// Internal method called when this widget is made visible.
@@ -4203,8 +4631,10 @@ if(!dojo._hasResource["dojo.string"]){ //_hasResource checks added by build. Do
dojo._hasResource["dojo.string"] = true;
dojo.provide("dojo.string");
+dojo.getObject("string", true, dojo);
+
/*=====
-dojo.string = {
+dojo.string = {
// summary: String utilities for Dojo
};
=====*/
@@ -4255,22 +4685,22 @@ dojo.string.pad = function(/*String*/text, /*Integer*/size, /*String?*/ch, /*Boo
return end ? out + pad : pad + out; // String
};
-dojo.string.substitute = function( /*String*/ template,
- /*Object|Array*/map,
- /*Function?*/ transform,
+dojo.string.substitute = function( /*String*/ template,
+ /*Object|Array*/map,
+ /*Function?*/ transform,
/*Object?*/ thisObject){
// summary:
// Performs parameterized substitutions on a string. Throws an
// exception if any parameter is unmatched.
- // template:
+ // template:
// a string with expressions in the form `${key}` to be replaced or
- // `${key:format}` which specifies a format function. keys are case-sensitive.
+ // `${key:format}` which specifies a format function. keys are case-sensitive.
// map:
// hash to search for substitutions
- // transform:
+ // transform:
// a function to process all parameters before substitution takes
// place, e.g. mylib.encodeXML
- // thisObject:
+ // thisObject:
// where to look for optional format function; default to the global
// namespace
// example:
@@ -4313,7 +4743,7 @@ dojo.string.substitute = function( /*String*/ template,
// | );
thisObject = thisObject || dojo.global;
- transform = transform ?
+ transform = transform ?
dojo.hitch(thisObject, transform) : function(v){ return v; };
return template.replace(/\$\{([^\s\:\}]+)(?:\:([^\s\:\}]+))?\}/g,
@@ -4361,14 +4791,14 @@ if(!dojo._hasResource["dojo.cache"]){ //_hasResource checks added by build. Do n
dojo._hasResource["dojo.cache"] = true;
dojo.provide("dojo.cache");
+
/*=====
-dojo.cache = {
+dojo.cache = {
// summary:
// A way to cache string content that is fetchable via `dojo.moduleUrl`.
};
=====*/
-(function(){
var cache = {};
dojo.cache = function(/*String||Object*/module, /*String*/url, /*String||Object?*/value){
// summary:
@@ -4407,7 +4837,7 @@ dojo.cache = {
// | var text = dojo["cache"]("my.module", "template.html");
// example:
// To ask dojo.cache to fetch content and store it in the cache, and sanitize the input
- // (the dojo["cache"] style of call is used to avoid an issue with the build system
+ // (the dojo["cache"] style of call is used to avoid an issue with the build system
// erroneously trying to intern this example. To get the build system to intern your
// dojo.cache calls, use the "dojo.cache" style of call):
// | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
@@ -4457,7 +4887,7 @@ dojo.cache = {
};
dojo.cache._sanitize = function(/*String*/val){
- // summary:
+ // summary:
// Strips <?xml ...?> declarations so that external SVG and XML
// documents can be added to a document without worry. Also, if the string
// is an HTML document, only the part inside the body tag is returned.
@@ -4474,7 +4904,6 @@ dojo.cache = {
}
return val; //String
};
-})();
}
@@ -4527,15 +4956,23 @@ dojo.declare("dijit._Templated",
// 'true' to re-enable to previous, arguably broken, behavior.
_earlyTemplatedStartup: false,
+/*=====
// _attachPoints: [private] String[]
// List of widget attribute names associated with dojoAttachPoint=... in the
// template, ex: ["containerNode", "labelNode"]
-/*=====
_attachPoints: [],
=====*/
+/*=====
+ // _attachEvents: [private] Handle[]
+ // List of connections associated with dojoAttachEvent=... in the
+ // template
+ _attachEvents: [],
+ =====*/
+
constructor: function(){
this._attachPoints = [];
+ this._attachEvents = [];
},
_stringRepl: function(tmpl){
@@ -4560,7 +4997,6 @@ dojo.declare("dijit._Templated",
}, this);
},
- // method over-ride
buildRendering: function(){
// summary:
// Construct the UI for this widget from a template, setting this.domNode.
@@ -4586,33 +5022,24 @@ dojo.declare("dijit._Templated",
this.domNode = node;
+ // Call down to _Widget.buildRendering() to get base classes assigned
+ // TODO: change the baseClass assignment to attributeMap
+ this.inherited(arguments);
+
// recurse through the node, looking for, and attaching to, our
// attachment points and events, which should be defined on the template node.
this._attachTemplateNodes(node);
if(this.widgetsInTemplate){
- // Make sure dojoType is used for parsing widgets in template.
- // The dojo.parser.query could be changed from multiversion support.
- var parser = dojo.parser, qry, attr;
- if(parser._query != "[dojoType]"){
- qry = parser._query;
- attr = parser._attrName;
- parser._query = "[dojoType]";
- parser._attrName = "dojoType";
- }
-
// Store widgets that we need to start at a later point in time
var cw = (this._startupWidgets = dojo.parser.parse(node, {
noStart: !this._earlyTemplatedStartup,
- inherited: {dir: this.dir, lang: this.lang}
+ template: true,
+ inherited: {dir: this.dir, lang: this.lang},
+ propsThis: this, // so data-dojo-props of widgets in the template can reference "this" to refer to me
+ scope: "dojo" // even in multi-version mode templates use dojoType/data-dojo-type
}));
- // Restore the query.
- if(qry){
- parser._query = qry;
- parser._attrName = attr;
- }
-
this._supportingWidgets = dijit.findWidgets(node);
this._attachTemplateNodes(cw, function(n,p){
@@ -4640,6 +5067,8 @@ dojo.declare("dijit._Templated",
_attachTemplateNodes: function(rootNode, getAttrFunc){
// summary:
// Iterate through the template and attach functions and nodes accordingly.
+ // Alternately, if rootNode is an array of widgets, then will process dojoAttachPoint
+ // etc. for those widgets.
// description:
// Map widget properties and functions to the handlers specified in
// the dom node and it's descendants. This function iterates over all
@@ -4662,11 +5091,11 @@ dojo.declare("dijit._Templated",
var x = dojo.isArray(rootNode) ? 0 : -1;
for(; x<nodes.length; x++){
var baseNode = (x == -1) ? rootNode : nodes[x];
- if(this.widgetsInTemplate && getAttrFunc(baseNode, "dojoType")){
+ if(this.widgetsInTemplate && (getAttrFunc(baseNode, "dojoType") || getAttrFunc(baseNode, "data-dojo-type"))){
continue;
}
// Process dojoAttachPoint
- var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint");
+ var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point");
if(attachPoint){
var point, points = attachPoint.split(/\s*,\s*/);
while((point = points.shift())){
@@ -4680,7 +5109,7 @@ dojo.declare("dijit._Templated",
}
// Process dojoAttachEvent
- var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent");
+ var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event");;
if(attachEvent){
// NOTE: we want to support attributes that have the form
// "domEvent: nativeEvent; ..."
@@ -4700,12 +5129,13 @@ dojo.declare("dijit._Templated",
if(!thisFunc){
thisFunc = event;
}
- this.connect(baseNode, event, thisFunc);
+ this._attachEvents.push(this.connect(baseNode, event, thisFunc));
}
}
}
// waiRole, waiState
+ // TODO: remove this in 2.0, templates are now using role=... and aria-XXX=... attributes directicly
var role = getAttrFunc(baseNode, "waiRole");
if(role){
dijit.setWaiRole(baseNode, role);
@@ -4738,6 +5168,10 @@ dojo.declare("dijit._Templated",
}, this);
this._attachPoints = [];
+ // And same for event handlers
+ dojo.forEach(this._attachEvents, this.disconnect, this);
+ this._attachEvents = [];
+
this.inherited(arguments);
}
}
@@ -4822,6 +5256,7 @@ if(!dojo._hasResource["dijit._Container"]){ //_hasResource checks added by build
dojo._hasResource["dijit._Container"] = true;
dojo.provide("dijit._Container");
+
dojo.declare("dijit._Container",
null,
{
@@ -4884,7 +5319,7 @@ dojo.declare("dijit._Container",
// not destroy it. You can also pass in an integer indicating
// the index within the container to remove
- if(typeof widget == "number" && widget > 0){
+ if(typeof widget == "number"){
widget = this.getChildren()[widget];
}
@@ -4959,6 +5394,7 @@ if(!dojo._hasResource["dijit._Contained"]){ //_hasResource checks added by build
dojo._hasResource["dijit._Contained"] = true;
dojo.provide("dijit._Contained");
+
dojo.declare("dijit._Contained",
null,
{
@@ -5022,7 +5458,6 @@ dojo.declare("dijit._Contained",
}
);
-
}
if(!dojo._hasResource["dijit.layout._LayoutWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -5051,10 +5486,9 @@ dojo.declare("dijit.layout._LayoutWidget",
// children widgets, setting their size, when they become visible.
isLayoutContainer: true,
- postCreate: function(){
- dojo.addClass(this.domNode, "dijitContainer");
-
+ buildRendering: function(){
this.inherited(arguments);
+ dojo.addClass(this.domNode, "dijitContainer");
},
startup: function(){
@@ -5188,10 +5622,9 @@ dojo.declare("dijit.layout._LayoutWidget",
// tags:
// protected extension
- dojo.addClass(child.domNode, this.baseClass+"-child");
- if(child.baseClass){
- dojo.addClass(child.domNode, this.baseClass+"-"+child.baseClass);
- }
+ var cls = this.baseClass + "-child "
+ + (child.baseClass ? this.baseClass + "-" + child.baseClass : "");
+ dojo.addClass(child.domNode, cls);
},
addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
@@ -5204,10 +5637,11 @@ dojo.declare("dijit.layout._LayoutWidget",
removeChild: function(/*dijit._Widget*/ child){
// Overrides _Container.removeChild() to remove class added by _setupChild()
- dojo.removeClass(child.domNode, this.baseClass+"-child");
- if(child.baseClass){
- dojo.removeClass(child.domNode, this.baseClass+"-"+child.baseClass);
- }
+ var cls = this.baseClass + "-child"
+ + (child.baseClass ?
+ " " + this.baseClass + "-" + child.baseClass : "");
+ dojo.removeClass(child.domNode, cls);
+
this.inherited(arguments);
}
}
@@ -5236,15 +5670,22 @@ dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
var size = function(widget, dim){
// size the child
- widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim);
+ var newSize = widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim);
- // record child's size, but favor our own numbers when we have them.
- // the browser lies sometimes
- dojo.mixin(widget, dojo.marginBox(widget.domNode));
- dojo.mixin(widget, dim);
+ // record child's size
+ if(newSize){
+ // if the child returned it's new size then use that
+ dojo.mixin(widget, newSize);
+ }else{
+ // otherwise, call marginBox(), but favor our own numbers when we have them.
+ // the browser lies sometimes
+ dojo.mixin(widget, dojo.marginBox(widget.domNode));
+ dojo.mixin(widget, dim);
+ }
};
- dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Object[]*/ children){
+ dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Widget[]*/ children,
+ /*String?*/ changedRegionId, /*Number?*/ changedRegionSize){
// summary
// Layout a bunch of child dom nodes within a parent dom node
// container:
@@ -5252,7 +5693,16 @@ dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
// dim:
// {l, t, w, h} object specifying dimensions of container into which to place children
// children:
- // an array like [ {domNode: foo, layoutAlign: "bottom" }, {domNode: bar, layoutAlign: "client"} ]
+ // an array of Widgets or at least objects containing:
+ // * domNode: pointer to DOM node to position
+ // * region or layoutAlign: position to place DOM node
+ // * resize(): (optional) method to set size of node
+ // * id: (optional) Id of widgets, referenced from resize object, below.
+ // changedRegionId:
+ // If specified, the slider for the region with the specified id has been dragged, and thus
+ // the region's height or width should be adjusted according to changedRegionSize
+ // changedRegionSize:
+ // See changedRegionId.
// copy dim because we are going to modify it
dim = dojo.mixin({}, dim);
@@ -5261,27 +5711,37 @@ dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
// Move "client" elements to the end of the array for layout. a11y dictates that the author
// needs to be able to put them in the document in tab-order, but this algorithm requires that
- // client be last.
- children = dojo.filter(children, function(item){ return item.layoutAlign != "client"; })
- .concat(dojo.filter(children, function(item){ return item.layoutAlign == "client"; }));
+ // client be last. TODO: move these lines to LayoutContainer? Unneeded other places I think.
+ children = dojo.filter(children, function(item){ return item.region != "center" && item.layoutAlign != "client"; })
+ .concat(dojo.filter(children, function(item){ return item.region == "center" || item.layoutAlign == "client"; }));
// set positions/sizes
dojo.forEach(children, function(child){
var elm = child.domNode,
- pos = child.layoutAlign;
+ pos = (child.region || child.layoutAlign);
// set elem to upper left corner of unused space; may move it later
var elmStyle = elm.style;
elmStyle.left = dim.l+"px";
elmStyle.top = dim.t+"px";
- elmStyle.bottom = elmStyle.right = "auto";
+ elmStyle.position = "absolute";
dojo.addClass(elm, "dijitAlign" + capitalize(pos));
+ // Size adjustments to make to this child widget
+ var sizeSetting = {};
+
+ // Check for optional size adjustment due to splitter drag (height adjustment for top/bottom align
+ // panes and width adjustment for left/right align panes.
+ if(changedRegionId && changedRegionId == child.id){
+ sizeSetting[child.region == "top" || child.region == "bottom" ? "h" : "w"] = changedRegionSize;
+ }
+
// set size && adjust record of remaining space.
// note that setting the width of a <div> may affect its height.
if(pos == "top" || pos == "bottom"){
- size(child, { w: dim.w });
+ sizeSetting.w = dim.w;
+ size(child, sizeSetting);
dim.h -= child.h;
if(pos == "top"){
dim.t += child.h;
@@ -5289,14 +5749,15 @@ dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
elmStyle.top = dim.t + dim.h + "px";
}
}else if(pos == "left" || pos == "right"){
- size(child, { h: dim.h });
+ sizeSetting.h = dim.h;
+ size(child, sizeSetting);
dim.w -= child.w;
if(pos == "left"){
dim.l += child.w;
}else{
elmStyle.left = dim.l + dim.w + "px";
}
- }else if(pos == "client"){
+ }else if(pos == "client" || pos == "center"){
size(child, dim);
}
});
@@ -5340,7 +5801,19 @@ dojo.declare("dijit._CssStateMixin", [], {
// is hovered, etc.
cssStateNodes: {},
- postCreate: function(){
+ // hovering: [readonly] Boolean
+ // True if cursor is over this widget
+ hovering: false,
+
+ // active: [readonly] Boolean
+ // True if mouse was pressed while over this widget, and hasn't been released yet
+ active: false,
+
+ _applyAttributes: function(){
+ // This code would typically be in postCreate(), but putting in _applyAttributes() for
+ // performance: so the class changes happen before DOM is inserted into the document.
+ // Change back to postCreate() in 2.0. See #11635.
+
this.inherited(arguments);
// Automatically monitor mouse events (essentially :hover and :active) on this.domNode
@@ -5349,15 +5822,8 @@ dojo.declare("dijit._CssStateMixin", [], {
}, this);
// Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node
- this.connect(this, "set", function(name, value){
- if(arguments.length >= 2 && {disabled: true, readOnly: true, checked:true, selected:true}[name]){
- this._setStateClass();
- }
- });
-
- // The widget coming in/out of the focus change affects it's state
- dojo.forEach(["_onFocus", "_onBlur"], function(ap){
- this.connect(this, ap, "_setStateClass");
+ dojo.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active"], function(attr){
+ this.watch(attr, dojo.hitch(this, "_setStateClass"));
}, this);
// Events on sub nodes within the widget
@@ -5365,44 +5831,42 @@ dojo.declare("dijit._CssStateMixin", [], {
this._trackMouseState(this[ap], this.cssStateNodes[ap]);
}
// Set state initially; there's probably no hover/active/focus state but widget might be
- // disabled/readonly so we want to set CSS classes for those conditions.
+ // disabled/readonly/checked/selected so we want to set CSS classes for those conditions.
this._setStateClass();
},
_cssMouseEvent: function(/*Event*/ event){
// summary:
- // Sets _hovering and _active properties depending on mouse state,
- // then calls _setStateClass() to set appropriate CSS classes for this.domNode.
+ // Sets hovering and active properties depending on mouse state,
+ // which triggers _setStateClass() to set appropriate CSS classes for this.domNode.
if(!this.disabled){
switch(event.type){
case "mouseenter":
case "mouseover": // generated on non-IE browsers even though we connected to mouseenter
- this._hovering = true;
- this._active = this._mouseDown;
+ this._set("hovering", true);
+ this._set("active", this._mouseDown);
break;
case "mouseleave":
case "mouseout": // generated on non-IE browsers even though we connected to mouseleave
- this._hovering = false;
- this._active = false;
+ this._set("hovering", false);
+ this._set("active", false);
break;
case "mousedown" :
- this._active = true;
+ this._set("active", true);
this._mouseDown = true;
// Set a global event to handle mouseup, so it fires properly
// even if the cursor leaves this.domNode before the mouse up event.
// Alternately could set active=false on mouseout.
var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){
- this._active = false;
this._mouseDown = false;
- this._setStateClass();
+ this._set("active", false);
this.disconnect(mouseUpConnector);
});
break;
}
- this._setStateClass();
}
},
@@ -5421,11 +5885,12 @@ dojo.declare("dijit._CssStateMixin", [], {
// The widget may have one or more of the following states, determined
// by this.state, this.checked, this.valid, and this.selected:
// - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid
+ // - Incomplete - ValidationTextBox sets this.state to "Incomplete" if the current input value is not finished yet
// - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true
// - Selected - ex: currently selected tab will have this.selected==true
//
// In addition, it may have one or more of the following states,
- // based on this.disabled and flags set in _onMouse (this._active, this._hovering, this._focused):
+ // based on this.disabled and flags set in _onMouse (this.active, this.hovering) and from focus manager (this.focused):
// - Disabled - if the widget is disabled
// - Active - if the mouse (or space/enter key?) is being pressed down
// - Focused - if the widget has focus
@@ -5458,9 +5923,9 @@ dojo.declare("dijit._CssStateMixin", [], {
}else if(this.readOnly){
multiply("ReadOnly");
}else{
- if(this._active){
+ if(this.active){
multiply("Active");
- }else if(this._hovering){
+ }else if(this.hovering){
multiply("Hover");
}
}
@@ -5553,11 +6018,8 @@ dojo.declare("dijit._CssStateMixin", [], {
// Just in case widget is enabled/disabled while it has focus/hover/active state.
// Maybe this is overkill.
- this.connect(this, "set", function(name, value){
- if(name == "disabled" || name == "readOnly"){
- setClass();
- }
- });
+ this.watch("disabled", setClass);
+ this.watch("readOnly", setClass);
}
});
@@ -5572,7 +6034,6 @@ dojo.provide("dijit.form._FormWidget");
-
dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
{
// summary:
@@ -5586,7 +6047,7 @@ dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._
//
// They also share some common methods.
- // name: String
+ // name: [const] String
// Name used when submitting form; same as "name" attribute or plain HTML elements
name: "",
@@ -5643,7 +6104,7 @@ dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._
},
_setDisabledAttr: function(/*Boolean*/ value){
- this.disabled = value;
+ this._set("disabled", value);
dojo.attr(this.focusNode, 'disabled', value);
if(this.valueNode){
dojo.attr(this.valueNode, 'disabled', value);
@@ -5653,8 +6114,8 @@ dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._
if(value){
// reset these, because after the domNode is disabled, we can no longer receive
// mouse related events, see #4200
- this._hovering = false;
- this._active = false;
+ this._set("hovering", false);
+ this._set("active", false);
// clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes)
var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex : "focusNode";
@@ -5664,17 +6125,19 @@ dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._
if(dojo.isWebKit || dijit.hasDefaultTabStop(node)){ // see #11064 about webkit bug
node.setAttribute('tabIndex', "-1");
}else{
- node.removeAttribute('tabIndex');
+ node.removeAttribute('tabIndex');
}
}, this);
}else{
- this.focusNode.setAttribute('tabIndex', this.tabIndex);
+ if(this.tabIndex != ""){
+ this.focusNode.setAttribute('tabIndex', this.tabIndex);
+ }
}
},
setDisabled: function(/*Boolean*/ disabled){
// summary:
- // Deprecated. Use set('disabled', ...) instead.
+ // Deprecated. Use set('disabled', ...) instead.
dojo.deprecated("setDisabled("+disabled+") is deprecated. Use set('disabled',"+disabled+") instead.", "", "2.0");
this.set('disabled', disabled);
},
@@ -5688,21 +6151,23 @@ dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._
isFocusable: function(){
// summary:
- // Tells if this widget is focusable or not. Used internally by dijit.
+ // Tells if this widget is focusable or not. Used internally by dijit.
// tags:
// protected
- return !this.disabled && !this.readOnly && this.focusNode && (dojo.style(this.domNode, "display") != "none");
+ return !this.disabled && this.focusNode && (dojo.style(this.domNode, "display") != "none");
},
focus: function(){
// summary:
// Put focus on this widget
- dijit.focus(this.focusNode);
+ if(!this.disabled){
+ dijit.focus(this.focusNode);
+ }
},
- compare: function(/*anything*/val1, /*anything*/val2){
+ compare: function(/*anything*/ val1, /*anything*/ val2){
// summary:
- // Compare 2 values (as returned by attr('value') for this widget).
+ // Compare 2 values (as returned by get('value') for this widget).
// tags:
// protected
if(typeof val1 == "number" && typeof val2 == "number"){
@@ -5729,27 +6194,28 @@ dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._
// when the initial value is set.
_onChangeActive: false,
- _handleOnChange: function(/*anything*/ newValue, /* Boolean? */ priorityChange){
+ _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
// summary:
// Called when the value of the widget is set. Calls onChange() if appropriate
// newValue:
// the new value
// priorityChange:
// For a slider, for example, dragging the slider is priorityChange==false,
- // but on mouse up, it's priorityChange==true. If intermediateChanges==true,
+ // but on mouse up, it's priorityChange==true. If intermediateChanges==false,
// onChange is only called form priorityChange=true events.
// tags:
// private
- this._lastValue = newValue;
if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
// this block executes not for a change, but during initialization,
// and is used to store away the original value (or for ToggleButton, the original checked state)
this._resetValue = this._lastValueReported = newValue;
}
- if((this.intermediateChanges || priorityChange || priorityChange === undefined) &&
- ((typeof newValue != typeof this._lastValueReported) ||
- this.compare(newValue, this._lastValueReported) != 0)){
+ this._pendingOnChange = this._pendingOnChange
+ || (typeof newValue != typeof this._lastValueReported)
+ || (this.compare(newValue, this._lastValueReported) != 0);
+ if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){
this._lastValueReported = newValue;
+ this._pendingOnChange = false;
if(this._onChangeActive){
if(this._onChangeHandle){
clearTimeout(this._onChangeHandle);
@@ -5781,14 +6247,14 @@ dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._
setValue: function(/*String*/ value){
// summary:
- // Deprecated. Use set('value', ...) instead.
+ // Deprecated. Use set('value', ...) instead.
dojo.deprecated("dijit.form._FormWidget:setValue("+value+") is deprecated. Use set('value',"+value+") instead.", "", "2.0");
this.set('value', value);
},
getValue: function(){
// summary:
- // Deprecated. Use get('value') instead.
+ // Deprecated. Use get('value') instead.
dojo.deprecated(this.declaredClass+"::getValue() is deprecated. Use get('value') instead.", "", "2.0");
return this.get('value');
},
@@ -5798,7 +6264,7 @@ dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._
// this button should get focus (to mimics native browser buttons).
// This is also needed on chrome because otherwise buttons won't get focus at all,
// which leads to bizarre focus restore on Dialog close etc.
- if(!e.ctrlKey && this.isFocusable()){ // !e.ctrlKey to ignore right-click on mac
+ if(!e.ctrlKey && dojo.mouseButtons.isLeft(e) && this.isFocusable()){ // !e.ctrlKey to ignore right-click on mac
// Set a global event to handle mouseup, so it fires properly
// even if the cursor leaves this.domNode before the mouse up event.
var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){
@@ -5822,7 +6288,7 @@ dojo.declare("dijit.form._FormValueWidget", dijit.form._FormWidget,
// Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared
// directly in the template as read by the parser in order to function. IE is known to specifically
- // require the 'name' attribute at element creation time. See #8484, #8660.
+ // require the 'name' attribute at element creation time. See #8484, #8660.
// TODO: unclear what that {value: ""} is for; FormWidget.attributeMap copies value to focusNode,
// so maybe {value: ""} is so the value *doesn't* get copied to focusNode?
// Seems like we really want value removed from attributeMap altogether
@@ -5840,39 +6306,40 @@ dojo.declare("dijit.form._FormValueWidget", dijit.form._FormWidget,
}),
_setReadOnlyAttr: function(/*Boolean*/ value){
- this.readOnly = value;
dojo.attr(this.focusNode, 'readOnly', value);
dijit.setWaiState(this.focusNode, "readonly", value);
+ this._set("readOnly", value);
},
postCreate: function(){
this.inherited(arguments);
- if(dojo.isIE){ // IE won't stop the event with keypress
+ if(dojo.isIE < 9 || (dojo.isIE && dojo.isQuirks)){ // IE won't stop the event with keypress
this.connect(this.focusNode || this.domNode, "onkeydown", this._onKeyDown);
}
// Update our reset value if it hasn't yet been set (because this.set()
// is only called when there *is* a value)
if(this._resetValue === undefined){
- this._resetValue = this.value;
+ this._lastValueReported = this._resetValue = this.value;
}
},
- _setValueAttr: function(/*anything*/ newValue, /*Boolean, optional*/ priorityChange){
+ _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
// summary:
- // Hook so attr('value', value) works.
+ // Hook so set('value', value) works.
// description:
// Sets the value of the widget.
// If the value has changed, then fire onChange event, unless priorityChange
// is specified as null (or false?)
- this.value = newValue;
this._handleOnChange(newValue, priorityChange);
},
- _getValueAttr: function(){
+ _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
// summary:
- // Hook so attr('value') works.
- return this._lastValue;
+ // Called when the value of the widget has changed. Saves the new value in this.value,
+ // and calls onChange() if appropriate. See _FormWidget._handleOnChange() for details.
+ this._set("value", newValue);
+ this.inherited(arguments);
},
undo: function(){
@@ -5933,6 +6400,14 @@ if(!dojo._hasResource["dijit.dijit"]){ //_hasResource checks added by build. Do
dojo._hasResource["dijit.dijit"] = true;
dojo.provide("dijit.dijit");
+
+
+
+
+
+
+
+
/*=====
dijit.dijit = {
// summary:
@@ -5949,21 +6424,15 @@ dijit.dijit = {
// All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require)
-
// And some other stuff that we tend to pull in all the time anyway
-
-
-
-
-
-
}
if(!dojo._hasResource["dojo.fx.Toggler"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.fx.Toggler"] = true;
dojo.provide("dojo.fx.Toggler");
+
dojo.declare("dojo.fx.Toggler", null, {
// summary:
// A simple `dojo.Animation` toggler API.
@@ -5971,16 +6440,16 @@ dojo.declare("dojo.fx.Toggler", null, {
// description:
// class constructor for an animation toggler. It accepts a packed
// set of arguments about what type of animation to use in each
- // direction, duration, etc. All available members are mixed into
- // these animations from the constructor (for example, `node`,
- // `showDuration`, `hideDuration`).
+ // direction, duration, etc. All available members are mixed into
+ // these animations from the constructor (for example, `node`,
+ // `showDuration`, `hideDuration`).
//
// example:
// | var t = new dojo.fx.Toggler({
// | node: "nodeId",
// | showDuration: 500,
// | // hideDuration will default to "200"
- // | showFunc: dojo.fx.wipeIn,
+ // | showFunc: dojo.fx.wipeIn,
// | // hideFunc will default to "fadeOut"
// | });
// | t.show(100); // delay showing for 100ms
@@ -5995,7 +6464,7 @@ dojo.declare("dojo.fx.Toggler", null, {
// The function that returns the `dojo.Animation` to show the node
showFunc: dojo.fadeIn,
- // hideFunc: Function
+ // hideFunc: Function
// The function that returns the `dojo.Animation` to hide the node
hideFunc: dojo.fadeOut,
@@ -6011,7 +6480,7 @@ dojo.declare("dojo.fx.Toggler", null, {
// time show/hide are called if we're stopped somewhere in the
// middle.
// FIXME: also would be nice to specify individual showArgs/hideArgs mixed into
- // each animation individually.
+ // each animation individually.
// FIXME: also would be nice to have events from the animations exposed/bridged
/*=====
@@ -6064,7 +6533,9 @@ dojo.declare("dojo.fx.Toggler", null, {
if(!dojo._hasResource["dojo.fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.fx"] = true;
dojo.provide("dojo.fx");
- // FIXME: remove this back-compat require in 2.0
+
+
+
/*=====
dojo.fx = {
// summary: Effects library on top of Base animations
@@ -6072,7 +6543,7 @@ dojo.fx = {
=====*/
(function(){
- var d = dojo,
+ var d = dojo,
_baseObj = {
_fire: function(evt, args){
if(this[evt]){
@@ -6191,14 +6662,14 @@ dojo.fx = {
d.extend(_chain, _baseObj);
dojo.fx.chain = function(/*dojo.Animation[]*/ animations){
- // summary:
+ // summary:
// Chain a list of `dojo.Animation`s to run in sequence
//
// description:
// Return a `dojo.Animation` which will play all passed
// `dojo.Animation` instances in sequence, firing its own
// synthesized events simulating a single animation. (eg:
- // onEnd of this animation means the end of the chain,
+ // onEnd of this animation means the end of the chain,
// not the individual animations within)
//
// example:
@@ -6226,7 +6697,7 @@ dojo.fx = {
this._pseudoAnimation = new d.Animation({curve: [0, 1], duration: this.duration});
var self = this;
- d.forEach(["beforeBegin", "onBegin", "onPlay", "onAnimate", "onPause", "onStop", "onEnd"],
+ d.forEach(["beforeBegin", "onBegin", "onPlay", "onAnimate", "onPause", "onStop", "onEnd"],
function(evt){
self._connects.push(d.connect(self._pseudoAnimation, evt,
function(){ self._fire(evt, arguments); }
@@ -6284,11 +6755,11 @@ dojo.fx = {
d.extend(_combine, _baseObj);
dojo.fx.combine = function(/*dojo.Animation[]*/ animations){
- // summary:
+ // summary:
// Combine a list of `dojo.Animation`s to run in parallel
//
// description:
- // Combine an array of `dojo.Animation`s to run in parallel,
+ // Combine an array of `dojo.Animation`s to run in parallel,
// providing a new `dojo.Animation` instance encompasing each
// animation, firing standard animation events.
//
@@ -6359,17 +6830,17 @@ dojo.fx = {
}
}, args));
- d.connect(anim, "onEnd", function(){
+ d.connect(anim, "onEnd", function(){
s.height = "auto";
s.overflow = o;
});
return anim; // dojo.Animation
- }
+ };
dojo.fx.wipeOut = function(/*Object*/ args){
// summary:
- // Shrink a node to nothing and hide it.
+ // Shrink a node to nothing and hide it.
//
// description:
// Returns an animation that will shrink node defined in "args"
@@ -6378,7 +6849,7 @@ dojo.fx = {
// args: Object
// A hash-map of standard `dojo.Animation` constructor properties
// (such as easing: node: duration: and so on)
- //
+ //
// example:
// | dojo.fx.wipeOut({ node:"someId" }).play()
@@ -6404,14 +6875,14 @@ dojo.fx = {
});
return anim; // dojo.Animation
- }
+ };
dojo.fx.slideTo = function(/*Object*/ args){
// summary:
// Slide a node to a new top/left position
//
// description:
- // Returns an animation that will slide "node"
+ // Returns an animation that will slide "node"
// defined in args Object from its current position to
// the position defined by (args.left, args.top).
//
@@ -6423,7 +6894,7 @@ dojo.fx = {
// example:
// | dojo.fx.slideTo({ node: node, left:"40", top:"50", units:"px" }).play()
- var node = args.node = d.byId(args.node),
+ var node = args.node = d.byId(args.node),
top = null, left = null;
var init = (function(n){
@@ -6453,7 +6924,7 @@ dojo.fx = {
d.connect(anim, "beforeBegin", anim, init);
return anim; // dojo.Animation
- }
+ };
})();
@@ -6464,6 +6935,7 @@ dojo._hasResource["dojo.NodeList-fx"] = true;
dojo.provide("dojo.NodeList-fx");
+
/*=====
dojo["NodeList-fx"] = {
// summary: Adds dojo.fx animation support to dojo.query()
@@ -6479,7 +6951,7 @@ dojo.extend(dojo.NodeList, {
dojo.mixin(tmpArgs, args);
return obj[method](tmpArgs);
})
- );
+ );
return args.auto ? a.play() && this : a; // dojo.Animation|dojo.NodeList
},
@@ -6488,7 +6960,7 @@ dojo.extend(dojo.NodeList, {
// wipe in all elements of this NodeList via `dojo.fx.wipeIn`
//
// args: Object?
- // Additional dojo.Animation arguments to mix into this set with the addition of
+ // Additional dojo.Animation arguments to mix into this set with the addition of
// an `auto` parameter.
//
// returns: dojo.Animation|dojo.NodeList
@@ -6512,7 +6984,7 @@ dojo.extend(dojo.NodeList, {
// wipe out all elements of this NodeList via `dojo.fx.wipeOut`
//
// args: Object?
- // Additional dojo.Animation arguments to mix into this set with the addition of
+ // Additional dojo.Animation arguments to mix into this set with the addition of
// an `auto` parameter.
//
// returns: dojo.Animation|dojo.NodeList
@@ -6531,7 +7003,7 @@ dojo.extend(dojo.NodeList, {
// slide all elements of the node list to the specified place via `dojo.fx.slideTo`
//
// args: Object?
- // Additional dojo.Animation arguments to mix into this set with the addition of
+ // Additional dojo.Animation arguments to mix into this set with the addition of
// an `auto` parameter.
//
// returns: dojo.Animation|dojo.NodeList
@@ -6554,7 +7026,7 @@ dojo.extend(dojo.NodeList, {
// fade in all elements of this NodeList via `dojo.fadeIn`
//
// args: Object?
- // Additional dojo.Animation arguments to mix into this set with the addition of
+ // Additional dojo.Animation arguments to mix into this set with the addition of
// an `auto` parameter.
//
// returns: dojo.Animation|dojo.NodeList
@@ -6573,7 +7045,7 @@ dojo.extend(dojo.NodeList, {
// fade out all elements of this NodeList via `dojo.fadeOut`
//
// args: Object?
- // Additional dojo.Animation arguments to mix into this set with the addition of
+ // Additional dojo.Animation arguments to mix into this set with the addition of
// an `auto` parameter.
//
// returns: dojo.Animation|dojo.NodeList
@@ -6609,14 +7081,14 @@ dojo.extend(dojo.NodeList, {
// example:
// | dojo.query(".zork").animateProperty({
// | duration: 500,
- // | properties: {
+ // | properties: {
// | color: { start: "black", end: "white" },
- // | left: { end: 300 }
- // | }
+ // | left: { end: 300 }
+ // | }
// | }).play();
//
// example:
- // | dojo.query(".grue").animateProperty({
+ // | dojo.query(".grue").animateProperty({
// | auto:true,
// | properties: {
// | height:240
@@ -6625,9 +7097,9 @@ dojo.extend(dojo.NodeList, {
return this._anim(dojo, "animateProperty", args); // dojo.Animation|dojo.NodeList
},
- anim: function( /*Object*/ properties,
- /*Integer?*/ duration,
- /*Function?*/ easing,
+ anim: function( /*Object*/ properties,
+ /*Integer?*/ duration,
+ /*Function?*/ easing,
/*Function?*/ onEnd,
/*Integer?*/ delay){
// summary:
@@ -6635,8 +7107,8 @@ dojo.extend(dojo.NodeList, {
// The returned animation object will already be playing when it
// is returned. See the docs for `dojo.anim` for full details.
// properties: Object
- // the properties to animate. does NOT support the `auto` parameter like other
- // NodeList-fx methods.
+ // the properties to animate. does NOT support the `auto` parameter like other
+ // NodeList-fx methods.
// duration: Integer?
// Optional. The time to run the animations for
// easing: Function?
@@ -6661,7 +7133,7 @@ dojo.extend(dojo.NodeList, {
easing: easing
});
})
- );
+ );
if(onEnd){
dojo.connect(canim, "onEnd", onEnd);
}
@@ -6675,6 +7147,8 @@ if(!dojo._hasResource["dojo.colors"]){ //_hasResource checks added by build. Do
dojo._hasResource["dojo.colors"] = true;
dojo.provide("dojo.colors");
+dojo.getObject("colors", true, dojo);
+
//TODO: this module appears to break naming conventions
/*=====
@@ -6721,9 +7195,9 @@ dojo.colors = {
var H = ((parseFloat(c[0]) % 360) + 360) % 360 / 360,
S = parseFloat(c[1]) / 100,
L = parseFloat(c[2]) / 100,
- // calculate rgb according to the algorithm
- // recommended by the CSS3 Color Module
- m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S,
+ // calculate rgb according to the algorithm
+ // recommended by the CSS3 Color Module
+ m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S,
m1 = 2 * L - m2;
a = [
hue2rgb(m1, m2, H + 1 / 3) * 256,
@@ -6905,13 +7379,16 @@ if(!dojo._hasResource["dojo.i18n"]){ //_hasResource checks added by build. Do no
dojo._hasResource["dojo.i18n"] = true;
dojo.provide("dojo.i18n");
+dojo.getObject("i18n", true, dojo);
+
/*=====
dojo.i18n = {
// summary: Utility classes to enable loading of resources for internationalization (i18n)
};
=====*/
-dojo.i18n.getLocalization = function(/*String*/packageName, /*String*/bundleName, /*String?*/locale){
+// when using a real AMD loader, dojo.i18n.getLocalization is already defined by dojo/lib/backCompat
+dojo.i18n.getLocalization = dojo.i18n.getLocalization || function(/*String*/packageName, /*String*/bundleName, /*String?*/locale){
// summary:
// Returns an Object containing the localization for a given resource
// bundle in a package, matching the specified locale.
@@ -6939,7 +7416,7 @@ dojo.i18n.getLocalization = function(/*String*/packageName, /*String*/bundleName
// look for nearest locale match
var elements = locale.split('-');
var module = [packageName,"nls",bundleName].join('.');
- var bundle = dojo._loadedModules[module];
+ var bundle = dojo._loadedModules[module];
if(bundle){
var localization;
for(var i = elements.length; i > 0; i--){
@@ -6989,7 +7466,7 @@ dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundle
var targetLocale = dojo.i18n.normalizeLocale(locale);
var bundlePackage = [moduleName, "nls", bundleName].join(".");
- // NOTE:
+ // NOTE:
// When loading these resources, the packaging does not match what is
// on disk. This is an implementation detail, as this is just a
// private data structure to hold the loaded resources. e.g.
@@ -6999,7 +7476,7 @@ dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundle
// in memory it is more logical and efficient to store in a different
// order. Locales cannot use dashes, since the resulting path will
// not evaluate as valid JS, so we translate them to underscores.
-
+
//Find the best-match locale to load if we have available flat locales.
var bestLocale = "";
if(availableFlatLocales){
@@ -7016,7 +7493,7 @@ dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundle
}
if(!bestLocale){
bestLocale = "ROOT";
- }
+ }
}
//See if the desired locale is already loaded.
@@ -7048,6 +7525,7 @@ dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundle
module.push(bundleName);
var filespec = module.join("/") + '.js';
loaded = dojo._loadPath(filespec, null, function(hash){
+ hash = hash.root || hash;
// Use singleton with prototype to point to parent bundle, then mix-in result from loadPath
var clazz = function(){};
clazz.prototype = parent;
@@ -7062,7 +7540,7 @@ dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundle
}else{
bundle[jsLoc] = parent;
}
-
+
if(availableFlatLocales){
//Stop the locale path searching if we know the availableFlatLocales, since
//the first call to this function will load the only bundle that is needed.
@@ -7080,8 +7558,8 @@ dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundle
(function(){
// If other locales are used, dojo.requireLocalization should load them as
- // well, by default.
- //
+ // well, by default.
+ //
// Override dojo.requireLocalization to do load the default bundle, then
// iterate through the extraLocale list and load those translations as
// well, unless a particular locale was requested.
@@ -7158,6 +7636,7 @@ dojo._hasResource["dijit._PaletteMixin"] = true;
dojo.provide("dijit._PaletteMixin");
+
dojo.declare("dijit._PaletteMixin",
[dijit._CssStateMixin],
{
@@ -7184,23 +7663,23 @@ dojo.declare("dijit._PaletteMixin",
// Index of the currently selected cell. Initially, none selected
_selectedCell: -1,
+/*=====
// _currentFocus: [private] DomNode
// The currently focused cell (if the palette itself has focus), or otherwise
// the cell to be focused when the palette itself gets focus.
// Different from value, which represents the selected (i.e. clicked) cell.
-/*=====
_currentFocus: null,
=====*/
+/*=====
// _xDim: [protected] Integer
// This is the number of cells horizontally across.
-/*=====
_xDim: null,
=====*/
+/*=====
// _yDim: [protected] Integer
// This is the number of cells vertically down.
-/*=====
_yDim: null,
=====*/
@@ -7217,7 +7696,7 @@ dojo.declare("dijit._PaletteMixin",
// dyeClass should implements dijit.Dye interface
dyeClass: '',
- _preparePalette: function(choices, titles) {
+ _preparePalette: function(choices, titles, dyeClassObj) {
// summary:
// Subclass must call _preparePalette() from postCreate(), passing in the tooltip
// for each cell
@@ -7225,18 +7704,20 @@ dojo.declare("dijit._PaletteMixin",
// id's for each cell of the palette, used to create Dye JS object for each cell
// titles: String[]
// Localized tooltip for each cell
+ // dyeClassObj: Constructor?
+ // If specified, use this constructor rather than this.dyeClass
this._cells = [];
var url = this._blankGif;
- var dyeClassObj = dojo.getObject(this.dyeClass);
+ dyeClassObj = dyeClassObj || dojo.getObject(this.dyeClass);
for(var row=0; row < choices.length; row++){
var rowNode = dojo.create("tr", {tabIndex: "-1"}, this.gridNode);
for(var col=0; col < choices[row].length; col++){
var value = choices[row][col];
if(value){
- var cellObject = new dyeClassObj(value);
+ var cellObject = new dyeClassObj(value, row, col);
var cellNode = dojo.create("td", {
"class": this.cellClass,
@@ -7316,7 +7797,7 @@ dojo.declare("dijit._PaletteMixin",
// tags:
// private
- var target = evt.currentTarget,
+ var target = evt.currentTarget,
value = this._getDye(target).getValue();
// First focus the clicked cell, and then send onChange() notification.
@@ -7326,8 +7807,8 @@ dojo.declare("dijit._PaletteMixin",
// Use setTimeout because IE doesn't like changing focus inside of an event handler.
this._setCurrent(target);
setTimeout(dojo.hitch(this, function(){
- dijit.focus(target);
- this._setValueAttr(value, true);
+ dijit.focus(target);
+ this._setValueAttr(value, true);
}));
// workaround bug where hover class is not removed on popup because the popup is
@@ -7371,8 +7852,7 @@ dojo.declare("dijit._PaletteMixin",
// Optional parameter used to tell the select whether or not to fire
// onChange event.
- // clear old value and selected cell
- this.value = null;
+ // clear old selected cell
if(this._selectedCell >= 0){
dojo.removeClass(this._cells[this._selectedCell].node, "dijitPaletteCellSelected");
}
@@ -7383,18 +7863,18 @@ dojo.declare("dijit._PaletteMixin",
for(var i = 0; i < this._cells.length; i++){
if(value == this._cells[i].dye.getValue()){
this._selectedCell = i;
- this.value = value;
-
dojo.addClass(this._cells[i].node, "dijitPaletteCellSelected");
-
- if(priorityChange || priorityChange === undefined){
- this.onChange(value);
- }
-
break;
}
}
}
+
+ // record new value, or null if no matching cell
+ this._set("value", this._selectedCell >= 0 ? value : null);
+
+ if(priorityChange || priorityChange === undefined){
+ this.onChange(value);
+ }
},
onChange: function(value){
@@ -7444,7 +7924,7 @@ dojo.declare("dijit.Dye",
// summary:
// Interface for the JS Object associated with a palette cell (i.e. DOMNode)
- constructor: function(alias){
+ constructor: function(alias, row, col){
// summary:
// Initialize according to value or alias like "white"
// alias: String
@@ -7483,7 +7963,6 @@ dojo.provide("dijit.ColorPalette");
-
dojo.declare("dijit.ColorPalette",
[dijit._Widget, dijit._Templated, dijit._PaletteMixin],
{
@@ -7501,7 +7980,7 @@ dojo.declare("dijit.ColorPalette",
// | picker.startup();
- // palette: String
+ // palette: [const] String
// Size of grid, either "7x10" or "3x4".
palette: "7x10",
@@ -7523,65 +8002,88 @@ dojo.declare("dijit.ColorPalette",
["gray", "red", "purple", "black"]]
},
- // _imagePaths: [protected] Map
- // This is stores the path to the palette images
- _imagePaths: {
- "7x10": dojo.moduleUrl("dijit.themes", "a11y/colors7x10.png"),
- "3x4": dojo.moduleUrl("dijit.themes", "a11y/colors3x4.png"),
- "7x10-rtl": dojo.moduleUrl("dijit.themes", "a11y/colors7x10-rtl.png"),
- "3x4-rtl": dojo.moduleUrl("dijit.themes", "a11y/colors3x4-rtl.png")
- },
-
// templateString: String
// The template of this widget.
- templateString: dojo.cache("dijit", "templates/ColorPalette.html", "<div class=\"dijitInline dijitColorPalette\">\n\t<img class=\"dijitColorPaletteUnder\" dojoAttachPoint=\"imageNode\" waiRole=\"presentation\" alt=\"\"/>\n\t<table class=\"dijitPaletteTable\" cellSpacing=\"0\" cellPadding=\"0\">\n\t\t<tbody dojoAttachPoint=\"gridNode\"></tbody>\n\t</table>\n</div>\n"),
+ templateString: dojo.cache("dijit", "templates/ColorPalette.html", "<div class=\"dijitInline dijitColorPalette\">\n\t<table class=\"dijitPaletteTable\" cellSpacing=\"0\" cellPadding=\"0\">\n\t\t<tbody dojoAttachPoint=\"gridNode\"></tbody>\n\t</table>\n</div>\n"),
baseClass: "dijitColorPalette",
- dyeClass: 'dijit._Color',
-
buildRendering: function(){
// Instantiate the template, which makes a skeleton into which we'll insert a bunch of
// <img> nodes
-
this.inherited(arguments);
- this.imageNode.setAttribute("src", this._imagePaths[this.palette + (this.isLeftToRight() ? "" : "-rtl")].toString());
-
- var i18nColorNames = dojo.i18n.getLocalization("dojo", "colors", this.lang);
+ // Creates <img> nodes in each cell of the template.
+ // Pass in "customized" dijit._Color constructor for specified palette and high-contrast vs. normal mode
this._preparePalette(
this._palettes[this.palette],
- i18nColorNames
+ dojo.i18n.getLocalization("dojo", "colors", this.lang),
+ dojo.declare(dijit._Color, {
+ hc: dojo.hasClass(dojo.body(), "dijit_a11y"),
+ palette: this.palette
+ })
);
}
});
-dojo.declare("dijit._Color", dojo.Color,
+dojo.declare("dijit._Color", dojo.Color, {
// summary:
// Object associated with each cell in a ColorPalette palette.
// Implements dijit.Dye.
- {
- constructor: function(/*String*/alias){
- this._alias = alias;
- this.setColor(dojo.Color.named[alias]);
- },
- getValue: function(){
- // summary:
- // Note that although dijit._Color is initialized with a value like "white" getValue() always
- // returns a hex value
- return this.toHex();
- },
+ // Template for each cell in normal (non-high-contrast mode). Each cell contains a wrapper
+ // node for showing the border (called dijitPaletteImg for back-compat), and dijitColorPaletteSwatch
+ // for showing the color.
+ template:
+ "<span class='dijitInline dijitPaletteImg'>" +
+ "<img src='${blankGif}' alt='${alt}' class='dijitColorPaletteSwatch' style='background-color: ${color}'/>" +
+ "</span>",
+
+ // Template for each cell in high contrast mode. Each cell contains an image with the whole palette,
+ // but scrolled and clipped to show the correct color only
+ hcTemplate:
+ "<span class='dijitInline dijitPaletteImg' style='position: relative; overflow: hidden; height: 12px; width: 14px;'>" +
+ "<img src='${image}' alt='${alt}' style='position: absolute; left: ${left}px; top: ${top}px; ${size}'/>" +
+ "</span>",
- fillCell: function(/*DOMNode*/ cell, /*String*/ blankGif){
- dojo.create("img", {
- src: blankGif,
- "class": "dijitPaletteImg",
- alt: this._alias
- }, cell);
- }
+ // _imagePaths: [protected] Map
+ // This is stores the path to the palette images used for high-contrast mode display
+ _imagePaths: {
+ "7x10": dojo.moduleUrl("dijit.themes", "a11y/colors7x10.png"),
+ "3x4": dojo.moduleUrl("dijit.themes", "a11y/colors3x4.png")
+ },
+
+ constructor: function(/*String*/alias, /*Number*/ row, /*Number*/ col){
+ this._alias = alias;
+ this._row = row;
+ this._col = col;
+ this.setColor(dojo.Color.named[alias]);
+ },
+
+ getValue: function(){
+ // summary:
+ // Note that although dijit._Color is initialized with a value like "white" getValue() always
+ // returns a hex value
+ return this.toHex();
+ },
+
+ fillCell: function(/*DOMNode*/ cell, /*String*/ blankGif){
+ var html = dojo.string.substitute(this.hc ? this.hcTemplate : this.template, {
+ // substitution variables for normal mode
+ color: this.toHex(),
+ blankGif: blankGif,
+ alt: this._alias,
+
+ // variables used for high contrast mode
+ image: this._imagePaths[this.palette].toString(),
+ left: this._col * -20 - 5,
+ top: this._row * -20 - 5,
+ size: this.palette == "7x10" ? "height: 145px; width: 206px" : "height: 64px; width: 86px"
+ });
+
+ dojo.place(html, cell);
}
-);
+});
}
@@ -7589,6 +8091,8 @@ if(!dojo._hasResource["dojo.dnd.common"]){ //_hasResource checks added by build.
dojo._hasResource["dojo.dnd.common"] = true;
dojo.provide("dojo.dnd.common");
+dojo.getObject("dnd", true, dojo);
+
dojo.dnd.getCopyKeyState = dojo.isCopyKey;
dojo.dnd._uniqueId = 0;
@@ -7620,25 +8124,10 @@ if(!dojo._hasResource["dojo.dnd.autoscroll"]){ //_hasResource checks added by bu
dojo._hasResource["dojo.dnd.autoscroll"] = true;
dojo.provide("dojo.dnd.autoscroll");
-dojo.dnd.getViewport = function(){
- // summary:
- // Returns a viewport size (visible part of the window)
-
- // TODO: remove this when getViewport() moved to dojo core, see #7028
-
- // FIXME: need more docs!!
- var d = dojo.doc, dd = d.documentElement, w = window, b = dojo.body();
- if(dojo.isMozilla){
- return {w: dd.clientWidth, h: w.innerHeight}; // Object
- }else if(!dojo.isOpera && w.innerWidth){
- return {w: w.innerWidth, h: w.innerHeight}; // Object
- }else if (!dojo.isOpera && dd && dd.clientWidth){
- return {w: dd.clientWidth, h: dd.clientHeight}; // Object
- }else if (b.clientWidth){
- return {w: b.clientWidth, h: b.clientHeight}; // Object
- }
- return null; // Object
-};
+
+dojo.getObject("dnd", true, dojo);
+
+dojo.dnd.getViewport = dojo.window.getBox;
dojo.dnd.V_TRIGGER_AUTOSCROLL = 32;
dojo.dnd.H_TRIGGER_AUTOSCROLL = 32;
@@ -7654,7 +8143,7 @@ dojo.dnd.autoScroll = function(e){
// onmousemove event
// FIXME: needs more docs!
- var v = dojo.dnd.getViewport(), dx = 0, dy = 0;
+ var v = dojo.window.getBox(), dx = 0, dy = 0;
if(e.clientX < dojo.dnd.H_TRIGGER_AUTOSCROLL){
dx = -dojo.dnd.H_AUTOSCROLL_VALUE;
}else if(e.clientX > v.w - dojo.dnd.H_TRIGGER_AUTOSCROLL){
@@ -7685,14 +8174,15 @@ dojo.dnd.autoScrollNodes = function(e){
if(s.overflow.toLowerCase() in dojo.dnd._validOverflow){
var b = dojo._getContentBox(n, s), t = dojo.position(n, true);
//console.log(b.l, b.t, t.x, t.y, n.scrollLeft, n.scrollTop);
- var w = Math.min(dojo.dnd.H_TRIGGER_AUTOSCROLL, b.w / 2),
+ var w = Math.min(dojo.dnd.H_TRIGGER_AUTOSCROLL, b.w / 2),
h = Math.min(dojo.dnd.V_TRIGGER_AUTOSCROLL, b.h / 2),
rx = e.pageX - t.x, ry = e.pageY - t.y, dx = 0, dy = 0;
if(dojo.isWebKit || dojo.isOpera){
- // FIXME: this code should not be here, it should be taken into account
+ // FIXME: this code should not be here, it should be taken into account
// either by the event fixing code, or the dojo.position()
// FIXME: this code doesn't work on Opera 9.5 Beta
- rx += dojo.body().scrollLeft, ry += dojo.body().scrollTop;
+ rx += dojo.body().scrollLeft;
+ ry += dojo.body().scrollTop;
}
if(rx > 0 && rx < b.w){
if(rx < w){
@@ -7736,7 +8226,7 @@ dojo.provide("dojo.dnd.Mover");
dojo.declare("dojo.dnd.Mover", null, {
constructor: function(node, e, host){
// summary:
- // an object, which makes a node follow the mouse.
+ // an object which makes a node follow the mouse, or touch-drag on touch devices.
// Used as a default mover, and as a base class for custom movers.
// node: Node
// a node (or node's id) to be moved
@@ -7747,17 +8237,27 @@ dojo.declare("dojo.dnd.Mover", null, {
// object which implements the functionality of the move,
// and defines proper events (onMoveStart and onMoveStop)
this.node = dojo.byId(node);
- this.marginBox = {l: e.pageX, t: e.pageY};
+ var pos = e.touches ? e.touches[0] : e;
+ this.marginBox = {l: pos.pageX, t: pos.pageY};
this.mouseButton = e.button;
- var h = this.host = host, d = node.ownerDocument,
- firstEvent = dojo.connect(d, "onmousemove", this, "onFirstMove");
+ var h = (this.host = host), d = node.ownerDocument;
this.events = [
+ // At the start of a drag, onFirstMove is called, and then the following two
+ // connects are disconnected
+ dojo.connect(d, "onmousemove", this, "onFirstMove"),
+ dojo.connect(d, "ontouchmove", this, "onFirstMove"),
+
+ // These are called continually during the drag
dojo.connect(d, "onmousemove", this, "onMouseMove"),
+ dojo.connect(d, "ontouchmove", this, "onMouseMove"),
+
+ // And these are called at the end of the drag
dojo.connect(d, "onmouseup", this, "onMouseUp"),
+ dojo.connect(d, "ontouchend", this, "onMouseUp"),
+
// cancel text selection and text dragging
dojo.connect(d, "ondragstart", dojo.stopEvent),
- dojo.connect(d.body, "onselectstart", dojo.stopEvent),
- firstEvent
+ dojo.connect(d.body, "onselectstart", dojo.stopEvent)
];
// notify that the move has started
if(h && h.onMoveStart){
@@ -7767,17 +8267,18 @@ dojo.declare("dojo.dnd.Mover", null, {
// mouse event processors
onMouseMove: function(e){
// summary:
- // event processor for onmousemove
+ // event processor for onmousemove/ontouchmove
// e: Event
- // mouse event
+ // mouse/touch event
dojo.dnd.autoScroll(e);
- var m = this.marginBox;
- this.host.onMove(this, {l: m.l + e.pageX, t: m.t + e.pageY}, e);
+ var m = this.marginBox,
+ pos = e.touches ? e.touches[0] : e;
+ this.host.onMove(this, {l: m.l + pos.pageX, t: m.t + pos.pageY}, e);
dojo.stopEvent(e);
},
onMouseUp: function(e){
- if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ?
- e.button == 0 : this.mouseButton == e.button){
+ if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ?
+ e.button == 0 : this.mouseButton == e.button){ // TODO Should condition be met for touch devices, too?
this.destroy();
}
dojo.stopEvent(e);
@@ -7785,7 +8286,7 @@ dojo.declare("dojo.dnd.Mover", null, {
// utilities
onFirstMove: function(e){
// summary:
- // makes the node absolute; it is meant to be called only once.
+ // makes the node absolute; it is meant to be called only once.
// relative and absolutely positioned nodes are assumed to use pixel units
var s = this.node.style, l, t, h = this.host;
switch(s.position){
@@ -7805,7 +8306,7 @@ dojo.declare("dojo.dnd.Mover", null, {
// space into account - so we need to subtract the combined
// padding and margin. We use getComputedStyle and
// _getMarginBox/_getContentBox to avoid the extra lookup of
- // the computed style.
+ // the computed style.
var b = dojo.doc.body;
var bs = dojo.getComputedStyle(b);
var bm = dojo._getMarginBox(b, bs);
@@ -7819,7 +8320,10 @@ dojo.declare("dojo.dnd.Mover", null, {
if(h && h.onFirstMove){
h.onFirstMove(this, e);
}
- dojo.disconnect(this.events.pop());
+
+ // Disconnect onmousemove and ontouchmove events that call this function
+ dojo.disconnect(this.events.shift());
+ dojo.disconnect(this.events.shift());
},
destroy: function(){
// summary:
@@ -7886,6 +8390,7 @@ dojo.declare("dojo.dnd.Moveable", null, {
this.mover = params.mover ? params.mover : dojo.dnd.Mover;
this.events = [
dojo.connect(this.handle, "onmousedown", this, "onMouseDown"),
+ dojo.connect(this.handle, "ontouchstart", this, "onMouseDown"),
// cancel text selection and text dragging
dojo.connect(this.handle, "ondragstart", this, "onSelectStart"),
dojo.connect(this.handle, "onselectstart", this, "onSelectStart")
@@ -7908,17 +8413,20 @@ dojo.declare("dojo.dnd.Moveable", null, {
// mouse event processors
onMouseDown: function(e){
// summary:
- // event processor for onmousedown, creates a Mover for the node
+ // event processor for onmousedown/ontouchstart, creates a Mover for the node
// e: Event
- // mouse event
+ // mouse/touch event
if(this.skip && dojo.dnd.isFormElement(e)){ return; }
if(this.delay){
this.events.push(
dojo.connect(this.handle, "onmousemove", this, "onMouseMove"),
- dojo.connect(this.handle, "onmouseup", this, "onMouseUp")
+ dojo.connect(this.handle, "ontouchmove", this, "onMouseMove"),
+ dojo.connect(this.handle, "onmouseup", this, "onMouseUp"),
+ dojo.connect(this.handle, "ontouchend", this, "onMouseUp")
);
- this._lastX = e.pageX;
- this._lastY = e.pageY;
+ var pos = e.touches ? e.touches[0] : e;
+ this._lastX = pos.pageX;
+ this._lastY = pos.pageY;
}else{
this.onDragDetected(e);
}
@@ -7926,10 +8434,11 @@ dojo.declare("dojo.dnd.Moveable", null, {
},
onMouseMove: function(e){
// summary:
- // event processor for onmousemove, used only for delayed drags
+ // event processor for onmousemove/ontouchmove, used only for delayed drags
// e: Event
- // mouse event
- if(Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay){
+ // mouse/touch event
+ var pos = e.touches ? e.touches[0] : e;
+ if(Math.abs(pos.pageX - this._lastX) > this.delay || Math.abs(pos.pageY - this._lastY) > this.delay){
this.onMouseUp(e);
this.onDragDetected(e);
}
@@ -7966,8 +8475,8 @@ dojo.declare("dojo.dnd.Moveable", null, {
// summary:
// called before every move operation
dojo.publish("/dnd/move/start", [mover]);
- dojo.addClass(dojo.body(), "dojoMove");
- dojo.addClass(this.node, "dojoMoveItem");
+ dojo.addClass(dojo.body(), "dojoMove");
+ dojo.addClass(this.node, "dojoMoveItem");
},
onMoveStop: function(/* dojo.dnd.Mover */ mover){
// summary:
@@ -8059,7 +8568,7 @@ dojo.declare("dojo.dnd.move.constrainedMoveable", dojo.dnd.Moveable, {
c.r = c.l + c.w;
c.b = c.t + c.h;
if(this.within){
- var mb = dojo.marginBox(mover.node);
+ var mb = dojo._getMarginSize(mover.node);
c.r -= mb.w;
c.b -= mb.h;
}
@@ -8069,8 +8578,12 @@ dojo.declare("dojo.dnd.move.constrainedMoveable", dojo.dnd.Moveable, {
// called during every move notification;
// should actually move the node; can be overwritten.
var c = this.constraintBox, s = mover.node.style;
- s.left = (leftTop.l < c.l ? c.l : c.r < leftTop.l ? c.r : leftTop.l) + "px";
- s.top = (leftTop.t < c.t ? c.t : c.b < leftTop.t ? c.b : leftTop.t) + "px";
+ this.onMoving(mover, leftTop);
+ leftTop.l = leftTop.l < c.l ? c.l : c.r < leftTop.l ? c.r : leftTop.l;
+ leftTop.t = leftTop.t < c.t ? c.t : c.b < leftTop.t ? c.b : leftTop.t;
+ s.left = leftTop.l + "px";
+ s.top = leftTop.t + "px";
+ this.onMoved(mover, leftTop);
}
});
@@ -8132,8 +8645,8 @@ dojo.declare("dojo.dnd.move.parentConstrainedMoveable", dojo.dnd.move.constraine
// an optional object with parameters
var area = params && params.area;
this.constraints = function(){
- var n = this.node.parentNode,
- s = dojo.getComputedStyle(n),
+ var n = this.node.parentNode,
+ s = dojo.getComputedStyle(n),
mb = dojo._getMarginBox(n, s);
if(area == "margin"){
return mb; // Object
@@ -8155,100 +8668,6 @@ dojo.declare("dojo.dnd.move.parentConstrainedMoveable", dojo.dnd.move.constraine
}
});
-// WARNING: below are obsolete objects, instead of custom movers use custom moveables (above)
-
-dojo.dnd.move.constrainedMover = function(fun, within){
- // summary:
- // returns a constrained version of dojo.dnd.Mover
- // description:
- // this function produces n object, which will put a constraint on
- // the margin box of dragged object in absolute coordinates
- // fun: Function
- // called on drag, and returns a constraint box
- // within: Boolean
- // if true, constraints the whole dragged object withtin the rectangle,
- // otherwise the constraint is applied to the left-top corner
-
- dojo.deprecated("dojo.dnd.move.constrainedMover, use dojo.dnd.move.constrainedMoveable instead");
- var mover = function(node, e, notifier){
- dojo.dnd.Mover.call(this, node, e, notifier);
- };
- dojo.extend(mover, dojo.dnd.Mover.prototype);
- dojo.extend(mover, {
- onMouseMove: function(e){
- // summary: event processor for onmousemove
- // e: Event: mouse event
- dojo.dnd.autoScroll(e);
- var m = this.marginBox, c = this.constraintBox,
- l = m.l + e.pageX, t = m.t + e.pageY;
- l = l < c.l ? c.l : c.r < l ? c.r : l;
- t = t < c.t ? c.t : c.b < t ? c.b : t;
- this.host.onMove(this, {l: l, t: t});
- },
- onFirstMove: function(){
- // summary: called once to initialize things; it is meant to be called only once
- dojo.dnd.Mover.prototype.onFirstMove.call(this);
- var c = this.constraintBox = fun.call(this);
- c.r = c.l + c.w;
- c.b = c.t + c.h;
- if(within){
- var mb = dojo.marginBox(this.node);
- c.r -= mb.w;
- c.b -= mb.h;
- }
- }
- });
- return mover; // Object
-};
-
-dojo.dnd.move.boxConstrainedMover = function(box, within){
- // summary:
- // a specialization of dojo.dnd.constrainedMover, which constrains to the specified box
- // box: Object
- // a constraint box (l, t, w, h)
- // within: Boolean
- // if true, constraints the whole dragged object withtin the rectangle,
- // otherwise the constraint is applied to the left-top corner
-
- dojo.deprecated("dojo.dnd.move.boxConstrainedMover, use dojo.dnd.move.boxConstrainedMoveable instead");
- return dojo.dnd.move.constrainedMover(function(){ return box; }, within); // Object
-};
-
-dojo.dnd.move.parentConstrainedMover = function(area, within){
- // summary:
- // a specialization of dojo.dnd.constrainedMover, which constrains to the parent node
- // area: String
- // "margin" to constrain within the parent's margin box, "border" for the border box,
- // "padding" for the padding box, and "content" for the content box; "content" is the default value.
- // within: Boolean
- // if true, constraints the whole dragged object within the rectangle,
- // otherwise the constraint is applied to the left-top corner
-
- dojo.deprecated("dojo.dnd.move.parentConstrainedMover, use dojo.dnd.move.parentConstrainedMoveable instead");
- var fun = function(){
- var n = this.node.parentNode,
- s = dojo.getComputedStyle(n),
- mb = dojo._getMarginBox(n, s);
- if(area == "margin"){
- return mb; // Object
- }
- var t = dojo._getMarginExtents(n, s);
- mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
- if(area == "border"){
- return mb; // Object
- }
- t = dojo._getBorderExtents(n, s);
- mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
- if(area == "padding"){
- return mb; // Object
- }
- t = dojo._getPadExtents(n, s);
- mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
- return mb; // Object
- };
- return dojo.dnd.move.constrainedMover(fun, within); // Object
-};
-
// patching functions one level up for compatibility
dojo.dnd.constrainedMover = dojo.dnd.move.constrainedMover;
@@ -8279,7 +8698,7 @@ dojo.declare("dojo.dnd.__TimedMoveableArgs", [dojo.dnd.__MoveableArgs], {
dojo.declare("dojo.dnd.TimedMoveable", dojo.dnd.Moveable, {
// summary:
// A specialized version of Moveable to support an FPS throttling.
- // This class puts an upper restriction on FPS, which may reduce
+ // This class puts an upper restriction on FPS, which may reduce
// the CPU load. The additional parameter "timeout" regulates
// the delay before actually moving the moveable object.
@@ -8338,8 +8757,7 @@ dojo.provide("dijit.form._FormMixin");
-dojo.declare("dijit.form._FormMixin", null,
- {
+dojo.declare("dijit.form._FormMixin", null, {
// summary:
// Mixin for containers of form widgets (i.e. widgets that represent a single value
// and can be children of a <form> node or dijit.form.Form widget)
@@ -8350,10 +8768,10 @@ dojo.declare("dijit.form._FormMixin", null,
// form widgets
/*=====
- // value: Object
+ // value: Object
// Name/value hash for each child widget with a name and value.
// Child widgets without names are not part of the hash.
- //
+ //
// If there are multiple child widgets w/the same name, value is an array,
// unless they are radio buttons in which case value is a scalar (since only
// one radio button can be checked at a time).
@@ -8364,6 +8782,12 @@ dojo.declare("dijit.form._FormMixin", null,
// | { name: "John Smith", interests: ["sports", "movies"] }
=====*/
+ // state: [readonly] String
+ // Will be "Error" if one or more of the child widgets has an invalid value,
+ // "Incomplete" if not all of the required child widgets are filled in. Otherwise, "",
+ // which indicates that the form is ready to be submitted.
+ state: "",
+
// TODO:
// * Repeater
// * better handling for arrays. Often form elements have names with [] like
@@ -8382,11 +8806,11 @@ dojo.declare("dijit.form._FormMixin", null,
validate: function(){
// summary:
// returns if the form is valid - same as isValid - but
- // provides a few additional (ui-specific) features.
- // 1 - it will highlight any sub-widgets that are not
- // valid
- // 2 - it will call focus() on the first invalid
- // sub-widget
+ // provides a few additional (ui-specific) features.
+ // 1 - it will highlight any sub-widgets that are not
+ // valid
+ // 2 - it will call focus() on the first invalid
+ // sub-widget
var didFocus = false;
return dojo.every(dojo.map(this.getDescendants(), function(widget){
// Need to set this so that "required" widgets get their
@@ -8407,9 +8831,9 @@ dojo.declare("dijit.form._FormMixin", null,
dojo.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.", "", "2.0");
return this.set('value', val);
},
- _setValueAttr: function(/*object*/obj){
+ _setValueAttr: function(/*Object*/ obj){
// summary:
- // Fill in form values from according to an Object (in the format returned by attr('value'))
+ // Fill in form values from according to an Object (in the format returned by get('value'))
// generate map from name --> [list of widgets with that name]
var map = { };
@@ -8487,7 +8911,7 @@ dojo.declare("dijit.form._FormMixin", null,
return; // like "continue"
}
- // TODO: widget values (just call attr('value', ...) on the widget)
+ // TODO: widget values (just call set('value', ...) on the widget)
// TODO: maybe should call dojo.getNodeProp() instead
switch(element.type){
@@ -8519,6 +8943,9 @@ dojo.declare("dijit.form._FormMixin", null,
}
});
*/
+
+ // Note: no need to call this._set("value", ...) as the child updates will trigger onChange events
+ // which I am monitoring.
},
getValues: function(){
@@ -8527,17 +8954,18 @@ dojo.declare("dijit.form._FormMixin", null,
},
_getValueAttr: function(){
// summary:
- // Returns Object representing form values.
+ // Returns Object representing form values. See description of `value` for details.
// description:
- // Returns name/value hash for each form element.
- // If there are multiple elements w/the same name, value is an array,
- // unless they are radio buttons in which case value is a scalar since only
- // one can be checked at a time.
+
+ // The value is updated into this.value every time a child has an onChange event,
+ // so in the common case this function could just return this.value. However,
+ // that wouldn't work when:
//
- // If the name is a dot separated list (like a.b.c.d), creates a nested structure.
- // Only works on widget form elements.
- // example:
- // | { name: "John Smith", interests: ["sports", "movies"] }
+ // 1. User presses return key to submit a form. That doesn't fire an onchange event,
+ // and even if it did it would come too late due to the setTimout(..., 0) in _handleOnChange()
+ //
+ // 2. app for some reason calls this.get("value") while the user is typing into a
+ // form field. Not sure if that case needs to be supported or not.
// get widget values
var obj = { };
@@ -8545,7 +8973,7 @@ dojo.declare("dijit.form._FormMixin", null,
var name = widget.name;
if(!name || widget.disabled){ return; }
- // Single value widget (checkbox, radio, or plain <input> type widget
+ // Single value widget (checkbox, radio, or plain <input> type widget)
var value = widget.get('value');
// Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays
@@ -8648,98 +9076,132 @@ dojo.declare("dijit.form._FormMixin", null,
return obj;
},
- // TODO: ComboBox might need time to process a recently input value. This should be async?
isValid: function(){
// summary:
- // Returns true if all of the widgets are valid
-
- // This also populate this._invalidWidgets[] array with list of invalid widgets...
- // TODO: put that into separate function? It's confusing to have that as a side effect
- // of a method named isValid().
+ // Returns true if all of the widgets are valid.
+ // Deprecated, will be removed in 2.0. Use get("state") instead.
- this._invalidWidgets = dojo.filter(this.getDescendants(), function(widget){
- return !widget.disabled && widget.isValid && !widget.isValid();
- });
- return !this._invalidWidgets.length;
+ return this.state == "";
},
-
onValidStateChange: function(isValid){
// summary:
// Stub function to connect to if you want to do something
// (like disable/enable a submit button) when the valid
// state changes on the form as a whole.
+ //
+ // Deprecated. Will be removed in 2.0. Use watch("state", ...) instead.
},
- _widgetChange: function(widget){
+ _getState: function(){
// summary:
- // Connected to a widget's onChange function - update our
- // valid state, if needed.
- var isValid = this._lastValidState;
- if(!widget || this._lastValidState === undefined){
- // We have passed a null widget, or we haven't been validated
- // yet - let's re-check all our children
- // This happens when we connect (or reconnect) our children
- isValid = this.isValid();
- if(this._lastValidState === undefined){
- // Set this so that we don't fire an onValidStateChange
- // the first time
- this._lastValidState = isValid;
- }
- }else if(widget.isValid){
- this._invalidWidgets = dojo.filter(this._invalidWidgets || [], function(w){
- return (w != widget);
- }, this);
- if(!widget.isValid() && !widget.get("disabled")){
- this._invalidWidgets.push(widget);
- }
- isValid = (this._invalidWidgets.length === 0);
- }
- if(isValid !== this._lastValidState){
- this._lastValidState = isValid;
- this.onValidStateChange(isValid);
- }
+ // Compute what this.state should be based on state of children
+ var states = dojo.map(this._descendants, function(w){
+ return w.get("state") || "";
+ });
+
+ return dojo.indexOf(states, "Error") >= 0 ? "Error" :
+ dojo.indexOf(states, "Incomplete") >= 0 ? "Incomplete" : "";
},
- connectChildren: function(){
+ disconnectChildren: function(){
// summary:
- // Connects to the onChange function of all children to
- // track valid state changes. You can call this function
- // directly, ex. in the event that you programmatically
- // add a widget to the form *after* the form has been
+ // Remove connections to monitor changes to children's value, error state, and disabled state,
+ // in order to update Form.value and Form.state.
+ dojo.forEach(this._childConnections || [], dojo.hitch(this, "disconnect"));
+ dojo.forEach(this._childWatches || [], function(w){ w.unwatch(); });
+ },
+
+ connectChildren: function(/*Boolean*/ inStartup){
+ // summary:
+ // Setup connections to monitor changes to children's value, error state, and disabled state,
+ // in order to update Form.value and Form.state.
+ //
+ // You can call this function directly, ex. in the event that you
+ // programmatically add a widget to the form *after* the form has been
// initialized.
- dojo.forEach(this._changeConnections, dojo.hitch(this, "disconnect"));
+
var _this = this;
- // we connect to validate - so that it better reflects the states
- // of the widgets - also, we only connect if it has a validate
- // function (to avoid too many unneeded connections)
- var conns = (this._changeConnections = []);
- dojo.forEach(dojo.filter(this.getDescendants(),
+ // Remove old connections, if any
+ this.disconnectChildren();
+
+ this._descendants = this.getDescendants();
+
+ // (Re)set this.value and this.state. Send watch() notifications but not on startup.
+ var set = inStartup ? function(name, val){ _this[name] = val; } : dojo.hitch(this, "_set");
+ set("value", this.get("value"));
+ set("state", this._getState());
+
+ // Monitor changes to error state and disabled state in order to update
+ // Form.state
+ var conns = (this._childConnections = []),
+ watches = (this._childWatches = []);
+ dojo.forEach(dojo.filter(this._descendants,
function(item){ return item.validate; }
),
function(widget){
- // We are interested in whenever the widget is validated - or
- // whenever the disabled attribute on that widget is changed
- conns.push(_this.connect(widget, "validate",
- dojo.hitch(_this, "_widgetChange", widget)));
- conns.push(_this.connect(widget, "_setDisabledAttr",
- dojo.hitch(_this, "_widgetChange", widget)));
+ // We are interested in whenever the widget changes validity state - or
+ // whenever the disabled attribute on that widget is changed.
+ dojo.forEach(["state", "disabled"], function(attr){
+ watches.push(widget.watch(attr, function(attr, oldVal, newVal){
+ _this.set("state", _this._getState());
+ }));
+ });
});
- // Call the widget change function to update the valid state, in
- // case something is different now.
- this._widgetChange(null);
+ // And monitor calls to child.onChange so we can update this.value
+ var onChange = function(){
+ // summary:
+ // Called when child's value or disabled state changes
+
+ // Use setTimeout() to collapse value changes in multiple children into a single
+ // update to my value. Multiple updates will occur on:
+ // 1. Form.set()
+ // 2. Form.reset()
+ // 3. user selecting a radio button (which will de-select another radio button,
+ // causing two onChange events)
+ if(_this._onChangeDelayTimer){
+ clearTimeout(_this._onChangeDelayTimer);
+ }
+ _this._onChangeDelayTimer = setTimeout(function(){
+ delete _this._onChangeDelayTimer;
+ _this._set("value", _this.get("value"));
+ }, 10);
+ };
+ dojo.forEach(
+ dojo.filter(this._descendants, function(item){ return item.onChange; } ),
+ function(widget){
+ // When a child widget's value changes,
+ // the efficient thing to do is to just update that one attribute in this.value,
+ // but that gets a little complicated when a checkbox is checked/unchecked
+ // since this.value["checkboxName"] contains an array of all the checkboxes w/the same name.
+ // Doing simple thing for now.
+ conns.push(_this.connect(widget, "onChange", onChange));
+
+ // Disabling/enabling a child widget should remove it's value from this.value.
+ // Again, this code could be more efficient, doing simple thing for now.
+ watches.push(widget.watch("disabled", onChange));
+ }
+ );
},
startup: function(){
this.inherited(arguments);
- // Initialize our valid state tracking. Needs to be done in startup
- // because it's not guaranteed that our children are initialized
- // yet.
- this._changeConnections = [];
- this.connectChildren();
+
+ // Initialize value and valid/invalid state tracking. Needs to be done in startup()
+ // so that children are initialized.
+ this.connectChildren(true);
+
+ // Make state change call onValidStateChange(), will be removed in 2.0
+ this.watch("state", function(attr, oldVal, newVal){ this.onValidStateChange(newVal == ""); });
+ },
+
+ destroy: function(){
+ this.disconnectChildren();
+ this.inherited(arguments);
}
+
});
}
@@ -8802,22 +9264,16 @@ dojo.declare("dijit._DialogMixin", null,
this.execute(this.get('value'));
},
- _getFocusItems: function(/*Node*/ dialogNode){
+ _getFocusItems: function(){
// summary:
- // Find focusable Items each time a dialog is opened,
- // setting _firstFocusItem and _lastFocusItem
+ // Finds focusable items in dialog,
+ // and sets this._firstFocusItem and this._lastFocusItem
// tags:
// protected
- var elems = dijit._getTabNavigable(dojo.byId(dialogNode));
- this._firstFocusItem = elems.lowest || elems.first || dialogNode;
+ var elems = dijit._getTabNavigable(this.containerNode);
+ this._firstFocusItem = elems.lowest || elems.first || this.closeButtonNode || this.domNode;
this._lastFocusItem = elems.last || elems.highest || this._firstFocusItem;
- if(dojo.isMoz && this._firstFocusItem.tagName.toLowerCase() == "input" &&
- dojo.getNodeProp(this._firstFocusItem, "type").toLowerCase() == "file"){
- // FF doesn't behave well when first element is input type=file, set first focusable to dialog container
- dojo.attr(dialogNode, "tabIndex", "0");
- this._firstFocusItem = dialogNode;
- }
}
}
);
@@ -8832,7 +9288,6 @@ dojo.provide("dijit.DialogUnderlay");
-
dojo.declare(
"dijit.DialogUnderlay",
[dijit._Widget, dijit._Templated],
@@ -8869,10 +9324,12 @@ dojo.declare(
_setDialogIdAttr: function(id){
dojo.attr(this.node, "id", id + "_underlay");
+ this._set("dialogId", id);
},
_setClassAttr: function(clazz){
this.node.className = "dijitDialogUnderlay " + clazz;
+ this._set("class", clazz);
},
postCreate: function(){
@@ -8921,17 +9378,265 @@ dojo.declare(
// summary:
// Hides the dialog underlay
this.bgIframe.destroy();
+ delete this.bgIframe;
this.domNode.style.display = "none";
- },
+ }
+ }
+);
- uninitialize: function(){
- if(this.bgIframe){
- this.bgIframe.destroy();
+}
+
+if(!dojo._hasResource["dijit.layout._ContentPaneResizeMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dijit.layout._ContentPaneResizeMixin"] = true;
+dojo.provide("dijit.layout._ContentPaneResizeMixin");
+
+
+
+
+dojo.declare("dijit.layout._ContentPaneResizeMixin", null, {
+ // summary:
+ // Resize() functionality of ContentPane. If there's a single layout widget
+ // child then it will call resize() with the same dimensions as the ContentPane.
+ // Otherwise just calls resize on each child.
+ //
+ // Also implements basic startup() functionality, where starting the parent
+ // will start the children
+
+ // doLayout: Boolean
+ // - false - don't adjust size of children
+ // - true - if there is a single visible child widget, set it's size to
+ // however big the ContentPane is
+ doLayout: true,
+
+ // isContainer: [protected] Boolean
+ // Indicates that this widget acts as a "parent" to the descendant widgets.
+ // When the parent is started it will call startup() on the child widgets.
+ // See also `isLayoutContainer`.
+ isContainer: true,
+
+ // isLayoutContainer: [protected] Boolean
+ // Indicates that this widget will call resize() on it's child widgets
+ // when they become visible.
+ isLayoutContainer: true,
+
+ _startChildren: function(){
+ // summary:
+ // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
+
+ // This starts all the widgets
+ dojo.forEach(this.getChildren(), function(child){
+ child.startup();
+ child._started = true;
+ });
+ },
+
+ startup: function(){
+ // summary:
+ // See `dijit.layout._LayoutWidget.startup` for description.
+ // Although ContentPane doesn't extend _LayoutWidget, it does implement
+ // the same API.
+
+ if(this._started){ return; }
+
+ var parent = dijit._Contained.prototype.getParent.call(this);
+ this._childOfLayoutWidget = parent && parent.isLayoutContainer;
+
+ // I need to call resize() on my child/children (when I become visible), unless
+ // I'm the child of a layout widget in which case my parent will call resize() on me and I'll do it then.
+ this._needLayout = !this._childOfLayoutWidget;
+
+ this.inherited(arguments);
+
+ this._startChildren();
+
+ if(this._isShown()){
+ this._onShow();
+ }
+
+ if(!this._childOfLayoutWidget){
+ // If my parent isn't a layout container, since my style *may be* width=height=100%
+ // or something similar (either set directly or via a CSS class),
+ // monitor when my size changes so that I can re-layout.
+ // For browsers where I can't directly monitor when my size changes,
+ // monitor when the viewport changes size, which *may* indicate a size change for me.
+ this.connect(dojo.isIE ? this.domNode : dojo.global, 'onresize', function(){
+ // Using function(){} closure to ensure no arguments to resize.
+ this._needLayout = !this._childOfLayoutWidget;
+ this.resize();
+ });
+ }
+ },
+
+ _checkIfSingleChild: function(){
+ // summary:
+ // Test if we have exactly one visible widget as a child,
+ // and if so assume that we are a container for that widget,
+ // and should propagate startup() and resize() calls to it.
+ // Skips over things like data stores since they aren't visible.
+
+ var childNodes = dojo.query("> *", this.containerNode).filter(function(node){
+ return node.tagName !== "SCRIPT"; // or a regexp for hidden elements like script|area|map|etc..
+ }),
+ childWidgetNodes = childNodes.filter(function(node){
+ return dojo.hasAttr(node, "data-dojo-type") || dojo.hasAttr(node, "dojoType") || dojo.hasAttr(node, "widgetId");
+ }),
+ candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){
+ return widget && widget.domNode && widget.resize;
+ });
+
+ if(
+ // all child nodes are widgets
+ childNodes.length == childWidgetNodes.length &&
+
+ // all but one are invisible (like dojo.data)
+ candidateWidgets.length == 1
+ ){
+ this._singleChild = candidateWidgets[0];
+ }else{
+ delete this._singleChild;
+ }
+
+ // So we can set overflow: hidden to avoid a safari bug w/scrollbars showing up (#9449)
+ dojo.toggleClass(this.containerNode, this.baseClass + "SingleChild", !!this._singleChild);
+ },
+
+ resize: function(changeSize, resultSize){
+ // summary:
+ // See `dijit.layout._LayoutWidget.resize` for description.
+ // Although ContentPane doesn't extend _LayoutWidget, it does implement
+ // the same API.
+
+ // For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is
+ // never called, so resize() is our trigger to do the initial href download (see [20099]).
+ // However, don't load href for closed TitlePanes.
+ if(!this._wasShown && this.open !== false){
+ this._onShow();
+ }
+
+ this._resizeCalled = true;
+
+ this._scheduleLayout(changeSize, resultSize);
+ },
+
+ _scheduleLayout: function(changeSize, resultSize){
+ // summary:
+ // Resize myself, and call resize() on each of my child layout widgets, either now
+ // (if I'm currently visible) or when I become visible
+ if(this._isShown()){
+ this._layout(changeSize, resultSize);
+ }else{
+ this._needLayout = true;
+ this._changeSize = changeSize;
+ this._resultSize = resultSize;
+ }
+ },
+
+ _layout: function(changeSize, resultSize){
+ // summary:
+ // Resize myself according to optional changeSize/resultSize parameters, like a layout widget.
+ // Also, since I am a Container widget, each of my children expects me to
+ // call resize() or layout() on them.
+ //
+ // Should be called on initialization and also whenever we get new content
+ // (from an href, or from set('content', ...))... but deferred until
+ // the ContentPane is visible
+
+ // Set margin box size, unless it wasn't specified, in which case use current size.
+ if(changeSize){
+ dojo.marginBox(this.domNode, changeSize);
+ }
+
+ // Compute content box size of containerNode in case we [later] need to size our single child.
+ var cn = this.containerNode;
+ if(cn === this.domNode){
+ // If changeSize or resultSize was passed to this method and this.containerNode ==
+ // this.domNode then we can compute the content-box size without querying the node,
+ // which is more reliable (similar to LayoutWidget.resize) (see for example #9449).
+ var mb = resultSize || {};
+ dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize
+ if(!("h" in mb) || !("w" in mb)){
+ mb = dojo.mixin(dojo.marginBox(cn), mb); // just use dojo.marginBox() to fill in missing values
}
- this.inherited(arguments);
+ this._contentBox = dijit.layout.marginBox2contentBox(cn, mb);
+ }else{
+ this._contentBox = dojo.contentBox(cn);
}
+
+ this._layoutChildren();
+
+ delete this._needLayout;
+ },
+
+ _layoutChildren: function(){
+ // Call _checkIfSingleChild() again in case app has manually mucked w/the content
+ // of the ContentPane (rather than changing it through the set("content", ...) API.
+ if(this.doLayout){
+ this._checkIfSingleChild();
+ }
+
+ if(this._singleChild && this._singleChild.resize){
+ var cb = this._contentBox || dojo.contentBox(this.containerNode);
+
+ // note: if widget has padding this._contentBox will have l and t set,
+ // but don't pass them to resize() or it will doubly-offset the child
+ this._singleChild.resize({w: cb.w, h: cb.h});
+ }else{
+ // All my child widgets are independently sized (rather than matching my size),
+ // but I still need to call resize() on each child to make it layout.
+ dojo.forEach(this.getChildren(), function(widget){
+ if(widget.resize){
+ widget.resize();
+ }
+ });
+ }
+ },
+
+ _isShown: function(){
+ // summary:
+ // Returns true if the content is currently shown.
+ // description:
+ // If I am a child of a layout widget then it actually returns true if I've ever been visible,
+ // not whether I'm currently visible, since that's much faster than tracing up the DOM/widget
+ // tree every call, and at least solves the performance problem on page load by deferring loading
+ // hidden ContentPanes until they are first shown
+
+ if(this._childOfLayoutWidget){
+ // If we are TitlePane, etc - we return that only *IF* we've been resized
+ if(this._resizeCalled && "open" in this){
+ return this.open;
+ }
+ return this._resizeCalled;
+ }else if("open" in this){
+ return this.open; // for TitlePane, etc.
+ }else{
+ var node = this.domNode, parent = this.domNode.parentNode;
+ return (node.style.display != 'none') && (node.style.visibility != 'hidden') && !dojo.hasClass(node, "dijitHidden") &&
+ parent && parent.style && (parent.style.display != 'none');
+ }
+ },
+
+ _onShow: function(){
+ // summary:
+ // Called when the ContentPane is made visible
+ // description:
+ // For a plain ContentPane, this is called on initialization, from startup().
+ // If the ContentPane is a hidden pane of a TabContainer etc., then it's
+ // called whenever the pane is made visible.
+ //
+ // Does layout/resize of child widget(s)
+
+ if(this._needLayout){
+ // If a layout has been scheduled for when we become visible, do it now
+ this._layout(this._changeSize, this._resultSize);
+ }
+
+ this.inherited(arguments);
+
+ // Need to keep track of whether ContentPane has been shown (which is different than
+ // whether or not it's currently visible).
+ this._wasShown = true;
}
-);
+});
}
@@ -8939,24 +9644,25 @@ if(!dojo._hasResource["dojo.html"]){ //_hasResource checks added by build. Do no
dojo._hasResource["dojo.html"] = true;
dojo.provide("dojo.html");
-// the parser might be needed..
-
+dojo.getObject("html", true, dojo);
+
+// the parser might be needed..
(function(){ // private scope, sort of a namespace
// idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes
- var idCounter = 0,
+ var idCounter = 0,
d = dojo;
dojo.html._secureForInnerHtml = function(/*String*/ cont){
// summary:
// removes !DOCTYPE and title elements from the html string.
- //
+ //
// khtml is picky about dom faults, you can't attach a style or <title> node as child of body
// must go into head, so we need to cut out those tags
// cont:
// An html string for insertion into the dom
- //
+ //
return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String
};
@@ -8976,7 +9682,7 @@ dojo.provide("dojo.html");
// node:
// the parent element
// content:
- // the content to be set on the parent element.
+ // the content to be set on the parent element.
// This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
// always empty
@@ -9002,7 +9708,7 @@ dojo.provide("dojo.html");
};
// we wrap up the content-setting operation in a object
- dojo.declare("dojo.html._ContentSetter", null,
+ dojo.declare("dojo.html._ContentSetter", null,
{
// node: DomNode|String
// An node which will be the parent element that we set content into
@@ -9013,11 +9719,11 @@ dojo.provide("dojo.html");
content: "",
// id: String?
- // Usually only used internally, and auto-generated with each instance
+ // Usually only used internally, and auto-generated with each instance
id: "",
// cleanContent: Boolean
- // Should the content be treated as a full html document,
+ // Should the content be treated as a full html document,
// and the real content stripped of <html>, <body> wrapper before injection
cleanContent: false,
@@ -9028,6 +9734,17 @@ dojo.provide("dojo.html");
// parseContent: Boolean
// Should the node by passed to the parser after the new content is set
parseContent: false,
+
+ // parserScope: String
+ // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo,
+ // will search for data-dojo-type (or dojoType). For backwards compatibility
+ // reasons defaults to dojo._scopeName (which is "dojo" except when
+ // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
+ parserScope: dojo._scopeName,
+
+ // startup: Boolean
+ // Start the child widgets after parsing them. Only obeyed if parseContent is true.
+ startup: true,
// lifecyle methods
constructor: function(/* Object */params, /* String|DomNode */node){
@@ -9045,14 +9762,14 @@ dojo.provide("dojo.html");
if(!this.id){
this.id = [
"Setter",
- (node) ? node.id || node.tagName : "",
+ (node) ? node.id || node.tagName : "",
idCounter++
].join("_");
}
},
set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){
// summary:
- // front-end to the set-content sequence
+ // front-end to the set-content sequence
// cont:
// An html string, node or enumerable list of nodes for insertion into the dom
// If not provided, the object's content property will be used
@@ -9072,9 +9789,9 @@ dojo.provide("dojo.html");
},
setContent: function(){
// summary:
- // sets the content on the node
+ // sets the content on the node
- var node = this.node;
+ var node = this.node;
if(!node) {
// can't proceed
throw new Error(this.declaredClass + ": setContent given no node");
@@ -9086,7 +9803,7 @@ dojo.provide("dojo.html");
// like for instance if domNode is a UL and we try append a DIV
// FIXME: need to allow the user to provide a content error message string
- var errMess = this.onContentError(e);
+ var errMess = this.onContentError(e);
try{
node.innerHTML = errMess;
}catch(e){
@@ -9102,7 +9819,7 @@ dojo.provide("dojo.html");
// cleanly empty out existing content
// destroy any widgets from a previous run
- // NOTE: if you dont want this you'll need to empty
+ // NOTE: if you dont want this you'll need to empty
// the parseResults array property yourself to avoid bad things happenning
if(this.parseResults && this.parseResults.length) {
dojo.forEach(this.parseResults, function(w) {
@@ -9112,17 +9829,17 @@ dojo.provide("dojo.html");
});
delete this.parseResults;
}
- // this is fast, but if you know its already empty or safe, you could
+ // this is fast, but if you know its already empty or safe, you could
// override empty to skip this step
dojo.html._emptyNode(this.node);
},
onBegin: function(){
// summary
- // Called after instantiation, but before set();
- // It allows modification of any of the object properties
+ // Called after instantiation, but before set();
+ // It allows modification of any of the object properties
// - including the node and content provided - before the set operation actually takes place
- // This default implementation checks for cleanContent and extractContent flags to
+ // This default implementation checks for cleanContent and extractContent flags to
// optionally pre-process html string content
var cont = this.content;
@@ -9160,21 +9877,21 @@ dojo.provide("dojo.html");
// summary
// manually reset the Setter instance if its being re-used for example for another set()
// description
- // tearDown() is not called automatically.
+ // tearDown() is not called automatically.
// In normal use, the Setter instance properties are simply allowed to fall out of scope
// but the tearDown method can be called to explicitly reset this instance.
- delete this.parseResults;
- delete this.node;
- delete this.content;
+ delete this.parseResults;
+ delete this.node;
+ delete this.content;
},
onContentError: function(err){
- return "Error occured setting content: " + err;
+ return "Error occured setting content: " + err;
},
_mixin: function(params){
// mix properties/methods into the instance
- // TODO: the intention with tearDown is to put the Setter's state
+ // TODO: the intention with tearDown is to put the Setter's state
// back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params)
// so we could do something here to move the original properties aside for later restoration
var empty = {}, key;
@@ -9182,21 +9899,28 @@ dojo.provide("dojo.html");
if(key in empty){ continue; }
// TODO: here's our opportunity to mask the properties we dont consider configurable/overridable
// .. but history shows we'll almost always guess wrong
- this[key] = params[key];
+ this[key] = params[key];
}
},
_parse: function(){
- // summary:
+ // summary:
// runs the dojo parser over the node contents, storing any results in this.parseResults
// Any errors resulting from parsing are passed to _onError for handling
var rootNode = this.node;
try{
// store the results (widgets, whatever) for potential retrieval
+ var inherited = {};
+ dojo.forEach(["dir", "lang", "textDir"], function(name){
+ if(this[name]){
+ inherited[name] = this[name];
+ }
+ }, this);
this.parseResults = dojo.parser.parse({
rootNode: rootNode,
- dir: this.dir,
- lang: this.lang
+ noStart: !this.startup,
+ inherited: inherited,
+ scope: this.parserScope
});
}catch(e){
this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id);
@@ -9229,29 +9953,29 @@ dojo.provide("dojo.html");
// node:
// the parent element that will receive the content
// cont:
- // the content to be set on the parent element.
+ // the content to be set on the parent element.
// This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
- // params:
+ // params:
// Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter
// example:
// A safe string/node/nodelist content replacement/injection with hooks for extension
- // Example Usage:
- // dojo.html.set(node, "some string");
- // dojo.html.set(node, contentNode, {options});
- // dojo.html.set(node, myNode.childNodes, {options});
+ // Example Usage:
+ // dojo.html.set(node, "some string");
+ // dojo.html.set(node, contentNode, {options});
+ // dojo.html.set(node, myNode.childNodes, {options});
if(undefined == cont){
console.warn("dojo.html.set: no cont argument provided, using empty string");
cont = "";
- }
+ }
if(!params){
// simple and fast
return dojo.html._setNodeContent(node, cont, true);
- }else{
+ }else{
// more options but slower
// note the arguments are reversed in order, to match the convention for instantiation via the parser
- var op = new dojo.html._ContentSetter(dojo.mixin(
- params,
- { content: cont, node: node }
+ var op = new dojo.html._ContentSetter(dojo.mixin(
+ params,
+ { content: cont, node: node }
));
return op.set();
}
@@ -9266,35 +9990,39 @@ dojo.provide("dijit.layout.ContentPane");
- // for dijit.layout.marginBox2contentBox()
-
-
dojo.declare(
- "dijit.layout.ContentPane", dijit._Widget,
+ "dijit.layout.ContentPane", [dijit._Widget, dijit.layout._ContentPaneResizeMixin],
{
// summary:
- // A widget that acts as a container for mixed HTML and widgets, and includes an Ajax interface
+ // A widget containing an HTML fragment, specified inline
+ // or by uri. Fragment may include widgets.
+ //
// description:
- // A widget that can be used as a stand alone widget
- // or as a base class for other widgets.
+ // This widget embeds a document fragment in the page, specified
+ // either by uri, javascript generated markup or DOM reference.
+ // Any widgets within this content are instantiated and managed,
+ // but laid out according to the HTML structure. Unlike IFRAME,
+ // ContentPane embeds a document fragment as would be found
+ // inside the BODY tag of a full HTML document. It should not
+ // contain the HTML, HEAD, or BODY tags.
+ // For more advanced functionality with scripts and
+ // stylesheets, see dojox.layout.ContentPane. This widget may be
+ // used stand alone or as a base class for other widgets.
+ // ContentPane is useful as a child of other layout containers
+ // such as BorderContainer or TabContainer, but note that those
+ // widgets can contain any widget as a child.
//
- // Handles replacement of document fragment using either external uri or javascript
- // generated markup or DOM content, instantiating widgets within that content.
- // Don't confuse it with an iframe, it only needs/wants document fragments.
- // It's useful as a child of LayoutContainer, SplitContainer, or TabContainer.
- // But note that those classes can contain any widget as a child.
// example:
// Some quick samples:
- // To change the innerHTML use .set('content', '<b>new content</b>')
+ // To change the innerHTML: cp.set('content', '<b>new content</b>')
//
- // Or you can send it a NodeList, .set('content', dojo.query('div [class=selected]', userSelection))
- // please note that the nodes in NodeList will copied, not moved
+ // Or you can send it a NodeList: cp.set('content', dojo.query('div [class=selected]', userSelection))
//
- // To do a ajax update use .set('href', url)
+ // To do an ajax update: cp.set('href', url)
// href: String
// The href of the content that displays now.
@@ -9306,7 +10034,7 @@ dojo.declare(
/*=====
// content: String || DomNode || NodeList || dijit._Widget
// The innerHTML of the ContentPane.
- // Note that the initialization parameter / argument to attr("content", ...)
+ // Note that the initialization parameter / argument to set("content", ...)
// can be a String, DomNode, Nodelist, or _Widget.
content: "",
=====*/
@@ -9320,6 +10048,13 @@ dojo.declare(
// Parse content and create the widgets, if any.
parseOnLoad: true,
+ // parserScope: String
+ // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo,
+ // will search for data-dojo-type (or dojoType). For backwards compatibility
+ // reasons defaults to dojo._scopeName (which is "dojo" except when
+ // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
+ parserScope: dojo._scopeName,
+
// preventCache: Boolean
// Prevent caching of data from href's by appending a timestamp to the href.
preventCache: false,
@@ -9343,7 +10078,7 @@ dojo.declare(
// isLoaded: [readonly] Boolean
// True if the ContentPane has data in it, either specified
// during initialization (via href or inline content), or set
- // via attr('content', ...) / attr('href', ...)
+ // via set('content', ...) / set('href', ...)
//
// False if it doesn't have any content, or if ContentPane is
// still in the process of downloading href.
@@ -9351,35 +10086,19 @@ dojo.declare(
baseClass: "dijitContentPane",
- // doLayout: Boolean
- // - false - don't adjust size of children
- // - true - if there is a single visible child widget, set it's size to
- // however big the ContentPane is
- doLayout: true,
-
// ioArgs: Object
// Parameters to pass to xhrGet() request, for example:
// | <div dojoType="dijit.layout.ContentPane" href="./bar" ioArgs="{timeout: 500}">
ioArgs: {},
- // isContainer: [protected] Boolean
- // Indicates that this widget acts as a "parent" to the descendant widgets.
- // When the parent is started it will call startup() on the child widgets.
- // See also `isLayoutContainer`.
- isContainer: true,
-
- // isLayoutContainer: [protected] Boolean
- // Indicates that this widget will call resize() on it's child widgets
- // when they become visible.
- isLayoutContainer: true,
-
// onLoadDeferred: [readonly] dojo.Deferred
- // This is the `dojo.Deferred` returned by attr('href', ...) and refresh().
+ // This is the `dojo.Deferred` returned by set('href', ...) and refresh().
// Calling onLoadDeferred.addCallback() or addErrback() registers your
- // callback to be called only once, when the prior attr('href', ...) call or
+ // callback to be called only once, when the prior set('href', ...) call or
// the initial href parameter to the constructor finishes loading.
//
- // This is different than an onLoad() handler which gets called any time any href is loaded.
+ // This is different than an onLoad() handler which gets called any time any href
+ // or content is loaded.
onLoadDeferred: null,
// Override _Widget's attributeMap because we don't want the title attribute (used to specify
@@ -9389,99 +10108,71 @@ dojo.declare(
title: []
}),
+ // Flag to parser that I'll parse my contents, so it shouldn't.
+ stopParser: true,
+
+ // template: [private] Boolean
+ // Flag from the parser that this ContentPane is inside a template
+ // so the contents are pre-parsed.
+ // (TODO: this declaration can be commented out in 2.0)
+ template: false,
+
+ create: function(params, srcNodeRef){
+ // Convert a srcNodeRef argument into a content parameter, so that the original contents are
+ // processed in the same way as contents set via set("content", ...), calling the parser etc.
+ // Avoid modifying original params object since that breaks NodeList instantiation, see #11906.
+ if((!params || !params.template) && srcNodeRef && !("href" in params) && !("content" in params)){
+ var df = dojo.doc.createDocumentFragment();
+ srcNodeRef = dojo.byId(srcNodeRef)
+ while(srcNodeRef.firstChild){
+ df.appendChild(srcNodeRef.firstChild);
+ }
+ params = dojo.delegate(params, {content: df});
+ }
+ this.inherited(arguments, [params, srcNodeRef]);
+ },
+
postMixInProperties: function(){
this.inherited(arguments);
var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang);
this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages);
this.errorMessage = dojo.string.substitute(this.errorMessage, messages);
-
- // Detect if we were initialized with data
- if(!this.href && this.srcNodeRef && this.srcNodeRef.innerHTML){
- this.isLoaded = true;
- }
},
buildRendering: function(){
- // Overrides Widget.buildRendering().
- // Since we have no template we need to set this.containerNode ourselves.
- // For subclasses of ContentPane do have a template, does nothing.
this.inherited(arguments);
+
+ // Since we have no template we need to set this.containerNode ourselves, to make getChildren() work.
+ // For subclasses of ContentPane that do have a template, does nothing.
if(!this.containerNode){
- // make getDescendants() work
this.containerNode = this.domNode;
}
- },
- postCreate: function(){
// remove the title attribute so it doesn't show up when hovering
- // over a node
+ // over a node (TODO: remove in 2.0, no longer needed after #11490)
this.domNode.title = "";
if(!dojo.attr(this.domNode,"role")){
dijit.setWaiRole(this.domNode, "group");
}
-
- dojo.addClass(this.domNode, this.baseClass);
},
- startup: function(){
+ _startChildren: function(){
// summary:
- // See `dijit.layout._LayoutWidget.startup` for description.
- // Although ContentPane doesn't extend _LayoutWidget, it does implement
- // the same API.
- if(this._started){ return; }
-
- var parent = dijit._Contained.prototype.getParent.call(this);
- this._childOfLayoutWidget = parent && parent.isLayoutContainer;
-
- // I need to call resize() on my child/children (when I become visible), unless
- // I'm the child of a layout widget in which case my parent will call resize() on me and I'll do it then.
- this._needLayout = !this._childOfLayoutWidget;
-
- if(this.isLoaded){
- dojo.forEach(this.getChildren(), function(child){
- child.startup();
- });
- }
-
- if(this._isShown() || this.preload){
- this._onShow();
- }
+ // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
+ // This starts all the widgets
this.inherited(arguments);
- },
-
- _checkIfSingleChild: function(){
- // summary:
- // Test if we have exactly one visible widget as a child,
- // and if so assume that we are a container for that widget,
- // and should propogate startup() and resize() calls to it.
- // Skips over things like data stores since they aren't visible.
-
- var childNodes = dojo.query("> *", this.containerNode).filter(function(node){
- return node.tagName !== "SCRIPT"; // or a regexp for hidden elements like script|area|map|etc..
- }),
- childWidgetNodes = childNodes.filter(function(node){
- return dojo.hasAttr(node, "dojoType") || dojo.hasAttr(node, "widgetId");
- }),
- candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){
- return widget && widget.domNode && widget.resize;
- });
- if(
- // all child nodes are widgets
- childNodes.length == childWidgetNodes.length &&
-
- // all but one are invisible (like dojo.data)
- candidateWidgets.length == 1
- ){
- this._singleChild = candidateWidgets[0];
- }else{
- delete this._singleChild;
+ // And this catches stuff like dojo.dnd.Source
+ if(this._contentSetter){
+ dojo.forEach(this._contentSetter.parseResults, function(obj){
+ if(!obj._started && !obj._destroyed && dojo.isFunction(obj.startup)){
+ obj.startup();
+ obj._started = true;
+ }
+ }, this);
}
-
- // So we can set overflow: hidden to avoid a safari bug w/scrollbars showing up (#9449)
- dojo.toggleClass(this.containerNode, this.baseClass + "SingleChild", !!this._singleChild);
},
setHref: function(/*String|Uri*/ href){
@@ -9492,23 +10183,25 @@ dojo.declare(
},
_setHrefAttr: function(/*String|Uri*/ href){
// summary:
- // Hook so attr("href", ...) works.
+ // Hook so set("href", ...) works.
// description:
// Reset the (external defined) content of this pane and replace with new url
// Note: It delays the download until widget is shown if preload is false.
// href:
// url to the page you want to get, must be within the same domain as your mainpage
- // Cancel any in-flight requests (an attr('href') will cancel any in-flight attr('href', ...))
+ // Cancel any in-flight requests (a set('href', ...) will cancel any in-flight set('href', ...))
this.cancel();
this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
+ this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad"));
- this.href = href;
+ this._set("href", href);
// _setHrefAttr() is called during creation and by the user, after creation.
- // only in the second case do we actually load the URL; otherwise it's done in startup()
- if(this._created && (this.preload || this._isShown())){
+ // Assuming preload == false, only in the second case do we actually load the URL;
+ // otherwise it's done in startup(), and only if this widget is shown.
+ if(this.preload || (this._created && this._isShown())){
this._load();
}else{
// Set flag to indicate that href needs to be loaded the next time the
@@ -9527,7 +10220,7 @@ dojo.declare(
},
_setContentAttr: function(/*String|DomNode|Nodelist*/data){
// summary:
- // Hook to make attr("content", ...) work.
+ // Hook to make set("content", ...) work.
// Replaces old content with data content, include style classes from old content
// data:
// the new Content may be String, DomNode or NodeList
@@ -9537,24 +10230,30 @@ dojo.declare(
// clear href so we can't run refresh and clear content
// refresh should only work if we downloaded the content
- this.href = "";
+ this._set("href", "");
- // Cancel any in-flight requests (an attr('content') will cancel any in-flight attr('href', ...))
+ // Cancel any in-flight requests (a set('content', ...) will cancel any in-flight set('href', ...))
this.cancel();
// Even though user is just setting content directly, still need to define an onLoadDeferred
// because the _onLoadHandler() handler is still getting called from setContent()
this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
+ if(this._created){
+ // For back-compat reasons, call onLoad() for set('content', ...)
+ // calls but not for content specified in srcNodeRef (ie: <div dojoType=ContentPane>...</div>)
+ // or as initialization parameter (ie: new ContentPane({content: ...})
+ this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad"));
+ }
this._setContent(data || "");
- this._isDownloaded = false; // mark that content is from a attr('content') not an attr('href')
+ this._isDownloaded = false; // mark that content is from a set('content') not a set('href')
return this.onLoadDeferred; // dojo.Deferred
},
_getContentAttr: function(){
// summary:
- // Hook to make attr("content") work
+ // Hook to make get("content") work
return this.containerNode.innerHTML;
},
@@ -9587,69 +10286,6 @@ dojo.declare(
this.inherited(arguments);
},
- resize: function(changeSize, resultSize){
- // summary:
- // See `dijit.layout._LayoutWidget.resize` for description.
- // Although ContentPane doesn't extend _LayoutWidget, it does implement
- // the same API.
-
- // For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is
- // never called, so resize() is our trigger to do the initial href download.
- if(!this._wasShown){
- this._onShow();
- }
-
- this._resizeCalled = true;
-
- // Set margin box size, unless it wasn't specified, in which case use current size.
- if(changeSize){
- dojo.marginBox(this.domNode, changeSize);
- }
-
- // Compute content box size of containerNode in case we [later] need to size our single child.
- var cn = this.containerNode;
- if(cn === this.domNode){
- // If changeSize or resultSize was passed to this method and this.containerNode ==
- // this.domNode then we can compute the content-box size without querying the node,
- // which is more reliable (similar to LayoutWidget.resize) (see for example #9449).
- var mb = resultSize || {};
- dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize
- if(!("h" in mb) || !("w" in mb)){
- mb = dojo.mixin(dojo.marginBox(cn), mb); // just use dojo.marginBox() to fill in missing values
- }
- this._contentBox = dijit.layout.marginBox2contentBox(cn, mb);
- }else{
- this._contentBox = dojo.contentBox(cn);
- }
-
- // Make my children layout, or size my single child widget
- this._layoutChildren();
- },
-
- _isShown: function(){
- // summary:
- // Returns true if the content is currently shown.
- // description:
- // If I am a child of a layout widget then it actually returns true if I've ever been visible,
- // not whether I'm currently visible, since that's much faster than tracing up the DOM/widget
- // tree every call, and at least solves the performance problem on page load by deferring loading
- // hidden ContentPanes until they are first shown
-
- if(this._childOfLayoutWidget){
- // If we are TitlePane, etc - we return that only *IF* we've been resized
- if(this._resizeCalled && "open" in this){
- return this.open;
- }
- return this._resizeCalled;
- }else if("open" in this){
- return this.open; // for TitlePane, etc.
- }else{
- // TODO: with _childOfLayoutWidget check maybe this branch no longer necessary?
- var node = this.domNode;
- return (node.style.display != 'none') && (node.style.visibility != 'hidden') && !dojo.hasClass(node, "dijitHidden");
- }
- },
-
_onShow: function(){
// summary:
// Called when the ContentPane is made visible
@@ -9661,26 +10297,15 @@ dojo.declare(
// Does necessary processing, including href download and layout/resize of
// child widget(s)
+ this.inherited(arguments);
+
if(this.href){
if(!this._xhrDfd && // if there's an href that isn't already being loaded
(!this.isLoaded || this._hrefChanged || this.refreshOnShow)
){
- this.refresh();
- }
- }else{
- // If we are the child of a layout widget then the layout widget will call resize() on
- // us, and then we will size our child/children. Otherwise, we need to do it now.
- if(!this._childOfLayoutWidget && this._needLayout){
- // If a layout has been scheduled for when we become visible, do it now
- this._layoutChildren();
+ return this.refresh(); // If child has an href, promise that fires when the load is complete
}
}
-
- this.inherited(arguments);
-
- // Need to keep track of whether ContentPane has been shown (which is different than
- // whether or not it's currently visible).
- this._wasShown = true;
},
refresh: function(){
@@ -9695,8 +10320,9 @@ dojo.declare(
this.cancel();
this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
+ this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad"));
this._load();
- return this.onLoadDeferred;
+ return this.onLoadDeferred; // If child has an href, promise that fires when refresh is complete
},
_load: function(){
@@ -9746,10 +10372,9 @@ dojo.declare(
_onLoadHandler: function(data){
// summary:
// This is called whenever new content is being loaded
- this.isLoaded = true;
+ this._set("isLoaded", true);
try{
this.onLoadDeferred.callback(data);
- this.onLoad(data);
}catch(e){
console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message);
}
@@ -9758,7 +10383,7 @@ dojo.declare(
_onUnloadHandler: function(){
// summary:
// This is called whenever the content is being unloaded
- this.isLoaded = false;
+ this._set("isLoaded", false);
try{
this.onUnload();
}catch(e){
@@ -9804,7 +10429,7 @@ dojo.declare(
delete this._singleChild;
},
- _setContent: function(cont, isFakeContent){
+ _setContent: function(/*String|DocumentFragment*/ cont, /*Boolean*/ isFakeContent){
// summary:
// Insert the content into the container node
@@ -9839,31 +10464,31 @@ dojo.declare(
cleanContent: this.cleanContent,
extractContent: this.extractContent,
parseContent: this.parseOnLoad,
+ parserScope: this.parserScope,
+ startup: false,
dir: this.dir,
lang: this.lang
}, this._contentSetterParams || {});
- dojo.mixin(setter, setterParams);
-
- setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont );
+ setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams );
// setter params must be pulled afresh from the ContentPane each time
delete this._contentSetterParams;
- if(!isFakeContent){
- // Startup each top level child widget (and they will start their children, recursively)
- dojo.forEach(this.getChildren(), function(child){
- // The parser has already called startup on all widgets *without* a getParent() method
- if(!this.parseOnLoad || child.getParent){
- child.startup();
- }
- }, this);
+ if(this.doLayout){
+ this._checkIfSingleChild();
+ }
- // Call resize() on each of my child layout widgets,
- // or resize() on my single child layout widget...
- // either now (if I'm currently visible)
- // or when I become visible
- this._scheduleLayout();
+ if(!isFakeContent){
+ if(this._started){
+ // Startup each top level child widget (and they will start their children, recursively)
+ this._startChildren();
+
+ // Call resize() on each of my child layout widgets,
+ // or resize() on my single child layout widget...
+ // either now (if I'm currently visible) or when I become visible
+ this._scheduleLayout();
+ }
this._onLoadHandler(cont);
}
@@ -9873,7 +10498,7 @@ dojo.declare(
this.onLoadDeferred.errback(err);
// shows user the string that is returned by on[type]Error
- // overide on[type]Error and return your own string to customize
+ // override on[type]Error and return your own string to customize
var errText = this['on' + type + 'Error'].call(this, err);
if(consoleText){
console.error(consoleText, err);
@@ -9882,48 +10507,6 @@ dojo.declare(
}
},
- _scheduleLayout: function(){
- // summary:
- // Call resize() on each of my child layout widgets, either now
- // (if I'm currently visible) or when I become visible
- if(this._isShown()){
- this._layoutChildren();
- }else{
- this._needLayout = true;
- }
- },
-
- _layoutChildren: function(){
- // summary:
- // Since I am a Container widget, each of my children expects me to
- // call resize() or layout() on them.
- // description:
- // Should be called on initialization and also whenever we get new content
- // (from an href, or from attr('content', ...))... but deferred until
- // the ContentPane is visible
-
- if(this.doLayout){
- this._checkIfSingleChild();
- }
-
- if(this._singleChild && this._singleChild.resize){
- var cb = this._contentBox || dojo.contentBox(this.containerNode);
-
- // note: if widget has padding this._contentBox will have l and t set,
- // but don't pass them to resize() or it will doubly-offset the child
- this._singleChild.resize({w: cb.w, h: cb.h});
- }else{
- // All my child widgets are independently sized (rather than matching my size),
- // but I still need to call resize() on each child to make it layout.
- dojo.forEach(this.getChildren(), function(widget){
- if(widget.resize){
- widget.resize();
- }
- });
- }
- delete this._needLayout;
- },
-
// EVENT's, should be overide-able
onLoad: function(data){
// summary:
@@ -10035,12 +10618,16 @@ dojo.declare(
// Set by `dijit._DialogMixin._getFocusItems`.
_lastFocusItem: null,
- templateString: dojo.cache("dijit", "templates/TooltipDialog.html", "<div waiRole=\"presentation\">\n\t<div class=\"dijitTooltipContainer\" waiRole=\"presentation\">\n\t\t<div class =\"dijitTooltipContents dijitTooltipFocusNode\" dojoAttachPoint=\"containerNode\" tabindex=\"-1\" waiRole=\"dialog\"></div>\n\t</div>\n\t<div class=\"dijitTooltipConnector\" waiRole=\"presentation\"></div>\n</div>\n"),
+ templateString: dojo.cache("dijit", "templates/TooltipDialog.html", "<div role=\"presentation\" tabIndex=\"-1\">\n\t<div class=\"dijitTooltipContainer\" role=\"presentation\">\n\t\t<div class =\"dijitTooltipContents dijitTooltipFocusNode\" dojoAttachPoint=\"containerNode\" role=\"dialog\"></div>\n\t</div>\n\t<div class=\"dijitTooltipConnector\" role=\"presentation\"></div>\n</div>\n"),
+
+ _setTitleAttr: function(/*String*/ title){
+ this.containerNode.title = title;
+ this._set("title", title)
+ },
postCreate: function(){
this.inherited(arguments);
this.connect(this.containerNode, "onkeypress", "_onKey");
- this.containerNode.title = this.title;
},
orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ corner){
@@ -10050,13 +10637,19 @@ dojo.declare(
// directly.
// tags:
// protected
- var c = this._currentOrientClass;
- if(c){
- dojo.removeClass(this.domNode, c);
- }
- c = "dijitTooltipAB"+(corner.charAt(1) == 'L'?"Left":"Right")+" dijitTooltip"+(corner.charAt(0) == 'T' ? "Below" : "Above");
- dojo.addClass(this.domNode, c);
- this._currentOrientClass = c;
+ var newC = "dijitTooltipAB" + (corner.charAt(1) == 'L' ? "Left" : "Right")
+ + " dijitTooltip"
+ + (corner.charAt(0) == 'T' ? "Below" : "Above");
+
+ dojo.replaceClass(this.domNode, newC, this._currentOrientClass || "");
+ this._currentOrientClass = newC;
+ },
+
+ focus: function(){
+ // summary:
+ // Focus on first field
+ this._getFocusItems(this.containerNode);
+ dijit.focus(this._firstFocusItem);
},
onOpen: function(/*Object*/ pos){
@@ -10068,11 +10661,6 @@ dojo.declare(
this.orient(this.domNode,pos.aroundCorner, pos.corner);
this._onShow(); // lazy load trigger
-
- if(this.autofocus){
- this._getFocusItems(this.containerNode);
- dijit.focus(this._firstFocusItem);
- }
},
onClose: function(){
@@ -10141,6 +10729,8 @@ dojo.provide("dijit.Dialog");
+// dijit/TooltipDialog required for back-compat. TODO: remove in 2.0
+
/*=====
dijit._underlay = function(kwArgs){
// summary:
@@ -10152,7 +10742,6 @@ dijit._underlay = function(kwArgs){
// or subclass thereof is shown.
};
=====*/
-
dojo.declare(
"dijit._DialogBase",
[dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin, dijit._CssStateMixin],
@@ -10173,7 +10762,7 @@ dojo.declare(
// | dojo.body().appendChild(foo.domNode);
// | foo.startup();
- templateString: dojo.cache("dijit", "templates/Dialog.html", "<div class=\"dijitDialog\" tabindex=\"-1\" waiRole=\"dialog\" waiState=\"labelledby-${id}_title\">\n\t<div dojoAttachPoint=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t<span dojoAttachPoint=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"></span>\n\t<span dojoAttachPoint=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" dojoAttachEvent=\"onclick: onCancel\" title=\"${buttonCancel}\">\n\t\t<span dojoAttachPoint=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t</span>\n\t</div>\n\t\t<div dojoAttachPoint=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n"),
+ templateString: dojo.cache("dijit", "templates/Dialog.html", "<div class=\"dijitDialog\" role=\"dialog\" aria-labelledby=\"${id}_title\">\n\t<div dojoAttachPoint=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t<span dojoAttachPoint=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"></span>\n\t<span dojoAttachPoint=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" dojoAttachEvent=\"ondijitclick: onCancel\" title=\"${buttonCancel}\" role=\"button\" tabIndex=\"-1\">\n\t\t<span dojoAttachPoint=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t</span>\n\t</div>\n\t\t<div dojoAttachPoint=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n"),
baseClass: "dijitDialog",
@@ -10189,7 +10778,7 @@ dojo.declare(
"aria-describedby":""
}),
- // open: Boolean
+ // open: [readonly] Boolean
// True if Dialog is currently displayed on screen.
open: false,
@@ -10209,12 +10798,12 @@ dojo.declare(
// False will disable autofocusing. Default: true
autofocus: true,
- // _firstFocusItem: [private] [readonly] DomNode
+ // _firstFocusItem: [private readonly] DomNode
// The pointer to the first focusable node in the dialog.
// Set by `dijit._DialogMixin._getFocusItems`.
_firstFocusItem: null,
- // _lastFocusItem: [private] [readonly] DomNode
+ // _lastFocusItem: [private readonly] DomNode
// The pointer to which node has focus prior to our dialog.
// Set by `dijit._DialogMixin._getFocusItems`.
_lastFocusItem: null,
@@ -10266,14 +10855,14 @@ dojo.declare(
// summary:
// Called when data has been loaded from an href.
// Unlike most other callbacks, this function can be connected to (via `dojo.connect`)
- // but should *not* be overriden.
+ // but should *not* be overridden.
// tags:
// callback
// when href is specified we need to reposition the dialog after the data is loaded
// and find the focusable elements
this._position();
- if(this.autofocus){
+ if(this.autofocus && dijit._DialogLevelManager.isTop(this)){
this._getFocusItems(this.domNode);
dijit.focus(this._firstFocusItem);
}
@@ -10304,7 +10893,7 @@ dojo.declare(
this._moveable = (dojo.isIE == 6) ?
new dojo.dnd.TimedMoveable(node, { handle: this.titleBar }) : // prevent overload, see #5285
new dojo.dnd.Moveable(node, { handle: this.titleBar, timeout: 0 });
- dojo.subscribe("/dnd/move/stop",this,"_endDrag");
+ this._dndListener = dojo.subscribe("/dnd/move/stop",this,"_endDrag");
}else{
dojo.addClass(node,"dijitDialogFixed");
}
@@ -10313,95 +10902,6 @@ dojo.declare(
dialogId: this.id,
"class": dojo.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" ")
};
-
- this._fadeIn = dojo.fadeIn({
- node: node,
- duration: this.duration,
- beforeBegin: dojo.hitch(this, function(){
- var underlay = dijit._underlay;
- if(!underlay){
- underlay = dijit._underlay = new dijit.DialogUnderlay(this.underlayAttrs);
- }else{
- underlay.set(this.underlayAttrs);
- }
-
- var ds = dijit._dialogStack,
- zIndex = 948 + ds.length*2;
- if(ds.length == 1){ // first dialog
- underlay.show();
- }
- dojo.style(dijit._underlay.domNode, 'zIndex', zIndex);
- dojo.style(this.domNode, 'zIndex', zIndex + 1);
- }),
- onEnd: dojo.hitch(this, function(){
- if(this.autofocus){
- // find focusable Items each time dialog is shown since if dialog contains a widget the
- // first focusable items can change
- this._getFocusItems(this.domNode);
- dijit.focus(this._firstFocusItem);
- }
- })
- });
-
- this._fadeOut = dojo.fadeOut({
- node: node,
- duration: this.duration,
- onEnd: dojo.hitch(this, function(){
- node.style.display = "none";
-
- // Restore the previous dialog in the stack, or if this is the only dialog
- // then restore to original page
- var ds = dijit._dialogStack;
- if(ds.length == 0){
- dijit._underlay.hide();
- }else{
- dojo.style(dijit._underlay.domNode, 'zIndex', 948 + ds.length*2);
- dijit._underlay.set(ds[ds.length-1].underlayAttrs);
- }
-
- // Restore focus to wherever it was before this dialog was displayed
- if(this.refocus){
- var focus = this._savedFocus;
-
- // If we are returning control to a previous dialog but for some reason
- // that dialog didn't have a focused field, set focus to first focusable item.
- // This situation could happen if two dialogs appeared at nearly the same time,
- // since a dialog doesn't set it's focus until the fade-in is finished.
- if(ds.length > 0){
- var pd = ds[ds.length-1];
- if(!dojo.isDescendant(focus.node, pd.domNode)){
- pd._getFocusItems(pd.domNode);
- focus = pd._firstFocusItem;
- }
- }
-
- dijit.focus(focus);
- }
- })
- });
- },
-
- uninitialize: function(){
- var wasPlaying = false;
- if(this._fadeIn && this._fadeIn.status() == "playing"){
- wasPlaying = true;
- this._fadeIn.stop();
- }
- if(this._fadeOut && this._fadeOut.status() == "playing"){
- wasPlaying = true;
- this._fadeOut.stop();
- }
-
- // Hide the underlay, unless the underlay widget has already been destroyed
- // because we are being called during page unload (when all widgets are destroyed)
- if((this.open || wasPlaying) && !dijit._underlay._destroyed){
- dijit._underlay.hide();
- }
-
- if(this._moveable){
- this._moveable.destroy();
- }
- this.inherited(arguments);
},
_size: function(){
@@ -10427,7 +10927,7 @@ dojo.declare(
});
}
- var mb = dojo.marginBox(this.domNode);
+ var mb = dojo._getMarginSize(this.domNode);
var viewport = dojo.window.getBox();
if(mb.w >= viewport.w || mb.h >= viewport.h){
// Reduce size of dialog contents so that dialog fits in viewport
@@ -10482,12 +10982,6 @@ dojo.declare(
// tags:
// private
- var ds = dijit._dialogStack;
- if(ds[ds.length-1] != this){
- // console.debug(this.id + ': skipping because', this, 'is not the active dialog');
- return;
- }
-
if(evt.charOrCode){
var dk = dojo.keys;
var node = evt.target;
@@ -10534,16 +11028,23 @@ dojo.declare(
show: function(){
// summary:
// Display the dialog
+ // returns: dojo.Deferred
+ // Deferred object that resolves when the display animation is complete
+
if(this.open){ return; }
+ if(!this._started){
+ this.startup();
+ }
+
// first time we show the dialog, there's some initialization stuff to do
if(!this._alreadyInitialized){
this._setup();
this._alreadyInitialized=true;
}
- if(this._fadeOut.status() == "playing"){
- this._fadeOut.stop();
+ if(this._fadeOutDeferred){
+ this._fadeOutDeferred.cancel();
}
this._modalconnects.push(dojo.connect(window, "onscroll", this, "layout"));
@@ -10558,43 +11059,81 @@ dojo.declare(
this._oldViewport = viewport;
}
}));
- this._modalconnects.push(dojo.connect(dojo.doc.documentElement, "onkeypress", this, "_onKey"));
+ this._modalconnects.push(dojo.connect(this.domNode, "onkeypress", this, "_onKey"));
dojo.style(this.domNode, {
opacity:0,
display:""
});
- this.open = true;
+ this._set("open", true);
this._onShow(); // lazy load trigger
this._size();
this._position();
- dijit._dialogStack.push(this);
- this._fadeIn.play();
- this._savedFocus = dijit.getFocus(this);
+ // fade-in Animation object, setup below
+ var fadeIn;
+
+ this._fadeInDeferred = new dojo.Deferred(dojo.hitch(this, function(){
+ fadeIn.stop();
+ delete this._fadeInDeferred;
+ }));
+
+ fadeIn = dojo.fadeIn({
+ node: this.domNode,
+ duration: this.duration,
+ beforeBegin: dojo.hitch(this, function(){
+ dijit._DialogLevelManager.show(this, this.underlayAttrs);
+ }),
+ onEnd: dojo.hitch(this, function(){
+ if(this.autofocus && dijit._DialogLevelManager.isTop(this)){
+ // find focusable items each time dialog is shown since if dialog contains a widget the
+ // first focusable items can change
+ this._getFocusItems(this.domNode);
+ dijit.focus(this._firstFocusItem);
+ }
+ this._fadeInDeferred.callback(true);
+ delete this._fadeInDeferred;
+ })
+ }).play();
+
+ return this._fadeInDeferred;
},
hide: function(){
// summary:
// Hide the dialog
+ // returns: dojo.Deferred
+ // Deferred object that resolves when the hide animation is complete
// if we haven't been initialized yet then we aren't showing and we can just return
- // or if we aren't the active dialog, don't allow us to close yet
- var ds = dijit._dialogStack;
- if(!this._alreadyInitialized || this != ds[ds.length-1]){
+ if(!this._alreadyInitialized){
return;
}
-
- if(this._fadeIn.status() == "playing"){
- this._fadeIn.stop();
+ if(this._fadeInDeferred){
+ this._fadeInDeferred.cancel();
}
- // throw away current active dialog from stack -- making the previous dialog or the node on the original page active
- ds.pop();
+ // fade-in Animation object, setup below
+ var fadeOut;
+
+ this._fadeOutDeferred = new dojo.Deferred(dojo.hitch(this, function(){
+ fadeOut.stop();
+ delete this._fadeOutDeferred;
+ }));
- this._fadeOut.play();
+ fadeOut = dojo.fadeOut({
+ node: this.domNode,
+ duration: this.duration,
+ onEnd: dojo.hitch(this, function(){
+ this.domNode.style.display = "none";
+ dijit._DialogLevelManager.hide(this);
+ this.onHide();
+ this._fadeOutDeferred.callback(true);
+ delete this._fadeOutDeferred;
+ })
+ }).play();
if(this._scrollConnected){
this._scrollConnected = false;
@@ -10605,9 +11144,9 @@ dojo.declare(
if(this._relativePosition){
delete this._relativePosition;
}
- this.open = false;
+ this._set("open", false);
- this.onHide();
+ return this._fadeOutDeferred;
},
layout: function(){
@@ -10624,10 +11163,22 @@ dojo.declare(
},
destroy: function(){
- dojo.forEach(this._modalconnects, dojo.disconnect);
- if(this.refocus && this.open){
- setTimeout(dojo.hitch(dijit,"focus",this._savedFocus), 25);
+ if(this._fadeInDeferred){
+ this._fadeInDeferred.cancel();
+ }
+ if(this._fadeOutDeferred){
+ this._fadeOutDeferred.cancel();
+ }
+ if(this._moveable){
+ this._moveable.destroy();
+ }
+ if(this._dndListener){
+ dojo.unsubscribe(this._dndListener);
}
+ dojo.forEach(this._modalconnects, dojo.disconnect);
+
+ dijit._DialogLevelManager.hide(this);
+
this.inherited(arguments);
}
}
@@ -10639,11 +11190,129 @@ dojo.declare(
{}
);
-// Stack of currenctly displayed dialogs, layered on top of each other
-dijit._dialogStack = [];
+dijit._DialogLevelManager = {
+ // summary:
+ // Controls the various active "levels" on the page, starting with the
+ // stuff initially visible on the page (at z-index 0), and then having an entry for
+ // each Dialog shown.
-// For back-compat. TODO: remove in 2.0
+ show: function(/*dijit._Widget*/ dialog, /*Object*/ underlayAttrs){
+ // summary:
+ // Call right before fade-in animation for new dialog.
+ // Saves current focus, displays/adjusts underlay for new dialog,
+ // and sets the z-index of the dialog itself.
+ //
+ // New dialog will be displayed on top of all currently displayed dialogs.
+ //
+ // Caller is responsible for setting focus in new dialog after the fade-in
+ // animation completes.
+
+ var ds = dijit._dialogStack;
+
+ // Save current focus
+ ds[ds.length-1].focus = dijit.getFocus(dialog);
+
+ // Display the underlay, or if already displayed then adjust for this new dialog
+ var underlay = dijit._underlay;
+ if(!underlay || underlay._destroyed){
+ underlay = dijit._underlay = new dijit.DialogUnderlay(underlayAttrs);
+ }else{
+ underlay.set(dialog.underlayAttrs);
+ }
+
+ // Set z-index a bit above previous dialog
+ var zIndex = ds[ds.length-1].dialog ? ds[ds.length-1].zIndex + 2 : 950;
+ if(ds.length == 1){ // first dialog
+ underlay.show();
+ }
+ dojo.style(dijit._underlay.domNode, 'zIndex', zIndex - 1);
+
+ // Dialog
+ dojo.style(dialog.domNode, 'zIndex', zIndex);
+
+ ds.push({dialog: dialog, underlayAttrs: underlayAttrs, zIndex: zIndex});
+ },
+
+ hide: function(/*dijit._Widget*/ dialog){
+ // summary:
+ // Called when the specified dialog is hidden/destroyed, after the fade-out
+ // animation ends, in order to reset page focus, fix the underlay, etc.
+ // If the specified dialog isn't open then does nothing.
+ //
+ // Caller is responsible for either setting display:none on the dialog domNode,
+ // or calling dijit.popup.hide(), or removing it from the page DOM.
+
+ var ds = dijit._dialogStack;
+
+ if(ds[ds.length-1].dialog == dialog){
+ // Removing the top (or only) dialog in the stack, return focus
+ // to previous dialog
+
+ ds.pop();
+
+ var pd = ds[ds.length-1]; // the new active dialog (or the base page itself)
+
+ // Adjust underlay
+ if(ds.length == 1){
+ // Returning to original page.
+ // Hide the underlay, unless the underlay widget has already been destroyed
+ // because we are being called during page unload (when all widgets are destroyed)
+ if(!dijit._underlay._destroyed){
+ dijit._underlay.hide();
+ }
+ }else{
+ // Popping back to previous dialog, adjust underlay
+ dojo.style(dijit._underlay.domNode, 'zIndex', pd.zIndex - 1);
+ dijit._underlay.set(pd.underlayAttrs);
+ }
+
+ // Adjust focus
+ if(dialog.refocus){
+ // If we are returning control to a previous dialog but for some reason
+ // that dialog didn't have a focused field, set focus to first focusable item.
+ // This situation could happen if two dialogs appeared at nearly the same time,
+ // since a dialog doesn't set it's focus until the fade-in is finished.
+ var focus = pd.focus;
+ if(!focus || (pd.dialog && !dojo.isDescendant(focus.node, pd.dialog.domNode))){
+ pd.dialog._getFocusItems(pd.dialog.domNode);
+ focus = pd.dialog._firstFocusItem;
+ }
+
+ try{
+ dijit.focus(focus);
+ }catch(e){
+ /* focus() will fail if user opened the dialog by clicking a non-focusable element */
+ }
+ }
+ }else{
+ // Removing a dialog out of order (#9944, #10705).
+ // Don't need to mess with underlay or z-index or anything.
+ var idx = dojo.indexOf(dojo.map(ds, function(elem){return elem.dialog}), dialog);
+ if(idx != -1){
+ ds.splice(idx, 1);
+ }
+ }
+ },
+
+ isTop: function(/*dijit._Widget*/ dialog){
+ // summary:
+ // Returns true if specified Dialog is the top in the task
+ var ds = dijit._dialogStack;
+ return ds[ds.length-1].dialog == dialog;
+ }
+};
+// Stack representing the various active "levels" on the page, starting with the
+// stuff initially visible on the page (at z-index 0), and then having an entry for
+// each Dialog shown.
+// Each element in stack has form {
+// dialog: dialogWidget,
+// focus: returnFromGetFocus(),
+// underlayAttrs: attributes to set on underlay (when this widget is active)
+// }
+dijit._dialogStack = [
+ {dialog: null, focus: null, underlayAttrs: null} // entry for stuff at z-index: 0
+];
}
@@ -10653,7 +11322,6 @@ dojo.provide("dijit._HasDropDown");
-
dojo.declare("dijit._HasDropDown",
null,
{
@@ -10702,8 +11370,9 @@ dojo.declare("dijit._HasDropDown",
forceWidth: false,
// maxHeight: [protected] Integer
- // The max height for our dropdown. Set to 0 for no max height.
- // any dropdown taller than this will have scrollbars
+ // The max height for our dropdown.
+ // Any dropdown taller than this will have scrollbars.
+ // Set to 0 for no max height, or -1 to limit height to available space in viewport
maxHeight: 0,
// dropDownPosition: [const] String[]
@@ -10733,6 +11402,8 @@ dojo.declare("dijit._HasDropDown",
if(this.disabled || this.readOnly){ return; }
+ dojo.stopEvent(e);
+
this._docHandler = this.connect(dojo.doc, "onmouseup", "_onDropDownMouseUp");
this.toggleDropDown();
@@ -10786,7 +11457,7 @@ dojo.declare("dijit._HasDropDown",
}
}
}
- if(this._opened && dropDown.focus){
+ if(this._opened && dropDown.focus && dropDown.autoFocus !== false){
// Focus the dropdown widget - do it on a delay so that we
// don't steal our own focus.
window.setTimeout(dojo.hitch(dropDown, "focus"), 1);
@@ -10797,27 +11468,14 @@ dojo.declare("dijit._HasDropDown",
// the drop down was already opened on mousedown/keydown; just need to call stopEvent()
if(this._stopClickEvents){
dojo.stopEvent(e);
- }
+ }
},
- _setupDropdown: function(){
- // summary:
- // set up nodes and connect our mouse and keypress events
+ buildRendering: function(){
+ this.inherited(arguments);
+
this._buttonNode = this._buttonNode || this.focusNode || this.domNode;
this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode;
- this._aroundNode = this._aroundNode || this.domNode;
- this.connect(this._buttonNode, "onmousedown", "_onDropDownMouseDown");
- this.connect(this._buttonNode, "onclick", "_onDropDownClick");
- this.connect(this._buttonNode, "onkeydown", "_onDropDownKeydown");
- this.connect(this._buttonNode, "onkeyup", "_onKey");
-
- // If we have a _setStateClass function (which happens when
- // we are a form widget), then we need to connect our open/close
- // functions to it
- if(this._setStateClass){
- this.connect(this, "openDropDown", "_setStateClass");
- this.connect(this, "closeDropDown", "_setStateClass");
- }
// Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow
// based on where drop down will normally appear
@@ -10833,11 +11491,18 @@ dojo.declare("dijit._HasDropDown",
},
postCreate: function(){
- this._setupDropdown();
+ // summary:
+ // set up nodes and connect our mouse and keypress events
+
this.inherited(arguments);
+
+ this.connect(this._buttonNode, "onmousedown", "_onDropDownMouseDown");
+ this.connect(this._buttonNode, "onclick", "_onDropDownClick");
+ this.connect(this.focusNode, "onkeypress", "_onKey");
+ this.connect(this.focusNode, "onkeyup", "_onKeyUp");
},
- destroyDescendants: function(){
+ destroy: function(){
if(this.dropDown){
// Destroy the drop down, unless it's already been destroyed. This can happen because
// the drop down is a direct child of <body> even though it's logically my child.
@@ -10849,27 +11514,43 @@ dojo.declare("dijit._HasDropDown",
this.inherited(arguments);
},
- _onDropDownKeydown: function(/*Event*/ e){
- if(e.keyCode == dojo.keys.DOWN_ARROW || e.keyCode == dojo.keys.ENTER || e.keyCode == dojo.keys.SPACE){
- e.preventDefault(); // stop IE screen jump
- }
- },
-
_onKey: function(/*Event*/ e){
// summary:
// Callback when the user presses a key while focused on the button node
if(this.disabled || this.readOnly){ return; }
- var d = this.dropDown;
+
+ var d = this.dropDown, target = e.target;
if(d && this._opened && d.handleKey){
- if(d.handleKey(e) === false){ return; }
+ if(d.handleKey(e) === false){
+ /* false return code means that the drop down handled the key */
+ dojo.stopEvent(e);
+ return;
+ }
}
- if(d && this._opened && e.keyCode == dojo.keys.ESCAPE){
- this.toggleDropDown();
- }else if(d && !this._opened &&
- (e.keyCode == dojo.keys.DOWN_ARROW || e.keyCode == dojo.keys.ENTER || e.keyCode == dojo.keys.SPACE)){
+ if(d && this._opened && e.charOrCode == dojo.keys.ESCAPE){
+ this.closeDropDown();
+ dojo.stopEvent(e);
+ }else if(!this._opened &&
+ (e.charOrCode == dojo.keys.DOWN_ARROW ||
+ ( (e.charOrCode == dojo.keys.ENTER || e.charOrCode == " ") &&
+ //ignore enter and space if the event is for a text input
+ ((target.tagName || "").toLowerCase() !== 'input' ||
+ (target.type && target.type.toLowerCase() !== 'text'))))){
+ // Toggle the drop down, but wait until keyup so that the drop down doesn't
+ // get a stray keyup event, or in the case of key-repeat (because user held
+ // down key for too long), stray keydown events
+ this._toggleOnKeyUp = true;
+ dojo.stopEvent(e);
+ }
+ },
+
+ _onKeyUp: function(){
+ if(this._toggleOnKeyUp){
+ delete this._toggleOnKeyUp;
this.toggleDropDown();
- if(d.focus){
+ var d = this.dropDown; // drop down may not exist until toggleDropDown() call
+ if(d && d.focus){
setTimeout(dojo.hitch(d, "focus"), 1);
}
}
@@ -10879,8 +11560,14 @@ dojo.declare("dijit._HasDropDown",
// summary:
// Called magically when focus has shifted away from this widget and it's dropdown
- this.closeDropDown();
- // don't focus on button. the user has explicitly focused on something else.
+ // Don't focus on button if the user has explicitly focused on something else (happens
+ // when user clicks another control causing the current popup to close)..
+ // But if focus is inside of the drop down then reset focus to me, because IE doesn't like
+ // it when you display:none a node with focus.
+ var focusMe = dijit._curFocus && this.dropDown && dojo.isDescendant(dijit._curFocus, this.dropDown.domNode);
+
+ this.closeDropDown(focusMe);
+
this.inherited(arguments);
},
@@ -10897,7 +11584,8 @@ dojo.declare("dijit._HasDropDown",
loadDropDown: function(/* Function */ loadCallback){
// summary:
// Loads the data for the dropdown, and at some point, calls
- // the given callback
+ // the given callback. This is basically a callback when the
+ // user presses the down arrow button to open the drop down.
// tags:
// protected
@@ -10906,14 +11594,13 @@ dojo.declare("dijit._HasDropDown",
toggleDropDown: function(){
// summary:
+ // Callback when the user presses the down arrow button or presses
+ // the down arrow key to open/close the drop down.
// Toggle the drop-down widget; if it is up, close it, if not, open it
// tags:
// protected
if(this.disabled || this.readOnly){ return; }
- this.focus();
- var dropDown = this.dropDown;
- if(!dropDown){ return; }
if(!this._opened){
// If we aren't loaded, load it first so there isn't a flicker
if(!this.isLoaded()){
@@ -10929,14 +11616,17 @@ dojo.declare("dijit._HasDropDown",
openDropDown: function(){
// summary:
- // Opens the dropdown for this widget - it returns the
- // return value of dijit.popup.open
+ // Opens the dropdown for this widget. To be called only when this.dropDown
+ // has been created and is ready to display (ie, it's data is loaded).
+ // returns:
+ // return value of dijit.popup.open()
// tags:
// protected
- var dropDown = this.dropDown;
- var ddNode = dropDown.domNode;
- var self = this;
+ var dropDown = this.dropDown,
+ ddNode = dropDown.domNode,
+ aroundNode = this._aroundNode || this.domNode,
+ self = this;
// Prepare our popup's height and honor maxHeight if it exists.
@@ -10944,8 +11634,7 @@ dojo.declare("dijit._HasDropDown",
// ie, dependent on how much space is available (BK)
if(!this._preparedNode){
- dijit.popup.moveOffScreen(ddNode);
- this._preparedNode = true;
+ this._preparedNode = true;
// Check if we have explicitly set width and height on the dropdown widget dom node
if(ddNode.style.width){
this._explicitDDWidth = true;
@@ -10969,29 +11658,44 @@ dojo.declare("dijit._HasDropDown",
}
dojo.style(ddNode, myStyle);
+ // Figure out maximum height allowed (if there is a height restriction)
+ var maxHeight = this.maxHeight;
+ if(maxHeight == -1){
+ // limit height to space available in viewport either above or below my domNode
+ // (whichever side has more room)
+ var viewport = dojo.window.getBox(),
+ position = dojo.position(aroundNode, false);
+ maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h)));
+ }
+
+ // Attach dropDown to DOM and make make visibility:hidden rather than display:none
+ // so we call startup() and also get the size
+ if(dropDown.startup && !dropDown._started){
+ dropDown.startup();
+ }
+
+ dijit.popup.moveOffScreen(dropDown);
// Get size of drop down, and determine if vertical scroll bar needed
- var mb = dojo.marginBox(ddNode);
- var overHeight = (this.maxHeight && mb.h > this.maxHeight);
+ var mb = dojo._getMarginSize(ddNode);
+ var overHeight = (maxHeight && mb.h > maxHeight);
dojo.style(ddNode, {
overflowX: "hidden",
overflowY: overHeight ? "auto" : "hidden"
});
if(overHeight){
- mb.h = this.maxHeight;
+ mb.h = maxHeight;
if("w" in mb){
mb.w += 16; // room for vertical scrollbar
}
}else{
delete mb.h;
}
- delete mb.t;
- delete mb.l;
// Adjust dropdown width to match or be larger than my width
if(this.forceWidth){
- mb.w = this.domNode.offsetWidth;
+ mb.w = aroundNode.offsetWidth;
}else if(this.autoWidth){
- mb.w = Math.max(mb.w, this.domNode.offsetWidth);
+ mb.w = Math.max(mb.w, aroundNode.offsetWidth);
}else{
delete mb.w;
}
@@ -11007,7 +11711,7 @@ dojo.declare("dijit._HasDropDown",
var retVal = dijit.popup.open({
parent: this,
popup: dropDown,
- around: this._aroundNode,
+ around: aroundNode,
orient: dijit.getPopupAroundAlignment((this.dropDownPosition && this.dropDownPosition.length) ? this.dropDownPosition : ["below"],this.isLeftToRight()),
onExecute: function(){
self.closeDropDown(true);
@@ -11019,13 +11723,12 @@ dojo.declare("dijit._HasDropDown",
dojo.attr(self._popupStateNode, "popupActive", false);
dojo.removeClass(self._popupStateNode, "dijitHasDropDownOpen");
self._opened = false;
- self.state = "";
}
});
dojo.attr(this._popupStateNode, "popupActive", "true");
dojo.addClass(self._popupStateNode, "dijitHasDropDownOpen");
this._opened=true;
- this.state="Opened";
+
// TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown
return retVal;
},
@@ -11033,6 +11736,8 @@ dojo.declare("dijit._HasDropDown",
closeDropDown: function(/*Boolean*/ focus){
// summary:
// Closes the drop down on this widget
+ // focus:
+ // If true, refocuses the button widget
// tags:
// protected
@@ -11040,7 +11745,6 @@ dojo.declare("dijit._HasDropDown",
if(focus){ this.focus(); }
dijit.popup.close(this.dropDown);
this._opened = false;
- this.state = "";
}
}
@@ -11099,14 +11803,12 @@ dojo.declare("dijit.form.Button",
baseClass: "dijitButton",
- templateString: dojo.cache("dijit.form", "templates/Button.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode,focusNode\"\n\t\t\twaiRole=\"button\" waiState=\"labelledby-${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">&#x25CF;</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdojoAttachPoint=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),
+ templateString: dojo.cache("dijit.form", "templates/Button.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode,focusNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">&#x25CF;</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdojoAttachPoint=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),
attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
- value: "valueNode",
- iconClass: { node: "iconNode", type: "class" }
+ value: "valueNode"
}),
-
_onClick: function(/*Event*/ e){
// summary:
// Internal function to handle click actions
@@ -11136,25 +11838,26 @@ dojo.declare("dijit.form.Button",
}
},
+ buildRendering: function(){
+ this.inherited(arguments);
+ dojo.setSelectable(this.focusNode, false);
+ },
+
_fillContent: function(/*DomNode*/ source){
// Overrides _Templated._fillContent().
// If button label is specified as srcNodeRef.innerHTML rather than
// this.params.label, handle it here.
+ // TODO: remove the method in 2.0, parser will do it all for me
if(source && (!this.params || !("label" in this.params))){
this.set('label', source.innerHTML);
}
},
- postCreate: function(){
- dojo.setSelectable(this.focusNode, false);
- this.inherited(arguments);
- },
-
_setShowLabelAttr: function(val){
if(this.containerNode){
dojo.toggleClass(this.containerNode, "dijitDisplayNone", !val);
}
- this.showLabel = val;
+ this._set("showLabel", val);
},
onClick: function(/*Event*/ e){
@@ -11180,13 +11883,24 @@ dojo.declare("dijit.form.Button",
_setLabelAttr: function(/*String*/ content){
// summary:
- // Hook for attr('label', ...) to work.
+ // Hook for set('label', ...) to work.
// description:
// Set the label (text) of the button; takes an HTML string.
- this.containerNode.innerHTML = this.label = content;
+ this._set("label", content);
+ this.containerNode.innerHTML = content;
if(this.showLabel == false && !this.params.title){
this.titleNode.title = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || '');
}
+ },
+
+ _setIconClassAttr: function(/*String*/ val){
+ // Custom method so that icon node is hidden when not in use, to avoid excess padding/margin
+ // appearing around it (even if it's a 0x0 sized <img> node)
+
+ var oldVal = this.iconClass || "dijitNoIcon",
+ newVal = val || "dijitNoIcon";
+ dojo.replaceClass(this.iconNode, newVal, oldVal);
+ this._set("iconClass", val);
}
});
@@ -11207,7 +11921,7 @@ dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container,
baseClass : "dijitDropDownButton",
- templateString: dojo.cache("dijit.form", "templates/DropDownButton.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\" dojoAttachPoint=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"focusNode,titleNode,_arrowWrapperNode\"\n\t\t\twaiRole=\"button\" waiState=\"haspopup-true,labelledby-${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\"\n\t\t\t\tdojoAttachPoint=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdojoAttachPoint=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">&#9660;</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),
+ templateString: dojo.cache("dijit.form", "templates/DropDownButton.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\" dojoAttachPoint=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"focusNode,titleNode,_arrowWrapperNode\"\n\t\t\trole=\"button\" aria-haspopup=\"true\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\"\n\t\t\t\tdojoAttachPoint=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdojoAttachPoint=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">&#9660;</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),
_fillContent: function(){
// Overrides Button._fillContent().
@@ -11232,12 +11946,14 @@ dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container,
// the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM,
// make it invisible, and store a reference to pass to the popup code.
- if(!this.dropDown){
+ if(!this.dropDown && this.dropDownContainer){
var dropDownNode = dojo.query("[widgetId]", this.dropDownContainer)[0];
this.dropDown = dijit.byNode(dropDownNode);
delete this.dropDownContainer;
}
- dijit.popup.moveOffScreen(this.dropDown.domNode);
+ if(this.dropDown){
+ dijit.popup.hide(this.dropDown);
+ }
this.inherited(arguments);
},
@@ -11246,7 +11962,7 @@ dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container,
// Returns whether or not we are loaded - if our dropdown has an href,
// then we want to check that.
var dropDown = this.dropDown;
- return (!dropDown.href || dropDown.isLoaded);
+ return (!!dropDown && (!dropDown.href || dropDown.isLoaded));
},
loadDropDown: function(){
@@ -11288,7 +12004,7 @@ dojo.declare("dijit.form.ComboButton", dijit.form.DropDownButton, {
// | dojo.body().appendChild(button1.domNode);
//
- templateString: dojo.cache("dijit.form", "templates/ComboButton.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tcellspacing='0' cellpadding='0' waiRole=\"presentation\"\n\t><tbody waiRole=\"presentation\"><tr waiRole=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonNode\" dojoAttachPoint=\"buttonNode\" dojoAttachEvent=\"ondijitclick:_onButtonClick,onkeypress:_onButtonKeyPress\"\n\t\t><div id=\"${id}_button\" class=\"dijitReset dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode\"\n\t\t\twaiRole=\"button\" waiState=\"labelledby-${id}_label\"\n\t\t\t><div class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitInline dijitButtonText\" id=\"${id}_label\" dojoAttachPoint=\"containerNode\" waiRole=\"presentation\"></div\n\t\t></div\n\t\t></td\n\t\t><td id=\"${id}_arrow\" class='dijitReset dijitRight dijitButtonNode dijitArrowButton'\n\t\t\tdojoAttachPoint=\"_popupStateNode,focusNode,_buttonNode\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onArrowKeyPress\"\n\t\t\ttitle=\"${optionsTitle}\"\n\t\t\twaiRole=\"button\" waiState=\"haspopup-true\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" waiRole=\"presentation\">&#9660;</div\n\t\t></td\n\t\t><td style=\"display:none !important;\"\n\t\t\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" dojoAttachPoint=\"valueNode\"\n\t\t/></td></tr></tbody\n></table>\n"),
+ templateString: dojo.cache("dijit.form", "templates/ComboButton.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tcellspacing='0' cellpadding='0' role=\"presentation\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonNode\" dojoAttachPoint=\"buttonNode\" dojoAttachEvent=\"ondijitclick:_onButtonClick,onkeypress:_onButtonKeyPress\"\n\t\t><div id=\"${id}_button\" class=\"dijitReset dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><div class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitInline dijitButtonText\" id=\"${id}_label\" dojoAttachPoint=\"containerNode\" role=\"presentation\"></div\n\t\t></div\n\t\t></td\n\t\t><td id=\"${id}_arrow\" class='dijitReset dijitRight dijitButtonNode dijitArrowButton'\n\t\t\tdojoAttachPoint=\"_popupStateNode,focusNode,_buttonNode\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onArrowKeyPress\"\n\t\t\ttitle=\"${optionsTitle}\"\n\t\t\trole=\"button\" aria-haspopup=\"true\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">&#9660;</div\n\t\t></td\n\t\t><td style=\"display:none !important;\"\n\t\t\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" dojoAttachPoint=\"valueNode\"\n\t\t/></td></tr></tbody\n></table>\n"),
attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), {
id: "",
@@ -11336,8 +12052,9 @@ dojo.declare("dijit.form.ComboButton", dijit.form.DropDownButton, {
// otherwise on arrow node
// position:
// "start" or "end"
-
- dijit.focus(position == "start" ? this.titleNode : this._popupStateNode);
+ if(!this.disabled){
+ dijit.focus(position == "start" ? this.titleNode : this._popupStateNode);
+ }
}
});
@@ -11363,8 +12080,8 @@ dojo.declare("dijit.form.ToggleButton", dijit.form.Button, {
this.set('checked', !this.checked);
},
- _setCheckedAttr: function(/*Boolean*/ value, /* Boolean? */ priorityChange){
- this.checked = value;
+ _setCheckedAttr: function(/*Boolean*/ value, /*Boolean?*/ priorityChange){
+ this._set("checked", value);
dojo.attr(this.focusNode || this.domNode, "checked", value);
dijit.setWaiState(this.focusNode || this.domNode, "pressed", value);
this._handleOnChange(value, priorityChange);
@@ -11372,7 +12089,7 @@ dojo.declare("dijit.form.ToggleButton", dijit.form.Button, {
setChecked: function(/*Boolean*/ checked){
// summary:
- // Deprecated. Use set('checked', true/false) instead.
+ // Deprecated. Use set('checked', true/false) instead.
dojo.deprecated("setChecked("+checked+") is deprecated. Use set('checked',"+checked+") instead.", "", "2.0");
this.set('checked', checked);
},
@@ -11395,6 +12112,8 @@ dojo._hasResource["dijit.form.ToggleButton"] = true;
dojo.provide("dijit.form.ToggleButton");
+
+
}
if(!dojo._hasResource["dijit.form.CheckBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -11424,26 +12143,26 @@ dojo.declare(
// In case 2, the regular html inputs are invisible but still used by
// the user. They are turned quasi-invisible and overlay the background-image.
- templateString: dojo.cache("dijit.form", "templates/CheckBox.html", "<div class=\"dijit dijitReset dijitInline\" waiRole=\"presentation\"\n\t><input\n\t \t${!nameAttrSetting} type=\"${type}\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdojoAttachPoint=\"focusNode\"\n\t \tdojoAttachEvent=\"onclick:_onClick\"\n/></div>\n"),
+ templateString: dojo.cache("dijit.form", "templates/CheckBox.html", "<div class=\"dijit dijitReset dijitInline\" role=\"presentation\"\n\t><input\n\t \t${!nameAttrSetting} type=\"${type}\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdojoAttachPoint=\"focusNode\"\n\t \tdojoAttachEvent=\"onclick:_onClick\"\n/></div>\n"),
baseClass: "dijitCheckBox",
// type: [private] String
// type attribute on <input> node.
- // Overrides `dijit.form.Button.type`. Users should not change this value.
+ // Overrides `dijit.form.Button.type`. Users should not change this value.
type: "checkbox",
// value: String
// As an initialization parameter, equivalent to value field on normal checkbox
// (if checked, the value is passed as the value when form is submitted).
//
- // However, attr('value') will return either the string or false depending on
+ // However, get('value') will return either the string or false depending on
// whether or not the checkbox is checked.
//
- // attr('value', string) will check the checkbox and change the value to the
+ // set('value', string) will check the checkbox and change the value to the
// specified string
//
- // attr('value', boolean) will change the checked state.
+ // set('value', boolean) will change the checked state.
value: "on",
// readOnly: Boolean
@@ -11452,22 +12171,22 @@ dojo.declare(
// Similar to disabled except readOnly form values are submitted.
readOnly: false,
- // the attributeMap should inherit from dijit.form._FormWidget.prototype.attributeMap
+ // the attributeMap should inherit from dijit.form._FormWidget.prototype.attributeMap
// instead of ToggleButton as the icon mapping has no meaning for a CheckBox
attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
readOnly: "focusNode"
}),
_setReadOnlyAttr: function(/*Boolean*/ value){
- this.readOnly = value;
+ this._set("readOnly", value);
dojo.attr(this.focusNode, 'readOnly', value);
dijit.setWaiState(this.focusNode, "readonly", value);
},
- _setValueAttr: function(/*String or Boolean*/ newValue, /*Boolean*/ priorityChange){
+ _setValueAttr: function(/*String|Boolean*/ newValue, /*Boolean*/ priorityChange){
// summary:
// Handler for value= attribute to constructor, and also calls to
- // attr('value', val).
+ // set('value', val).
// description:
// During initialization, just saves as attribute to the <input type=checkbox>.
//
@@ -11477,7 +12196,7 @@ dojo.declare(
// specified as "value" when the CheckBox was constructed (ex: <input
// dojoType="dijit.CheckBox" value="chicken">)
if(typeof newValue == "string"){
- this.value = newValue;
+ this._set("value", newValue);
dojo.attr(this.focusNode, 'value', newValue);
newValue = true;
}
@@ -11487,7 +12206,7 @@ dojo.declare(
},
_getValueAttr: function(){
// summary:
- // Hook so attr('value') works.
+ // Hook so get('value') works.
// description:
// If the CheckBox is checked, returns the value attribute.
// Otherwise returns false.
@@ -11524,7 +12243,7 @@ dojo.declare(
this.set('checked', this.params.checked || false);
// Handle unlikely event that the <input type=checkbox> value attribute has changed
- this.value = this.params.value || "on";
+ this._set("value", this.params.value || "on");
dojo.attr(this.focusNode, 'value', this.value);
},
@@ -11547,6 +12266,7 @@ dojo.declare(
// Internal function to handle click actions - need to check
// readOnly, since button no longer does that check.
if(this.readOnly){
+ dojo.stopEvent(e);
return false;
}
return this.inherited(arguments);
@@ -11600,12 +12320,15 @@ dojo.provide("dijit.form.DropDownButton");
+
}
if(!dojo._hasResource["dojo.regexp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.regexp"] = true;
dojo.provide("dojo.regexp");
+dojo.getObject("regexp", true, dojo);
+
/*=====
dojo.regexp = {
// summary: Regular expressions and Builder resources
@@ -11624,7 +12347,7 @@ dojo.regexp.escapeString = function(/*String*/str, /*String?*/except){
}
return "\\" + ch;
}); // String
-}
+};
dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boolean?*/nonCapture){
// summary:
@@ -11639,7 +12362,7 @@ dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boole
// A single value or an array of values.
// re:
// A function. Takes one parameter and converts it to a regular
- // expression.
+ // expression.
// nonCapture:
// If true, uses non-capturing match, otherwise matches are retained
// by regular expression. Defaults to false
@@ -11658,16 +12381,16 @@ dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boole
// join the REs as alternatives in a RE group.
return dojo.regexp.group(b.join("|"), nonCapture); // String
-}
+};
dojo.regexp.group = function(/*String*/expression, /*Boolean?*/nonCapture){
// summary:
// adds group match to expression
// nonCapture:
// If true, uses non-capturing match, otherwise matches are retained
- // by regular expression.
+ // by regular expression.
return "(" + (nonCapture ? "?:":"") + expression + ")"; // String
-}
+};
}
@@ -11675,11 +12398,13 @@ if(!dojo._hasResource["dojo.data.util.sorter"]){ //_hasResource checks added by
dojo._hasResource["dojo.data.util.sorter"] = true;
dojo.provide("dojo.data.util.sorter");
-dojo.data.util.sorter.basicComparator = function( /*anything*/ a,
+dojo.getObject("data.util.sorter", true, dojo);
+
+dojo.data.util.sorter.basicComparator = function( /*anything*/ a,
/*anything*/ b){
- // summary:
+ // summary:
// Basic comparision function that compares if an item is greater or less than another item
- // description:
+ // description:
// returns 1 if a > b, -1 if a < b, 0 if equal.
// 'null' values (null, undefined) are treated as larger values so that they're pushed to the end of the list.
// And compared to each other, null is equivalent to undefined.
@@ -11696,18 +12421,18 @@ dojo.data.util.sorter.basicComparator = function( /*anything*/ a,
b = undefined;
}
if(a == b){
- r = 0;
+ r = 0;
}else if(a > b || a == null){
- r = 1;
+ r = 1;
}
return r; //int {-1,0,1}
};
dojo.data.util.sorter.createSortFunction = function( /* attributes array */sortSpec,
/*dojo.data.core.Read*/ store){
- // summary:
+ // summary:
// Helper function to generate the sorting function based off the list of sort attributes.
- // description:
+ // description:
// The sort function creation will look for a property on the store called 'comparatorMap'. If it exists
// it will look in the mapping for comparisons function for the attributes. If one is found, it will
// use it instead of the basic comparator, which is typically used for strings, ints, booleans, and dates.
@@ -11749,7 +12474,7 @@ dojo.data.util.sorter.createSortFunction = function( /* attributes array */sortS
}
comp = map[attr] || bc;
}
- sortFunctions.push(createSortFunction(attr,
+ sortFunctions.push(createSortFunction(attr,
dir, comp, store));
}
}
@@ -11761,7 +12486,7 @@ dojo.data.util.sorter.createSortFunction = function( /* attributes array */sortS
return ret;//int
}
}
- return 0; //int
+ return 0; //int
}; // Function
};
@@ -11772,30 +12497,32 @@ dojo._hasResource["dojo.data.util.simpleFetch"] = true;
dojo.provide("dojo.data.util.simpleFetch");
+dojo.getObject("data.util.simpleFetch", true, dojo);
+
dojo.data.util.simpleFetch.fetch = function(/* Object? */ request){
// summary:
// The simpleFetch mixin is designed to serve as a set of function(s) that can
- // be mixed into other datastore implementations to accelerate their development.
- // The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems()
+ // be mixed into other datastore implementations to accelerate their development.
+ // The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems()
// call by returning an array of all the found items that matched the query. The simpleFetch mixin
// is not designed to work for datastores that respond to a fetch() call by incrementally
// loading items, or sequentially loading partial batches of the result
- // set. For datastores that mixin simpleFetch, simpleFetch
+ // set. For datastores that mixin simpleFetch, simpleFetch
// implements a fetch method that automatically handles eight of the fetch()
// arguments -- onBegin, onItem, onComplete, onError, start, count, sort and scope
// The class mixing in simpleFetch should not implement fetch(),
- // but should instead implement a _fetchItems() method. The _fetchItems()
- // method takes three arguments, the keywordArgs object that was passed
+ // but should instead implement a _fetchItems() method. The _fetchItems()
+ // method takes three arguments, the keywordArgs object that was passed
// to fetch(), a callback function to be called when the result array is
// available, and an error callback to be called if something goes wrong.
// The _fetchItems() method should ignore any keywordArgs parameters for
- // start, count, onBegin, onItem, onComplete, onError, sort, and scope.
+ // start, count, onBegin, onItem, onComplete, onError, sort, and scope.
// The _fetchItems() method needs to correctly handle any other keywordArgs
- // parameters, including the query parameter and any optional parameters
- // (such as includeChildren). The _fetchItems() method should create an array of
- // result items and pass it to the fetchHandler along with the original request object
- // -- or, the _fetchItems() method may, if it wants to, create an new request object
- // with other specifics about the request that are specific to the datastore and pass
+ // parameters, including the query parameter and any optional parameters
+ // (such as includeChildren). The _fetchItems() method should create an array of
+ // result items and pass it to the fetchHandler along with the original request object
+ // -- or, the _fetchItems() method may, if it wants to, create an new request object
+ // with other specifics about the request that are specific to the datastore and pass
// that as the request object to the handler.
//
// For more information on this specific function, see dojo.data.api.Read.fetch()
@@ -11862,12 +12589,14 @@ if(!dojo._hasResource["dojo.data.util.filter"]){ //_hasResource checks added by
dojo._hasResource["dojo.data.util.filter"] = true;
dojo.provide("dojo.data.util.filter");
+dojo.getObject("data.util.filter", true, dojo);
+
dojo.data.util.filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/ ignoreCase){
- // summary:
+ // summary:
// Helper function to convert a simple pattern to a regular expression for matching.
// description:
// Returns a regular expression object that conforms to the defined conversion rules.
- // For example:
+ // For example:
// ca* -> /^ca.*$/
// *ca* -> /^.*ca.*$/
// *c\*a* -> /^.*c\*a.*$/
@@ -11879,7 +12608,7 @@ dojo.data.util.filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/
// * Means match anything, so ca* means match anything starting with ca
// ? Means match single character. So, b?b will match to bob and bab, and so on.
// \ is an escape character. So for example, \* means do not treat * as a match, but literal character *.
- // To use a \ as a character in the string, it must be escaped. So in the pattern it should be
+ // To use a \ as a character in the string, it must be escaped. So in the pattern it should be
// represented by \\ to be treated as an ordinary \ character instead of an escape.
//
// ignoreCase:
@@ -11957,20 +12686,20 @@ dojo.declare(
// Converts the first character of each word to uppercase if true.
propercase: false,
- // maxLength: String
+ // maxLength: String
// HTML INPUT tag maxLength declaration.
maxLength: "",
- // selectOnClick: [const] Boolean
+ // selectOnClick: [const] Boolean
// If true, all text will be selected when focused with mouse
selectOnClick: false,
- // placeHolder: String
+ // placeHolder: String
// Defines a hint to help users fill out the input field (as defined in HTML 5).
// This should only contain plain text (no html markup).
placeHolder: "",
- templateString: dojo.cache("dijit.form", "templates/TextBox.html", "<div class=\"dijit dijitReset dijitInline dijitLeft\" id=\"widget_${id}\" waiRole=\"presentation\"\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
+ templateString: dojo.cache("dijit.form", "templates/TextBox.html", "<div class=\"dijit dijitReset dijitInline dijitLeft\" id=\"widget_${id}\" role=\"presentation\"\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
_singleNodeTemplate: '<input class="dijit dijitReset dijitLeft dijitInputField" dojoAttachPoint="textbox,focusNode" autocomplete="off" type="${type}" ${!nameAttrSetting} />',
_buttonInputDisabled: dojo.isIE ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events
@@ -11983,14 +12712,14 @@ dojo.declare(
postMixInProperties: function(){
var type = this.type.toLowerCase();
- if(this.templateString.toLowerCase() == "input" || ((type == "hidden" || type == "file") && this.templateString == dijit.form.TextBox.prototype.templateString)){
+ if(this.templateString && this.templateString.toLowerCase() == "input" || ((type == "hidden" || type == "file") && this.templateString == dijit.form.TextBox.prototype.templateString)){
this.templateString = this._singleNodeTemplate;
}
this.inherited(arguments);
},
_setPlaceHolderAttr: function(v){
- this.placeHolder = v;
+ this._set("placeHolder", v);
if(!this._phspan){
this._attachPoints.push('_phspan');
/* dijitInputField class gives placeHolder same padding as the input field
@@ -12013,7 +12742,7 @@ dojo.declare(
_getValueAttr: function(){
// summary:
- // Hook so attr('value') works as we like.
+ // Hook so get('value') works as we like.
// description:
// For `dijit.form.TextBox` this basically returns the value of the <input>.
//
@@ -12026,7 +12755,7 @@ dojo.declare(
_setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
// summary:
- // Hook so attr('value', ...) works.
+ // Hook so set('value', ...) works.
//
// description:
// Sets the value of the widget to "value" which can be of
@@ -12057,6 +12786,7 @@ dojo.declare(
}
if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){
this.textbox.value = formattedValue;
+ this._set("displayedValue", this.get("displayedValue"));
}
this._updatePlaceHolder();
@@ -12069,7 +12799,7 @@ dojo.declare(
// (ex: Kentucky) and the serialized value (ex: KY) are different,
// this represents the displayed value.
//
- // Setting 'displayedValue' through attr('displayedValue', ...)
+ // Setting 'displayedValue' through set('displayedValue', ...)
// updates 'value', and vice-versa. Otherwise 'value' is updated
// from 'displayedValue' periodically, like onBlur etc.
//
@@ -12080,7 +12810,7 @@ dojo.declare(
getDisplayedValue: function(){
// summary:
- // Deprecated. Use set('displayedValue') instead.
+ // Deprecated. Use get('displayedValue') instead.
// tags:
// deprecated
dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use set('displayedValue') instead.", "", "2.0");
@@ -12089,7 +12819,7 @@ dojo.declare(
_getDisplayedValueAttr: function(){
// summary:
- // Hook so attr('displayedValue') works.
+ // Hook so get('displayedValue') works.
// description:
// Returns the displayed value (what the user sees on the screen),
// after filtering (ie, trimming spaces etc.).
@@ -12098,21 +12828,24 @@ dojo.declare(
// is different from the serialized value that's actually
// sent to the server (see dijit.form.ValidationTextBox.serialize)
+ // TODO: maybe we should update this.displayedValue on every keystroke so that we don't need
+ // this method
+ // TODO: this isn't really the displayed value when the user is typing
return this.filter(this.textbox.value);
},
- setDisplayedValue: function(/*String*/value){
+ setDisplayedValue: function(/*String*/ value){
// summary:
- // Deprecated. Use set('displayedValue', ...) instead.
+ // Deprecated. Use set('displayedValue', ...) instead.
// tags:
// deprecated
dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use set('displayedValue', ...) instead.", "", "2.0");
this.set('displayedValue', value);
},
- _setDisplayedValueAttr: function(/*String*/value){
+ _setDisplayedValueAttr: function(/*String*/ value){
// summary:
- // Hook so attr('displayedValue', ...) works.
+ // Hook so set('displayedValue', ...) works.
// description:
// Sets the value of the visual element to the string "value".
// The widget value is also set to a corresponding,
@@ -12120,11 +12853,18 @@ dojo.declare(
if(value === null || value === undefined){ value = '' }
else if(typeof value != "string"){ value = String(value) }
+
this.textbox.value = value;
- this._setValueAttr(this.get('value'), undefined, value);
+
+ // sets the serialized value to something corresponding to specified displayedValue
+ // (if possible), and also updates the textbox.value, for example converting "123"
+ // to "123.00"
+ this._setValueAttr(this.get('value'), undefined);
+
+ this._set("displayedValue", this.get('displayedValue'));
},
- format: function(/* String */ value, /* Object */ constraints){
+ format: function(/*String*/ value, /*Object*/ constraints){
// summary:
// Replacable function to convert a value to a properly formatted string.
// tags:
@@ -12132,7 +12872,7 @@ dojo.declare(
return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
},
- parse: function(/* String */ value, /* Object */ constraints){
+ parse: function(/*String*/ value, /*Object*/ constraints){
// summary:
// Replacable function to convert a formatted string to a value
// tags:
@@ -12166,12 +12906,15 @@ dojo.declare(
setTimeout(function(){ _this._handleOnChange(_this.get('value'), false); }, 0);
}
this._refreshState();
+
+ // In case someone is watch()'ing for changes to displayedValue
+ this._set("displayedValue", this.get("displayedValue"));
},
postCreate: function(){
- // setting the value here is needed since value="" in the template causes "undefined"
- // and setting in the DOM (instead of the JS object) helps with form reset actions
if(dojo.isIE){ // IE INPUT tag fontFamily has to be set directly using STYLE
+ // the setTimeout gives IE a chance to render the TextBox and to deal with font inheritance
+ setTimeout(dojo.hitch(this, function(){
var s = dojo.getComputedStyle(this.domNode);
if(s){
var ff = s.fontFamily;
@@ -12184,16 +12927,22 @@ dojo.declare(
}
}
}
+ }), 0);
}
- this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values shuld be the same
+
+ // setting the value here is needed since value="" in the template causes "undefined"
+ // and setting in the DOM (instead of the JS object) helps with form reset actions
+ this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values should be the same
+
this.inherited(arguments);
+
if(dojo.isMoz || dojo.isOpera){
- this.connect(this.textbox, "oninput", this._onInput);
+ this.connect(this.textbox, "oninput", "_onInput");
}else{
- this.connect(this.textbox, "onkeydown", this._onInput);
- this.connect(this.textbox, "onkeyup", this._onInput);
- this.connect(this.textbox, "onpaste", this._onInput);
- this.connect(this.textbox, "oncut", this._onInput);
+ this.connect(this.textbox, "onkeydown", "_onInput");
+ this.connect(this.textbox, "onkeyup", "_onInput");
+ this.connect(this.textbox, "onpaste", "_onInput");
+ this.connect(this.textbox, "oncut", "_onInput");
}
},
@@ -12205,8 +12954,8 @@ dojo.declare(
// description:
// For MappedTextBox subclasses, this is called twice
// - once with the display value
- // - once the value as set/returned by attr('value', ...)
- // and attr('value'), ex: a Number for NumberTextBox.
+ // - once the value as set/returned by set('value', ...)
+ // and get('value'), ex: a Number for NumberTextBox.
//
// In the latter case it does corrections like converting null to NaN. In
// the former case the NumberTextBox.filter() method calls this.inherited()
@@ -12284,8 +13033,11 @@ dojo.declare(
this._updatePlaceHolder();
- this._refreshState();
+ // call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport
+ // (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip
this.inherited(arguments);
+
+ this._refreshState();
},
reset: function(){
@@ -12297,7 +13049,7 @@ dojo.declare(
}
);
-dijit.selectInputText = function(/*DomNode*/element, /*Number?*/ start, /*Number?*/ stop){
+dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
// summary:
// Select text in the input element argument, from start (default 0), to stop (default end).
@@ -12310,14 +13062,12 @@ dijit.selectInputText = function(/*DomNode*/element, /*Number?*/ start, /*Number
dijit.focus(element);
if(_document["selection"] && dojo.body()["createTextRange"]){ // IE
if(element.createTextRange){
- var range = element.createTextRange();
- with(range){
- collapse(true);
- moveStart("character", -99999); // move to 0
- moveStart("character", start); // delta from 0 is the correct position
- moveEnd("character", stop-start);
- select();
- }
+ var r = element.createTextRange();
+ r.collapse(true);
+ r.moveStart("character", -99999); // move to 0
+ r.moveStart("character", start); // delta from 0 is the correct position
+ r.moveEnd("character", stop-start);
+ r.select();
}
}else if(_window["getSelection"]){
if(element.setSelectionRange){
@@ -12351,7 +13101,7 @@ dojo.declare(
// Milliseconds to fade in/fade out
duration: dijit.defaultDuration,
- templateString: dojo.cache("dijit", "templates/Tooltip.html", "<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\">\n\t<div class=\"dijitTooltipContainer dijitTooltipContents\" dojoAttachPoint=\"containerNode\" waiRole='alert'></div>\n\t<div class=\"dijitTooltipConnector\"></div>\n</div>\n"),
+ templateString: dojo.cache("dijit", "templates/Tooltip.html", "<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\"\n\t><div class=\"dijitTooltipContainer dijitTooltipContents\" dojoAttachPoint=\"containerNode\" role='alert'></div\n\t><div class=\"dijitTooltipConnector\" dojoAttachPoint=\"connectorNode\"></div\n></div>\n"),
postCreate: function(){
dojo.body().appendChild(this.domNode);
@@ -12361,7 +13111,6 @@ dojo.declare(
// Setup fade-in and fade-out functions.
this.fadeIn = dojo.fadeIn({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onShow") });
this.fadeOut = dojo.fadeOut({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onHide") });
-
},
show: function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
@@ -12373,6 +13122,9 @@ dojo.declare(
return;
}
+ // reset width; it may have been set by orient() on a previous tooltip show()
+ this.domNode.width = "auto";
+
if(this.fadeOut.status() == "playing"){
// previous tooltip is being hidden; wait until the hide completes then show new one
this._onDeck=arguments;
@@ -12389,12 +13141,17 @@ dojo.declare(
this.aroundNode = aroundNode;
},
- orient: function(/* DomNode */ node, /* String */ aroundCorner, /* String */ tooltipCorner){
+ orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ tooltipCorner, /*Object*/ spaceAvailable, /*Object*/ aroundNodeCoords){
// summary:
// Private function to set CSS for tooltip node based on which position it's in.
- // This is called by the dijit popup code.
+ // This is called by the dijit popup code. It will also reduce the tooltip's
+ // width to whatever width is available
// tags:
// protected
+ this.connectorNode.style.top = ""; //reset to default
+
+ //Adjust the spaceAvailable width, without changing the spaceAvailable object
+ var tooltipSpaceAvaliableWidth = spaceAvailable.w - this.connectorNode.offsetWidth;
node.className = "dijitTooltip " +
{
@@ -12405,6 +13162,52 @@ dojo.declare(
"BR-BL": "dijitTooltipRight",
"BL-BR": "dijitTooltipLeft"
}[aroundCorner + "-" + tooltipCorner];
+
+ // reduce tooltip's width to the amount of width available, so that it doesn't overflow screen
+ this.domNode.style.width = "auto";
+ var size = dojo.contentBox(this.domNode);
+
+ var width = Math.min((Math.max(tooltipSpaceAvaliableWidth,1)), size.w);
+ var widthWasReduced = width < size.w;
+
+ this.domNode.style.width = width+"px";
+
+ //Adjust width for tooltips that have a really long word or a nowrap setting
+ if(widthWasReduced){
+ this.containerNode.style.overflow = "auto"; //temp change to overflow to detect if our tooltip needs to be wider to support the content
+ var scrollWidth = this.containerNode.scrollWidth;
+ this.containerNode.style.overflow = "visible"; //change it back
+ if(scrollWidth > width){
+ scrollWidth = scrollWidth + dojo.style(this.domNode,"paddingLeft") + dojo.style(this.domNode,"paddingRight");
+ this.domNode.style.width = scrollWidth + "px";
+ }
+ }
+
+ // Reposition the tooltip connector.
+ if(tooltipCorner.charAt(0) == 'B' && aroundCorner.charAt(0) == 'B'){
+ var mb = dojo.marginBox(node);
+ var tooltipConnectorHeight = this.connectorNode.offsetHeight;
+ if(mb.h > spaceAvailable.h){
+ // The tooltip starts at the top of the page and will extend past the aroundNode
+ var aroundNodePlacement = spaceAvailable.h - (aroundNodeCoords.h / 2) - (tooltipConnectorHeight / 2);
+ this.connectorNode.style.top = aroundNodePlacement + "px";
+ this.connectorNode.style.bottom = "";
+ }else{
+ // Align center of connector with center of aroundNode, except don't let bottom
+ // of connector extend below bottom of tooltip content, or top of connector
+ // extend past top of tooltip content
+ this.connectorNode.style.bottom = Math.min(
+ Math.max(aroundNodeCoords.h/2 - tooltipConnectorHeight/2, 0),
+ mb.h - tooltipConnectorHeight) + "px";
+ this.connectorNode.style.top = "";
+ }
+ }else{
+ // reset the tooltip back to the defaults
+ this.connectorNode.style.top = "";
+ this.connectorNode.style.bottom = "";
+ }
+
+ return Math.max(0, size.w - tooltipSpaceAvaliableWidth);
},
_onShow: function(){
@@ -12421,6 +13224,7 @@ dojo.declare(
hide: function(aroundNode){
// summary:
// Hide the tooltip
+
if(this._onDeck && this._onDeck[1] == aroundNode){
// this hide request is for a show() that hasn't even started yet;
// just cancel the pending show()
@@ -12487,69 +13291,70 @@ dojo.declare(
// the tooltip is displayed.
showDelay: 400,
- // connectId: [const] String[]
- // Id's of domNodes to attach the tooltip to.
- // When user hovers over any of the specified dom nodes, the tooltip will appear.
- //
- // Note: Currently connectId can only be specified on initialization, it cannot
- // be changed via attr('connectId', ...)
- //
- // Note: in 2.0 this will be renamed to connectIds for less confusion.
+ // connectId: String|String[]
+ // Id of domNode(s) to attach the tooltip to.
+ // When user hovers over specified dom node, the tooltip will appear.
connectId: [],
// position: String[]
// See description of `dijit.Tooltip.defaultPosition` for details on position parameter.
position: [],
- constructor: function(){
- // Map id's of nodes I'm connected to to a list of the this.connect() handles
- this._nodeConnectionsById = {};
- },
+ _setConnectIdAttr: function(/*String*/ newId){
+ // summary:
+ // Connect to node(s) (specified by id)
- _setConnectIdAttr: function(newIds){
- for(var oldId in this._nodeConnectionsById){
- this.removeTarget(oldId);
- }
- dojo.forEach(dojo.isArrayLike(newIds) ? newIds : [newIds], this.addTarget, this);
- },
+ // Remove connections to old nodes (if there are any)
+ dojo.forEach(this._connections || [], function(nested){
+ dojo.forEach(nested, dojo.hitch(this, "disconnect"));
+ }, this);
- _getConnectIdAttr: function(){
- var ary = [];
- for(var id in this._nodeConnectionsById){
- ary.push(id);
- }
- return ary;
+ // Make connections to nodes in newIds.
+ var ary = dojo.isArrayLike(newId) ? newId : (newId ? [newId] : []);
+ this._connections = dojo.map(ary, function(id){
+ var node = dojo.byId(id);
+ return node ? [
+ this.connect(node, "onmouseenter", "_onTargetMouseEnter"),
+ this.connect(node, "onmouseleave", "_onTargetMouseLeave"),
+ this.connect(node, "onfocus", "_onTargetFocus"),
+ this.connect(node, "onblur", "_onTargetBlur")
+ ] : [];
+ }, this);
+
+ this._set("connectId", newId);
+
+ this._connectIds = ary; // save as array
},
- addTarget: function(/*DOMNODE || String*/ id){
+ addTarget: function(/*DOMNODE || String*/ node){
// summary:
- // Attach tooltip to specified node, if it's not already connected
- var node = dojo.byId(id);
- if(!node){ return; }
- if(node.id in this._nodeConnectionsById){ return; }//Already connected
-
- this._nodeConnectionsById[node.id] = [
- this.connect(node, "onmouseenter", "_onTargetMouseEnter"),
- this.connect(node, "onmouseleave", "_onTargetMouseLeave"),
- this.connect(node, "onfocus", "_onTargetFocus"),
- this.connect(node, "onblur", "_onTargetBlur")
- ];
+ // Attach tooltip to specified node if it's not already connected
+
+ // TODO: remove in 2.0 and just use set("connectId", ...) interface
+
+ var id = node.id || node;
+ if(dojo.indexOf(this._connectIds, id) == -1){
+ this.set("connectId", this._connectIds.concat(id));
+ }
},
removeTarget: function(/*DOMNODE || String*/ node){
// summary:
// Detach tooltip from specified node
- // map from DOMNode back to plain id string
- var id = node.id || node;
-
- if(id in this._nodeConnectionsById){
- dojo.forEach(this._nodeConnectionsById[id], this.disconnect, this);
- delete this._nodeConnectionsById[id];
+ // TODO: remove in 2.0 and just use set("connectId", ...) interface
+
+ var id = node.id || node, // map from DOMNode back to plain id string
+ idx = dojo.indexOf(this._connectIds, id);
+ if(idx >= 0){
+ // remove id (modifies original this._connectIds but that's OK in this case)
+ this._connectIds.splice(idx, 1);
+ this.set("connectId", this._connectIds);
}
},
- postCreate: function(){
+ buildRendering: function(){
+ this.inherited(arguments);
dojo.addClass(this.domNode,"dijitTooltipData");
},
@@ -12716,8 +13521,6 @@ dojo.provide("dijit.form.ValidationTextBox");
-
-
/*=====
dijit.form.ValidationTextBox.__Constraints = function(){
// locale: String
@@ -12738,7 +13541,7 @@ dojo.declare(
// tags:
// protected
- templateString: dojo.cache("dijit.form", "templates/ValidationTextBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" waiRole=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&Chi; \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
+ templateString: dojo.cache("dijit.form", "templates/ValidationTextBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" role=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&#935; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
baseClass: "dijitTextBox dijitValidationTextBox",
// required: Boolean
@@ -12747,6 +13550,7 @@ dojo.declare(
// promptMessage: String
// If defined, display this hint string immediately on focus to the textbox, if empty.
+ // Also displays if the textbox value is Incomplete (not yet valid but will be with additional input).
// Think of this like a tooltip that tells the user what to do, not an error message
// that tells the user what they've done wrong.
//
@@ -12765,6 +13569,12 @@ dojo.declare(
// Set to "" to use the invalidMessage instead.
missingMessage: "$_unset_$",
+ // message: String
+ // Currently error/prompt message.
+ // When using the default tooltip implementation, this will only be
+ // displayed when the field is focused.
+ message: "",
+
// constraints: dijit.form.ValidationTextBox.__Constraints
// user-defined object needed to pass parameters to the validator functions
constraints: {},
@@ -12774,7 +13584,7 @@ dojo.declare(
// Do not specify both regExp and regExpGen
regExp: ".*",
- regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/constraints){
+ regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/ constraints){
// summary:
// Overridable function used to generate regExp when dependent on constraints.
// Do not specify both regExp and regExpGen.
@@ -12784,7 +13594,7 @@ dojo.declare(
},
// state: [readonly] String
- // Shows current state (ie, validation result) of input (Normal, Warning, or Error)
+ // Shows current state (ie, validation result) of input (""=Normal, Incomplete, or Error)
state: "",
// tooltipPosition: String[]
@@ -12793,12 +13603,12 @@ dojo.declare(
_setValueAttr: function(){
// summary:
- // Hook so attr('value', ...) works.
+ // Hook so set('value', ...) works.
this.inherited(arguments);
this.validate(this._focused);
},
- validator: function(/*anything*/value, /*dijit.form.ValidationTextBox.__Constraints*/constraints){
+ validator: function(/*anything*/ value, /*dijit.form.ValidationTextBox.__Constraints*/ constraints){
// summary:
// Overridable function used to validate the text input against the regular expression.
// tags:
@@ -12827,7 +13637,7 @@ dojo.declare(
_isEmpty: function(value){
// summary:
// Checks for whitespace
- return /^\s*$/.test(value); // Boolean
+ return (this.trim ? /^\s*$/ : /^$/).test(value); // Boolean
},
getErrorMessage: function(/*Boolean*/ isFocused){
@@ -12858,37 +13668,32 @@ dojo.declare(
var isValid = this.disabled || this.isValid(isFocused);
if(isValid){ this._maskValidSubsetError = true; }
var isEmpty = this._isEmpty(this.textbox.value);
- var isValidSubset = !isValid && !isEmpty && isFocused && this._isValidSubset();
- this.state = ((isValid || ((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && this._maskValidSubsetError) ? "" : "Error";
- if(this.state == "Error"){ this._maskValidSubsetError = isFocused; } // we want the error to show up afer a blur and refocus
- this._setStateClass();
+ var isValidSubset = !isValid && isFocused && this._isValidSubset();
+ this._set("state", isValid ? "" : (((((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && this._maskValidSubsetError) ? "Incomplete" : "Error"));
dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true");
- if(isFocused){
- if(this.state == "Error"){
- message = this.getErrorMessage(true);
- }else{
- message = this.getPromptMessage(true); // show the prompt whever there's no error
- }
- this._maskValidSubsetError = true; // since we're focused, always mask warnings
+
+ if(this.state == "Error"){
+ this._maskValidSubsetError = isFocused && isValidSubset; // we want the error to show up after a blur and refocus
+ message = this.getErrorMessage(isFocused);
+ }else if(this.state == "Incomplete"){
+ message = this.getPromptMessage(isFocused); // show the prompt whenever the value is not yet complete
+ this._maskValidSubsetError = !this._hasBeenBlurred || isFocused; // no Incomplete warnings while focused
+ }else if(isEmpty){
+ message = this.getPromptMessage(isFocused); // show the prompt whenever there's no error and no text
}
- this.displayMessage(message);
+ this.set("message", message);
+
return isValid;
},
- // _message: String
- // Currently displayed message
- _message: "",
-
displayMessage: function(/*String*/ message){
// summary:
// Overridable method to display validation errors/hints.
// By default uses a tooltip.
// tags:
// extension
- if(this._message == message){ return; }
- this._message = message;
dijit.hideTooltip(this.domNode);
- if(message){
+ if(message && this._focused){
dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
}
},
@@ -12905,11 +13710,11 @@ dojo.declare(
this.constraints = {};
},
- _setConstraintsAttr: function(/* Object */ constraints){
+ _setConstraintsAttr: function(/*Object*/ constraints){
if(!constraints.locale && this.lang){
constraints.locale = this.lang;
}
- this.constraints = constraints;
+ this._set("constraints", constraints);
this._computePartialRE();
},
@@ -12966,11 +13771,16 @@ dojo.declare(
},
_setRequiredAttr: function(/*Boolean*/ value){
- this.required = value;
+ this._set("required", value);
dijit.setWaiState(this.focusNode, "required", value);
this._refreshState();
},
+ _setMessageAttr: function(/*String*/ message){
+ this._set("message", message);
+ this.displayMessage(message);
+ },
+
reset:function(){
// Overrides dijit.form.TextBox.reset() by also
// hiding errors about partial matches
@@ -12979,7 +13789,10 @@ dojo.declare(
},
_onBlur: function(){
+ // the message still exists but for back-compat, and to erase the tooltip
+ // (if the message is being displayed as a tooltip), call displayMessage('')
this.displayMessage('');
+
this.inherited(arguments);
}
}
@@ -13010,9 +13823,9 @@ dojo.declare(
this.nameAttrSetting = "";
},
- serialize: function(/*anything*/val, /*Object?*/options){
+ serialize: function(/*anything*/ val, /*Object?*/ options){
// summary:
- // Overridable function used to convert the attr('value') result to a canonical
+ // Overridable function used to convert the get('value') result to a canonical
// (non-localized) string. For example, will print dates in ISO format, and
// numbers the same way as they are represented in javascript.
// tags:
@@ -13044,10 +13857,10 @@ dojo.declare(
// (as opposed to the displayed value).
// Passing in name as markup rather than calling dojo.create() with an attrs argument
// to make dojo.query(input[name=...]) work on IE. (see #8660)
- this.valueNode = dojo.place("<input type='hidden'" + (this.name ? " name='" + this.name + "'" : "") + ">", this.textbox, "after");
+ this.valueNode = dojo.place("<input type='hidden'" + (this.name ? " name='" + this.name.replace(/'/g, "&quot;") + "'" : "") + "/>", this.textbox, "after");
},
- reset:function(){
+ reset: function(){
// Overrides `dijit.form.ValidationTextBox.reset` to
// reset the hidden textbox value to ''
this.valueNode.value = '';
@@ -13151,7 +13964,7 @@ dojo.declare(
}
},
- _setConstraintsAttr: function(/* Object */ constraints){
+ _setConstraintsAttr: function(/*Object*/ constraints){
this.inherited(arguments);
if(this.focusNode){ // not set when called from postMixInProperties
if(this.constraints.min !== undefined){
@@ -13169,7 +13982,7 @@ dojo.declare(
_setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){
// summary:
- // Hook so attr('value', ...) works.
+ // Hook so set('value', ...) works.
dijit.setWaiState(this.focusNode, "valuenow", value);
this.inherited(arguments);
@@ -13193,10 +14006,9 @@ dojo.provide("dijit.form.ComboBox");
-
dojo.declare(
"dijit.form.ComboBoxMixin",
- null,
+ dijit._HasDropDown,
{
// summary:
// Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect`
@@ -13215,14 +14027,14 @@ dojo.declare(
// Specifies number of search results per page (before hitting "next" button)
pageSize: Infinity,
- // store: Object
+ // store: [const] Object
// Reference to data provider object used by this ComboBox
store: null,
// fetchProperties: Object
// Mixin to the dojo.data store's fetch.
// For example, to set the sort order of the ComboBox menu, pass:
- // | { sort: {attribute:"name",descending: true} }
+ // | { sort: [{attribute:"name",descending: true}] }
// To override the default queryOptions so that deep=false, do:
// | { queryOptions: {ignoreCase: true, deep: false} }
fetchProperties:{},
@@ -13275,7 +14087,7 @@ dojo.declare(
// This specifies what query ComboBox/FilteringSelect sends to the data store,
// based on what the user has typed. Changing this expression will modify
// whether the drop down shows only exact matches, a "starting with" match,
- // etc. Use it in conjunction with highlightMatch.
+ // etc. Use it in conjunction with highlightMatch.
// dojo.data query expression pattern.
// `${0}` will be substituted for the user text.
// `*` is used for wildcards.
@@ -13286,21 +14098,32 @@ dojo.declare(
// Set true if the ComboBox/FilteringSelect should ignore case when matching possible items
ignoreCase: true,
- // hasDownArrow: [const] Boolean
+ // hasDownArrow: Boolean
// Set this textbox to have a down arrow button, to display the drop down list.
// Defaults to true.
hasDownArrow: true,
- templateString: dojo.cache("dijit.form", "templates/ComboBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\tdojoAttachPoint=\"comboNode\" waiRole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"downArrowNode\" waiRole=\"presentation\"\n\t\tdojoAttachEvent=\"onmousedown:_onArrowMouseDown\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"&#9660; \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&Chi; \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onKeyPress,compositionend\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" waiRole=\"textbox\" waiState=\"haspopup-true,autocomplete-list\"\n\t/></div\n></div>\n"),
+ templateString: dojo.cache("dijit.form", "templates/DropDownBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"&#9660; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&#935; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n"),
baseClass: "dijitTextBox dijitComboBox",
+ // dropDownClass: [protected extension] String
+ // Name of the dropdown widget class used to select a date/time.
+ // Subclasses should specify this.
+ dropDownClass: "dijit.form._ComboBoxMenu",
+
// Set classes like dijitDownArrowButtonHover depending on
// mouse action over button node
cssStateNodes: {
- "downArrowNode": "dijitDownArrowButton"
+ "_buttonNode": "dijitDownArrowButton"
},
+ // Flags to _HasDropDown to limit height of drop down to make it fit in viewport
+ maxHeight: -1,
+
+ // For backwards compatibility let onClick events propagate, even clicks on the down arrow button
+ _stopClickEvents: false,
+
_getCaretPos: function(/*DomNode*/ element){
// khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
var pos = 0;
@@ -13317,7 +14140,7 @@ dojo.declare(
tr.move("character",0);
ntr.move("character",0);
try{
- // If control doesnt have focus, you get an exception.
+ // If control doesn't have focus, you get an exception.
// Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
// There appears to be no workaround for this - googled for quite a while.
ntr.setEndPoint("EndToEnd", tr);
@@ -13338,7 +14161,7 @@ dojo.declare(
// Additional code to set disabled state of ComboBox node.
// Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr().
this.inherited(arguments);
- dijit.setWaiState(this.comboNode, "disabled", value);
+ dijit.setWaiState(this.domNode, "disabled", value);
},
_abortQuery: function(){
@@ -13358,29 +14181,39 @@ dojo.declare(
// Handles paste events
if(!this.searchTimer && (evt.type == 'paste'/*IE|WebKit*/ || evt.type == 'input'/*Firefox*/) && this._lastInput != this.textbox.value){
this.searchTimer = setTimeout(dojo.hitch(this, function(){
- this._onKeyPress({charOrCode: 229}); // fake IME key to cause a search
+ this._onKey({charOrCode: 229}); // fake IME key to cause a search
}), 100); // long delay that will probably be preempted by keyboard input
}
this.inherited(arguments);
},
- _onKeyPress: function(/*Event*/ evt){
+ _onKey: function(/*Event*/ evt){
// summary:
// Handles keyboard events
+
var key = evt.charOrCode;
+
// except for cutting/pasting case - ctrl + x/v
if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == dojo.keys.SHIFT){
return; // throw out weird key combinations and spurious events
}
+
var doSearch = false;
- var searchFunction = "_startSearchFromInput";
- var pw = this._popupWidget;
+ var pw = this.dropDown;
var dk = dojo.keys;
var highlighted = null;
this._prev_key_backspace = false;
this._abortQuery();
- if(this._isShowingNow){
- pw.handleKey(key);
+
+ // _HasDropDown will do some of the work:
+ // 1. when drop down is not yet shown:
+ // - if user presses the down arrow key, call loadDropDown()
+ // 2. when drop down is already displayed:
+ // - on ESC key, call closeDropDown()
+ // - otherwise, call dropDown.handleKey() to process the keystroke
+ this.inherited(arguments);
+
+ if(this._opened){
highlighted = pw.getHighlightedOption();
}
switch(key){
@@ -13388,10 +14221,9 @@ dojo.declare(
case dk.DOWN_ARROW:
case dk.PAGE_UP:
case dk.UP_ARROW:
- if(!this._isShowingNow){
- doSearch = true;
- searchFunction = "_startSearchAll";
- }else{
+ // Keystroke caused ComboBox_menu to move to a different item.
+ // Copy new item to <input> box.
+ if(this._opened){
this._announceOption(highlighted);
}
dojo.stopEvent(evt);
@@ -13418,8 +14250,12 @@ dojo.declare(
this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting
}
// default case:
+ // if enter pressed while drop down is open, or for FilteringSelect,
+ // if we are in the middle of a query to convert a directly typed in value to an item,
// prevent submit, but allow event to bubble
+ if(this._opened || this._fetchHandle){
evt.preventDefault();
+ }
// fall through
case dk.TAB:
@@ -13435,29 +14271,24 @@ dojo.declare(
if(highlighted){
this._selectOption();
}
- if(this._isShowingNow){
+ if(this._opened){
this._lastQuery = null; // in case results come back later
- this._hideResultList();
+ this.closeDropDown();
}
break;
case ' ':
if(highlighted){
+ // user is effectively clicking a choice in the drop down menu
dojo.stopEvent(evt);
this._selectOption();
- this._hideResultList();
+ this.closeDropDown();
}else{
+ // user typed a space into the input box, treat as normal character
doSearch = true;
}
break;
- case dk.ESCAPE:
- if(this._isShowingNow){
- dojo.stopEvent(evt);
- this._hideResultList();
- }
- break;
-
case dk.DELETE:
case dk.BACKSPACE:
this._prev_key_backspace = true;
@@ -13467,15 +14298,14 @@ dojo.declare(
default:
// Non char keys (F1-F12 etc..) shouldn't open list.
// Ascii characters and IME input (Chinese, Japanese etc.) should.
- // On IE and safari, IME input produces keycode == 229, and we simulate
- // it on firefox by attaching to compositionend event (see compositionend method)
+ //IME input produces keycode == 229.
doSearch = typeof key == 'string' || key == 229;
}
if(doSearch){
// need to wait a tad before start search so that the event
// bubbles through DOM and we have value visible
this.item = undefined; // undefined means item needs to be set
- this.searchTimer = setTimeout(dojo.hitch(this, searchFunction),1);
+ this.searchTimer = setTimeout(dojo.hitch(this, "_startSearchFromInput"),1);
}
},
@@ -13511,6 +14341,12 @@ dojo.declare(
},
_openResultList: function(/*Object*/ results, /*Object*/ dataObject){
+ // summary:
+ // Callback when a search completes.
+ // description:
+ // 1. generates drop-down list and calls _showResultList() to display it
+ // 2. if this result list is from user pressing "more choices"/"previous choices"
+ // then tell screen reader to announce new option
this._fetchHandle = null;
if( this.disabled ||
this.readOnly ||
@@ -13518,13 +14354,13 @@ dojo.declare(
){
return;
}
- this._popupWidget.clearResultList();
- if(!results.length && !this._maxOptions){ // this condition needs to match !this._isvalid set in FilteringSelect::_openResultList
- this._hideResultList();
+ var wasSelected = this.dropDown._highlighted_option && dojo.hasClass(this.dropDown._highlighted_option, "dijitMenuItemSelected");
+ this.dropDown.clearResultList();
+ if(!results.length && !this._maxOptions){ // if no results and not just the previous choices button
+ this.closeDropDown();
return;
}
-
// Fill in the textbox with the first item from the drop down list,
// and highlight the characters that were auto-completed. For
// example, if user typed "CA" and the drop down list appeared, the
@@ -13532,7 +14368,7 @@ dojo.declare(
// highlighted.
dataObject._maxOptions = this._maxOptions;
- var nodes = this._popupWidget.createOptions(
+ var nodes = this.dropDown.createOptions(
results,
dataObject,
dojo.hitch(this, "_getMenuLabelFromItem")
@@ -13546,12 +14382,14 @@ dojo.declare(
// shouting the next choice
if(dataObject.direction){
if(1 == dataObject.direction){
- this._popupWidget.highlightFirstOption();
+ this.dropDown.highlightFirstOption();
}else if(-1 == dataObject.direction){
- this._popupWidget.highlightLastOption();
+ this.dropDown.highlightLastOption();
}
- this._announceOption(this._popupWidget.getHighlightedOption());
- }else if(this.autoComplete && !this._prev_key_backspace /*&& !dataObject.direction*/
+ if(wasSelected){
+ this._announceOption(this.dropDown.getHighlightedOption());
+ }
+ }else if(this.autoComplete && !this._prev_key_backspace
// when the user clicks the arrow button to show the full list,
// startSearch looks for "*".
// it does not make sense to autocomplete
@@ -13562,62 +14400,42 @@ dojo.declare(
},
_showResultList: function(){
- this._hideResultList();
+ // summary:
+ // Display the drop down if not already displayed, or if it is displayed, then
+ // reposition it if necessary (reposition may be necessary if drop down's height changed).
+
+ this.closeDropDown(true);
+
// hide the tooltip
this.displayMessage("");
- // Position the list and if it's too big to fit on the screen then
- // size it to the maximum possible height
- // Our dear friend IE doesnt take max-height so we need to
- // calculate that on our own every time
-
- // TODO: want to redo this, see
- // http://trac.dojotoolkit.org/ticket/3272
- // and
- // http://trac.dojotoolkit.org/ticket/4108
-
-
- // natural size of the list has changed, so erase old
- // width/height settings, which were hardcoded in a previous
- // call to this function (via dojo.marginBox() call)
- dojo.style(this._popupWidget.domNode, {width: "", height: ""});
-
- var best = this.open();
- // #3212:
- // only set auto scroll bars if necessary prevents issues with
- // scroll bars appearing when they shouldn't when node is made
- // wider (fractional pixels cause this)
- var popupbox = dojo.marginBox(this._popupWidget.domNode);
- this._popupWidget.domNode.style.overflow =
- ((best.h == popupbox.h) && (best.w == popupbox.w)) ? "hidden" : "auto";
- // #4134:
- // borrow TextArea scrollbar test so content isn't covered by
- // scrollbar and horizontal scrollbar doesn't appear
- var newwidth = best.w;
- if(best.h < this._popupWidget.domNode.scrollHeight){
- newwidth += 16;
- }
- dojo.marginBox(this._popupWidget.domNode, {
- h: best.h,
- w: Math.max(newwidth, this.domNode.offsetWidth)
- });
+ this.openDropDown();
+
+ dijit.setWaiState(this.domNode, "expanded", "true");
+ },
+
+ loadDropDown: function(/*Function*/ callback){
+ // Overrides _HasDropDown.loadDropDown().
+ // This is called when user has pressed button icon or pressed the down arrow key
+ // to open the drop down.
- // If we increased the width of drop down to match the width of ComboBox.domNode,
- // then need to reposition the drop down (wrapper) so (all of) the drop down still
- // appears underneath the ComboBox.domNode
- if(newwidth < this.domNode.offsetWidth){
- this._popupWidget.domNode.parentNode.style.left = dojo.position(this.domNode, true).x + "px";
- }
+ this._startSearchAll();
+ },
- dijit.setWaiState(this.comboNode, "expanded", "true");
+ isLoaded: function(){
+ // signal to _HasDropDown that it needs to call loadDropDown() to load the
+ // drop down asynchronously before displaying it
+ return false;
},
- _hideResultList: function(){
+ closeDropDown: function(){
+ // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open).
+ // This method is the callback when the user types ESC or clicking
+ // the button icon while the drop down is open. It's also called by other code.
this._abortQuery();
- if(this._isShowingNow){
- dijit.popup.close(this._popupWidget);
- this._isShowingNow=false;
- dijit.setWaiState(this.comboNode, "expanded", "false");
+ if(this._opened){
+ this.inherited(arguments);
+ dijit.setWaiState(this.domNode, "expanded", "false");
dijit.removeWaiState(this.focusNode,"activedescendant");
}
},
@@ -13629,7 +14447,7 @@ dojo.declare(
// if value is now more choices or previous choices, revert
// the value
var newvalue = this.get('displayedValue');
- var pw = this._popupWidget;
+ var pw = this.dropDown;
if(pw && (
newvalue == pw._messages["previousMessage"] ||
newvalue == pw._messages["nextMessage"]
@@ -13651,23 +14469,25 @@ dojo.declare(
_onBlur: function(){
// summary:
// Called magically when focus has shifted away from this widget and it's drop down
- this._hideResultList();
+ this.closeDropDown();
this.inherited(arguments);
},
_setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
// summary:
- // Set the displayed valued in the input box, and the hidden value
- // that gets submitted, based on a dojo.data store item.
+ // Set the displayed valued in the input box, and the hidden value
+ // that gets submitted, based on a dojo.data store item.
// description:
- // Users shouldn't call this function; they should be calling
- // attr('item', value)
+ // Users shouldn't call this function; they should be calling
+ // set('item', value)
// tags:
- // private
- if(!displayedValue){ displayedValue = this.labelFunc(item, this.store); }
- this.value = this._getValueField() != this.searchAttr? this.store.getIdentity(item) : displayedValue;
- this.item = item;
- dijit.form.ComboBox.superclass._setValueAttr.call(this, this.value, priorityChange, displayedValue);
+ // private
+ if(!displayedValue){
+ displayedValue = this.store.getValue(item, this.searchAttr);
+ }
+ var value = this._getValueField() != this.searchAttr? this.store.getIdentity(item) : displayedValue;
+ this._set("item", item);
+ dijit.form.ComboBox.superclass._setValueAttr.call(this, value, priorityChange, displayedValue);
},
_announceOption: function(/*Node*/ node){
@@ -13681,13 +14501,13 @@ dojo.declare(
}
// pull the text value from the item attached to the DOM node
var newValue;
- if(node == this._popupWidget.nextButton ||
- node == this._popupWidget.previousButton){
+ if(node == this.dropDown.nextButton ||
+ node == this.dropDown.previousButton){
newValue = node.innerHTML;
this.item = undefined;
this.value = '';
}else{
- newValue = this.labelFunc(node.item, this.store);
+ newValue = this.store.getValue(node.item, this.searchAttr).toString();
this.set('item', node.item, false, newValue);
}
// get the text that the user manually entered (cut off autocompleted text)
@@ -13704,28 +14524,11 @@ dojo.declare(
if(evt){
this._announceOption(evt.target);
}
- this._hideResultList();
+ this.closeDropDown();
this._setCaretPos(this.focusNode, this.focusNode.value.length);
dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); // set this.value and fire onChange
},
- _onArrowMouseDown: function(evt){
- // summary:
- // Callback when arrow is clicked
- if(this.disabled || this.readOnly){
- return;
- }
- dojo.stopEvent(evt);
- this.focus();
- if(this._isShowingNow){
- this._hideResultList();
- }else{
- // forces full population of results, if they click
- // on the arrow it means they want to see more options
- this._startSearchAll();
- }
- },
-
_startSearchAll: function(){
this._startSearch('');
},
@@ -13739,9 +14542,13 @@ dojo.declare(
},
_startSearch: function(/*String*/ key){
- if(!this._popupWidget){
- var popupId = this.id + "_popup";
- this._popupWidget = new dijit.form._ComboBoxMenu({
+ // summary:
+ // Starts a search for elements matching key (key=="" means to return all items),
+ // and calls _openResultList() when the search completes, to display the results.
+ if(!this.dropDown){
+ var popupId = this.id + "_popup",
+ dropDownConstructor = dojo.getObject(this.dropDownClass, false);
+ this.dropDown = new dropDownConstructor({
onChange: dojo.hitch(this, this._selectOption),
id: popupId,
dir: this.dir
@@ -13770,7 +14577,7 @@ dojo.declare(
onError: function(errText){
_this._fetchHandle = null;
console.error('dijit.form.ComboBox: ' + errText);
- dojo.hitch(_this, "_hideResultList")();
+ _this.closeDropDown();
},
start: 0,
count: this.pageSize
@@ -13785,8 +14592,9 @@ dojo.declare(
// reader knows which menu option to shout
dataObject.direction = direction;
this._fetchHandle = this.store.fetch(dataObject);
+ this.focus();
};
- this._nextSearch = this._popupWidget.onPage = dojo.hitch(this, nextSearch, this._fetchHandle);
+ this._nextSearch = this.dropDown.onPage = dojo.hitch(this, nextSearch, this._fetchHandle);
}, query, this), this.searchDelay);
},
@@ -13795,29 +14603,12 @@ dojo.declare(
},
_getValueField: function(){
- // summmary:
+ // summary:
// Helper for postMixInProperties() to set this.value based on data inlined into the markup.
// Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value.
return this.searchAttr;
},
- /////////////// Event handlers /////////////////////
-
- // FIXME: For 2.0, rename to "_compositionEnd"
- compositionend: function(/*Event*/ evt){
- // summary:
- // When inputting characters using an input method, such as
- // Asian languages, it will generate this event instead of
- // onKeyDown event.
- // Note: this event is only triggered in FF (not in IE/safari)
- // tags:
- // private
-
- // 229 is the code produced by IE and safari while pressing keys during
- // IME input mode
- this._onKeyPress({charOrCode: 229});
- },
-
//////////// INITIALIZATION METHODS ///////////////////////////////////////
constructor: function(){
@@ -13841,13 +14632,14 @@ dojo.declare(
// by the selected attribute of an option tag
// IE6 does not set value, Opera sets value = selectedIndex
if(!("value" in this.params)){
- var item = this.store.fetchSelectedItem();
+ var item = (this.item = this.store.fetchSelectedItem());
if(item){
var valueField = this._getValueField();
- this.value = valueField != this.searchAttr? this.store.getValue(item, valueField) : this.labelFunc(item, this.store);
+ this.value = this.store.getValue(item, valueField);
}
}
}
+
this.inherited(arguments);
},
@@ -13857,32 +14649,24 @@ dojo.declare(
// tags:
// protected
- if(!this.hasDownArrow){
- this.downArrowNode.style.display = "none";
- }
-
// find any associated label element and add to ComboBox node.
var label=dojo.query('label[for="'+this.id+'"]');
if(label.length){
label[0].id = (this.id+"_label");
- var cn=this.comboNode;
- dijit.setWaiState(cn, "labelledby", label[0].id);
+ dijit.setWaiState(this.domNode, "labelledby", label[0].id);
}
this.inherited(arguments);
},
- uninitialize: function(){
- if(this._popupWidget && !this._popupWidget._destroyed){
- this._hideResultList();
- this._popupWidget.destroy();
- }
- this.inherited(arguments);
+ _setHasDownArrowAttr: function(val){
+ this.hasDownArrow = val;
+ this._buttonNode.style.display = val ? "" : "none";
},
_getMenuLabelFromItem: function(/*Item*/ item){
- var label = this.labelAttr? this.store.getValue(item, this.labelAttr) : this.labelFunc(item, this.store);
- var labelType = this.labelType;
+ var label = this.labelFunc(item, this.store),
+ labelType = this.labelType;
// If labelType is not "text" we don't want to screw any markup ot whatever.
if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){
label = this.doHighlight(label, this._escapeHtml(this._lastInput));
@@ -13891,24 +14675,27 @@ dojo.declare(
return {html: labelType == "html", label: label};
},
- doHighlight: function(/*String*/label, /*String*/find){
+ doHighlight: function(/*String*/ label, /*String*/ find){
// summary:
// Highlights the string entered by the user in the menu. By default this
- // highlights the first occurence found. Override this method
- // to implement your custom highlighing.
+ // highlights the first occurrence found. Override this method
+ // to implement your custom highlighting.
// tags:
// protected
- // Add greedy when this.highlightMatch == "all"
- var modifiers = "i"+(this.highlightMatch == "all"?"g":"");
- var escapedLabel = this._escapeHtml(label);
+ var
+ // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true
+ modifiers = (this.ignoreCase ? "i" : "") + (this.highlightMatch == "all" ? "g" : ""),
+ i = this.queryExpr.indexOf("${0}");
find = dojo.regexp.escapeString(find); // escape regexp special chars
- var ret = escapedLabel.replace(new RegExp("(^|\\s)("+ find +")", modifiers),
- '$1<span class="dijitComboBoxHighlightMatch">$2</span>');
- return ret;// returns String, (almost) valid HTML (entities encoded)
+ return this._escapeHtml(label).replace(
+ // prepend ^ when this.queryExpr == "${0}*" and append $ when this.queryExpr == "*${0}"
+ new RegExp((i == 0 ? "^" : "") + "("+ find +")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers),
+ '<span class="dijitComboBoxHighlightMatch">$1</span>'
+ ); // returns String, (almost) valid HTML (entities encoded)
},
- _escapeHtml: function(/*string*/str){
+ _escapeHtml: function(/*String*/ str){
// TODO Should become dojo.html.entities(), when exists use instead
// summary:
// Adds escape sequences for special characters in XML: &<>"'
@@ -13917,19 +14704,6 @@ dojo.declare(
return str; // string
},
- open: function(){
- // summary:
- // Opens the drop down menu. TODO: rename to _open.
- // tags:
- // private
- this._isShowingNow=true;
- return dijit.popup.open({
- popup: this._popupWidget,
- around: this.domNode,
- parent: this
- });
- },
-
reset: function(){
// Overrides the _FormWidget.reset().
// Additionally reset the .item (to clean up).
@@ -13939,15 +14713,15 @@ dojo.declare(
labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){
// summary:
- // Computes the label to display based on the dojo.data store item.
+ // Computes the label to display based on the dojo.data store item.
// returns:
- // The label that the ComboBox should display
+ // The label that the ComboBox should display
// tags:
- // private
+ // private
// Use toString() because XMLStore returns an XMLItem whereas this
// method is expected to return a String (#9354)
- return store.getValue(item, this.searchAttr).toString(); // String
+ return store.getValue(item, this.labelAttr || this.searchAttr).toString(); // String
}
}
);
@@ -13961,9 +14735,9 @@ dojo.declare(
// tags:
// private
- templateString: "<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' tabIndex='-1' style='overflow: \"auto\"; overflow-x: \"hidden\";'>"
- +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' waiRole='option'></li>"
- +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' waiRole='option'></li>"
+ templateString: "<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' style='overflow: \"auto\"; overflow-x: \"hidden\";'>"
+ +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' role='option'></li>"
+ +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' role='option'></li>"
+"</ul>",
// _messages: Object
@@ -13973,8 +14747,16 @@ dojo.declare(
baseClass: "dijitComboBoxMenu",
postMixInProperties: function(){
+ this.inherited(arguments);
this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang);
+ },
+
+ buildRendering: function(){
this.inherited(arguments);
+
+ // fill in template with i18n messages
+ this.previousButton.innerHTML = this._messages["previousMessage"];
+ this.nextButton.innerHTML = this._messages["nextMessage"];
},
_setValueAttr: function(/*Object*/ value){
@@ -13997,13 +14779,6 @@ dojo.declare(
// callback
},
- postCreate: function(){
- // fill in template with i18n messages
- this.previousButton.innerHTML = this._messages["previousMessage"];
- this.nextButton.innerHTML = this._messages["nextMessage"];
- this.inherited(arguments);
- },
-
onClose: function(){
// summary:
// Callback from dijit.popup code to this widget, notifying it that it closed
@@ -14017,9 +14792,11 @@ dojo.declare(
// Creates an option to appear on the popup menu subclassed by
// `dijit.form.FilteringSelect`.
+ var menuitem = dojo.create("li", {
+ "class": "dijitReset dijitMenuItem" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl"),
+ role: "option"
+ });
var labelObject = labelFunc(item);
- var menuitem = dojo.doc.createElement("li");
- dijit.setWaiRole(menuitem, "option");
if(labelObject.html){
menuitem.innerHTML = labelObject.label;
}else{
@@ -14056,8 +14833,6 @@ dojo.declare(
// iterate over cache nondestructively
dojo.forEach(results, function(item, i){
var menuitem = this._createOption(item, labelFunc);
- menuitem.className = "dijitReset dijitMenuItem" +
- (this.isLeftToRight() ? "" : " dijitMenuItemRtl");
dojo.attr(menuitem, "id", this.id + i);
this.domNode.insertBefore(menuitem, this.nextButton);
}, this);
@@ -14090,6 +14865,7 @@ dojo.declare(
while(this.domNode.childNodes.length>2){
this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]);
}
+ this._blurOptionNode();
},
_onMouseDown: function(/*Event*/ evt){
@@ -14098,10 +14874,14 @@ dojo.declare(
_onMouseUp: function(/*Event*/ evt){
if(evt.target === this.domNode || !this._highlighted_option){
+ // !this._highlighted_option check to prevent immediate selection when menu appears on top
+ // of <input>, see #9898. Note that _HasDropDown also has code to prevent this.
return;
}else if(evt.target == this.previousButton){
+ this._blurOptionNode();
this.onPage(-1);
}else if(evt.target == this.nextButton){
+ this._blurOptionNode();
this.onPage(1);
}else{
var tgt = evt.target;
@@ -14268,20 +15048,25 @@ dojo.declare(
return (ho && ho.parentNode) ? ho : null;
},
- handleKey: function(key){
- switch(key){
+ handleKey: function(evt){
+ // summary:
+ // Handle keystroke event forwarded from ComboBox, returning false if it's
+ // a keystroke I recognize and process, true otherwise.
+ switch(evt.charOrCode){
case dojo.keys.DOWN_ARROW:
this._highlightNextOption();
- break;
+ return false;
case dojo.keys.PAGE_DOWN:
this.pageDown();
- break;
+ return false;
case dojo.keys.UP_ARROW:
this._highlightPrevOption();
- break;
+ return false;
case dojo.keys.PAGE_UP:
this.pageUp();
- break;
+ return false;
+ default:
+ return true;
}
}
}
@@ -14309,10 +15094,10 @@ dojo.declare(
_setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
// summary:
- // Hook so attr('value', value) works.
+ // Hook so set('value', value) works.
// description:
// Sets the value of the select.
- this.item = null; // value not looked up in store
+ this._set("item", null); // value not looked up in store
if(!value){ value = ''; } // null translates to blank
dijit.form.ValidationTextBox.prototype._setValueAttr.call(this, value, priorityChange, displayedValue);
}
@@ -14356,13 +15141,13 @@ dojo.declare("dijit.form._ComboBoxDataStore", null, {
},
- getValue: function( /* item */ item,
- /* attribute-name-string */ attribute,
- /* value? */ defaultValue){
+ getValue: function( /*item*/ item,
+ /*attribute-name-string*/ attribute,
+ /*value?*/ defaultValue){
return (attribute == "value") ? item.value : (item.innerText || item.textContent || '');
},
- isItemLoaded: function(/* anything */ something){
+ isItemLoaded: function(/*anything*/ something){
return true;
},
@@ -14370,9 +15155,9 @@ dojo.declare("dijit.form._ComboBoxDataStore", null, {
return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true};
},
- _fetchItems: function( /* Object */ args,
- /* Function */ findCallback,
- /* Function */ errorCallback){
+ _fetchItems: function( /*Object*/ args,
+ /*Function*/ findCallback,
+ /*Function*/ errorCallback){
// summary:
// See dojo.data.util.simpleFetch.fetch()
if(!args.query){ args.query = {}; }
@@ -14388,19 +15173,19 @@ dojo.declare("dijit.form._ComboBoxDataStore", null, {
findCallback(items, args);
},
- close: function(/*dojo.data.api.Request || args || null */ request){
+ close: function(/*dojo.data.api.Request || args || null*/ request){
return;
},
- getLabel: function(/* item */ item){
+ getLabel: function(/*item*/ item){
return item.innerHTML;
},
- getIdentity: function(/* item */ item){
+ getIdentity: function(/*item*/ item){
return dojo.attr(item, "value");
},
- fetchItemByIdentity: function(/* Object */ args){
+ fetchItemByIdentity: function(/*Object*/ args){
// summary:
// Given the identity of an item, this method returns the item that has
// that identity through the onItem callback.
@@ -14468,17 +15253,19 @@ dojo.declare(
// - List can be specified either as a static list or via a javascript
// function (that can get the list from a server)
- _isvalid: true,
-
// required: Boolean
// True (default) if user is required to enter a value into this field.
required: true,
_lastDisplayedValue: "",
+ _isValidSubset: function(){
+ return this._opened;
+ },
+
isValid: function(){
// Overrides ValidationTextBox.isValid()
- return this._isvalid || (!this.required && this.get('displayedValue') == ""); // #5974
+ return this.item || (!this.required && this.get('displayedValue') == ""); // #5974
},
_refreshState: function(){
@@ -14487,12 +15274,12 @@ dojo.declare(
}
},
- _callbackSetLabel: function( /*Array*/ result,
+ _callbackSetLabel: function(
+ /*Array*/ result,
/*Object*/ dataObject,
/*Boolean?*/ priorityChange){
// summary:
- // Callback function that dynamically sets the label of the
- // ComboBox
+ // Callback from dojo.data after lookup of user entered value finishes
// setValue does a synchronous lookup,
// so it calls _callbackSetLabel directly,
@@ -14502,35 +15289,38 @@ dojo.declare(
return;
}
if(!result.length){
- //#3268: do nothing on bad input
+ //#3268: don't modify display value on bad input
//#3285: change CSS to indicate error
this.valueNode.value = "";
dijit.form.TextBox.superclass._setValueAttr.call(this, "", priorityChange || (priorityChange === undefined && !this._focused));
- this._isvalid = false;
+ this._set("item", null);
this.validate(this._focused);
- this.item = null;
}else{
this.set('item', result[0], priorityChange);
}
},
_openResultList: function(/*Object*/ results, /*Object*/ dataObject){
+ // Callback when a data store query completes.
// Overrides ComboBox._openResultList()
// #3285: tap into search callback to see if user's query resembles a match
if(dataObject.query[this.searchAttr] != this._lastQuery){
return;
}
+ dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments);
+
if(this.item === undefined){ // item == undefined for keyboard search
- this._isvalid = results.length != 0 || this._maxOptions != 0; // result.length==0 && maxOptions != 0 implies the nextChoices item selected but then the datastore returned 0 more entries
+ // If the search returned no items that means that the user typed
+ // in something invalid (and they can't make it valid by typing more characters),
+ // so flag the FilteringSelect as being in an invalid state
this.validate(true);
}
- dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments);
},
_getValueAttr: function(){
// summary:
- // Hook for attr('value') to work.
+ // Hook for get('value') to work.
// don't get the textbox value but rather the previously set hidden value.
// Use this.valueNode.value which isn't always set for other MappedTextBox widgets until blur
@@ -14544,7 +15334,7 @@ dojo.declare(
_setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){
// summary:
- // Hook so attr('value', value) works.
+ // Hook so set('value', value) works.
// description:
// Sets the value of the select.
// Also sets the label to the corresponding value by reverse lookup.
@@ -14572,10 +15362,9 @@ dojo.declare(
// that gets submitted, based on a dojo.data store item.
// description:
// Users shouldn't call this function; they should be calling
- // attr('item', value)
+ // set('item', value)
// tags:
// private
- this._isvalid = true;
this.inherited(arguments);
this.valueNode.value = this.value;
this._lastDisplayedValue = this.textbox.value;
@@ -14587,30 +15376,38 @@ dojo.declare(
_setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){
// summary:
- // Hook so attr('displayedValue', label) works.
+ // Hook so set('displayedValue', label) works.
// description:
// Sets textbox to display label. Also performs reverse lookup
- // to set the hidden value.
+ // to set the hidden value. label should corresponding to item.searchAttr.
- // When this is called during initialization it'll ping the datastore
- // for reverse lookup, and when that completes (after an XHR request)
- // will call setValueAttr()... but that shouldn't trigger an onChange()
- // event, even when it happens after creation has finished
+ if(label == null){ label = ''; }
+
+ // This is called at initialization along with every custom setter.
+ // Usually (or always?) the call can be ignored. If it needs to be
+ // processed then at least make sure that the XHR request doesn't trigger an onChange()
+ // event, even if it returns after creation has finished
if(!this._created){
+ if(!("displayedValue" in this.params)){
+ return;
+ }
priorityChange = false;
}
+ // Do a reverse lookup to map the specified displayedValue to the hidden value.
+ // Note that if there's a custom labelFunc() this code
if(this.store){
- this._hideResultList();
+ this.closeDropDown();
var query = dojo.clone(this.query); // #6196: populate query with user-specifics
// escape meta characters of dojo.data.util.filter.patternToRegExp().
this._lastQuery = query[this.searchAttr] = this._getDisplayQueryString(label);
- // if the label is not valid, the callback will never set it,
- // so the last valid value will get the warning textbox set the
+ // If the label is not valid, the callback will never set it,
+ // so the last valid value will get the warning textbox. Set the
// textbox value now so that the impending warning will make
// sense to the user
this.textbox.value = label;
this._lastDisplayedValue = label;
+ this._set("displayedValue", label); // for watch("displayedValue") notification
var _this = this;
var fetch = {
query: query,
@@ -14633,11 +15430,6 @@ dojo.declare(
}
},
- postMixInProperties: function(){
- this.inherited(arguments);
- this._isvalid = !this.required;
- },
-
undo: function(){
this.set('displayedValue', this._lastDisplayedValue);
}
@@ -14654,9 +15446,10 @@ dojo.provide("dijit.form.Form");
+
dojo.declare(
"dijit.form.Form",
- [dijit._Widget, dijit._Templated, dijit.form._FormMixin],
+ [dijit._Widget, dijit._Templated, dijit.form._FormMixin, dijit.layout._ContentPaneResizeMixin],
{
// summary:
// Widget corresponding to HTML form tag, for validation and serialization
@@ -14740,7 +15533,7 @@ dojo.declare(
postCreate: function(){
// IE tries to hide encType
- // TODO: this code should be in parser, not here.
+ // TODO: remove in 2.0, no longer necessary with data-dojo-params
if(dojo.isIE && this.srcNodeRef && this.srcNodeRef.attributes){
var item = this.srcNodeRef.attributes.getNamedItem('encType');
if(item && !item.specified && (typeof item.value == "string")){
@@ -14761,8 +15554,8 @@ dojo.declare(
preventDefault: function(){ // not IE
this.returnValue = false;
},
- stopPropagation: function(){},
- currentTarget: e ? e.target : this.domNode,
+ stopPropagation: function(){},
+ currentTarget: e ? e.target : this.domNode,
target: e ? e.target : this.domNode
};
// if return value is not exactly false, and haven't called preventDefault(), then reset
@@ -14801,7 +15594,7 @@ dojo.declare(
}
},
- onSubmit: function(/*Event?*/e){
+ onSubmit: function(/*Event?*/ e){
// summary:
// Callback when user submits the form.
// description:
@@ -14833,6 +15626,7 @@ dojo._hasResource["dijit.form.RadioButton"] = true;
dojo.provide("dijit.form.RadioButton");
+
// TODO: for 2.0, move the RadioButton code into this file
}
@@ -14851,7 +15645,7 @@ dijit.form.__SelectOption = function(){
// place a separator at that location
// label: String
// The label for our option. It can contain html tags.
- // selected: Boolean
+ // selected: Boolean
// Whether or not we are a selected option
// disabled: Boolean
// Whether or not this specific option is disabled
@@ -14869,13 +15663,13 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
// This also provides the mechanism for reading the elements from
// a store, if desired.
- // multiple: Boolean
+ // multiple: [const] Boolean
// Whether or not we are multi-valued
multiple: false,
// options: dijit.form.__SelectOption[]
// The set of options for our select item. Roughly corresponds to
- // the html <option> tag.
+ // the html <option> tag.
options: null,
// store: dojo.data.api.Identity
@@ -14897,20 +15691,20 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
// iterated over (i.e. to filter even futher what you want to add)
onFetch: null,
- // sortByLabel: boolean
+ // sortByLabel: Boolean
// Flag to sort the options returned from a store by the label of
// the store.
sortByLabel: true,
- // loadChildrenOnOpen: boolean
+ // loadChildrenOnOpen: Boolean
// By default loadChildren is called when the items are fetched from the
// store. This property allows delaying loadChildren (and the creation
- // of the options/menuitems) until the user opens the click the button.
- // dropdown
+ // of the options/menuitems) until the user clicks the button to open the
+ // dropdown.
loadChildrenOnOpen: false,
- getOptions: function(/* anything */ valueOrIdx){
+ getOptions: function(/*anything*/ valueOrIdx){
// summary:
// Returns a given option (or options).
// valueOrIdx:
@@ -14976,7 +15770,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
return null; // null
},
- addOption: function(/* dijit.form.__SelectOption, dijit.form.__SelectOption[] */ option){
+ addOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ option){
// summary:
// Adds an option or options to the end of the select. If value
// of the option is empty or missing, a separator is created instead.
@@ -14991,7 +15785,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
this._loadChildren();
},
- removeOption: function(/* string, dijit.form.__SelectOption, number, or array */ valueOrIdx){
+ removeOption: function(/*String|dijit.form.__SelectOption|Number|Array*/ valueOrIdx){
// summary:
// Removes the given option or options. You can remove by string
// (in which case the value is removed), number (in which case the
@@ -15006,7 +15800,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
// that case, we don't want to blow up...
if(i){
this.options = dojo.filter(this.options, function(node, idx){
- return (node.value !== i.value);
+ return (node.value !== i.value || node.label !== i.label);
});
this._removeOptionItem(i);
}
@@ -15014,7 +15808,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
this._loadChildren();
},
- updateOption: function(/* dijit.form.__SelectOption, dijit.form.__SelectOption[] */ newOption){
+ updateOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ newOption){
// summary:
// Updates the values of the given option. The option to update
// is matched based on the value of the entered option. Passing
@@ -15030,9 +15824,9 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
this._loadChildren();
},
- setStore: function(/* dojo.data.api.Identity */ store,
- /* anything? */ selectedValue,
- /* Object? */ fetchArgs){
+ setStore: function(/*dojo.data.api.Identity*/ store,
+ /*anything?*/ selectedValue,
+ /*Object?*/ fetchArgs){
// summary:
// Sets the store you would like to use with this select widget.
// The selected value is the value of the new store to set. This
@@ -15059,7 +15853,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
dojo.connect(store, "onSet", this, "_onSetItem")
];
}
- this.store = store;
+ this._set("store", store);
}
// Turn off change notifications while we make all these changes
@@ -15072,48 +15866,52 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
// Add our new options
if(store){
- var cb = function(items){
- if(this.sortByLabel && !fetchArgs.sort && items.length){
- items.sort(dojo.data.util.sorter.createSortFunction([{
- attribute: store.getLabelAttributes(items[0])[0]
- }], store));
- }
-
- if(fetchArgs.onFetch){
- items = fetchArgs.onFetch(items);
- }
- // TODO: Add these guys as a batch, instead of separately
- dojo.forEach(items, function(i){
- this._addOptionForItem(i);
- }, this);
-
- // Set our value (which might be undefined), and then tweak
- // it to send a change event with the real value
- this._loadingStore = false;
- this.set("value", (("_pendingValue" in this) ? this._pendingValue : selectedValue));
- delete this._pendingValue;
-
- if(!this.loadChildrenOnOpen){
- this._loadChildren();
- }else{
- this._pseudoLoadChildren(items);
- }
- this._fetchedWith = opts;
- this._lastValueReported = this.multiple ? [] : null;
- this._onChangeActive = true;
- this.onSetStore();
- this._handleOnChange(this.value);
- };
- var opts = dojo.mixin({onComplete:cb, scope: this}, fetchArgs);
this._loadingStore = true;
- store.fetch(opts);
+ store.fetch(dojo.delegate(fetchArgs, {
+ onComplete: function(items, opts){
+ if(this.sortByLabel && !fetchArgs.sort && items.length){
+ items.sort(dojo.data.util.sorter.createSortFunction([{
+ attribute: store.getLabelAttributes(items[0])[0]
+ }], store));
+ }
+
+ if(fetchArgs.onFetch){
+ items = fetchArgs.onFetch.call(this, items, opts);
+ }
+ // TODO: Add these guys as a batch, instead of separately
+ dojo.forEach(items, function(i){
+ this._addOptionForItem(i);
+ }, this);
+
+ // Set our value (which might be undefined), and then tweak
+ // it to send a change event with the real value
+ this._loadingStore = false;
+ this.set("value", "_pendingValue" in this ? this._pendingValue : selectedValue);
+ delete this._pendingValue;
+
+ if(!this.loadChildrenOnOpen){
+ this._loadChildren();
+ }else{
+ this._pseudoLoadChildren(items);
+ }
+ this._fetchedWith = opts;
+ this._lastValueReported = this.multiple ? [] : null;
+ this._onChangeActive = true;
+ this.onSetStore();
+ this._handleOnChange(this.value);
+ },
+ scope: this
+ }));
}else{
delete this._fetchedWith;
}
return oStore; // dojo.data.api.Identity
},
- _setValueAttr: function(/*anything*/ newValue, /*Boolean, optional*/ priorityChange){
+ // TODO: implement set() and watch() for store and query, although not sure how to handle
+ // setting them individually rather than together (as in setStore() above)
+
+ _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
// summary:
// set the value of the widget.
// If a string is passed, then we set our value from looking it up.
@@ -15149,7 +15947,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
var val = dojo.map(newValue, function(i){ return i.value; }),
disp = dojo.map(newValue, function(i){ return i.label; });
- this.value = this.multiple ? val : val[0];
+ this._set("value", this.multiple ? val : val[0]);
this._setDisplay(this.multiple ? disp : disp[0]);
this._updateSelection();
this._handleOnChange(this.value, priorityChange);
@@ -15173,23 +15971,10 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
return this.multiple ? ret : ret[0];
},
- _getValueDeprecated: false, // remove when _FormWidget:getValue is removed
- getValue: function(){
- // summary:
- // get the value of the widget.
- return this._lastValue;
- },
-
- undo: function(){
- // summary:
- // restore the value to the last value passed to onChange
- this._setValueAttr(this._lastValueReported, false);
- },
-
_loadChildren: function(){
// summary:
// Loads the children represented by this widget's options.
- // reset the menu to make it "populatable on the next click
+ // reset the menu to make it populatable on the next click
if(this._loadingStore){ return; }
dojo.forEach(this._getChildren(), function(child){
child.destroyRecursive();
@@ -15204,7 +15989,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
_updateSelection: function(){
// summary:
// Sets the "selected" class on the item for styling purposes
- this.value = this._getValueFromOpts();
+ this._set("value", this._getValueFromOpts());
var val = this.value;
if(!dojo.isArray(val)){
val = [val];
@@ -15218,7 +16003,6 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
dijit.setWaiState(child.domNode, "selected", isSelected);
}, this);
}
- this._handleOnChange(this.value);
},
_getValueFromOpts: function(){
@@ -15249,17 +16033,17 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
},
// Internal functions to call when we have store notifications come in
- _onNewItem: function(/* item */ item, /* Object? */ parentInfo){
+ _onNewItem: function(/*item*/ item, /*Object?*/ parentInfo){
if(!parentInfo || !parentInfo.parent){
// Only add it if we are top-level
this._addOptionForItem(item);
}
},
- _onDeleteItem: function(/* item */ item){
+ _onDeleteItem: function(/*item*/ item){
var store = this.store;
this.removeOption(store.getIdentity(item));
},
- _onSetItem: function(/* item */ item){
+ _onSetItem: function(/*item*/ item){
this.updateOption(this._getOptionObjForItem(item));
},
@@ -15274,7 +16058,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
return {value: value, label: label, item:item}; // dijit.form.__SelectOption
},
- _addOptionForItem: function(/* item */ item){
+ _addOptionForItem: function(/*item*/ item){
// summary:
// Creates (and adds) the option for the given item
var store = this.store;
@@ -15290,13 +16074,18 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
this.addOption(newOpt);
},
- constructor: function(/* Object */ keywordArgs){
+ constructor: function(/*Object*/ keywordArgs){
// summary:
// Saves off our value, if we have an initial one set so we
// can use it if we have a store as well (see startup())
this._oValue = (keywordArgs || {}).value || null;
},
+ buildRendering: function(){
+ this.inherited(arguments);
+ dojo.setSelectable(this.focusNode, false);
+ },
+
_fillContent: function(){
// summary:
// Loads our options and sets up our dropdown correctly. We
@@ -15309,16 +16098,21 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
if(node.getAttribute("type") === "separator"){
return { value: "", label: "", selected: false, disabled: false };
}
- return { value: node.getAttribute("value"),
+ return {
+ value: (node.getAttribute("data-" + dojo._scopeName + "-value") || node.getAttribute("value")),
label: String(node.innerHTML),
+ // FIXME: disabled and selected are not valid on complex markup children (which is why we're
+ // looking for data-dojo-value above. perhaps we should data-dojo-props="" this whole thing?)
+ // decide before 1.6
selected: node.getAttribute("selected") || false,
- disabled: node.getAttribute("disabled") || false };
+ disabled: node.getAttribute("disabled") || false
+ };
}, this) : [];
}
if(!this.value){
- this.value = this._getValueFromOpts();
+ this._set("value", this._getValueFromOpts());
}else if(this.multiple && typeof this.value == "string"){
- this.value = this.value.split(",");
+ this_set("value", this.value.split(","));
}
},
@@ -15326,7 +16120,6 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
// summary:
// sets up our event handling that we need for functioning
// as a select
- dojo.setSelectable(this.focusNode, false);
this.inherited(arguments);
// Make our event connections for updating state
@@ -15362,7 +16155,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
this.inherited(arguments);
},
- _addOptionItem: function(/* dijit.form.__SelectOption */ option){
+ _addOptionItem: function(/*dijit.form.__SelectOption*/ option){
// summary:
// User-overridable function which, for the given option, adds an
// item to the select. If the option doesn't have a value, then a
@@ -15370,7 +16163,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
// in the created option widget.
},
- _removeOptionItem: function(/* dijit.form.__SelectOption */ option){
+ _removeOptionItem: function(/*dijit.form.__SelectOption*/ option){
// summary:
// User-overridable function which, for the given option, removes
// its item from the select.
@@ -15396,7 +16189,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
return this.getOptions(this.get("value"));
},
- _pseudoLoadChildren: function(/* item[] */ items){
+ _pseudoLoadChildren: function(/*item[]*/ items){
// summary:
// a function that will "fake" loading children, if needed, and
// if we have set to not load children until the widget opens.
@@ -15419,6 +16212,7 @@ dojo._hasResource["dijit._KeyNavContainer"] = true;
dojo.provide("dijit._KeyNavContainer");
+
dojo.declare("dijit._KeyNavContainer",
dijit._Container,
{
@@ -15460,6 +16254,8 @@ dojo.declare("dijit._KeyNavContainer",
var next = dojo.hitch(this, this.focusNext);
dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; });
dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; });
+ keyCodes[dojo.keys.HOME] = dojo.hitch(this, "focusFirstChild");
+ keyCodes[dojo.keys.END] = dojo.hitch(this, "focusLastChild");
this.connect(this.domNode, "onkeypress", "_onContainerKeypress");
this.connect(this.domNode, "onfocus", "_onContainerFocus");
},
@@ -15496,6 +16292,17 @@ dojo.declare("dijit._KeyNavContainer",
}
},
+ focusLastChild: function(){
+ // summary:
+ // Focus the last focusable child in the container.
+ // tags:
+ // protected
+ var child = this._getLastFocusableChild();
+ if(child){ // edge case: Menu could be empty or hidden
+ this.focusChild(child);
+ }
+ },
+
focusNext: function(){
// summary:
// Focus the next widget
@@ -15529,15 +16336,16 @@ dojo.declare("dijit._KeyNavContainer",
if(this.focusedChild && widget !== this.focusedChild){
this._onChildBlur(this.focusedChild);
}
+ widget.set("tabIndex", this.tabIndex); // for IE focus outline to appear, must set tabIndex before focs
widget.focus(last ? "end" : "start");
- this.focusedChild = widget;
+ this._set("focusedChild", widget);
},
_startupChild: function(/*dijit._Widget*/ widget){
// summary:
// Setup for each child widget
// description:
- // Sets tabIndex=-1 on each child, so that the tab key will
+ // Sets tabIndex=-1 on each child, so that the tab key will
// leave the container rather than visiting each child.
// tags:
// private
@@ -15618,6 +16426,12 @@ dojo.declare("dijit._KeyNavContainer",
return this._getNextFocusableChild(null, 1); // dijit._Widget
},
+ _getLastFocusableChild: function(){
+ // summary:
+ // Returns last child that can be focused
+ return this._getNextFocusableChild(null, -1); // dijit._Widget
+ },
+
_getNextFocusableChild: function(child, dir){
// summary:
// Returns the next or previous focusable child, compared
@@ -15665,7 +16479,7 @@ dojo.declare("dijit.MenuItem",
// Make 3 columns
// icon, label, and expand arrow (BiDi-dependent) indicating sub-menu
- templateString: dojo.cache("dijit", "templates/MenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitem\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" waiRole=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" waiRole=\"presentation\">\n\t\t<div dojoAttachPoint=\"arrowWrapper\" style=\"visibility: hidden\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuExpand\"/>\n\t\t\t<span class=\"dijitMenuExpandA11y\">+</span>\n\t\t</div>\n\t</td>\n</tr>\n"),
+ templateString: dojo.cache("dijit", "templates/MenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" role=\"menuitem\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" role=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\">\n\t\t<div dojoAttachPoint=\"arrowWrapper\" style=\"visibility: hidden\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuExpand\"/>\n\t\t\t<span class=\"dijitMenuExpandA11y\">+</span>\n\t\t</div>\n\t</td>\n</tr>\n"),
attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
label: { node: "containerNode", type: "innerHTML" },
@@ -15702,9 +16516,8 @@ dojo.declare("dijit.MenuItem",
}
},
- postCreate: function(){
+ buildRendering: function(){
this.inherited(arguments);
- dojo.setSelectable(this.domNode, false);
var label = this.id+"_text";
dojo.attr(this.containerNode, "id", label);
if(this.accelKeyNode){
@@ -15712,6 +16525,7 @@ dojo.declare("dijit.MenuItem",
label += " " + this.id + "_accel";
}
dijit.setWaiState(this.domNode, "labelledby", label);
+ dojo.setSelectable(this.domNode, false);
},
_onHover: function(){
@@ -15734,11 +16548,10 @@ dojo.declare("dijit.MenuItem",
// then unselect it
this.getParent().onItemUnhover(this);
- // _onUnhover() is called when the menu is hidden (collapsed), due to clicking
- // a MenuItem and having it execut. When that happens, FF and IE don't generate
- // an onmouseout event for the MenuItem, so give _CssStateMixin some help
- this._hovering = false;
- this._setStateClass();
+ // When menu is hidden (collapsed) due to clicking a MenuItem and having it execute,
+ // FF and IE don't generate an onmouseout event for the MenuItem.
+ // So, help out _CssStateMixin in this case.
+ this._set("hovering", false);
},
_onClick: function(evt){
@@ -15823,19 +16636,21 @@ dojo.declare("dijit.MenuItem",
// summary:
// Hook for attr('disabled', ...) to work.
// Enable or disable this menu item.
- this.disabled = value;
+
dijit.setWaiState(this.focusNode, 'disabled', value ? 'true' : 'false');
+ this._set("disabled", value);
},
_setAccelKeyAttr: function(/*String*/ value){
// summary:
// Hook for attr('accelKey', ...) to work.
// Set accelKey on this menu item.
- this.accelKey=value;
this.accelKeyNode.style.display=value?"":"none";
this.accelKeyNode.innerHTML=value;
//have to use colSpan to make it work in IE
dojo.attr(this.containerNode,'colSpan',value?"1":"2");
+
+ this._set("accelKey", value);
}
});
@@ -15907,7 +16722,6 @@ dojo.declare("dijit.PopupMenuItem",
}
});
-
}
if(!dojo._hasResource["dijit.CheckedMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -15922,7 +16736,7 @@ dojo.declare("dijit.CheckedMenuItem",
// summary:
// A checkbox-like menu item for toggling on and off
- templateString: dojo.cache("dijit", "templates/CheckedMenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitemcheckbox\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" waiRole=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuItemIcon dijitCheckedMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t<span class=\"dijitCheckedMenuItemIconChar\">&#10003;</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" waiRole=\"presentation\">&nbsp;</td>\n</tr>\n"),
+ templateString: dojo.cache("dijit", "templates/CheckedMenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" role=\"menuitemcheckbox\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" role=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuItemIcon dijitCheckedMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t<span class=\"dijitCheckedMenuItemIconChar\">&#10003;</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\">&nbsp;</td>\n</tr>\n"),
// checked: Boolean
// Our checked state
@@ -15933,7 +16747,7 @@ dojo.declare("dijit.CheckedMenuItem",
// Sets the class and state for the check box.
dojo.toggleClass(this.domNode, "dijitCheckedMenuItemChecked", checked);
dijit.setWaiState(this.domNode, "checked", checked);
- this.checked = checked;
+ this._set("checked", checked);
},
onChange: function(/*Boolean*/ checked){
@@ -15974,7 +16788,8 @@ dojo.declare("dijit.MenuSeparator",
templateString: dojo.cache("dijit", "templates/MenuSeparator.html", "<tr class=\"dijitMenuSeparator\">\n\t<td class=\"dijitMenuSeparatorIconCell\">\n\t\t<div class=\"dijitMenuSeparatorTop\"></div>\n\t\t<div class=\"dijitMenuSeparatorBottom\"></div>\n\t</td>\n\t<td colspan=\"3\" class=\"dijitMenuSeparatorLabelCell\">\n\t\t<div class=\"dijitMenuSeparatorTop dijitMenuSeparatorLabel\"></div>\n\t\t<div class=\"dijitMenuSeparatorBottom\"></div>\n\t</td>\n</tr>\n"),
- postCreate: function(){
+ buildRendering: function(){
+ this.inherited(arguments);
dojo.setSelectable(this.domNode, false);
},
@@ -15988,7 +16803,6 @@ dojo.declare("dijit.MenuSeparator",
}
});
-
}
if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -16001,6 +16815,11 @@ dojo.provide("dijit.Menu");
+
+
+
+// "dijit/MenuItem", "dijit/PopupMenuItem", "dijit/CheckedMenuItem", "dijit/MenuSeparator" for Back-compat (TODO: remove in 2.0)
+
dojo.declare("dijit._MenuBase",
[dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
{
@@ -16277,8 +17096,7 @@ dojo.declare("dijit._MenuBase",
// menus (similar to TAB navigation) but the menu is not active
// (ie no dropdown) until an item is clicked.
this.isActive = true;
- dojo.addClass(this.domNode, "dijitMenuActive");
- dojo.removeClass(this.domNode, "dijitMenuPassive");
+ dojo.replaceClass(this.domNode, "dijitMenuActive", "dijitMenuPassive");
},
onOpen: function(/*Event*/ e){
@@ -16297,8 +17115,7 @@ dojo.declare("dijit._MenuBase",
// summary:
// Mark this menu's state as inactive.
this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here
- dojo.removeClass(this.domNode, "dijitMenuActive");
- dojo.addClass(this.domNode, "dijitMenuPassive");
+ dojo.replaceClass(this.domNode, "dijitMenuPassive", "dijitMenuActive");
},
onClose: function(){
@@ -16321,16 +17138,25 @@ dojo.declare("dijit._MenuBase",
// tags:
// private
this._stopPopupTimer();
- if(this.focusedChild){ // unhighlight the focused item
- this.focusedChild._setSelected(false);
- this.focusedChild._onUnhover();
- this.focusedChild = null;
- }
+
+ var fromItem = this.focusedChild && this.focusedChild.from_item;
+
if(this.currentPopup){
+ // If focus is on my child menu then move focus to me,
+ // because IE doesn't like it when you display:none a node with focus
+ if(dijit._curFocus && dojo.isDescendant(dijit._curFocus, this.currentPopup.domNode)){
+ this.focusedChild.focusNode.focus();
+ }
// Close all popups that are open and descendants of this menu
dijit.popup.close(this.currentPopup);
this.currentPopup = null;
}
+
+ if(this.focusedChild){ // unhighlight the focused item
+ this.focusedChild._setSelected(false);
+ this.focusedChild._onUnhover();
+ this.focusedChild = null;
+ }
},
_onItemFocus: function(/*MenuItem*/ item){
@@ -16381,7 +17207,7 @@ dojo.declare("dijit.Menu",
this._bindings = [];
},
- templateString: dojo.cache("dijit", "templates/Menu.html", "<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" waiRole=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\" cellspacing=0>\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"),
+ templateString: dojo.cache("dijit", "templates/Menu.html", "<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" role=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\" cellspacing=\"0\">\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"),
baseClass: "dijitMenu",
@@ -16408,7 +17234,7 @@ dojo.declare("dijit.Menu",
this.bindDomNode(dojo.body());
}else{
// TODO: should have _setTargetNodeIds() method to handle initialization and a possible
- // later attr('targetNodeIds', ...) call. There's also a problem that targetNodeIds[]
+ // later set('targetNodeIds', ...) call. There's also a problem that targetNodeIds[]
// gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610)
dojo.forEach(this.targetNodeIds, this.bindDomNode, this);
}
@@ -16521,7 +17347,7 @@ dojo.declare("dijit.Menu",
this._scheduleOpen(evt.target, iframe); // no coords - open near target node
}
})
- ];
+ ];
});
binding.connects = cn ? doConnects(cn) : [];
@@ -16687,13 +17513,6 @@ dojo.declare("dijit.Menu",
}
);
-// Back-compat (TODO: remove in 2.0)
-
-
-
-
-
-
}
if(!dojo._hasResource["dijit.form.Select"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -16706,7 +17525,6 @@ dojo.provide("dijit.form.Select");
-
dojo.declare("dijit.form._SelectMenu", dijit.Menu, {
// summary:
// An internally-used menu for dropdown that allows us a vertical scrollbar
@@ -16727,6 +17545,16 @@ dojo.declare("dijit.form._SelectMenu", dijit.Menu, {
dijit.setWaiRole(n,"presentation");
n.appendChild(o);
},
+
+ postCreate: function(){
+ // summary:
+ // stop mousemove from selecting text on IE to be consistent with other browsers
+
+ this.inherited(arguments);
+
+ this.connect(this.domNode, "onmousemove", dojo.stopEvent);
+ },
+
resize: function(/*Object*/ mb){
// summary:
// Overridden so that we are able to handle resizing our
@@ -16755,7 +17583,7 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
baseClass: "dijitSelect",
- templateString: dojo.cache("dijit.form", "templates/Select.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdojoAttachPoint=\"_buttonNode,tableNode,focusNode\" cellspacing='0' cellpadding='0'\n\twaiRole=\"combobox\" waiState=\"haspopup-true\"\n\t><tbody waiRole=\"presentation\"><tr waiRole=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents dijitButtonNode\" waiRole=\"presentation\"\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\" dojoAttachPoint=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} dojoAttachPoint=\"valueNode\" value=\"${value}\" waiState=\"hidden-true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdojoAttachPoint=\"titleNode\" waiRole=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" waiRole=\"presentation\">&#9660;</div\n\t\t></td\n\t></tr></tbody\n></table>\n"),
+ templateString: dojo.cache("dijit.form", "templates/Select.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdojoAttachPoint=\"_buttonNode,tableNode,focusNode\" cellspacing='0' cellpadding='0'\n\trole=\"combobox\" aria-haspopup=\"true\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents dijitButtonNode\" role=\"presentation\"\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\" dojoAttachPoint=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} dojoAttachPoint=\"valueNode\" value=\"${value}\" aria-hidden=\"true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdojoAttachPoint=\"titleNode\" role=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">&#9660;</div\n\t\t></td\n\t></tr></tbody\n></table>\n"),
// attributeMap: Object
// Add in our style to be applied to the focus node
@@ -16769,13 +17597,17 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
// Shows current state (ie, validation result) of input (Normal, Warning, or Error)
state: "",
+ // message: String
+ // Currently displayed error/prompt message
+ message: "",
+
// tooltipPosition: String[]
// See description of dijit.Tooltip.defaultPosition for details on this parameter.
tooltipPosition: [],
// emptyLabel: string
// What to display in an "empty" dropdown
- emptyLabel: "",
+ emptyLabel: "&nbsp;",
// _isLoaded: Boolean
// Whether or not we have been loaded
@@ -16789,11 +17621,11 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
// summary:
// Set the value to be the first, or the selected index
this.inherited(arguments);
+ // set value from selected option
if(this.options.length && !this.value && this.srcNodeRef){
- var si = this.srcNodeRef.selectedIndex;
- this.value = this.options[si != -1 ? si : 0].value;
+ var si = this.srcNodeRef.selectedIndex || 0; // || 0 needed for when srcNodeRef is not a SELECT
+ this.value = this.options[si >= 0 ? si : 0].value;
}
-
// Create the dropDown widget
this.dropDown = new dijit.form._SelectMenu({id: this.id + "_menu"});
dojo.addClass(this.dropDown.domNode, this.baseClass + "Menu");
@@ -16803,7 +17635,7 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
// summary:
// For the given option, return the menu item that should be
// used to display it. This can be overridden as needed
- if(!option.value){
+ if(!option.value && !option.label){
// We are a separator (no label set for it)
return new dijit.MenuSeparator();
}else{
@@ -16811,7 +17643,7 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
var click = dojo.hitch(this, "_setValueAttr", option);
var item = new dijit.MenuItem({
option: option,
- label: option.label,
+ label: option.label || this.emptyLabel,
onClick: click,
disabled: option.disabled || false
});
@@ -16865,7 +17697,6 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
this._updateSelection();
}
- var len = this.options.length;
this._isLoaded = false;
this._childrenLoaded = true;
@@ -16883,10 +17714,9 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
_setDisplay: function(/*String*/ newDisplay){
// summary:
// sets the display for the given value (or values)
- this.containerNode.innerHTML = '<span class="dijitReset dijitInline ' + this.baseClass + 'Label">' +
- (newDisplay || this.emptyLabel || "&nbsp;") +
- '</span>';
- dijit.setWaiState(this.focusNode, "valuetext", (newDisplay || this.emptyLabel || "&nbsp;") );
+ var lbl = newDisplay || this.emptyLabel;
+ this.containerNode.innerHTML = '<span class="dijitReset dijitInline ' + this.baseClass + 'Label">' + lbl + '</span>';
+ dijit.setWaiState(this.focusNode, "valuetext", lbl);
},
validate: function(/*Boolean*/ isFocused){
@@ -16898,12 +17728,11 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
// set the value.
var isValid = this.isValid(isFocused);
- this.state = isValid ? "" : "Error";
- this._setStateClass();
+ this._set("state", isValid ? "" : "Error");
dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true");
var message = isValid ? "" : this._missingMsg;
- if(this._message !== message){
- this._message = message;
+ if(this.message !== message){
+ this._set("message", message);
dijit.hideTooltip(this.domNode);
if(message){
dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
@@ -16914,9 +17743,9 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
isValid: function(/*Boolean*/ isFocused){
// summary:
- // Whether or not this is a valid value. The only way a Select
+ // Whether or not this is a valid value. The only way a Select
// can be invalid is when it's required but nothing is selected.
- return (!this.required || !(/^\s*$/.test(this.value)));
+ return (!this.required || this.value === 0 || !(/^\s*$/.test(this.value || ""))); // handle value is null or undefined
},
reset: function(){
@@ -16924,9 +17753,8 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
// Overridden so that the state will be cleared.
this.inherited(arguments);
dijit.hideTooltip(this.domNode);
- this.state = "";
- this._setStateClass();
- delete this._message;
+ this._set("state", "");
+ this._set("message", "")
},
postMixInProperties: function(){
@@ -16938,10 +17766,17 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD
},
postCreate: function(){
+ // summary:
+ // stop mousemove from selecting text on IE to be consistent with other browsers
+
this.inherited(arguments);
- if(this.tableNode.style.width){
- dojo.addClass(this.domNode, this.baseClass + "FixedWidth");
- }
+
+ this.connect(this.domNode, "onmousemove", dojo.stopEvent);
+ },
+
+ _setStyleAttr: function(/*String||Object*/ value){
+ this.inherited(arguments);
+ dojo.toggleClass(this.domNode, this.baseClass + "FixedWidth", !!this.tableNode.style.width);
},
isLoaded: function(){
@@ -17017,12 +17852,20 @@ dojo.declare("dijit.form.SimpleTextarea",
postMixInProperties: function(){
// Copy value from srcNodeRef, unless user specified a value explicitly (or there is no srcNodeRef)
+ // TODO: parser will handle this in 2.0
if(!this.value && this.srcNodeRef){
this.value = this.srcNodeRef.value;
}
this.inherited(arguments);
},
+ buildRendering: function(){
+ this.inherited(arguments);
+ if(dojo.isIE && this.cols){ // attribute selectors is not supported in IE6
+ dojo.addClass(this.textbox, "dijitTextAreaCols");
+ }
+ },
+
filter: function(/*String*/ value){
// Override TextBox.filter to deal with newlines... specifically (IIRC) this is for IE which writes newlines
// as \r\n instead of just \n
@@ -17032,13 +17875,6 @@ dojo.declare("dijit.form.SimpleTextarea",
return this.inherited(arguments);
},
- postCreate: function(){
- this.inherited(arguments);
- if(dojo.isIE && this.cols){ // attribute selectors is not supported in IE6
- dojo.addClass(this.textbox, "dijitTextAreaCols");
- }
- },
-
_previousValue: "",
_onInput: function(/*Event?*/ e){
// Override TextBox._onInput() to enforce maxLength restriction
@@ -17086,8 +17922,6 @@ dojo.provide("dijit.InlineEditBox");
-
-
dojo.declare("dijit.InlineEditBox",
dijit._Widget,
{
@@ -17131,12 +17965,12 @@ dojo.declare("dijit.InlineEditBox",
// rather than plain text (ex: `dijit.Editor`)
renderAsHtml: false,
- // editor: String
- // Class name for Editor widget
+ // editor: String|Function
+ // Class name (or reference to the Class) for Editor widget
editor: "dijit.form.TextBox",
- // editorWrapper: String
- // Class name for widget that wraps the editor widget, displaying save/cancel
+ // editorWrapper: String|Function
+ // Class name (or reference to the Class) for widget that wraps the editor widget, displaying save/cancel
// buttons.
editorWrapper: "dijit._InlineEditor",
@@ -17144,6 +17978,10 @@ dojo.declare("dijit.InlineEditBox",
// Set of parameters for editor, like {required: true}
editorParams: {},
+ // disabled: Boolean
+ // If true, clicking the InlineEditBox to edit it will have no effect.
+ disabled: false,
+
onChange: function(value){
// summary:
// Set this handler to be notified of changes to value.
@@ -17226,7 +18064,6 @@ dojo.declare("dijit.InlineEditBox",
// summary:
// Hook to make set("disabled", ...) work.
// Set disabled state of widget.
- this.disabled = disabled;
dijit.setWaiState(this.domNode, "disabled", disabled);
if(disabled){
this.displayNode.removeAttribute("tabIndex");
@@ -17234,6 +18071,7 @@ dojo.declare("dijit.InlineEditBox",
this.displayNode.setAttribute("tabIndex", 0);
}
dojo.toggleClass(this.displayNode, "dijitInlineEditBoxDisplayModeDisabled", disabled);
+ this._set("disabled", disabled);
},
_onMouseOver: function(){
@@ -17291,7 +18129,7 @@ dojo.declare("dijit.InlineEditBox",
var placeholder = dojo.create("span", null, this.domNode, "before");
// Create the editor wrapper (the thing that holds the editor widget and the save/cancel buttons)
- var ewc = dojo.getObject(this.editorWrapper);
+ var ewc = typeof this.editorWrapper == "string" ? dojo.getObject(this.editorWrapper) : this.editorWrapper;
this.wrapperWidget = new ewc({
value: this.value,
buttonSave: this.buttonSave,
@@ -17305,6 +18143,9 @@ dojo.declare("dijit.InlineEditBox",
save: dojo.hitch(this, "save"),
cancel: dojo.hitch(this, "cancel")
}, placeholder);
+ if(!this._started){
+ this.startup();
+ }
}
var ww = this.wrapperWidget;
@@ -17350,7 +18191,7 @@ dojo.declare("dijit.InlineEditBox",
},
destroy: function(){
- if(this.wrapperWidget){
+ if(this.wrapperWidget && !this.wrapperWidget._destroyed){
this.wrapperWidget.destroy();
delete this.wrapperWidget;
}
@@ -17387,9 +18228,6 @@ dojo.declare("dijit.InlineEditBox",
var value = ww.getValue();
this.set('value', value); // display changed, formatted value
- // tell the world that we have changed
- setTimeout(dojo.hitch(this, "onChange", value), 0); // setTimeout prevents browser freeze for long-running event handlers
-
this._showText(focus); // set focus as needed
},
@@ -17407,11 +18245,15 @@ dojo.declare("dijit.InlineEditBox",
// Hook to make set("value", ...) work.
// Inserts specified HTML value into this node, or an "input needed" character if node is blank.
- this.value = val = dojo.trim(val);
- if(!this.renderAsHtml){
- val = val.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;").replace(/\n/g, "<br>");
+ val = dojo.trim(val);
+ var renderVal = this.renderAsHtml ? val : val.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;").replace(/\n/g, "<br>");
+ this.displayNode.innerHTML = renderVal || this.noValueIndicator;
+ this._set("value", val);
+
+ if(this._started){
+ // tell the world that we have changed
+ setTimeout(dojo.hitch(this, "onChange", val), 0); // setTimeout prevents browser freeze for long-running event handlers
}
- this.displayNode.innerHTML = val || this.noValueIndicator;
},
getValue: function(){
@@ -17456,7 +18298,7 @@ dojo.declare(
// value: String
// Value as an HTML string or plain text string, depending on renderAsHTML flag
- templateString: dojo.cache("dijit", "templates/InlineEditBox.html", "<span dojoAttachPoint=\"editNode\" waiRole=\"presentation\" style=\"position: absolute; visibility:hidden\" class=\"dijitReset dijitInline\"\n\tdojoAttachEvent=\"onkeypress: _onKeyPress\"\n\t><span dojoAttachPoint=\"editorPlaceholder\"></span\n\t><span dojoAttachPoint=\"buttonContainer\"\n\t\t><button class='saveButton' dojoAttachPoint=\"saveButton\" dojoType=\"dijit.form.Button\" dojoAttachEvent=\"onClick:save\" label=\"${buttonSave}\"></button\n\t\t><button class='cancelButton' dojoAttachPoint=\"cancelButton\" dojoType=\"dijit.form.Button\" dojoAttachEvent=\"onClick:cancel\" label=\"${buttonCancel}\"></button\n\t></span\n></span>\n"),
+ templateString: dojo.cache("dijit", "templates/InlineEditBox.html", "<span data-dojo-attach-point=\"editNode\" role=\"presentation\" style=\"position: absolute; visibility:hidden\" class=\"dijitReset dijitInline\"\n\tdata-dojo-attach-event=\"onkeypress: _onKeyPress\"\n\t><span data-dojo-attach-point=\"editorPlaceholder\"></span\n\t><span data-dojo-attach-point=\"buttonContainer\"\n\t\t><button data-dojo-type=\"dijit.form.Button\" data-dojo-props=\"label: '${buttonSave}', 'class': 'saveButton'\"\n\t\t\tdata-dojo-attach-point=\"saveButton\" data-dojo-attach-event=\"onClick:save\"></button\n\t\t><button data-dojo-type=\"dijit.form.Button\" data-dojo-props=\"label: '${buttonCancel}', 'class': 'cancelButton'\"\n\t\t\tdata-dojo-attach-point=\"cancelButton\" data-dojo-attach-event=\"onClick:cancel\"></button\n\t></span\n></span>\n"),
widgetsInTemplate: true,
postMixInProperties: function(){
@@ -17467,9 +18309,11 @@ dojo.declare(
}, this);
},
- postCreate: function(){
+ buildRendering: function(){
+ this.inherited(arguments);
+
// Create edit widget in place in the template
- var cls = dojo.getObject(this.editor);
+ var cls = typeof this.editor == "string" ? dojo.getObject(this.editor) : this.editor;
// Copy the style from the source
// Don't copy ALL properties though, just the necessary/applicable ones.
@@ -17504,13 +18348,21 @@ dojo.declare(
lang: this.lang
});
editorParams[ "displayedValue" in cls.prototype ? "displayedValue" : "value"] = this.value;
- var ew = (this.editWidget = new cls(editorParams, this.editorPlaceholder));
+ this.editWidget = new cls(editorParams, this.editorPlaceholder);
if(this.inlineEditBox.autoSave){
// Remove the save/cancel buttons since saving is done by simply tabbing away or
// selecting a value from the drop down list
dojo.destroy(this.buttonContainer);
+ }
+ },
+ postCreate: function(){
+ this.inherited(arguments);
+
+ var ew = this.editWidget;
+
+ if(this.inlineEditBox.autoSave){
// Selecting a value from a drop down list causes an onChange event and then we save
this.connect(ew, "onChange", "_onChange");
@@ -17520,7 +18372,7 @@ dojo.declare(
this.connect(ew, "onKeyPress", "_onKeyPress");
}else{
// If possible, enable/disable save button based on whether the user has changed the value
- if("intermediateChanges" in cls.prototype){
+ if("intermediateChanges" in ew){
ew.set("intermediateChanges", true);
this.connect(ew, "onChange", "_onIntermediateChange");
this.saveButton.set("disabled", true);
@@ -17670,7 +18522,7 @@ dojo.__cookieProps = function(){
dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/props){
- // summary:
+ // summary:
// Get or set a cookie.
// description:
// If one argument is passed, returns the value of the cookie
@@ -17679,17 +18531,17 @@ dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/
// Name of the cookie
// value:
// Value for the cookie
- // props:
+ // props:
// Properties for the cookie
// example:
// set a cookie with the JSON-serialized contents of an object which
// will expire 5 days from now:
// | dojo.cookie("configObj", dojo.toJson(config), { expires: 5 });
- //
+ //
// example:
// de-serialize a cookie back into a JavaScript object:
// | var config = dojo.fromJson(dojo.cookie("configObj"));
- //
+ //
// example:
// delete a cookie:
// | dojo.cookie("configObj", null, {expires: -1});
@@ -17701,7 +18553,7 @@ dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/
props = props || {};
// FIXME: expires=0 seems to disappear right away, not on close? (FF3) Change docs?
var exp = props.expires;
- if(typeof exp == "number"){
+ if(typeof exp == "number"){
var d = new Date();
d.setTime(d.getTime() + exp*24*60*60*1000);
exp = props.expires = d;
@@ -17722,7 +18574,7 @@ dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/
dojo.cookie.isSupported = function(){
// summary:
// Use to determine if the current browser supports cookies or not.
- //
+ //
// Returns true if user allows cookies.
// Returns false if user doesn't allow cookies.
@@ -17758,7 +18610,7 @@ dojo.declare(
// Monitors the specified StackContainer, and whenever a page is
// added, deleted, or selected, updates itself accordingly.
- templateString: "<span wairole='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>",
+ templateString: "<span role='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>",
// containerId: [const] String
// The id of the page container that I point to
@@ -17768,11 +18620,19 @@ dojo.declare(
// The name of the button widget to create to correspond to each page
buttonWidget: "dijit.layout._StackButton",
- postCreate: function(){
- dijit.setWaiRole(this.domNode, "tablist");
-
+ constructor: function(){
this.pane2button = {}; // mapping from pane id to buttons
- this.pane2handles = {}; // mapping from pane id to this.connect() handles
+ this.pane2connects = {}; // mapping from pane id to this.connect() handles
+ this.pane2watches = {}; // mapping from pane id to watch() handles
+ },
+
+ buildRendering: function(){
+ this.inherited(arguments);
+ dijit.setWaiRole(this.domNode, "tablist"); // TODO: unneeded? it's in template above.
+ },
+
+ postCreate: function(){
+ this.inherited(arguments);
// Listen to notifications from StackContainer
this.subscribe(this.containerId+"-startup", "onStartup");
@@ -17822,22 +18682,25 @@ dojo.declare(
title: page.tooltip
});
dijit.setWaiState(button.focusNode,"selected", "false");
- this.pane2handles[page.id] = [
- this.connect(page, 'set', function(name, value){
- var buttonAttr = {
- title: 'label',
- showTitle: 'showLabel',
- iconClass: 'iconClass',
- closable: 'closeButton',
- tooltip: 'title'
- }[name];
- if(buttonAttr){
- button.set(buttonAttr, value);
- }
- }),
+
+
+ // map from page attribute to corresponding tab button attribute
+ var pageAttrList = ["title", "showTitle", "iconClass", "closable", "tooltip"],
+ buttonAttrList = ["label", "showLabel", "iconClass", "closeButton", "title"];
+
+ // watch() so events like page title changes are reflected in tab button
+ this.pane2watches[page.id] = dojo.map(pageAttrList, function(pageAttr, idx){
+ return page.watch(pageAttr, function(name, oldVal, newVal){
+ button.set(buttonAttrList[idx], newVal);
+ });
+ });
+
+ // connections so that clicking a tab button selects the corresponding page
+ this.pane2connects[page.id] = [
this.connect(button, 'onClick', dojo.hitch(this,"onButtonClick", page)),
this.connect(button, 'onClickCloseButton', dojo.hitch(this,"onCloseButtonClick", page))
];
+
this.addChild(button, insertIndex);
this.pane2button[page.id] = button;
page.controlButton = button; // this value might be overwritten if two tabs point to same container
@@ -17860,8 +18723,13 @@ dojo.declare(
// private
if(this._currentChild === page){ this._currentChild = null; }
- dojo.forEach(this.pane2handles[page.id], this.disconnect, this);
- delete this.pane2handles[page.id];
+
+ // disconnect/unwatch connections/watches related to page being removed
+ dojo.forEach(this.pane2connects[page.id], dojo.hitch(this, "disconnect"));
+ delete this.pane2connects[page.id];
+ dojo.forEach(this.pane2watches[page.id], function(w){ w.unwatch(); });
+ delete this.pane2watches[page.id];
+
var button = this.pane2button[page.id];
if(button){
this.removeChild(button);
@@ -17963,6 +18831,14 @@ dojo.declare(
case k.PAGE_DOWN:
if(e.ctrlKey){ forward = true; }
break;
+ case k.HOME:
+ case k.END:
+ var children = this.getChildren();
+ if(children && children.length){
+ children[e.charOrCode == k.HOME ? 0 : children.length-1].onClick();
+ }
+ dojo.stopEvent(e);
+ break;
case k.DELETE:
if(this._currentChild.closable){
this.onCloseButtonClick(this._currentChild);
@@ -17982,7 +18858,7 @@ dojo.declare(
}
}
}
- // handle page navigation
+ // handle next/previous page navigation (left/right arrow, etc.)
if(forward !== null){
this.adjacent(forward).onClick();
dojo.stopEvent(e);
@@ -18016,9 +18892,9 @@ dojo.declare("dijit.layout._StackButton",
// Probably we should be calling this.startupKeyNavChildren() instead.
tabIndex: "-1",
- postCreate: function(/*Event*/ evt){
- dijit.setWaiRole((this.focusNode || this.domNode), "tab");
+ buildRendering: function(/*Event*/ evt){
this.inherited(arguments);
+ dijit.setWaiRole((this.focusNode || this.domNode), "tab");
},
onClick: function(/*Event*/ evt){
@@ -18040,7 +18916,6 @@ dojo.declare("dijit.layout._StackButton",
}
});
-
}
if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -18052,6 +18927,7 @@ dojo.provide("dijit.layout.StackContainer");
+
dojo.declare(
"dijit.layout.StackContainer",
dijit.layout._LayoutWidget,
@@ -18085,10 +18961,14 @@ dojo.declare(
selectedChildWidget: null,
=====*/
- postCreate: function(){
+ buildRendering: function(){
this.inherited(arguments);
dojo.addClass(this.domNode, "dijitLayoutContainer");
dijit.setWaiRole(this.containerNode, "tabpanel");
+ },
+
+ postCreate: function(){
+ this.inherited(arguments);
this.connect(this.domNode, "onkeypress", this._onKeyPress);
},
@@ -18144,8 +19024,7 @@ dojo.declare(
this.inherited(arguments);
- dojo.removeClass(child.domNode, "dijitVisible");
- dojo.addClass(child.domNode, "dijitHidden");
+ dojo.replaceClass(child.domNode, "dijitHidden", "dijitVisible");
// remove the title attribute so it doesn't show up when i hover
// over a node
@@ -18218,17 +19097,19 @@ dojo.declare(
if(this.selectedChildWidget != page){
// Deselect old page and select new one
- this._transition(page, this.selectedChildWidget, animate);
- this.selectedChildWidget = page;
+ var d = this._transition(page, this.selectedChildWidget, animate);
+ this._set("selectedChildWidget", page);
dojo.publish(this.id+"-selectChild", [page]);
if(this.persist){
dojo.cookie(this.id + "_selectedChild", this.selectedChildWidget.id);
}
}
+
+ return d; // If child has an href, promise that fires when the child's href finishes loading
},
- _transition: function(/*dijit._Widget*/newWidget, /*dijit._Widget*/oldWidget){
+ _transition: function(/*dijit._Widget*/ newWidget, /*dijit._Widget*/ oldWidget, /*Boolean*/ animate){
// summary:
// Hide the old widget and display the new widget.
// Subclasses should override this.
@@ -18237,7 +19118,7 @@ dojo.declare(
if(oldWidget){
this._hideChild(oldWidget);
}
- this._showChild(newWidget);
+ var d = this._showChild(newWidget);
// Size the new widget, in case this is the first time it's being shown,
// or I have been resized since the last time it was shown.
@@ -18251,6 +19132,8 @@ dojo.declare(
newWidget.resize();
}
}
+
+ return d; // If child has an href, promise that fires when the child's href finishes loading
},
_adjacent: function(/*Boolean*/ forward){
@@ -18265,13 +19148,13 @@ dojo.declare(
forward: function(){
// summary:
// Advance to next page.
- this.selectChild(this._adjacent(true), true);
+ return this.selectChild(this._adjacent(true), true);
},
back: function(){
// summary:
// Go back to previous page.
- this.selectChild(this._adjacent(false), true);
+ return this.selectChild(this._adjacent(false), true);
},
_onKeyPress: function(e){
@@ -18289,24 +19172,24 @@ dojo.declare(
// summary:
// Show the specified child by changing it's CSS, and call _onShow()/onShow() so
// it can do any updates it needs regarding loading href's etc.
+ // returns:
+ // Promise that fires when page has finished showing, or true if there's no href
var children = this.getChildren();
page.isFirstChild = (page == children[0]);
page.isLastChild = (page == children[children.length-1]);
- page.selected = true;
+ page._set("selected", true);
- dojo.removeClass(page.domNode, "dijitHidden");
- dojo.addClass(page.domNode, "dijitVisible");
+ dojo.replaceClass(page.domNode, "dijitVisible", "dijitHidden");
- page._onShow();
+ return page._onShow() || true;
},
_hideChild: function(/*dijit._Widget*/ page){
// summary:
// Hide the specified child by changing it's CSS, and call _onHide() so
// it's notified.
- page.selected=false;
- dojo.removeClass(page.domNode, "dijitVisible");
- dojo.addClass(page.domNode, "dijitHidden");
+ page._set("selected", false);
+ dojo.replaceClass(page.domNode, "dijitHidden", "dijitVisible");
page.onHide();
},
@@ -18325,7 +19208,7 @@ dojo.declare(
}
},
- destroyDescendants: function(/*Boolean*/preserveDom){
+ destroyDescendants: function(/*Boolean*/ preserveDom){
dojo.forEach(this.getChildren(), function(child){
this.removeChild(child);
child.destroyRecursive(preserveDom);
@@ -18336,7 +19219,6 @@ dojo.declare(
// For back-compat, remove for 2.0
-
// These arguments can be specified for the children of a StackContainer.
// Since any widget can be specified as a StackContainer child, mix them
// into the base widget class. (This is a hack, but it's effective.)
@@ -18401,8 +19283,28 @@ dojo.provide("dijit.layout.AccordionContainer");
+//dojo.require("dijit.layout.AccordionPane "); // for back compat, remove for 2.0
- // for back compat, remove for 2.0
+// Design notes:
+//
+// An AccordionContainer is a StackContainer, but each child (typically ContentPane)
+// is wrapped in a _AccordionInnerContainer. This is hidden from the caller.
+//
+// The resulting markup will look like:
+//
+// <div class=dijitAccordionContainer>
+// <div class=dijitAccordionInnerContainer> (one pane)
+// <div class=dijitAccordionTitle> (title bar) ... </div>
+// <div class=dijtAccordionChildWrapper> (content pane) </div>
+// </div>
+// </div>
+//
+// Normally the dijtAccordionChildWrapper is hidden for all but one child (the shown
+// child), so the space for the content pane is all the title bars + the one dijtAccordionChildWrapper,
+// which on claro has a 1px border plus a 2px bottom margin.
+//
+// During animation there are two dijtAccordionChildWrapper's shown, so we need
+// to compensate for that.
dojo.declare(
"dijit.layout.AccordionContainer",
@@ -18428,17 +19330,18 @@ dojo.declare(
// The name of the widget used to display the title of each pane
buttonWidget: "dijit.layout._AccordionButton",
+/*=====
// _verticalSpace: Number
// Pixels of space available for the open pane
// (my content box size minus the cumulative size of all the title bars)
_verticalSpace: 0,
-
+=====*/
baseClass: "dijitAccordionContainer",
- postCreate: function(){
- this.domNode.style.overflow = "hidden";
+ buildRendering: function(){
this.inherited(arguments);
- dijit.setWaiRole(this.domNode, "tablist");
+ this.domNode.style.overflow = "hidden"; // TODO: put this in dijit.css
+ dijit.setWaiRole(this.domNode, "tablist"); // TODO: put this in template
},
startup: function(){
@@ -18452,20 +19355,6 @@ dojo.declare(
}
},
- _getTargetHeight: function(/* Node */ node){
- // summary:
- // For the given node, returns the height that should be
- // set to achieve our vertical space (subtract any padding
- // we may have).
- //
- // This is used by the animations.
- //
- // TODO: I don't think this works correctly in IE quirks when an elements
- // style.height including padding and borders
- var cs = dojo.getComputedStyle(node);
- return Math.max(this._verticalSpace - dojo._getPadBorderExtents(node, cs).h - dojo._getMarginExtents(node, cs).h, 0);
- },
-
layout: function(){
// Implement _LayoutWidget.layout() virtual method.
// Set the height of the open pane based on what room remains.
@@ -18474,25 +19363,31 @@ dojo.declare(
if(!openPane){ return;}
- var openPaneContainer = openPane._wrapperWidget.domNode,
- openPaneContainerMargin = dojo._getMarginExtents(openPaneContainer),
- openPaneContainerPadBorder = dojo._getPadBorderExtents(openPaneContainer),
+ // space taken up by title, plus wrapper div (with border/margin) for open pane
+ var wrapperDomNode = openPane._wrapperWidget.domNode,
+ wrapperDomNodeMargin = dojo._getMarginExtents(wrapperDomNode),
+ wrapperDomNodePadBorder = dojo._getPadBorderExtents(wrapperDomNode),
+ wrapperContainerNode = openPane._wrapperWidget.containerNode,
+ wrapperContainerNodeMargin = dojo._getMarginExtents(wrapperContainerNode),
+ wrapperContainerNodePadBorder = dojo._getPadBorderExtents(wrapperContainerNode),
mySize = this._contentBox;
// get cumulative height of all the unselected title bars
var totalCollapsedHeight = 0;
dojo.forEach(this.getChildren(), function(child){
if(child != openPane){
- totalCollapsedHeight += dojo.marginBox(child._wrapperWidget.domNode).h;
+ totalCollapsedHeight += dojo._getMarginSize(child._wrapperWidget.domNode).h;
}
});
- this._verticalSpace = mySize.h - totalCollapsedHeight - openPaneContainerMargin.h
- - openPaneContainerPadBorder.h - openPane._buttonWidget.getTitleHeight();
+ this._verticalSpace = mySize.h - totalCollapsedHeight - wrapperDomNodeMargin.h
+ - wrapperDomNodePadBorder.h - wrapperContainerNodeMargin.h - wrapperContainerNodePadBorder.h
+ - openPane._buttonWidget.getTitleHeight();
// Memo size to make displayed child
this._containerContentBox = {
h: this._verticalSpace,
- w: this._contentBox.w - openPaneContainerMargin.w - openPaneContainerPadBorder.w
+ w: this._contentBox.w - wrapperDomNodeMargin.w - wrapperDomNodePadBorder.w
+ - wrapperContainerNodeMargin.w - wrapperContainerNodePadBorder.w
};
if(openPane){
@@ -18516,7 +19411,7 @@ dojo.declare(
this.inherited(arguments);
},
- addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
+ addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
if(this._started){
// Adding a child to a started Accordion is complicated because children have
// wrapper widgets. Default code path (calling this.inherited()) would add
@@ -18532,7 +19427,7 @@ dojo.declare(
// Then stick the wrapper widget around the child widget
this._setupChild(child);
- // Code below copied from StackContainer
+ // Code below copied from StackContainer
dojo.publish(this.id+"-addChild", [child, insertIndex]);
this.layout();
if(!this.selectedChildWidget){
@@ -18548,9 +19443,15 @@ dojo.declare(
removeChild: function(child){
// Overrides _LayoutWidget.removeChild().
- // destroy wrapper widget first, before StackContainer.getChildren() call
- child._wrapperWidget.destroy();
- delete child._wrapperWidget;
+ // Destroy wrapper widget first, before StackContainer.getChildren() call.
+ // Replace wrapper widget with true child widget (ContentPane etc.).
+ // This step only happens if the AccordionContainer has been started; otherwise there's no wrapper.
+ if(child._wrapperWidget){
+ dojo.place(child.domNode, child._wrapperWidget.domNode, "after");
+ child._wrapperWidget.destroy();
+ delete child._wrapperWidget;
+ }
+
dojo.removeClass(child.domNode, "dijitHidden");
this.inherited(arguments);
@@ -18564,23 +19465,53 @@ dojo.declare(
},
destroy: function(){
+ if(this._animation){
+ this._animation.stop();
+ }
dojo.forEach(this.getChildren(), function(child){
- child._wrapperWidget.destroy();
+ // If AccordionContainer has been started, then each child has a wrapper widget which
+ // also needs to be destroyed.
+ if(child._wrapperWidget){
+ child._wrapperWidget.destroy();
+ }else{
+ child.destroyRecursive();
+ }
});
this.inherited(arguments);
},
- _transition: function(/*dijit._Widget?*/newWidget, /*dijit._Widget?*/oldWidget, /*Boolean*/ animate){
+ _showChild: function(child){
+ // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode
+ child._wrapperWidget.containerNode.style.display="block";
+ return this.inherited(arguments);
+ },
+
+ _hideChild: function(child){
+ // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode
+ child._wrapperWidget.containerNode.style.display="none";
+ this.inherited(arguments);
+ },
+
+ _transition: function(/*dijit._Widget?*/ newWidget, /*dijit._Widget?*/ oldWidget, /*Boolean*/ animate){
// Overrides StackContainer._transition() to provide sliding of title bars etc.
-//TODO: should be able to replace this with calls to slideIn/slideOut
- if(this._inTransition){ return; }
- var animations = [];
- var paneHeight = this._verticalSpace;
+ if(dojo.isIE < 8){
+ // workaround animation bugs by not animating; not worth supporting animation for IE6 & 7
+ animate = false;
+ }
+
+ if(this._animation){
+ // there's an in-progress animation. speedily end it so we can do the newly requested one
+ this._animation.stop(true);
+ delete this._animation;
+ }
+
+ var self = this;
+
if(newWidget){
newWidget._wrapperWidget.set("selected", true);
- this._showChild(newWidget); // prepare widget to be slid in
+ var d = this._showChild(newWidget); // prepare widget to be slid in
// Size the new widget, in case this is the first time it's being shown,
// or I have been resized since the last time it was shown.
@@ -18588,74 +19519,51 @@ dojo.declare(
if(this.doLayout && newWidget.resize){
newWidget.resize(this._containerContentBox);
}
-
- var newContents = newWidget.domNode;
- dojo.addClass(newContents, "dijitVisible");
- dojo.removeClass(newContents, "dijitHidden");
-
- if(animate){
- var newContentsOverflow = newContents.style.overflow;
- newContents.style.overflow = "hidden";
- animations.push(dojo.animateProperty({
- node: newContents,
- duration: this.duration,
- properties: {
- height: { start: 1, end: this._getTargetHeight(newContents) }
- },
- onEnd: function(){
- newContents.style.overflow = newContentsOverflow;
-
- // Kick IE to workaround layout bug, see #11415
- if(dojo.isIE){
- setTimeout(function(){
- dojo.removeClass(newContents.parentNode, "dijitAccordionInnerContainerFocused");
- setTimeout(function(){
- dojo.addClass(newContents.parentNode, "dijitAccordionInnerContainerFocused");
- }, 0);
- }, 0);
- }
- }
- }));
- }
}
+
if(oldWidget){
oldWidget._wrapperWidget.set("selected", false);
- var oldContents = oldWidget.domNode;
- if(animate){
- var oldContentsOverflow = oldContents.style.overflow;
- oldContents.style.overflow = "hidden";
- animations.push(dojo.animateProperty({
- node: oldContents,
- duration: this.duration,
- properties: {
- height: { start: this._getTargetHeight(oldContents), end: 1 }
- },
- onEnd: function(){
- dojo.addClass(oldContents, "dijitHidden");
- dojo.removeClass(oldContents, "dijitVisible");
- oldContents.style.overflow = oldContentsOverflow;
- if(oldWidget.onHide){
- oldWidget.onHide();
- }
- }
- }));
- }else{
- dojo.addClass(oldContents, "dijitHidden");
- dojo.removeClass(oldContents, "dijitVisible");
- if(oldWidget.onHide){
- oldWidget.onHide();
- }
+ if(!animate){
+ this._hideChild(oldWidget);
}
}
if(animate){
- this._inTransition = true;
- var combined = dojo.fx.combine(animations);
- combined.onEnd = dojo.hitch(this, function(){
- delete this._inTransition;
+ var newContents = newWidget._wrapperWidget.containerNode,
+ oldContents = oldWidget._wrapperWidget.containerNode;
+
+ // During the animation we will be showing two dijitAccordionChildWrapper nodes at once,
+ // which on claro takes up 4px extra space (compared to stable AccordionContainer).
+ // Have to compensate for that by immediately shrinking the pane being closed.
+ var wrapperContainerNode = newWidget._wrapperWidget.containerNode,
+ wrapperContainerNodeMargin = dojo._getMarginExtents(wrapperContainerNode),
+ wrapperContainerNodePadBorder = dojo._getPadBorderExtents(wrapperContainerNode),
+ animationHeightOverhead = wrapperContainerNodeMargin.h + wrapperContainerNodePadBorder.h;
+
+ oldContents.style.height = (self._verticalSpace - animationHeightOverhead) + "px";
+
+ this._animation = new dojo.Animation({
+ node: newContents,
+ duration: this.duration,
+ curve: [1, this._verticalSpace - animationHeightOverhead - 1],
+ onAnimate: function(value){
+ value = Math.floor(value); // avoid fractional values
+ newContents.style.height = value + "px";
+ oldContents.style.height = (self._verticalSpace - animationHeightOverhead - value) + "px";
+ },
+ onEnd: function(){
+ delete self._animation;
+ newContents.style.height = "auto";
+ oldWidget._wrapperWidget.containerNode.style.display = "none";
+ oldContents.style.height = "auto";
+ self._hideChild(oldWidget);
+ }
});
- combined.play();
- }
+ this._animation.onStop = this._animation.onEnd;
+ this._animation.play();
+ }
+
+ return d; // If child has an href, promise that fires when the widget has finished loading
},
// note: we are treating the container as controller here
@@ -18666,10 +19574,7 @@ dojo.declare(
// This is called from a handler on AccordionContainer.domNode
// (setup in StackContainer), and is also called directly from
// the click handler for accordion labels
- if(this._inTransition || this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){
- if(this._inTransition){
- dojo.stopEvent(e);
- }
+ if(this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){
return;
}
var k = dojo.keys,
@@ -18694,16 +19599,17 @@ dojo.declare("dijit.layout._AccordionInnerContainer",
// When other widgets are added as children to an AccordionContainer they are wrapped in
// this widget.
+/*=====
// buttonWidget: String
// Name of class to use to instantiate title
// (Wish we didn't have a separate widget for just the title but maintaining it
// for backwards compatibility, is it worth it?)
-/*=====
buttonWidget: null,
=====*/
+
+/*=====
// contentWidget: dijit._Widget
// Pointer to the real child widget
-/*=====
contentWidget: null,
=====*/
@@ -18713,7 +19619,15 @@ dojo.declare("dijit.layout._AccordionInnerContainer",
isContainer: true,
isLayoutContainer: true,
- buildRendering: function(){
+ buildRendering: function(){
+ // Builds a template like:
+ // <div class=dijitAccordionInnerContainer>
+ // Button
+ // <div class=dijitAccordionChildWrapper>
+ // ContentPane
+ // </div>
+ // </div>
+
// Create wrapper div, placed where the child is now
this.domNode = dojo.place("<div class='" + this.baseClass + "'>", this.contentWidget.domNode, "after");
@@ -18731,22 +19645,32 @@ dojo.declare("dijit.layout._AccordionInnerContainer",
parent: this.parent
})).placeAt(this.domNode);
- // and then the actual content widget (changing it from prior-sibling to last-child)
- dojo.place(this.contentWidget.domNode, this.domNode);
+ // and then the actual content widget (changing it from prior-sibling to last-child),
+ // wrapped by a <div class=dijitAccordionChildWrapper>
+ this.containerNode = dojo.place("<div class='dijitAccordionChildWrapper' style='display:none'>", this.domNode);
+ dojo.place(this.contentWidget.domNode, this.containerNode);
},
postCreate: function(){
this.inherited(arguments);
- this.connect(this.contentWidget, 'set', function(name, value){
- var mappedName = {title: "label", tooltip: "title", iconClass: "iconClass"}[name];
- if(mappedName){
- this.button.set(mappedName, value);
- }
- }, this);
+
+ // Map changes in content widget's title etc. to changes in the button
+ var button = this.button;
+ this._contentWidgetWatches = [
+ this.contentWidget.watch('title', dojo.hitch(this, function(name, oldValue, newValue){
+ button.set("label", newValue);
+ })),
+ this.contentWidget.watch('tooltip', dojo.hitch(this, function(name, oldValue, newValue){
+ button.set("title", newValue);
+ })),
+ this.contentWidget.watch('iconClass', dojo.hitch(this, function(name, oldValue, newValue){
+ button.set("iconClass", newValue);
+ }))
+ ];
},
_setSelectedAttr: function(/*Boolean*/ isSelected){
- this.selected = isSelected;
+ this._set("selected", isSelected);
this.button.set("selected", isSelected);
if(isSelected){
var cw = this.contentWidget;
@@ -18761,7 +19685,9 @@ dojo.declare("dijit.layout._AccordionInnerContainer",
destroy: function(){
this.button.destroyRecursive();
-
+
+ dojo.forEach(this._contentWidgetWatches || [], function(w){ w.unwatch(); });
+
delete this.contentWidget._buttonWidget;
delete this.contentWidget._wrapperWidget;
@@ -18783,7 +19709,7 @@ dojo.declare("dijit.layout._AccordionButton",
// tags:
// private
- templateString: dojo.cache("dijit.layout", "templates/AccordionButton.html", "<div dojoAttachEvent='onclick:_onTitleClick' class='dijitAccordionTitle'>\n\t<div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='onkeypress:_onTitleKeyPress'\n\t\t\tclass='dijitAccordionTitleFocus' wairole=\"tab\" waiState=\"expanded-false\"\n\t\t><span class='dijitInline dijitAccordionArrow' waiRole=\"presentation\"></span\n\t\t><span class='arrowTextUp' waiRole=\"presentation\">+</span\n\t\t><span class='arrowTextDown' waiRole=\"presentation\">-</span\n\t\t><img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' style=\"vertical-align: middle\" waiRole=\"presentation\"/>\n\t\t<span waiRole=\"presentation\" dojoAttachPoint='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n"),
+ templateString: dojo.cache("dijit.layout", "templates/AccordionButton.html", "<div dojoAttachEvent='onclick:_onTitleClick' class='dijitAccordionTitle'>\n\t<div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='onkeypress:_onTitleKeyPress'\n\t\t\tclass='dijitAccordionTitleFocus' role=\"tab\" aria-expanded=\"false\"\n\t\t><span class='dijitInline dijitAccordionArrow' role=\"presentation\"></span\n\t\t><span class='arrowTextUp' role=\"presentation\">+</span\n\t\t><span class='arrowTextDown' role=\"presentation\">-</span\n\t\t><img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' style=\"vertical-align: middle\" role=\"presentation\"/>\n\t\t<span role=\"presentation\" dojoAttachPoint='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n"),
attributeMap: dojo.mixin(dojo.clone(dijit.layout.ContentPane.prototype.attributeMap), {
label: {node: "titleTextNode", type: "innerHTML" },
title: {node: "titleTextNode", type: "attribute", attribute: "title"},
@@ -18800,18 +19726,18 @@ dojo.declare("dijit.layout._AccordionButton",
return this.parent;
},
- postCreate: function(){
+ buildRendering: function(){
this.inherited(arguments);
- dojo.setSelectable(this.domNode, false);
- var titleTextNodeId = dojo.attr(this.domNode,'id').replace(' ','_');
+ var titleTextNodeId = this.id.replace(' ','_');
dojo.attr(this.titleTextNode, "id", titleTextNodeId+"_title");
dijit.setWaiState(this.focusNode, "labelledby", dojo.attr(this.titleTextNode, "id"));
+ dojo.setSelectable(this.domNode, false);
},
getTitleHeight: function(){
// summary:
// Returns the height of the title dom node.
- return dojo.marginBox(this.domNode).h; // Integer
+ return dojo._getMarginSize(this.domNode).h; // Integer
},
// TODO: maybe the parent should set these methods directly rather than forcing the code
@@ -18820,10 +19746,8 @@ dojo.declare("dijit.layout._AccordionButton",
// summary:
// Callback when someone clicks my title.
var parent = this.getParent();
- if(!parent._inTransition){
parent.selectChild(this.contentWidget, true);
dijit.focus(this.focusNode);
- }
},
_onTitleKeyPress: function(/*Event*/ evt){
@@ -18831,7 +19755,7 @@ dojo.declare("dijit.layout._AccordionButton",
},
_setSelectedAttr: function(/*Boolean*/ isSelected){
- this.selected = isSelected;
+ this._set("selected", isSelected);
dijit.setWaiState(this.focusNode, "expanded", isSelected);
dijit.setWaiState(this.focusNode, "selected", isSelected);
this.focusNode.setAttribute("tabIndex", isSelected ? "0" : "-1");
@@ -18847,6 +19771,7 @@ dojo.provide("dijit.layout.BorderContainer");
+
dojo.declare(
"dijit.layout.BorderContainer",
dijit.layout._LayoutWidget,
@@ -18862,19 +19787,21 @@ dojo.declare(
// include optional splitters (splitter="true") to make them resizable by the user. The remaining
// space is designated for the center region.
//
- // NOTE: Splitters must not be more than 50 pixels in width.
- //
// The outer size must be specified on the BorderContainer node. Width must be specified for the sides
// and height for the top and bottom, respectively. No dimensions should be specified on the center;
// it will fill the remaining space. Regions named "leading" and "trailing" may be used just like
// "left" and "right" except that they will be reversed in right-to-left environments.
//
+ // For complex layouts, multiple children can be specified for a single region. In this case, the
+ // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority)
+ // and which child is closer to the center (high layoutPriority). layoutPriority can also be used
+ // instead of the design attribute to conrol layout precedence of horizontal vs. vertical panes.
// example:
// | <div dojoType="dijit.layout.BorderContainer" design="sidebar" gutters="false"
// | style="width: 400px; height: 300px;">
- // | <div dojoType="ContentPane" region="top">header text</div>
- // | <div dojoType="ContentPane" region="right" splitter="true" style="width: 200px;">table of contents</div>
- // | <div dojoType="ContentPane" region="center">client area</div>
+ // | <div dojoType="dijit.layout.ContentPane" region="top">header text</div>
+ // | <div dojoType="dijit.layout.ContentPane" region="right" splitter="true" style="width: 200px;">table of contents</div>
+ // | <div dojoType="dijit.layout.ContentPane" region="center">client area</div>
// | </div>
// design: String
@@ -18884,13 +19811,13 @@ dojo.declare(
// - "sidebar" where the left and right sides extend from top to bottom.
design: "headline",
- // gutters: Boolean
+ // gutters: [const] Boolean
// Give each pane a border and margin.
// Margin determined by domNode.paddingLeft.
// When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing.
gutters: true,
- // liveSplitters: Boolean
+ // liveSplitters: [const] Boolean
// Specifies whether splitters resize as you drag (true) or only upon mouseup (false)
liveSplitters: true,
@@ -18913,13 +19840,6 @@ dojo.declare(
this.inherited(arguments);
},
- postCreate: function(){
- this.inherited(arguments);
-
- this._splitters = {};
- this._splitterThickness = {};
- },
-
startup: function(){
if(this._started){ return; }
dojo.forEach(this.getChildren(), this._setupChild, this);
@@ -18939,14 +19859,10 @@ dojo.declare(
if(region == "leading"){ region = ltr ? "left" : "right"; }
if(region == "trailing"){ region = ltr ? "right" : "left"; }
- //FIXME: redundant?
- this["_"+region] = child.domNode;
- this["_"+region+"Widget"] = child;
-
// Create draggable splitter for resizing pane,
// or alternately if splitter=false but BorderContainer.gutters=true then
// insert dummy div just for spacing
- if((child.splitter || this.gutters) && !this._splitters[region]){
+ if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){
var _Splitter = dojo.getObject(child.splitter ? this._splitterClass : "dijit.layout._Gutter");
var splitter = new _Splitter({
id: child.id + "_splitter",
@@ -18956,24 +19872,19 @@ dojo.declare(
live: this.liveSplitters
});
splitter.isSplitter = true;
- this._splitters[region] = splitter.domNode;
- dojo.place(this._splitters[region], child.domNode, "after");
+ child._splitterWidget = splitter;
- // Splitters arent added as Contained children, so we need to call startup explicitly
+ dojo.place(splitter.domNode, child.domNode, "after");
+
+ // Splitters aren't added as Contained children, so we need to call startup explicitly
splitter.startup();
}
- child.region = region;
+ child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc.
}
},
- _computeSplitterThickness: function(region){
- this._splitterThickness[region] = this._splitterThickness[region] ||
- dojo.marginBox(this._splitters[region])[(/top|bottom/.test(region) ? 'h' : 'w')];
- },
-
layout: function(){
// Implement _LayoutWidget.layout() virtual method.
- for(var region in this._splitters){ this._computeSplitterThickness(region); }
this._layoutChildren();
},
@@ -18987,20 +19898,29 @@ dojo.declare(
removeChild: function(/*dijit._Widget*/ child){
// Override _LayoutWidget.removeChild().
+
var region = child.region;
- var splitter = this._splitters[region];
+ var splitter = child._splitterWidget
if(splitter){
- dijit.byNode(splitter).destroy();
- delete this._splitters[region];
- delete this._splitterThickness[region];
+ splitter.destroy();
+ delete child._splitterWidget;
}
this.inherited(arguments);
- delete this["_"+region];
- delete this["_" +region+"Widget"];
+
if(this._started){
this._layoutChildren();
}
+ // Clean up whatever style changes we made to the child pane.
+ // Unclear how height and width should be handled.
dojo.removeClass(child.domNode, this.baseClass+"Pane");
+ dojo.style(child.domNode, {
+ top: "auto",
+ bottom: "auto",
+ left: "auto",
+ right: "auto",
+ position: "static"
+ });
+ dojo.style(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto");
},
getChildren: function(){
@@ -19010,11 +19930,15 @@ dojo.declare(
});
},
+ // TODO: remove in 2.0
getSplitter: function(/*String*/region){
// summary:
// Returns the widget responsible for rendering the splitter associated with region
- var splitter = this._splitters[region];
- return splitter ? dijit.byNode(splitter) : null;
+ // tags:
+ // deprecated
+ return dojo.filter(this.getChildren(), function(child){
+ return child.region == region;
+ })[0]._splitterWidget;
},
resize: function(newSize, currentSize){
@@ -19035,7 +19959,7 @@ dojo.declare(
this.inherited(arguments);
},
- _layoutChildren: function(/*String?*/changedRegion, /*Number?*/ changedRegionSize){
+ _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){
// summary:
// This is the main routine for setting size/position of each child.
// description:
@@ -19045,11 +19969,10 @@ dojo.declare(
// With changedRegion specified (as "left", "top", "bottom", or "right"),
// it changes that region's width/height to changedRegionSize and
// then resizes other regions that were affected.
- // changedRegion:
- // The region should be changed because splitter was dragged.
- // "left", "right", "top", or "bottom".
- // changedRegionSize:
- // The new width/height (in pixels) to make changedRegion
+ // changedChildId:
+ // Id of the child which should be resized because splitter was dragged.
+ // changedChildSize:
+ // The new width/height (in pixels) to make specified child
if(!this._borderBox || !this._borderBox.h){
// We are currently hidden, or we haven't been sized by our parent yet.
@@ -19057,196 +19980,63 @@ dojo.declare(
return;
}
- var sidebarLayout = (this.design == "sidebar");
- var topHeight = 0, bottomHeight = 0, leftWidth = 0, rightWidth = 0;
- var topStyle = {}, leftStyle = {}, rightStyle = {}, bottomStyle = {},
- centerStyle = (this._center && this._center.style) || {};
-
- var changedSide = /left|right/.test(changedRegion);
-
- var layoutSides = !changedRegion || (!changedSide && !sidebarLayout);
- var layoutTopBottom = !changedRegion || (changedSide && sidebarLayout);
-
- // Ask browser for width/height of side panes.
- // Would be nice to cache this but height can change according to width
- // (because words wrap around). I don't think width will ever change though
- // (except when the user drags a splitter).
- if(this._top){
- topStyle = (changedRegion == "top" || layoutTopBottom) && this._top.style;
- topHeight = changedRegion == "top" ? changedRegionSize : dojo.marginBox(this._top).h;
- }
- if(this._left){
- leftStyle = (changedRegion == "left" || layoutSides) && this._left.style;
- leftWidth = changedRegion == "left" ? changedRegionSize : dojo.marginBox(this._left).w;
- }
- if(this._right){
- rightStyle = (changedRegion == "right" || layoutSides) && this._right.style;
- rightWidth = changedRegion == "right" ? changedRegionSize : dojo.marginBox(this._right).w;
- }
- if(this._bottom){
- bottomStyle = (changedRegion == "bottom" || layoutTopBottom) && this._bottom.style;
- bottomHeight = changedRegion == "bottom" ? changedRegionSize : dojo.marginBox(this._bottom).h;
- }
-
- var splitters = this._splitters;
- var topSplitter = splitters.top, bottomSplitter = splitters.bottom,
- leftSplitter = splitters.left, rightSplitter = splitters.right;
- var splitterThickness = this._splitterThickness;
- var topSplitterThickness = splitterThickness.top || 0,
- leftSplitterThickness = splitterThickness.left || 0,
- rightSplitterThickness = splitterThickness.right || 0,
- bottomSplitterThickness = splitterThickness.bottom || 0;
-
- // Check for race condition where CSS hasn't finished loading, so
- // the splitter width == the viewport width (#5824)
- if(leftSplitterThickness > 50 || rightSplitterThickness > 50){
- setTimeout(dojo.hitch(this, function(){
- // Results are invalid. Clear them out.
- this._splitterThickness = {};
-
- for(var region in this._splitters){
- this._computeSplitterThickness(region);
+ // Generate list of wrappers of my children in the order that I want layoutChildren()
+ // to process them (i.e. from the outside to the inside)
+ var wrappers = dojo.map(this.getChildren(), function(child, idx){
+ return {
+ pane: child,
+ weight: [
+ child.region == "center" ? Infinity : 0,
+ child.layoutPriority,
+ (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1),
+ idx
+ ]
+ };
+ }, this);
+ wrappers.sort(function(a, b){
+ var aw = a.weight, bw = b.weight;
+ for(var i=0; i<aw.length; i++){
+ if(aw[i] != bw[i]){
+ return aw[i] - bw[i];
}
- this._layoutChildren();
- }), 50);
- return false;
- }
-
- var pe = this.pe;
-
- var splitterBounds = {
- left: (sidebarLayout ? leftWidth + leftSplitterThickness: 0) + pe.l + "px",
- right: (sidebarLayout ? rightWidth + rightSplitterThickness: 0) + pe.r + "px"
- };
-
- if(topSplitter){
- dojo.mixin(topSplitter.style, splitterBounds);
- topSplitter.style.top = topHeight + pe.t + "px";
- }
-
- if(bottomSplitter){
- dojo.mixin(bottomSplitter.style, splitterBounds);
- bottomSplitter.style.bottom = bottomHeight + pe.b + "px";
- }
-
- splitterBounds = {
- top: (sidebarLayout ? 0 : topHeight + topSplitterThickness) + pe.t + "px",
- bottom: (sidebarLayout ? 0 : bottomHeight + bottomSplitterThickness) + pe.b + "px"
- };
-
- if(leftSplitter){
- dojo.mixin(leftSplitter.style, splitterBounds);
- leftSplitter.style.left = leftWidth + pe.l + "px";
- }
-
- if(rightSplitter){
- dojo.mixin(rightSplitter.style, splitterBounds);
- rightSplitter.style.right = rightWidth + pe.r + "px";
- }
-
- dojo.mixin(centerStyle, {
- top: pe.t + topHeight + topSplitterThickness + "px",
- left: pe.l + leftWidth + leftSplitterThickness + "px",
- right: pe.r + rightWidth + rightSplitterThickness + "px",
- bottom: pe.b + bottomHeight + bottomSplitterThickness + "px"
+ }
+ return 0;
});
- var bounds = {
- top: sidebarLayout ? pe.t + "px" : centerStyle.top,
- bottom: sidebarLayout ? pe.b + "px" : centerStyle.bottom
- };
- dojo.mixin(leftStyle, bounds);
- dojo.mixin(rightStyle, bounds);
- leftStyle.left = pe.l + "px"; rightStyle.right = pe.r + "px"; topStyle.top = pe.t + "px"; bottomStyle.bottom = pe.b + "px";
- if(sidebarLayout){
- topStyle.left = bottomStyle.left = leftWidth + leftSplitterThickness + pe.l + "px";
- topStyle.right = bottomStyle.right = rightWidth + rightSplitterThickness + pe.r + "px";
- }else{
- topStyle.left = bottomStyle.left = pe.l + "px";
- topStyle.right = bottomStyle.right = pe.r + "px";
- }
-
- // More calculations about sizes of panes
- var containerHeight = this._borderBox.h - pe.t - pe.b,
- middleHeight = containerHeight - ( topHeight + topSplitterThickness + bottomHeight + bottomSplitterThickness),
- sidebarHeight = sidebarLayout ? containerHeight : middleHeight;
-
- var containerWidth = this._borderBox.w - pe.l - pe.r,
- middleWidth = containerWidth - (leftWidth + leftSplitterThickness + rightWidth + rightSplitterThickness),
- sidebarWidth = sidebarLayout ? middleWidth : containerWidth;
+ // Make new list, combining the externally specified children with splitters and gutters
+ var childrenAndSplitters = [];
+ dojo.forEach(wrappers, function(wrapper){
+ var pane = wrapper.pane;
+ childrenAndSplitters.push(pane);
+ if(pane._splitterWidget){
+ childrenAndSplitters.push(pane._splitterWidget);
+ }
+ });
- // New margin-box size of each pane
+ // Compute the box in which to lay out my children
var dim = {
- top: { w: sidebarWidth, h: topHeight },
- bottom: { w: sidebarWidth, h: bottomHeight },
- left: { w: leftWidth, h: sidebarHeight },
- right: { w: rightWidth, h: sidebarHeight },
- center: { h: middleHeight, w: middleWidth }
+ l: this.pe.l,
+ t: this.pe.t,
+ w: this._borderBox.w - this.pe.w,
+ h: this._borderBox.h - this.pe.h
};
- if(changedRegion){
- // Respond to splitter drag event by changing changedRegion's width or height
- var child = this["_" + changedRegion + "Widget"],
- mb = {};
- mb[ /top|bottom/.test(changedRegion) ? "h" : "w"] = changedRegionSize;
- child.resize ? child.resize(mb, dim[child.region]) : dojo.marginBox(child.domNode, mb);
- }
+ // Layout the children, possibly changing size due to a splitter drag
+ dijit.layout.layoutChildren(this.domNode, dim, childrenAndSplitters,
+ changedChildId, changedChildSize);
+ },
- // Nodes in IE<8 don't respond to t/l/b/r, and TEXTAREA doesn't respond in any browser
- var janky = dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.some(this.getChildren(), function(child){
- return child.domNode.tagName == "TEXTAREA" || child.domNode.tagName == "INPUT";
+ destroyRecursive: function(){
+ // Destroy splitters first, while getChildren() still works
+ dojo.forEach(this.getChildren(), function(child){
+ var splitter = child._splitterWidget;
+ if(splitter){
+ splitter.destroy();
+ }
+ delete child._splitterWidget;
});
- if(janky){
- // Set the size of the children the old fashioned way, by setting
- // CSS width and height
-
- var resizeWidget = function(widget, changes, result){
- if(widget){
- (widget.resize ? widget.resize(changes, result) : dojo.marginBox(widget.domNode, changes));
- }
- };
-
- if(leftSplitter){ leftSplitter.style.height = sidebarHeight; }
- if(rightSplitter){ rightSplitter.style.height = sidebarHeight; }
- resizeWidget(this._leftWidget, {h: sidebarHeight}, dim.left);
- resizeWidget(this._rightWidget, {h: sidebarHeight}, dim.right);
-
- if(topSplitter){ topSplitter.style.width = sidebarWidth; }
- if(bottomSplitter){ bottomSplitter.style.width = sidebarWidth; }
- resizeWidget(this._topWidget, {w: sidebarWidth}, dim.top);
- resizeWidget(this._bottomWidget, {w: sidebarWidth}, dim.bottom);
-
- resizeWidget(this._centerWidget, dim.center);
- }else{
- // Calculate which panes need a notification that their size has been changed
- // (we've already set style.top/bottom/left/right on those other panes).
- var notifySides = !changedRegion || (/top|bottom/.test(changedRegion) && this.design != "sidebar"),
- notifyTopBottom = !changedRegion || (/left|right/.test(changedRegion) && this.design == "sidebar"),
- notifyList = {
- center: true,
- left: notifySides,
- right: notifySides,
- top: notifyTopBottom,
- bottom: notifyTopBottom
- };
-
- // Send notification to those panes that have changed size
- dojo.forEach(this.getChildren(), function(child){
- if(child.resize && notifyList[child.region]){
- child.resize(null, dim[child.region]);
- }
- }, this);
- }
- },
- destroy: function(){
- for(var region in this._splitters){
- var splitter = this._splitters[region];
- dijit.byNode(splitter).destroy();
- dojo.destroy(splitter);
- }
- delete this._splitters;
- delete this._splitterThickness;
+ // Then destroy the real children, and myself
this.inherited(arguments);
}
});
@@ -19261,6 +20051,12 @@ dojo.extend(dijit._Widget, {
// See the `dijit.layout.BorderContainer` description for details.
region: '',
+ // layoutPriority: [const] Number
+ // Parameter for children of `dijit.layout.BorderContainer`.
+ // Children with a higher layoutPriority will be placed closer to the BorderContainer center,
+ // between children with a lower layoutPriority.
+ layoutPriority: 0,
+
// splitter: [const] Boolean
// Parameter for child of `dijit.layout.BorderContainer` where region != "center".
// If true, enables user to resize the widget by putting a draggable splitter between
@@ -19278,8 +20074,6 @@ dojo.extend(dijit._Widget, {
maxSize: Infinity
});
-
-
dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
{
// summary:
@@ -19299,7 +20093,7 @@ dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
// Pointer to the pane associated with this splitter
child: null,
- // region: String
+ // region: [const] String
// Region of pane associated with this splitter.
// "top", "bottom", "left", "right".
region: null,
@@ -19310,18 +20104,21 @@ dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
// otherwise, the size doesn't change until you drop the splitter (by mouse-up)
live: true,
- templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" waiRole="separator"><div class="dijitSplitterThumb"></div></div>',
+ templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>',
- postCreate: function(){
+ postMixInProperties: function(){
this.inherited(arguments);
- this.horizontal = /top|bottom/.test(this.region);
- dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V"));
-// dojo.addClass(this.child.domNode, "dijitSplitterPane");
-// dojo.setSelectable(this.domNode, false); //TODO is this necessary?
+ this.horizontal = /top|bottom/.test(this.region);
this._factor = /top|left/.test(this.region) ? 1 : -1;
-
this._cookieName = this.container.id + "_" + this.region;
+ },
+
+ buildRendering: function(){
+ this.inherited(arguments);
+
+ dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V"));
+
if(this.container.persist){
// restore old size
var persistSize = dojo.cookie(this._cookieName);
@@ -19333,23 +20130,14 @@ dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
_computeMaxSize: function(){
// summary:
- // Compute the maximum size that my corresponding pane can be set to
+ // Return the maximum size that my corresponding pane can be set to
var dim = this.horizontal ? 'h' : 'w',
- thickness = this.container._splitterThickness[this.region];
-
- // Get DOMNode of opposite pane, if an opposite pane exists.
- // Ex: if I am the _Splitter for the left pane, then get the right pane.
- var flip = {left:'right', right:'left', top:'bottom', bottom:'top', leading:'trailing', trailing:'leading'},
- oppNode = this.container["_" + flip[this.region]];
-
- // I can expand up to the edge of the opposite pane, or if there's no opposite pane, then to
- // edge of BorderContainer
- var available = dojo.contentBox(this.container.domNode)[dim] -
- (oppNode ? dojo.marginBox(oppNode)[dim] : 0) -
- 20 - thickness * 2;
+ childSize = dojo.marginBox(this.child.domNode)[dim],
+ center = dojo.filter(this.container.getChildren(), function(child){ return child.region == "center";})[0],
+ spaceAvailable = dojo.marginBox(center.domNode)[dim]; // can expand until center is crushed to 0
- return Math.min(this.child.maxSize, available);
+ return Math.min(this.child.maxSize, childSize + spaceAvailable);
},
_startDrag: function(e){
@@ -19368,28 +20156,26 @@ dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
dojo.addClass(this.domNode, "dijitSplitterShadow");
dojo.place(this.fake, this.domNode, "after");
}
- dojo.addClass(this.domNode, "dijitSplitterActive");
- dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
+ dojo.addClass(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
if(this.fake){
- dojo.removeClass(this.fake, "dijitSplitterHover");
- dojo.removeClass(this.fake, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover");
+ dojo.removeClass(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover");
}
//Performance: load data info local vars for onmousevent function closure
var factor = this._factor,
- max = this._computeMaxSize(),
- min = this.child.minSize || 20,
isHorizontal = this.horizontal,
axis = isHorizontal ? "pageY" : "pageX",
pageStart = e[axis],
splitterStyle = this.domNode.style,
dim = isHorizontal ? 'h' : 'w',
childStart = dojo.marginBox(this.child.domNode)[dim],
+ max = this._computeMaxSize(),
+ min = this.child.minSize || 20,
region = this.region,
- splitterStart = parseInt(this.domNode.style[region], 10),
+ splitterAttr = region == "top" || region == "bottom" ? "top" : "left", // style attribute of splitter to adjust
+ splitterStart = parseInt(splitterStyle[splitterAttr], 10),
resize = this._resize,
- childNode = this.child.domNode,
- layoutFunc = dojo.hitch(this.container, this.container._layoutChildren),
+ layoutFunc = dojo.hitch(this.container, "_layoutChildren", this.child.id),
de = dojo.doc;
this._handlers = (this._handlers || []).concat([
@@ -19399,9 +20185,10 @@ dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
boundChildSize = Math.max(Math.min(childSize, max), min);
if(resize || forceResize){
- layoutFunc(region, boundChildSize);
+ layoutFunc(boundChildSize);
}
- splitterStyle[region] = factor * delta + splitterStart + (boundChildSize - childSize) + "px";
+ // TODO: setting style directly (usually) sets content box size, need to set margin box size
+ splitterStyle[splitterAttr] = delta + splitterStart + factor*(boundChildSize - childSize) + "px";
}),
dojo.connect(de, "ondragstart", dojo.stopEvent),
dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent),
@@ -19422,9 +20209,8 @@ dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
dojo.removeClass(this.cover, "dijitSplitterCoverActive");
}
if(this.fake){ dojo.destroy(this.fake); }
- dojo.removeClass(this.domNode, "dijitSplitterActive");
- dojo.removeClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
- dojo.removeClass(this.domNode, "dijitSplitterShadow");
+ dojo.removeClass(this.domNode, "dijitSplitterActive dijitSplitter"
+ + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow");
this._drag(e); //TODO: redundant with onmousemove?
this._drag(e, true);
}finally{
@@ -19458,8 +20244,8 @@ dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
// this.inherited(arguments);
return;
}
- var childSize = dojo.marginBox(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick;
- this.container._layoutChildren(this.region, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize));
+ var childSize = dojo._getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick;
+ this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize));
dojo.stopEvent(e);
},
@@ -19473,7 +20259,7 @@ dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
}
});
-dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated ],
+dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated],
{
// summary:
// Just a spacer div to separate side pane from center pane.
@@ -19484,10 +20270,15 @@ dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated ],
// tags:
// private
- templateString: '<div class="dijitGutter" waiRole="presentation"></div>',
+ templateString: '<div class="dijitGutter" role="presentation"></div>',
- postCreate: function(){
+ postMixInProperties: function(){
+ this.inherited(arguments);
this.horizontal = /top|bottom/.test(this.region);
+ },
+
+ buildRendering: function(){
+ this.inherited(arguments);
dojo.addClass(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V"));
}
});
@@ -19519,12 +20310,12 @@ dojo.declare("dijit.layout._TabContainerBase",
baseClass: "dijitTabContainer",
- // tabStrip: Boolean
+ // tabStrip: [const] Boolean
// Defines whether the tablist gets an extra class for layouting, putting a border/shading
- // around the set of tabs.
+ // around the set of tabs. Not supported by claro theme.
tabStrip: false,
- // nested: Boolean
+ // nested: [const] Boolean
// If true, use styling for a TabContainer nested inside another TabContainer.
// For tundra etc., makes tabs look like links, and hides the outer
// border since the outer TabContainer already has a border.
@@ -19541,7 +20332,7 @@ dojo.declare("dijit.layout._TabContainerBase",
this.inherited(arguments);
},
- postCreate: function(){
+ buildRendering: function(){
this.inherited(arguments);
// Create the tab list that will have a tab (a.k.a. tab button) for each tab panel
@@ -19608,7 +20399,12 @@ dojo.declare("dijit.layout._TabContainerBase",
}else{
// just layout the tab controller, so it can position left/right buttons etc.
if(this.tablist.resize){
- this.tablist.resize({w: dojo.contentBox(this.domNode).w});
+ //make the tabs zero width so that they don't interfere with width calc, then reset
+ var s = this.tablist.domNode.style;
+ s.width="0";
+ var width = dojo.contentBox(this.domNode).w;
+ s.width="";
+ this.tablist.resize({w: width});
}
// and call resize() on the selected pane just to tell it that it's been made visible
@@ -19626,7 +20422,6 @@ dojo.declare("dijit.layout._TabContainerBase",
}
});
-
}
if(!dojo._hasResource["dijit.layout.TabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -19635,10 +20430,10 @@ dojo.provide("dijit.layout.TabController");
-// Menu is used for an accessible close button, would be nice to have a lighter-weight solution
+// Menu is used for an accessible close button, would be nice to have a lighter-weight solution
dojo.declare("dijit.layout.TabController",
@@ -19654,7 +20449,7 @@ dojo.declare("dijit.layout.TabController",
// tags:
// private
- templateString: "<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>",
+ templateString: "<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>",
// tabPosition: String
// Defines where tabs go relative to the content.
@@ -19704,32 +20499,16 @@ dojo.declare("dijit.layout._TabButton",
closeNode: "dijitTabCloseButton"
},
- templateString: dojo.cache("dijit.layout", "templates/_TabButton.html", "<div waiRole=\"presentation\" dojoAttachPoint=\"titleNode\" dojoAttachEvent='onclick:onClick'>\n <div waiRole=\"presentation\" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'>\n <div waiRole=\"presentation\" class='dijitTabContent' dojoAttachPoint='tabContent'>\n \t<div waiRole=\"presentation\" dojoAttachPoint='focusNode'>\n\t\t <img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' />\n\t\t <span dojoAttachPoint='containerNode' class='tabLabel'></span>\n\t\t <span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" dojoAttachPoint='closeNode'\n\t\t \t\tdojoAttachEvent='onclick: onClickCloseButton' waiRole=\"presentation\">\n\t\t <span dojoAttachPoint='closeText' class='dijitTabCloseText'>x</span\n\t\t ></span>\n\t\t\t</div>\n </div>\n </div>\n</div>\n"),
+ templateString: dojo.cache("dijit.layout", "templates/_TabButton.html", "<div role=\"presentation\" dojoAttachPoint=\"titleNode\" dojoAttachEvent='onclick:onClick'>\n <div role=\"presentation\" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'>\n <div role=\"presentation\" class='dijitTabContent' dojoAttachPoint='tabContent'>\n \t<div role=\"presentation\" dojoAttachPoint='focusNode'>\n\t\t <img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitTabButtonIcon\" dojoAttachPoint='iconNode' />\n\t\t <span dojoAttachPoint='containerNode' class='tabLabel'></span>\n\t\t <span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" dojoAttachPoint='closeNode'\n\t\t \t\tdojoAttachEvent='onclick: onClickCloseButton' role=\"presentation\">\n\t\t <span dojoAttachPoint='closeText' class='dijitTabCloseText'>[x]</span\n\t\t ></span>\n\t\t\t</div>\n </div>\n </div>\n</div>\n"),
// Override _FormWidget.scrollOnFocus.
// Don't scroll the whole tab container into view when the button is focused.
scrollOnFocus: false,
- postMixInProperties: function(){
- // Override blank iconClass from Button to do tab height adjustment on IE6,
- // to make sure that tabs with and w/out close icons are same height
- if(!this.iconClass){
- this.iconClass = "dijitTabButtonIcon";
- }
- },
-
- postCreate: function(){
+ buildRendering: function(){
this.inherited(arguments);
- dojo.setSelectable(this.containerNode, false);
- // If a custom icon class has not been set for the
- // tab icon, set its width to one pixel. This ensures
- // that the height styling of the tab is maintained,
- // as it is based on the height of the icon.
- // TODO: I still think we can just set dijitTabButtonIcon to 1px in CSS <Bill>
- if(this.iconNode.className == "dijitTabButtonIcon"){
- dojo.style(this.iconNode, "width", "1px");
- }
+ dojo.setSelectable(this.containerNode, false);
},
startup: function(){
@@ -19743,8 +20522,10 @@ dojo.declare("dijit.layout._TabButton",
}, 1);
},
- _setCloseButtonAttr: function(disp){
- this.closeButton = disp;
+ _setCloseButtonAttr: function(/*Boolean*/ disp){
+ // summary:
+ // Hide/show close button
+ this._set("closeButton", disp);
dojo.toggleClass(this.innerDiv, "dijitClosable", disp);
this.closeNode.style.display = disp ? "" : "none";
if(disp){
@@ -19776,16 +20557,16 @@ dojo.declare("dijit.layout._TabButton",
},
_setLabelAttr: function(/*String*/ content){
// summary:
- // Hook for attr('label', ...) to work.
+ // Hook for set('label', ...) to work.
// description:
// takes an HTML string.
- // Inherited ToggleButton implementation will Set the label (text) of the button;
+ // Inherited ToggleButton implementation will Set the label (text) of the button;
// Need to set the alt attribute of icon on tab buttons if no label displayed
- this.inherited(arguments);
- if(this.showLabel == false && !this.params.title){
- this.iconNode.alt = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || '');
- }
- },
+ this.inherited(arguments);
+ if(this.showLabel == false && !this.params.title){
+ this.iconNode.alt = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || '');
+ }
+ },
destroy: function(){
if(this._closeMenu){
@@ -19805,6 +20586,8 @@ dojo.provide("dijit.layout.ScrollingTabController");
+
+
dojo.declare("dijit.layout.ScrollingTabController",
dijit.layout.TabController,
{
@@ -19816,9 +20599,9 @@ dojo.declare("dijit.layout.ScrollingTabController",
// tags:
// private
- templateString: dojo.cache("dijit.layout", "templates/ScrollingTabController.html", "<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\" iconClass=\"dijitTabStripMenuIcon\"\n\t\t\tdojoAttachPoint=\"_menuBtn\" showLabel=false>&#9660;</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\" iconClass=\"dijitTabStripSlideLeftIcon\"\n\t\t\tdojoAttachPoint=\"_leftBtn\" dojoAttachEvent=\"onClick: doSlideLeft\" showLabel=false>&#9664;</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\" iconClass=\"dijitTabStripSlideRightIcon\"\n\t\t\tdojoAttachPoint=\"_rightBtn\" dojoAttachEvent=\"onClick: doSlideRight\" showLabel=false>&#9654;</div>\n\t<div class='dijitTabListWrapper' dojoAttachPoint='tablistWrapper'>\n\t\t<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'\n\t\t\t\tdojoAttachPoint='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>\n"),
+ templateString: dojo.cache("dijit.layout", "templates/ScrollingTabController.html", "<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerMenuButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\" containerId=\"${containerId}\" iconClass=\"dijitTabStripMenuIcon\"\n\t\t\tdropDownPosition=\"below-alt, above-alt\"\n\t\t\tdojoAttachPoint=\"_menuBtn\" showLabel=\"false\">&#9660;</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\" iconClass=\"dijitTabStripSlideLeftIcon\"\n\t\t\tdojoAttachPoint=\"_leftBtn\" dojoAttachEvent=\"onClick: doSlideLeft\" showLabel=\"false\">&#9664;</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\" iconClass=\"dijitTabStripSlideRightIcon\"\n\t\t\tdojoAttachPoint=\"_rightBtn\" dojoAttachEvent=\"onClick: doSlideRight\" showLabel=\"false\">&#9654;</div>\n\t<div class='dijitTabListWrapper' dojoAttachPoint='tablistWrapper'>\n\t\t<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'\n\t\t\t\tdojoAttachPoint='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>\n"),
- // useMenu:[const] Boolean
+ // useMenu: [const] Boolean
// True if a menu should be used to select tabs when they are too
// wide to fit the TabContainer, false otherwise.
useMenu: true,
@@ -19828,7 +20611,7 @@ dojo.declare("dijit.layout.ScrollingTabController",
// wide to fit the TabContainer, false otherwise.
useSlider: true,
- // tabStripClass: String
+ // tabStripClass: [const] String
// The css class to apply to the tab strip, if it is visible.
tabStripClass: "",
@@ -19844,7 +20627,7 @@ dojo.declare("dijit.layout.ScrollingTabController",
"class": "containerNode"
}),
- postCreate: function(){
+ buildRendering: function(){
this.inherited(arguments);
var n = this.domNode;
@@ -19874,41 +20657,18 @@ dojo.declare("dijit.layout.ScrollingTabController",
onAddChild: function(page, insertIndex){
this.inherited(arguments);
- var menuItem;
- if(this.useMenu){
- var containerId = this.containerId;
- menuItem = new dijit.MenuItem({
- id: page.id + "_stcMi",
- label: page.title,
- dir: page.dir,
- lang: page.lang,
- onClick: dojo.hitch(this, function(){
- var container = dijit.byId(containerId);
- container.selectChild(page);
- })
- });
- this._menuChildren[page.id] = menuItem;
- this._menu.addChild(menuItem, insertIndex);
- }
- // update the menuItem label when the button label is updated
- this.pane2handles[page.id].push(
- this.connect(this.pane2button[page.id], "set", function(name, value){
- if(this._postStartup){
- if(name == "label"){
- if(menuItem){
- menuItem.set(name, value);
- }
-
- // The changed label will have changed the width of the
- // buttons, so do a resize
- if(this._dim){
- this.resize(this._dim);
- }
+ // changes to the tab button label or iconClass will have changed the width of the
+ // buttons, so do a resize
+ dojo.forEach(["label", "iconClass"], function(attr){
+ this.pane2watches[page.id].push(
+ this.pane2button[page.id].watch(attr, dojo.hitch(this, function(name, oldValue, newValue){
+ if(this._postStartup && this._dim){
+ this.resize(this._dim);
}
- }
- })
- );
+ }))
+ );
+ }, this);
// Increment the width of the wrapper when a tab is added
// This makes sure that the buttons never wrap.
@@ -19925,13 +20685,6 @@ dojo.declare("dijit.layout.ScrollingTabController",
this._selectedTab = null;
}
- // delete menu entry corresponding to pane that was removed from TabContainer
- if(this.useMenu && page && page.id && this._menuChildren[page.id]){
- this._menu.removeChild(this._menuChildren[page.id]);
- this._menuChildren[page.id].destroy();
- delete this._menuChildren[page.id];
- }
-
this.inherited(arguments);
},
@@ -19939,7 +20692,6 @@ dojo.declare("dijit.layout.ScrollingTabController",
// summary:
// Creates the buttons used to scroll to view tabs that
// may not be visible if the TabContainer is too narrow.
- this._menuChildren = {};
// Make a list of the buttons to display when the tab labels become
// wider than the TabContainer, and hide the other buttons.
@@ -19948,26 +20700,13 @@ dojo.declare("dijit.layout.ScrollingTabController",
this._buttons = dojo.query("> .tabStripButton", this.domNode).filter(function(btn){
if((this.useMenu && btn == this._menuBtn.domNode) ||
(this.useSlider && (btn == this._rightBtn.domNode || btn == this._leftBtn.domNode))){
- this._btnWidth += dojo.marginBox(btn).w;
+ this._btnWidth += dojo._getMarginSize(btn).w;
return true;
}else{
dojo.style(btn, "display", "none");
return false;
}
}, this);
-
- if(this.useMenu){
- // Create the menu that is used to select tabs.
- this._menu = new dijit.Menu({
- id: this.id + "_menu",
- dir: this.dir,
- lang: this.lang,
- targetNodeIds: [this._menuBtn.domNode],
- leftClickToOpen: true,
- refocus: false // selecting a menu item sets focus to a TabButton
- });
- this._supportingWidgets.push(this._menu);
- }
},
_getTabsWidth: function(){
@@ -20037,6 +20776,10 @@ dojo.declare("dijit.layout.ScrollingTabController",
this._setButtonClass(this._getScroll());
this._postResize = true;
+
+ // Return my size so layoutChildren() can use it.
+ // Also avoids IE9 layout glitch on browser resize when scroll buttons present
+ return {h: this._contentBox.h, w: dim.w};
},
_getScroll: function(){
@@ -20139,7 +20882,7 @@ dojo.declare("dijit.layout.ScrollingTabController",
return pos;
},
- createSmoothScroll : function(x){
+ createSmoothScroll: function(x){
// summary:
// Creates a dojo._Animation object that smoothly scrolls the tab list
// either to a fixed horizontal pixel value, or to the selected tab.
@@ -20185,7 +20928,7 @@ dojo.declare("dijit.layout.ScrollingTabController",
return anim; // dojo._Animation
},
- _getBtnNode: function(e){
+ _getBtnNode: function(/*Event*/ e){
// summary:
// Gets a button DOM node from a mouse click event.
// e:
@@ -20197,7 +20940,7 @@ dojo.declare("dijit.layout.ScrollingTabController",
return n;
},
- doSlideRight: function(e){
+ doSlideRight: function(/*Event*/ e){
// summary:
// Scrolls the menu to the right.
// e:
@@ -20205,7 +20948,7 @@ dojo.declare("dijit.layout.ScrollingTabController",
this.doSlide(1, this._getBtnNode(e));
},
- doSlideLeft: function(e){
+ doSlideLeft: function(/*Event*/ e){
// summary:
// Scrolls the menu to the left.
// e:
@@ -20213,7 +20956,7 @@ dojo.declare("dijit.layout.ScrollingTabController",
this.doSlide(-1,this._getBtnNode(e));
},
- doSlide: function(direction, node){
+ doSlide: function(/*Number*/ direction, /*DomNode*/ node){
// summary:
// Scrolls the tab list to the left or right by 75% of the widget width.
// direction:
@@ -20232,7 +20975,7 @@ dojo.declare("dijit.layout.ScrollingTabController",
this.createSmoothScroll(to).play();
},
- _setButtonClass: function(scroll){
+ _setButtonClass: function(/*Number*/ scroll){
// summary:
// Disables the left scroll button if the tabs are scrolled all the way to the left,
// or the right scroll button in the opposite case.
@@ -20245,18 +20988,71 @@ dojo.declare("dijit.layout.ScrollingTabController",
}
});
-dojo.declare("dijit.layout._ScrollingTabControllerButton",
- dijit.form.Button,
- {
- baseClass: "dijitTab tabStripButton",
- templateString: dojo.cache("dijit.layout", "templates/_ScrollingTabControllerButton.html", "<div dojoAttachEvent=\"onclick:_onButtonClick\">\n\t<div waiRole=\"presentation\" class=\"dijitTabInnerDiv\" dojoattachpoint=\"innerDiv,focusNode\">\n\t\t<div waiRole=\"presentation\" class=\"dijitTabContent dijitButtonContents\" dojoattachpoint=\"tabContent\">\n\t\t\t<img waiRole=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t\t<span dojoAttachPoint=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>\n"),
+dojo.declare("dijit.layout._ScrollingTabControllerButtonMixin", null, {
+ baseClass: "dijitTab tabStripButton",
+
+ templateString: dojo.cache("dijit.layout", "templates/_ScrollingTabControllerButton.html", "<div dojoAttachEvent=\"onclick:_onButtonClick\">\n\t<div role=\"presentation\" class=\"dijitTabInnerDiv\" dojoattachpoint=\"innerDiv,focusNode\">\n\t\t<div role=\"presentation\" class=\"dijitTabContent dijitButtonContents\" dojoattachpoint=\"tabContent\">\n\t\t\t<img role=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t\t<span dojoAttachPoint=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>\n"),
// Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be
// able to tab to the left/right/menu buttons
- tabIndex: "-1"
+ tabIndex: "",
+
+ // Similarly, override FormWidget.isFocusable() because clicking a button shouldn't focus it
+ // either (this override avoids focus() call in FormWidget.js)
+ isFocusable: function(){ return false; }
+});
+
+dojo.declare("dijit.layout._ScrollingTabControllerButton",
+ [dijit.form.Button, dijit.layout._ScrollingTabControllerButtonMixin]);
+
+dojo.declare(
+ "dijit.layout._ScrollingTabControllerMenuButton",
+ [dijit.form.Button, dijit._HasDropDown, dijit.layout._ScrollingTabControllerButtonMixin],
+{
+ // id of the TabContainer itself
+ containerId: "",
+
+ // -1 so user can't tab into the button, but so that button can still be focused programatically.
+ // Because need to move focus to the button (or somewhere) before the menu is hidden or IE6 will crash.
+ tabIndex: "-1",
+
+ isLoaded: function(){
+ // recreate menu every time, in case the TabContainer's list of children (or their icons/labels) have changed
+ return false;
+ },
+
+ loadDropDown: function(callback){
+ this.dropDown = new dijit.Menu({
+ id: this.containerId + "_menu",
+ dir: this.dir,
+ lang: this.lang
+ });
+ var container = dijit.byId(this.containerId);
+ dojo.forEach(container.getChildren(), function(page){
+ var menuItem = new dijit.MenuItem({
+ id: page.id + "_stcMi",
+ label: page.title,
+ iconClass: page.iconClass,
+ dir: page.dir,
+ lang: page.lang,
+ onClick: function(){
+ container.selectChild(page);
+ }
+ });
+ this.dropDown.addChild(menuItem);
+ }, this);
+ callback();
+ },
+
+ closeDropDown: function(/*Boolean*/ focus){
+ this.inherited(arguments);
+ if(this.dropDown){
+ this.dropDown.destroyRecursive();
+ delete this.dropDown;
+ }
}
-);
+});
}
@@ -20328,7 +21124,6 @@ dojo.declare("dijit.layout.TabContainer",
}
});
-
}
if(!dojo._hasResource["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -20339,7 +21134,7 @@ dojo.provide("dojo.number");
-
+dojo.getObject("number", true, dojo);
/*=====
dojo.number = {
@@ -20444,7 +21239,7 @@ dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.
if(options.fractional === false){ options.places = 0; }
return pattern.replace(numberPatternRE,
dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round}));
-}
+};
dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){
// summary:
@@ -20470,7 +21265,7 @@ dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/incr
// 10.75
var factor = 10 / (increment || 10);
return (factor * +value).toFixed(places) / factor; // Number
-}
+};
if((0.9).toFixed() == 0){
// (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit
@@ -20483,7 +21278,7 @@ if((0.9).toFixed() == 0){
d = 0;
}
return round(v, p, m) + (v > 0 ? d : -d);
- }
+ };
})();
}
@@ -20506,7 +21301,7 @@ dojo.number.__FormatAbsoluteOptions = function(){
=====*/
dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){
- // summary:
+ // summary:
// Apply numeric pattern to absolute value using options. Gives no
// consideration to local customs.
// value:
@@ -20621,7 +21416,7 @@ dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){
// Returns regular expression with positive and negative match, group
// and decimal separators
return dojo.number._parseInfo(options).regexp; // String
-}
+};
dojo.number._parseInfo = function(/*Object?*/options){
options = options || {};
@@ -20709,7 +21504,7 @@ dojo.number._parseInfo = function(/*Object?*/options){
// normalize whitespace and return
return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object
-}
+};
/*=====
dojo.number.__ParseOptions = function(){
@@ -20820,10 +21615,10 @@ dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*
var re = "";
if(q && (flags.places!==0)){
re = "\\" + flags.decimal;
- if(flags.places == Infinity){
- re = "(?:" + re + "\\d+)?";
+ if(flags.places == Infinity){
+ re = "(?:" + re + "\\d+)?";
}else{
- re += "\\d{" + flags.places + "}";
+ re += "\\d{" + flags.places + "}";
}
}
return re;
@@ -20832,9 +21627,9 @@ dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*
);
var exponentRE = dojo.regexp.buildGroupRE(flags.exponent,
- function(q){
+ function(q){
if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; }
- return "";
+ return "";
}
);
@@ -20866,7 +21661,7 @@ dojo.number.__IntegerRegexpFlags = function(){
=====*/
dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){
- // summary:
+ // summary:
// Builds a regular expression that matches an integer
// assign default values to missing parameters
@@ -20905,7 +21700,7 @@ dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags
);
return signRE + numberRE; // String
-}
+};
}
@@ -20918,7 +21713,6 @@ dojo.provide("dijit.ProgressBar");
-
dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
// summary:
// A progress indication widget, showing the amount completed
@@ -20927,20 +21721,21 @@ dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
// example:
// | <div dojoType="ProgressBar"
// | places="0"
- // | progress="..." maximum="...">
+ // | value="..." maximum="...">
// | </div>
- //
- // description:
- // Note that the progress bar is updated via (a non-standard)
- // update() method, rather than via attr() like other widgets.
// progress: [const] String (Percentage or Number)
// Number or percentage indicating amount of task completed.
- // With "%": percentage value, 0% <= progress <= 100%, or
- // without "%": absolute value, 0 <= progress <= maximum
- // TODO: rename to value for 2.0
+ // Deprecated. Use "value" instead.
progress: "0",
+ // value: String (Percentage or Number)
+ // Number or percentage indicating amount of task completed.
+ // With "%": percentage value, 0% <= progress <= 100%, or
+ // without "%": absolute value, 0 <= progress <= maximum.
+ // Infinity means that the progress bar is indeterminate.
+ value: "",
+
// maximum: [const] Float
// Max sample number
maximum: 100,
@@ -20952,22 +21747,34 @@ dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
// indeterminate: [const] Boolean
// If false: show progress value (number or percentage).
// If true: show that a process is underway but that the amount completed is unknown.
+ // Deprecated. Use "value" instead.
indeterminate: false,
+ // label: String?
+ // Label on progress bar. Defaults to percentage for determinate progress bar and
+ // blank for indeterminate progress bar.
+ label:"",
+
// name: String
// this is the field name (for a form) if set. This needs to be set if you want to use
// this widget in a dijit.form.Form widget (such as dijit.Dialog)
name: '',
- templateString: dojo.cache("dijit", "templates/ProgressBar.html", "<div class=\"dijitProgressBar dijitProgressBarEmpty\"\n\t><div waiRole=\"progressbar\" dojoAttachPoint=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\"></div\n\t\t><span style=\"visibility:hidden\">&nbsp;</span\n\t></div\n\t><div dojoAttachPoint=\"label\" class=\"dijitProgressBarLabel\" id=\"${id}_label\">&nbsp;</div\n\t><img dojoAttachPoint=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n"),
+ templateString: dojo.cache("dijit", "templates/ProgressBar.html", "<div class=\"dijitProgressBar dijitProgressBarEmpty\" role=\"progressbar\"\n\t><div dojoAttachPoint=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\" role=\"presentation\"></div\n\t\t><span style=\"visibility:hidden\">&nbsp;</span\n\t></div\n\t><div dojoAttachPoint=\"labelNode\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"></div\n\t><img dojoAttachPoint=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n"),
// _indeterminateHighContrastImagePath: [private] dojo._URL
// URL to image to use for indeterminate progress bar when display is in high contrast mode
_indeterminateHighContrastImagePath:
dojo.moduleUrl("dijit", "themes/a11y/indeterminate_progress.gif"),
- // public functions
- postCreate: function(){
+ postMixInProperties: function(){
+ this.inherited(arguments);
+ if(!("value" in this.params)){
+ this.value = this.indeterminate ? Infinity : this.progress;
+ }
+ },
+
+ buildRendering: function(){
this.inherited(arguments);
this.indeterminateHighContrastImage.setAttribute("src",
this._indeterminateHighContrastImagePath.toString());
@@ -20976,28 +21783,28 @@ dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
update: function(/*Object?*/attributes){
// summary:
- // Change attributes of ProgressBar, similar to attr(hash).
- //
+ // Internal method to change attributes of ProgressBar, similar to set(hash). Users should call
+ // set("value", ...) rather than calling this method directly.
// attributes:
// May provide progress and/or maximum properties on this parameter;
// see attribute specs for details.
- //
// example:
// | myProgressBar.update({'indeterminate': true});
// | myProgressBar.update({'progress': 80});
+ // | myProgressBar.update({'indeterminate': true, label:"Loading ..." })
+ // tags:
+ // private
// TODO: deprecate this method and use set() instead
dojo.mixin(this, attributes || {});
- var tip = this.internalProgress;
- var percent = 1, classFunc;
+ var tip = this.internalProgress, ap = this.domNode;
+ var percent = 1;
if(this.indeterminate){
- classFunc = "addClass";
- dijit.removeWaiState(tip, "valuenow");
- dijit.removeWaiState(tip, "valuemin");
- dijit.removeWaiState(tip, "valuemax");
+ dijit.removeWaiState(ap, "valuenow");
+ dijit.removeWaiState(ap, "valuemin");
+ dijit.removeWaiState(ap, "valuemax");
}else{
- classFunc = "removeClass";
if(String(this.progress).indexOf("%") != -1){
percent = Math.min(parseFloat(this.progress)/100, 1);
this.progress = percent * this.maximum;
@@ -21005,19 +21812,21 @@ dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
this.progress = Math.min(this.progress, this.maximum);
percent = this.progress / this.maximum;
}
- var text = this.report(percent);
- this.label.firstChild.nodeValue = text;
- dijit.setWaiState(tip, "describedby", this.label.id);
- dijit.setWaiState(tip, "valuenow", this.progress);
- dijit.setWaiState(tip, "valuemin", 0);
- dijit.setWaiState(tip, "valuemax", this.maximum);
+
+ dijit.setWaiState(ap, "describedby", this.labelNode.id);
+ dijit.setWaiState(ap, "valuenow", this.progress);
+ dijit.setWaiState(ap, "valuemin", 0);
+ dijit.setWaiState(ap, "valuemax", this.maximum);
}
- dojo[classFunc](this.domNode, "dijitProgressBarIndeterminate");
+ this.labelNode.innerHTML = this.report(percent);
+
+ dojo.toggleClass(this.domNode, "dijitProgressBarIndeterminate", this.indeterminate);
tip.style.width = (percent * 100) + "%";
this.onChange();
},
_setValueAttr: function(v){
+ this._set("value", v);
if(v == Infinity){
this.update({indeterminate:true});
}else{
@@ -21025,8 +21834,15 @@ dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
}
},
- _getValueAttr: function(){
- return this.progress;
+ _setLabelAttr: function(label){
+ this._set("label", label);
+ this.update();
+ },
+
+ _setIndeterminateAttr: function(indeterminate){
+ // Deprecated, use set("value", ...) instead
+ this.indeterminate = indeterminate;
+ this.update();
},
report: function(/*float*/percent){
@@ -21036,14 +21852,15 @@ dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
// tags:
// extension
- return dojo.number.format(percent, { type: "percent", places: this.places, locale: this.lang });
+ return this.label ? this.label :
+ (this.indeterminate ? "&nbsp;" : dojo.number.format(percent, { type: "percent", places: this.places, locale: this.lang }));
},
onChange: function(){
// summary:
// Callback fired when progress updates.
// tags:
- // progress
+ // extension
}
});
@@ -21061,8 +21878,11 @@ dojo.declare("dijit.ToolbarSeparator",
{
// summary:
// A spacer between two `dijit.Toolbar` items
- templateString: '<div class="dijitToolbarSeparator dijitInline" waiRole="presentation"></div>',
- postCreate: function(){ dojo.setSelectable(this.domNode, false); },
+ templateString: '<div class="dijitToolbarSeparator dijitInline" role="presentation"></div>',
+ buildRendering: function(){
+ this.inherited(arguments);
+ dojo.setSelectable(this.domNode, false);
+ },
isFocusable: function(){
// summary:
// This widget isn't focusable, so pass along that fact.
@@ -21073,8 +21893,6 @@ dojo.declare("dijit.ToolbarSeparator",
});
-
-
}
if(!dojo._hasResource["dijit.Toolbar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -21085,6 +21903,9 @@ dojo.provide("dijit.Toolbar");
+
+// Note: require of ToolbarSeparator is for back-compat, remove for 2.0
+
dojo.declare("dijit.Toolbar",
[dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
{
@@ -21092,7 +21913,7 @@ dojo.declare("dijit.Toolbar",
// A Toolbar widget, used to hold things like `dijit.Editor` buttons
templateString:
- '<div class="dijit" waiRole="toolbar" tabIndex="${tabIndex}" dojoAttachPoint="containerNode">' +
+ '<div class="dijit" role="toolbar" tabIndex="${tabIndex}" dojoAttachPoint="containerNode">' +
// '<table style="table-layout: fixed" class="dijitReset dijitToolbarTable">' + // factor out style
// '<tr class="dijitReset" dojoAttachPoint="containerNode"></tr>'+
// '</table>' +
@@ -21101,11 +21922,12 @@ dojo.declare("dijit.Toolbar",
baseClass: "dijitToolbar",
postCreate: function(){
+ this.inherited(arguments);
+
this.connectKeyNavHandlers(
this.isLeftToRight() ? [dojo.keys.LEFT_ARROW] : [dojo.keys.RIGHT_ARROW],
this.isLeftToRight() ? [dojo.keys.RIGHT_ARROW] : [dojo.keys.LEFT_ARROW]
);
- this.inherited(arguments);
},
startup: function(){
@@ -21118,14 +21940,13 @@ dojo.declare("dijit.Toolbar",
}
);
-// For back-compat, remove for 2.0
-
-
}
if(!dojo._hasResource["dojo.DeferredList"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.DeferredList"] = true;
dojo.provide("dojo.DeferredList");
+
+
dojo.DeferredList = function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/ consumeErrors, /*Function?*/ canceller){
// summary:
// Provides event handling for a group of Deferred objects.
@@ -21183,7 +22004,7 @@ dojo.DeferredList = function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*B
dojo.DeferredList.prototype = new dojo.Deferred();
dojo.DeferredList.prototype.gatherResults= function(deferredList){
- // summary:
+ // summary:
// Gathers the results of the deferreds for packaging
// as the parameters to the Deferred Lists' callback
@@ -21204,6 +22025,7 @@ if(!dojo._hasResource["dijit.tree.TreeStoreModel"]){ //_hasResource checks added
dojo._hasResource["dijit.tree.TreeStoreModel"] = true;
dojo.provide("dijit.tree.TreeStoreModel");
+
dojo.declare(
"dijit.tree.TreeStoreModel",
null,
@@ -21415,7 +22237,7 @@ dojo.declare(
// to parents with multiple children attributes, in order to define which
// children attribute points to the new item.
- var pInfo = {parent: parent, attribute: this.childrenAttrs[0], insertIndex: insertIndex};
+ var pInfo = {parent: parent, attribute: this.childrenAttrs[0]}, LnewItem;
if(this.newItemIdAttr && args[this.newItemIdAttr]){
// Maybe there's already a corresponding item in the store; if so, reuse it.
@@ -21425,12 +22247,20 @@ dojo.declare(
this.pasteItem(item, null, parent, true, insertIndex);
}else{
// Create new item in the tree, based on the drag source.
- this.store.newItem(args, pInfo);
+ LnewItem=this.store.newItem(args, pInfo);
+ if (LnewItem && (insertIndex!=undefined)){
+ // Move new item to desired position
+ this.pasteItem(LnewItem, parent, parent, false, insertIndex);
+ }
}
}});
}else{
// [as far as we know] there is no id so we must assume this is a new item
- this.store.newItem(args, pInfo);
+ LnewItem=this.store.newItem(args, pInfo);
+ if (LnewItem && (insertIndex!=undefined)){
+ // Move new item to desired position
+ this.pasteItem(LnewItem, parent, parent, false, insertIndex);
+ }
}
},
@@ -21564,8 +22394,6 @@ dojo.declare(
}
});
-
-
}
if(!dojo._hasResource["dijit.tree.ForestStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -21576,14 +22404,22 @@ dojo.provide("dijit.tree.ForestStoreModel");
dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.TreeStoreModel, {
// summary:
- // Interface between Tree and a dojo.store that doesn't have a root item,
- // i.e. has multiple "top level" items.
+ // Interface between a dijit.Tree and a dojo.data store that doesn't have a root item,
+ // a.k.a. a store that has multiple "top level" items.
//
// description
- // Use this class to wrap a dojo.store, making all the items matching the specified query
+ // Use this class to wrap a dojo.data store, making all the items matching the specified query
// appear as children of a fabricated "root item". If no query is specified then all the
// items returned by fetch() on the underlying store become children of the root item.
- // It allows dijit.Tree to assume a single root item, even if the store doesn't have one.
+ // This class allows dijit.Tree to assume a single root item, even if the store doesn't have one.
+ //
+ // When using this class the developer must override a number of methods according to their app and
+ // data, including:
+ // - onNewRootItem
+ // - onAddToRoot
+ // - onLeaveRoot
+ // - onNewItem
+ // - onSetItem
// Parameters to constructor
@@ -21783,9 +22619,9 @@ dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.TreeStoreModel, {
// a new item is created, since any new item could be a top level item (even in
// addition to being a child of another item, since items can have multiple parents).
//
- // Developers can override this function to do something more efficient if they can
- // detect which items are possible top level items (based on the item and the
- // parentInfo parameters). Often all top level items have parentInfo==null, but
+ // If developers can detect which items are possible top level items (based on the item and the
+ // parentInfo parameters), they should override this method to only call _requeryTop() for top
+ // level items. Often all top level items have parentInfo==null, but
// that will depend on which store you use and what your data is like.
// tags:
// extension
@@ -21805,10 +22641,926 @@ dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.TreeStoreModel, {
}
this.inherited(arguments);
+ },
+
+ onSetItem: function(/* item */ item,
+ /* attribute-name-string */ attribute,
+ /* object | array */ oldValue,
+ /* object | array */ newValue){
+ // summary:
+ // Updates the tree view according to changes to an item in the data store.
+ // Developers should override this method to be more efficient based on their app/data.
+ // description:
+ // Handles updates to an item's children by calling onChildrenChange(), and
+ // other updates to an item by calling onChange().
+ //
+ // Also, any change to any item re-executes the query for the tree's top-level items,
+ // since this modified item may have started/stopped matching the query for top level items.
+ //
+ // If possible, developers should override this function to only call _requeryTop() when
+ // the change to the item has caused it to stop/start being a top level item in the tree.
+ // tags:
+ // extension
+
+ this._requeryTop();
+ this.inherited(arguments);
}
+
+});
+
+}
+
+if(!dojo._hasResource["dojo.dnd.Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dojo.dnd.Container"] = true;
+dojo.provide("dojo.dnd.Container");
+
+
+
+
+/*
+ Container states:
+ "" - normal state
+ "Over" - mouse over a container
+ Container item states:
+ "" - normal state
+ "Over" - mouse over a container item
+*/
+
+/*=====
+dojo.declare("dojo.dnd.__ContainerArgs", [], {
+ creator: function(){
+ // summary:
+ // a creator function, which takes a data item, and returns an object like that:
+ // {node: newNode, data: usedData, type: arrayOfStrings}
+ },
+
+ // skipForm: Boolean
+ // don't start the drag operation, if clicked on form elements
+ skipForm: false,
+
+ // dropParent: Node||String
+ // node or node's id to use as the parent node for dropped items
+ // (must be underneath the 'node' parameter in the DOM)
+ dropParent: null,
+
+ // _skipStartup: Boolean
+ // skip startup(), which collects children, for deferred initialization
+ // (this is used in the markup mode)
+ _skipStartup: false
+});
+
+dojo.dnd.Item = function(){
+ // summary:
+ // Represents (one of) the source node(s) being dragged.
+ // Contains (at least) the "type" and "data" attributes.
+ // type: String[]
+ // Type(s) of this item, by default this is ["text"]
+ // data: Object
+ // Logical representation of the object being dragged.
+ // If the drag object's type is "text" then data is a String,
+ // if it's another type then data could be a different Object,
+ // perhaps a name/value hash.
+
+ this.type = type;
+ this.data = data;
+}
+=====*/
+
+dojo.declare("dojo.dnd.Container", null, {
+ // summary:
+ // a Container object, which knows when mouse hovers over it,
+ // and over which element it hovers
+
+ // object attributes (for markup)
+ skipForm: false,
+
+ /*=====
+ // current: DomNode
+ // The DOM node the mouse is currently hovered over
+ current: null,
+
+ // map: Hash<String, dojo.dnd.Item>
+ // Map from an item's id (which is also the DOMNode's id) to
+ // the dojo.dnd.Item itself.
+ map: {},
+ =====*/
+
+ constructor: function(node, params){
+ // summary:
+ // a constructor of the Container
+ // node: Node
+ // node or node's id to build the container on
+ // params: dojo.dnd.__ContainerArgs
+ // a dictionary of parameters
+ this.node = dojo.byId(node);
+ if(!params){ params = {}; }
+ this.creator = params.creator || null;
+ this.skipForm = params.skipForm;
+ this.parent = params.dropParent && dojo.byId(params.dropParent);
+
+ // class-specific variables
+ this.map = {};
+ this.current = null;
+
+ // states
+ this.containerState = "";
+ dojo.addClass(this.node, "dojoDndContainer");
+
+ // mark up children
+ if(!(params && params._skipStartup)){
+ this.startup();
+ }
+
+ // set up events
+ this.events = [
+ dojo.connect(this.node, "onmouseover", this, "onMouseOver"),
+ dojo.connect(this.node, "onmouseout", this, "onMouseOut"),
+ // cancel text selection and text dragging
+ dojo.connect(this.node, "ondragstart", this, "onSelectStart"),
+ dojo.connect(this.node, "onselectstart", this, "onSelectStart")
+ ];
+ },
+
+ // object attributes (for markup)
+ creator: function(){
+ // summary:
+ // creator function, dummy at the moment
+ },
+
+ // abstract access to the map
+ getItem: function(/*String*/ key){
+ // summary:
+ // returns a data item by its key (id)
+ return this.map[key]; // dojo.dnd.Item
+ },
+ setItem: function(/*String*/ key, /*dojo.dnd.Item*/ data){
+ // summary:
+ // associates a data item with its key (id)
+ this.map[key] = data;
+ },
+ delItem: function(/*String*/ key){
+ // summary:
+ // removes a data item from the map by its key (id)
+ delete this.map[key];
+ },
+ forInItems: function(/*Function*/ f, /*Object?*/ o){
+ // summary:
+ // iterates over a data map skipping members that
+ // are present in the empty object (IE and/or 3rd-party libraries).
+ o = o || dojo.global;
+ var m = this.map, e = dojo.dnd._empty;
+ for(var i in m){
+ if(i in e){ continue; }
+ f.call(o, m[i], i, this);
+ }
+ return o; // Object
+ },
+ clearItems: function(){
+ // summary:
+ // removes all data items from the map
+ this.map = {};
+ },
+
+ // methods
+ getAllNodes: function(){
+ // summary:
+ // returns a list (an array) of all valid child nodes
+ return dojo.query("> .dojoDndItem", this.parent); // NodeList
+ },
+ sync: function(){
+ // summary:
+ // sync up the node list with the data map
+ var map = {};
+ this.getAllNodes().forEach(function(node){
+ if(node.id){
+ var item = this.getItem(node.id);
+ if(item){
+ map[node.id] = item;
+ return;
+ }
+ }else{
+ node.id = dojo.dnd.getUniqueId();
+ }
+ var type = node.getAttribute("dndType"),
+ data = node.getAttribute("dndData");
+ map[node.id] = {
+ data: data || node.innerHTML,
+ type: type ? type.split(/\s*,\s*/) : ["text"]
+ };
+ }, this);
+ this.map = map;
+ return this; // self
+ },
+ insertNodes: function(data, before, anchor){
+ // summary:
+ // inserts an array of new nodes before/after an anchor node
+ // data: Array
+ // a list of data items, which should be processed by the creator function
+ // before: Boolean
+ // insert before the anchor, if true, and after the anchor otherwise
+ // anchor: Node
+ // the anchor node to be used as a point of insertion
+ if(!this.parent.firstChild){
+ anchor = null;
+ }else if(before){
+ if(!anchor){
+ anchor = this.parent.firstChild;
+ }
+ }else{
+ if(anchor){
+ anchor = anchor.nextSibling;
+ }
+ }
+ if(anchor){
+ for(var i = 0; i < data.length; ++i){
+ var t = this._normalizedCreator(data[i]);
+ this.setItem(t.node.id, {data: t.data, type: t.type});
+ this.parent.insertBefore(t.node, anchor);
+ }
+ }else{
+ for(var i = 0; i < data.length; ++i){
+ var t = this._normalizedCreator(data[i]);
+ this.setItem(t.node.id, {data: t.data, type: t.type});
+ this.parent.appendChild(t.node);
+ }
+ }
+ return this; // self
+ },
+ destroy: function(){
+ // summary:
+ // prepares this object to be garbage-collected
+ dojo.forEach(this.events, dojo.disconnect);
+ this.clearItems();
+ this.node = this.parent = this.current = null;
+ },
+
+ // markup methods
+ markupFactory: function(params, node){
+ params._skipStartup = true;
+ return new dojo.dnd.Container(node, params);
+ },
+ startup: function(){
+ // summary:
+ // collects valid child items and populate the map
+
+ // set up the real parent node
+ if(!this.parent){
+ // use the standard algorithm, if not assigned
+ this.parent = this.node;
+ if(this.parent.tagName.toLowerCase() == "table"){
+ var c = this.parent.getElementsByTagName("tbody");
+ if(c && c.length){ this.parent = c[0]; }
+ }
+ }
+ this.defaultCreator = dojo.dnd._defaultCreator(this.parent);
+
+ // process specially marked children
+ this.sync();
+ },
+
+ // mouse events
+ onMouseOver: function(e){
+ // summary:
+ // event processor for onmouseover
+ // e: Event
+ // mouse event
+ var n = e.relatedTarget;
+ while(n){
+ if(n == this.node){ break; }
+ try{
+ n = n.parentNode;
+ }catch(x){
+ n = null;
+ }
+ }
+ if(!n){
+ this._changeState("Container", "Over");
+ this.onOverEvent();
+ }
+ n = this._getChildByEvent(e);
+ if(this.current == n){ return; }
+ if(this.current){ this._removeItemClass(this.current, "Over"); }
+ if(n){ this._addItemClass(n, "Over"); }
+ this.current = n;
+ },
+ onMouseOut: function(e){
+ // summary:
+ // event processor for onmouseout
+ // e: Event
+ // mouse event
+ for(var n = e.relatedTarget; n;){
+ if(n == this.node){ return; }
+ try{
+ n = n.parentNode;
+ }catch(x){
+ n = null;
+ }
+ }
+ if(this.current){
+ this._removeItemClass(this.current, "Over");
+ this.current = null;
+ }
+ this._changeState("Container", "");
+ this.onOutEvent();
+ },
+ onSelectStart: function(e){
+ // summary:
+ // event processor for onselectevent and ondragevent
+ // e: Event
+ // mouse event
+ if(!this.skipForm || !dojo.dnd.isFormElement(e)){
+ dojo.stopEvent(e);
+ }
+ },
+
+ // utilities
+ onOverEvent: function(){
+ // summary:
+ // this function is called once, when mouse is over our container
+ },
+ onOutEvent: function(){
+ // summary:
+ // this function is called once, when mouse is out of our container
+ },
+ _changeState: function(type, newState){
+ // summary:
+ // changes a named state to new state value
+ // type: String
+ // a name of the state to change
+ // newState: String
+ // new state
+ var prefix = "dojoDnd" + type;
+ var state = type.toLowerCase() + "State";
+ //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
+ dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
+ this[state] = newState;
+ },
+ _addItemClass: function(node, type){
+ // summary:
+ // adds a class with prefix "dojoDndItem"
+ // node: Node
+ // a node
+ // type: String
+ // a variable suffix for a class name
+ dojo.addClass(node, "dojoDndItem" + type);
+ },
+ _removeItemClass: function(node, type){
+ // summary:
+ // removes a class with prefix "dojoDndItem"
+ // node: Node
+ // a node
+ // type: String
+ // a variable suffix for a class name
+ dojo.removeClass(node, "dojoDndItem" + type);
+ },
+ _getChildByEvent: function(e){
+ // summary:
+ // gets a child, which is under the mouse at the moment, or null
+ // e: Event
+ // a mouse event
+ var node = e.target;
+ if(node){
+ for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){
+ if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; }
+ }
+ }
+ return null;
+ },
+ _normalizedCreator: function(/*dojo.dnd.Item*/ item, /*String*/ hint){
+ // summary:
+ // adds all necessary data to the output of the user-supplied creator function
+ var t = (this.creator || this.defaultCreator).call(this, item, hint);
+ if(!dojo.isArray(t.type)){ t.type = ["text"]; }
+ if(!t.node.id){ t.node.id = dojo.dnd.getUniqueId(); }
+ dojo.addClass(t.node, "dojoDndItem");
+ return t;
+ }
+});
+
+dojo.dnd._createNode = function(tag){
+ // summary:
+ // returns a function, which creates an element of given tag
+ // (SPAN by default) and sets its innerHTML to given text
+ // tag: String
+ // a tag name or empty for SPAN
+ if(!tag){ return dojo.dnd._createSpan; }
+ return function(text){ // Function
+ return dojo.create(tag, {innerHTML: text}); // Node
+ };
+};
+
+dojo.dnd._createTrTd = function(text){
+ // summary:
+ // creates a TR/TD structure with given text as an innerHTML of TD
+ // text: String
+ // a text for TD
+ var tr = dojo.create("tr");
+ dojo.create("td", {innerHTML: text}, tr);
+ return tr; // Node
+};
+
+dojo.dnd._createSpan = function(text){
+ // summary:
+ // creates a SPAN element with given text as its innerHTML
+ // text: String
+ // a text for SPAN
+ return dojo.create("span", {innerHTML: text}); // Node
+};
+
+// dojo.dnd._defaultCreatorNodes: Object
+// a dictionary that maps container tag names to child tag names
+dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"};
+
+dojo.dnd._defaultCreator = function(node){
+ // summary:
+ // takes a parent node, and returns an appropriate creator function
+ // node: Node
+ // a container node
+ var tag = node.tagName.toLowerCase();
+ var c = tag == "tbody" || tag == "thead" ? dojo.dnd._createTrTd :
+ dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]);
+ return function(item, hint){ // Function
+ var isObj = item && dojo.isObject(item), data, type, n;
+ if(isObj && item.tagName && item.nodeType && item.getAttribute){
+ // process a DOM node
+ data = item.getAttribute("dndData") || item.innerHTML;
+ type = item.getAttribute("dndType");
+ type = type ? type.split(/\s*,\s*/) : ["text"];
+ n = item; // this node is going to be moved rather than copied
+ }else{
+ // process a DnD item object or a string
+ data = (isObj && item.data) ? item.data : item;
+ type = (isObj && item.type) ? item.type : ["text"];
+ n = (hint == "avatar" ? dojo.dnd._createSpan : c)(String(data));
+ }
+ if(!n.id){
+ n.id = dojo.dnd.getUniqueId();
+ }
+ return {node: n, data: data, type: type};
+ };
+};
+
+}
+
+if(!dojo._hasResource["dijit.tree._dndContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dijit.tree._dndContainer"] = true;
+dojo.provide("dijit.tree._dndContainer");
+
+
+
+
+dojo.getObject("tree", true, dojo);
+
+dijit.tree._compareNodes = function(n1, n2){
+ if(n1 === n2){
+ return 0;
+ }
+
+ if('sourceIndex' in document.documentElement){ //IE
+ //TODO: does not yet work if n1 and/or n2 is a text node
+ return n1.sourceIndex - n2.sourceIndex;
+ }else if('compareDocumentPosition' in document.documentElement){ //FF, Opera
+ return n1.compareDocumentPosition(n2) & 2 ? 1: -1;
+ }else if(document.createRange){ //Webkit
+ var r1 = doc.createRange();
+ r1.setStartBefore(n1);
+
+ var r2 = doc.createRange();
+ r2.setStartBefore(n2);
+
+ return r1.compareBoundaryPoints(r1.END_TO_END, r2);
+ }else{
+ throw Error("dijit.tree._compareNodes don't know how to compare two different nodes in this browser");
+ }
+};
+
+dojo.declare("dijit.tree._dndContainer",
+ null,
+ {
+
+ // summary:
+ // This is a base class for `dijit.tree._dndSelector`, and isn't meant to be used directly.
+ // It's modeled after `dojo.dnd.Container`.
+ // tags:
+ // protected
+
+ /*=====
+ // current: DomNode
+ // The currently hovered TreeNode.rowNode (which is the DOM node
+ // associated w/a given node in the tree, excluding it's descendants)
+ current: null,
+ =====*/
+
+ constructor: function(tree, params){
+ // summary:
+ // A constructor of the Container
+ // tree: Node
+ // Node or node's id to build the container on
+ // params: dijit.tree.__SourceArgs
+ // A dict of parameters, which gets mixed into the object
+ // tags:
+ // private
+ this.tree = tree;
+ this.node = tree.domNode; // TODO: rename; it's not a TreeNode but the whole Tree
+ dojo.mixin(this, params);
+
+ // class-specific variables
+ this.map = {};
+ this.current = null; // current TreeNode's DOM node
+
+ // states
+ this.containerState = "";
+ dojo.addClass(this.node, "dojoDndContainer");
+
+ // set up events
+ this.events = [
+ // container level events
+ dojo.connect(this.node, "onmouseenter", this, "onOverEvent"),
+ dojo.connect(this.node, "onmouseleave", this, "onOutEvent"),
+
+ // switching between TreeNodes
+ dojo.connect(this.tree, "_onNodeMouseEnter", this, "onMouseOver"),
+ dojo.connect(this.tree, "_onNodeMouseLeave", this, "onMouseOut"),
+
+ // cancel text selection and text dragging
+ dojo.connect(this.node, "ondragstart", dojo, "stopEvent"),
+ dojo.connect(this.node, "onselectstart", dojo, "stopEvent")
+ ];
+ },
+
+ getItem: function(/*String*/ key){
+ // summary:
+ // Returns the dojo.dnd.Item (representing a dragged node) by it's key (id).
+ // Called by dojo.dnd.Source.checkAcceptance().
+ // tags:
+ // protected
+
+ var widget = this.selection[key],
+ ret = {
+ data: widget,
+ type: ["treeNode"]
+ };
+
+ return ret; // dojo.dnd.Item
+ },
+
+ destroy: function(){
+ // summary:
+ // Prepares this object to be garbage-collected
+
+ dojo.forEach(this.events, dojo.disconnect);
+ // this.clearItems();
+ this.node = this.parent = null;
+ },
+
+ // mouse events
+ onMouseOver: function(/*TreeNode*/ widget, /*Event*/ evt){
+ // summary:
+ // Called when mouse is moved over a TreeNode
+ // tags:
+ // protected
+ this.current = widget;
+ },
+
+ onMouseOut: function(/*TreeNode*/ widget, /*Event*/ evt){
+ // summary:
+ // Called when mouse is moved away from a TreeNode
+ // tags:
+ // protected
+ this.current = null;
+ },
+
+ _changeState: function(type, newState){
+ // summary:
+ // Changes a named state to new state value
+ // type: String
+ // A name of the state to change
+ // newState: String
+ // new state
+ var prefix = "dojoDnd" + type;
+ var state = type.toLowerCase() + "State";
+ //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
+ dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
+ this[state] = newState;
+ },
+
+ _addItemClass: function(node, type){
+ // summary:
+ // Adds a class with prefix "dojoDndItem"
+ // node: Node
+ // A node
+ // type: String
+ // A variable suffix for a class name
+ dojo.addClass(node, "dojoDndItem" + type);
+ },
+
+ _removeItemClass: function(node, type){
+ // summary:
+ // Removes a class with prefix "dojoDndItem"
+ // node: Node
+ // A node
+ // type: String
+ // A variable suffix for a class name
+ dojo.removeClass(node, "dojoDndItem" + type);
+ },
+
+ onOverEvent: function(){
+ // summary:
+ // This function is called once, when mouse is over our container
+ // tags:
+ // protected
+ this._changeState("Container", "Over");
+ },
+
+ onOutEvent: function(){
+ // summary:
+ // This function is called once, when mouse is out of our container
+ // tags:
+ // protected
+ this._changeState("Container", "");
+ }
});
+}
+
+if(!dojo._hasResource["dijit.tree._dndSelector"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dijit.tree._dndSelector"] = true;
+dojo.provide("dijit.tree._dndSelector");
+
+
+
+
+dojo.declare("dijit.tree._dndSelector",
+ dijit.tree._dndContainer,
+ {
+ // summary:
+ // This is a base class for `dijit.tree.dndSource` , and isn't meant to be used directly.
+ // It's based on `dojo.dnd.Selector`.
+ // tags:
+ // protected
+
+ /*=====
+ // selection: Hash<String, DomNode>
+ // (id, DomNode) map for every TreeNode that's currently selected.
+ // The DOMNode is the TreeNode.rowNode.
+ selection: {},
+ =====*/
+
+ constructor: function(tree, params){
+ // summary:
+ // Initialization
+ // tags:
+ // private
+
+ this.selection={};
+ this.anchor = null;
+
+ dijit.setWaiState(this.tree.domNode, "multiselect", !this.singular);
+
+ this.events.push(
+ dojo.connect(this.tree.domNode, "onmousedown", this,"onMouseDown"),
+ dojo.connect(this.tree.domNode, "onmouseup", this,"onMouseUp"),
+ dojo.connect(this.tree.domNode, "onmousemove", this,"onMouseMove")
+ );
+ },
+
+ // singular: Boolean
+ // Allows selection of only one element, if true.
+ // Tree hasn't been tested in singular=true mode, unclear if it works.
+ singular: false,
+
+ // methods
+ getSelectedTreeNodes: function(){
+ // summary:
+ // Returns a list of selected node(s).
+ // Used by dndSource on the start of a drag.
+ // tags:
+ // protected
+ var nodes=[], sel = this.selection;
+ for(var i in sel){
+ nodes.push(sel[i]);
+ }
+ return nodes;
+ },
+
+ selectNone: function(){
+ // summary:
+ // Unselects all items
+ // tags:
+ // private
+
+ this.setSelection([]);
+ return this; // self
+ },
+
+ destroy: function(){
+ // summary:
+ // Prepares the object to be garbage-collected
+ this.inherited(arguments);
+ this.selection = this.anchor = null;
+ },
+ addTreeNode: function(/*dijit._TreeNode*/node, /*Boolean?*/isAnchor){
+ // summary
+ // add node to current selection
+ // node: Node
+ // node to add
+ // isAnchor: Boolean
+ // Whether the node should become anchor.
+ this.setSelection(this.getSelectedTreeNodes().concat( [node] ));
+ if(isAnchor){ this.anchor = node; }
+ return node;
+ },
+ removeTreeNode: function(/*dijit._TreeNode*/node){
+ // summary
+ // remove node from current selection
+ // node: Node
+ // node to remove
+ this.setSelection(this._setDifference(this.getSelectedTreeNodes(), [node]))
+ return node;
+ },
+ isTreeNodeSelected: function(/*dijit._TreeNode*/node){
+ // summary
+ // return true if node is currently selected
+ // node: Node
+ // the node to check whether it's in the current selection
+
+ return node.id && !!this.selection[node.id];
+ },
+ setSelection: function(/*dijit._treeNode[]*/ newSelection){
+ // summary
+ // set the list of selected nodes to be exactly newSelection. All changes to the
+ // selection should be passed through this function, which ensures that derived
+ // attributes are kept up to date. Anchor will be deleted if it has been removed
+ // from the selection, but no new anchor will be added by this function.
+ // newSelection: Node[]
+ // list of tree nodes to make selected
+ var oldSelection = this.getSelectedTreeNodes();
+ dojo.forEach(this._setDifference(oldSelection, newSelection), dojo.hitch(this, function(node){
+ node.setSelected(false);
+ if(this.anchor == node){
+ delete this.anchor;
+ }
+ delete this.selection[node.id];
+ }));
+ dojo.forEach(this._setDifference(newSelection, oldSelection), dojo.hitch(this, function(node){
+ node.setSelected(true);
+ this.selection[node.id] = node;
+ }));
+ this._updateSelectionProperties();
+ },
+ _setDifference: function(xs,ys){
+ // summary
+ // Returns a copy of xs which lacks any objects
+ // occurring in ys. Checks for membership by
+ // modifying and then reading the object, so it will
+ // not properly handle sets of numbers or strings.
+
+ dojo.forEach(ys, function(y){ y.__exclude__ = true; });
+ var ret = dojo.filter(xs, function(x){ return !x.__exclude__; });
+
+ // clean up after ourselves.
+ dojo.forEach(ys, function(y){ delete y['__exclude__'] });
+ return ret;
+ },
+ _updateSelectionProperties: function() {
+ // summary
+ // Update the following tree properties from the current selection:
+ // path[s], selectedItem[s], selectedNode[s]
+
+ var selected = this.getSelectedTreeNodes();
+ var paths = [], nodes = [];
+ dojo.forEach(selected, function(node) {
+ nodes.push(node);
+ paths.push(node.getTreePath());
+ });
+ var items = dojo.map(nodes,function(node) { return node.item; });
+ this.tree._set("paths", paths);
+ this.tree._set("path", paths[0] || []);
+ this.tree._set("selectedNodes", nodes);
+ this.tree._set("selectedNode", nodes[0] || null);
+ this.tree._set("selectedItems", items);
+ this.tree._set("selectedItem", items[0] || null);
+ },
+ // mouse events
+ onMouseDown: function(e){
+ // summary:
+ // Event processor for onmousedown
+ // e: Event
+ // mouse event
+ // tags:
+ // protected
+
+ // ignore click on expando node
+ if(!this.current || this.tree.isExpandoNode( e.target, this.current)){ return; }
+
+ if(e.button == dojo.mouseButtons.RIGHT){ return; } // ignore right-click
+
+ dojo.stopEvent(e);
+
+ var treeNode = this.current,
+ copy = dojo.isCopyKey(e), id = treeNode.id;
+
+ // if shift key is not pressed, and the node is already in the selection,
+ // delay deselection until onmouseup so in the case of DND, deselection
+ // will be canceled by onmousemove.
+ if(!this.singular && !e.shiftKey && this.selection[id]){
+ this._doDeselect = true;
+ return;
+ }else{
+ this._doDeselect = false;
+ }
+ this.userSelect(treeNode, copy, e.shiftKey);
+ },
+
+ onMouseUp: function(e){
+ // summary:
+ // Event processor for onmouseup
+ // e: Event
+ // mouse event
+ // tags:
+ // protected
+
+ // _doDeselect is the flag to indicate that the user wants to either ctrl+click on
+ // a already selected item (to deselect the item), or click on a not-yet selected item
+ // (which should remove all current selection, and add the clicked item). This can not
+ // be done in onMouseDown, because the user may start a drag after mousedown. By moving
+ // the deselection logic here, the user can drags an already selected item.
+ if(!this._doDeselect){ return; }
+ this._doDeselect = false;
+ this.userSelect(this.current, dojo.isCopyKey( e ), e.shiftKey);
+ },
+ onMouseMove: function(e){
+ // summary
+ // event processor for onmousemove
+ // e: Event
+ // mouse event
+ this._doDeselect = false;
+ },
+
+ userSelect: function(node, multi, range){
+ // summary:
+ // Add or remove the given node from selection, responding
+ // to a user action such as a click or keypress.
+ // multi: Boolean
+ // Indicates whether this is meant to be a multi-select action (e.g. ctrl-click)
+ // range: Boolean
+ // Indicates whether this is meant to be a ranged action (e.g. shift-click)
+ // tags:
+ // protected
+
+ if(this.singular){
+ if(this.anchor == node && multi){
+ this.selectNone();
+ }else{
+ this.setSelection([node]);
+ this.anchor = node;
+ }
+ }else{
+ if(range && this.anchor){
+ var cr = dijit.tree._compareNodes(this.anchor.rowNode, node.rowNode),
+ begin, end, anchor = this.anchor;
+
+ if(cr < 0){ //current is after anchor
+ begin = anchor;
+ end = node;
+ }else{ //current is before anchor
+ begin = node;
+ end = anchor;
+ }
+ nodes = [];
+ //add everything betweeen begin and end inclusively
+ while(begin != end) {
+ nodes.push(begin)
+ begin = this.tree._getNextNode(begin);
+ }
+ nodes.push(end)
+
+ this.setSelection(nodes);
+ }else{
+ if( this.selection[ node.id ] && multi ) {
+ this.removeTreeNode( node );
+ } else if(multi) {
+ this.addTreeNode(node, true);
+ } else {
+ this.setSelection([node]);
+ this.anchor = node;
+ }
+ }
+ }
+ },
+
+ forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){
+ // summary:
+ // Iterates over selected items;
+ // see `dojo.dnd.Container.forInItems()` for details
+ o = o || dojo.global;
+ for(var id in this.selection){
+ // console.log("selected item id: " + id);
+ f.call(o, this.getItem(id), id, this);
+ }
+ }
+});
}
@@ -21826,6 +23578,8 @@ dojo.provide("dijit.Tree");
+
+
dojo.declare(
"dijit._TreeNode",
[dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained, dijit._CssStateMixin],
@@ -21836,7 +23590,7 @@ dojo.declare(
// tags:
// private
- // item: dojo.data.Item
+ // item: [const] dojo.data.Item
// the dojo.data entry this tree represents
item: null,
@@ -21863,7 +23617,7 @@ dojo.declare(
// then after dojo.data query it becomes "LOADING" and, finally "LOADED"
state: "UNCHECKED",
- templateString: dojo.cache("dijit", "templates/TreeNode.html", "<div class=\"dijitTreeNode\" waiRole=\"presentation\"\n\t><div dojoAttachPoint=\"rowNode\" class=\"dijitTreeRow\" waiRole=\"presentation\" dojoAttachEvent=\"onmouseenter:_onMouseEnter, onmouseleave:_onMouseLeave, onclick:_onClick, ondblclick:_onDblClick\"\n\t\t><img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" waiRole=\"presentation\"\n\t\t/><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" waiRole=\"presentation\"\n\t\t></span\n\t\t><span dojoAttachPoint=\"contentNode\"\n\t\t\tclass=\"dijitTreeContent\" waiRole=\"presentation\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"iconNode\" class=\"dijitIcon dijitTreeIcon\" waiRole=\"presentation\"\n\t\t\t/><span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" wairole=\"treeitem\" tabindex=\"-1\" waiState=\"selected-false\" dojoAttachEvent=\"onfocus:_onLabelFocus\"></span>\n\t\t</span\n\t></div>\n\t<div dojoAttachPoint=\"containerNode\" class=\"dijitTreeContainer\" waiRole=\"presentation\" style=\"display: none;\"></div>\n</div>\n"),
+ templateString: dojo.cache("dijit", "templates/TreeNode.html", "<div class=\"dijitTreeNode\" role=\"presentation\"\n\t><div dojoAttachPoint=\"rowNode\" class=\"dijitTreeRow\" role=\"presentation\" dojoAttachEvent=\"onmouseenter:_onMouseEnter, onmouseleave:_onMouseLeave, onclick:_onClick, ondblclick:_onDblClick\"\n\t\t><img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" role=\"presentation\"\n\t\t/><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" role=\"presentation\"\n\t\t></span\n\t\t><span dojoAttachPoint=\"contentNode\"\n\t\t\tclass=\"dijitTreeContent\" role=\"presentation\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"iconNode\" class=\"dijitIcon dijitTreeIcon\" role=\"presentation\"\n\t\t\t/><span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" role=\"treeitem\" tabindex=\"-1\" aria-selected=\"false\" dojoAttachEvent=\"onfocus:_onLabelFocus\"></span>\n\t\t</span\n\t></div>\n\t<div dojoAttachPoint=\"containerNode\" class=\"dijitTreeContainer\" role=\"presentation\" style=\"display: none;\"></div>\n</div>\n"),
baseClass: "dijitTreeNode",
@@ -21878,7 +23632,7 @@ dojo.declare(
tooltip: {node: "rowNode", type: "attribute", attribute: "title"}
}),
- postCreate: function(){
+ buildRendering: function(){
this.inherited(arguments);
// set expand icon for leaf
@@ -21890,6 +23644,9 @@ dojo.declare(
if(this.isExpandable){
dijit.setWaiState(this.labelNode, "expanded", this.isExpanded);
}
+
+ //aria-selected should be false on all selectable elements.
+ this.setSelected(false);
},
_setIndentAttr: function(indent){
@@ -21898,7 +23655,6 @@ dojo.declare(
// description:
// 0 for top level nodes, 1 for their children, 2 for their
// grandchildren, etc.
- this.indent = indent;
// Math.max() is to prevent negative padding on hidden root node (when indent == -1)
var pixels = (Math.max(indent, 0) * this.tree._nodePixelIndent) + "px";
@@ -21909,6 +23665,8 @@ dojo.declare(
dojo.forEach(this.getChildren(), function(child){
child.set("indent", indent+1);
});
+
+ this._set("indent", indent);
},
markProcessing: function(){
@@ -21962,14 +23720,11 @@ dojo.declare(
var clsName = "_" + lower + "Class";
var nodeName = lower + "Node";
+ var oldCls = this[clsName];
- if(this[clsName]){
- dojo.removeClass(this[nodeName], this[clsName]);
- }
this[clsName] = this.tree["get" + upper + "Class"](item, this.isExpanded);
- if(this[clsName]){
- dojo.addClass(this[nodeName], this[clsName]);
- }
+ dojo.replaceClass(this[nodeName], this[clsName] || "", oldCls || "");
+
dojo.style(this[nodeName], this.tree["get" + upper + "Style"](item, this.isExpanded) || {});
},
@@ -21999,8 +23754,7 @@ dojo.declare(
idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3);
// apply the appropriate class to the expando node
- dojo.removeClass(this.expandoNode, styles);
- dojo.addClass(this.expandoNode, styles[idx]);
+ dojo.replaceClass(this.expandoNode, styles[idx], styles);
// provide a non-image based indicator for images-off mode
this.expandoNodeText.innerHTML = _a11yStates[idx];
@@ -22025,7 +23779,9 @@ dojo.declare(
// set when the animation completes instead
this.isExpanded = true;
dijit.setWaiState(this.labelNode, "expanded", "true");
- dijit.setWaiRole(this.containerNode, "group");
+ if(this.tree.showRoot || this !== this.tree.rootNode){
+ dijit.setWaiRole(this.containerNode, "group");
+ }
dojo.addClass(this.contentNode,'dijitTreeContentExpanded');
this._setExpando();
this._updateItemClasses(this.item);
@@ -22187,6 +23943,22 @@ dojo.declare(
return new dojo.DeferredList(defs); // dojo.Deferred
},
+ getTreePath: function(){
+ var node = this;
+ var path = [];
+ while(node && node !== this.tree.rootNode){
+ path.unshift(node.item);
+ node = node.getParent();
+ }
+ path.unshift(this.tree.rootNode.item);
+
+ return path;
+ },
+
+ getIdentity: function() {
+ return this.tree.model.getIdentity(this.item);
+ },
+
removeChild: function(/* treeNode */ node){
this.inherited(arguments);
@@ -22314,17 +24086,25 @@ dojo.declare(
// One ore more attributes that holds children of a tree node
childrenAttr: ["children"],
- // path: String[] or Item[]
- // Full path from rootNode to selected node expressed as array of items or array of ids.
- // Since setting the path may be asynchronous (because ofwaiting on dojo.data), set("path", ...)
+ // paths: String[][] or Item[][]
+ // Full paths from rootNode to selected nodes expressed as array of items or array of ids.
+ // Since setting the paths may be asynchronous (because ofwaiting on dojo.data), set("paths", ...)
// returns a Deferred to indicate when the set is complete.
+ paths: [],
+
+ // path: String[] or Item[]
+ // Backward compatible singular variant of paths.
path: [],
- // selectedItem: [readonly] Item
- // The currently selected item in this tree.
- // This property can only be set (via set('selectedItem', ...)) when that item is already
+ // selectedItems: [readonly] Item[]
+ // The currently selected items in this tree.
+ // This property can only be set (via set('selectedItems', ...)) when that item is already
// visible in the tree. (I.e. the tree has already been expanded to show that node.)
- // Should generally use `path` attribute to set the selected item instead.
+ // Should generally use `paths` attribute to set the selected items instead.
+ selectedItems: null,
+
+ // selectedItem: [readonly] Item
+ // Backward compatible singular variant of selectedItems.
selectedItem: null,
// openOnClick: Boolean
@@ -22335,20 +24115,21 @@ dojo.declare(
// If true, double-clicking a folder node's label will open it, rather than calling onDblClick()
openOnDblClick: false,
- templateString: dojo.cache("dijit", "templates/Tree.html", "<div class=\"dijitTree dijitTreeContainer\" waiRole=\"tree\"\n\tdojoAttachEvent=\"onkeypress:_onKeyPress\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" dojoAttachPoint=\"indentDetector\"></div>\n</div>\n"),
+ templateString: dojo.cache("dijit", "templates/Tree.html", "<div class=\"dijitTree dijitTreeContainer\" role=\"tree\"\n\tdojoAttachEvent=\"onkeypress:_onKeyPress\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" dojoAttachPoint=\"indentDetector\"></div>\n</div>\n"),
// persist: Boolean
// Enables/disables use of cookies for state saving.
persist: true,
// autoExpand: Boolean
- // Fully expand the tree on load. Overrides `persist`
+ // Fully expand the tree on load. Overrides `persist`.
autoExpand: false,
// dndController: [protected] String
// Class name to use as as the dnd controller. Specifying this class enables DnD.
// Generally you should specify this as "dijit.tree.dndSource".
- dndController: null,
+ // Default of "dijit.tree._dndSelector" handles selection only (no actual DnD).
+ dndController: "dijit.tree._dndSelector",
// parameters to pull off of the tree and pass on to the dndController as its params
dndParams: ["onDndDrop","itemCreator","onDndCancel","checkAcceptance", "checkItemAcceptance", "dragThreshold", "betweenThreshold"],
@@ -22566,6 +24347,12 @@ dojo.declare(
}));
if(!this.showRoot){
rn.rowNode.style.display="none";
+ // if root is not visible, move tree role to the invisible
+ // root node's containerNode, see #12135
+ dijit.setWaiRole(this.domNode, 'presentation');
+
+ dijit.setWaiRole(rn.labelNode, 'presentation');
+ dijit.setWaiRole(rn.containerNode, 'tree');
}
this.domNode.appendChild(rn.domNode);
var identity = this.model.getIdentity(item);
@@ -22602,104 +24389,103 @@ dojo.declare(
},
_setSelectedItemAttr: function(/*dojo.data.Item or id*/ item){
- // summary:
- // Select a tree node related to passed item.
- // WARNING: if model use multi-parented items or desired tree node isn't already loaded
- // behavior is undefined. Use set('path', ...) instead.
-
- var oldValue = this.get("selectedItem");
- var identity = (!item || dojo.isString(item)) ? item : this.model.getIdentity(item);
- if(identity == oldValue ? this.model.getIdentity(oldValue) : null){ return; }
- var nodes = this._itemNodesMap[identity];
- this._selectNode((nodes && nodes[0]) || null); //select the first item
+ this.set('selectedItems', [item]);
},
- _getSelectedItemAttr: function(){
+ _setSelectedItemsAttr: function(/*dojo.data.Items or ids*/ items){
// summary:
- // Return item related to selected tree node.
- return this.selectedNode && this.selectedNode.item;
+ // Select tree nodes related to passed items.
+ // WARNING: if model use multi-parented items or desired tree node isn't already loaded
+ // behavior is undefined. Use set('paths', ...) instead.
+ var tree = this;
+ this._loadDeferred.addCallback( dojo.hitch(this, function(){
+ var identities = dojo.map(items, function(item){
+ return (!item || dojo.isString(item)) ? item : tree.model.getIdentity(item);
+ });
+ var nodes = [];
+ dojo.forEach(identities, function(id){
+ nodes = nodes.concat(tree._itemNodesMap[id] || []);
+ });
+ this.set('selectedNodes', nodes);
+ }));
},
_setPathAttr: function(/*Item[] || String[]*/ path){
// summary:
- // Select the tree node identified by passed path.
- // path:
- // Array of items or item id's
+ // Singular variant of _setPathsAttr
+ if(path.length) {
+ return this.set("paths", [path]);
+ } else {
+ //Empty list is interpreted as "select nothing"
+ return this.set("paths", []);
+ }
+ },
+
+ _setPathsAttr: function(/*Item[][] || String[][]*/ paths){
+ // summary:
+ // Select the tree nodes identified by passed paths.
+ // paths:
+ // Array of arrays of items or item id's
// returns:
// Deferred to indicate when the set is complete
+ var tree = this;
- var d = new dojo.Deferred();
-
- this._selectNode(null);
- if(!path || !path.length){
- d.resolve(true);
- return d;
- }
+ // We may need to wait for some nodes to expand, so setting
+ // each path will involve a Deferred. We bring those deferreds
+ // together witha DeferredList.
+ return new dojo.DeferredList(dojo.map(paths, function(path){
+ var d = new dojo.Deferred();
+
+ // normalize path to use identity
+ path = dojo.map(path, function(item){
+ return dojo.isString(item) ? item : tree.model.getIdentity(item);
+ });
- // If this is called during initialization, defer running until Tree has finished loading
- this._loadDeferred.addCallback(dojo.hitch(this, function(){
- if(!this.rootNode){
- d.reject(new Error("!this.rootNode"));
- return;
- }
- if(path[0] !== this.rootNode.item && (dojo.isString(path[0]) && path[0] != this.model.getIdentity(this.rootNode.item))){
- d.reject(new Error(this.id + ":path[0] doesn't match this.rootNode.item. Maybe you are using the wrong tree."));
- return;
+ if(path.length){
+ // Wait for the tree to load, if it hasn't already.
+ tree._loadDeferred.addCallback(function(){ selectPath(path, [tree.rootNode], d); });
+ }else{
+ d.errback("Empty path");
}
- path.shift();
-
- var node = this.rootNode;
-
- function advance(){
- // summary:
- // Called when "node" has completed loading and expanding. Pop the next item from the path
- // (which must be a child of "node") and advance to it, and then recurse.
-
- // Set item and identity to next item in path (node is pointing to the item that was popped
- // from the path _last_ time.
- var item = path.shift(),
- identity = dojo.isString(item) ? item : this.model.getIdentity(item);
-
- // Change "node" from previous item in path to the item we just popped from path
- dojo.some(this._itemNodesMap[identity], function(n){
- if(n.getParent() == node){
- node = n;
- return true;
- }
- return false;
- });
+ return d;
+ })).addCallback(setNodes);
+ function selectPath(path, nodes, def){
+ // Traverse path; the next path component should be among "nodes".
+ var nextPath = path.shift();
+ var nextNode = dojo.filter(nodes, function(node){
+ return node.getIdentity() == nextPath;
+ })[0];
+ if(!!nextNode){
if(path.length){
- // Need to do more expanding
- this._expandNode(node).addCallback(dojo.hitch(this, advance));
+ tree._expandNode(nextNode).addCallback(function(){ selectPath(path, nextNode.getChildren(), def); });
}else{
- // Final destination node, select it
- this._selectNode(node);
-
- // signal that path setting is finished
- d.resolve(true);
+ //Successfully reached the end of this path
+ def.callback(nextNode);
}
+ } else {
+ def.errback("Could not expand path at " + nextPath);
}
+ }
+
+ function setNodes(newNodes){
+ //After all expansion is finished, set the selection to
+ //the set of nodes successfully found.
+ tree.set("selectedNodes", dojo.map(
+ dojo.filter(newNodes,function(x){return x[0];}),
+ function(x){return x[1];}));
+ }
+ },
- this._expandNode(node).addCallback(dojo.hitch(this, advance));
+ _setSelectedNodeAttr: function(node){
+ this.set('selectedNodes', [node]);
+ },
+ _setSelectedNodesAttr: function(nodes){
+ this._loadDeferred.addCallback( dojo.hitch(this, function(){
+ this.dndController.setSelection(nodes);
}));
-
- return d;
},
- _getPathAttr: function(){
- // summary:
- // Return an array of items that is the path to selected tree node.
- if(!this.selectedNode){ return; }
- var res = [];
- var treeNode = this.selectedNode;
- while(treeNode && treeNode !== this.rootNode){
- res.unshift(treeNode.item);
- treeNode = treeNode.getParent();
- }
- res.unshift(this.rootNode.item);
- return res;
- },
////////////// Data store related functions //////////////////////
// These just get passed to the model; they are here for back-compat
@@ -22804,7 +24590,7 @@ dojo.declare(
if(!treeNode){ return; }
var key = e.charOrCode;
- if(typeof key == "string"){ // handle printables (letter navigation)
+ if(typeof key == "string" && key != " "){ // handle printables (letter navigation)
// Check for key navigation.
if(!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey){
this._onLetterKeyNav( { node: treeNode, key: key.toLowerCase() } );
@@ -22823,6 +24609,11 @@ dojo.declare(
// setup table mapping keys to events
map = {};
map[dk.ENTER]="_onEnterKey";
+ //On WebKit based browsers, the combination ctrl-enter
+ //does not get passed through. To allow accessible
+ //multi-select on those browsers, the space key is
+ //also used for selection.
+ map[dk.SPACE]= map[" "] = "_onEnterKey";
map[this.isLeftToRight() ? dk.LEFT_ARROW : dk.RIGHT_ARROW]="_onLeftArrow";
map[this.isLeftToRight() ? dk.RIGHT_ARROW : dk.LEFT_ARROW]="_onRightArrow";
map[dk.UP_ARROW]="_onUpArrow";
@@ -22838,10 +24629,10 @@ dojo.declare(
}
},
- _onEnterKey: function(/*Object*/ message, /*Event*/ evt){
+ _onEnterKey: function(/*Object*/ message){
this._publish("execute", { item: message.item, node: message.node } );
- this._selectNode(message.node);
- this.onClick(message.item, message.node, evt);
+ this.dndController.userSelect(message.node, dojo.isCopyKey( message.evt ), message.evt.shiftKey);
+ this.onClick(message.item, message.node, message.evt);
},
_onDownArrow: function(/*Object*/ message){
@@ -22993,12 +24784,17 @@ dojo.declare(
}
},
+ isExpandoNode: function(node, widget){
+ // summary:
+ // check whether a dom node is the expandoNode for a particular TreeNode widget
+ return dojo.isDescendant(node, widget.expandoNode);
+ },
_onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){
// summary:
// Translates click events into commands for the controller to process
var domElement = e.target,
- isExpandoClick = (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText);
+ isExpandoClick = this.isExpandoNode(domElement, nodeWidget);
if( (this.openOnClick && nodeWidget.isExpandable) || isExpandoClick ){
// expando node was clicked, or label of a folder node was clicked; open it
@@ -23010,9 +24806,6 @@ dojo.declare(
this.onClick(nodeWidget.item, nodeWidget, e);
this.focusNode(nodeWidget);
}
- if(!isExpandoClick){
- this._selectNode(nodeWidget);
- }
dojo.stopEvent(e);
},
_onDblClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){
@@ -23032,9 +24825,6 @@ dojo.declare(
this.onDblClick(nodeWidget.item, nodeWidget, e);
this.focusNode(nodeWidget);
}
- if(!isExpandoClick){
- this._selectNode(nodeWidget);
- }
dojo.stopEvent(e);
},
@@ -23213,21 +25003,6 @@ dojo.declare(
dijit.focus(node.labelNode);
},
- _selectNode: function(/*_tree.Node*/ node){
- // summary:
- // Mark specified node as select, and unmark currently selected node.
- // tags:
- // protected
-
- if(this.selectedNode && !this.selectedNode._destroyed){
- this.selectedNode.setSelected(false);
- }
- if(node){
- node.setSelected(true);
- }
- this.selectedNode = node;
- },
-
_onNodeFocus: function(/*dijit._Widget*/ node){
// summary:
// Called when a TreeNode gets focus, either by user clicking
@@ -23306,13 +25081,16 @@ dojo.declare(
if(nodes){
dojo.forEach(nodes,function(node){
+ // Remove node from set of selected nodes (if it's selected)
+ this.dndController.removeTreeNode(node);
+
var parent = node.getParent();
if(parent){
// if node has not already been orphaned from a _onSetItem(parent, "children", ..) call...
parent.removeChild(node);
}
node.destroyRecursive();
- });
+ }, this);
delete this._itemNodesMap[identity];
}
},
@@ -23385,13 +25163,12 @@ dojo.declare(
resize: function(changeSize){
if(changeSize){
dojo.marginBox(this.domNode, changeSize);
- dojo.style(this.domNode, "overflow", "auto"); // for scrollbars
}
// The only JS sizing involved w/tree is the indentation, which is specified
// in CSS and read in through this dummy indentDetector node (tree must be
// visible and attached to the DOM to read this)
- this._nodePixelIndent = dojo.marginBox(this.tree.indentDetector).w;
+ this._nodePixelIndent = dojo._getMarginSize(this.tree.indentDetector).w;
if(this.tree.rootNode){
// If tree has already loaded, then reset indent for all the nodes
@@ -23413,822 +25190,6 @@ dojo.declare(
// For back-compat. TODO: remove in 2.0
-
-
-}
-
-if(!dojo._hasResource["dojo.dnd.Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
-dojo._hasResource["dojo.dnd.Container"] = true;
-dojo.provide("dojo.dnd.Container");
-
-
-
-
-/*
- Container states:
- "" - normal state
- "Over" - mouse over a container
- Container item states:
- "" - normal state
- "Over" - mouse over a container item
-*/
-
-/*=====
-dojo.declare("dojo.dnd.__ContainerArgs", [], {
- creator: function(){
- // summary:
- // a creator function, which takes a data item, and returns an object like that:
- // {node: newNode, data: usedData, type: arrayOfStrings}
- },
-
- // skipForm: Boolean
- // don't start the drag operation, if clicked on form elements
- skipForm: false,
-
- // dropParent: Node||String
- // node or node's id to use as the parent node for dropped items
- // (must be underneath the 'node' parameter in the DOM)
- dropParent: null,
-
- // _skipStartup: Boolean
- // skip startup(), which collects children, for deferred initialization
- // (this is used in the markup mode)
- _skipStartup: false
-});
-
-dojo.dnd.Item = function(){
- // summary:
- // Represents (one of) the source node(s) being dragged.
- // Contains (at least) the "type" and "data" attributes.
- // type: String[]
- // Type(s) of this item, by default this is ["text"]
- // data: Object
- // Logical representation of the object being dragged.
- // If the drag object's type is "text" then data is a String,
- // if it's another type then data could be a different Object,
- // perhaps a name/value hash.
-
- this.type = type;
- this.data = data;
-}
-=====*/
-
-dojo.declare("dojo.dnd.Container", null, {
- // summary:
- // a Container object, which knows when mouse hovers over it,
- // and over which element it hovers
-
- // object attributes (for markup)
- skipForm: false,
-
- /*=====
- // current: DomNode
- // The DOM node the mouse is currently hovered over
- current: null,
-
- // map: Hash<String, dojo.dnd.Item>
- // Map from an item's id (which is also the DOMNode's id) to
- // the dojo.dnd.Item itself.
- map: {},
- =====*/
-
- constructor: function(node, params){
- // summary:
- // a constructor of the Container
- // node: Node
- // node or node's id to build the container on
- // params: dojo.dnd.__ContainerArgs
- // a dictionary of parameters
- this.node = dojo.byId(node);
- if(!params){ params = {}; }
- this.creator = params.creator || null;
- this.skipForm = params.skipForm;
- this.parent = params.dropParent && dojo.byId(params.dropParent);
-
- // class-specific variables
- this.map = {};
- this.current = null;
-
- // states
- this.containerState = "";
- dojo.addClass(this.node, "dojoDndContainer");
-
- // mark up children
- if(!(params && params._skipStartup)){
- this.startup();
- }
-
- // set up events
- this.events = [
- dojo.connect(this.node, "onmouseover", this, "onMouseOver"),
- dojo.connect(this.node, "onmouseout", this, "onMouseOut"),
- // cancel text selection and text dragging
- dojo.connect(this.node, "ondragstart", this, "onSelectStart"),
- dojo.connect(this.node, "onselectstart", this, "onSelectStart")
- ];
- },
-
- // object attributes (for markup)
- creator: function(){
- // summary:
- // creator function, dummy at the moment
- },
-
- // abstract access to the map
- getItem: function(/*String*/ key){
- // summary:
- // returns a data item by its key (id)
- return this.map[key]; // dojo.dnd.Item
- },
- setItem: function(/*String*/ key, /*dojo.dnd.Item*/ data){
- // summary:
- // associates a data item with its key (id)
- this.map[key] = data;
- },
- delItem: function(/*String*/ key){
- // summary:
- // removes a data item from the map by its key (id)
- delete this.map[key];
- },
- forInItems: function(/*Function*/ f, /*Object?*/ o){
- // summary:
- // iterates over a data map skipping members that
- // are present in the empty object (IE and/or 3rd-party libraries).
- o = o || dojo.global;
- var m = this.map, e = dojo.dnd._empty;
- for(var i in m){
- if(i in e){ continue; }
- f.call(o, m[i], i, this);
- }
- return o; // Object
- },
- clearItems: function(){
- // summary:
- // removes all data items from the map
- this.map = {};
- },
-
- // methods
- getAllNodes: function(){
- // summary:
- // returns a list (an array) of all valid child nodes
- return dojo.query("> .dojoDndItem", this.parent); // NodeList
- },
- sync: function(){
- // summary:
- // sync up the node list with the data map
- var map = {};
- this.getAllNodes().forEach(function(node){
- if(node.id){
- var item = this.getItem(node.id);
- if(item){
- map[node.id] = item;
- return;
- }
- }else{
- node.id = dojo.dnd.getUniqueId();
- }
- var type = node.getAttribute("dndType"),
- data = node.getAttribute("dndData");
- map[node.id] = {
- data: data || node.innerHTML,
- type: type ? type.split(/\s*,\s*/) : ["text"]
- };
- }, this);
- this.map = map;
- return this; // self
- },
- insertNodes: function(data, before, anchor){
- // summary:
- // inserts an array of new nodes before/after an anchor node
- // data: Array
- // a list of data items, which should be processed by the creator function
- // before: Boolean
- // insert before the anchor, if true, and after the anchor otherwise
- // anchor: Node
- // the anchor node to be used as a point of insertion
- if(!this.parent.firstChild){
- anchor = null;
- }else if(before){
- if(!anchor){
- anchor = this.parent.firstChild;
- }
- }else{
- if(anchor){
- anchor = anchor.nextSibling;
- }
- }
- if(anchor){
- for(var i = 0; i < data.length; ++i){
- var t = this._normalizedCreator(data[i]);
- this.setItem(t.node.id, {data: t.data, type: t.type});
- this.parent.insertBefore(t.node, anchor);
- }
- }else{
- for(var i = 0; i < data.length; ++i){
- var t = this._normalizedCreator(data[i]);
- this.setItem(t.node.id, {data: t.data, type: t.type});
- this.parent.appendChild(t.node);
- }
- }
- return this; // self
- },
- destroy: function(){
- // summary:
- // prepares this object to be garbage-collected
- dojo.forEach(this.events, dojo.disconnect);
- this.clearItems();
- this.node = this.parent = this.current = null;
- },
-
- // markup methods
- markupFactory: function(params, node){
- params._skipStartup = true;
- return new dojo.dnd.Container(node, params);
- },
- startup: function(){
- // summary:
- // collects valid child items and populate the map
-
- // set up the real parent node
- if(!this.parent){
- // use the standard algorithm, if not assigned
- this.parent = this.node;
- if(this.parent.tagName.toLowerCase() == "table"){
- var c = this.parent.getElementsByTagName("tbody");
- if(c && c.length){ this.parent = c[0]; }
- }
- }
- this.defaultCreator = dojo.dnd._defaultCreator(this.parent);
-
- // process specially marked children
- this.sync();
- },
-
- // mouse events
- onMouseOver: function(e){
- // summary:
- // event processor for onmouseover
- // e: Event
- // mouse event
- var n = e.relatedTarget;
- while(n){
- if(n == this.node){ break; }
- try{
- n = n.parentNode;
- }catch(x){
- n = null;
- }
- }
- if(!n){
- this._changeState("Container", "Over");
- this.onOverEvent();
- }
- n = this._getChildByEvent(e);
- if(this.current == n){ return; }
- if(this.current){ this._removeItemClass(this.current, "Over"); }
- if(n){ this._addItemClass(n, "Over"); }
- this.current = n;
- },
- onMouseOut: function(e){
- // summary:
- // event processor for onmouseout
- // e: Event
- // mouse event
- for(var n = e.relatedTarget; n;){
- if(n == this.node){ return; }
- try{
- n = n.parentNode;
- }catch(x){
- n = null;
- }
- }
- if(this.current){
- this._removeItemClass(this.current, "Over");
- this.current = null;
- }
- this._changeState("Container", "");
- this.onOutEvent();
- },
- onSelectStart: function(e){
- // summary:
- // event processor for onselectevent and ondragevent
- // e: Event
- // mouse event
- if(!this.skipForm || !dojo.dnd.isFormElement(e)){
- dojo.stopEvent(e);
- }
- },
-
- // utilities
- onOverEvent: function(){
- // summary:
- // this function is called once, when mouse is over our container
- },
- onOutEvent: function(){
- // summary:
- // this function is called once, when mouse is out of our container
- },
- _changeState: function(type, newState){
- // summary:
- // changes a named state to new state value
- // type: String
- // a name of the state to change
- // newState: String
- // new state
- var prefix = "dojoDnd" + type;
- var state = type.toLowerCase() + "State";
- //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
- dojo.removeClass(this.node, prefix + this[state]);
- dojo.addClass(this.node, prefix + newState);
- this[state] = newState;
- },
- _addItemClass: function(node, type){
- // summary:
- // adds a class with prefix "dojoDndItem"
- // node: Node
- // a node
- // type: String
- // a variable suffix for a class name
- dojo.addClass(node, "dojoDndItem" + type);
- },
- _removeItemClass: function(node, type){
- // summary:
- // removes a class with prefix "dojoDndItem"
- // node: Node
- // a node
- // type: String
- // a variable suffix for a class name
- dojo.removeClass(node, "dojoDndItem" + type);
- },
- _getChildByEvent: function(e){
- // summary:
- // gets a child, which is under the mouse at the moment, or null
- // e: Event
- // a mouse event
- var node = e.target;
- if(node){
- for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){
- if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; }
- }
- }
- return null;
- },
- _normalizedCreator: function(/*dojo.dnd.Item*/ item, /*String*/ hint){
- // summary:
- // adds all necessary data to the output of the user-supplied creator function
- var t = (this.creator || this.defaultCreator).call(this, item, hint);
- if(!dojo.isArray(t.type)){ t.type = ["text"]; }
- if(!t.node.id){ t.node.id = dojo.dnd.getUniqueId(); }
- dojo.addClass(t.node, "dojoDndItem");
- return t;
- }
-});
-
-dojo.dnd._createNode = function(tag){
- // summary:
- // returns a function, which creates an element of given tag
- // (SPAN by default) and sets its innerHTML to given text
- // tag: String
- // a tag name or empty for SPAN
- if(!tag){ return dojo.dnd._createSpan; }
- return function(text){ // Function
- return dojo.create(tag, {innerHTML: text}); // Node
- };
-};
-
-dojo.dnd._createTrTd = function(text){
- // summary:
- // creates a TR/TD structure with given text as an innerHTML of TD
- // text: String
- // a text for TD
- var tr = dojo.create("tr");
- dojo.create("td", {innerHTML: text}, tr);
- return tr; // Node
-};
-
-dojo.dnd._createSpan = function(text){
- // summary:
- // creates a SPAN element with given text as its innerHTML
- // text: String
- // a text for SPAN
- return dojo.create("span", {innerHTML: text}); // Node
-};
-
-// dojo.dnd._defaultCreatorNodes: Object
-// a dictionary that maps container tag names to child tag names
-dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"};
-
-dojo.dnd._defaultCreator = function(node){
- // summary:
- // takes a parent node, and returns an appropriate creator function
- // node: Node
- // a container node
- var tag = node.tagName.toLowerCase();
- var c = tag == "tbody" || tag == "thead" ? dojo.dnd._createTrTd :
- dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]);
- return function(item, hint){ // Function
- var isObj = item && dojo.isObject(item), data, type, n;
- if(isObj && item.tagName && item.nodeType && item.getAttribute){
- // process a DOM node
- data = item.getAttribute("dndData") || item.innerHTML;
- type = item.getAttribute("dndType");
- type = type ? type.split(/\s*,\s*/) : ["text"];
- n = item; // this node is going to be moved rather than copied
- }else{
- // process a DnD item object or a string
- data = (isObj && item.data) ? item.data : item;
- type = (isObj && item.type) ? item.type : ["text"];
- n = (hint == "avatar" ? dojo.dnd._createSpan : c)(String(data));
- }
- if(!n.id){
- n.id = dojo.dnd.getUniqueId();
- }
- return {node: n, data: data, type: type};
- };
-};
-
-}
-
-if(!dojo._hasResource["dijit.tree._dndContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
-dojo._hasResource["dijit.tree._dndContainer"] = true;
-dojo.provide("dijit.tree._dndContainer");
-
-
-
-dojo.declare("dijit.tree._dndContainer",
- null,
- {
-
- // summary:
- // This is a base class for `dijit.tree._dndSelector`, and isn't meant to be used directly.
- // It's modeled after `dojo.dnd.Container`.
- // tags:
- // protected
-
- /*=====
- // current: DomNode
- // The currently hovered TreeNode.rowNode (which is the DOM node
- // associated w/a given node in the tree, excluding it's descendants)
- current: null,
- =====*/
-
- constructor: function(tree, params){
- // summary:
- // A constructor of the Container
- // tree: Node
- // Node or node's id to build the container on
- // params: dijit.tree.__SourceArgs
- // A dict of parameters, which gets mixed into the object
- // tags:
- // private
- this.tree = tree;
- this.node = tree.domNode; // TODO: rename; it's not a TreeNode but the whole Tree
- dojo.mixin(this, params);
-
- // class-specific variables
- this.map = {};
- this.current = null; // current TreeNode's DOM node
-
- // states
- this.containerState = "";
- dojo.addClass(this.node, "dojoDndContainer");
-
- // set up events
- this.events = [
- // container level events
- dojo.connect(this.node, "onmouseenter", this, "onOverEvent"),
- dojo.connect(this.node, "onmouseleave", this, "onOutEvent"),
-
- // switching between TreeNodes
- dojo.connect(this.tree, "_onNodeMouseEnter", this, "onMouseOver"),
- dojo.connect(this.tree, "_onNodeMouseLeave", this, "onMouseOut"),
-
- // cancel text selection and text dragging
- dojo.connect(this.node, "ondragstart", dojo, "stopEvent"),
- dojo.connect(this.node, "onselectstart", dojo, "stopEvent")
- ];
- },
-
- getItem: function(/*String*/ key){
- // summary:
- // Returns the dojo.dnd.Item (representing a dragged node) by it's key (id).
- // Called by dojo.dnd.Source.checkAcceptance().
- // tags:
- // protected
-
- var node = this.selection[key],
- ret = {
- data: dijit.getEnclosingWidget(node),
- type: ["treeNode"]
- };
-
- return ret; // dojo.dnd.Item
- },
-
- destroy: function(){
- // summary:
- // Prepares this object to be garbage-collected
-
- dojo.forEach(this.events, dojo.disconnect);
- // this.clearItems();
- this.node = this.parent = null;
- },
-
- // mouse events
- onMouseOver: function(/*TreeNode*/ widget, /*Event*/ evt){
- // summary:
- // Called when mouse is moved over a TreeNode
- // tags:
- // protected
- this.current = widget.rowNode;
- this.currentWidget = widget;
- },
-
- onMouseOut: function(/*TreeNode*/ widget, /*Event*/ evt){
- // summary:
- // Called when mouse is moved away from a TreeNode
- // tags:
- // protected
- this.current = null;
- this.currentWidget = null;
- },
-
- _changeState: function(type, newState){
- // summary:
- // Changes a named state to new state value
- // type: String
- // A name of the state to change
- // newState: String
- // new state
- var prefix = "dojoDnd" + type;
- var state = type.toLowerCase() + "State";
- //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
- dojo.removeClass(this.node, prefix + this[state]);
- dojo.addClass(this.node, prefix + newState);
- this[state] = newState;
- },
-
- _addItemClass: function(node, type){
- // summary:
- // Adds a class with prefix "dojoDndItem"
- // node: Node
- // A node
- // type: String
- // A variable suffix for a class name
- dojo.addClass(node, "dojoDndItem" + type);
- },
-
- _removeItemClass: function(node, type){
- // summary:
- // Removes a class with prefix "dojoDndItem"
- // node: Node
- // A node
- // type: String
- // A variable suffix for a class name
- dojo.removeClass(node, "dojoDndItem" + type);
- },
-
- onOverEvent: function(){
- // summary:
- // This function is called once, when mouse is over our container
- // tags:
- // protected
- this._changeState("Container", "Over");
- },
-
- onOutEvent: function(){
- // summary:
- // This function is called once, when mouse is out of our container
- // tags:
- // protected
- this._changeState("Container", "");
- }
-});
-
-}
-
-if(!dojo._hasResource["dijit.tree._dndSelector"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
-dojo._hasResource["dijit.tree._dndSelector"] = true;
-dojo.provide("dijit.tree._dndSelector");
-
-
-
-dojo.declare("dijit.tree._dndSelector",
- dijit.tree._dndContainer,
- {
- // summary:
- // This is a base class for `dijit.tree.dndSource` , and isn't meant to be used directly.
- // It's based on `dojo.dnd.Selector`.
- // tags:
- // protected
-
- /*=====
- // selection: Hash<String, DomNode>
- // (id, DomNode) map for every TreeNode that's currently selected.
- // The DOMNode is the TreeNode.rowNode.
- selection: {},
- =====*/
-
- constructor: function(tree, params){
- // summary:
- // Initialization
- // tags:
- // private
-
- this.selection={};
- this.anchor = null;
- this.simpleSelection=false;
-
- this.events.push(
- dojo.connect(this.tree.domNode, "onmousedown", this,"onMouseDown"),
- dojo.connect(this.tree.domNode, "onmouseup", this,"onMouseUp"),
- dojo.connect(this.tree.domNode, "onmousemove", this,"onMouseMove")
- );
- },
-
- // singular: Boolean
- // Allows selection of only one element, if true.
- // Tree hasn't been tested in singular=true mode, unclear if it works.
- singular: false,
-
- // methods
-
- getSelectedNodes: function(){
- // summary:
- // Returns the set of selected nodes.
- // Used by dndSource on the start of a drag.
- // tags:
- // protected
- return this.selection;
- },
-
- selectNone: function(){
- // summary:
- // Unselects all items
- // tags:
- // private
-
- return this._removeSelection()._removeAnchor(); // self
- },
-
- destroy: function(){
- // summary:
- // Prepares the object to be garbage-collected
- this.inherited(arguments);
- this.selection = this.anchor = null;
- },
-
- // mouse events
- onMouseDown: function(e){
- // summary:
- // Event processor for onmousedown
- // e: Event
- // mouse event
- // tags:
- // protected
-
- if(!this.current){ return; }
-
- if(e.button == dojo.mouseButtons.RIGHT){ return; } // ignore right-click
-
- var treeNode = dijit.getEnclosingWidget(this.current),
- id = treeNode.id + "-dnd" // so id doesn't conflict w/widget
-
- if(!dojo.hasAttr(this.current, "id")){
- dojo.attr(this.current, "id", id);
- }
-
- if(!this.singular && !dojo.isCopyKey(e) && !e.shiftKey && (this.current.id in this.selection)){
- this.simpleSelection = true;
- dojo.stopEvent(e);
- return;
- }
- if(this.singular){
- if(this.anchor == this.current){
- if(dojo.isCopyKey(e)){
- this.selectNone();
- }
- }else{
- this.selectNone();
- this.anchor = this.current;
- this._addItemClass(this.anchor, "Anchor");
-
- this.selection[this.current.id] = this.current;
- }
- }else{
- if(!this.singular && e.shiftKey){
- if(dojo.isCopyKey(e)){
- //TODO add range to selection
- }else{
- //TODO select new range from anchor
- }
- }else{
- if(dojo.isCopyKey(e)){
- if(this.anchor == this.current){
- delete this.selection[this.anchor.id];
- this._removeAnchor();
- }else{
- if(this.current.id in this.selection){
- this._removeItemClass(this.current, "Selected");
- delete this.selection[this.current.id];
- }else{
- if(this.anchor){
- this._removeItemClass(this.anchor, "Anchor");
- this._addItemClass(this.anchor, "Selected");
- }
- this.anchor = this.current;
- this._addItemClass(this.current, "Anchor");
- this.selection[this.current.id] = this.current;
- }
- }
- }else{
- if(!(id in this.selection)){
- this.selectNone();
- this.anchor = this.current;
- this._addItemClass(this.current, "Anchor");
- this.selection[id] = this.current;
- }
- }
- }
- }
-
- dojo.stopEvent(e);
- },
-
- onMouseUp: function(e){
- // summary:
- // Event processor for onmouseup
- // e: Event
- // mouse event
- // tags:
- // protected
-
- // TODO: this code is apparently for handling an edge case when the user is selecting
- // multiple nodes and then mousedowns on a node by accident... it lets the user keep the
- // current selection by moving the mouse away (or something like that). It doesn't seem
- // to work though and requires a lot of plumbing (including this code, the onmousemove
- // handler, and the this.simpleSelection attribute. Consider getting rid of all of it.
-
- if(!this.simpleSelection){ return; }
- this.simpleSelection = false;
- this.selectNone();
- if(this.current){
- this.anchor = this.current;
- this._addItemClass(this.anchor, "Anchor");
- this.selection[this.current.id] = this.current;
- }
- },
- onMouseMove: function(e){
- // summary
- // event processor for onmousemove
- // e: Event
- // mouse event
- this.simpleSelection = false;
- },
-
- _removeSelection: function(){
- // summary:
- // Unselects all items
- // tags:
- // private
- var e = dojo.dnd._empty;
- for(var i in this.selection){
- if(i in e){ continue; }
- var node = dojo.byId(i);
- if(node){ this._removeItemClass(node, "Selected"); }
- }
- this.selection = {};
- return this; // self
- },
-
- _removeAnchor: function(){
- // summary:
- // Removes the Anchor CSS class from a node.
- // According to `dojo.dnd.Selector`, anchor means that
- // "an item is selected, and is an anchor for a 'shift' selection".
- // It's not relevant for Tree at this point, since we don't support multiple selection.
- // tags:
- // private
- if(this.anchor){
- this._removeItemClass(this.anchor, "Anchor");
- this.anchor = null;
- }
- return this; // self
- },
-
- forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){
- // summary:
- // Iterates over selected items;
- // see `dojo.dnd.Container.forInItems()` for details
- o = o || dojo.global;
- for(var id in this.selection){
- console.log("selected item id: " + id);
- f.call(o, this.getItem(id), id, this);
- }
- }
-});
-
}
if(!dojo._hasResource["dojo.dnd.Avatar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
@@ -24319,7 +25280,7 @@ dojo.declare("dojo.dnd.Avatar", null, {
var icon = dojo.byId("a11yIcon");
var text = '+'; // assume canDrop && copy
if (this.manager.canDropFlag && !this.manager.copy) {
- text = '< '; // canDrop && move
+ text = '< '; // canDrop && move
}else if (!this.manager.canDropFlag && !this.manager.copy) {
text = "o"; //!canDrop && move
}else if(!this.manager.canDropFlag){
@@ -24420,7 +25381,7 @@ dojo.declare("dojo.dnd.Manager", null, {
dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent)
];
var c = "dojoDnd" + (copy ? "Copy" : "Move");
- dojo.addClass(dojo.body(), c);
+ dojo.addClass(dojo.body(), c);
},
canDrop: function(flag){
// summary:
@@ -24434,8 +25395,7 @@ dojo.declare("dojo.dnd.Manager", null, {
stopDrag: function(){
// summary:
// stop the DnD in progress
- dojo.removeClass(dojo.body(), "dojoDndCopy");
- dojo.removeClass(dojo.body(), "dojoDndMove");
+ dojo.removeClass(dojo.body(), ["dojoDndCopy", "dojoDndMove"]);
dojo.forEach(this.events, dojo.disconnect);
this.events = [];
this.avatar.destroy();
@@ -24468,7 +25428,7 @@ dojo.declare("dojo.dnd.Manager", null, {
s.left = (e.pageX + this.OFFSET_X) + "px";
s.top = (e.pageY + this.OFFSET_Y) + "px";
var copy = Boolean(this.source.copyState(dojo.isCopyKey(e)));
- if(this.copy != copy){
+ if(this.copy != copy){
this._setCopyStatus(copy);
}
}
@@ -24502,7 +25462,7 @@ dojo.declare("dojo.dnd.Manager", null, {
switch(e.keyCode){
case dojo.keys.CTRL:
var copy = Boolean(this.source.copyState(true));
- if(this.copy != copy){
+ if(this.copy != copy){
this._setCopyStatus(copy);
}
break;
@@ -24520,7 +25480,7 @@ dojo.declare("dojo.dnd.Manager", null, {
// keyboard event
if(this.avatar && e.keyCode == dojo.keys.CTRL){
var copy = Boolean(this.source.copyState(false));
- if(this.copy != copy){
+ if(this.copy != copy){
this._setCopyStatus(copy);
}
}
@@ -24535,8 +25495,9 @@ dojo.declare("dojo.dnd.Manager", null, {
this.copy = copy;
this.source._markDndStatus(this.copy);
this.updateAvatar();
- dojo.removeClass(dojo.body(), "dojoDnd" + (this.copy ? "Move" : "Copy"));
- dojo.addClass(dojo.body(), "dojoDnd" + (this.copy ? "Copy" : "Move"));
+ dojo.replaceClass(dojo.body(),
+ "dojoDnd" + (this.copy ? "Copy" : "Move"),
+ "dojoDnd" + (this.copy ? "Move" : "Copy"));
}
});
@@ -24694,9 +25655,8 @@ dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
// Keeps track of current drop target.
var m = dojo.dnd.manager(),
- oldTarget = this.targetAnchor, // the DOMNode corresponding to TreeNode mouse was previously over
- newTarget = this.current, // DOMNode corresponding to TreeNode mouse is currently over
- newTargetWidget = this.currentWidget, // the TreeNode itself
+ oldTarget = this.targetAnchor, // the TreeNode corresponding to TreeNode mouse was previously over
+ newTarget = this.current, // TreeNode corresponding to TreeNode mouse is currently over
oldDropPosition = this.dropPosition; // the previous drop position (over/before/after)
// calculate if user is indicating to drop the dragged node before, after, or over
@@ -24705,7 +25665,7 @@ dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
if(newTarget && this.betweenThreshold > 0){
// If mouse is over a new TreeNode, then get new TreeNode's position and size
if(!this.targetBox || oldTarget != newTarget){
- this.targetBox = dojo.position(newTarget, true);
+ this.targetBox = dojo.position(newTarget.rowNode, true);
}
if((e.pageY - this.targetBox.y) <= this.betweenThreshold){
newDropPosition = "Before";
@@ -24716,23 +25676,23 @@ dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
if(newTarget != oldTarget || newDropPosition != oldDropPosition){
if(oldTarget){
- this._removeItemClass(oldTarget, oldDropPosition);
+ this._removeItemClass(oldTarget.rowNode, oldDropPosition);
}
if(newTarget){
- this._addItemClass(newTarget, newDropPosition);
+ this._addItemClass(newTarget.rowNode, newDropPosition);
}
// Check if it's ok to drop the dragged node on/before/after the target node.
if(!newTarget){
m.canDrop(false);
- }else if(newTargetWidget == this.tree.rootNode && newDropPosition != "Over"){
+ }else if(newTarget == this.tree.rootNode && newDropPosition != "Over"){
// Can't drop before or after tree's root node; the dropped node would just disappear (at least visually)
m.canDrop(false);
}else if(m.source == this && (newTarget.id in this.selection)){
// Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140)
m.canDrop(false);
- }else if(this.checkItemAcceptance(newTarget, m.source, newDropPosition.toLowerCase())
- && !this._isParentChildDrop(m.source, newTarget)){
+ }else if(this.checkItemAcceptance(newTarget.rowNode, m.source, newDropPosition.toLowerCase())
+ && !this._isParentChildDrop(m.source, newTarget.rowNode)){
m.canDrop(true);
}else{
m.canDrop(false);
@@ -24758,12 +25718,23 @@ dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
}else{
if(this.mouseDown && this.isSource &&
(Math.abs(e.pageX-this._lastX)>=this.dragThreshold || Math.abs(e.pageY-this._lastY)>=this.dragThreshold)){
- var n = this.getSelectedNodes();
- var nodes=[];
- for(var i in n){
- nodes.push(n[i]);
- }
+ var nodes = this.getSelectedTreeNodes();
if(nodes.length){
+ if(nodes.length > 1){
+ //filter out all selected items which has one of their ancestor selected as well
+ var seen = this.selection, i = 0, r = [], n, p;
+ nextitem: while((n = nodes[i++])){
+ for(p = n.getParent(); p && p !== this.tree; p = p.getParent()){
+ if(seen[p.id]){ //parent is already selected, skip this node
+ continue nextitem;
+ }
+ }
+ //this node does not have any ancestors selected, add it
+ r.push(n);
+ }
+ nodes = r;
+ }
+ nodes = dojo.map(nodes, function(n){return n.domNode});
m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e)));
}
}
@@ -24781,7 +25752,7 @@ dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
this.mouseButton = e.button;
this._lastX = e.pageX;
this._lastY = e.pageY;
- this.inherited("onMouseDown",arguments);
+ this.inherited(arguments);
},
onMouseUp: function(e){
@@ -24793,7 +25764,7 @@ dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
// private
if(this.mouseDown){
this.mouseDown = false;
- this.inherited("onMouseUp",arguments);
+ this.inherited(arguments);
}
},
@@ -24919,7 +25890,7 @@ dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
this.isDragging = false;
// Compute the new parent item
- var targetWidget = dijit.getEnclosingWidget(target);
+ var targetWidget = target;
var newParentItem;
var insertIndex;
newParentItem = (targetWidget && targetWidget.item) || tree.item;
@@ -24975,7 +25946,7 @@ dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
// Get the hash to pass to model.newItem(). A single call to
// itemCreator() returns an array of hashes, one for each drag source node.
if(!newItemsParams){
- newItemsParams = this.itemCreator(nodes, target, source);
+ newItemsParams = this.itemCreator(nodes, target.rowNode, source);
}
// Create new item in the tree, based on the drag source.
@@ -25049,16 +26020,13 @@ dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
var root = source.tree.domNode;
- var ids = {};
- for(var x in source.selection){
- ids[source.selection[x].parentNode.id] = true;
- }
+ var ids = source.selection;
var node = targetRow.parentNode;
// Iterate up the DOM hierarchy from the target drop row,
// checking of any of the dragged nodes have the same ID.
- while(node != root && (!node.id || !ids[node.id])){
+ while(node != root && !ids[node.id]){
node = node.parentNode;
}
@@ -25071,7 +26039,7 @@ dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
// tags:
// private
if(!this.targetAnchor){ return; }
- this._removeItemClass(this.targetAnchor, this.dropPosition);
+ this._removeItemClass(this.targetAnchor.rowNode, this.dropPosition);
this.targetAnchor = null;
this.targetBox = null;
this.dropPosition = null;
@@ -25103,7 +26071,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
// { name:'Fozzie Bear', wears:['hat', 'tie']},
// { name:'Miss Piggy', pets:'Foo-Foo'}
// ]}
- // Note that it can also contain an 'identifer' property that specified which attribute on the items
+ // Note that it can also contain an 'identifer' property that specified which attribute on the items
// in the array of items that acts as the unique identifier for that item.
//
constructor: function(/* Object */ keywordParameters){
@@ -25118,7 +26086,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
// ...
// typeN: function || object
// }
- // Where if it is a function, it is assumed to be an object constructor that takes the
+ // Where if it is a function, it is assumed to be an object constructor that takes the
// value of _value as the initialization parameters. If it is an object, then it is assumed
// to be an object of general form:
// {
@@ -25184,7 +26152,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
//all item handles will become invalid and a new fetch must be issued.
clearOnClose: false,
- //Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url.
+ //Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url.
//Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option.
//Added for tracker: #6072
urlPreventCache: false,
@@ -25192,19 +26160,19 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
//Parameter for specifying that it is OK for the xhrGet call to fail silently.
failOk: false,
- //Parameter to indicate to process data from the url as hierarchical
- //(data items can contain other data items in js form). Default is true
- //for backwards compatibility. False means only root items are processed
- //as items, all child objects outside of type-mapped objects and those in
+ //Parameter to indicate to process data from the url as hierarchical
+ //(data items can contain other data items in js form). Default is true
+ //for backwards compatibility. False means only root items are processed
+ //as items, all child objects outside of type-mapped objects and those in
//specific reference format, are left straight JS data objects.
hierarchical: true,
_assertIsItem: function(/* item */ item){
// summary:
// This function tests whether the item passed in is indeed an item in the store.
- // item:
+ // item:
// The item to test for being contained by the store.
- if(!this.isItem(item)){
+ if(!this.isItem(item)){
throw new Error("dojo.data.ItemFileReadStore: Invalid item argument.");
}
},
@@ -25212,25 +26180,25 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
_assertIsAttribute: function(/* attribute-name-string */ attribute){
// summary:
// This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
- // attribute:
+ // attribute:
// The attribute to test for being contained by the store.
- if(typeof attribute !== "string"){
+ if(typeof attribute !== "string"){
throw new Error("dojo.data.ItemFileReadStore: Invalid attribute argument.");
}
},
- getValue: function( /* item */ item,
- /* attribute-name-string */ attribute,
+ getValue: function( /* item */ item,
+ /* attribute-name-string */ attribute,
/* value? */ defaultValue){
- // summary:
+ // summary:
// See dojo.data.api.Read.getValue()
var values = this.getValues(item, attribute);
return (values.length > 0)?values[0]:defaultValue; // mixed
},
- getValues: function(/* item */ item,
+ getValues: function(/* item */ item,
/* attribute-name-string */ attribute){
- // summary:
+ // summary:
// See dojo.data.api.Read.getValues()
this._assertIsItem(item);
@@ -25240,7 +26208,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
},
getAttributes: function(/* item */ item){
- // summary:
+ // summary:
// See dojo.data.api.Read.getAttributes()
this._assertIsItem(item);
var attributes = [];
@@ -25255,17 +26223,17 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
hasAttribute: function( /* item */ item,
/* attribute-name-string */ attribute){
- // summary:
+ // summary:
// See dojo.data.api.Read.hasAttribute()
this._assertIsItem(item);
this._assertIsAttribute(attribute);
return (attribute in item);
},
- containsValue: function(/* item */ item,
- /* attribute-name-string */ attribute,
+ containsValue: function(/* item */ item,
+ /* attribute-name-string */ attribute,
/* anything */ value){
- // summary:
+ // summary:
// See dojo.data.api.Read.containsValue()
var regexp = undefined;
if(typeof value === "string"){
@@ -25274,22 +26242,22 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
return this._containsValue(item, attribute, value, regexp); //boolean.
},
- _containsValue: function( /* item */ item,
- /* attribute-name-string */ attribute,
+ _containsValue: function( /* item */ item,
+ /* attribute-name-string */ attribute,
/* anything */ value,
/* RegExp?*/ regexp){
- // summary:
+ // summary:
// Internal function for looking at the values contained by the item.
- // description:
- // Internal function for looking at the values contained by the item. This
+ // description:
+ // Internal function for looking at the values contained by the item. This
// function allows for denoting if the comparison should be case sensitive for
// strings or not (for handling filtering cases where string case should not matter)
- //
+ //
// item:
// The data item to examine for attribute values.
// attribute:
// The attribute to inspect.
- // value:
+ // value:
// The value to match.
// regexp:
// Optional regular expression generated off value if value was of string type to handle wildcarding.
@@ -25306,7 +26274,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
},
isItem: function(/* anything */ something){
- // summary:
+ // summary:
// See dojo.data.api.Read.isItem()
if(something && something[this._storeRefPropName] === this){
if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){
@@ -25317,25 +26285,25 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
},
isItemLoaded: function(/* anything */ something){
- // summary:
+ // summary:
// See dojo.data.api.Read.isItemLoaded()
return this.isItem(something); //boolean
},
loadItem: function(/* object */ keywordArgs){
- // summary:
+ // summary:
// See dojo.data.api.Read.loadItem()
this._assertIsItem(keywordArgs.item);
},
getFeatures: function(){
- // summary:
+ // summary:
// See dojo.data.api.Read.getFeatures()
return this._features; //Object
},
getLabel: function(/* item */ item){
- // summary:
+ // summary:
// See dojo.data.api.Read.getLabel()
if(this._labelAttr && this.isItem(item)){
return this.getValue(item,this._labelAttr); //String
@@ -25344,7 +26312,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
},
getLabelAttributes: function(/* item */ item){
- // summary:
+ // summary:
// See dojo.data.api.Read.getLabelAttributes()
if(this._labelAttr){
return [this._labelAttr]; //array
@@ -25352,10 +26320,10 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
return null; //null
},
- _fetchItems: function( /* Object */ keywordArgs,
- /* Function */ findCallback,
+ _fetchItems: function( /* Object */ keywordArgs,
+ /* Function */ findCallback,
/* Function */ errorCallback){
- // summary:
+ // summary:
// See dojo.data.util.simpleFetch.fetch()
var self = this,
filter = function(requestArgs, arrayOfItems){
@@ -25395,8 +26363,8 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
}
findCallback(items, requestArgs);
}else{
- // We want a copy to pass back in case the parent wishes to sort the array.
- // We shouldn't allow resort of the internal list, so that multiple callers
+ // We want a copy to pass back in case the parent wishes to sort the array.
+ // We shouldn't allow resort of the internal list, so that multiple callers
// can get lists and sort without affecting each other. We also need to
// filter out any null values that have been left as a result of deleteItem()
// calls in ItemFileWriteStore.
@@ -25416,11 +26384,11 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
//Do a check on the JsonFileUrl and crosscheck it.
//If it doesn't match the cross-check, it needs to be updated
//This allows for either url or _jsonFileUrl to he changed to
- //reset the store load location. Done this way for backwards
+ //reset the store load location. Done this way for backwards
//compatibility. People use _jsonFileUrl (even though officially
//private.
if(this._jsonFileUrl !== this._ccUrl){
- dojo.deprecated("dojo.data.ItemFileReadStore: ",
+ dojo.deprecated("dojo.data.ItemFileReadStore: ",
"To change the url, set the url property of the store," +
" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
this._ccUrl = this._jsonFileUrl;
@@ -25431,21 +26399,21 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
}
//See if there was any forced reset of data.
- if(this.data != null && this._jsonData == null){
+ if(this.data != null){
this._jsonData = this.data;
this.data = null;
}
if(this._jsonFileUrl){
//If fetches come in before the loading has finished, but while
- //a load is in progress, we have to defer the fetching to be
+ //a load is in progress, we have to defer the fetching to be
//invoked in the callback.
if(this._loadInProgress){
this._queuedFetches.push({args: keywordArgs, filter: filter});
}else{
this._loadInProgress = true;
var getArgs = {
- url: self._jsonFileUrl,
+ url: self._jsonFileUrl,
handleAs: "json-comment-optional",
preventCache: this.urlPreventCache,
failOk: this.failOk
@@ -25505,7 +26473,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
},
_handleQueuedFetches: function(){
- // summary:
+ // summary:
// Internal function to execute delayed request in the store.
//Execute any deferred fetches now.
if(this._queuedFetches.length > 0){
@@ -25514,7 +26482,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
delayedQuery = fData.args,
delayedFilter = fData.filter;
if(delayedFilter){
- delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions));
+ delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions));
}else{
this.fetchItemByIdentity(delayedQuery);
}
@@ -25524,31 +26492,31 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
},
_getItemsArray: function(/*object?*/queryOptions){
- // summary:
+ // summary:
// Internal function to determine which list of items to search over.
// queryOptions: The query options parameter, if any.
if(queryOptions && queryOptions.deep){
- return this._arrayOfAllItems;
+ return this._arrayOfAllItems;
}
return this._arrayOfTopLevelItems;
},
close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
- // summary:
+ // summary:
// See dojo.data.api.Read.close()
- if(this.clearOnClose &&
- this._loadFinished &&
+ if(this.clearOnClose &&
+ this._loadFinished &&
!this._loadInProgress){
//Reset all internalsback to default state. This will force a reload
- //on next fetch. This also checks that the data or url param was set
+ //on next fetch. This also checks that the data or url param was set
//so that the store knows it can get data. Without one of those being set,
//the next fetch will trigger an error.
- if(((this._jsonFileUrl == "" || this._jsonFileUrl == null) &&
+ if(((this._jsonFileUrl == "" || this._jsonFileUrl == null) &&
(this.url == "" || this.url == null)
) && this.data == null){
console.debug("dojo.data.ItemFileReadStore: WARNING! Data reload " +
- " information has not been provided." +
+ " information has not been provided." +
" Please set 'url' or 'data' to the appropriate value before" +
" the next fetch");
}
@@ -25586,7 +26554,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
// | false == valueIsAnItem("Kermit");
// | false == valueIsAnItem(42);
// | false == valueIsAnItem(new Date());
- // | false == valueIsAnItem({_type:'Date', _value:'May 14, 1802'});
+ // | false == valueIsAnItem({_type:'Date', _value:'1802-05-14'});
// | false == valueIsAnItem({_reference:'Kermit'});
// | true == valueIsAnItem({name:'Kermit', color:'green'});
// | true == valueIsAnItem({iggy:'pop'});
@@ -25597,8 +26565,8 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
(!dojo.isArray(aValue) || addingArrays) &&
(!dojo.isFunction(aValue)) &&
(aValue.constructor == Object || dojo.isArray(aValue)) &&
- (typeof aValue._reference === "undefined") &&
- (typeof aValue._type === "undefined") &&
+ (typeof aValue._reference === "undefined") &&
+ (typeof aValue._type === "undefined") &&
(typeof aValue._value === "undefined") &&
self.hierarchical
);
@@ -25648,13 +26616,13 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
item[this._rootItemPropName]=true;
}
- // Step 2: Walk through all the attribute values of all the items,
+ // Step 2: Walk through all the attribute values of all the items,
// and replace single values with arrays. For example, we change this:
// { name:'Miss Piggy', pets:'Foo-Foo'}
// into this:
// { name:['Miss Piggy'], pets:['Foo-Foo']}
- //
- // We also store the attribute names so we can validate our store
+ //
+ // We also store the attribute names so we can validate our store
// reference and item id special properties for the O(1) isItem
var allAttributeNames = {},
key;
@@ -25688,9 +26656,9 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
this._reverseRefMap += "_";
}
- // Step 4: Some data files specify an optional 'identifier', which is
- // the name of an attribute that holds the identity of each item.
- // If this data file specified an identifier attribute, then build a
+ // Step 4: Some data files specify an optional 'identifier', which is
+ // the name of an attribute that holds the identity of each item.
+ // If this data file specified an identifier attribute, then build a
// hash table of items keyed by the identity of the items.
var arrayOfValues;
@@ -25702,7 +26670,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
item = this._arrayOfAllItems[i];
arrayOfValues = item[identifier];
var identity = arrayOfValues[0];
- if(!this._itemsByIdentity[identity]){
+ if(!Object.hasOwnProperty.call(this._itemsByIdentity, identity)){
this._itemsByIdentity[identity] = item;
}else{
if(this._jsonFileUrl){
@@ -25716,7 +26684,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
this._features['dojo.data.api.Identity'] = Number;
}
- // Step 5: Walk through all the items, and set each item's properties
+ // Step 5: Walk through all the items, and set each item's properties
// for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true.
for(i = 0; i < this._arrayOfAllItems.length; ++i){
item = this._arrayOfAllItems[i];
@@ -25730,13 +26698,13 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
// We replace item-references with pointers to items. For example, we change:
// { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
// into this:
- // { name:['Kermit'], friends:[miss_piggy] }
+ // { name:['Kermit'], friends:[miss_piggy] }
// (where miss_piggy is the object representing the 'Miss Piggy' item).
//
// We replace type/value pairs with typed-literals. For example, we change:
- // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'July 18, 1918'}] }
+ // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'1918-07-18'}] }
// into this:
- // { name:['Kermit'], born:(new Date('July 18, 1918')) }
+ // { name:['Kermit'], born:(new Date(1918, 6, 18)) }
//
// We also generate the associate map for all items for the O(1) isItem function.
for(i = 0; i < this._arrayOfAllItems.length; ++i){
@@ -25749,7 +26717,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
if(("_type" in value) && ("_value" in value)){
var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber'
var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}}
- if(!mappingObj){
+ if(!mappingObj){
throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'");
}else if(dojo.isFunction(mappingObj)){
arrayOfValues[j] = new mappingObj(value._value);
@@ -25772,12 +26740,12 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
var candidateItem = this._arrayOfAllItems[k],
found = true;
for(var refKey in referenceDescription){
- if(candidateItem[refKey] != referenceDescription[refKey]){
- found = false;
+ if(candidateItem[refKey] != referenceDescription[refKey]){
+ found = false;
}
}
- if(found){
- arrayOfValues[j] = candidateItem;
+ if(found){
+ arrayOfValues[j] = candidateItem;
}
}
}
@@ -25788,7 +26756,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
}
}
}else if(this.isItem(value)){
- //It's a child item (not one referenced through _reference).
+ //It's a child item (not one referenced through _reference).
//We need to treat this as a referenced item, so it can be cleaned up
//in a write store easily.
if(this.referenceIntegrity){
@@ -25817,7 +26785,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
},
getIdentity: function(/* item */ item){
- // summary:
+ // summary:
// See dojo.data.api.Identity.getIdentity()
var identifier = this._features['dojo.data.api.Identity'];
if(identifier === Number){
@@ -25832,7 +26800,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
},
fetchItemByIdentity: function(/* Object */ keywordArgs){
- // summary:
+ // summary:
// See dojo.data.api.Identity.fetchItemByIdentity()
// Hasn't loaded yet, we have to trigger the load.
@@ -25843,11 +26811,11 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
//Do a check on the JsonFileUrl and crosscheck it.
//If it doesn't match the cross-check, it needs to be updated
//This allows for either url or _jsonFileUrl to he changed to
- //reset the store load location. Done this way for backwards
+ //reset the store load location. Done this way for backwards
//compatibility. People use _jsonFileUrl (even though officially
//private.
if(this._jsonFileUrl !== this._ccUrl){
- dojo.deprecated("dojo.data.ItemFileReadStore: ",
+ dojo.deprecated("dojo.data.ItemFileReadStore: ",
"To change the url, set the url property of the store," +
" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
this._ccUrl = this._jsonFileUrl;
@@ -25870,7 +26838,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
}else{
this._loadInProgress = true;
var getArgs = {
- url: self._jsonFileUrl,
+ url: self._jsonFileUrl,
handleAs: "json-comment-optional",
preventCache: this.urlPreventCache,
failOk: this.failOk
@@ -25913,7 +26881,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
keywordArgs.onItem.call(scope, item);
}
- }
+ }
}else{
// Already loaded. We can just look it up and call back.
item = this._getItemByIdentity(keywordArgs.identity);
@@ -25928,9 +26896,10 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
// summary:
// Internal function to look an item up by its identity map.
var item = null;
- if(this._itemsByIdentity){
+ if(this._itemsByIdentity &&
+ Object.hasOwnProperty.call(this._itemsByIdentity, identity)){
item = this._itemsByIdentity[identity];
- }else{
+ }else if (Object.hasOwnProperty.call(this._arrayOfAllItems, identity)){
item = this._arrayOfAllItems[identity];
}
if(item === undefined){
@@ -25940,15 +26909,15 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
},
getIdentityAttributes: function(/* item */ item){
- // summary:
- // See dojo.data.api.Identity.getIdentifierAttributes()
+ // summary:
+ // See dojo.data.api.Identity.getIdentityAttributes()
var identifier = this._features['dojo.data.api.Identity'];
if(identifier === Number){
// If (identifier === Number) it means getIdentity() just returns
// an integer item-number for each item. The dojo.data.api.Identity
- // spec says we need to return null if the identity is not composed
- // of attributes
+ // spec says we need to return null if the identity is not composed
+ // of attributes
return null; // null
}else{
return [identifier]; // Array
@@ -25956,18 +26925,18 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
},
_forceLoad: function(){
- // summary:
+ // summary:
// Internal function to force a load of the store if it hasn't occurred yet. This is required
- // for specific functions to work properly.
+ // for specific functions to work properly.
var self = this;
//Do a check on the JsonFileUrl and crosscheck it.
//If it doesn't match the cross-check, it needs to be updated
//This allows for either url or _jsonFileUrl to he changed to
- //reset the store load location. Done this way for backwards
+ //reset the store load location. Done this way for backwards
//compatibility. People use _jsonFileUrl (even though officially
//private.
if(this._jsonFileUrl !== this._ccUrl){
- dojo.deprecated("dojo.data.ItemFileReadStore: ",
+ dojo.deprecated("dojo.data.ItemFileReadStore: ",
"To change the url, set the url property of the store," +
" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
this._ccUrl = this._jsonFileUrl;
@@ -25978,14 +26947,14 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
}
//See if there was any forced reset of data.
- if(this.data != null && this._jsonData == null){
+ if(this.data != null){
this._jsonData = this.data;
this.data = null;
}
if(this._jsonFileUrl){
var getArgs = {
- url: this._jsonFileUrl,
+ url: this._jsonFileUrl,
handleAs: "json-comment-optional",
preventCache: this.urlPreventCache,
failOk: this.failOk,
@@ -25994,7 +26963,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
var getHandler = dojo.xhrGet(getArgs);
getHandler.addCallback(function(data){
try{
- //Check to be sure there wasn't another load going on concurrently
+ //Check to be sure there wasn't another load going on concurrently
//So we don't clobber data that comes in on it. If there is a load going on
//then do not save this data. It will potentially clobber current data.
//We mainly wanted to sync/wait here.
@@ -26007,7 +26976,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
//Okay, we hit an error state we can't recover from. A forced load occurred
//while an async load was occurring. Since we cannot block at this point, the best
//that can be managed is to throw an error.
- throw new Error("dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress.");
+ throw new Error("dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress.");
}
}catch(e){
console.log(e);
@@ -26021,7 +26990,7 @@ dojo.declare("dojo.data.ItemFileReadStore", null,{
self._getItemsFromLoadedData(self._jsonData);
self._jsonData = null;
self._loadFinished = true;
- }
+ }
}
});
//Mix in the simple fetch implementation to this class.
@@ -26034,6 +27003,7 @@ dojo._hasResource["dojo.data.ItemFileWriteStore"] = true;
dojo.provide("dojo.data.ItemFileWriteStore");
+
dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
constructor: function(/* object */ keywordParameters){
// keywordParameters: {typeMap: object)
@@ -26044,7 +27014,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
// ...
// typeN: function || object
// }
- // Where if it is a function, it is assumed to be an object constructor that takes the
+ // Where if it is a function, it is assumed to be an object constructor that takes the
// value of _value as the initialization parameters. It is serialized assuming object.toString()
// serialization. If it is an object, then it is assumed
// to be an object of general form:
@@ -26060,8 +27030,8 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
// For keeping track of changes so that we can implement isDirty and revert
this._pending = {
- _newItems:{},
- _modifiedItems:{},
+ _newItems:{},
+ _modifiedItems:{},
_deletedItems:{}
};
@@ -26124,8 +27094,8 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
}
}
- // make sure this identity is not already in use by another item, if identifiers were
- // defined in the file. Otherwise it would be the item count,
+ // make sure this identity is not already in use by another item, if identifiers were
+ // defined in the file. Otherwise it would be the item count,
// which should always be unique in this case.
if(this._itemsByIdentity){
this._assert(typeof this._itemsByIdentity[newIdentity] === "undefined");
@@ -26134,7 +27104,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
this._assert(typeof this._pending._deletedItems[newIdentity] === "undefined");
var newItem = {};
- newItem[this._storeRefPropName] = this;
+ newItem[this._storeRefPropName] = this;
newItem[this._itemNumPropName] = this._arrayOfAllItems.length;
if(this._itemsByIdentity){
this._itemsByIdentity[newIdentity] = newItem;
@@ -26187,14 +27157,14 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
// Bummer, the user is trying to do something like
// newItem({_S:"foo"}). Unfortunately, our superclass,
// ItemFileReadStore, is already using _S in each of our items
- // to hold private info. To avoid a naming collision, we
- // need to move all our private info to some other property
+ // to hold private info. To avoid a naming collision, we
+ // need to move all our private info to some other property
// of all the items/objects. So, we need to iterate over all
- // the items and do something like:
+ // the items and do something like:
// item.__S = item._S;
// item._S = undefined;
- // But first we have to make sure the new "__S" variable is
- // not in use, which means we have to iterate over all the
+ // But first we have to make sure the new "__S" variable is
+ // not in use, which means we have to iterate over all the
// items checking for that.
throw new Error("encountered bug in ItemFileWriteStore.newItem");
}
@@ -26231,17 +27201,17 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
this._assertIsItem(item);
// Remove this item from the _arrayOfAllItems, but leave a null value in place
- // of the item, so as not to change the length of the array, so that in newItem()
+ // of the item, so as not to change the length of the array, so that in newItem()
// we can still safely do: newIdentity = this._arrayOfAllItems.length;
var indexInArrayOfAllItems = item[this._itemNumPropName];
var identity = this.getIdentity(item);
//If we have reference integrity on, we need to do reference cleanup for the deleted item
if(this.referenceIntegrity){
- //First scan all the attributes of this items for references and clean them up in the map
+ //First scan all the attributes of this items for references and clean them up in the map
//As this item is going away, no need to track its references anymore.
- //Get the attributes list before we generate the backup so it
+ //Get the attributes list before we generate the backup so it
//doesn't pollute the attributes list.
var attributes = this.getAttributes(item);
@@ -26287,7 +27257,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
return !(this.isItem(possibleItem) && this.getIdentity(possibleItem) == identity);
}, this);
//Remove the note of the reference to the item and set the values on the modified attribute.
- this._removeReferenceFromMap(item, containingItem, attribute);
+ this._removeReferenceFromMap(item, containingItem, attribute);
if(newValues.length < oldValues.length){
this._setValueOrValues(containingItem, attribute, newValues, true);
}
@@ -26349,11 +27319,11 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
var identity = this.getIdentity(item);
if(!this._pending._modifiedItems[identity]){
- // Before we actually change the item, we make a copy of it to
- // record the original state, so that we'll be able to revert if
+ // Before we actually change the item, we make a copy of it to
+ // record the original state, so that we'll be able to revert if
// the revert method gets called. If the item has already been
// modified then there's no need to do this now, since we already
- // have a record of the original state.
+ // have a record of the original state.
var copyOfItemState = {};
for(var key in item){
if((key === this._storeRefPropName) || (key === this._itemNumPropName) || (key === this._rootItemPropName)){
@@ -26374,7 +27344,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
if(dojo.isArray(newValueOrValues) && newValueOrValues.length === 0){
// If we were passed an empty array as the value, that counts
- // as "unsetting" the attribute, so we need to remove this
+ // as "unsetting" the attribute, so we need to remove this
// attribute from the item.
success = delete item[attribute];
newValueOrValues = undefined; // used in the onSet Notification call below
@@ -26398,7 +27368,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
// Unfortunately, it's not safe to just do this:
// newValueArray = newValues;
// Instead, we need to copy the array, which slice() does very nicely.
- // This is so that our internal data structure won't
+ // This is so that our internal data structure won't
// get corrupted if the user mucks with the values array *after*
// calling setValues().
newValueArray = newValueOrValues.slice(0, newValueOrValues.length);
@@ -26406,7 +27376,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
newValueArray = [newValueOrValues];
}
- //We need to handle reference integrity if this is on.
+ //We need to handle reference integrity if this is on.
//In the case of set, we need to see if references were added or removed
//and update the reference tracking map accordingly.
if(this.referenceIntegrity){
@@ -26433,7 +27403,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
if(map[id.toString()]){
delete map[id.toString()];
}else{
- this._addReferenceToMap(possibleItem, item, attribute);
+ this._addReferenceToMap(possibleItem, item, attribute);
}
}
}, this);
@@ -26463,7 +27433,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
// Now we make the dojo.data.api.Notification call
if(callOnSet){
- this.onSet(item, attribute, oldValueOrValues, newValueOrValues);
+ this.onSet(item, attribute, oldValueOrValues, newValueOrValues);
}
return success; // boolean
},
@@ -26498,7 +27468,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
// Method to remove an reference map entry for an item and attribute.
// description:
// Method to remove an reference map entry for an item and attribute. This will
- // also perform cleanup on the map such that if there are no more references at all to
+ // also perform cleanup on the map such that if there are no more references at all to
// the item, its reference object and entry are removed.
//
// refItem:
@@ -26555,7 +27525,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
_flatten: function(/* anything */ value){
if(this.isItem(value)){
var item = value;
- // Given an item, return an serializable object that provides a
+ // Given an item, return an serializable object that provides a
// reference to the item.
// For example, given kermit:
// var kermit = store.newItem({id:2, name:"Kermit"});
@@ -26586,7 +27556,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
},
_getNewFileContentString: function(){
- // summary:
+ // summary:
// Generate a string that can be saved to a file.
// The result should look similar to:
// http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json
@@ -26627,7 +27597,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
},
_isEmpty: function(something){
- // summary:
+ // summary:
// Function to determine if an array or object has no properties or values.
// something:
// The array or object to examine.
@@ -26656,7 +27626,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
var self = this;
var saveCompleteCallback = function(){
self._pending = {
- _newItems:{},
+ _newItems:{},
_modifiedItems:{},
_deletedItems:{}
};
@@ -26705,7 +27675,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
modifiedItem = this._arrayOfAllItems[identity];
}
- // Restore the original item into a full-fledged item again, we want to try to
+ // Restore the original item into a full-fledged item again, we want to try to
// keep the same object instance as if we don't it, causes bugs like #9022.
copyOfItemState[this._storeRefPropName] = this;
for(key in modifiedItem){
@@ -26746,7 +27716,7 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
}
this._addReferenceToMap(refItem, deletedItem, reference.attr);
}, this);
- delete deletedItem["backupRefs_" + this._reverseRefMap];
+ delete deletedItem["backupRefs_" + this._reverseRefMap];
}
}
@@ -26765,8 +27735,8 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
}
this._pending = {
- _newItems:{},
- _modifiedItems:{},
+ _newItems:{},
+ _modifiedItems:{},
_deletedItems:{}
};
return true; // boolean
@@ -26777,13 +27747,13 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
if(item){
// return true if the item is dirty
var identity = this.getIdentity(item);
- return new Boolean(this._pending._newItems[identity] ||
+ return new Boolean(this._pending._newItems[identity] ||
this._pending._modifiedItems[identity] ||
this._pending._deletedItems[identity]).valueOf(); // boolean
}else{
// return true if the store is dirty -- which means return true
// if there are any new items, dirty items, or modified items
- if(!this._isEmpty(this._pending._newItems) ||
+ if(!this._isEmpty(this._pending._newItems) ||
!this._isEmpty(this._pending._modifiedItems) ||
!this._isEmpty(this._pending._deletedItems)){
return true;
@@ -26794,28 +27764,28 @@ dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
/* dojo.data.api.Notification */
- onSet: function(/* item */ item,
- /*attribute-name-string*/ attribute,
+ onSet: function(/* item */ item,
+ /*attribute-name-string*/ attribute,
/*object | array*/ oldValue,
/*object | array*/ newValue){
// summary: See dojo.data.api.Notification.onSet()
- // No need to do anything. This method is here just so that the
+ // No need to do anything. This method is here just so that the
// client code can connect observers to it.
},
onNew: function(/* item */ newItem, /*object?*/ parentInfo){
// summary: See dojo.data.api.Notification.onNew()
- // No need to do anything. This method is here just so that the
- // client code can connect observers to it.
+ // No need to do anything. This method is here just so that the
+ // client code can connect observers to it.
},
onDelete: function(/* item */ deletedItem){
// summary: See dojo.data.api.Notification.onDelete()
- // No need to do anything. This method is here just so that the
- // client code can connect observers to it.
+ // No need to do anything. This method is here just so that the
+ // client code can connect observers to it.
},
close: function(/* object? */ request){