diff options
author | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
commit | 81bea17aefb26859f825b9293c7c99192874806e (patch) | |
tree | fb244408ca271affa2899adb634788802c9a89d8 /lib/dojo/tt-rss-layer.js.uncompressed.js | |
parent | 870a70e109ac9e80a88047044530de53d0404ec7 (diff) |
upgrade Dojo to 1.6.1
Diffstat (limited to 'lib/dojo/tt-rss-layer.js.uncompressed.js')
-rw-r--r-- | lib/dojo/tt-rss-layer.js.uncompressed.js | 8906 |
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\">●</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\">●</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\">▼</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\">▼</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\">▼</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\">▼</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=\"Χ \" 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=\"Χ \" 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, """) + "'" : "") + "/>", 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=\"▼ \" 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=\"Χ \" 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=\"▼ \" 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=\"Χ \" 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\">✓</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\"> </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\">✓</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\"> </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\">▼</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\">▼</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: " ", // _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 || " ") + - '</span>'; - dijit.setWaiState(this.focusNode, "valuetext", (newDisplay || this.emptyLabel || " ") ); + 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, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """).replace(/\n/g, "<br>"); + val = dojo.trim(val); + var renderVal = this.renderAsHtml ? val : val.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """).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>▼</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>◀</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>▶</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\">▼</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\">◀</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\">▶</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\"> </span\n\t></div\n\t><div dojoAttachPoint=\"label\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"> </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\"> </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 ? " " : 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){ |