2 Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
3 Available via Academic Free License >= 2.1 OR the modified BSD license.
4 see: http://dojotoolkit.org/license for details
8 This is an optimized version of Dojo, built for deployment and not for
9 development. To get sources and documentation, please visit:
11 http://dojotoolkit.org
14 dojo
.provide("tt-rss-layer");
15 if(!dojo
._hasResource
["dojo.date.stamp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16 dojo
._hasResource
["dojo.date.stamp"] = true;
17 dojo
.provide("dojo.date.stamp");
19 dojo
.getObject("date.stamp", true, dojo
);
21 // Methods to convert dates to or from a wire (string) format using well-known conventions
23 dojo
.date
.stamp
.fromISOString = function(/*String*/formattedString
, /*Number?*/defaultTime
){
25 // Returns a Date object given a string formatted according to a subset of the ISO-8601 standard.
28 // Accepts a string formatted according to a profile of ISO8601 as defined by
29 // [RFC3339](http://www.ietf.org/rfc/rfc3339.txt), except that partial input is allowed.
30 // Can also process dates as specified [by the W3C](http://www.w3.org/TR/NOTE-datetime)
31 // The following combinations are valid:
37 // * times only, with an optional time zone appended
41 // * and "datetimes" which could be any combination of the above
43 // timezones may be specified as Z (for UTC) or +/- followed by a time expression HH:mm
44 // Assumes the local time zone if not specified. Does not validate. Improperly formatted
45 // input may return null. Arguments which are out of bounds will be handled
46 // by the Date constructor (e.g. January 32nd typically gets resolved to February 1st)
47 // Only years between 100 and 9999 are supported.
50 // A string such as 2005-06-30T08:05:00-07:00 or 2005-06-30 or T08:05:00
53 // Used for defaults for fields omitted in the formattedString.
54 // Uses 1970-01-01T00:00:00.0Z by default.
56 if(!dojo
.date
.stamp
._isoRegExp
){
57 dojo
.date
.stamp
._isoRegExp
=
58 //TODO: could be more restrictive and check for 00-59, etc.
59 /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/;
62 var match
= dojo
.date
.stamp
._isoRegExp
.exec(formattedString
),
67 if(match
[1]){match
[1]--;} // Javascript Date months are 0-based
68 if(match
[6]){match
[6] *= 1000;} // Javascript Date expects fractional seconds as milliseconds
71 // mix in defaultTime. Relatively expensive, so use || operators for the fast path of defaultTime === 0
72 defaultTime
= new Date(defaultTime
);
73 dojo
.forEach(dojo
.map(["FullYear", "Month", "Date", "Hours", "Minutes", "Seconds", "Milliseconds"], function(prop
){
74 return defaultTime
["get" + prop
]();
75 }), function(value
, index
){
76 match
[index
] = match
[index
] || value
;
79 result
= new Date(match
[0]||1970, match
[1]||0, match
[2]||1, match
[3]||0, match
[4]||0, match
[5]||0, match
[6]||0); //TODO: UTC defaults
81 result
.setFullYear(match
[0] || 1970);
85 zoneSign
= match
[7] && match
[7].charAt(0);
87 offset
= ((match
[8] || 0) * 60) + (Number(match
[9]) || 0);
88 if(zoneSign
!= '-'){ offset
*= -1; }
91 offset
-= result
.getTimezoneOffset();
94 result
.setTime(result
.getTime() + offset
* 60000);
98 return result
; // Date or null
102 dojo.date.stamp.__Options = function(){
104 // "date" or "time" for partial formatting of the Date object.
105 // Both date and time will be formatted by default.
107 // if true, UTC/GMT is used for a timezone
108 // milliseconds: Boolean
109 // if true, output milliseconds
110 this.selector = selector;
112 this.milliseconds = milliseconds;
116 dojo
.date
.stamp
.toISOString = function(/*Date*/dateObject
, /*dojo.date.stamp.__Options?*/options
){
118 // Format a Date object as a string according a subset of the ISO-8601 standard
121 // When options.selector is omitted, output follows [RFC3339](http://www.ietf.org/rfc/rfc3339.txt)
122 // The local time zone is included as an offset from GMT, except when selector=='time' (time without a date)
123 // Does not check bounds. Only years between 100 and 9999 are supported.
128 var _ = function(n
){ return (n
< 10) ? "0" + n
: n
; };
129 options
= options
|| {};
130 var formattedDate
= [],
131 getter
= options
.zulu
? "getUTC" : "get",
133 if(options
.selector
!= "time"){
134 var year
= dateObject
[getter
+"FullYear"]();
135 date
= ["0000".substr((year
+"").length
)+year
, _(dateObject
[getter
+"Month"]()+1), _(dateObject
[getter
+"Date"]())].join('-');
137 formattedDate
.push(date
);
138 if(options
.selector
!= "date"){
139 var time
= [_(dateObject
[getter
+"Hours"]()), _(dateObject
[getter
+"Minutes"]()), _(dateObject
[getter
+"Seconds"]())].join(':');
140 var millis
= dateObject
[getter
+"Milliseconds"]();
141 if(options
.milliseconds
){
142 time
+= "."+ (millis
< 100 ? "0" : "") + _(millis
);
146 }else if(options
.selector
!= "time"){
147 var timezoneOffset
= dateObject
.getTimezoneOffset();
148 var absOffset
= Math
.abs(timezoneOffset
);
149 time
+= (timezoneOffset
> 0 ? "-" : "+") +
150 _(Math
.floor(absOffset
/60)) + ":" + _(absOffset
%60);
152 formattedDate
.push(time
);
154 return formattedDate
.join('T'); // String
159 if(!dojo
._hasResource
["dojo.parser"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
160 dojo
._hasResource
["dojo.parser"] = true;
161 dojo
.provide("dojo.parser");
165 new Date("X"); // workaround for #11279, new Date("") == NaN
167 dojo
.parser
= new function(){
169 // The Dom/Widget parsing package
173 function val2type(/*Object*/ value
){
175 // Returns name of type of given value.
177 if(d
.isString(value
)){ return "string"; }
178 if(typeof value
== "number"){ return "number"; }
179 if(typeof value
== "boolean"){ return "boolean"; }
180 if(d
.isFunction(value
)){ return "function"; }
181 if(d
.isArray(value
)){ return "array"; } // typeof [] == "object"
182 if(value
instanceof Date
) { return "date"; } // assume timestamp
183 if(value
instanceof d
._Url
){ return "url"; }
187 function str2obj(/*String*/ value
, /*String*/ type
){
189 // Convert given string value to given type
194 return value
.length
? Number(value
) : NaN
;
196 // for checked/disabled value might be "" or "checked". interpret as true.
197 return typeof value
== "boolean" ? value
: !(value
.toLowerCase()=="false");
199 if(d
.isFunction(value
)){
200 // IE gives us a function, even when we say something like onClick="foo"
201 // (in which case it gives us an invalid function "function(){ foo }").
202 // Therefore, convert to string
203 value
=value
.toString();
204 value
=d
.trim(value
.substring(value
.indexOf('{')+1, value
.length
-1));
207 if(value
=== "" || value
.search(/[^\w\.]+/i) != -1){
208 // The user has specified some text for a function like "return x+5"
209 return new Function(value
);
211 // The user has specified the name of a function like "myOnClick"
212 // or a single word function "return"
213 return d
.getObject(value
, false) || new Function(value
);
215 }catch(e
){ return new Function(); }
217 return value
? value
.split(/\s*,\s*/) : [];
220 case "": return new Date(""); // the NaN of dates
221 case "now": return new Date(); // current date
222 default: return d
.date
.stamp
.fromISOString(value
);
225 return d
.baseUrl
+ value
;
227 return d
.fromJson(value
);
231 var dummyClass
= {}, instanceClasses
= {
232 // map from fully qualified name (like "dijit.Button") to structure like
233 // { cls: dijit.Button, params: {label: "string", disabled: "boolean"} }
236 // Widgets like BorderContainer add properties to _Widget via dojo.extend().
237 // If BorderContainer is loaded after _Widget's parameter list has been cached,
238 // we need to refresh that parameter list (for _Widget and all widgets that extend _Widget).
239 // TODO: remove this in 2.0, when we stop caching parameters.
240 d
.connect(d
, "extend", function(){
241 instanceClasses
= {};
244 function getProtoInfo(cls
, params
){
246 // The prototype of the class to check props on
248 // The parameters object to mix found parameters onto.
249 for(var name
in cls
){
250 if(name
.charAt(0)=="_"){ continue; } // skip internal properties
251 if(name
in dummyClass
){ continue; } // skip "constructor" and "toString"
252 params
[name
] = val2type(cls
[name
]);
257 function getClassInfo(/*String*/ className
, /*Boolean*/ skipParamsLookup
){
259 // Maps a widget name string like "dijit.form.Button" to the widget constructor itself,
260 // and a list of that widget's parameters and their types
262 // fully qualified name (like "dijit.form.Button")
266 // cls: dijit.Button,
267 // params: { label: "string", disabled: "boolean"}
270 var c
= instanceClasses
[className
];
272 // get pointer to widget class
273 var cls
= d
.getObject(className
), params
= null;
274 if(!cls
){ return null; } // class not defined [yet]
275 if(!skipParamsLookup
){ // from fastpath, we don't need to lookup the attrs on the proto because they are explicit
276 params
= getProtoInfo(cls
.prototype, {})
278 c
= { cls
: cls
, params
: params
};
280 }else if(!skipParamsLookup
&& !c
.params
){
281 // if we're calling getClassInfo and have a cls proto, but no params info, scan that cls for params now
282 // and update the pointer in instanceClasses[className]. This happens when a widget appears in another
283 // widget's template which still uses dojoType, but an instance of the widget appears prior with a data-dojo-type,
284 // skipping this lookup the first time.
285 c
.params
= getProtoInfo(c
.cls
.prototype, {});
291 this._functionFromScript = function(script
, attrData
){
293 // Convert a <script type="dojo/method" args="a, b, c"> ... </script>
296 // The <script> DOMNode
298 // For HTML5 compliance, searches for attrData + "args" (typically
299 // "data-dojo-args") instead of "args"
302 var argsStr
= (script
.getAttribute(attrData
+ "args") || script
.getAttribute("args"));
304 d
.forEach(argsStr
.split(/\s*,\s*/), function(part
, idx
){
305 preamble
+= "var "+part
+" = arguments["+idx
+"]; ";
308 var withStr
= script
.getAttribute("with");
309 if(withStr
&& withStr
.length
){
310 d
.forEach(withStr
.split(/\s*,\s*/), function(part
){
311 preamble
+= "with("+part
+"){";
315 return new Function(preamble
+script
.innerHTML
+suffix
);
318 this.instantiate = function(/* Array */nodes
, /* Object? */mixin
, /* Object? */args
){
320 // Takes array of nodes, and turns them into class instances and
321 // potentially calls a startup method to allow them to connect with
324 // Array of nodes or objects like
326 // | type: "dijit.form.Button",
328 // | scripts: [ ... ], // array of <script type="dojo/..."> children of node
329 // | inherited: { ... } // settings inherited from ancestors like dir, theme, etc.
332 // An object that will be mixed in with each node in the array.
333 // Values in the mixin will override values in the node, if they
336 // An object used to hold kwArgs for instantiation.
337 // See parse.args argument for details.
343 // TODO: for 2.0 default to data-dojo- regardless of scopeName (or maybe scopeName won't exist in 2.0)
344 var attrName
= (args
.scope
|| d
._scopeName
) + "Type", // typically "dojoType"
345 attrData
= "data-" + (args
.scope
|| d
._scopeName
) + "-"; // typically "data-dojo-"
347 d
.forEach(nodes
, function(obj
){
350 // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc.
351 var node
, type
, clsInfo
, clazz
, scripts
, fastpath
;
353 // new format of nodes[] array, object w/lots of properties pre-computed for me
356 fastpath
= obj
.fastpath
;
357 clsInfo
= obj
.clsInfo
|| (type
&& getClassInfo(type
, fastpath
));
358 clazz
= clsInfo
&& clsInfo
.cls
;
359 scripts
= obj
.scripts
;
361 // old (backwards compatible) format of nodes[] array, simple array of DOMNodes. no fastpath/data-dojo-type support here.
363 type
= attrName
in mixin
? mixin
[attrName
] : node
.getAttribute(attrName
);
364 clsInfo
= type
&& getClassInfo(type
);
365 clazz
= clsInfo
&& clsInfo
.cls
;
366 scripts
= (clazz
&& (clazz
._noScript
|| clazz
.prototype._noScript
) ? [] :
367 d
.query("> script[type^='dojo/']", node
));
370 throw new Error("Could not load class '" + type
);
373 // Setup hash to hold parameter settings for this widget. Start with the parameter
374 // settings inherited from ancestors ("dir" and "lang").
375 // Inherited setting may later be overridden by explicit settings on node itself.
379 // settings for the document itself (or whatever subtree is being parsed)
380 d
._mixin(params
, args
.defaults
);
383 // settings from dir=rtl or lang=... on a node above this node
384 d
._mixin(params
, obj
.inherited
);
387 // mix things found in data-dojo-props into the params
389 var extra
= node
.getAttribute(attrData
+ "props");
390 if(extra
&& extra
.length
){
392 extra
= d
.fromJson
.call(args
.propsThis
, "{" + extra
+ "}");
393 d
._mixin(params
, extra
);
395 // give the user a pointer to their invalid parameters. FIXME: can we kill this in production?
396 throw new Error(e
.toString() + " in data-dojo-props='" + extra
+ "'");
400 // For the benefit of _Templated, check if node has data-dojo-attach-point/data-dojo-attach-event
401 // and mix those in as though they were parameters
402 var attachPoint
= node
.getAttribute(attrData
+ "attach-point");
404 params
.dojoAttachPoint
= attachPoint
;
406 var attachEvent
= node
.getAttribute(attrData
+ "attach-event");
408 params
.dojoAttachEvent
= attachEvent
;
410 dojo
.mixin(params
, mixin
);
412 // FIXME: we need something like "deprecateOnce()" to throw dojo.deprecation for something.
413 // remove this logic in 2.0
414 // read parameters (ie, attributes) specified on DOMNode
416 var attributes
= node
.attributes
;
418 // clsInfo.params lists expected params like {"checked": "boolean", "n": "number"}
419 for(var name
in clsInfo
.params
){
420 var item
= name
in mixin
? { value
:mixin
[name
], specified
:true } : attributes
.getNamedItem(name
);
421 if(!item
|| (!item
.specified
&& (!dojo
.isIE
|| name
.toLowerCase()!="value"))){ continue; }
422 var value
= item
.value
;
423 // Deal with IE quirks for 'class' and 'style'
426 value
= "className" in mixin
? mixin
.className
: node
.className
;
429 value
= "style" in mixin
? mixin
.style
: (node
.style
&& node
.style
.cssText
); // FIXME: Opera?
431 var _type
= clsInfo
.params
[name
];
432 if(typeof value
== "string"){
433 params
[name
] = str2obj(value
, _type
);
435 params
[name
] = value
;
440 // Process <script type="dojo/*"> script tags
441 // <script type="dojo/method" event="foo"> tags are added to params, and passed to
442 // the widget on instantiation.
443 // <script type="dojo/method"> tags (with no event) are executed after instantiation
444 // <script type="dojo/connect" event="foo"> tags are dojo.connected after instantiation
445 // note: dojo/* script tags cannot exist in self closing widgets, like <input />
446 var connects
= [], // functions to connect after instantiation
447 calls
= []; // functions to call after instantiation
449 d
.forEach(scripts
, function(script
){
450 node
.removeChild(script
);
451 // FIXME: drop event="" support in 2.0. use data-dojo-event="" instead
452 var event
= (script
.getAttribute(attrData
+ "event") || script
.getAttribute("event")),
453 type
= script
.getAttribute("type"),
454 nf
= d
.parser
._functionFromScript(script
, attrData
);
456 if(type
== "dojo/connect"){
457 connects
.push({event
: event
, func
: nf
});
466 var markupFactory
= clazz
.markupFactory
|| clazz
.prototype && clazz
.prototype.markupFactory
;
467 // create the instance
468 var instance
= markupFactory
? markupFactory(params
, node
, clazz
) : new clazz(params
, node
);
469 thelist
.push(instance
);
471 // map it to the JS namespace if that makes sense
472 // FIXME: in 2.0, drop jsId support. use data-dojo-id instead
473 var jsname
= (node
.getAttribute(attrData
+ "id") || node
.getAttribute("jsId"));
475 d
.setObject(jsname
, instance
);
478 // process connections and startup functions
479 d
.forEach(connects
, function(connect
){
480 d
.connect(instance
, connect
.event
, null, connect
.func
);
482 d
.forEach(calls
, function(func
){
487 // Call startup on each top level instance if it makes sense (as for
488 // widgets). Parent widgets will recursively call startup on their
489 // (non-top level) children
491 // TODO: for 2.0, when old instantiate() API is desupported, store parent-child
492 // relationships in the nodes[] array so that no getParent() call is needed.
493 // Note that will require a parse() call from ContentPane setting a param that the
494 // ContentPane is the parent widget (so that the parse doesn't call startup() on the
495 // ContentPane's children)
496 d
.forEach(thelist
, function(instance
){
497 if( !args
.noStart
&& instance
&&
498 dojo
.isFunction(instance
.startup
) &&
499 !instance
._started
&&
500 (!instance
.getParent
|| !instance
.getParent())
509 this.parse = function(rootNode
, args
){
511 // Scan the DOM for class instances, and instantiate them.
514 // Search specified node (or root node) recursively for class instances,
515 // and instantiate them. Searches for either data-dojo-type="Class" or
516 // dojoType="Class" where "Class" is a a fully qualified class name,
517 // like `dijit.form.Button`
519 // Using `data-dojo-type`:
520 // Attributes using can be mixed into the parameters used to instantitate the
521 // Class by using a `data-dojo-props` attribute on the node being converted.
522 // `data-dojo-props` should be a string attribute to be converted from JSON.
525 // Attributes are read from the original domNode and converted to appropriate
526 // types by looking up the Class prototype values. This is the default behavior
527 // from Dojo 1.0 to Dojo 1.5. `dojoType` support is deprecated, and will
528 // go away in Dojo 2.0.
530 // rootNode: DomNode?
531 // A default starting root node from which to start the parsing. Can be
532 // omitted, defaulting to the entire document. If omitted, the `args`
533 // object can be passed in this place. If the `args` object has a
534 // `rootNode` member, that is used.
537 // a kwArgs object passed along to instantiate()
539 // * noStart: Boolean?
540 // when set will prevent the parser from calling .startup()
541 // when locating the nodes.
542 // * rootNode: DomNode?
543 // identical to the function's `rootNode` argument, though
544 // allowed to be passed in via this `args object.
545 // * template: Boolean
546 // If true, ignores ContentPane's stopParser flag and parses contents inside of
547 // a ContentPane inside of a template. This allows dojoAttachPoint on widgets/nodes
548 // nested inside the ContentPane to work.
549 // * inherited: Object
550 // Hash possibly containing dir and lang settings to be applied to
551 // parsed widgets, unless there's another setting on a sub-node that overrides
553 // Root for attribute names to search for. If scopeName is dojo,
554 // will search for data-dojo-type (or dojoType). For backwards compatibility
555 // reasons defaults to dojo._scopeName (which is "dojo" except when
556 // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
557 // * propsThis: Object
558 // If specified, "this" referenced from data-dojo-props will refer to propsThis.
559 // Intended for use from the widgets-in-template feature of `dijit._Templated`
562 // Parse all widgets on a page:
563 // | dojo.parser.parse();
566 // Parse all classes within the node with id="foo"
567 // | dojo.parser.parse(dojo.byId('foo'));
570 // Parse all classes in a page, but do not call .startup() on any
572 // | dojo.parser.parse({ noStart: true })
575 // Parse all classes in a node, but do not call .startup()
576 // | dojo.parser.parse(someNode, { noStart:true });
578 // | dojo.parser.parse({ noStart:true, rootNode: someNode });
580 // determine the root node based on the passed arguments.
582 if(!args
&& rootNode
&& rootNode
.rootNode
){
584 root
= args
.rootNode
;
588 root
= root
? dojo
.byId(root
) : dojo
.body();
591 var attrName
= (args
.scope
|| d
._scopeName
) + "Type", // typically "dojoType"
592 attrData
= "data-" + (args
.scope
|| d
._scopeName
) + "-"; // typically "data-dojo-"
594 function scan(parent
, list
){
596 // Parent is an Object representing a DOMNode, with or without a dojoType specified.
597 // Scan parent's children looking for nodes with dojoType specified, storing in list[].
598 // If parent has a dojoType, also collects <script type=dojo/*> children and stores in parent.scripts[].
600 // Object representing the parent node, like
602 // | node: DomNode, // scan children of this node
603 // | inherited: {dir: "rtl"}, // dir/lang setting inherited from above node
605 // | // attributes only set if node has dojoType specified
606 // | scripts: [], // empty array, put <script type=dojo/*> in here
607 // | clsInfo: { cls: dijit.form.Button, ...}
610 // Output array of objects (same format as parent) representing nodes to be turned into widgets
612 // Effective dir and lang settings on parent node, either set directly or inherited from grandparent
613 var inherited
= dojo
.clone(parent
.inherited
);
614 dojo
.forEach(["dir", "lang"], function(name
){
615 // TODO: what if this is a widget and dir/lang are declared in data-dojo-props?
616 var val
= parent
.node
.getAttribute(name
);
618 inherited
[name
] = val
;
622 // if parent is a widget, then search for <script type=dojo/*> tags and put them in scripts[].
623 var scripts
= parent
.clsInfo
&& !parent
.clsInfo
.cls
.prototype._noScript
? parent
.scripts
: null;
625 // unless parent is a widget with the stopParser flag set, continue search for dojoType, recursively
626 var recurse
= (!parent
.clsInfo
|| !parent
.clsInfo
.cls
.prototype.stopParser
) || (args
&& args
.template
);
628 // scan parent's children looking for dojoType and <script type=dojo/*>
629 for(var child
= parent
.node
.firstChild
; child
; child
= child
.nextSibling
){
630 if(child
.nodeType
== 1){
631 // FIXME: desupport dojoType in 2.0. use data-dojo-type instead
632 var type
, html5
= recurse
&& child
.getAttribute(attrData
+ "type");
636 // fallback to backward compatible mode, using dojoType. remove in 2.0
637 type
= recurse
&& child
.getAttribute(attrName
);
640 var fastpath
= html5
== type
;
643 // if dojoType/data-dojo-type specified, add to output array of nodes to instantiate
647 clsInfo
: getClassInfo(type
, fastpath
), // note: won't find classes declared via dojo.Declaration
649 scripts
: [], // <script> nodes that are parent's children
650 inherited
: inherited
// dir & lang attributes inherited from parent
654 // Recurse, collecting <script type="dojo/..."> children, and also looking for
655 // descendant nodes with dojoType specified (unless the widget has the stopParser flag),
657 }else if(scripts
&& child
.nodeName
.toLowerCase() == "script"){
658 // if <script type="dojo/...">, save in scripts[]
659 type
= child
.getAttribute("type");
660 if (type
&& /^dojo\/\w/i.test(type
)) {
664 // Recurse, looking for grandchild nodes with dojoType specified
674 // Ignore bogus entries in inherited hash like {dir: ""}
676 if(args
&& args
.inherited
){
677 for(var key
in args
.inherited
){
678 if(args
.inherited
[key
]){ inherited
[key
] = args
.inherited
[key
]; }
682 // Make list of all nodes on page w/dojoType specified
689 // go build the object instances
690 var mixin
= args
&& args
.template
? {template
: true} : null;
691 return this.instantiate(list
, mixin
, args
); // Array
695 //Register the parser callback. It should be the first callback
696 //after the a11y test.
699 var parseRunner = function(){
700 if(dojo
.config
.parseOnLoad
){
705 // FIXME: need to clobber cross-dependency!!
706 if(dojo
.getObject("dijit.wai.onload") === dojo
._loaders
[0]){
707 dojo
._loaders
.splice(1, 0, parseRunner
);
709 dojo
._loaders
.unshift(parseRunner
);
715 if(!dojo
._hasResource
["dojo.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
716 dojo
._hasResource
["dojo.window"] = true;
717 dojo
.provide("dojo.window");
719 dojo
.getObject("window", true, dojo
);
721 dojo
.window
.getBox = function(){
723 // Returns the dimensions and scroll position of the viewable area of a browser window
725 var scrollRoot
= (dojo
.doc
.compatMode
== 'BackCompat') ? dojo
.body() : dojo
.doc
.documentElement
;
727 // get scroll position
728 var scroll
= dojo
._docScroll(); // scrollRoot.scrollTop/Left should work
729 return { w
: scrollRoot
.clientWidth
, h
: scrollRoot
.clientHeight
, l
: scroll
.x
, t
: scroll
.y
};
732 dojo
.window
.get = function(doc
){
734 // Get window object associated with document doc
736 // In some IE versions (at least 6.0), document.parentWindow does not return a
737 // reference to the real window object (maybe a copy), so we must fix it as well
738 // We use IE specific execScript to attach the real window reference to
739 // document._parentWindow for later use
740 if(dojo
.isIE
&& window
!== document
.parentWindow
){
742 In IE 6, only the variable "window" can be used to connect events (others
745 doc
.parentWindow
.execScript("document._parentWindow = window;", "Javascript");
746 //to prevent memory leak, unset it after use
747 //another possibility is to add an onUnload handler which seems overkill to me (liucougar)
748 var win
= doc
._parentWindow
;
749 doc
._parentWindow
= null;
750 return win
; // Window
753 return doc
.parentWindow
|| doc
.defaultView
; // Window
756 dojo
.window
.scrollIntoView = function(/*DomNode*/ node
, /*Object?*/ pos
){
758 // Scroll the passed node into view, if it is not already.
760 // don't rely on node.scrollIntoView working just because the function is there
762 try{ // catch unexpected/unrecreatable errors (#7808) since we can recover using a semi-acceptable native method
763 node
= dojo
.byId(node
);
764 var doc
= node
.ownerDocument
|| dojo
.doc
,
765 body
= doc
.body
|| dojo
.body(),
766 html
= doc
.documentElement
|| body
.parentNode
,
767 isIE
= dojo
.isIE
, isWK
= dojo
.isWebKit
;
768 // if an untested browser, then use the native method
769 if((!(dojo
.isMoz
|| isIE
|| isWK
|| dojo
.isOpera
) || node
== body
|| node
== html
) && (typeof node
.scrollIntoView
!= "undefined")){
770 node
.scrollIntoView(false); // short-circuit to native if possible
773 var backCompat
= doc
.compatMode
== 'BackCompat',
774 clientAreaRoot
= (isIE
>= 9 && node
.ownerDocument
.parentWindow
.frameElement
)
775 ? ((html
.clientHeight
> 0 && html
.clientWidth
> 0 && (body
.clientHeight
== 0 || body
.clientWidth
== 0 || body
.clientHeight
> html
.clientHeight
|| body
.clientWidth
> html
.clientWidth
)) ? html
: body
)
776 : (backCompat
? body
: html
),
777 scrollRoot
= isWK
? body
: clientAreaRoot
,
778 rootWidth
= clientAreaRoot
.clientWidth
,
779 rootHeight
= clientAreaRoot
.clientHeight
,
780 rtl
= !dojo
._isBodyLtr(),
781 nodePos
= pos
|| dojo
.position(node
),
782 el
= node
.parentNode
,
783 isFixed = function(el
){
784 return ((isIE
<= 6 || (isIE
&& backCompat
))? false : (dojo
.style(el
, 'position').toLowerCase() == "fixed"));
786 if(isFixed(node
)){ return; } // nothing to do
789 if(el
== body
){ el
= scrollRoot
; }
790 var elPos
= dojo
.position(el
),
791 fixedPos
= isFixed(el
);
793 if(el
== scrollRoot
){
794 elPos
.w
= rootWidth
; elPos
.h
= rootHeight
;
795 if(scrollRoot
== html
&& isIE
&& rtl
){ elPos
.x
+= scrollRoot
.offsetWidth
-elPos
.w
; } // IE workaround where scrollbar causes negative x
796 if(elPos
.x
< 0 || !isIE
){ elPos
.x
= 0; } // IE can have values > 0
797 if(elPos
.y
< 0 || !isIE
){ elPos
.y
= 0; }
799 var pb
= dojo
._getPadBorderExtents(el
);
800 elPos
.w
-= pb
.w
; elPos
.h
-= pb
.h
; elPos
.x
+= pb
.l
; elPos
.y
+= pb
.t
;
801 var clientSize
= el
.clientWidth
,
802 scrollBarSize
= elPos
.w
- clientSize
;
803 if(clientSize
> 0 && scrollBarSize
> 0){
804 elPos
.w
= clientSize
;
805 elPos
.x
+= (rtl
&& (isIE
|| el
.clientLeft
> pb
.l
/*Chrome*/)) ? scrollBarSize
: 0;
807 clientSize
= el
.clientHeight
;
808 scrollBarSize
= elPos
.h
- clientSize
;
809 if(clientSize
> 0 && scrollBarSize
> 0){
810 elPos
.h
= clientSize
;
813 if(fixedPos
){ // bounded by viewport, not parents
815 elPos
.h
+= elPos
.y
; elPos
.y
= 0;
818 elPos
.w
+= elPos
.x
; elPos
.x
= 0;
820 if(elPos
.y
+ elPos
.h
> rootHeight
){
821 elPos
.h
= rootHeight
- elPos
.y
;
823 if(elPos
.x
+ elPos
.w
> rootWidth
){
824 elPos
.w
= rootWidth
- elPos
.x
;
827 // calculate overflow in all 4 directions
828 var l
= nodePos
.x
- elPos
.x
, // beyond left: < 0
829 t
= nodePos
.y
- Math
.max(elPos
.y
, 0), // beyond top: < 0
830 r
= l
+ nodePos
.w
- elPos
.w
, // beyond right: > 0
831 bot
= t
+ nodePos
.h
- elPos
.h
; // beyond bottom: > 0
833 var s
= Math
[l
< 0? "max" : "min"](l
, r
);
834 if(rtl
&& ((isIE
== 8 && !backCompat
) || isIE
>= 9)){ s
= -s
; }
835 nodePos
.x
+= el
.scrollLeft
;
837 nodePos
.x
-= el
.scrollLeft
;
840 nodePos
.y
+= el
.scrollTop
;
841 el
.scrollTop
+= Math
[t
< 0? "max" : "min"](t
, bot
);
842 nodePos
.y
-= el
.scrollTop
;
844 el
= (el
!= scrollRoot
) && !fixedPos
&& el
.parentNode
;
847 console
.error('scrollIntoView: ' + error
);
848 node
.scrollIntoView(false);
854 if(!dojo
._hasResource
["dijit._base.manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
855 dojo
._hasResource
["dijit._base.manager"] = true;
856 dojo
.provide("dijit._base.manager");
859 dojo
.declare("dijit.WidgetSet", null, {
861 // A set of widgets indexed by id. A default instance of this class is
862 // available as `dijit.registry`
865 // Create a small list of widgets:
866 // | var ws = new dijit.WidgetSet();
867 // | ws.add(dijit.byId("one"));
868 // | ws.add(dijit.byId("two"));
869 // | // destroy both:
870 // | ws.forEach(function(w){ w.destroy(); });
873 // Using dijit.registry:
874 // | dijit.registry.forEach(function(w){ /* do something */ });
876 constructor: function(){
881 add: function(/*dijit._Widget*/ widget
){
883 // Add a widget to this list. If a duplicate ID is detected, a error is thrown.
885 // widget: dijit._Widget
886 // Any dijit._Widget subclass.
887 if(this._hash
[widget
.id
]){
888 throw new Error("Tried to register widget with id==" + widget
.id
+ " but that id is already registered");
890 this._hash
[widget
.id
] = widget
;
894 remove: function(/*String*/ id
){
896 // Remove a widget from this WidgetSet. Does not destroy the widget; simply
897 // removes the reference.
899 delete this._hash
[id
];
904 forEach: function(/*Function*/ func
, /* Object? */thisObj
){
906 // Call specified function for each widget in this set.
909 // A callback function to run for each item. Is passed the widget, the index
910 // in the iteration, and the full hash, similar to `dojo.forEach`.
913 // An optional scope parameter
916 // Using the default `dijit.registry` instance:
917 // | dijit.registry.forEach(function(widget){
918 // | console.log(widget.declaredClass);
922 // Returns self, in order to allow for further chaining.
924 thisObj
= thisObj
|| dojo
.global
;
926 for(id
in this._hash
){
927 func
.call(thisObj
, this._hash
[id
], i
++, this._hash
);
929 return this; // dijit.WidgetSet
932 filter: function(/*Function*/ filter
, /* Object? */thisObj
){
934 // Filter down this WidgetSet to a smaller new WidgetSet
935 // Works the same as `dojo.filter` and `dojo.NodeList.filter`
938 // Callback function to test truthiness. Is passed the widget
939 // reference and the pseudo-index in the object.
942 // Option scope to use for the filter function.
945 // Arbitrary: select the odd widgets in this list
946 // | dijit.registry.filter(function(w, i){
947 // | return i % 2 == 0;
948 // | }).forEach(function(w){ /* odd ones */ });
950 thisObj
= thisObj
|| dojo
.global
;
951 var res
= new dijit
.WidgetSet(), i
= 0, id
;
952 for(id
in this._hash
){
953 var w
= this._hash
[id
];
954 if(filter
.call(thisObj
, w
, i
++, this._hash
)){
958 return res
; // dijit.WidgetSet
961 byId: function(/*String*/ id
){
963 // Find a widget in this list by it's id.
965 // Test if an id is in a particular WidgetSet
966 // | var ws = new dijit.WidgetSet();
967 // | ws.add(dijit.byId("bar"));
968 // | var t = ws.byId("bar") // returns a widget
969 // | var x = ws.byId("foo"); // returns undefined
971 return this._hash
[id
]; // dijit._Widget
974 byClass: function(/*String*/ cls
){
976 // Reduce this widgetset to a new WidgetSet of a particular `declaredClass`
979 // The Class to scan for. Full dot-notated string.
982 // Find all `dijit.TitlePane`s in a page:
983 // | dijit.registry.byClass("dijit.TitlePane").forEach(function(tp){ tp.close(); });
985 var res
= new dijit
.WidgetSet(), id
, widget
;
986 for(id
in this._hash
){
987 widget
= this._hash
[id
];
988 if(widget
.declaredClass
== cls
){
992 return res
; // dijit.WidgetSet
997 // Convert this WidgetSet into a true Array
1000 // Work with the widget .domNodes in a real Array
1001 // | dojo.map(dijit.registry.toArray(), function(w){ return w.domNode; });
1004 for(var id
in this._hash
){
1005 ar
.push(this._hash
[id
]);
1007 return ar
; // dijit._Widget[]
1010 map: function(/* Function */func
, /* Object? */thisObj
){
1012 // Create a new Array from this WidgetSet, following the same rules as `dojo.map`
1014 // | var nodes = dijit.registry.map(function(w){ return w.domNode; });
1017 // A new array of the returned values.
1018 return dojo
.map(this.toArray(), func
, thisObj
); // Array
1021 every: function(func
, thisObj
){
1023 // A synthetic clone of `dojo.every` acting explicitly on this WidgetSet
1026 // A callback function run for every widget in this list. Exits loop
1027 // when the first false return is encountered.
1030 // Optional scope parameter to use for the callback
1032 thisObj
= thisObj
|| dojo
.global
;
1034 for(i
in this._hash
){
1035 if(!func
.call(thisObj
, this._hash
[i
], x
++, this._hash
)){
1036 return false; // Boolean
1039 return true; // Boolean
1042 some: function(func
, thisObj
){
1044 // A synthetic clone of `dojo.some` acting explictly on this WidgetSet
1047 // A callback function run for every widget in this list. Exits loop
1048 // when the first true return is encountered.
1051 // Optional scope parameter to use for the callback
1053 thisObj
= thisObj
|| dojo
.global
;
1055 for(i
in this._hash
){
1056 if(func
.call(thisObj
, this._hash
[i
], x
++, this._hash
)){
1057 return true; // Boolean
1060 return false; // Boolean
1070 // A list of widgets on a page.
1072 // Is an instance of `dijit.WidgetSet`
1075 dijit
.registry
= new dijit
.WidgetSet();
1077 var hash
= dijit
.registry
._hash
,
1079 hasAttr
= dojo
.hasAttr
,
1082 dijit
.byId = function(/*String|dijit._Widget*/ id
){
1084 // Returns a widget by it's id, or if passed a widget, no-op (like dojo.byId())
1085 return typeof id
== "string" ? hash
[id
] : id
; // dijit._Widget
1088 var _widgetTypeCtr
= {};
1089 dijit
.getUniqueId = function(/*String*/widgetType
){
1091 // Generates a unique id for a given widgetType
1095 id
= widgetType
+ "_" +
1096 (widgetType
in _widgetTypeCtr
?
1097 ++_widgetTypeCtr
[widgetType
] : _widgetTypeCtr
[widgetType
] = 0);
1099 return dijit
._scopeName
== "dijit" ? id
: dijit
._scopeName
+ "_" + id
; // String
1102 dijit
.findWidgets = function(/*DomNode*/ root
){
1104 // Search subtree under root returning widgets found.
1105 // Doesn't search for nested widgets (ie, widgets inside other widgets).
1109 function getChildrenHelper(root
){
1110 for(var node
= root
.firstChild
; node
; node
= node
.nextSibling
){
1111 if(node
.nodeType
== 1){
1112 var widgetId
= node
.getAttribute("widgetId");
1114 var widget
= hash
[widgetId
];
1115 if(widget
){ // may be null on page w/multiple dojo's loaded
1116 outAry
.push(widget
);
1119 getChildrenHelper(node
);
1125 getChildrenHelper(root
);
1129 dijit
._destroyAll = function(){
1131 // Code to destroy all widgets and do other cleanup on page unload
1133 // Clean up focus manager lingering references to widgets and nodes
1134 dijit
._curFocus
= null;
1135 dijit
._prevFocus
= null;
1136 dijit
._activeStack
= [];
1138 // Destroy all the widgets, top down
1139 dojo
.forEach(dijit
.findWidgets(dojo
.body()), function(widget
){
1140 // Avoid double destroy of widgets like Menu that are attached to <body>
1141 // even though they are logically children of other widgets.
1142 if(!widget
._destroyed
){
1143 if(widget
.destroyRecursive
){
1144 widget
.destroyRecursive();
1145 }else if(widget
.destroy
){
1153 // Only run _destroyAll() for IE because we think it's only necessary in that case,
1154 // and because it causes problems on FF. See bug #3531 for details.
1155 dojo
.addOnWindowUnload(function(){
1156 dijit
._destroyAll();
1160 dijit
.byNode = function(/*DOMNode*/ node
){
1162 // Returns the widget corresponding to the given DOMNode
1163 return hash
[node
.getAttribute("widgetId")]; // dijit._Widget
1166 dijit
.getEnclosingWidget = function(/*DOMNode*/ node
){
1168 // Returns the widget whose DOM tree contains the specified DOMNode, or null if
1169 // the node is not contained within the DOM tree of any widget
1171 var id
= node
.getAttribute
&& node
.getAttribute("widgetId");
1175 node
= node
.parentNode
;
1180 var shown
= (dijit
._isElementShown = function(/*Element*/ elem
){
1181 var s
= style(elem
);
1182 return (s
.visibility
!= "hidden")
1183 && (s
.visibility
!= "collapsed")
1184 && (s
.display
!= "none")
1185 && (attr(elem
, "type") != "hidden");
1188 dijit
.hasDefaultTabStop = function(/*Element*/ elem
){
1190 // Tests if element is tab-navigable even without an explicit tabIndex setting
1192 // No explicit tabIndex setting, need to investigate node type
1193 switch(elem
.nodeName
.toLowerCase()){
1195 // An <a> w/out a tabindex is only navigable if it has an href
1196 return hasAttr(elem
, "href");
1203 // These are navigable by default
1206 // If it's an editor <iframe> then it's tab navigable.
1210 var contentDocument
= elem
.contentDocument
;
1211 if("designMode" in contentDocument
&& contentDocument
.designMode
== "on"){
1214 body
= contentDocument
.body
;
1216 // contentWindow.document isn't accessible within IE7/8
1217 // if the iframe.src points to a foreign url and this
1218 // page contains an element, that could get focus
1220 body
= elem
.contentWindow
.document
.body
;
1225 return body
.contentEditable
== 'true' || (body
.firstChild
&& body
.firstChild
.contentEditable
== 'true');
1227 return elem
.contentEditable
== 'true';
1231 var isTabNavigable
= (dijit
.isTabNavigable = function(/*Element*/ elem
){
1233 // Tests if an element is tab-navigable
1235 // TODO: convert (and rename method) to return effective tabIndex; will save time in _getTabNavigable()
1236 if(attr(elem
, "disabled")){
1238 }else if(hasAttr(elem
, "tabIndex")){
1239 // Explicit tab index setting
1240 return attr(elem
, "tabIndex") >= 0; // boolean
1242 // No explicit tabIndex setting, so depends on node type
1243 return dijit
.hasDefaultTabStop(elem
);
1247 dijit
._getTabNavigable = function(/*DOMNode*/ root
){
1249 // Finds descendants of the specified root node.
1252 // Finds the following descendants of the specified root node:
1253 // * the first tab-navigable element in document order
1254 // without a tabIndex or with tabIndex="0"
1255 // * the last tab-navigable element in document order
1256 // without a tabIndex or with tabIndex="0"
1257 // * the first element in document order with the lowest
1258 // positive tabIndex value
1259 // * the last element in document order with the highest
1260 // positive tabIndex value
1261 var first
, last
, lowest
, lowestTabindex
, highest
, highestTabindex
, radioSelected
= {};
1262 function radioName(node
) {
1263 // If this element is part of a radio button group, return the name for that group.
1264 return node
&& node
.tagName
.toLowerCase() == "input" &&
1265 node
.type
&& node
.type
.toLowerCase() == "radio" &&
1266 node
.name
&& node
.name
.toLowerCase();
1268 var walkTree = function(/*DOMNode*/parent
){
1269 dojo
.query("> *", parent
).forEach(function(child
){
1270 // Skip hidden elements, and also non-HTML elements (those in custom namespaces) in IE,
1271 // since show() invokes getAttribute("type"), which crash on VML nodes in IE.
1272 if((dojo
.isIE
&& child
.scopeName
!=="HTML") || !shown(child
)){
1276 if(isTabNavigable(child
)){
1277 var tabindex
= attr(child
, "tabIndex");
1278 if(!hasAttr(child
, "tabIndex") || tabindex
== 0){
1279 if(!first
){ first
= child
; }
1281 }else if(tabindex
> 0){
1282 if(!lowest
|| tabindex
< lowestTabindex
){
1283 lowestTabindex
= tabindex
;
1286 if(!highest
|| tabindex
>= highestTabindex
){
1287 highestTabindex
= tabindex
;
1291 var rn
= radioName(child
);
1292 if(dojo
.attr(child
, "checked") && rn
) {
1293 radioSelected
[rn
] = child
;
1296 if(child
.nodeName
.toUpperCase() != 'SELECT'){
1301 if(shown(root
)){ walkTree(root
) }
1303 // substitute checked radio button for unchecked one, if there is a checked one with the same name.
1304 return radioSelected
[radioName(node
)] || node
;
1306 return { first
: rs(first
), last
: rs(last
), lowest
: rs(lowest
), highest
: rs(highest
) };
1308 dijit
.getFirstInTabbingOrder = function(/*String|DOMNode*/ root
){
1310 // Finds the descendant of the specified root node
1311 // that is first in the tabbing order
1312 var elems
= dijit
._getTabNavigable(dojo
.byId(root
));
1313 return elems
.lowest
? elems
.lowest
: elems
.first
; // DomNode
1316 dijit
.getLastInTabbingOrder = function(/*String|DOMNode*/ root
){
1318 // Finds the descendant of the specified root node
1319 // that is last in the tabbing order
1320 var elems
= dijit
._getTabNavigable(dojo
.byId(root
));
1321 return elems
.last
? elems
.last
: elems
.highest
; // DomNode
1326 // defaultDuration: Integer
1327 // The default animation speed (in ms) to use for all Dijit
1328 // transitional animations, unless otherwise specified
1329 // on a per-instance basis. Defaults to 200, overrided by
1330 // `djConfig.defaultDuration`
1331 defaultDuration: 200
1335 dijit
.defaultDuration
= dojo
.config
["defaultDuration"] || 200;
1341 if(!dojo
._hasResource
["dijit._base.focus"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1342 dojo
._hasResource
["dijit._base.focus"] = true;
1343 dojo
.provide("dijit._base.focus");
1349 // These functions are used to query or set the focus and selection.
1351 // Also, they trace when widgets become activated/deactivated,
1352 // so that the widget can fire _onFocus/_onBlur events.
1353 // "Active" here means something similar to "focused", but
1354 // "focus" isn't quite the right word because we keep track of
1355 // a whole stack of "active" widgets. Example: ComboButton --> Menu -->
1356 // MenuItem. The onBlur event for ComboButton doesn't fire due to focusing
1357 // on the Menu or a MenuItem, since they are considered part of the
1358 // ComboButton widget. It only happens when focus is shifted
1359 // somewhere completely different.
1362 // _curFocus: DomNode
1363 // Currently focused item on screen
1366 // _prevFocus: DomNode
1367 // Previously focused item on screen
1370 isCollapsed: function(){
1372 // Returns true if there is no text selected
1373 return dijit
.getBookmark().isCollapsed
;
1376 getBookmark: function(){
1378 // Retrieves a bookmark that can be used with moveToBookmark to return to the same range
1379 var bm
, rg
, tg
, sel
= dojo
.doc
.selection
, cf
= dijit
._curFocus
;
1381 if(dojo
.global
.getSelection
){
1382 //W3C Range API for selections.
1383 sel
= dojo
.global
.getSelection();
1385 if(sel
.isCollapsed
){
1386 tg
= cf
? cf
.tagName
: "";
1388 //Create a fake rangelike item to restore selections.
1389 tg
= tg
.toLowerCase();
1390 if(tg
== "textarea" ||
1391 (tg
== "input" && (!cf
.type
|| cf
.type
.toLowerCase() == "text"))){
1393 start
: cf
.selectionStart
,
1394 end
: cf
.selectionEnd
,
1398 return {isCollapsed
: (sel
.end
<= sel
.start
), mark
: sel
}; //Object.
1401 bm
= {isCollapsed
:true};
1403 bm
.mark
= sel
.getRangeAt(0).cloneRange();
1406 rg
= sel
.getRangeAt(0);
1407 bm
= {isCollapsed
: false, mark
: rg
.cloneRange()};
1411 // If the current focus was a input of some sort and no selection, don't bother saving
1412 // a native bookmark. This is because it causes issues with dialog/page selection restore.
1413 // So, we need to create psuedo bookmarks to work with.
1414 tg
= cf
? cf
.tagName
: "";
1415 tg
= tg
.toLowerCase();
1416 if(cf
&& tg
&& (tg
== "button" || tg
== "textarea" || tg
== "input")){
1417 if(sel
.type
&& sel
.type
.toLowerCase() == "none"){
1423 rg
= sel
.createRange();
1425 isCollapsed
: rg
.text
&& rg
.text
.length
?false:true,
1435 //'IE' way for selections.
1437 // createRange() throws exception when dojo in iframe
1438 //and nothing selected, see #9632
1439 rg
= sel
.createRange();
1440 bm
.isCollapsed
= !(sel
.type
== 'Text' ? rg
.htmlText
.length
: rg
.length
);
1442 bm
.isCollapsed
= true;
1445 if(sel
.type
.toUpperCase() == 'CONTROL'){
1448 var i
=0,len
=rg
.length
;
1450 bm
.mark
.push(rg
.item(i
++));
1453 bm
.isCollapsed
= true;
1457 bm
.mark
= rg
.getBookmark();
1460 console
.warn("No idea how to store the current selection for this browser!");
1462 return bm
; // Object
1465 moveToBookmark: function(/*Object*/bookmark
){
1467 // Moves current selection to a bookmark
1469 // This should be a returned object from dijit.getBookmark()
1471 var _doc
= dojo
.doc
,
1472 mark
= bookmark
.mark
;
1474 if(dojo
.global
.getSelection
){
1475 //W3C Rangi API (FF, WebKit, Opera, etc)
1476 var sel
= dojo
.global
.getSelection();
1477 if(sel
&& sel
.removeAllRanges
){
1481 n
.selectionStart
= r
.start
;
1482 n
.selectionEnd
= r
.end
;
1484 sel
.removeAllRanges();
1488 console
.warn("No idea how to restore selection for this browser!");
1490 }else if(_doc
.selection
&& mark
){
1495 }else if(dojo
.isArray(mark
)){
1496 rg
= _doc
.body
.createControlRange();
1497 //rg.addElement does not have call/apply method, so can not call it directly
1498 //rg is not available in "range.addElement(item)", so can't use that either
1499 dojo
.forEach(mark
, function(n
){
1503 rg
= _doc
.body
.createTextRange();
1504 rg
.moveToBookmark(mark
);
1511 getFocus: function(/*Widget?*/ menu
, /*Window?*/ openedForWindow
){
1513 // Called as getFocus(), this returns an Object showing the current focus
1514 // and selected text.
1516 // Called as getFocus(widget), where widget is a (widget representing) a button
1517 // that was just pressed, it returns where focus was before that button
1518 // was pressed. (Pressing the button may have either shifted focus to the button,
1519 // or removed focus altogether.) In this case the selected text is not returned,
1520 // since it can't be accurately determined.
1522 // menu: dijit._Widget or {domNode: DomNode} structure
1523 // The button that was just pressed. If focus has disappeared or moved
1524 // to this button, returns the previous focus. In this case the bookmark
1525 // information is already lost, and null is returned.
1528 // iframe in which menu was opened
1531 // A handle to restore focus/selection, to be passed to `dijit.focus`
1532 var node
= !dijit
._curFocus
|| (menu
&& dojo
.isDescendant(dijit
._curFocus
, menu
.domNode
)) ? dijit
._prevFocus
: dijit
._curFocus
;
1535 bookmark
: (node
== dijit
._curFocus
) && dojo
.withGlobal(openedForWindow
|| dojo
.global
, dijit
.getBookmark
),
1536 openedForWindow
: openedForWindow
1540 focus: function(/*Object || DomNode */ handle
){
1542 // Sets the focused node and the selection according to argument.
1543 // To set focus to an iframe's content, pass in the iframe itself.
1545 // object returned by get(), or a DomNode
1547 if(!handle
){ return; }
1549 var node
= "node" in handle
? handle
.node
: handle
, // because handle is either DomNode or a composite object
1550 bookmark
= handle
.bookmark
,
1551 openedForWindow
= handle
.openedForWindow
,
1552 collapsed
= bookmark
? bookmark
.isCollapsed
: false;
1555 // Note that for iframe's we need to use the <iframe> to follow the parentNode chain,
1556 // but we need to set focus to iframe.contentWindow
1558 var focusNode
= (node
.tagName
.toLowerCase() == "iframe") ? node
.contentWindow
: node
;
1559 if(focusNode
&& focusNode
.focus
){
1561 // Gecko throws sometimes if setting focus is impossible,
1562 // node not displayed or something like that
1564 }catch(e
){/*quiet*/}
1566 dijit
._onFocusNode(node
);
1569 // set the selection
1570 // do not need to restore if current selection is not empty
1571 // (use keyboard to select a menu item) or if previous selection was collapsed
1572 // as it may cause focus shift (Esp in IE).
1573 if(bookmark
&& dojo
.withGlobal(openedForWindow
|| dojo
.global
, dijit
.isCollapsed
) && !collapsed
){
1574 if(openedForWindow
){
1575 openedForWindow
.focus();
1578 dojo
.withGlobal(openedForWindow
|| dojo
.global
, dijit
.moveToBookmark
, null, [bookmark
]);
1580 /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */
1585 // _activeStack: dijit._Widget[]
1586 // List of currently active widgets (focused widget and it's ancestors)
1589 registerIframe: function(/*DomNode*/ iframe
){
1591 // Registers listeners on the specified iframe so that any click
1592 // or focus event on that iframe (or anything in it) is reported
1593 // as a focus/click event on the <iframe> itself.
1595 // Currently only used by editor.
1597 // Handle to pass to unregisterIframe()
1598 return dijit
.registerWin(iframe
.contentWindow
, iframe
);
1601 unregisterIframe: function(/*Object*/ handle
){
1603 // Unregisters listeners on the specified iframe created by registerIframe.
1604 // After calling be sure to delete or null out the handle itself.
1606 // Handle returned by registerIframe()
1608 dijit
.unregisterWin(handle
);
1611 registerWin: function(/*Window?*/targetWindow
, /*DomNode?*/ effectiveNode
){
1613 // Registers listeners on the specified window (either the main
1614 // window or an iframe's window) to detect when the user has clicked somewhere
1615 // or focused somewhere.
1617 // Users should call registerIframe() instead of this method.
1619 // If specified this is the window associated with the iframe,
1620 // i.e. iframe.contentWindow.
1622 // If specified, report any focus events inside targetWindow as
1623 // an event on effectiveNode, rather than on evt.target.
1625 // Handle to pass to unregisterWin()
1627 // TODO: make this function private in 2.0; Editor/users should call registerIframe(),
1629 var mousedownListener = function(evt
){
1630 dijit
._justMouseDowned
= true;
1631 setTimeout(function(){ dijit
._justMouseDowned
= false; }, 0);
1633 // workaround weird IE bug where the click is on an orphaned node
1634 // (first time clicking a Select/DropDownButton inside a TooltipDialog)
1635 if(dojo
.isIE
&& evt
&& evt
.srcElement
&& evt
.srcElement
.parentNode
== null){
1639 dijit
._onTouchNode(effectiveNode
|| evt
.target
|| evt
.srcElement
, "mouse");
1641 //dojo.connect(targetWindow, "onscroll", ???);
1643 // Listen for blur and focus events on targetWindow's document.
1644 // IIRC, I'm using attachEvent() rather than dojo.connect() because focus/blur events don't bubble
1645 // through dojo.connect(), and also maybe to catch the focus events early, before onfocus handlers
1647 // Connect to <html> (rather than document) on IE to avoid memory leaks, but document on other browsers because
1648 // (at least for FF) the focus event doesn't fire on <html> or <body>.
1649 var doc
= dojo
.isIE
? targetWindow
.document
.documentElement
: targetWindow
.document
;
1652 targetWindow
.document
.body
.attachEvent('onmousedown', mousedownListener
);
1653 var activateListener = function(evt
){
1654 // IE reports that nodes like <body> have gotten focus, even though they have tabIndex=-1,
1655 // Should consider those more like a mouse-click than a focus....
1656 if(evt
.srcElement
.tagName
.toLowerCase() != "#document" &&
1657 dijit
.isTabNavigable(evt
.srcElement
)){
1658 dijit
._onFocusNode(effectiveNode
|| evt
.srcElement
);
1660 dijit
._onTouchNode(effectiveNode
|| evt
.srcElement
);
1663 doc
.attachEvent('onactivate', activateListener
);
1664 var deactivateListener = function(evt
){
1665 dijit
._onBlurNode(effectiveNode
|| evt
.srcElement
);
1667 doc
.attachEvent('ondeactivate', deactivateListener
);
1670 targetWindow
.document
.detachEvent('onmousedown', mousedownListener
);
1671 doc
.detachEvent('onactivate', activateListener
);
1672 doc
.detachEvent('ondeactivate', deactivateListener
);
1673 doc
= null; // prevent memory leak (apparent circular reference via closure)
1676 doc
.body
.addEventListener('mousedown', mousedownListener
, true);
1677 var focusListener = function(evt
){
1678 dijit
._onFocusNode(effectiveNode
|| evt
.target
);
1680 doc
.addEventListener('focus', focusListener
, true);
1681 var blurListener = function(evt
){
1682 dijit
._onBlurNode(effectiveNode
|| evt
.target
);
1684 doc
.addEventListener('blur', blurListener
, true);
1687 doc
.body
.removeEventListener('mousedown', mousedownListener
, true);
1688 doc
.removeEventListener('focus', focusListener
, true);
1689 doc
.removeEventListener('blur', blurListener
, true);
1690 doc
= null; // prevent memory leak (apparent circular reference via closure)
1696 unregisterWin: function(/*Handle*/ handle
){
1698 // Unregisters listeners on the specified window (either the main
1699 // window or an iframe's window) according to handle returned from registerWin().
1700 // After calling be sure to delete or null out the handle itself.
1702 // Currently our handle is actually a function
1706 _onBlurNode: function(/*DomNode*/ node
){
1708 // Called when focus leaves a node.
1709 // Usually ignored, _unless_ it *isn't* follwed by touching another node,
1710 // which indicates that we tabbed off the last field on the page,
1711 // in which case every widget is marked inactive
1712 dijit
._prevFocus
= dijit
._curFocus
;
1713 dijit
._curFocus
= null;
1715 if(dijit
._justMouseDowned
){
1716 // the mouse down caused a new widget to be marked as active; this blur event
1717 // is coming late, so ignore it.
1721 // if the blur event isn't followed by a focus event then mark all widgets as inactive.
1722 if(dijit
._clearActiveWidgetsTimer
){
1723 clearTimeout(dijit
._clearActiveWidgetsTimer
);
1725 dijit
._clearActiveWidgetsTimer
= setTimeout(function(){
1726 delete dijit
._clearActiveWidgetsTimer
;
1727 dijit
._setStack([]);
1728 dijit
._prevFocus
= null;
1732 _onTouchNode: function(/*DomNode*/ node
, /*String*/ by
){
1734 // Callback when node is focused or mouse-downed
1736 // The node that was touched.
1738 // "mouse" if the focus/touch was caused by a mouse down event
1740 // ignore the recent blurNode event
1741 if(dijit
._clearActiveWidgetsTimer
){
1742 clearTimeout(dijit
._clearActiveWidgetsTimer
);
1743 delete dijit
._clearActiveWidgetsTimer
;
1746 // compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem)
1750 var popupParent
= dojo
.attr(node
, "dijitPopupParent");
1752 node
=dijit
.byId(popupParent
).domNode
;
1753 }else if(node
.tagName
&& node
.tagName
.toLowerCase() == "body"){
1754 // is this the root of the document or just the root of an iframe?
1755 if(node
=== dojo
.body()){
1756 // node is the root of the main document
1759 // otherwise, find the iframe this node refers to (can't access it via parentNode,
1760 // need to do this trick instead). window.frameElement is supported in IE/FF/Webkit
1761 node
=dojo
.window
.get(node
.ownerDocument
).frameElement
;
1763 // if this node is the root node of a widget, then add widget id to stack,
1764 // except ignore clicks on disabled widgets (actually focusing a disabled widget still works,
1765 // to support MenuItem)
1766 var id
= node
.getAttribute
&& node
.getAttribute("widgetId"),
1767 widget
= id
&& dijit
.byId(id
);
1768 if(widget
&& !(by
== "mouse" && widget
.get("disabled"))){
1769 newStack
.unshift(id
);
1771 node
=node
.parentNode
;
1774 }catch(e
){ /* squelch */ }
1776 dijit
._setStack(newStack
, by
);
1779 _onFocusNode: function(/*DomNode*/ node
){
1781 // Callback when node is focused
1787 if(node
.nodeType
== 9){
1788 // Ignore focus events on the document itself. This is here so that
1789 // (for example) clicking the up/down arrows of a spinner
1790 // (which don't get focus) won't cause that widget to blur. (FF issue)
1794 dijit
._onTouchNode(node
);
1796 if(node
== dijit
._curFocus
){ return; }
1797 if(dijit
._curFocus
){
1798 dijit
._prevFocus
= dijit
._curFocus
;
1800 dijit
._curFocus
= node
;
1801 dojo
.publish("focusNode", [node
]);
1804 _setStack: function(/*String[]*/ newStack
, /*String*/ by
){
1806 // The stack of active widgets has changed. Send out appropriate events and records new stack.
1808 // array of widget id's, starting from the top (outermost) widget
1810 // "mouse" if the focus/touch was caused by a mouse down event
1812 var oldStack
= dijit
._activeStack
;
1813 dijit
._activeStack
= newStack
;
1815 // compare old stack to new stack to see how many elements they have in common
1816 for(var nCommon
=0; nCommon
<Math
.min(oldStack
.length
, newStack
.length
); nCommon
++){
1817 if(oldStack
[nCommon
] != newStack
[nCommon
]){
1823 // for all elements that have gone out of focus, send blur event
1824 for(var i
=oldStack
.length
-1; i
>=nCommon
; i
--){
1825 widget
= dijit
.byId(oldStack
[i
]);
1827 widget
._focused
= false;
1828 widget
.set("focused", false);
1829 widget
._hasBeenBlurred
= true;
1833 dojo
.publish("widgetBlur", [widget
, by
]);
1837 // for all element that have come into focus, send focus event
1838 for(i
=nCommon
; i
<newStack
.length
; i
++){
1839 widget
= dijit
.byId(newStack
[i
]);
1841 widget
._focused
= true;
1842 widget
.set("focused", true);
1843 if(widget
._onFocus
){
1844 widget
._onFocus(by
);
1846 dojo
.publish("widgetFocus", [widget
, by
]);
1852 // register top window and all the iframes it contains
1853 dojo
.addOnLoad(function(){
1854 var handle
= dijit
.registerWin(window
);
1856 dojo
.addOnWindowUnload(function(){
1857 dijit
.unregisterWin(handle
);
1865 if(!dojo
._hasResource
["dojo.AdapterRegistry"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1866 dojo
._hasResource
["dojo.AdapterRegistry"] = true;
1867 dojo
.provide("dojo.AdapterRegistry");
1870 dojo
.AdapterRegistry = function(/*Boolean?*/ returnWrappers
){
1872 // A registry to make contextual calling/searching easier.
1874 // Objects of this class keep list of arrays in the form [name, check,
1875 // wrap, directReturn] that are used to determine what the contextual
1876 // result of a set of checked arguments is. All check/wrap functions
1877 // in this registry should be of the same arity.
1879 // | // create a new registry
1880 // | var reg = new dojo.AdapterRegistry();
1881 // | reg.register("handleString",
1884 // | // do something with the string here
1887 // | reg.register("handleArr",
1890 // | // do something with the array here
1894 // | // now we can pass reg.match() *either* an array or a string and
1895 // | // the value we pass will get handled by the right function
1896 // | reg.match("someValue"); // will call the first function
1897 // | reg.match(["someValue"]); // will call the second
1900 this.returnWrappers
= returnWrappers
|| false; // Boolean
1903 dojo
.extend(dojo
.AdapterRegistry
, {
1904 register: function(/*String*/ name
, /*Function*/ check
, /*Function*/ wrap
, /*Boolean?*/ directReturn
, /*Boolean?*/ override
){
1906 // register a check function to determine if the wrap function or
1907 // object gets selected
1909 // a way to identify this matcher.
1911 // a function that arguments are passed to from the adapter's
1912 // match() function. The check function should return true if the
1913 // given arguments are appropriate for the wrap function.
1915 // If directReturn is true, the value passed in for wrap will be
1916 // returned instead of being called. Alternately, the
1917 // AdapterRegistry can be set globally to "return not call" using
1918 // the returnWrappers property. Either way, this behavior allows
1919 // the registry to act as a "search" function instead of a
1920 // function interception library.
1922 // If override is given and true, the check function will be given
1923 // highest priority. Otherwise, it will be the lowest priority
1925 this.pairs
[((override
) ? "unshift" : "push")]([name
, check
, wrap
, directReturn
]);
1928 match: function(/* ... */){
1930 // Find an adapter for the given arguments. If no suitable adapter
1931 // is found, throws an exception. match() accepts any number of
1932 // arguments, all of which are passed to all matching functions
1933 // from the registered pairs.
1934 for(var i
= 0; i
< this.pairs
.length
; i
++){
1935 var pair
= this.pairs
[i
];
1936 if(pair
[1].apply(this, arguments
)){
1937 if((pair
[3])||(this.returnWrappers
)){
1940 return pair
[2].apply(this, arguments
);
1944 throw new Error("No match found");
1947 unregister: function(name
){
1948 // summary: Remove a named adapter from the registry
1950 // FIXME: this is kind of a dumb way to handle this. On a large
1951 // registry this will be slow-ish and we can use the name as a lookup
1952 // should we choose to trade memory for speed.
1953 for(var i
= 0; i
< this.pairs
.length
; i
++){
1954 var pair
= this.pairs
[i
];
1955 if(pair
[0] == name
){
1956 this.pairs
.splice(i
, 1);
1966 if(!dojo
._hasResource
["dijit._base.place"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1967 dojo
._hasResource
["dijit._base.place"] = true;
1968 dojo
.provide("dijit._base.place");
1973 dijit
.getViewport = function(){
1975 // Returns the dimensions and scroll position of the viewable area of a browser window
1977 return dojo
.window
.getBox();
1981 dijit.__Position = function(){
1983 // horizontal coordinate in pixels, relative to document body
1985 // vertical coordinate in pixels, relative to document body
1993 dijit
.placeOnScreen = function(
1995 /* dijit.__Position */ pos
,
1996 /* String[] */ corners
,
1997 /* dijit.__Position? */ padding
){
1999 // Positions one of the node's corners at specified position
2000 // such that node is fully visible in viewport.
2002 // NOTE: node is assumed to be absolutely or relatively positioned.
2004 // Object like {x: 10, y: 20}
2006 // Array of Strings representing order to try corners in, like ["TR", "BL"].
2007 // Possible values are:
2008 // * "BL" - bottom left
2009 // * "BR" - bottom right
2010 // * "TL" - top left
2011 // * "TR" - top right
2013 // set padding to put some buffer around the element you want to position.
2015 // Try to place node's top right corner at (10,20).
2016 // If that makes node go (partially) off screen, then try placing
2017 // bottom left corner at (10,20).
2018 // | placeOnScreen(node, {x: 10, y: 20}, ["TR", "BL"])
2020 var choices
= dojo
.map(corners
, function(corner
){
2021 var c
= { corner
: corner
, pos
: {x
:pos
.x
,y
:pos
.y
} };
2023 c
.pos
.x
+= corner
.charAt(1) == 'L' ? padding
.x
: -padding
.x
;
2024 c
.pos
.y
+= corner
.charAt(0) == 'T' ? padding
.y
: -padding
.y
;
2029 return dijit
._place(node
, choices
);
2032 dijit
._place = function(/*DomNode*/ node
, choices
, layoutNode
, /*Object*/ aroundNodeCoords
){
2034 // Given a list of spots to put node, put it at the first spot where it fits,
2035 // of if it doesn't fit anywhere then the place with the least overflow
2037 // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} }
2038 // Above example says to put the top-left corner of the node at (10,20)
2039 // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size)
2040 // for things like tooltip, they are displayed differently (and have different dimensions)
2041 // based on their orientation relative to the parent. This adjusts the popup based on orientation.
2042 // It also passes in the available size for the popup, which is useful for tooltips to
2043 // tell them that their width is limited to a certain amount. layoutNode() may return a value expressing
2044 // how much the popup had to be modified to fit into the available space. This is used to determine
2045 // what the best placement is.
2046 // aroundNodeCoords: Object
2047 // Size of aroundNode, ex: {w: 200, h: 50}
2049 // get {x: 10, y: 10, w: 100, h:100} type obj representing position of
2050 // viewport over document
2051 var view
= dojo
.window
.getBox();
2053 // This won't work if the node is inside a <div style="position: relative">,
2054 // so reattach it to dojo.doc.body. (Otherwise, the positioning will be wrong
2055 // and also it might get cutoff)
2056 if(!node
.parentNode
|| String(node
.parentNode
.tagName
).toLowerCase() != "body"){
2057 dojo
.body().appendChild(node
);
2061 dojo
.some(choices
, function(choice
){
2062 var corner
= choice
.corner
;
2063 var pos
= choice
.pos
;
2066 // calculate amount of space available given specified position of node
2067 var spaceAvailable
= {
2068 w
: corner
.charAt(1) == 'L' ? (view
.l
+ view
.w
) - pos
.x
: pos
.x
- view
.l
,
2069 h
: corner
.charAt(1) == 'T' ? (view
.t
+ view
.h
) - pos
.y
: pos
.y
- view
.t
2072 // configure node to be displayed in given position relative to button
2073 // (need to do this in order to get an accurate size for the node, because
2074 // a tooltip's size changes based on position, due to triangle)
2076 var res
= layoutNode(node
, choice
.aroundCorner
, corner
, spaceAvailable
, aroundNodeCoords
);
2077 overflow
= typeof res
== "undefined" ? 0 : res
;
2081 var style
= node
.style
;
2082 var oldDisplay
= style
.display
;
2083 var oldVis
= style
.visibility
;
2084 style
.visibility
= "hidden";
2086 var mb
= dojo
.marginBox(node
);
2087 style
.display
= oldDisplay
;
2088 style
.visibility
= oldVis
;
2090 // coordinates and size of node with specified corner placed at pos,
2091 // and clipped by viewport
2092 var startX
= Math
.max(view
.l
, corner
.charAt(1) == 'L' ? pos
.x
: (pos
.x
- mb
.w
)),
2093 startY
= Math
.max(view
.t
, corner
.charAt(0) == 'T' ? pos
.y
: (pos
.y
- mb
.h
)),
2094 endX
= Math
.min(view
.l
+ view
.w
, corner
.charAt(1) == 'L' ? (startX
+ mb
.w
) : pos
.x
),
2095 endY
= Math
.min(view
.t
+ view
.h
, corner
.charAt(0) == 'T' ? (startY
+ mb
.h
) : pos
.y
),
2096 width
= endX
- startX
,
2097 height
= endY
- startY
;
2099 overflow
+= (mb
.w
- width
) + (mb
.h
- height
);
2101 if(best
== null || overflow
< best
.overflow
){
2104 aroundCorner
: choice
.aroundCorner
,
2110 spaceAvailable
: spaceAvailable
2117 // In case the best position is not the last one we checked, need to call
2118 // layoutNode() again.
2119 if(best
.overflow
&& layoutNode
){
2120 layoutNode(node
, best
.aroundCorner
, best
.corner
, best
.spaceAvailable
, aroundNodeCoords
);
2123 // And then position the node. Do this last, after the layoutNode() above
2124 // has sized the node, due to browser quirks when the viewport is scrolled
2125 // (specifically that a Tooltip will shrink to fit as though the window was
2126 // scrolled to the left).
2128 // In RTL mode, set style.right rather than style.left so in the common case,
2129 // window resizes move the popup along with the aroundNode.
2130 var l
= dojo
._isBodyLtr(),
2132 s
.top
= best
.y
+ "px";
2133 s
[l
? "left" : "right"] = (l
? best
.x
: view
.w
- best
.x
- best
.w
) + "px";
2138 dijit
.placeOnScreenAroundNode = function(
2140 /* DomNode */ aroundNode
,
2141 /* Object */ aroundCorners
,
2142 /* Function? */ layoutNode
){
2145 // Position node adjacent or kitty-corner to aroundNode
2146 // such that it's fully visible in viewport.
2149 // Place node such that corner of node touches a corner of
2150 // aroundNode, and that node is fully visible.
2153 // Ordered list of pairs of corners to try matching up.
2154 // Each pair of corners is represented as a key/value in the hash,
2155 // where the key corresponds to the aroundNode's corner, and
2156 // the value corresponds to the node's corner:
2158 // | { aroundNodeCorner1: nodeCorner1, aroundNodeCorner2: nodeCorner2, ...}
2160 // The following strings are used to represent the four corners:
2161 // * "BL" - bottom left
2162 // * "BR" - bottom right
2163 // * "TL" - top left
2164 // * "TR" - top right
2166 // layoutNode: Function(node, aroundNodeCorner, nodeCorner)
2167 // For things like tooltip, they are displayed differently (and have different dimensions)
2168 // based on their orientation relative to the parent. This adjusts the popup based on orientation.
2171 // | dijit.placeOnScreenAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'});
2172 // This will try to position node such that node's top-left corner is at the same position
2173 // as the bottom left corner of the aroundNode (ie, put node below
2174 // aroundNode, with left edges aligned). If that fails it will try to put
2175 // the bottom-right corner of node where the top right corner of aroundNode is
2176 // (ie, put node above aroundNode, with right edges aligned)
2179 // get coordinates of aroundNode
2180 aroundNode
= dojo
.byId(aroundNode
);
2181 var aroundNodePos
= dojo
.position(aroundNode
, true);
2183 // place the node around the calculated rectangle
2184 return dijit
._placeOnScreenAroundRect(node
,
2185 aroundNodePos
.x
, aroundNodePos
.y
, aroundNodePos
.w
, aroundNodePos
.h
, // rectangle
2186 aroundCorners
, layoutNode
);
2190 dijit.__Rectangle = function(){
2192 // horizontal offset in pixels, relative to document body
2194 // vertical offset in pixels, relative to document body
2203 this.height = height;
2208 dijit
.placeOnScreenAroundRectangle = function(
2210 /* dijit.__Rectangle */ aroundRect
,
2211 /* Object */ aroundCorners
,
2212 /* Function */ layoutNode
){
2215 // Like dijit.placeOnScreenAroundNode(), except that the "around"
2216 // parameter is an arbitrary rectangle on the screen (x, y, width, height)
2217 // instead of a dom node.
2219 return dijit
._placeOnScreenAroundRect(node
,
2220 aroundRect
.x
, aroundRect
.y
, aroundRect
.width
, aroundRect
.height
, // rectangle
2221 aroundCorners
, layoutNode
);
2224 dijit
._placeOnScreenAroundRect = function(
2229 /* Number */ height
,
2230 /* Object */ aroundCorners
,
2231 /* Function */ layoutNode
){
2234 // Like dijit.placeOnScreenAroundNode(), except it accepts coordinates
2235 // of a rectangle to place node adjacent to.
2237 // TODO: combine with placeOnScreenAroundRectangle()
2239 // Generate list of possible positions for node
2241 for(var nodeCorner
in aroundCorners
){
2243 aroundCorner
: nodeCorner
,
2244 corner
: aroundCorners
[nodeCorner
],
2246 x
: x
+ (nodeCorner
.charAt(1) == 'L' ? 0 : width
),
2247 y
: y
+ (nodeCorner
.charAt(0) == 'T' ? 0 : height
)
2252 return dijit
._place(node
, choices
, layoutNode
, {w
: width
, h
: height
});
2255 dijit
.placementRegistry
= new dojo
.AdapterRegistry();
2256 dijit
.placementRegistry
.register("node",
2258 return typeof x
== "object" &&
2259 typeof x
.offsetWidth
!= "undefined" && typeof x
.offsetHeight
!= "undefined";
2261 dijit
.placeOnScreenAroundNode
);
2262 dijit
.placementRegistry
.register("rect",
2264 return typeof x
== "object" &&
2265 "x" in x
&& "y" in x
&& "width" in x
&& "height" in x
;
2267 dijit
.placeOnScreenAroundRectangle
);
2269 dijit
.placeOnScreenAroundElement = function(
2271 /* Object */ aroundElement
,
2272 /* Object */ aroundCorners
,
2273 /* Function */ layoutNode
){
2276 // Like dijit.placeOnScreenAroundNode(), except it accepts an arbitrary object
2277 // for the "around" argument and finds a proper processor to place a node.
2279 return dijit
.placementRegistry
.match
.apply(dijit
.placementRegistry
, arguments
);
2282 dijit
.getPopupAroundAlignment = function(/*Array*/ position
, /*Boolean*/ leftToRight
){
2284 // Transforms the passed array of preferred positions into a format suitable for passing as the aroundCorners argument to dijit.placeOnScreenAroundElement.
2286 // position: String[]
2287 // This variable controls the position of the drop down.
2288 // It's an array of strings with the following values:
2290 // * before: places drop down to the left of the target node/widget, or to the right in
2291 // the case of RTL scripts like Hebrew and Arabic
2292 // * after: places drop down to the right of the target node/widget, or to the left in
2293 // the case of RTL scripts like Hebrew and Arabic
2294 // * above: drop down goes above target node
2295 // * below: drop down goes below target node
2297 // The list is positions is tried, in order, until a position is found where the drop down fits
2298 // within the viewport.
2300 // leftToRight: Boolean
2301 // Whether the popup will be displaying in leftToRight mode.
2304 dojo
.forEach(position
, function(pos
){
2307 align
[leftToRight
? "BR" : "BL"] = leftToRight
? "BL" : "BR";
2310 align
[leftToRight
? "BL" : "BR"] = leftToRight
? "BR" : "BL";
2313 leftToRight
= !leftToRight
;
2316 // first try to align left borders, next try to align right borders (or reverse for RTL mode)
2317 align
[leftToRight
? "BL" : "BR"] = leftToRight
? "TL" : "TR";
2318 align
[leftToRight
? "BR" : "BL"] = leftToRight
? "TR" : "TL";
2321 leftToRight
= !leftToRight
;
2325 // first try to align left borders, next try to align right borders (or reverse for RTL mode)
2326 align
[leftToRight
? "TL" : "TR"] = leftToRight
? "BL" : "BR";
2327 align
[leftToRight
? "TR" : "TL"] = leftToRight
? "BR" : "BL";
2336 if(!dojo
._hasResource
["dijit._base.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2337 dojo
._hasResource
["dijit._base.window"] = true;
2338 dojo
.provide("dijit._base.window");
2342 dijit
.getDocumentWindow = function(doc
){
2343 return dojo
.window
.get(doc
);
2348 if(!dojo
._hasResource
["dijit._base.popup"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2349 dojo
._hasResource
["dijit._base.popup"] = true;
2350 dojo
.provide("dijit._base.popup");
2357 dijit.popup.__OpenArgs = function(){
2359 // widget to display
2361 // the button etc. that is displaying this popup
2363 // DOM node (typically a button); place popup relative to this node. (Specify this *or* "x" and "y" parameters.)
2365 // Absolute horizontal position (in pixels) to place node at. (Specify this *or* "around" parameter.)
2367 // Absolute vertical position (in pixels) to place node at. (Specify this *or* "around" parameter.)
2368 // orient: Object|String
2369 // When the around parameter is specified, orient should be an
2370 // ordered list of tuples of the form (around-node-corner, popup-node-corner).
2371 // dijit.popup.open() tries to position the popup according to each tuple in the list, in order,
2372 // until the popup appears fully within the viewport.
2374 // The default value is {BL:'TL', TL:'BL'}, which represents a list of two tuples:
2377 // where BL means "bottom left" and "TL" means "top left".
2378 // So by default, it first tries putting the popup below the around node, left-aligning them,
2379 // and then tries to put it above the around node, still left-aligning them. Note that the
2380 // default is horizontally reversed when in RTL mode.
2382 // When an (x,y) position is specified rather than an around node, orient is either
2383 // "R" or "L". R (for right) means that it tries to put the popup to the right of the mouse,
2384 // specifically positioning the popup's top-right corner at the mouse position, and if that doesn't
2385 // fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner,
2386 // and the top-right corner.
2387 // onCancel: Function
2388 // callback when user has canceled the popup by
2389 // 1. hitting ESC or
2390 // 2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog);
2391 // i.e. whenever popupWidget.onCancel() is called, args.onCancel is called
2392 // onClose: Function
2393 // callback whenever this popup is closed
2394 // onExecute: Function
2395 // callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only)
2396 // padding: dijit.__Position
2397 // adding a buffer around the opening position. This is only useful when around is not set.
2399 this.parent = parent;
2400 this.around = around;
2403 this.orient = orient;
2404 this.onCancel = onCancel;
2405 this.onClose = onClose;
2406 this.onExecute = onExecute;
2407 this.padding = padding;
2413 // This singleton is used to show/hide widgets as popups.
2415 // _stack: dijit._Widget[]
2416 // Stack of currently popped up widgets.
2417 // (someone opened _stack[0], and then it opened _stack[1], etc.)
2420 // _beginZIndex: Number
2421 // Z-index of the first popup. (If first popup opens other
2422 // popups they get a higher z-index.)
2427 _createWrapper: function(/*Widget || DomNode*/ widget
){
2429 // Initialization for widgets that will be used as popups.
2430 // Puts widget inside a wrapper DIV (if not already in one),
2431 // and returns pointer to that wrapper DIV.
2433 var wrapper
= widget
.declaredClass
? widget
._popupWrapper
: (widget
.parentNode
&& dojo
.hasClass(widget
.parentNode
, "dijitPopup")),
2434 node
= widget
.domNode
|| widget
;
2437 // Create wrapper <div> for when this widget [in the future] will be used as a popup.
2438 // This is done early because of IE bugs where creating/moving DOM nodes causes focus
2439 // to go wonky, see tests/robot/Toolbar.html to reproduce
2440 wrapper
= dojo
.create("div",{
2441 "class":"dijitPopup",
2442 style
:{ display
: "none"},
2443 role
: "presentation"
2445 wrapper
.appendChild(node
);
2453 if(widget
.declaredClass
){ // TODO: in 2.0 change signature to always take widget, then remove if()
2454 widget
._popupWrapper
= wrapper
;
2455 dojo
.connect(widget
, "destroy", function(){
2456 dojo
.destroy(wrapper
);
2457 delete widget
._popupWrapper
;
2465 moveOffScreen: function(/*Widget || DomNode*/ widget
){
2467 // Moves the popup widget off-screen.
2468 // Do not use this method to hide popups when not in use, because
2469 // that will create an accessibility issue: the offscreen popup is
2470 // still in the tabbing order.
2472 // Create wrapper if not already there
2473 var wrapper
= this._createWrapper(widget
);
2475 dojo
.style(wrapper
, {
2476 visibility
: "hidden",
2477 top
: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
2482 hide: function(/*dijit._Widget*/ widget
){
2484 // Hide this popup widget (until it is ready to be shown).
2485 // Initialization for widgets that will be used as popups
2487 // Also puts widget inside a wrapper DIV (if not already in one)
2489 // If popup widget needs to layout it should
2490 // do so when it is made visible, and popup._onShow() is called.
2492 // Create wrapper if not already there
2493 var wrapper
= this._createWrapper(widget
);
2495 dojo
.style(wrapper
, "display", "none");
2498 getTopPopup: function(){
2500 // Compute the closest ancestor popup that's *not* a child of another popup.
2501 // Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button.
2502 var stack
= this._stack
;
2503 for(var pi
=stack
.length
-1; pi
> 0 && stack
[pi
].parent
=== stack
[pi
-1].widget
; pi
--){
2504 /* do nothing, just trying to get right value for pi */
2509 open: function(/*dijit.popup.__OpenArgs*/ args
){
2511 // Popup the widget at the specified position
2514 // opening at the mouse position
2515 // | dijit.popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
2518 // opening the widget as a dropdown
2519 // | dijit.popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
2521 // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback
2522 // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed.
2524 var stack
= this._stack
,
2525 widget
= args
.popup
,
2526 orient
= args
.orient
|| (
2527 (args
.parent
? args
.parent
.isLeftToRight() : dojo
._isBodyLtr()) ?
2528 {'BL':'TL', 'BR':'TR', 'TL':'BL', 'TR':'BR'} :
2529 {'BR':'TR', 'BL':'TL', 'TR':'BR', 'TL':'BL'}
2531 around
= args
.around
,
2532 id
= (args
.around
&& args
.around
.id
) ? (args
.around
.id
+"_dropdown") : ("popup_"+this._idGen
++);
2534 // If we are opening a new popup that isn't a child of a currently opened popup, then
2535 // close currently opened popup(s). This should happen automatically when the old popups
2536 // gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198].
2537 while(stack
.length
&& (!args
.parent
|| !dojo
.isDescendant(args
.parent
.domNode
, stack
[stack
.length
-1].widget
.domNode
))){
2538 dijit
.popup
.close(stack
[stack
.length
-1].widget
);
2541 // Get pointer to popup wrapper, and create wrapper if it doesn't exist
2542 var wrapper
= this._createWrapper(widget
);
2545 dojo
.attr(wrapper
, {
2548 zIndex
: this._beginZIndex
+ stack
.length
2550 "class": "dijitPopup " + (widget
.baseClass
|| widget
["class"] || "").split(" ")[0] +"Popup",
2551 dijitPopupParent
: args
.parent
? args
.parent
.id
: ""
2554 if(dojo
.isIE
|| dojo
.isMoz
){
2555 if(!widget
.bgIframe
){
2556 // setting widget.bgIframe triggers cleanup in _Widget.destroy()
2557 widget
.bgIframe
= new dijit
.BackgroundIframe(wrapper
);
2561 // position the wrapper node and make it visible
2563 dijit
.placeOnScreenAroundElement(wrapper
, around
, orient
, widget
.orient
? dojo
.hitch(widget
, "orient") : null) :
2564 dijit
.placeOnScreen(wrapper
, args
, orient
== 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args
.padding
);
2566 wrapper
.style
.display
= "";
2567 wrapper
.style
.visibility
= "visible";
2568 widget
.domNode
.style
.visibility
= "visible"; // counteract effects from _HasDropDown
2572 // provide default escape and tab key handling
2573 // (this will work for any widget, not just menu)
2574 handlers
.push(dojo
.connect(wrapper
, "onkeypress", this, function(evt
){
2575 if(evt
.charOrCode
== dojo
.keys
.ESCAPE
&& args
.onCancel
){
2576 dojo
.stopEvent(evt
);
2578 }else if(evt
.charOrCode
=== dojo
.keys
.TAB
){
2579 dojo
.stopEvent(evt
);
2580 var topPopup
= this.getTopPopup();
2581 if(topPopup
&& topPopup
.onCancel
){
2582 topPopup
.onCancel();
2587 // watch for cancel/execute events on the popup and notify the caller
2588 // (for a menu, "execute" means clicking an item)
2589 if(widget
.onCancel
){
2590 handlers
.push(dojo
.connect(widget
, "onCancel", args
.onCancel
));
2593 handlers
.push(dojo
.connect(widget
, widget
.onExecute
? "onExecute" : "onChange", this, function(){
2594 var topPopup
= this.getTopPopup();
2595 if(topPopup
&& topPopup
.onExecute
){
2596 topPopup
.onExecute();
2602 parent
: args
.parent
,
2603 onExecute
: args
.onExecute
,
2604 onCancel
: args
.onCancel
,
2605 onClose
: args
.onClose
,
2610 // TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here)
2611 widget
.onOpen(best
);
2617 close: function(/*dijit._Widget?*/ popup
){
2619 // Close specified popup and any popups that it parented.
2620 // If no popup is specified, closes all popups.
2622 var stack
= this._stack
;
2624 // Basically work backwards from the top of the stack closing popups
2625 // until we hit the specified popup, but IIRC there was some issue where closing
2626 // a popup would cause others to close too. Thus if we are trying to close B in [A,B,C]
2627 // closing C might close B indirectly and then the while() condition will run where stack==[A]...
2628 // so the while condition is constructed defensively.
2629 while((popup
&& dojo
.some(stack
, function(elem
){return elem
.widget
== popup
;})) ||
2630 (!popup
&& stack
.length
)){
2631 var top
= stack
.pop(),
2632 widget
= top
.widget
,
2633 onClose
= top
.onClose
;
2636 // TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here)
2639 dojo
.forEach(top
.handlers
, dojo
.disconnect
);
2641 // Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc.
2642 if(widget
&& widget
.domNode
){
2653 // TODO: remove dijit._frames, it isn't being used much, since popups never release their
2654 // iframes (see [22236])
2655 dijit
._frames
= new function(){
2661 this.pop = function(){
2664 iframe
= queue
.pop();
2665 iframe
.style
.display
="";
2668 var burl
= dojo
.config
["dojoBlankHtmlUrl"] || (dojo
.moduleUrl("dojo", "resources/blank.html")+"") || "javascript:\"\"";
2669 var html
="<iframe src='" + burl
+ "'"
2670 + " style='position: absolute; left: 0px; top: 0px;"
2671 + "z-index: -1; filter:Alpha(Opacity=\"0\");'>";
2672 iframe
= dojo
.doc
.createElement(html
);
2674 iframe
= dojo
.create("iframe");
2675 iframe
.src
= 'javascript:""';
2676 iframe
.className
= "dijitBackgroundIframe";
2677 dojo
.style(iframe
, "opacity", 0.1);
2679 iframe
.tabIndex
= -1; // Magic to prevent iframe from getting focus on tab keypress - as style didn't work.
2680 dijit
.setWaiRole(iframe
,"presentation");
2685 this.push = function(iframe
){
2686 iframe
.style
.display
="none";
2692 dijit
.BackgroundIframe = function(/*DomNode*/ node
){
2694 // For IE/FF z-index schenanigans. id attribute is required.
2697 // new dijit.BackgroundIframe(node)
2698 // Makes a background iframe as a child of node, that fills
2699 // area (and position) of node
2701 if(!node
.id
){ throw new Error("no id"); }
2702 if(dojo
.isIE
|| dojo
.isMoz
){
2703 var iframe
= (this.iframe
= dijit
._frames
.pop());
2704 node
.appendChild(iframe
);
2705 if(dojo
.isIE
<7 || dojo
.isQuirks
){
2707 this._conn
= dojo
.connect(node
, 'onresize', this, function(){
2711 dojo
.style(iframe
, {
2719 dojo
.extend(dijit
.BackgroundIframe
, {
2720 resize: function(node
){
2722 // Resize the iframe so it's the same size as node.
2723 // Needed on IE6 and IE/quirks because height:100% doesn't work right.
2725 dojo
.style(this.iframe
, {
2726 width
: node
.offsetWidth
+ 'px',
2727 height
: node
.offsetHeight
+ 'px'
2731 destroy: function(){
2733 // destroy the iframe
2735 dojo
.disconnect(this._conn
);
2739 dijit
._frames
.push(this.iframe
);
2747 if(!dojo
._hasResource
["dijit._base.scroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2748 dojo
._hasResource
["dijit._base.scroll"] = true;
2749 dojo
.provide("dijit._base.scroll");
2753 dijit
.scrollIntoView = function(/*DomNode*/ node
, /*Object?*/ pos
){
2755 // Scroll the passed node into view, if it is not already.
2756 // Deprecated, use `dojo.window.scrollIntoView` instead.
2758 dojo
.window
.scrollIntoView(node
, pos
);
2763 if(!dojo
._hasResource
["dojo.uacss"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2764 dojo
._hasResource
["dojo.uacss"] = true;
2765 dojo
.provide("dojo.uacss");
2770 // Applies pre-set CSS classes to the top-level HTML node, based on:
2771 // - browser (ex: dj_ie)
2772 // - browser version (ex: dj_ie6)
2773 // - box model (ex: dj_contentBox)
2774 // - text direction (ex: dijitRtl)
2776 // In addition, browser, browser version, and box model are
2777 // combined with an RTL flag when browser text is RTL. ex: dj_ie-rtl.
2780 html
= d
.doc
.documentElement
,
2785 boxModel
= d
.boxModel
.replace(/-/,''),
2789 dj_ie6
: maj(ie
) == 6,
2790 dj_ie7
: maj(ie
) == 7,
2791 dj_ie8
: maj(ie
) == 8,
2792 dj_ie9
: maj(ie
) == 9,
2793 dj_quirks
: d
.isQuirks
,
2794 dj_iequirks
: ie
&& d
.isQuirks
,
2796 // NOTE: Opera not supported by dijit
2799 dj_khtml
: d
.isKhtml
,
2801 dj_webkit
: d
.isWebKit
,
2802 dj_safari
: d
.isSafari
,
2803 dj_chrome
: d
.isChrome
,
2805 dj_gecko
: d
.isMozilla
,
2806 dj_ff3
: maj(ff
) == 3
2807 }; // no dojo unsupported browsers
2809 classes
["dj_" + boxModel
] = true;
2811 // apply browser, browser version, and box model class names
2813 for(var clz
in classes
){
2815 classStr
+= clz
+ " ";
2818 html
.className
= d
.trim(html
.className
+ " " + classStr
);
2820 // If RTL mode, then add dj_rtl flag plus repeat existing classes with -rtl extension.
2821 // We can't run the code below until the <body> tag has loaded (so we can check for dir=rtl).
2822 // Unshift() is to run sniff code before the parser.
2823 dojo
._loaders
.unshift(function(){
2824 if(!dojo
._isBodyLtr()){
2825 var rtlClassStr
= "dj_rtl dijitRtl " + classStr
.replace(/ /g
, "-rtl ")
2826 html
.className
= d
.trim(html
.className
+ " " + rtlClassStr
);
2833 if(!dojo
._hasResource
["dijit._base.sniff"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2834 dojo
._hasResource
["dijit._base.sniff"] = true;
2835 dojo
.provide("dijit._base.sniff");
2840 // Applies pre-set CSS classes to the top-level HTML node, see
2841 // `dojo.uacss` for details.
2843 // Simply doing a require on this module will
2844 // establish this CSS. Modified version of Morris' CSS hack.
2848 if(!dojo
._hasResource
["dijit._base.typematic"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2849 dojo
._hasResource
["dijit._base.typematic"] = true;
2850 dojo
.provide("dijit._base.typematic");
2855 // These functions are used to repetitively call a user specified callback
2856 // method when a specific key or mouse click over a specific DOM node is
2857 // held down for a specific amount of time.
2858 // Only 1 such event is allowed to occur on the browser page at 1 time.
2860 _fireEventAndReload: function(){
2862 this._callback(++this._count
, this._node
, this._evt
);
2864 // Schedule next event, timer is at most minDelay (default 10ms) to avoid
2865 // browser overload (particularly avoiding starving DOH robot so it never gets to send a mouseup)
2866 this._currentTimeout
= Math
.max(
2867 this._currentTimeout
< 0 ? this._initialDelay
:
2868 (this._subsequentDelay
> 1 ? this._subsequentDelay
: Math
.round(this._currentTimeout
* this._subsequentDelay
)),
2870 this._timer
= setTimeout(dojo
.hitch(this, "_fireEventAndReload"), this._currentTimeout
);
2873 trigger: function(/*Event*/ evt
, /*Object*/ _this
, /*DOMNode*/ node
, /*Function*/ callback
, /*Object*/ obj
, /*Number*/ subsequentDelay
, /*Number*/ initialDelay
, /*Number?*/ minDelay
){
2875 // Start a timed, repeating callback sequence.
2876 // If already started, the function call is ignored.
2877 // This method is not normally called by the user but can be
2878 // when the normal listener code is insufficient.
2880 // key or mouse event object to pass to the user callback
2882 // pointer to the user's widget space.
2884 // the DOM node object to pass the the callback function
2886 // function to call until the sequence is stopped called with 3 parameters:
2888 // integer representing number of repeated calls (0..n) with -1 indicating the iteration has stopped
2890 // the DOM node object passed in
2892 // key or mouse event object
2894 // user space object used to uniquely identify each typematic sequence
2895 // subsequentDelay (optional):
2896 // if > 1, the number of milliseconds until the 3->n events occur
2897 // or else the fractional time multiplier for the next event's delay, default=0.9
2898 // initialDelay (optional):
2899 // the number of milliseconds until the 2nd event occurs, default=500ms
2900 // minDelay (optional):
2901 // the maximum delay in milliseconds for event to fire, default=10ms
2902 if(obj
!= this._obj
){
2904 this._initialDelay
= initialDelay
|| 500;
2905 this._subsequentDelay
= subsequentDelay
|| 0.90;
2906 this._minDelay
= minDelay
|| 10;
2910 this._currentTimeout
= -1;
2912 this._callback
= dojo
.hitch(_this
, callback
);
2913 this._fireEventAndReload();
2914 this._evt
= dojo
.mixin({faux
: true}, evt
);
2920 // Stop an ongoing timed, repeating callback sequence.
2922 clearTimeout(this._timer
);
2926 this._callback(-1, this._node
, this._evt
);
2931 addKeyListener: function(/*DOMNode*/ node
, /*Object*/ keyObject
, /*Object*/ _this
, /*Function*/ callback
, /*Number*/ subsequentDelay
, /*Number*/ initialDelay
, /*Number?*/ minDelay
){
2933 // Start listening for a specific typematic key.
2934 // See also the trigger method for other parameters.
2936 // an object defining the key to listen for:
2938 // the printable character (string) or keyCode (number) to listen for.
2940 // (deprecated - use charOrCode) the keyCode (number) to listen for (implies charCode = 0).
2942 // (deprecated - use charOrCode) the charCode (number) to listen for.
2944 // desired ctrl key state to initiate the callback sequence:
2946 // - released (false)
2947 // - either (unspecified)
2949 // same as ctrlKey but for the alt key
2951 // same as ctrlKey but for the shift key
2953 // an array of dojo.connect handles
2954 if(keyObject
.keyCode
){
2955 keyObject
.charOrCode
= keyObject
.keyCode
;
2956 dojo
.deprecated("keyCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0");
2957 }else if(keyObject
.charCode
){
2958 keyObject
.charOrCode
= String
.fromCharCode(keyObject
.charCode
);
2959 dojo
.deprecated("charCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0");
2962 dojo
.connect(node
, "onkeypress", this, function(evt
){
2963 if(evt
.charOrCode
== keyObject
.charOrCode
&&
2964 (keyObject
.ctrlKey
=== undefined || keyObject
.ctrlKey
== evt
.ctrlKey
) &&
2965 (keyObject
.altKey
=== undefined || keyObject
.altKey
== evt
.altKey
) &&
2966 (keyObject
.metaKey
=== undefined || keyObject
.metaKey
== (evt
.metaKey
|| false)) && // IE doesn't even set metaKey
2967 (keyObject
.shiftKey
=== undefined || keyObject
.shiftKey
== evt
.shiftKey
)){
2968 dojo
.stopEvent(evt
);
2969 dijit
.typematic
.trigger(evt
, _this
, node
, callback
, keyObject
, subsequentDelay
, initialDelay
, minDelay
);
2970 }else if(dijit
.typematic
._obj
== keyObject
){
2971 dijit
.typematic
.stop();
2974 dojo
.connect(node
, "onkeyup", this, function(evt
){
2975 if(dijit
.typematic
._obj
== keyObject
){
2976 dijit
.typematic
.stop();
2982 addMouseListener: function(/*DOMNode*/ node
, /*Object*/ _this
, /*Function*/ callback
, /*Number*/ subsequentDelay
, /*Number*/ initialDelay
, /*Number?*/ minDelay
){
2984 // Start listening for a typematic mouse click.
2985 // See the trigger method for other parameters.
2987 // an array of dojo.connect handles
2988 var dc
= dojo
.connect
;
2990 dc(node
, "mousedown", this, function(evt
){
2991 dojo
.stopEvent(evt
);
2992 dijit
.typematic
.trigger(evt
, _this
, node
, callback
, node
, subsequentDelay
, initialDelay
, minDelay
);
2994 dc(node
, "mouseup", this, function(evt
){
2995 dojo
.stopEvent(evt
);
2996 dijit
.typematic
.stop();
2998 dc(node
, "mouseout", this, function(evt
){
2999 dojo
.stopEvent(evt
);
3000 dijit
.typematic
.stop();
3002 dc(node
, "mousemove", this, function(evt
){
3003 evt
.preventDefault();
3005 dc(node
, "dblclick", this, function(evt
){
3006 dojo
.stopEvent(evt
);
3008 dijit
.typematic
.trigger(evt
, _this
, node
, callback
, node
, subsequentDelay
, initialDelay
, minDelay
);
3009 setTimeout(dojo
.hitch(this, dijit
.typematic
.stop
), 50);
3015 addListener: function(/*Node*/ mouseNode
, /*Node*/ keyNode
, /*Object*/ keyObject
, /*Object*/ _this
, /*Function*/ callback
, /*Number*/ subsequentDelay
, /*Number*/ initialDelay
, /*Number?*/ minDelay
){
3017 // Start listening for a specific typematic key and mouseclick.
3018 // This is a thin wrapper to addKeyListener and addMouseListener.
3019 // See the addMouseListener and addKeyListener methods for other parameters.
3021 // the DOM node object to listen on for mouse events.
3023 // the DOM node object to listen on for key events.
3025 // an array of dojo.connect handles
3026 return this.addKeyListener(keyNode
, keyObject
, _this
, callback
, subsequentDelay
, initialDelay
, minDelay
).concat(
3027 this.addMouseListener(mouseNode
, _this
, callback
, subsequentDelay
, initialDelay
, minDelay
));
3033 if(!dojo
._hasResource
["dijit._base.wai"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3034 dojo
._hasResource
["dijit._base.wai"] = true;
3035 dojo
.provide("dijit._base.wai");
3041 // Detects if we are in high-contrast mode or not
3043 // This must be a named function and not an anonymous
3044 // function, so that the widget parsing code can make sure it
3045 // registers its onload function after this function.
3046 // DO NOT USE "this" within this function.
3048 // create div for testing if high contrast mode is on or images are turned off
3049 var div
= dojo
.create("div",{
3052 cssText
:'border: 1px solid;'
3053 + 'border-color:red green;'
3054 + 'position: absolute;'
3057 + 'background-image: url("' + (dojo
.config
.blankGif
|| dojo
.moduleUrl("dojo", "resources/blank.gif")) + '");'
3062 var cs
= dojo
.getComputedStyle(div
);
3064 var bkImg
= cs
.backgroundImage
;
3065 var needsA11y
= (cs
.borderTopColor
== cs
.borderRightColor
) || (bkImg
!= null && (bkImg
== "none" || bkImg
== "url(invalid-url:)" ));
3066 dojo
[needsA11y
? "addClass" : "removeClass"](dojo
.body(), "dijit_a11y");
3068 div
.outerHTML
= ""; // prevent mixed-content warning, see http://support.microsoft.com/kb/925014
3070 dojo
.body().removeChild(div
);
3076 // Test if computer is in high contrast mode.
3077 // Make sure the a11y test runs first, before widgets are instantiated.
3078 if(dojo
.isIE
|| dojo
.isMoz
){ // NOTE: checking in Safari messes things up
3079 dojo
._loaders
.unshift(dijit
.wai
.onload
);
3083 hasWaiRole: function(/*Element*/ elem
, /*String?*/ role
){
3085 // Determines if an element has a particular role.
3087 // True if elem has the specific role attribute and false if not.
3088 // For backwards compatibility if role parameter not provided,
3089 // returns true if has a role
3090 var waiRole
= this.getWaiRole(elem
);
3091 return role
? (waiRole
.indexOf(role
) > -1) : (waiRole
.length
> 0);
3094 getWaiRole: function(/*Element*/ elem
){
3096 // Gets the role for an element (which should be a wai role).
3098 // The role of elem or an empty string if elem
3099 // does not have a role.
3100 return dojo
.trim((dojo
.attr(elem
, "role") || "").replace("wairole:",""));
3103 setWaiRole: function(/*Element*/ elem
, /*String*/ role
){
3105 // Sets the role on an element.
3107 // Replace existing role attribute with new role.
3109 dojo
.attr(elem
, "role", role
);
3112 removeWaiRole: function(/*Element*/ elem
, /*String*/ role
){
3114 // Removes the specified role from an element.
3115 // Removes role attribute if no specific role provided (for backwards compat.)
3117 var roleValue
= dojo
.attr(elem
, "role");
3118 if(!roleValue
){ return; }
3120 var t
= dojo
.trim((" " + roleValue
+ " ").replace(" " + role
+ " ", " "));
3121 dojo
.attr(elem
, "role", t
);
3123 elem
.removeAttribute("role");
3127 hasWaiState: function(/*Element*/ elem
, /*String*/ state
){
3129 // Determines if an element has a given state.
3131 // Checks for an attribute called "aria-"+state.
3133 // true if elem has a value for the given state and
3134 // false if it does not.
3136 return elem
.hasAttribute
? elem
.hasAttribute("aria-"+state
) : !!elem
.getAttribute("aria-"+state
);
3139 getWaiState: function(/*Element*/ elem
, /*String*/ state
){
3141 // Gets the value of a state on an element.
3143 // Checks for an attribute called "aria-"+state.
3145 // The value of the requested state on elem
3146 // or an empty string if elem has no value for state.
3148 return elem
.getAttribute("aria-"+state
) || "";
3151 setWaiState: function(/*Element*/ elem
, /*String*/ state
, /*String*/ value
){
3153 // Sets a state on an element.
3155 // Sets an attribute called "aria-"+state.
3157 elem
.setAttribute("aria-"+state
, value
);
3160 removeWaiState: function(/*Element*/ elem
, /*String*/ state
){
3162 // Removes a state from an element.
3164 // Sets an attribute called "aria-"+state.
3166 elem
.removeAttribute("aria-"+state
);
3172 if(!dojo
._hasResource
["dijit._base"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3173 dojo
._hasResource
["dijit._base"] = true;
3174 dojo
.provide("dijit._base");
3189 if(!dojo
._hasResource
["dojo.Stateful"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3190 dojo
._hasResource
["dojo.Stateful"] = true;
3191 dojo
.provide("dojo.Stateful");
3194 dojo
.declare("dojo.Stateful", null, {
3196 // Base class for objects that provide named properties with optional getter/setter
3197 // control and the ability to watch for property changes
3199 // | var obj = new dojo.Stateful();
3200 // | obj.watch("foo", function(){
3201 // | console.log("foo changed to " + this.get("foo"));
3203 // | obj.set("foo","bar");
3204 postscript: function(mixin
){
3206 dojo
.mixin(this, mixin
);
3210 get: function(/*String*/name
){
3212 // Get a property on a Stateful instance.
3214 // The property to get.
3216 // Get a named property on a Stateful object. The property may
3217 // potentially be retrieved via a getter method in subclasses. In the base class
3218 // this just retrieves the object's property.
3220 // | stateful = new dojo.Stateful({foo: 3});
3221 // | stateful.get("foo") // returns 3
3222 // | stateful.foo // returns 3
3226 set: function(/*String*/name
, /*Object*/value
){
3228 // Set a property on a Stateful instance
3230 // The property to set.
3232 // The value to set in the property.
3234 // Sets named properties on a stateful object and notifies any watchers of
3235 // the property. A programmatic setter may be defined in subclasses.
3237 // | stateful = new dojo.Stateful();
3238 // | stateful.watch(function(name, oldValue, value){
3239 // | // this will be called on the set below
3241 // | stateful.set(foo, 5);
3243 // set() may also be called with a hash of name/value pairs, ex:
3248 // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
3249 if(typeof name
=== "object"){
3251 this.set(x
, name
[x
]);
3255 var oldValue
= this[name
];
3257 if(this._watchCallbacks
){
3258 this._watchCallbacks(name
, oldValue
, value
);
3262 watch: function(/*String?*/name
, /*Function*/callback
){
3264 // Watches a property for changes
3266 // Indicates the property to watch. This is optional (the callback may be the
3267 // only parameter), and if omitted, all the properties will be watched
3269 // An object handle for the watch. The unwatch method of this object
3270 // can be used to discontinue watching this property:
3271 // | var watchHandle = obj.watch("foo", callback);
3272 // | watchHandle.unwatch(); // callback won't be called now
3274 // The function to execute when the property changes. This will be called after
3275 // the property has been changed. The callback will be called with the |this|
3276 // set to the instance, the first argument as the name of the property, the
3277 // second argument as the old value and the third argument as the new value.
3279 var callbacks
= this._watchCallbacks
;
3282 callbacks
= this._watchCallbacks = function(name
, oldValue
, value
, ignoreCatchall
){
3283 var notify = function(propertyCallbacks
){
3284 if(propertyCallbacks
){
3285 propertyCallbacks
= propertyCallbacks
.slice();
3286 for(var i
= 0, l
= propertyCallbacks
.length
; i
< l
; i
++){
3288 propertyCallbacks
[i
].call(self
, name
, oldValue
, value
);
3295 notify(callbacks
['_' + name
]);
3296 if(!ignoreCatchall
){
3297 notify(callbacks
["*"]); // the catch-all
3299 }; // we use a function instead of an object so it will be ignored by JSON conversion
3301 if(!callback
&& typeof name
=== "function"){
3305 // prepend with dash to prevent name conflicts with function (like "name" property)
3308 var propertyCallbacks
= callbacks
[name
];
3309 if(typeof propertyCallbacks
!== "object"){
3310 propertyCallbacks
= callbacks
[name
] = [];
3312 propertyCallbacks
.push(callback
);
3314 unwatch: function(){
3315 propertyCallbacks
.splice(dojo
.indexOf(propertyCallbacks
, callback
), 1);
3324 if(!dojo
._hasResource
["dijit._WidgetBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3325 dojo
._hasResource
["dijit._WidgetBase"] = true;
3326 dojo
.provide("dijit._WidgetBase");
3333 dojo
.declare("dijit._WidgetBase", dojo
.Stateful
, {
3335 // Future base class for all Dijit widgets.
3336 // _Widget extends this class adding support for various features needed by desktop.
3338 // id: [const] String
3339 // A unique, opaque ID string that can be assigned by users or by the
3340 // system. If the developer passes an ID which is known not to be
3341 // unique, the specified ID is ignored and the system-generated ID is
3345 // lang: [const] String
3346 // Rarely used. Overrides the default Dojo locale used to render this widget,
3347 // as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute.
3348 // Value must be among the list of locales specified during by the Dojo bootstrap,
3349 // formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us).
3352 // dir: [const] String
3353 // Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir)
3354 // attribute. Either left-to-right "ltr" or right-to-left "rtl". If undefined, widgets renders in page's
3355 // default direction.
3359 // HTML class attribute
3362 // style: String||Object
3363 // HTML style attributes as cssText string or name/value hash
3367 // HTML title attribute.
3369 // For form widgets this specifies a tooltip to display when hovering over
3370 // the widget (just like the native HTML title attribute).
3372 // For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer,
3373 // etc., it's used to specify the tab label, accordion pane title, etc.
3377 // When this widget's title attribute is used to for a tab label, accordion pane title, etc.,
3378 // this specifies the tooltip to appear when the mouse is hovered over that text.
3381 // baseClass: [protected] String
3382 // Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate
3386 // srcNodeRef: [readonly] DomNode
3387 // pointer to original DOM node
3390 // domNode: [readonly] DomNode
3391 // This is our visible representation of the widget! Other DOM
3392 // Nodes may by assigned to other properties, usually through the
3393 // template system's dojoAttachPoint syntax, but the domNode
3394 // property is the canonical "top level" node in widget UI.
3397 // containerNode: [readonly] DomNode
3398 // Designates where children of the source DOM node will be placed.
3399 // "Children" in this case refers to both DOM nodes and widgets.
3400 // For example, for myWidget:
3402 // | <div dojoType=myWidget>
3403 // | <b> here's a plain DOM node
3404 // | <span dojoType=subWidget>and a widget</span>
3405 // | <i> and another plain DOM node </i>
3408 // containerNode would point to:
3410 // | <b> here's a plain DOM node
3411 // | <span dojoType=subWidget>and a widget</span>
3412 // | <i> and another plain DOM node </i>
3414 // In templated widgets, "containerNode" is set via a
3415 // dojoAttachPoint assignment.
3417 // containerNode must be defined for any widget that accepts innerHTML
3418 // (like ContentPane or BorderContainer or even Button), and conversely
3419 // is null for widgets that don't, like TextBox.
3420 containerNode
: null,
3423 // _started: Boolean
3424 // startup() has completed.
3428 // attributeMap: [protected] Object
3429 // attributeMap sets up a "binding" between attributes (aka properties)
3430 // of the widget and the widget's DOM.
3431 // Changes to widget attributes listed in attributeMap will be
3432 // reflected into the DOM.
3434 // For example, calling set('title', 'hello')
3435 // on a TitlePane will automatically cause the TitlePane's DOM to update
3436 // with the new title.
3438 // attributeMap is a hash where the key is an attribute of the widget,
3439 // and the value reflects a binding to a:
3441 // - DOM node attribute
3442 // | focus: {node: "focusNode", type: "attribute"}
3443 // Maps this.focus to this.focusNode.focus
3445 // - DOM node innerHTML
3446 // | title: { node: "titleNode", type: "innerHTML" }
3447 // Maps this.title to this.titleNode.innerHTML
3449 // - DOM node innerText
3450 // | title: { node: "titleNode", type: "innerText" }
3451 // Maps this.title to this.titleNode.innerText
3453 // - DOM node CSS class
3454 // | myClass: { node: "domNode", type: "class" }
3455 // Maps this.myClass to this.domNode.className
3457 // If the value is an array, then each element in the array matches one of the
3458 // formats of the above list.
3460 // There are also some shorthands for backwards compatibility:
3461 // - string --> { node: string, type: "attribute" }, for example:
3462 // | "focusNode" ---> { node: "focusNode", type: "attribute" }
3463 // - "" --> { node: "domNode", type: "attribute" }
3464 attributeMap
: {id
:"", dir
:"", lang
:"", "class":"", style
:"", title
:""},
3466 // _blankGif: [protected] String
3467 // Path to a blank 1x1 image.
3468 // Used by <img> nodes in templates that really get their image via CSS background-image.
3469 _blankGif
: (dojo
.config
.blankGif
|| dojo
.moduleUrl("dojo", "resources/blank.gif")).toString(),
3471 //////////// INITIALIZATION METHODS ///////////////////////////////////////
3473 postscript: function(/*Object?*/params
, /*DomNode|String*/srcNodeRef
){
3475 // Kicks off widget instantiation. See create() for details.
3478 this.create(params
, srcNodeRef
);
3481 create: function(/*Object?*/params
, /*DomNode|String?*/srcNodeRef
){
3483 // Kick off the life-cycle of a widget
3485 // Hash of initialization parameters for widget, including
3486 // scalar values (like title, duration etc.) and functions,
3487 // typically callbacks like onClick.
3489 // If a srcNodeRef (DOM node) is specified:
3490 // - use srcNodeRef.innerHTML as my contents
3491 // - if this is a behavioral widget then apply behavior
3492 // to that srcNodeRef
3493 // - otherwise, replace srcNodeRef with my generated DOM
3496 // Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate,
3497 // etc.), some of which of you'll want to override. See http://docs.dojocampus.org/dijit/_Widget
3498 // for a discussion of the widget creation lifecycle.
3500 // Of course, adventurous developers could override create entirely, but this should
3501 // only be done as a last resort.
3505 // store pointer to original DOM tree
3506 this.srcNodeRef
= dojo
.byId(srcNodeRef
);
3508 // For garbage collection. An array of handles returned by Widget.connect()
3509 // Each handle returned from Widget.connect() is an array of handles from dojo.connect()
3510 this._connects
= [];
3512 // For garbage collection. An array of handles returned by Widget.subscribe()
3513 // The handle returned from Widget.subscribe() is the handle returned from dojo.subscribe()
3514 this._subscribes
= [];
3516 // mix in our passed parameters
3517 if(this.srcNodeRef
&& (typeof this.srcNodeRef
.id
== "string")){ this.id
= this.srcNodeRef
.id
; }
3519 this.params
= params
;
3520 dojo
._mixin(this, params
);
3522 this.postMixInProperties();
3524 // generate an id for the widget if one wasn't specified
3525 // (be sure to do this before buildRendering() because that function might
3526 // expect the id to be there.)
3528 this.id
= dijit
.getUniqueId(this.declaredClass
.replace(/\./g,"_"));
3530 dijit
.registry
.add(this);
3532 this.buildRendering();
3535 // Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
3536 // Also calls custom setters for all attributes with custom setters.
3537 this._applyAttributes();
3539 // If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree.
3540 // For 2.0, move this after postCreate(). postCreate() shouldn't depend on the
3541 // widget being attached to the DOM since it isn't when a widget is created programmatically like
3542 // new MyWidget({}). See #11635.
3543 var source
= this.srcNodeRef
;
3544 if(source
&& source
.parentNode
&& this.domNode
!== source
){
3545 source
.parentNode
.replaceChild(this.domNode
, source
);
3550 // Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId",
3551 // assuming that dojo._scopeName even exists in 2.0
3552 this.domNode
.setAttribute("widgetId", this.id
);
3556 // If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC.
3557 if(this.srcNodeRef
&& !this.srcNodeRef
.parentNode
){
3558 delete this.srcNodeRef
;
3561 this._created
= true;
3564 _applyAttributes: function(){
3566 // Step during widget creation to copy all widget attributes to the
3567 // DOM as per attributeMap and _setXXXAttr functions.
3569 // Skips over blank/false attribute values, unless they were explicitly specified
3570 // as parameters to the widget, since those are the default anyway,
3571 // and setting tabIndex="" is different than not setting tabIndex at all.
3573 // It processes the attributes in the attribute map first, and then
3574 // it goes through and processes the attributes for the _setXXXAttr
3575 // functions that have been specified
3578 var condAttrApply = function(attr
, scope
){
3579 if((scope
.params
&& attr
in scope
.params
) || scope
[attr
]){
3580 scope
.set(attr
, scope
[attr
]);
3584 // Do the attributes in attributeMap
3585 for(var attr
in this.attributeMap
){
3586 condAttrApply(attr
, this);
3589 // And also any attributes with custom setters
3590 dojo
.forEach(this._getSetterAttributes(), function(a
){
3591 if(!(a
in this.attributeMap
)){
3592 condAttrApply(a
, this);
3597 _getSetterAttributes: function(){
3599 // Returns list of attributes with custom setters for this widget
3600 var ctor
= this.constructor;
3601 if(!ctor
._setterAttrs
){
3602 var r
= (ctor
._setterAttrs
= []),
3604 proto
= ctor
.prototype;
3605 for(var fxName
in proto
){
3606 if(dojo
.isFunction(proto
[fxName
]) && (attrs
= fxName
.match(/^_set([a-zA-Z]*)Attr$/)) && attrs
[1]){
3607 r
.push(attrs
[1].charAt(0).toLowerCase() + attrs
[1].substr(1));
3611 return ctor
._setterAttrs
; // String[]
3614 postMixInProperties: function(){
3616 // Called after the parameters to the widget have been read-in,
3617 // but before the widget template is instantiated. Especially
3618 // useful to set properties that are referenced in the widget
3624 buildRendering: function(){
3626 // Construct the UI for this widget, setting this.domNode
3628 // Most widgets will mixin `dijit._Templated`, which implements this
3634 // Create root node if it wasn't created by _Templated
3635 this.domNode
= this.srcNodeRef
|| dojo
.create('div');
3638 // baseClass is a single class name or occasionally a space-separated list of names.
3639 // Add those classes to the DOMNode. If RTL mode then also add with Rtl suffix.
3640 // TODO: make baseClass custom setter
3642 var classes
= this.baseClass
.split(" ");
3643 if(!this.isLeftToRight()){
3644 classes
= classes
.concat( dojo
.map(classes
, function(name
){ return name
+"Rtl"; }));
3646 dojo
.addClass(this.domNode
, classes
);
3650 postCreate: function(){
3652 // Processing after the DOM fragment is created
3654 // Called after the DOM fragment has been created, but not necessarily
3655 // added to the document. Do not include any operations which rely on
3656 // node dimensions or placement.
3661 startup: function(){
3663 // Processing after the DOM fragment is added to the document
3665 // Called after a widget and its children have been created and added to the page,
3666 // and all related widgets have finished their create() cycle, up through postCreate().
3667 // This is useful for composite widgets that need to control or layout sub-widgets.
3668 // Many layout widgets can use this as a wiring phase.
3669 this._started
= true;
3672 //////////// DESTROY FUNCTIONS ////////////////////////////////
3674 destroyRecursive: function(/*Boolean?*/ preserveDom
){
3676 // Destroy this widget and its descendants
3678 // This is the generic "destructor" function that all widget users
3679 // should call to cleanly discard with a widget. Once a widget is
3680 // destroyed, it is removed from the manager object.
3682 // If true, this method will leave the original DOM structure
3683 // alone of descendant Widgets. Note: This will NOT work with
3684 // dijit._Templated widgets.
3686 this._beingDestroyed
= true;
3687 this.destroyDescendants(preserveDom
);
3688 this.destroy(preserveDom
);
3691 destroy: function(/*Boolean*/ preserveDom
){
3693 // Destroy this widget, but not its descendants.
3694 // This method will, however, destroy internal widgets such as those used within a template.
3695 // preserveDom: Boolean
3696 // If true, this method will leave the original DOM structure alone.
3697 // Note: This will not yet work with _Templated widgets
3699 this._beingDestroyed
= true;
3700 this.uninitialize();
3703 dun
= d
.unsubscribe
;
3704 dfe(this._connects
, function(array
){
3705 dfe(array
, d
.disconnect
);
3707 dfe(this._subscribes
, function(handle
){
3711 // destroy widgets created as part of template, etc.
3712 dfe(this._supportingWidgets
|| [], function(w
){
3713 if(w
.destroyRecursive
){
3714 w
.destroyRecursive();
3715 }else if(w
.destroy
){
3720 this.destroyRendering(preserveDom
);
3721 dijit
.registry
.remove(this.id
);
3722 this._destroyed
= true;
3725 destroyRendering: function(/*Boolean?*/ preserveDom
){
3727 // Destroys the DOM nodes associated with this widget
3729 // If true, this method will leave the original DOM structure alone
3730 // during tear-down. Note: this will not work with _Templated
3736 this.bgIframe
.destroy(preserveDom
);
3737 delete this.bgIframe
;
3742 dojo
.removeAttr(this.domNode
, "widgetId");
3744 dojo
.destroy(this.domNode
);
3746 delete this.domNode
;
3749 if(this.srcNodeRef
){
3751 dojo
.destroy(this.srcNodeRef
);
3753 delete this.srcNodeRef
;
3757 destroyDescendants: function(/*Boolean?*/ preserveDom
){
3759 // Recursively destroy the children of this widget and their
3762 // If true, the preserveDom attribute is passed to all descendant
3763 // widget's .destroy() method. Not for use with _Templated
3766 // get all direct descendants and destroy them recursively
3767 dojo
.forEach(this.getChildren(), function(widget
){
3768 if(widget
.destroyRecursive
){
3769 widget
.destroyRecursive(preserveDom
);
3774 uninitialize: function(){
3776 // Stub function. Override to implement custom widget tear-down
3783 ////////////////// GET/SET, CUSTOM SETTERS, ETC. ///////////////////
3785 _setClassAttr: function(/*String*/ value
){
3787 // Custom setter for the CSS "class" attribute
3790 var mapNode
= this[this.attributeMap
["class"] || 'domNode'];
3791 dojo
.replaceClass(mapNode
, value
, this["class"]);
3792 this._set("class", value
);
3795 _setStyleAttr: function(/*String||Object*/ value
){
3797 // Sets the style attribute of the widget according to value,
3798 // which is either a hash like {height: "5px", width: "3px"}
3799 // or a plain string
3801 // Determines which node to set the style on based on style setting
3806 var mapNode
= this[this.attributeMap
.style
|| 'domNode'];
3808 // Note: technically we should revert any style setting made in a previous call
3809 // to his method, but that's difficult to keep track of.
3811 if(dojo
.isObject(value
)){
3812 dojo
.style(mapNode
, value
);
3814 if(mapNode
.style
.cssText
){
3815 mapNode
.style
.cssText
+= "; " + value
;
3817 mapNode
.style
.cssText
= value
;
3821 this._set("style", value
);
3824 _attrToDom: function(/*String*/ attr
, /*String*/ value
){
3826 // Reflect a widget attribute (title, tabIndex, duration etc.) to
3827 // the widget DOM, as specified in attributeMap.
3828 // Note some attributes like "type"
3829 // cannot be processed this way as they are not mutable.
3834 var commands
= this.attributeMap
[attr
];
3835 dojo
.forEach(dojo
.isArray(commands
) ? commands
: [commands
], function(command
){
3837 // Get target node and what we are doing to that node
3838 var mapNode
= this[command
.node
|| command
|| "domNode"]; // DOM node
3839 var type
= command
.type
|| "attribute"; // class, innerHTML, innerText, or attribute
3843 if(dojo
.isFunction(value
)){ // functions execute in the context of the widget
3844 value
= dojo
.hitch(this, value
);
3847 // Get the name of the DOM node attribute; usually it's the same
3848 // as the name of the attribute in the widget (attr), but can be overridden.
3849 // Also maps handler names to lowercase, like onSubmit --> onsubmit
3850 var attrName
= command
.attribute
? command
.attribute
:
3851 (/^on[A-Z][a-zA-Z]*$/.test(attr
) ? attr
.toLowerCase() : attr
);
3853 dojo
.attr(mapNode
, attrName
, value
);
3856 mapNode
.innerHTML
= "";
3857 mapNode
.appendChild(dojo
.doc
.createTextNode(value
));
3860 mapNode
.innerHTML
= value
;
3863 dojo
.replaceClass(mapNode
, value
, this[attr
]);
3869 get: function(name
){
3871 // Get a property from a widget.
3873 // The property to get.
3875 // Get a named property from a widget. The property may
3876 // potentially be retrieved via a getter method. If no getter is defined, this
3877 // just retrieves the object's property.
3878 // For example, if the widget has a properties "foo"
3879 // and "bar" and a method named "_getFooAttr", calling:
3880 // | myWidget.get("foo");
3881 // would be equivalent to writing:
3882 // | widget._getFooAttr();
3884 // | myWidget.get("bar");
3885 // would be equivalent to writing:
3887 var names
= this._getAttrNames(name
);
3888 return this[names
.g
] ? this[names
.g
]() : this[name
];
3891 set: function(name
, value
){
3893 // Set a property on a widget
3895 // The property to set.
3897 // The value to set in the property.
3899 // Sets named properties on a widget which may potentially be handled by a
3900 // setter in the widget.
3901 // For example, if the widget has a properties "foo"
3902 // and "bar" and a method named "_setFooAttr", calling:
3903 // | myWidget.set("foo", "Howdy!");
3904 // would be equivalent to writing:
3905 // | widget._setFooAttr("Howdy!");
3907 // | myWidget.set("bar", 3);
3908 // would be equivalent to writing:
3909 // | widget.bar = 3;
3911 // set() may also be called with a hash of name/value pairs, ex:
3916 // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
3918 if(typeof name
=== "object"){
3920 this.set(x
, name
[x
]);
3924 var names
= this._getAttrNames(name
);
3926 // use the explicit setter
3927 var result
= this[names
.s
].apply(this, Array
.prototype.slice
.call(arguments
, 1));
3929 // if param is specified as DOM node attribute, copy it
3930 if(name
in this.attributeMap
){
3931 this._attrToDom(name
, value
);
3933 this._set(name
, value
);
3935 return result
|| this;
3938 _attrPairNames
: {}, // shared between all widgets
3939 _getAttrNames: function(name
){
3941 // Helper function for get() and set().
3942 // Caches attribute name values so we don't do the string ops every time.
3946 var apn
= this._attrPairNames
;
3947 if(apn
[name
]){ return apn
[name
]; }
3948 var uc
= name
.charAt(0).toUpperCase() + name
.substr(1);
3949 return (apn
[name
] = {
3951 s
: "_set"+uc
+"Attr",
3956 _set: function(/*String*/ name
, /*anything*/ value
){
3958 // Helper function to set new value for specified attribute, and call handlers
3959 // registered with watch() if the value has changed.
3960 var oldValue
= this[name
];
3962 if(this._watchCallbacks
&& this._created
&& value
!== oldValue
){
3963 this._watchCallbacks(name
, oldValue
, value
);
3967 toString: function(){
3969 // Returns a string that represents the widget
3971 // When a widget is cast to a string, this method will be used to generate the
3972 // output. Currently, it does not implement any sort of reversible
3974 return '[Widget ' + this.declaredClass
+ ', ' + (this.id
|| 'NO ID') + ']'; // String
3977 getDescendants: function(){
3979 // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
3980 // This method should generally be avoided as it returns widgets declared in templates, which are
3981 // supposed to be internal/hidden, but it's left here for back-compat reasons.
3983 return this.containerNode
? dojo
.query('[widgetId]', this.containerNode
).map(dijit
.byNode
) : []; // dijit._Widget[]
3986 getChildren: function(){
3988 // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
3989 // Does not return nested widgets, nor widgets that are part of this widget's template.
3990 return this.containerNode
? dijit
.findWidgets(this.containerNode
) : []; // dijit._Widget[]
3994 /*Object|null*/ obj
,
3995 /*String|Function*/ event
,
3996 /*String|Function*/ method
){
3998 // Connects specified obj/event to specified method of this object
3999 // and registers for disconnect() on widget destroy.
4001 // Provide widget-specific analog to dojo.connect, except with the
4002 // implicit use of this widget as the target object.
4003 // Events connected with `this.connect` are disconnected upon
4006 // A handle that can be passed to `disconnect` in order to disconnect before
4007 // the widget is destroyed.
4009 // | var btn = new dijit.form.Button();
4010 // | // when foo.bar() is called, call the listener we're going to
4011 // | // provide in the scope of btn
4012 // | btn.connect(foo, "bar", function(){
4013 // | console.debug(this.toString());
4018 var handles
= [dojo
._connect(obj
, event
, this, method
)];
4019 this._connects
.push(handles
);
4020 return handles
; // _Widget.Handle
4023 disconnect: function(/* _Widget.Handle */ handles
){
4025 // Disconnects handle created by `connect`.
4026 // Also removes handle from this widget's list of connects.
4029 for(var i
=0; i
<this._connects
.length
; i
++){
4030 if(this._connects
[i
] == handles
){
4031 dojo
.forEach(handles
, dojo
.disconnect
);
4032 this._connects
.splice(i
, 1);
4038 subscribe: function(
4040 /*String|Function*/ method
){
4042 // Subscribes to the specified topic and calls the specified method
4043 // of this object and registers for unsubscribe() on widget destroy.
4045 // Provide widget-specific analog to dojo.subscribe, except with the
4046 // implicit use of this widget as the target object.
4048 // | var btn = new dijit.form.Button();
4049 // | // when /my/topic is published, this button changes its label to
4050 // | // be the parameter of the topic.
4051 // | btn.subscribe("/my/topic", function(v){
4052 // | this.set("label", v);
4054 var handle
= dojo
.subscribe(topic
, this, method
);
4056 // return handles for Any widget that may need them
4057 this._subscribes
.push(handle
);
4061 unsubscribe: function(/*Object*/ handle
){
4063 // Unsubscribes handle created by this.subscribe.
4064 // Also removes handle from this widget's list of subscriptions
4065 for(var i
=0; i
<this._subscribes
.length
; i
++){
4066 if(this._subscribes
[i
] == handle
){
4067 dojo
.unsubscribe(handle
);
4068 this._subscribes
.splice(i
, 1);
4074 isLeftToRight: function(){
4076 // Return this widget's explicit or implicit orientation (true for LTR, false for RTL)
4079 return this.dir
? (this.dir
== "ltr") : dojo
._isBodyLtr(); //Boolean
4082 placeAt: function(/* String|DomNode|_Widget */reference
, /* String?|Int? */position
){
4084 // Place this widget's domNode reference somewhere in the DOM based
4085 // on standard dojo.place conventions, or passing a Widget reference that
4086 // contains and addChild member.
4089 // A convenience function provided in all _Widgets, providing a simple
4090 // shorthand mechanism to put an existing (or newly created) Widget
4091 // somewhere in the dom, and allow chaining.
4094 // The String id of a domNode, a domNode reference, or a reference to a Widget posessing
4095 // an addChild method.
4098 // If passed a string or domNode reference, the position argument
4099 // accepts a string just as dojo.place does, one of: "first", "last",
4100 // "before", or "after".
4102 // If passed a _Widget reference, and that widget reference has an ".addChild" method,
4103 // it will be called passing this widget instance into that method, supplying the optional
4104 // position index passed.
4108 // Provides a useful return of the newly created dijit._Widget instance so you
4109 // can "chain" this function by instantiating, placing, then saving the return value
4113 // | // create a Button with no srcNodeRef, and place it in the body:
4114 // | var button = new dijit.form.Button({ label:"click" }).placeAt(dojo.body());
4115 // | // now, 'button' is still the widget reference to the newly created button
4116 // | dojo.connect(button, "onClick", function(e){ console.log('click'); });
4119 // | // create a button out of a node with id="src" and append it to id="wrapper":
4120 // | var button = new dijit.form.Button({},"src").placeAt("wrapper");
4123 // | // place a new button as the first element of some div
4124 // | var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first");
4127 // | // create a contentpane and add it to a TabContainer
4128 // | var tc = dijit.byId("myTabs");
4129 // | new dijit.layout.ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc)
4131 if(reference
.declaredClass
&& reference
.addChild
){
4132 reference
.addChild(this, position
);
4134 dojo
.place(this.domNode
, reference
, position
);
4144 if(!dojo
._hasResource
["dijit._Widget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4145 dojo
._hasResource
["dijit._Widget"] = true;
4146 dojo
.provide("dijit._Widget");
4152 ////////////////// DEFERRED CONNECTS ///////////////////
4154 // This code is to assist deferring dojo.connect() calls in widgets (connecting to events on the widgets'
4155 // DOM nodes) until someone actually needs to monitor that event.
4156 dojo
.connect(dojo
, "_connect",
4157 function(/*dijit._Widget*/ widget
, /*String*/ event
){
4158 if(widget
&& dojo
.isFunction(widget
._onConnect
)){
4159 widget
._onConnect(event
);
4163 dijit
._connectOnUseEventHandler = function(/*Event*/ event
){};
4165 ////////////////// ONDIJITCLICK SUPPORT ///////////////////
4167 // Keep track of where the last keydown event was, to help avoid generating
4168 // spurious ondijitclick events when:
4169 // 1. focus is on a <button> or <a>
4170 // 2. user presses then releases the ENTER key
4171 // 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler
4172 // 4. onkeyup event fires, causing the ondijitclick handler to fire
4173 dijit
._lastKeyDownNode
= null;
4176 var keydownCallback = function(evt
){
4177 dijit
._lastKeyDownNode
= evt
.srcElement
;
4179 dojo
.doc
.attachEvent('onkeydown', keydownCallback
);
4180 dojo
.addOnWindowUnload(function(){
4181 dojo
.doc
.detachEvent('onkeydown', keydownCallback
);
4185 dojo
.doc
.addEventListener('keydown', function(evt
){
4186 dijit
._lastKeyDownNode
= evt
.target
;
4192 dojo
.declare("dijit._Widget", dijit
._WidgetBase
, {
4194 // Base class for all Dijit widgets.
4196 // Extends _WidgetBase, adding support for:
4197 // - deferred connections
4198 // A call like dojo.connect(myWidget, "onMouseMove", func)
4199 // will essentially do a dojo.connect(myWidget.domNode, "onMouseMove", func)
4201 // Support new dojoAttachEvent="ondijitclick: ..." that is triggered by a mouse click or a SPACE/ENTER keypress
4202 // - focus related functions
4203 // In particular, the onFocus()/onBlur() callbacks. Driven internally by
4204 // dijit/_base/focus.js.
4205 // - deprecated methods
4206 // - onShow(), onHide(), onClose()
4208 // Also, by loading code in dijit/_base, turns on:
4209 // - browser sniffing (putting browser id like .dj_ie on <html> node)
4210 // - high contrast mode sniffing (add .dijit_a11y class to <body> if machine is in high contrast mode)
4213 ////////////////// DEFERRED CONNECTS ///////////////////
4215 // _deferredConnects: [protected] Object
4216 // attributeMap addendum for event handlers that should be connected only on first use
4217 _deferredConnects
: {
4232 onClick
: dijit
._connectOnUseEventHandler
,
4234 onClick: function(event){
4236 // Connect to this function to receive notifications of mouse click events.
4243 onDblClick
: dijit
._connectOnUseEventHandler
,
4245 onDblClick: function(event){
4247 // Connect to this function to receive notifications of mouse double click events.
4254 onKeyDown
: dijit
._connectOnUseEventHandler
,
4256 onKeyDown: function(event){
4258 // Connect to this function to receive notifications of keys being pressed down.
4265 onKeyPress
: dijit
._connectOnUseEventHandler
,
4267 onKeyPress: function(event){
4269 // Connect to this function to receive notifications of printable keys being typed.
4276 onKeyUp
: dijit
._connectOnUseEventHandler
,
4278 onKeyUp: function(event){
4280 // Connect to this function to receive notifications of keys being released.
4287 onMouseDown
: dijit
._connectOnUseEventHandler
,
4289 onMouseDown: function(event){
4291 // Connect to this function to receive notifications of when the mouse button is pressed down.
4298 onMouseMove
: dijit
._connectOnUseEventHandler
,
4300 onMouseMove: function(event){
4302 // Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget.
4309 onMouseOut
: dijit
._connectOnUseEventHandler
,
4311 onMouseOut: function(event){
4313 // Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget.
4320 onMouseOver
: dijit
._connectOnUseEventHandler
,
4322 onMouseOver: function(event){
4324 // Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget.
4331 onMouseLeave
: dijit
._connectOnUseEventHandler
,
4333 onMouseLeave: function(event){
4335 // Connect to this function to receive notifications of when the mouse moves off of this widget.
4342 onMouseEnter
: dijit
._connectOnUseEventHandler
,
4344 onMouseEnter: function(event){
4346 // Connect to this function to receive notifications of when the mouse moves onto this widget.
4353 onMouseUp
: dijit
._connectOnUseEventHandler
,
4355 onMouseUp: function(event){
4357 // Connect to this function to receive notifications of when the mouse button is released.
4365 create: function(/*Object?*/params
, /*DomNode|String?*/srcNodeRef
){
4366 // To avoid double-connects, remove entries from _deferredConnects
4367 // that have been setup manually by a subclass (ex, by dojoAttachEvent).
4368 // If a subclass has redefined a callback (ex: onClick) then assume it's being
4369 // connected to manually.
4370 this._deferredConnects
= dojo
.clone(this._deferredConnects
);
4371 for(var attr
in this.attributeMap
){
4372 delete this._deferredConnects
[attr
]; // can't be in both attributeMap and _deferredConnects
4374 for(attr
in this._deferredConnects
){
4375 if(this[attr
] !== dijit
._connectOnUseEventHandler
){
4376 delete this._deferredConnects
[attr
]; // redefined, probably dojoAttachEvent exists
4380 this.inherited(arguments
);
4383 // If the developer has specified a handler as a widget parameter
4384 // (ex: new Button({onClick: ...})
4385 // then naturally need to connect from DOM node to that handler immediately,
4386 for(attr
in this.params
){
4387 this._onConnect(attr
);
4392 _onConnect: function(/*String*/ event
){
4394 // Called when someone connects to one of my handlers.
4395 // "Turn on" that handler if it isn't active yet.
4397 // This is also called for every single initialization parameter
4398 // so need to do nothing for parameters like "id".
4401 if(event
in this._deferredConnects
){
4402 var mapNode
= this[this._deferredConnects
[event
] || 'domNode'];
4403 this.connect(mapNode
, event
.toLowerCase(), event
);
4404 delete this._deferredConnects
[event
];
4408 ////////////////// FOCUS RELATED ///////////////////
4409 // _onFocus() and _onBlur() are called by the focus manager
4411 // focused: [readonly] Boolean
4412 // This widget or a widget it contains has focus, or is "active" because
4413 // it was recently clicked.
4416 isFocusable: function(){
4418 // Return true if this widget can currently be focused
4420 return this.focus
&& (dojo
.style(this.domNode
, "display") != "none");
4423 onFocus: function(){
4425 // Called when the widget becomes "active" because
4426 // it or a widget inside of it either has focus, or has recently
4434 // Called when the widget stops being "active" because
4435 // focus moved to something outside of it, or the user
4436 // clicked somewhere outside of it, or the widget was
4442 _onFocus: function(e
){
4444 // This is where widgets do processing for when they are active,
4445 // such as changing CSS classes. See onFocus() for more details.
4451 _onBlur: function(){
4453 // This is where widgets do processing for when they stop being active,
4454 // such as changing CSS classes. See onBlur() for more details.
4460 ////////////////// DEPRECATED METHODS ///////////////////
4462 setAttribute: function(/*String*/ attr
, /*anything*/ value
){
4464 // Deprecated. Use set() instead.
4467 dojo
.deprecated(this.declaredClass
+"::setAttribute(attr, value) is deprecated. Use set() instead.", "", "2.0");
4468 this.set(attr
, value
);
4471 attr: function(/*String|Object*/name
, /*Object?*/value
){
4473 // Set or get properties on a widget instance.
4475 // The property to get or set. If an object is passed here and not
4476 // a string, its keys are used as names of attributes to be set
4477 // and the value of the object as values to set in the widget.
4479 // Optional. If provided, attr() operates as a setter. If omitted,
4480 // the current value of the named property is returned.
4482 // This method is deprecated, use get() or set() directly.
4484 // Print deprecation warning but only once per calling function
4485 if(dojo
.config
.isDebug
){
4486 var alreadyCalledHash
= arguments
.callee
._ach
|| (arguments
.callee
._ach
= {}),
4487 caller
= (arguments
.callee
.caller
|| "unknown caller").toString();
4488 if(!alreadyCalledHash
[caller
]){
4489 dojo
.deprecated(this.declaredClass
+ "::attr() is deprecated. Use get() or set() instead, called from " +
4491 alreadyCalledHash
[caller
] = true;
4495 var args
= arguments
.length
;
4496 if(args
>= 2 || typeof name
=== "object"){ // setter
4497 return this.set.apply(this, arguments
);
4499 return this.get(name
);
4503 ////////////////// ONDIJITCLICK SUPPORT ///////////////////
4505 // nodesWithKeyClick: [private] String[]
4506 // List of nodes that correctly handle click events via native browser support,
4507 // and don't need dijit's help
4508 nodesWithKeyClick
: ["input", "button"],
4511 /*Object|null*/ obj
,
4512 /*String|Function*/ event
,
4513 /*String|Function*/ method
){
4515 // Connects specified obj/event to specified method of this object
4516 // and registers for disconnect() on widget destroy.
4518 // Provide widget-specific analog to dojo.connect, except with the
4519 // implicit use of this widget as the target object.
4520 // This version of connect also provides a special "ondijitclick"
4521 // event which triggers on a click or space or enter keyup.
4522 // Events connected with `this.connect` are disconnected upon
4525 // A handle that can be passed to `disconnect` in order to disconnect before
4526 // the widget is destroyed.
4528 // | var btn = new dijit.form.Button();
4529 // | // when foo.bar() is called, call the listener we're going to
4530 // | // provide in the scope of btn
4531 // | btn.connect(foo, "bar", function(){
4532 // | console.debug(this.toString());
4539 handles
= this.inherited(arguments
, [obj
, event
== "ondijitclick" ? "onclick" : event
, method
]);
4541 if(event
== "ondijitclick"){
4542 // add key based click activation for unsupported nodes.
4543 // do all processing onkey up to prevent spurious clicks
4544 // for details see comments at top of this file where _lastKeyDownNode is defined
4545 if(d
.indexOf(this.nodesWithKeyClick
, obj
.nodeName
.toLowerCase()) == -1){ // is NOT input or button
4546 var m
= d
.hitch(this, method
);
4548 dc(obj
, "onkeydown", this, function(e
){
4549 //console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
4550 if((e
.keyCode
== d
.keys
.ENTER
|| e
.keyCode
== d
.keys
.SPACE
) &&
4551 !e
.ctrlKey
&& !e
.shiftKey
&& !e
.altKey
&& !e
.metaKey
){
4552 // needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
4553 dijit
._lastKeyDownNode
= e
.target
;
4555 // Stop event to prevent scrolling on space key in IE.
4556 // But don't do this for _HasDropDown because it surpresses the onkeypress
4557 // event needed to open the drop down when the user presses the SPACE key.
4558 if(!("openDropDown" in this && obj
== this._buttonNode
)){
4563 dc(obj
, "onkeyup", this, function(e
){
4564 //console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
4565 if( (e
.keyCode
== d
.keys
.ENTER
|| e
.keyCode
== d
.keys
.SPACE
) &&
4566 e
.target
== dijit
._lastKeyDownNode
&& // === breaks greasemonkey
4567 !e
.ctrlKey
&& !e
.shiftKey
&& !e
.altKey
&& !e
.metaKey
){
4568 //need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
4569 dijit
._lastKeyDownNode
= null;
4577 return handles
; // _Widget.Handle
4580 ////////////////// MISCELLANEOUS METHODS ///////////////////
4582 _onShow: function(){
4584 // Internal method called when this widget is made visible.
4585 // See `onShow` for details.
4591 // Called when this widget becomes the selected pane in a
4592 // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`,
4593 // `dijit.layout.AccordionContainer`, etc.
4595 // Also called to indicate display of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
4602 // Called when another widget becomes the selected pane in a
4603 // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`,
4604 // `dijit.layout.AccordionContainer`, etc.
4606 // Also called to indicate hide of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
4611 onClose: function(){
4613 // Called when this widget is being displayed as a popup (ex: a Calendar popped
4614 // up from a DateTextBox), and it is hidden.
4615 // This is called from the dijit.popup code, and should not be called directly.
4617 // Also used as a parameter for children of `dijit.layout.StackContainer` or subclasses.
4618 // Callback if a user tries to close the child. Child will be closed if this function returns true.
4622 return true; // Boolean
4630 if(!dojo
._hasResource
["dojo.string"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4631 dojo
._hasResource
["dojo.string"] = true;
4632 dojo
.provide("dojo.string");
4634 dojo
.getObject("string", true, dojo
);
4638 // summary: String utilities for Dojo
4642 dojo
.string
.rep = function(/*String*/str
, /*Integer*/num
){
4644 // Efficiently replicate a string `n` times.
4646 // the string to replicate
4648 // number of times to replicate the string
4650 if(num
<= 0 || !str
){ return ""; }
4657 if(!(num
>>= 1)){ break; }
4660 return buf
.join(""); // String
4663 dojo
.string
.pad = function(/*String*/text
, /*Integer*/size
, /*String?*/ch
, /*Boolean?*/end
){
4665 // Pad a string to guarantee that it is at least `size` length by
4666 // filling with the character `ch` at either the start or end of the
4667 // string. Pads at the start, by default.
4669 // the string to pad
4671 // length to provide padding
4673 // character to pad, defaults to '0'
4675 // adds padding at the end if true, otherwise pads at start
4677 // | // Fill the string to length 10 with "+" characters on the right. Yields "Dojo++++++".
4678 // | dojo.string.pad("Dojo", 10, "+", true);
4683 var out
= String(text
),
4684 pad
= dojo
.string
.rep(ch
, Math
.ceil((size
- out
.length
) / ch
.length
));
4685 return end
? out
+ pad
: pad
+ out
; // String
4688 dojo
.string
.substitute = function( /*String*/ template
,
4689 /*Object|Array*/map
,
4690 /*Function?*/ transform
,
4691 /*Object?*/ thisObject
){
4693 // Performs parameterized substitutions on a string. Throws an
4694 // exception if any parameter is unmatched.
4696 // a string with expressions in the form `${key}` to be replaced or
4697 // `${key:format}` which specifies a format function. keys are case-sensitive.
4699 // hash to search for substitutions
4701 // a function to process all parameters before substitution takes
4702 // place, e.g. mylib.encodeXML
4704 // where to look for optional format function; default to the global
4707 // Substitutes two expressions in a string from an Array or Object
4708 // | // returns "File 'foo.html' is not found in directory '/temp'."
4709 // | // by providing substitution data in an Array
4710 // | dojo.string.substitute(
4711 // | "File '${0}' is not found in directory '${1}'.",
4712 // | ["foo.html","/temp"]
4715 // | // also returns "File 'foo.html' is not found in directory '/temp'."
4716 // | // but provides substitution data in an Object structure. Dotted
4717 // | // notation may be used to traverse the structure.
4718 // | dojo.string.substitute(
4719 // | "File '${name}' is not found in directory '${info.dir}'.",
4720 // | { name: "foo.html", info: { dir: "/temp" } }
4723 // Use a transform function to modify the values:
4724 // | // returns "file 'foo.html' is not found in directory '/temp'."
4725 // | dojo.string.substitute(
4726 // | "${0} is not found in ${1}.",
4727 // | ["foo.html","/temp"],
4729 // | // try to figure out the type
4730 // | var prefix = (str.charAt(0) == "/") ? "directory": "file";
4731 // | return prefix + " '" + str + "'";
4736 // | // returns "thinger -- howdy"
4737 // | dojo.string.substitute(
4738 // | "${0:postfix}", ["thinger"], null, {
4739 // | postfix: function(value, key){
4740 // | return value + " -- howdy";
4745 thisObject
= thisObject
|| dojo
.global
;
4746 transform
= transform
?
4747 dojo
.hitch(thisObject
, transform
) : function(v
){ return v
; };
4749 return template
.replace(/\$\{([^\s\:\}]+)(?:\:([^\s\:\}]+))?\}/g,
4750 function(match
, key
, format
){
4751 var value
= dojo
.getObject(key
, false, map
);
4753 value
= dojo
.getObject(format
, false, thisObject
).call(thisObject
, value
, key
);
4755 return transform(value
, key
).toString();
4760 dojo.string.trim = function(str){
4762 // Trims whitespace from both sides of the string
4764 // String to be trimmed
4766 // Returns the trimmed string
4768 // This version of trim() was taken from [Steven Levithan's blog](http://blog.stevenlevithan.com/archives/faster-trim-javascript).
4769 // The short yet performant version of this function is dojo.trim(),
4770 // which is part of Dojo base. Uses String.prototype.trim instead, if available.
4771 return ""; // String
4775 dojo
.string
.trim
= String
.prototype.trim
?
4776 dojo
.trim
: // aliasing to the native function
4778 str
= str
.replace(/^\s+/, '');
4779 for(var i
= str
.length
- 1; i
>= 0; i
--){
4780 if(/\S/.test(str
.charAt(i
))){
4781 str
= str
.substring(0, i
+ 1);
4790 if(!dojo
._hasResource
["dojo.cache"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4791 dojo
._hasResource
["dojo.cache"] = true;
4792 dojo
.provide("dojo.cache");
4798 // A way to cache string content that is fetchable via `dojo.moduleUrl`.
4803 dojo
.cache = function(/*String||Object*/module
, /*String*/url
, /*String||Object?*/value
){
4805 // A getter and setter for storing the string content associated with the
4806 // module and url arguments.
4808 // module and url are used to call `dojo.moduleUrl()` to generate a module URL.
4809 // If value is specified, the cache value for the moduleUrl will be set to
4810 // that value. Otherwise, dojo.cache will fetch the moduleUrl and store it
4811 // in its internal cache and return that cached value for the URL. To clear
4812 // a cache value pass null for value. Since XMLHttpRequest (XHR) is used to fetch the
4813 // the URL contents, only modules on the same domain of the page can use this capability.
4814 // The build system can inline the cache values though, to allow for xdomain hosting.
4815 // module: String||Object
4816 // If a String, the module name to use for the base part of the URL, similar to module argument
4817 // to `dojo.moduleUrl`. If an Object, something that has a .toString() method that
4818 // generates a valid path for the cache item. For example, a dojo._Url object.
4820 // The rest of the path to append to the path derived from the module argument. If
4821 // module is an object, then this second argument should be the "value" argument instead.
4822 // value: String||Object?
4823 // If a String, the value to use in the cache for the module/url combination.
4824 // If an Object, it can have two properties: value and sanitize. The value property
4825 // should be the value to use in the cache, and sanitize can be set to true or false,
4826 // to indicate if XML declarations should be removed from the value and if the HTML
4827 // inside a body tag in the value should be extracted as the real value. The value argument
4828 // or the value property on the value argument are usually only used by the build system
4829 // as it inlines cache content.
4831 // To ask dojo.cache to fetch content and store it in the cache (the dojo["cache"] style
4832 // of call is used to avoid an issue with the build system erroneously trying to intern
4833 // this example. To get the build system to intern your dojo.cache calls, use the
4834 // "dojo.cache" style of call):
4835 // | //If template.html contains "<h1>Hello</h1>" that will be
4836 // | //the value for the text variable.
4837 // | var text = dojo["cache"]("my.module", "template.html");
4839 // To ask dojo.cache to fetch content and store it in the cache, and sanitize the input
4840 // (the dojo["cache"] style of call is used to avoid an issue with the build system
4841 // erroneously trying to intern this example. To get the build system to intern your
4842 // dojo.cache calls, use the "dojo.cache" style of call):
4843 // | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
4844 // | //text variable will contain just "<h1>Hello</h1>".
4845 // | var text = dojo["cache"]("my.module", "template.html", {sanitize: true});
4847 // Same example as previous, but demostrates how an object can be passed in as
4848 // the first argument, then the value argument can then be the second argument.
4849 // | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
4850 // | //text variable will contain just "<h1>Hello</h1>".
4851 // | var text = dojo["cache"](new dojo._Url("my/module/template.html"), {sanitize: true});
4853 //Module could be a string, or an object that has a toString() method
4854 //that will return a useful path. If it is an object, then the "url" argument
4855 //will actually be the value argument.
4856 if(typeof module
== "string"){
4857 var pathObj
= dojo
.moduleUrl(module
, url
);
4862 var key
= pathObj
.toString();
4865 if(value
!= undefined && !dojo
.isString(value
)){
4866 val
= ("value" in value
? value
.value
: undefined);
4869 var sanitize
= value
&& value
.sanitize
? true : false;
4871 if(typeof val
== "string"){
4872 //We have a string, set cache value
4873 val
= cache
[key
] = sanitize
? dojo
.cache
._sanitize(val
) : val
;
4874 }else if(val
=== null){
4875 //Remove cached value
4878 //Allow cache values to be empty strings. If key property does
4879 //not exist, fetch it.
4880 if(!(key
in cache
)){
4881 val
= dojo
._getText(key
);
4882 cache
[key
] = sanitize
? dojo
.cache
._sanitize(val
) : val
;
4886 return val
; //String
4889 dojo
.cache
._sanitize = function(/*String*/val
){
4891 // Strips <?xml ...?> declarations so that external SVG and XML
4892 // documents can be added to a document without worry. Also, if the string
4893 // is an HTML document, only the part inside the body tag is returned.
4895 // Copied from dijit._Templated._sanitizeTemplateString.
4897 val
= val
.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, "");
4898 var matches
= val
.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
4905 return val
; //String
4910 if(!dojo
._hasResource
["dijit._Templated"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4911 dojo
._hasResource
["dijit._Templated"] = true;
4912 dojo
.provide("dijit._Templated");
4919 dojo
.declare("dijit._Templated",
4923 // Mixin for widgets that are instantiated from a template
4925 // templateString: [protected] String
4926 // A string that represents the widget template. Pre-empts the
4927 // templatePath. In builds that have their strings "interned", the
4928 // templatePath is converted to an inline templateString, thereby
4929 // preventing a synchronous network call.
4931 // Use in conjunction with dojo.cache() to load from a file.
4932 templateString
: null,
4934 // templatePath: [protected deprecated] String
4935 // Path to template (HTML file) for this widget relative to dojo.baseUrl.
4936 // Deprecated: use templateString with dojo.cache() instead.
4939 // widgetsInTemplate: [protected] Boolean
4940 // Should we parse the template to find widgets that might be
4941 // declared in markup inside it? False by default.
4942 widgetsInTemplate
: false,
4944 // skipNodeCache: [protected] Boolean
4945 // If using a cached widget template node poses issues for a
4946 // particular widget class, it can set this property to ensure
4947 // that its template is always re-built from a string
4948 _skipNodeCache
: false,
4950 // _earlyTemplatedStartup: Boolean
4951 // A fallback to preserve the 1.0 - 1.3 behavior of children in
4952 // templates having their startup called before the parent widget
4953 // fires postCreate. Defaults to 'false', causing child widgets to
4954 // have their .startup() called immediately before a parent widget
4955 // .startup(), but always after the parent .postCreate(). Set to
4956 // 'true' to re-enable to previous, arguably broken, behavior.
4957 _earlyTemplatedStartup
: false,
4960 // _attachPoints: [private] String[]
4961 // List of widget attribute names associated with dojoAttachPoint=... in the
4962 // template, ex: ["containerNode", "labelNode"]
4967 // _attachEvents: [private] Handle[]
4968 // List of connections associated with dojoAttachEvent=... in the
4973 constructor: function(){
4974 this._attachPoints
= [];
4975 this._attachEvents
= [];
4978 _stringRepl: function(tmpl
){
4980 // Does substitution of ${foo} type properties in template string
4983 var className
= this.declaredClass
, _this
= this;
4984 // Cache contains a string because we need to do property replacement
4985 // do the property replacement
4986 return dojo
.string
.substitute(tmpl
, this, function(value
, key
){
4987 if(key
.charAt(0) == '!'){ value
= dojo
.getObject(key
.substr(1), false, _this
); }
4988 if(typeof value
== "undefined"){ throw new Error(className
+" template:"+key
); } // a debugging aide
4989 if(value
== null){ return ""; }
4991 // Substitution keys beginning with ! will skip the transform step,
4992 // in case a user wishes to insert unescaped markup, e.g. ${!foo}
4993 return key
.charAt(0) == "!" ? value
:
4994 // Safer substitution, see heading "Attribute values" in
4995 // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
4996 value
.toString().replace(/"/g,"""); //TODO
: add
&
? use encodeXML method
?
5000 buildRendering: function(){
5002 // Construct the UI for this widget from a template, setting this.domNode.
5006 // Lookup cached version of template, and download to cache if it
5007 // isn't there already. Returns either a DomNode or a string, depending on
5008 // whether or not the template contains ${foo} replacement parameters.
5009 var cached
= dijit
._Templated
.getCachedTemplate(this.templatePath
, this.templateString
, this._skipNodeCache
);
5012 if(dojo
.isString(cached
)){
5013 node
= dojo
._toDom(this._stringRepl(cached
));
5014 if(node
.nodeType
!= 1){
5015 // Flag common problems such as templates with multiple top level nodes (nodeType == 11)
5016 throw new Error("Invalid template: " + cached
);
5019 // if it's a node, all we have to do is clone it
5020 node
= cached
.cloneNode(true);
5023 this.domNode
= node
;
5025 // Call down to _Widget.buildRendering() to get base classes assigned
5026 // TODO: change the baseClass assignment to attributeMap
5027 this.inherited(arguments
);
5029 // recurse through the node, looking for, and attaching to, our
5030 // attachment points and events, which should be defined on the template node.
5031 this._attachTemplateNodes(node
);
5033 if(this.widgetsInTemplate
){
5034 // Store widgets that we need to start at a later point in time
5035 var cw
= (this._startupWidgets
= dojo
.parser
.parse(node
, {
5036 noStart
: !this._earlyTemplatedStartup
,
5038 inherited
: {dir
: this.dir
, lang
: this.lang
},
5039 propsThis
: this, // so data-dojo-props of widgets in the template can reference "this" to refer to me
5040 scope
: "dojo" // even in multi-version mode templates use dojoType/data-dojo-type
5043 this._supportingWidgets
= dijit
.findWidgets(node
);
5045 this._attachTemplateNodes(cw
, function(n
,p
){
5050 this._fillContent(this.srcNodeRef
);
5053 _fillContent: function(/*DomNode*/ source
){
5055 // Relocate source contents to templated container node.
5056 // this.containerNode must be able to receive children, or exceptions will be thrown.
5059 var dest
= this.containerNode
;
5061 while(source
.hasChildNodes()){
5062 dest
.appendChild(source
.firstChild
);
5067 _attachTemplateNodes: function(rootNode
, getAttrFunc
){
5069 // Iterate through the template and attach functions and nodes accordingly.
5070 // Alternately, if rootNode is an array of widgets, then will process dojoAttachPoint
5071 // etc. for those widgets.
5073 // Map widget properties and functions to the handlers specified in
5074 // the dom node and it's descendants. This function iterates over all
5075 // nodes and looks for these properties:
5076 // * dojoAttachPoint
5077 // * dojoAttachEvent
5080 // rootNode: DomNode|Array[Widgets]
5081 // the node to search for properties. All children will be searched.
5082 // getAttrFunc: Function?
5083 // a function which will be used to obtain property for a given
5088 getAttrFunc
= getAttrFunc
|| function(n
,p
){ return n
.getAttribute(p
); };
5090 var nodes
= dojo
.isArray(rootNode
) ? rootNode
: (rootNode
.all
|| rootNode
.getElementsByTagName("*"));
5091 var x
= dojo
.isArray(rootNode
) ? 0 : -1;
5092 for(; x
<nodes
.length
; x
++){
5093 var baseNode
= (x
== -1) ? rootNode
: nodes
[x
];
5094 if(this.widgetsInTemplate
&& (getAttrFunc(baseNode
, "dojoType") || getAttrFunc(baseNode
, "data-dojo-type"))){
5097 // Process dojoAttachPoint
5098 var attachPoint
= getAttrFunc(baseNode
, "dojoAttachPoint") || getAttrFunc(baseNode
, "data-dojo-attach-point");
5100 var point
, points
= attachPoint
.split(/\s*,\s*/);
5101 while((point
= points
.shift())){
5102 if(dojo
.isArray(this[point
])){
5103 this[point
].push(baseNode
);
5105 this[point
]=baseNode
;
5107 this._attachPoints
.push(point
);
5111 // Process dojoAttachEvent
5112 var attachEvent
= getAttrFunc(baseNode
, "dojoAttachEvent") || getAttrFunc(baseNode
, "data-dojo-attach-event");;
5114 // NOTE: we want to support attributes that have the form
5115 // "domEvent: nativeEvent; ..."
5116 var event
, events
= attachEvent
.split(/\s*,\s*/);
5117 var trim
= dojo
.trim
;
5118 while((event
= events
.shift())){
5120 var thisFunc
= null;
5121 if(event
.indexOf(":") != -1){
5122 // oh, if only JS had tuple assignment
5123 var funcNameArr
= event
.split(":");
5124 event
= trim(funcNameArr
[0]);
5125 thisFunc
= trim(funcNameArr
[1]);
5127 event
= trim(event
);
5132 this._attachEvents
.push(this.connect(baseNode
, event
, thisFunc
));
5137 // waiRole, waiState
5138 // TODO: remove this in 2.0, templates are now using role=... and aria-XXX=... attributes directicly
5139 var role
= getAttrFunc(baseNode
, "waiRole");
5141 dijit
.setWaiRole(baseNode
, role
);
5143 var values
= getAttrFunc(baseNode
, "waiState");
5145 dojo
.forEach(values
.split(/\s*,\s*/), function(stateValue
){
5146 if(stateValue
.indexOf('-') != -1){
5147 var pair
= stateValue
.split('-');
5148 dijit
.setWaiState(baseNode
, pair
[0], pair
[1]);
5155 startup: function(){
5156 dojo
.forEach(this._startupWidgets
, function(w
){
5157 if(w
&& !w
._started
&& w
.startup
){
5161 this.inherited(arguments
);
5164 destroyRendering: function(){
5165 // Delete all attach points to prevent IE6 memory leaks.
5166 dojo
.forEach(this._attachPoints
, function(point
){
5169 this._attachPoints
= [];
5171 // And same for event handlers
5172 dojo
.forEach(this._attachEvents
, this.disconnect
, this);
5173 this._attachEvents
= [];
5175 this.inherited(arguments
);
5180 // key is either templatePath or templateString; object is either string or DOM tree
5181 dijit
._Templated
._templateCache
= {};
5183 dijit
._Templated
.getCachedTemplate = function(templatePath
, templateString
, alwaysUseString
){
5185 // Static method to get a template based on the templatePath or
5186 // templateString key
5187 // templatePath: String||dojo.uri.Uri
5188 // The URL to get the template from.
5189 // templateString: String?
5190 // a string to use in lieu of fetching the template from a URL. Takes precedence
5191 // over templatePath
5193 // Either string (if there are ${} variables that need to be replaced) or just
5194 // a DOM tree (if the node can be cloned directly)
5196 // is it already cached?
5197 var tmplts
= dijit
._Templated
._templateCache
;
5198 var key
= templateString
|| templatePath
;
5199 var cached
= tmplts
[key
];
5202 // if the cached value is an innerHTML string (no ownerDocument) or a DOM tree created within the current document, then use the current cached value
5203 if(!cached
.ownerDocument
|| cached
.ownerDocument
== dojo
.doc
){
5204 // string or node of the same document
5207 }catch(e
){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded
5208 dojo
.destroy(cached
);
5211 // If necessary, load template string from template path
5212 if(!templateString
){
5213 templateString
= dojo
.cache(templatePath
, {sanitize
: true});
5215 templateString
= dojo
.string
.trim(templateString
);
5217 if(alwaysUseString
|| templateString
.match(/\$\{([^\}]+)\}/g)){
5218 // there are variables in the template so all we can do is cache the string
5219 return (tmplts
[key
] = templateString
); //String
5221 // there are no variables in the template so we can cache the DOM tree
5222 var node
= dojo
._toDom(templateString
);
5223 if(node
.nodeType
!= 1){
5224 throw new Error("Invalid template: " + templateString
);
5226 return (tmplts
[key
] = node
); //Node
5231 dojo
.addOnWindowUnload(function(){
5232 var cache
= dijit
._Templated
._templateCache
;
5233 for(var key
in cache
){
5234 var value
= cache
[key
];
5235 if(typeof value
== "object"){ // value is either a string or a DOM node template
5236 dojo
.destroy(value
);
5243 // These arguments can be specified for widgets which are used in templates.
5244 // Since any widget can be specified as sub widgets in template, mix it
5245 // into the base widget class. (This is a hack, but it's effective.)
5246 dojo
.extend(dijit
._Widget
,{
5247 dojoAttachEvent
: "",
5248 dojoAttachPoint
: "",
5255 if(!dojo
._hasResource
["dijit._Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5256 dojo
._hasResource
["dijit._Container"] = true;
5257 dojo
.provide("dijit._Container");
5260 dojo
.declare("dijit._Container",
5264 // Mixin for widgets that contain a set of widget children.
5266 // Use this mixin for widgets that needs to know about and
5267 // keep track of their widget children. Suitable for widgets like BorderContainer
5268 // and TabContainer which contain (only) a set of child widgets.
5270 // It's not suitable for widgets like ContentPane
5271 // which contains mixed HTML (plain DOM nodes in addition to widgets),
5272 // and where contained widgets are not necessarily directly below
5273 // this.containerNode. In that case calls like addChild(node, position)
5274 // wouldn't make sense.
5276 // isContainer: [protected] Boolean
5277 // Indicates that this widget acts as a "parent" to the descendant widgets.
5278 // When the parent is started it will call startup() on the child widgets.
5279 // See also `isLayoutContainer`.
5282 buildRendering: function(){
5283 this.inherited(arguments
);
5284 if(!this.containerNode
){
5285 // all widgets with descendants must set containerNode
5286 this.containerNode
= this.domNode
;
5290 addChild: function(/*dijit._Widget*/ widget
, /*int?*/ insertIndex
){
5292 // Makes the given widget a child of this widget.
5294 // Inserts specified child widget's dom node as a child of this widget's
5295 // container node, and possibly does other processing (such as layout).
5297 var refNode
= this.containerNode
;
5298 if(insertIndex
&& typeof insertIndex
== "number"){
5299 var children
= this.getChildren();
5300 if(children
&& children
.length
>= insertIndex
){
5301 refNode
= children
[insertIndex
-1].domNode
;
5302 insertIndex
= "after";
5305 dojo
.place(widget
.domNode
, refNode
, insertIndex
);
5307 // If I've been started but the child widget hasn't been started,
5308 // start it now. Make sure to do this after widget has been
5309 // inserted into the DOM tree, so it can see that it's being controlled by me,
5310 // so it doesn't try to size itself.
5311 if(this._started
&& !widget
._started
){
5316 removeChild: function(/*Widget or int*/ widget
){
5318 // Removes the passed widget instance from this widget but does
5319 // not destroy it. You can also pass in an integer indicating
5320 // the index within the container to remove
5322 if(typeof widget
== "number"){
5323 widget
= this.getChildren()[widget
];
5327 var node
= widget
.domNode
;
5328 if(node
&& node
.parentNode
){
5329 node
.parentNode
.removeChild(node
); // detach but don't destroy
5334 hasChildren: function(){
5336 // Returns true if widget has children, i.e. if this.containerNode contains something.
5337 return this.getChildren().length
> 0; // Boolean
5340 destroyDescendants: function(/*Boolean*/ preserveDom
){
5342 // Destroys all the widgets inside this.containerNode,
5343 // but not this widget itself
5344 dojo
.forEach(this.getChildren(), function(child
){ child
.destroyRecursive(preserveDom
); });
5347 _getSiblingOfChild: function(/*dijit._Widget*/ child
, /*int*/ dir
){
5349 // Get the next or previous widget sibling of child
5351 // if 1, get the next sibling
5352 // if -1, get the previous sibling
5355 var node
= child
.domNode
,
5356 which
= (dir
>0 ? "nextSibling" : "previousSibling");
5359 }while(node
&& (node
.nodeType
!= 1 || !dijit
.byNode(node
)));
5360 return node
&& dijit
.byNode(node
); // dijit._Widget
5363 getIndexOfChild: function(/*dijit._Widget*/ child
){
5365 // Gets the index of the child in this container or -1 if not found
5366 return dojo
.indexOf(this.getChildren(), child
); // int
5369 startup: function(){
5371 // Called after all the widgets have been instantiated and their
5372 // dom nodes have been inserted somewhere under dojo.doc.body.
5374 // Widgets should override this method to do any initialization
5375 // dependent on other widgets existing, and then call
5376 // this superclass method to finish things off.
5378 // startup() in subclasses shouldn't do anything
5379 // size related because the size of the widget hasn't been set yet.
5381 if(this._started
){ return; }
5383 // Startup all children of this widget
5384 dojo
.forEach(this.getChildren(), function(child
){ child
.startup(); });
5386 this.inherited(arguments
);
5393 if(!dojo
._hasResource
["dijit._Contained"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5394 dojo
._hasResource
["dijit._Contained"] = true;
5395 dojo
.provide("dijit._Contained");
5398 dojo
.declare("dijit._Contained",
5402 // Mixin for widgets that are children of a container widget
5405 // | // make a basic custom widget that knows about it's parents
5406 // | dojo.declare("my.customClass",[dijit._Widget,dijit._Contained],{});
5408 getParent: function(){
5410 // Returns the parent widget of this widget, assuming the parent
5411 // specifies isContainer
5412 var parent
= dijit
.getEnclosingWidget(this.domNode
.parentNode
);
5413 return parent
&& parent
.isContainer
? parent
: null;
5416 _getSibling: function(/*String*/ which
){
5418 // Returns next or previous sibling
5420 // Either "next" or "previous"
5423 var node
= this.domNode
;
5425 node
= node
[which
+"Sibling"];
5426 }while(node
&& node
.nodeType
!= 1);
5427 return node
&& dijit
.byNode(node
); // dijit._Widget
5430 getPreviousSibling: function(){
5432 // Returns null if this is the first child of the parent,
5433 // otherwise returns the next element sibling to the "left".
5435 return this._getSibling("previous"); // dijit._Widget
5438 getNextSibling: function(){
5440 // Returns null if this is the last child of the parent,
5441 // otherwise returns the next element sibling to the "right".
5443 return this._getSibling("next"); // dijit._Widget
5446 getIndexInParent: function(){
5448 // Returns the index of this widget within its container parent.
5449 // It returns -1 if the parent does not exist, or if the parent
5450 // is not a dijit._Container
5452 var p
= this.getParent();
5453 if(!p
|| !p
.getIndexOfChild
){
5456 return p
.getIndexOfChild(this); // int
5463 if(!dojo
._hasResource
["dijit.layout._LayoutWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5464 dojo
._hasResource
["dijit.layout._LayoutWidget"] = true;
5465 dojo
.provide("dijit.layout._LayoutWidget");
5471 dojo
.declare("dijit.layout._LayoutWidget",
5472 [dijit
._Widget
, dijit
._Container
, dijit
._Contained
],
5475 // Base class for a _Container widget which is responsible for laying out its children.
5476 // Widgets which mixin this code must define layout() to manage placement and sizing of the children.
5478 // baseClass: [protected extension] String
5479 // This class name is applied to the widget's domNode
5480 // and also may be used to generate names for sub nodes,
5481 // for example dijitTabContainer-content.
5482 baseClass
: "dijitLayoutContainer",
5484 // isLayoutContainer: [protected] Boolean
5485 // Indicates that this widget is going to call resize() on its
5486 // children widgets, setting their size, when they become visible.
5487 isLayoutContainer
: true,
5489 buildRendering: function(){
5490 this.inherited(arguments
);
5491 dojo
.addClass(this.domNode
, "dijitContainer");
5494 startup: function(){
5496 // Called after all the widgets have been instantiated and their
5497 // dom nodes have been inserted somewhere under dojo.doc.body.
5499 // Widgets should override this method to do any initialization
5500 // dependent on other widgets existing, and then call
5501 // this superclass method to finish things off.
5503 // startup() in subclasses shouldn't do anything
5504 // size related because the size of the widget hasn't been set yet.
5506 if(this._started
){ return; }
5508 // Need to call inherited first - so that child widgets get started
5510 this.inherited(arguments
);
5512 // If I am a not being controlled by a parent layout widget...
5513 var parent
= this.getParent
&& this.getParent()
5514 if(!(parent
&& parent
.isLayoutContainer
)){
5515 // Do recursive sizing and layout of all my descendants
5516 // (passing in no argument to resize means that it has to glean the size itself)
5519 // Since my parent isn't a layout container, and my style *may be* width=height=100%
5520 // or something similar (either set directly or via a CSS class),
5521 // monitor when my size changes so that I can re-layout.
5522 // For browsers where I can't directly monitor when my size changes,
5523 // monitor when the viewport changes size, which *may* indicate a size change for me.
5524 this.connect(dojo
.isIE
? this.domNode
: dojo
.global
, 'onresize', function(){
5525 // Using function(){} closure to ensure no arguments to resize.
5531 resize: function(changeSize
, resultSize
){
5533 // Call this to resize a widget, or after its size has changed.
5535 // Change size mode:
5536 // When changeSize is specified, changes the marginBox of this widget
5537 // and forces it to relayout its contents accordingly.
5538 // changeSize may specify height, width, or both.
5540 // If resultSize is specified it indicates the size the widget will
5541 // become after changeSize has been applied.
5543 // Notification mode:
5544 // When changeSize is null, indicates that the caller has already changed
5545 // the size of the widget, or perhaps it changed because the browser
5546 // window was resized. Tells widget to relayout its contents accordingly.
5548 // If resultSize is also specified it indicates the size the widget has
5551 // In either mode, this method also:
5552 // 1. Sets this._borderBox and this._contentBox to the new size of
5553 // the widget. Queries the current domNode size if necessary.
5554 // 2. Calls layout() to resize contents (and maybe adjust child widgets).
5556 // changeSize: Object?
5557 // Sets the widget to this margin-box size and position.
5558 // May include any/all of the following properties:
5559 // | {w: int, h: int, l: int, t: int}
5561 // resultSize: Object?
5562 // The margin-box size of this widget after applying changeSize (if
5563 // changeSize is specified). If caller knows this size and
5564 // passes it in, we don't need to query the browser to get the size.
5565 // | {w: int, h: int}
5567 var node
= this.domNode
;
5569 // set margin box size, unless it wasn't specified, in which case use current size
5571 dojo
.marginBox(node
, changeSize
);
5573 // set offset of the node
5574 if(changeSize
.t
){ node
.style
.top
= changeSize
.t
+ "px"; }
5575 if(changeSize
.l
){ node
.style
.left
= changeSize
.l
+ "px"; }
5578 // If either height or width wasn't specified by the user, then query node for it.
5579 // But note that setting the margin box and then immediately querying dimensions may return
5580 // inaccurate results, so try not to depend on it.
5581 var mb
= resultSize
|| {};
5582 dojo
.mixin(mb
, changeSize
|| {}); // changeSize overrides resultSize
5583 if( !("h" in mb
) || !("w" in mb
) ){
5584 mb
= dojo
.mixin(dojo
.marginBox(node
), mb
); // just use dojo.marginBox() to fill in missing values
5587 // Compute and save the size of my border box and content box
5588 // (w/out calling dojo.contentBox() since that may fail if size was recently set)
5589 var cs
= dojo
.getComputedStyle(node
);
5590 var me
= dojo
._getMarginExtents(node
, cs
);
5591 var be
= dojo
._getBorderExtents(node
, cs
);
5592 var bb
= (this._borderBox
= {
5593 w
: mb
.w
- (me
.w
+ be
.w
),
5594 h
: mb
.h
- (me
.h
+ be
.h
)
5596 var pe
= dojo
._getPadExtents(node
, cs
);
5597 this._contentBox
= {
5598 l
: dojo
._toPixelValue(node
, cs
.paddingLeft
),
5599 t
: dojo
._toPixelValue(node
, cs
.paddingTop
),
5604 // Callback for widget to adjust size of its children
5610 // Widgets override this method to size and position their contents/children.
5611 // When this is called this._contentBox is guaranteed to be set (see resize()).
5613 // This is called after startup(), and also when the widget's size has been
5616 // protected extension
5619 _setupChild: function(/*dijit._Widget*/child
){
5621 // Common setup for initial children and children which are added after startup
5623 // protected extension
5625 var cls
= this.baseClass
+ "-child "
5626 + (child
.baseClass
? this.baseClass
+ "-" + child
.baseClass
: "");
5627 dojo
.addClass(child
.domNode
, cls
);
5630 addChild: function(/*dijit._Widget*/ child
, /*Integer?*/ insertIndex
){
5631 // Overrides _Container.addChild() to call _setupChild()
5632 this.inherited(arguments
);
5634 this._setupChild(child
);
5638 removeChild: function(/*dijit._Widget*/ child
){
5639 // Overrides _Container.removeChild() to remove class added by _setupChild()
5640 var cls
= this.baseClass
+ "-child"
5641 + (child
.baseClass
?
5642 " " + this.baseClass
+ "-" + child
.baseClass
: "");
5643 dojo
.removeClass(child
.domNode
, cls
);
5645 this.inherited(arguments
);
5650 dijit
.layout
.marginBox2contentBox = function(/*DomNode*/ node
, /*Object*/ mb
){
5652 // Given the margin-box size of a node, return its content box size.
5653 // Functions like dojo.contentBox() but is more reliable since it doesn't have
5654 // to wait for the browser to compute sizes.
5655 var cs
= dojo
.getComputedStyle(node
);
5656 var me
= dojo
._getMarginExtents(node
, cs
);
5657 var pb
= dojo
._getPadBorderExtents(node
, cs
);
5659 l
: dojo
._toPixelValue(node
, cs
.paddingLeft
),
5660 t
: dojo
._toPixelValue(node
, cs
.paddingTop
),
5661 w
: mb
.w
- (me
.w
+ pb
.w
),
5662 h
: mb
.h
- (me
.h
+ pb
.h
)
5667 var capitalize = function(word
){
5668 return word
.substring(0,1).toUpperCase() + word
.substring(1);
5671 var size = function(widget
, dim
){
5673 var newSize
= widget
.resize
? widget
.resize(dim
) : dojo
.marginBox(widget
.domNode
, dim
);
5675 // record child's size
5677 // if the child returned it's new size then use that
5678 dojo
.mixin(widget
, newSize
);
5680 // otherwise, call marginBox(), but favor our own numbers when we have them.
5681 // the browser lies sometimes
5682 dojo
.mixin(widget
, dojo
.marginBox(widget
.domNode
));
5683 dojo
.mixin(widget
, dim
);
5687 dijit
.layout
.layoutChildren = function(/*DomNode*/ container
, /*Object*/ dim
, /*Widget[]*/ children
,
5688 /*String?*/ changedRegionId
, /*Number?*/ changedRegionSize
){
5690 // Layout a bunch of child dom nodes within a parent dom node
5694 // {l, t, w, h} object specifying dimensions of container into which to place children
5696 // an array of Widgets or at least objects containing:
5697 // * domNode: pointer to DOM node to position
5698 // * region or layoutAlign: position to place DOM node
5699 // * resize(): (optional) method to set size of node
5700 // * id: (optional) Id of widgets, referenced from resize object, below.
5702 // If specified, the slider for the region with the specified id has been dragged, and thus
5703 // the region's height or width should be adjusted according to changedRegionSize
5704 // changedRegionSize:
5705 // See changedRegionId.
5707 // copy dim because we are going to modify it
5708 dim
= dojo
.mixin({}, dim
);
5710 dojo
.addClass(container
, "dijitLayoutContainer");
5712 // Move "client" elements to the end of the array for layout. a11y dictates that the author
5713 // needs to be able to put them in the document in tab-order, but this algorithm requires that
5714 // client be last. TODO: move these lines to LayoutContainer? Unneeded other places I think.
5715 children
= dojo
.filter(children
, function(item
){ return item
.region
!= "center" && item
.layoutAlign
!= "client"; })
5716 .concat(dojo
.filter(children
, function(item
){ return item
.region
== "center" || item
.layoutAlign
== "client"; }));
5718 // set positions/sizes
5719 dojo
.forEach(children
, function(child
){
5720 var elm
= child
.domNode
,
5721 pos
= (child
.region
|| child
.layoutAlign
);
5723 // set elem to upper left corner of unused space; may move it later
5724 var elmStyle
= elm
.style
;
5725 elmStyle
.left
= dim
.l
+"px";
5726 elmStyle
.top
= dim
.t
+"px";
5727 elmStyle
.position
= "absolute";
5729 dojo
.addClass(elm
, "dijitAlign" + capitalize(pos
));
5731 // Size adjustments to make to this child widget
5732 var sizeSetting
= {};
5734 // Check for optional size adjustment due to splitter drag (height adjustment for top/bottom align
5735 // panes and width adjustment for left/right align panes.
5736 if(changedRegionId
&& changedRegionId
== child
.id
){
5737 sizeSetting
[child
.region
== "top" || child
.region
== "bottom" ? "h" : "w"] = changedRegionSize
;
5740 // set size && adjust record of remaining space.
5741 // note that setting the width of a <div> may affect its height.
5742 if(pos
== "top" || pos
== "bottom"){
5743 sizeSetting
.w
= dim
.w
;
5744 size(child
, sizeSetting
);
5749 elmStyle
.top
= dim
.t
+ dim
.h
+ "px";
5751 }else if(pos
== "left" || pos
== "right"){
5752 sizeSetting
.h
= dim
.h
;
5753 size(child
, sizeSetting
);
5758 elmStyle
.left
= dim
.l
+ dim
.w
+ "px";
5760 }else if(pos
== "client" || pos
== "center"){
5770 if(!dojo
._hasResource
["dijit._CssStateMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5771 dojo
._hasResource
["dijit._CssStateMixin"] = true;
5772 dojo
.provide("dijit._CssStateMixin");
5775 dojo
.declare("dijit._CssStateMixin", [], {
5777 // Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus
5778 // state changes, and also higher-level state changes such becoming disabled or selected.
5781 // By mixing this class into your widget, and setting the this.baseClass attribute, it will automatically
5782 // maintain CSS classes on the widget root node (this.domNode) depending on hover,
5783 // active, focus, etc. state. Ex: with a baseClass of dijitButton, it will apply the classes
5784 // dijitButtonHovered and dijitButtonActive, as the user moves the mouse over the widget and clicks it.
5786 // It also sets CSS like dijitButtonDisabled based on widget semantic state.
5788 // By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons
5789 // within the widget).
5791 // cssStateNodes: [protected] Object
5792 // List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus
5794 // Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names
5795 // (like "dijitUpArrowButton"). Example:
5797 // | "upArrowButton": "dijitUpArrowButton",
5798 // | "downArrowButton": "dijitDownArrowButton"
5800 // The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it
5804 // hovering: [readonly] Boolean
5805 // True if cursor is over this widget
5808 // active: [readonly] Boolean
5809 // True if mouse was pressed while over this widget, and hasn't been released yet
5812 _applyAttributes: function(){
5813 // This code would typically be in postCreate(), but putting in _applyAttributes() for
5814 // performance: so the class changes happen before DOM is inserted into the document.
5815 // Change back to postCreate() in 2.0. See #11635.
5817 this.inherited(arguments
);
5819 // Automatically monitor mouse events (essentially :hover and :active) on this.domNode
5820 dojo
.forEach(["onmouseenter", "onmouseleave", "onmousedown"], function(e
){
5821 this.connect(this.domNode
, e
, "_cssMouseEvent");
5824 // Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node
5825 dojo
.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active"], function(attr
){
5826 this.watch(attr
, dojo
.hitch(this, "_setStateClass"));
5829 // Events on sub nodes within the widget
5830 for(var ap
in this.cssStateNodes
){
5831 this._trackMouseState(this[ap
], this.cssStateNodes
[ap
]);
5833 // Set state initially; there's probably no hover/active/focus state but widget might be
5834 // disabled/readonly/checked/selected so we want to set CSS classes for those conditions.
5835 this._setStateClass();
5838 _cssMouseEvent: function(/*Event*/ event
){
5840 // Sets hovering and active properties depending on mouse state,
5841 // which triggers _setStateClass() to set appropriate CSS classes for this.domNode.
5846 case "mouseover": // generated on non-IE browsers even though we connected to mouseenter
5847 this._set("hovering", true);
5848 this._set("active", this._mouseDown
);
5852 case "mouseout": // generated on non-IE browsers even though we connected to mouseleave
5853 this._set("hovering", false);
5854 this._set("active", false);
5858 this._set("active", true);
5859 this._mouseDown
= true;
5860 // Set a global event to handle mouseup, so it fires properly
5861 // even if the cursor leaves this.domNode before the mouse up event.
5862 // Alternately could set active=false on mouseout.
5863 var mouseUpConnector
= this.connect(dojo
.body(), "onmouseup", function(){
5864 this._mouseDown
= false;
5865 this._set("active", false);
5866 this.disconnect(mouseUpConnector
);
5873 _setStateClass: function(){
5875 // Update the visual state of the widget by setting the css classes on this.domNode
5876 // (or this.stateNode if defined) by combining this.baseClass with
5877 // various suffixes that represent the current widget state(s).
5880 // In the case where a widget has multiple
5881 // states, it sets the class based on all possible
5882 // combinations. For example, an invalid form widget that is being hovered
5883 // will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover".
5885 // The widget may have one or more of the following states, determined
5886 // by this.state, this.checked, this.valid, and this.selected:
5887 // - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid
5888 // - Incomplete - ValidationTextBox sets this.state to "Incomplete" if the current input value is not finished yet
5889 // - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true
5890 // - Selected - ex: currently selected tab will have this.selected==true
5892 // In addition, it may have one or more of the following states,
5893 // based on this.disabled and flags set in _onMouse (this.active, this.hovering) and from focus manager (this.focused):
5894 // - Disabled - if the widget is disabled
5895 // - Active - if the mouse (or space/enter key?) is being pressed down
5896 // - Focused - if the widget has focus
5897 // - Hover - if the mouse is over the widget
5899 // Compute new set of classes
5900 var newStateClasses
= this.baseClass
.split(" ");
5902 function multiply(modifier
){
5903 newStateClasses
= newStateClasses
.concat(dojo
.map(newStateClasses
, function(c
){ return c
+modifier
; }), "dijit"+modifier
);
5906 if(!this.isLeftToRight()){
5907 // For RTL mode we need to set an addition class like dijitTextBoxRtl.
5912 multiply("Checked");
5915 multiply(this.state
);
5918 multiply("Selected");
5922 multiply("Disabled");
5923 }else if(this.readOnly
){
5924 multiply("ReadOnly");
5928 }else if(this.hovering
){
5934 multiply("Focused");
5937 // Remove old state classes and add new ones.
5938 // For performance concerns we only write into domNode.className once.
5939 var tn
= this.stateNode
|| this.domNode
,
5940 classHash
= {}; // set of all classes (state and otherwise) for node
5942 dojo
.forEach(tn
.className
.split(" "), function(c
){ classHash
[c
] = true; });
5944 if("_stateClasses" in this){
5945 dojo
.forEach(this._stateClasses
, function(c
){ delete classHash
[c
]; });
5948 dojo
.forEach(newStateClasses
, function(c
){ classHash
[c
] = true; });
5950 var newClasses
= [];
5951 for(var c
in classHash
){
5954 tn
.className
= newClasses
.join(" ");
5956 this._stateClasses
= newStateClasses
;
5959 _trackMouseState: function(/*DomNode*/ node
, /*String*/ clazz
){
5961 // Track mouse/focus events on specified node and set CSS class on that node to indicate
5962 // current state. Usually not called directly, but via cssStateNodes attribute.
5964 // Given class=foo, will set the following CSS class on the node
5965 // - fooActive: if the user is currently pressing down the mouse button while over the node
5966 // - fooHover: if the user is hovering the mouse over the node, but not pressing down a button
5967 // - fooFocus: if the node is focused
5969 // Note that it won't set any classes if the widget is disabled.
5971 // Should be a sub-node of the widget, not the top node (this.domNode), since the top node
5972 // is handled specially and automatically just by mixing in this class.
5974 // CSS class name (ex: dijitSliderUpArrow).
5976 // Current state of node (initially false)
5977 // NB: setting specifically to false because dojo.toggleClass() needs true boolean as third arg
5978 var hovering
=false, active
=false, focused
=false;
5981 cn
= dojo
.hitch(this, "connect", node
);
5983 function setClass(){
5984 var disabled
= ("disabled" in self
&& self
.disabled
) || ("readonly" in self
&& self
.readonly
);
5985 dojo
.toggleClass(node
, clazz
+"Hover", hovering
&& !active
&& !disabled
);
5986 dojo
.toggleClass(node
, clazz
+"Active", active
&& !disabled
);
5987 dojo
.toggleClass(node
, clazz
+"Focused", focused
&& !disabled
);
5991 cn("onmouseenter", function(){
5995 cn("onmouseleave", function(){
6000 cn("onmousedown", function(){
6004 cn("onmouseup", function(){
6010 cn("onfocus", function(){
6014 cn("onblur", function(){
6019 // Just in case widget is enabled/disabled while it has focus/hover/active state.
6020 // Maybe this is overkill.
6021 this.watch("disabled", setClass
);
6022 this.watch("readOnly", setClass
);
6028 if(!dojo
._hasResource
["dijit.form._FormWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6029 dojo
._hasResource
["dijit.form._FormWidget"] = true;
6030 dojo
.provide("dijit.form._FormWidget");
6037 dojo
.declare("dijit.form._FormWidget", [dijit
._Widget
, dijit
._Templated
, dijit
._CssStateMixin
],
6040 // Base class for widgets corresponding to native HTML elements such as <checkbox> or <button>,
6041 // which can be children of a <form> node or a `dijit.form.Form` widget.
6044 // Represents a single HTML element.
6045 // All these widgets should have these attributes just like native HTML input elements.
6046 // You can set them during widget construction or afterwards, via `dijit._Widget.attr`.
6048 // They also share some common methods.
6050 // name: [const] String
6051 // Name used when submitting form; same as "name" attribute or plain HTML elements
6055 // Corresponds to the native HTML <input> element's attribute.
6059 // Corresponds to the native HTML <input> element's attribute.
6063 // Corresponds to the native HTML <input> element's attribute.
6066 // tabIndex: Integer
6067 // Order fields are traversed when user hits the tab key
6070 // disabled: Boolean
6071 // Should this widget respond to user input?
6072 // In markup, this is specified as "disabled='disabled'", or just "disabled".
6075 // intermediateChanges: Boolean
6076 // Fires onChange for each value change or only on demand
6077 intermediateChanges
: false,
6079 // scrollOnFocus: Boolean
6080 // On focus, should this widget scroll into view?
6081 scrollOnFocus
: true,
6083 // These mixins assume that the focus node is an INPUT, as many but not all _FormWidgets are.
6084 attributeMap
: dojo
.delegate(dijit
._Widget
.prototype.attributeMap
, {
6087 tabIndex
: "focusNode",
6092 postMixInProperties: function(){
6093 // Setup name=foo string to be referenced from the template (but only if a name has been specified)
6094 // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660
6095 // Regarding escaping, see heading "Attribute values" in
6096 // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
6097 this.nameAttrSetting
= this.name
? ('name="' + this.name
.replace(/'/g, """) + '"') : '';
6098 this.inherited(arguments);
6101 postCreate: function(){
6102 this.inherited(arguments);
6103 this.connect(this.domNode, "onmousedown
", "_onMouseDown
");
6106 _setDisabledAttr: function(/*Boolean*/ value){
6107 this._set("disabled
", value);
6108 dojo.attr(this.focusNode, 'disabled', value);
6110 dojo.attr(this.valueNode, 'disabled', value);
6112 dijit.setWaiState(this.focusNode, "disabled
", value);
6115 // reset these, because after the domNode is disabled, we can no longer receive
6116 // mouse related events, see #4200
6117 this._set("hovering
", false);
6118 this._set("active
", false);
6120 // clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes)
6121 var attachPointNames = "tabIndex
" in this.attributeMap ? this.attributeMap.tabIndex : "focusNode
";
6122 dojo.forEach(dojo.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){
6123 var node = this[attachPointName];
6124 // complex code because tabIndex=-1 on a <div> doesn't work on FF
6125 if(dojo.isWebKit || dijit.hasDefaultTabStop(node)){ // see #11064 about webkit bug
6126 node.setAttribute('tabIndex', "-1");
6128 node.removeAttribute('tabIndex');
6132 if(this.tabIndex != ""){
6133 this.focusNode.setAttribute('tabIndex', this.tabIndex);
6138 setDisabled: function(/*Boolean*/ disabled){
6140 // Deprecated. Use set('disabled', ...) instead.
6141 dojo.deprecated("setDisabled("+disabled+") is deprecated
. Use
set('disabled',"+disabled+") instead
.", "", "2.0");
6142 this.set('disabled', disabled);
6145 _onFocus: function(e){
6146 if(this.scrollOnFocus){
6147 dojo.window.scrollIntoView(this.domNode);
6149 this.inherited(arguments);
6152 isFocusable: function(){
6154 // Tells if this widget is focusable or not. Used internally by dijit.
6157 return !this.disabled && this.focusNode && (dojo.style(this.domNode, "display
") != "none
");
6162 // Put focus on this widget
6164 dijit.focus(this.focusNode);
6168 compare: function(/*anything*/ val1, /*anything*/ val2){
6170 // Compare 2 values (as returned by get('value') for this widget).
6173 if(typeof val1 == "number
" && typeof val2 == "number
"){
6174 return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
6175 }else if(val1 > val2){
6177 }else if(val1 < val2){
6184 onChange: function(newValue){
6186 // Callback when this widget's value is changed.
6191 // _onChangeActive: [private] Boolean
6192 // Indicates that changes to the value should call onChange() callback.
6193 // This is false during widget initialization, to avoid calling onChange()
6194 // when the initial value is set.
6195 _onChangeActive: false,
6197 _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
6199 // Called when the value of the widget is set. Calls onChange() if appropriate
6203 // For a slider, for example, dragging the slider is priorityChange==false,
6204 // but on mouse up, it's priorityChange==true. If intermediateChanges==false,
6205 // onChange is only called form priorityChange=true events.
6208 if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
6209 // this block executes not for a change, but during initialization,
6210 // and is used to store away the original value (or for ToggleButton, the original checked state)
6211 this._resetValue = this._lastValueReported = newValue;
6213 this._pendingOnChange = this._pendingOnChange
6214 || (typeof newValue != typeof this._lastValueReported)
6215 || (this.compare(newValue, this._lastValueReported) != 0);
6216 if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){
6217 this._lastValueReported = newValue;
6218 this._pendingOnChange = false;
6219 if(this._onChangeActive){
6220 if(this._onChangeHandle){
6221 clearTimeout(this._onChangeHandle);
6223 // setTimout allows hidden value processing to run and
6224 // also the onChange handler can safely adjust focus, etc
6225 this._onChangeHandle = setTimeout(dojo.hitch(this,
6227 this._onChangeHandle = null;
6228 this.onChange(newValue);
6229 }), 0); // try to collapse multiple onChange's fired faster than can be processed
6235 // Overrides _Widget.create()
6236 this.inherited(arguments);
6237 this._onChangeActive = true;
6240 destroy: function(){
6241 if(this._onChangeHandle){ // destroy called before last onChange has fired
6242 clearTimeout(this._onChangeHandle);
6243 this.onChange(this._lastValueReported);
6245 this.inherited(arguments);
6248 setValue: function(/*String*/ value){
6250 // Deprecated. Use set('value', ...) instead.
6251 dojo.deprecated("dijit
.form
._FormWidget
:setValue("+value+") is deprecated
. Use
set('value',"+value+") instead
.", "", "2.0");
6252 this.set('value', value);
6255 getValue: function(){
6257 // Deprecated. Use get('value') instead.
6258 dojo.deprecated(this.declaredClass+"::getValue() is deprecated
. Use
get('value') instead
.", "", "2.0");
6259 return this.get('value');
6262 _onMouseDown: function(e){
6263 // If user clicks on the button, even if the mouse is released outside of it,
6264 // this button should get focus (to mimics native browser buttons).
6265 // This is also needed on chrome because otherwise buttons won't get focus at all,
6266 // which leads to bizarre focus restore on Dialog close etc.
6267 if(!e.ctrlKey && dojo.mouseButtons.isLeft(e) && this.isFocusable()){ // !e.ctrlKey to ignore right-click on mac
6268 // Set a global event to handle mouseup, so it fires properly
6269 // even if the cursor leaves this.domNode before the mouse up event.
6270 var mouseUpConnector = this.connect(dojo.body(), "onmouseup
", function(){
6271 if (this.isFocusable()) {
6274 this.disconnect(mouseUpConnector);
6280 dojo.declare("dijit
.form
._FormValueWidget
", dijit.form._FormWidget,
6283 // Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values.
6285 // Each _FormValueWidget represents a single input value, and has a (possibly hidden) <input> element,
6286 // to which it serializes it's input value, so that form submission (either normal submission or via FormBind?)
6287 // works as expected.
6289 // Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared
6290 // directly in the template as read by the parser in order to function. IE is known to specifically
6291 // require the 'name' attribute at element creation time. See #8484, #8660.
6292 // TODO: unclear what that {value: ""} is for; FormWidget.attributeMap copies value to focusNode,
6293 // so maybe {value: ""} is so the value *doesn't* get copied to focusNode?
6294 // Seems like we really want value removed from attributeMap altogether
6295 // (although there's no easy way to do that now)
6297 // readOnly: Boolean
6298 // Should this widget respond to user input?
6299 // In markup, this is specified as "readOnly
".
6300 // Similar to disabled except readOnly form values are submitted.
6303 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
6305 readOnly: "focusNode
"
6308 _setReadOnlyAttr: function(/*Boolean*/ value){
6309 dojo.attr(this.focusNode, 'readOnly', value);
6310 dijit.setWaiState(this.focusNode, "readonly
", value);
6311 this._set("readOnly
", value);
6314 postCreate: function(){
6315 this.inherited(arguments);
6317 if(dojo.isIE < 9 || (dojo.isIE && dojo.isQuirks)){ // IE won't stop the event with keypress
6318 this.connect(this.focusNode || this.domNode, "onkeydown
", this._onKeyDown);
6320 // Update our reset value if it hasn't yet been set (because this.set()
6321 // is only called when there *is* a value)
6322 if(this._resetValue === undefined){
6323 this._lastValueReported = this._resetValue = this.value;
6327 _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
6329 // Hook so set('value', value) works.
6331 // Sets the value of the widget.
6332 // If the value has changed, then fire onChange event, unless priorityChange
6333 // is specified as null (or false?)
6334 this._handleOnChange(newValue, priorityChange);
6337 _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
6339 // Called when the value of the widget has changed. Saves the new value in this.value,
6340 // and calls onChange() if appropriate. See _FormWidget._handleOnChange() for details.
6341 this._set("value
", newValue);
6342 this.inherited(arguments);
6347 // Restore the value to the last value passed to onChange
6348 this._setValueAttr(this._lastValueReported, false);
6353 // Reset the widget's value to what it was at initialization time
6354 this._hasBeenBlurred = false;
6355 this._setValueAttr(this._resetValue, true);
6358 _onKeyDown: function(e){
6359 if(e.keyCode == dojo.keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){
6362 e.preventDefault(); // default behavior needs to be stopped here since keypress is too late
6363 te = document.createEventObject();
6364 te.keyCode = dojo.keys.ESCAPE;
6365 te.shiftKey = e.shiftKey;
6366 e.srcElement.fireEvent('onkeypress', te);
6371 _layoutHackIE7: function(){
6373 // Work around table sizing bugs on IE7 by forcing redraw
6375 if(dojo.isIE == 7){ // fix IE7 layout bug when the widget is scrolled out of sight
6376 var domNode = this.domNode;
6377 var parent = domNode.parentNode;
6378 var pingNode = domNode.firstChild || domNode; // target node most unlikely to have a custom filter
6379 var origFilter = pingNode.style.filter; // save custom filter, most likely nothing
6381 while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet
6383 var disconnectHandle = _this.connect(parent, "onscroll
",
6385 _this.disconnect(disconnectHandle); // only call once
6386 pingNode.style.filter = (new Date()).getMilliseconds(); // set to anything that's unique
6387 setTimeout(function(){ pingNode.style.filter = origFilter }, 0); // restore custom filter, if any
6391 parent = parent.parentNode;
6399 if(!dojo._hasResource["dijit
.dijit
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6400 dojo._hasResource["dijit
.dijit
"] = true;
6401 dojo.provide("dijit
.dijit
");
6414 // A roll-up for common dijit methods
6416 // A rollup file for the build system including the core and common
6420 // | <script type="text
/javascript" src="js/dojo/dijit/dijit.js
"></script>
6425 // All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require)
6427 // And some other stuff that we tend to pull in all the time anyway
6431 if(!dojo._hasResource["dojo
.fx
.Toggler
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6432 dojo._hasResource["dojo
.fx
.Toggler
"] = true;
6433 dojo.provide("dojo
.fx
.Toggler
");
6436 dojo.declare("dojo
.fx
.Toggler
", null, {
6438 // A simple `dojo.Animation` toggler API.
6441 // class constructor for an animation toggler. It accepts a packed
6442 // set of arguments about what type of animation to use in each
6443 // direction, duration, etc. All available members are mixed into
6444 // these animations from the constructor (for example, `node`,
6445 // `showDuration`, `hideDuration`).
6448 // | var t = new dojo.fx.Toggler({
6449 // | node: "nodeId
",
6450 // | showDuration: 500,
6451 // | // hideDuration will default to "200"
6452 // | showFunc: dojo.fx.wipeIn,
6453 // | // hideFunc will default to "fadeOut
"
6455 // | t.show(100); // delay showing for 100ms
6456 // | // ...time passes...
6460 // the node to target for the showing and hiding animations
6463 // showFunc: Function
6464 // The function that returns the `dojo.Animation` to show the node
6465 showFunc: dojo.fadeIn,
6467 // hideFunc: Function
6468 // The function that returns the `dojo.Animation` to hide the node
6469 hideFunc: dojo.fadeOut,
6472 // Time in milliseconds to run the show Animation
6476 // Time in milliseconds to run the hide Animation
6479 // FIXME: need a policy for where the toggler should "be
" the next
6480 // time show/hide are called if we're stopped somewhere in the
6482 // FIXME: also would be nice to specify individual showArgs/hideArgs mixed into
6483 // each animation individually.
6484 // FIXME: also would be nice to have events from the animations exposed/bridged
6497 constructor: function(args){
6500 dojo.mixin(_t, args);
6501 _t.node = args.node;
6502 _t._showArgs = dojo.mixin({}, args);
6503 _t._showArgs.node = _t.node;
6504 _t._showArgs.duration = _t.showDuration;
6505 _t.showAnim = _t.showFunc(_t._showArgs);
6507 _t._hideArgs = dojo.mixin({}, args);
6508 _t._hideArgs.node = _t.node;
6509 _t._hideArgs.duration = _t.hideDuration;
6510 _t.hideAnim = _t.hideFunc(_t._hideArgs);
6512 dojo.connect(_t.showAnim, "beforeBegin
", dojo.hitch(_t.hideAnim, "stop
", true));
6513 dojo.connect(_t.hideAnim, "beforeBegin
", dojo.hitch(_t.showAnim, "stop
", true));
6516 show: function(delay){
6517 // summary: Toggle the node to showing
6519 // Ammount of time to stall playing the show animation
6520 return this.showAnim.play(delay || 0);
6523 hide: function(delay){
6524 // summary: Toggle the node to hidden
6526 // Ammount of time to stall playing the hide animation
6527 return this.hideAnim.play(delay || 0);
6533 if(!dojo._hasResource["dojo
.fx
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6534 dojo._hasResource["dojo
.fx
"] = true;
6535 dojo.provide("dojo
.fx
");
6541 // summary: Effects library on top of Base animations
6548 _fire: function(evt, args){
6550 this[evt].apply(this, args||[]);
6556 var _chain = function(animations){
6558 this._animations = animations||[];
6559 this._current = this._onAnimateCtx = this._onEndCtx = null;
6562 d.forEach(this._animations, function(a){
6563 this.duration += a.duration;
6564 if(a.delay){ this.duration += a.delay; }
6568 _onAnimate: function(){
6569 this._fire("onAnimate
", arguments);
6572 d.disconnect(this._onAnimateCtx);
6573 d.disconnect(this._onEndCtx);
6574 this._onAnimateCtx = this._onEndCtx = null;
6575 if(this._index + 1 == this._animations.length){
6576 this._fire("onEnd
");
6578 // switch animations
6579 this._current = this._animations[++this._index];
6580 this._onAnimateCtx = d.connect(this._current, "onAnimate
", this, "_onAnimate
");
6581 this._onEndCtx = d.connect(this._current, "onEnd
", this, "_onEnd
");
6582 this._current.play(0, true);
6585 play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
6586 if(!this._current){ this._current = this._animations[this._index = 0]; }
6587 if(!gotoStart && this._current.status() == "playing
"){ return this; }
6588 var beforeBegin = d.connect(this._current, "beforeBegin
", this, function(){
6589 this._fire("beforeBegin
");
6591 onBegin = d.connect(this._current, "onBegin
", this, function(arg){
6592 this._fire("onBegin
", arguments);
6594 onPlay = d.connect(this._current, "onPlay
", this, function(arg){
6595 this._fire("onPlay
", arguments);
6596 d.disconnect(beforeBegin);
6597 d.disconnect(onBegin);
6598 d.disconnect(onPlay);
6600 if(this._onAnimateCtx){
6601 d.disconnect(this._onAnimateCtx);
6603 this._onAnimateCtx = d.connect(this._current, "onAnimate
", this, "_onAnimate
");
6605 d.disconnect(this._onEndCtx);
6607 this._onEndCtx = d.connect(this._current, "onEnd
", this, "_onEnd
");
6608 this._current.play.apply(this._current, arguments);
6613 var e = d.connect(this._current, "onPause
", this, function(arg){
6614 this._fire("onPause
", arguments);
6617 this._current.pause();
6621 gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){
6623 var offset = this.duration * percent;
6624 this._current = null;
6625 d.some(this._animations, function(a){
6626 if(a.duration <= offset){
6630 offset -= a.duration;
6634 this._current.gotoPercent(offset / this._current.duration, andPlay);
6638 stop: function(/*boolean?*/ gotoEnd){
6641 for(; this._index + 1 < this._animations.length; ++this._index){
6642 this._animations[this._index].stop(true);
6644 this._current = this._animations[this._index];
6646 var e = d.connect(this._current, "onStop
", this, function(arg){
6647 this._fire("onStop
", arguments);
6650 this._current.stop();
6655 return this._current ? this._current.status() : "stopped
";
6657 destroy: function(){
6658 if(this._onAnimateCtx){ d.disconnect(this._onAnimateCtx); }
6659 if(this._onEndCtx){ d.disconnect(this._onEndCtx); }
6662 d.extend(_chain, _baseObj);
6664 dojo.fx.chain = function(/*dojo.Animation[]*/ animations){
6666 // Chain a list of `dojo.Animation`s to run in sequence
6669 // Return a `dojo.Animation` which will play all passed
6670 // `dojo.Animation` instances in sequence, firing its own
6671 // synthesized events simulating a single animation. (eg:
6672 // onEnd of this animation means the end of the chain,
6673 // not the individual animations within)
6676 // Once `node` is faded out, fade in `otherNode`
6677 // | dojo.fx.chain([
6678 // | dojo.fadeIn({ node:node }),
6679 // | dojo.fadeOut({ node:otherNode })
6682 return new _chain(animations) // dojo.Animation
6685 var _combine = function(animations){
6686 this._animations = animations||[];
6687 this._connects = [];
6691 d.forEach(animations, function(a){
6692 var duration = a.duration;
6693 if(a.delay){ duration += a.delay; }
6694 if(this.duration < duration){ this.duration = duration; }
6695 this._connects.push(d.connect(a, "onEnd
", this, "_onEnd
"));
6698 this._pseudoAnimation = new d.Animation({curve: [0, 1], duration: this.duration});
6700 d.forEach(["beforeBegin
", "onBegin
", "onPlay
", "onAnimate
", "onPause
", "onStop
", "onEnd
"],
6702 self._connects.push(d.connect(self._pseudoAnimation, evt,
6703 function(){ self._fire(evt, arguments); }
6708 d.extend(_combine, {
6709 _doAction: function(action, args){
6710 d.forEach(this._animations, function(a){
6711 a[action].apply(a, args);
6716 if(++this._finished > this._animations.length){
6717 this._fire("onEnd
");
6720 _call: function(action, args){
6721 var t = this._pseudoAnimation;
6722 t[action].apply(t, args);
6724 play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
6726 this._doAction("play
", arguments);
6727 this._call("play
", arguments);
6731 this._doAction("pause
", arguments);
6732 this._call("pause
", arguments);
6735 gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){
6736 var ms = this.duration * percent;
6737 d.forEach(this._animations, function(a){
6738 a.gotoPercent(a.duration < ms ? 1 : (ms / a.duration), andPlay);
6740 this._call("gotoPercent
", arguments);
6743 stop: function(/*boolean?*/ gotoEnd){
6744 this._doAction("stop
", arguments);
6745 this._call("stop
", arguments);
6749 return this._pseudoAnimation.status();
6751 destroy: function(){
6752 d.forEach(this._connects, dojo.disconnect);
6755 d.extend(_combine, _baseObj);
6757 dojo.fx.combine = function(/*dojo.Animation[]*/ animations){
6759 // Combine a list of `dojo.Animation`s to run in parallel
6762 // Combine an array of `dojo.Animation`s to run in parallel,
6763 // providing a new `dojo.Animation` instance encompasing each
6764 // animation, firing standard animation events.
6767 // Fade out `node` while fading in `otherNode` simultaneously
6768 // | dojo.fx.combine([
6769 // | dojo.fadeIn({ node:node }),
6770 // | dojo.fadeOut({ node:otherNode })
6774 // When the longest animation ends, execute a function:
6775 // | var anim = dojo.fx.combine([
6776 // | dojo.fadeIn({ node: n, duration:700 }),
6777 // | dojo.fadeOut({ node: otherNode, duration: 300 })
6779 // | dojo.connect(anim, "onEnd
", function(){
6780 // | // overall animation is done.
6782 // | anim.play(); // play the animation
6784 return new _combine(animations); // dojo.Animation
6787 dojo.fx.wipeIn = function(/*Object*/ args){
6789 // Expand a node to it's natural height.
6792 // Returns an animation that will expand the
6793 // node defined in 'args' object from it's current height to
6794 // it's natural height (with no scrollbar).
6795 // Node must have no margin/border/padding.
6798 // A hash-map of standard `dojo.Animation` constructor properties
6799 // (such as easing: node: duration: and so on)
6802 // | dojo.fx.wipeIn({
6805 var node = args.node = d.byId(args.node), s = node.style, o;
6807 var anim = d.animateProperty(d.mixin({
6810 // wrapped in functions so we wait till the last second to query (in case value has changed)
6812 // start at current [computed] height, but use 1px rather than 0
6813 // because 0 causes IE to display the whole panel
6815 s.overflow = "hidden
";
6816 if(s.visibility == "hidden
" || s.display == "none
"){
6822 var height = d.style(node, "height
");
6823 return Math.max(height, 1);
6827 return node.scrollHeight;
6833 d.connect(anim, "onEnd
", function(){
6838 return anim; // dojo.Animation
6841 dojo.fx.wipeOut = function(/*Object*/ args){
6843 // Shrink a node to nothing and hide it.
6846 // Returns an animation that will shrink node defined in "args
"
6847 // from it's current height to 1px, and then hide it.
6850 // A hash-map of standard `dojo.Animation` constructor properties
6851 // (such as easing: node: duration: and so on)
6854 // | dojo.fx.wipeOut({ node:"someId
" }).play()
6856 var node = args.node = d.byId(args.node), s = node.style, o;
6858 var anim = d.animateProperty(d.mixin({
6861 end: 1 // 0 causes IE to display the whole panel
6866 d.connect(anim, "beforeBegin
", function(){
6868 s.overflow = "hidden
";
6871 d.connect(anim, "onEnd
", function(){
6877 return anim; // dojo.Animation
6880 dojo.fx.slideTo = function(/*Object*/ args){
6882 // Slide a node to a new top/left position
6885 // Returns an animation that will slide "node
"
6886 // defined in args Object from its current position to
6887 // the position defined by (args.left, args.top).
6890 // A hash-map of standard `dojo.Animation` constructor properties
6891 // (such as easing: node: duration: and so on). Special args members
6892 // are `top` and `left`, which indicate the new position to slide to.
6895 // | dojo.fx.slideTo({ node: node, left:"40", top:"50", units:"px
" }).play()
6897 var node = args.node = d.byId(args.node),
6898 top = null, left = null;
6900 var init = (function(n){
6902 var cs = d.getComputedStyle(n);
6903 var pos = cs.position;
6904 top = (pos == 'absolute' ? n.offsetTop : parseInt(cs.top) || 0);
6905 left = (pos == 'absolute' ? n.offsetLeft : parseInt(cs.left) || 0);
6906 if(pos != 'absolute' && pos != 'relative'){
6907 var ret = d.position(n, true);
6910 n.style.position="absolute
";
6911 n.style.top=top+"px
";
6912 n.style.left=left+"px
";
6918 var anim = d.animateProperty(d.mixin({
6921 left: args.left || 0
6924 d.connect(anim, "beforeBegin
", anim, init);
6926 return anim; // dojo.Animation
6933 if(!dojo._hasResource["dojo
.NodeList
-fx
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6934 dojo._hasResource["dojo
.NodeList
-fx
"] = true;
6935 dojo.provide("dojo
.NodeList
-fx
");
6940 dojo["NodeList
-fx
"] = {
6941 // summary: Adds dojo.fx animation support to dojo.query()
6945 dojo.extend(dojo.NodeList, {
6946 _anim: function(obj, method, args){
6948 var a = dojo.fx.combine(
6949 this.map(function(item){
6950 var tmpArgs = { node: item };
6951 dojo.mixin(tmpArgs, args);
6952 return obj[method](tmpArgs);
6955 return args.auto ? a.play() && this : a; // dojo.Animation|dojo.NodeList
6958 wipeIn: function(args){
6960 // wipe in all elements of this NodeList via `dojo.fx.wipeIn`
6963 // Additional dojo.Animation arguments to mix into this set with the addition of
6964 // an `auto` parameter.
6966 // returns: dojo.Animation|dojo.NodeList
6967 // A special args member `auto` can be passed to automatically play the animation.
6968 // If args.auto is present, the original dojo.NodeList will be returned for further
6969 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
6972 // Fade in all tables with class "blah
":
6973 // | dojo.query("table
.blah
").wipeIn().play();
6976 // Utilizing `auto` to get the NodeList back:
6977 // | dojo.query(".titles
").wipeIn({ auto:true }).onclick(someFunction);
6979 return this._anim(dojo.fx, "wipeIn
", args); // dojo.Animation|dojo.NodeList
6982 wipeOut: function(args){
6984 // wipe out all elements of this NodeList via `dojo.fx.wipeOut`
6987 // Additional dojo.Animation arguments to mix into this set with the addition of
6988 // an `auto` parameter.
6990 // returns: dojo.Animation|dojo.NodeList
6991 // A special args member `auto` can be passed to automatically play the animation.
6992 // If args.auto is present, the original dojo.NodeList will be returned for further
6993 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
6996 // Wipe out all tables with class "blah
":
6997 // | dojo.query("table
.blah
").wipeOut().play();
6998 return this._anim(dojo.fx, "wipeOut
", args); // dojo.Animation|dojo.NodeList
7001 slideTo: function(args){
7003 // slide all elements of the node list to the specified place via `dojo.fx.slideTo`
7006 // Additional dojo.Animation arguments to mix into this set with the addition of
7007 // an `auto` parameter.
7009 // returns: dojo.Animation|dojo.NodeList
7010 // A special args member `auto` can be passed to automatically play the animation.
7011 // If args.auto is present, the original dojo.NodeList will be returned for further
7012 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
7015 // | Move all tables with class "blah
" to 300/300:
7016 // | dojo.query("table
.blah
").slideTo({
7020 return this._anim(dojo.fx, "slideTo
", args); // dojo.Animation|dojo.NodeList
7024 fadeIn: function(args){
7026 // fade in all elements of this NodeList via `dojo.fadeIn`
7029 // Additional dojo.Animation arguments to mix into this set with the addition of
7030 // an `auto` parameter.
7032 // returns: dojo.Animation|dojo.NodeList
7033 // A special args member `auto` can be passed to automatically play the animation.
7034 // If args.auto is present, the original dojo.NodeList will be returned for further
7035 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
7038 // Fade in all tables with class "blah
":
7039 // | dojo.query("table
.blah
").fadeIn().play();
7040 return this._anim(dojo, "fadeIn
", args); // dojo.Animation|dojo.NodeList
7043 fadeOut: function(args){
7045 // fade out all elements of this NodeList via `dojo.fadeOut`
7048 // Additional dojo.Animation arguments to mix into this set with the addition of
7049 // an `auto` parameter.
7051 // returns: dojo.Animation|dojo.NodeList
7052 // A special args member `auto` can be passed to automatically play the animation.
7053 // If args.auto is present, the original dojo.NodeList will be returned for further
7054 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
7057 // Fade out all elements with class "zork
":
7058 // | dojo.query(".zork
").fadeOut().play();
7060 // Fade them on a delay and do something at the end:
7061 // | var fo = dojo.query(".zork
").fadeOut();
7062 // | dojo.connect(fo, "onEnd
", function(){ /*...*/ });
7066 // | dojo.query("li
").fadeOut({ auto:true }).filter(filterFn).forEach(doit);
7068 return this._anim(dojo, "fadeOut
", args); // dojo.Animation|dojo.NodeList
7071 animateProperty: function(args){
7073 // Animate all elements of this NodeList across the properties specified.
7074 // syntax identical to `dojo.animateProperty`
7076 // returns: dojo.Animation|dojo.NodeList
7077 // A special args member `auto` can be passed to automatically play the animation.
7078 // If args.auto is present, the original dojo.NodeList will be returned for further
7079 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
7082 // | dojo.query(".zork
").animateProperty({
7085 // | color: { start: "black
", end: "white
" },
7086 // | left: { end: 300 }
7091 // | dojo.query(".grue
").animateProperty({
7096 // | }).onclick(handler);
7097 return this._anim(dojo, "animateProperty
", args); // dojo.Animation|dojo.NodeList
7100 anim: function( /*Object*/ properties,
7101 /*Integer?*/ duration,
7102 /*Function?*/ easing,
7103 /*Function?*/ onEnd,
7104 /*Integer?*/ delay){
7106 // Animate one or more CSS properties for all nodes in this list.
7107 // The returned animation object will already be playing when it
7108 // is returned. See the docs for `dojo.anim` for full details.
7109 // properties: Object
7110 // the properties to animate. does NOT support the `auto` parameter like other
7111 // NodeList-fx methods.
7112 // duration: Integer?
7113 // Optional. The time to run the animations for
7114 // easing: Function?
7115 // Optional. The easing function to use.
7117 // A function to be called when the animation ends
7119 // how long to delay playing the returned animation
7121 // Another way to fade out:
7122 // | dojo.query(".thinger
").anim({ opacity: 0 });
7124 // animate all elements with the "thigner
" class to a width of 500
7125 // pixels over half a second
7126 // | dojo.query(".thinger
").anim({ width: 500 }, 700);
7127 var canim = dojo.fx.combine(
7128 this.map(function(item){
7129 return dojo.animateProperty({
7131 properties: properties,
7132 duration: duration||350,
7138 dojo.connect(canim, "onEnd
", onEnd);
7140 return canim.play(delay||0); // dojo.Animation
7146 if(!dojo._hasResource["dojo
.colors
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7147 dojo._hasResource["dojo
.colors
"] = true;
7148 dojo.provide("dojo
.colors
");
7150 dojo.getObject("colors
", true, dojo);
7152 //TODO: this module appears to break naming conventions
7156 // summary: Color utilities
7161 // this is a standard conversion prescribed by the CSS3 Color Module
7162 var hue2rgb = function(m1, m2, h){
7166 if(h6 < 1){ return m1 + (m2 - m1) * h6; }
7167 if(2 * h < 1){ return m2; }
7168 if(3 * h < 2){ return m1 + (m2 - m1) * (2 / 3 - h) * 6; }
7172 dojo.colorFromRgb = function(/*String*/ color, /*dojo.Color?*/ obj){
7174 // get rgb(a) array from css-style color declarations
7176 // this function can handle all 4 CSS3 Color Module formats: rgb,
7177 // rgba, hsl, hsla, including rgb(a) with percentage values.
7178 var m = color.toLowerCase().match(/^(rgba?|hsla?)\(([\s\.\-,%0-9]+)\)/);
7180 var c = m[2].split(/\s*,\s*/), l = c.length, t = m[1], a;
7181 if((t == "rgb
" && l == 3) || (t == "rgba
" && l == 4)){
7183 if(r.charAt(r.length - 1) == "%"){
7184 // 3 rgb percentage values
7185 a = dojo.map(c, function(x){
7186 return parseFloat(x) * 2.56;
7188 if(l == 4){ a[3] = c[3]; }
7189 return dojo.colorFromArray(a, obj); // dojo.Color
7191 return dojo.colorFromArray(c, obj); // dojo.Color
7193 if((t == "hsl
" && l == 3) || (t == "hsla
" && l == 4)){
7194 // normalize hsl values
7195 var H = ((parseFloat(c[0]) % 360) + 360) % 360 / 360,
7196 S = parseFloat(c[1]) / 100,
7197 L = parseFloat(c[2]) / 100,
7198 // calculate rgb according to the algorithm
7199 // recommended by the CSS3 Color Module
7200 m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S,
7203 hue2rgb(m1, m2, H + 1 / 3) * 256,
7204 hue2rgb(m1, m2, H) * 256,
7205 hue2rgb(m1, m2, H - 1 / 3) * 256,
7208 if(l == 4){ a[3] = c[3]; }
7209 return dojo.colorFromArray(a, obj); // dojo.Color
7212 return null; // dojo.Color
7215 var confine = function(c, low, high){
7217 // sanitize a color component by making sure it is a number,
7218 // and clamping it to valid values
7220 return isNaN(c) ? high : c < low ? low : c > high ? high : c; // Number
7223 dojo.Color.prototype.sanitize = function(){
7224 // summary: makes sure that the object has correct attributes
7226 t.r = Math.round(confine(t.r, 0, 255));
7227 t.g = Math.round(confine(t.g, 0, 255));
7228 t.b = Math.round(confine(t.b, 0, 255));
7229 t.a = confine(t.a, 0, 1);
7230 return this; // dojo.Color
7235 dojo.colors.makeGrey = function(/*Number*/ g, /*Number?*/ a){
7236 // summary: creates a greyscale color with an optional alpha
7237 return dojo.colorFromArray([g, g, g, a]);
7240 // mixin all CSS3 named colors not already in _base, along with SVG 1.0 variant spellings
7241 dojo.mixin(dojo.Color.named, {
7242 aliceblue: [240,248,255],
7243 antiquewhite: [250,235,215],
7244 aquamarine: [127,255,212],
7245 azure: [240,255,255],
7246 beige: [245,245,220],
7247 bisque: [255,228,196],
7248 blanchedalmond: [255,235,205],
7249 blueviolet: [138,43,226],
7251 burlywood: [222,184,135],
7252 cadetblue: [95,158,160],
7253 chartreuse: [127,255,0],
7254 chocolate: [210,105,30],
7255 coral: [255,127,80],
7256 cornflowerblue: [100,149,237],
7257 cornsilk: [255,248,220],
7258 crimson: [220,20,60],
7260 darkblue: [0,0,139],
7261 darkcyan: [0,139,139],
7262 darkgoldenrod: [184,134,11],
7263 darkgray: [169,169,169],
7264 darkgreen: [0,100,0],
7265 darkgrey: [169,169,169],
7266 darkkhaki: [189,183,107],
7267 darkmagenta: [139,0,139],
7268 darkolivegreen: [85,107,47],
7269 darkorange: [255,140,0],
7270 darkorchid: [153,50,204],
7272 darksalmon: [233,150,122],
7273 darkseagreen: [143,188,143],
7274 darkslateblue: [72,61,139],
7275 darkslategray: [47,79,79],
7276 darkslategrey: [47,79,79],
7277 darkturquoise: [0,206,209],
7278 darkviolet: [148,0,211],
7279 deeppink: [255,20,147],
7280 deepskyblue: [0,191,255],
7281 dimgray: [105,105,105],
7282 dimgrey: [105,105,105],
7283 dodgerblue: [30,144,255],
7284 firebrick: [178,34,34],
7285 floralwhite: [255,250,240],
7286 forestgreen: [34,139,34],
7287 gainsboro: [220,220,220],
7288 ghostwhite: [248,248,255],
7290 goldenrod: [218,165,32],
7291 greenyellow: [173,255,47],
7292 grey: [128,128,128],
7293 honeydew: [240,255,240],
7294 hotpink: [255,105,180],
7295 indianred: [205,92,92],
7297 ivory: [255,255,240],
7298 khaki: [240,230,140],
7299 lavender: [230,230,250],
7300 lavenderblush: [255,240,245],
7301 lawngreen: [124,252,0],
7302 lemonchiffon: [255,250,205],
7303 lightblue: [173,216,230],
7304 lightcoral: [240,128,128],
7305 lightcyan: [224,255,255],
7306 lightgoldenrodyellow: [250,250,210],
7307 lightgray: [211,211,211],
7308 lightgreen: [144,238,144],
7309 lightgrey: [211,211,211],
7310 lightpink: [255,182,193],
7311 lightsalmon: [255,160,122],
7312 lightseagreen: [32,178,170],
7313 lightskyblue: [135,206,250],
7314 lightslategray: [119,136,153],
7315 lightslategrey: [119,136,153],
7316 lightsteelblue: [176,196,222],
7317 lightyellow: [255,255,224],
7318 limegreen: [50,205,50],
7319 linen: [250,240,230],
7320 magenta: [255,0,255],
7321 mediumaquamarine: [102,205,170],
7322 mediumblue: [0,0,205],
7323 mediumorchid: [186,85,211],
7324 mediumpurple: [147,112,219],
7325 mediumseagreen: [60,179,113],
7326 mediumslateblue: [123,104,238],
7327 mediumspringgreen: [0,250,154],
7328 mediumturquoise: [72,209,204],
7329 mediumvioletred: [199,21,133],
7330 midnightblue: [25,25,112],
7331 mintcream: [245,255,250],
7332 mistyrose: [255,228,225],
7333 moccasin: [255,228,181],
7334 navajowhite: [255,222,173],
7335 oldlace: [253,245,230],
7336 olivedrab: [107,142,35],
7337 orange: [255,165,0],
7338 orangered: [255,69,0],
7339 orchid: [218,112,214],
7340 palegoldenrod: [238,232,170],
7341 palegreen: [152,251,152],
7342 paleturquoise: [175,238,238],
7343 palevioletred: [219,112,147],
7344 papayawhip: [255,239,213],
7345 peachpuff: [255,218,185],
7347 pink: [255,192,203],
7348 plum: [221,160,221],
7349 powderblue: [176,224,230],
7350 rosybrown: [188,143,143],
7351 royalblue: [65,105,225],
7352 saddlebrown: [139,69,19],
7353 salmon: [250,128,114],
7354 sandybrown: [244,164,96],
7355 seagreen: [46,139,87],
7356 seashell: [255,245,238],
7357 sienna: [160,82,45],
7358 skyblue: [135,206,235],
7359 slateblue: [106,90,205],
7360 slategray: [112,128,144],
7361 slategrey: [112,128,144],
7362 snow: [255,250,250],
7363 springgreen: [0,255,127],
7364 steelblue: [70,130,180],
7366 thistle: [216,191,216],
7367 tomato: [255,99,71],
7368 transparent: [0, 0, 0, 0],
7369 turquoise: [64,224,208],
7370 violet: [238,130,238],
7371 wheat: [245,222,179],
7372 whitesmoke: [245,245,245],
7373 yellowgreen: [154,205,50]
7378 if(!dojo._hasResource["dojo
.i18n
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7379 dojo._hasResource["dojo
.i18n
"] = true;
7380 dojo.provide("dojo
.i18n
");
7382 dojo.getObject("i18n
", true, dojo);
7386 // summary: Utility classes to enable loading of resources for internationalization (i18n)
7390 // when using a real AMD loader, dojo.i18n.getLocalization is already defined by dojo/lib/backCompat
7391 dojo.i18n.getLocalization = dojo.i18n.getLocalization || function(/*String*/packageName, /*String*/bundleName, /*String?*/locale){
7393 // Returns an Object containing the localization for a given resource
7394 // bundle in a package, matching the specified locale.
7396 // Returns a hash containing name/value pairs in its prototypesuch
7397 // that values can be easily overridden. Throws an exception if the
7398 // bundle is not found. Bundle must have already been loaded by
7399 // `dojo.requireLocalization()` or by a build optimization step. NOTE:
7400 // try not to call this method as part of an object property
7401 // definition (`var foo = { bar: dojo.i18n.getLocalization() }`). In
7402 // some loading situations, the bundle may not be available in time
7403 // for the object definition. Instead, call this method inside a
7404 // function that is run after all modules load or the page loads (like
7405 // in `dojo.addOnLoad()`), or in a widget lifecycle method.
7407 // package which is associated with this resource
7409 // the base filename of the resource bundle (without the ".js
" suffix)
7411 // the variant to load (optional). By default, the locale defined by
7412 // the host environment: dojo.locale
7414 locale = dojo.i18n.normalizeLocale(locale);
7416 // look for nearest locale match
7417 var elements = locale.split('-');
7418 var module = [packageName,"nls
",bundleName].join('.');
7419 var bundle = dojo._loadedModules[module];
7422 for(var i = elements.length; i > 0; i--){
7423 var loc = elements.slice(0, i).join('_');
7425 localization = bundle[loc];
7430 localization = bundle.ROOT;
7433 // make a singleton prototype so that the caller won't accidentally change the values globally
7435 var clazz = function(){};
7436 clazz.prototype = localization;
7437 return new clazz(); // Object
7441 throw new Error("Bundle not found
: " + bundleName + " in " + packageName+" , locale
=" + locale);
7444 dojo.i18n.normalizeLocale = function(/*String?*/locale){
7446 // Returns canonical form of locale, as used by Dojo.
7449 // All variants are case-insensitive and are separated by '-' as specified in [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt).
7450 // If no locale is specified, the dojo.locale is returned. dojo.locale is defined by
7451 // the user agent's locale unless overridden by djConfig.
7453 var result = locale ? locale.toLowerCase() : dojo.locale;
7454 if(result == "root
"){
7457 return result; // String
7460 dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundleName, /*String?*/locale, /*String?*/availableFlatLocales){
7462 // See dojo.requireLocalization()
7464 // Called by the bootstrap, but factored out so that it is only
7465 // included in the build when needed.
7467 var targetLocale = dojo.i18n.normalizeLocale(locale);
7468 var bundlePackage = [moduleName, "nls
", bundleName].join(".");
7470 // When loading these resources, the packaging does not match what is
7471 // on disk. This is an implementation detail, as this is just a
7472 // private data structure to hold the loaded resources. e.g.
7473 // `tests/hello/nls/en-us/salutations.js` is loaded as the object
7474 // `tests.hello.nls.salutations.en_us={...}` The structure on disk is
7475 // intended to be most convenient for developers and translators, but
7476 // in memory it is more logical and efficient to store in a different
7477 // order. Locales cannot use dashes, since the resulting path will
7478 // not evaluate as valid JS, so we translate them to underscores.
7480 //Find the best-match locale to load if we have available flat locales.
7481 var bestLocale = "";
7482 if(availableFlatLocales){
7483 var flatLocales = availableFlatLocales.split(",");
7484 for(var i = 0; i < flatLocales.length; i++){
7485 //Locale must match from start of string.
7486 //Using ["indexOf
"] so customBase builds do not see
7487 //this as a dojo._base.array dependency.
7488 if(targetLocale["indexOf
"](flatLocales[i]) == 0){
7489 if(flatLocales[i].length > bestLocale.length){
7490 bestLocale = flatLocales[i];
7495 bestLocale = "ROOT
";
7499 //See if the desired locale is already loaded.
7500 var tempLocale = availableFlatLocales ? bestLocale : targetLocale;
7501 var bundle = dojo._loadedModules[bundlePackage];
7502 var localizedBundle = null;
7504 if(dojo.config.localizationComplete && bundle._built){return;}
7505 var jsLoc = tempLocale.replace(/-/g, '_');
7506 var translationPackage = bundlePackage+"."+jsLoc;
7507 localizedBundle = dojo._loadedModules[translationPackage];
7510 if(!localizedBundle){
7511 bundle = dojo["provide
"](bundlePackage);
7512 var syms = dojo._getModuleSymbols(moduleName);
7513 var modpath = syms.concat("nls
").join("/");
7516 dojo.i18n._searchLocalePath(tempLocale, availableFlatLocales, function(loc){
7517 var jsLoc = loc.replace(/-/g, '_');
7518 var translationPackage = bundlePackage + "." + jsLoc;
7520 if(!dojo._loadedModules[translationPackage]){
7521 // Mark loaded whether it's found or not, so that further load attempts will not be made
7522 dojo["provide
"](translationPackage);
7523 var module = [modpath];
7524 if(loc != "ROOT
"){module.push(loc);}
7525 module.push(bundleName);
7526 var filespec = module.join("/") + '.js';
7527 loaded = dojo._loadPath(filespec, null, function(hash){
7528 hash = hash.root || hash;
7529 // Use singleton with prototype to point to parent bundle, then mix-in result from loadPath
7530 var clazz = function(){};
7531 clazz.prototype = parent;
7532 bundle[jsLoc] = new clazz();
7533 for(var j in hash){ bundle[jsLoc][j] = hash[j]; }
7538 if(loaded && bundle[jsLoc]){
7539 parent = bundle[jsLoc];
7541 bundle[jsLoc] = parent;
7544 if(availableFlatLocales){
7545 //Stop the locale path searching if we know the availableFlatLocales, since
7546 //the first call to this function will load the only bundle that is needed.
7552 //Save the best locale bundle as the target locale bundle when we know the
7553 //the available bundles.
7554 if(availableFlatLocales && targetLocale != bestLocale){
7555 bundle[targetLocale.replace(/-/g, '_')] = bundle[bestLocale.replace(/-/g, '_')];
7560 // If other locales are used, dojo.requireLocalization should load them as
7561 // well, by default.
7563 // Override dojo.requireLocalization to do load the default bundle, then
7564 // iterate through the extraLocale list and load those translations as
7565 // well, unless a particular locale was requested.
7567 var extra = dojo.config.extraLocale;
7569 if(!extra instanceof Array){
7573 var req = dojo.i18n._requireLocalization;
7574 dojo.i18n._requireLocalization = function(m, b, locale, availableFlatLocales){
7575 req(m,b,locale, availableFlatLocales);
7577 for(var i=0; i<extra.length; i++){
7578 req(m,b,extra[i], availableFlatLocales);
7584 dojo.i18n._searchLocalePath = function(/*String*/locale, /*Boolean*/down, /*Function*/searchFunc){
7586 // A helper method to assist in searching for locale-based resources.
7587 // Will iterate through the variants of a particular locale, either up
7588 // or down, executing a callback function. For example, "en
-us
" and
7589 // true will try "en
-us
" followed by "en
" and finally "ROOT
".
7591 locale = dojo.i18n.normalizeLocale(locale);
7593 var elements = locale.split('-');
7594 var searchlist = [];
7595 for(var i = elements.length; i > 0; i--){
7596 searchlist.push(elements.slice(0, i).join('-'));
7598 searchlist.push(false);
7599 if(down){searchlist.reverse();}
7601 for(var j = searchlist.length - 1; j >= 0; j--){
7602 var loc = searchlist[j] || "ROOT
";
7603 var stop = searchFunc(loc);
7608 dojo.i18n._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated){
7610 // Load built, flattened resource bundles, if available for all
7611 // locales used in the page. Only called by built layer files.
7613 function preload(locale){
7614 locale = dojo.i18n.normalizeLocale(locale);
7615 dojo.i18n._searchLocalePath(locale, true, function(loc){
7616 for(var i=0; i<localesGenerated.length;i++){
7617 if(localesGenerated[i] == loc){
7618 dojo["require
"](bundlePrefix+"_
"+loc);
7619 return true; // Boolean
7622 return false; // Boolean
7626 var extra = dojo.config.extraLocale||[];
7627 for(var i=0; i<extra.length; i++){
7634 if(!dojo._hasResource["dijit
._PaletteMixin
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7635 dojo._hasResource["dijit
._PaletteMixin
"] = true;
7636 dojo.provide("dijit
._PaletteMixin
");
7640 dojo.declare("dijit
._PaletteMixin
",
7641 [dijit._CssStateMixin],
7644 // A keyboard accessible palette, for picking a color/emoticon/etc.
7646 // A mixin for a grid showing various entities, so the user can pick a certain entity.
7648 // defaultTimeout: Number
7649 // Number of milliseconds before a held key or button becomes typematic
7650 defaultTimeout: 500,
7652 // timeoutChangeRate: Number
7653 // Fraction of time used to change the typematic timer between events
7654 // 1.0 means that each typematic event fires at defaultTimeout intervals
7655 // < 1.0 means that each typematic event fires at an increasing faster rate
7656 timeoutChangeRate: 0.90,
7659 // Currently selected color/emoticon/etc.
7662 // _selectedCell: [private] Integer
7663 // Index of the currently selected cell. Initially, none selected
7667 // _currentFocus: [private] DomNode
7668 // The currently focused cell (if the palette itself has focus), or otherwise
7669 // the cell to be focused when the palette itself gets focus.
7670 // Different from value, which represents the selected (i.e. clicked) cell.
7671 _currentFocus: null,
7675 // _xDim: [protected] Integer
7676 // This is the number of cells horizontally across.
7681 // _yDim: [protected] Integer
7682 // This is the number of cells vertically down.
7687 // Widget tab index.
7690 // cellClass: [protected] String
7691 // CSS class applied to each cell in the palette
7692 cellClass: "dijitPaletteCell
",
7694 // dyeClass: [protected] String
7695 // Name of javascript class for Object created for each cell of the palette.
7696 // dyeClass should implements dijit.Dye interface
7699 _preparePalette: function(choices, titles, dyeClassObj) {
7701 // Subclass must call _preparePalette() from postCreate(), passing in the tooltip
7703 // choices: String[][]
7704 // id's for each cell of the palette, used to create Dye JS object for each cell
7706 // Localized tooltip for each cell
7707 // dyeClassObj: Constructor?
7708 // If specified, use this constructor rather than this.dyeClass
7711 var url = this._blankGif;
7713 dyeClassObj = dyeClassObj || dojo.getObject(this.dyeClass);
7715 for(var row=0; row < choices.length; row++){
7716 var rowNode = dojo.create("tr
", {tabIndex: "-1"}, this.gridNode);
7717 for(var col=0; col < choices[row].length; col++){
7718 var value = choices[row][col];
7720 var cellObject = new dyeClassObj(value, row, col);
7722 var cellNode = dojo.create("td
", {
7723 "class": this.cellClass,
7725 title: titles[value]
7728 // prepare cell inner structure
7729 cellObject.fillCell(cellNode, url);
7731 this.connect(cellNode, "ondijitclick
", "_onCellClick
");
7732 this._trackMouseState(cellNode, this.cellClass);
7734 dojo.place(cellNode, rowNode);
7736 cellNode.index = this._cells.length;
7738 // save cell info into _cells
7739 this._cells.push({node:cellNode, dye:cellObject});
7743 this._xDim = choices[0].length;
7744 this._yDim = choices.length;
7746 // Now set all events
7747 // The palette itself is navigated to with the tab key on the keyboard
7748 // Keyboard navigation within the Palette is with the arrow keys
7749 // Spacebar selects the cell.
7750 // For the up key the index is changed by negative the x dimension.
7752 var keyIncrementMap = {
7753 UP_ARROW: -this._xDim,
7754 // The down key the index is increase by the x dimension.
7755 DOWN_ARROW: this._xDim,
7756 // Right and left move the index by 1.
7757 RIGHT_ARROW: this.isLeftToRight() ? 1 : -1,
7758 LEFT_ARROW: this.isLeftToRight() ? -1 : 1
7760 for(var key in keyIncrementMap){
7761 this._connects.push(
7762 dijit.typematic.addKeyListener(
7764 {charOrCode:dojo.keys[key], ctrlKey:false, altKey:false, shiftKey:false},
7767 var increment = keyIncrementMap[key];
7768 return function(count){ this._navigateByKey(increment, count); };
7770 this.timeoutChangeRate,
7777 postCreate: function(){
7778 this.inherited(arguments);
7780 // Set initial navigable node.
7781 this._setCurrent(this._cells[0].node);
7786 // Focus this widget. Puts focus on the most recently focused cell.
7788 // The cell already has tabIndex set, just need to set CSS and focus it
7789 dijit.focus(this._currentFocus);
7792 _onCellClick: function(/*Event*/ evt){
7794 // Handler for click, enter key & space key. Selects the cell.
7800 var target = evt.currentTarget,
7801 value = this._getDye(target).getValue();
7803 // First focus the clicked cell, and then send onChange() notification.
7804 // onChange() (via _setValueAttr) must be after the focus call, because
7805 // it may trigger a refocus to somewhere else (like the Editor content area), and that
7806 // second focus should win.
7807 // Use setTimeout because IE doesn't like changing focus inside of an event handler.
7808 this._setCurrent(target);
7809 setTimeout(dojo.hitch(this, function(){
7810 dijit.focus(target);
7811 this._setValueAttr(value, true);
7814 // workaround bug where hover class is not removed on popup because the popup is
7815 // closed and then there's no onblur event on the cell
7816 dojo.removeClass(target, "dijitPaletteCellHover
");
7818 dojo.stopEvent(evt);
7821 _setCurrent: function(/*DomNode*/ node){
7823 // Sets which node is the focused cell.
7825 // At any point in time there's exactly one
7826 // cell with tabIndex != -1. If focus is inside the palette then
7827 // focus is on that cell.
7829 // After calling this method, arrow key handlers and mouse click handlers
7830 // should focus the cell in a setTimeout().
7833 if("_currentFocus
" in this){
7834 // Remove tabIndex on old cell
7835 dojo.attr(this._currentFocus, "tabIndex
", "-1");
7838 // Set tabIndex of new cell
7839 this._currentFocus = node;
7841 dojo.attr(node, "tabIndex
", this.tabIndex);
7845 _setValueAttr: function(value, priorityChange){
7847 // This selects a cell. It triggers the onChange event.
7848 // value: String value of the cell to select
7852 // Optional parameter used to tell the select whether or not to fire
7855 // clear old selected cell
7856 if(this._selectedCell >= 0){
7857 dojo.removeClass(this._cells[this._selectedCell].node, "dijitPaletteCellSelected
");
7859 this._selectedCell = -1;
7861 // search for cell matching specified value
7863 for(var i = 0; i < this._cells.length; i++){
7864 if(value == this._cells[i].dye.getValue()){
7865 this._selectedCell = i;
7866 dojo.addClass(this._cells[i].node, "dijitPaletteCellSelected
");
7872 // record new value, or null if no matching cell
7873 this._set("value
", this._selectedCell >= 0 ? value : null);
7875 if(priorityChange || priorityChange === undefined){
7876 this.onChange(value);
7880 onChange: function(value){
7882 // Callback when a cell is selected.
7884 // Value corresponding to cell.
7887 _navigateByKey: function(increment, typeCount){
7889 // This is the callback for typematic.
7890 // It changes the focus and the highlighed cell.
7892 // How much the key is navigated.
7894 // How many times typematic has fired.
7898 // typecount == -1 means the key is released.
7899 if(typeCount == -1){ return; }
7901 var newFocusIndex = this._currentFocus.index + increment;
7902 if(newFocusIndex < this._cells.length && newFocusIndex > -1){
7903 var focusNode = this._cells[newFocusIndex].node;
7904 this._setCurrent(focusNode);
7906 // Actually focus the node, for the benefit of screen readers.
7907 // Use setTimeout because IE doesn't like changing focus inside of an event handler
7908 setTimeout(dojo.hitch(dijit, "focus
", focusNode), 0);
7912 _getDye: function(/*DomNode*/ cell){
7914 // Get JS object for given cell DOMNode
7916 return this._cells[cell.index].dye;
7921 dojo.declare("dijit
.Dye
",
7925 // Interface for the JS Object associated with a palette cell (i.e. DOMNode)
7927 constructor: function(alias, row, col){
7929 // Initialize according to value or alias like "white
"
7933 getValue: function(){
7935 // Return "value
" of cell; meaning of "value
" varies by subclass.
7937 // For example color hex value, emoticon ascii value etc, entity hex value.
7940 fillCell: function(cell, blankGif){
7942 // Add cell DOMNode inner structure
7944 // The surrounding cell
7946 // URL for blank cell image
7954 if(!dojo._hasResource["dijit
.ColorPalette
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7955 dojo._hasResource["dijit
.ColorPalette
"] = true;
7956 dojo.provide("dijit
.ColorPalette
");
7966 dojo.declare("dijit
.ColorPalette
",
7967 [dijit._Widget, dijit._Templated, dijit._PaletteMixin],
7970 // A keyboard accessible color-picking widget
7972 // Grid showing various colors, so the user can pick a certain color.
7973 // Can be used standalone, or as a popup.
7976 // | <div dojoType="dijit
.ColorPalette
"></div>
7979 // | var picker = new dijit.ColorPalette({ },srcNode);
7980 // | picker.startup();
7983 // palette: [const] String
7984 // Size of grid, either "7x10
" or "3x4
".
7987 // _palettes: [protected] Map
7988 // This represents the value of the colors.
7989 // The first level is a hashmap of the different palettes available.
7990 // The next two dimensions represent the columns and rows of colors.
7992 "7x10
": [["white
", "seashell
", "cornsilk
", "lemonchiffon
","lightyellow
", "palegreen
", "paleturquoise
", "lightcyan
", "lavender
", "plum
"],
7993 ["lightgray
", "pink
", "bisque
", "moccasin
", "khaki
", "lightgreen
", "lightseagreen
", "lightskyblue
", "cornflowerblue
", "violet
"],
7994 ["silver
", "lightcoral
", "sandybrown
", "orange
", "palegoldenrod
", "chartreuse
", "mediumturquoise
", "skyblue
", "mediumslateblue
","orchid
"],
7995 ["gray
", "red
", "orangered
", "darkorange
", "yellow
", "limegreen
", "darkseagreen
", "royalblue
", "slateblue
", "mediumorchid
"],
7996 ["dimgray
", "crimson
", "chocolate
", "coral
", "gold
", "forestgreen
", "seagreen
", "blue
", "blueviolet
", "darkorchid
"],
7997 ["darkslategray
","firebrick
","saddlebrown
", "sienna
", "olive
", "green
", "darkcyan
", "mediumblue
","darkslateblue
", "darkmagenta
" ],
7998 ["black
", "darkred
", "maroon
", "brown
", "darkolivegreen
", "darkgreen
", "midnightblue
", "navy
", "indigo
", "purple
"]],
8000 "3x4
": [["white
", "lime
", "green
", "blue
"],
8001 ["silver
", "yellow
", "fuchsia
", "navy
"],
8002 ["gray
", "red
", "purple
", "black
"]]
8005 // templateString: String
8006 // The template of this widget.
8007 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"),
8009 baseClass: "dijitColorPalette
",
8011 buildRendering: function(){
8012 // Instantiate the template, which makes a skeleton into which we'll insert a bunch of
8014 this.inherited(arguments);
8016 // Creates <img> nodes in each cell of the template.
8017 // Pass in "customized
" dijit._Color constructor for specified palette and high-contrast vs. normal mode
8018 this._preparePalette(
8019 this._palettes[this.palette],
8020 dojo.i18n.getLocalization("dojo
", "colors
", this.lang),
8021 dojo.declare(dijit._Color, {
8022 hc: dojo.hasClass(dojo.body(), "dijit_a11y
"),
8023 palette: this.palette
8029 dojo.declare("dijit
._Color
", dojo.Color, {
8031 // Object associated with each cell in a ColorPalette palette.
8032 // Implements dijit.Dye.
8034 // Template for each cell in normal (non-high-contrast mode). Each cell contains a wrapper
8035 // node for showing the border (called dijitPaletteImg for back-compat), and dijitColorPaletteSwatch
8036 // for showing the color.
8038 "<span
class='dijitInline dijitPaletteImg'>" +
8039 "<img src
='${blankGif}' alt
='${alt}' class='dijitColorPaletteSwatch' style
='background-color: ${color}'/>" +
8042 // Template for each cell in high contrast mode. Each cell contains an image with the whole palette,
8043 // but scrolled and clipped to show the correct color only
8045 "<span
class='dijitInline dijitPaletteImg' style
='position: relative; overflow: hidden; height: 12px; width: 14px;'>" +
8046 "<img src
='${image}' alt
='${alt}' style
='position: absolute; left: ${left}px; top: ${top}px; ${size}'/>" +
8049 // _imagePaths: [protected] Map
8050 // This is stores the path to the palette images used for high-contrast mode display
8052 "7x10
": dojo.moduleUrl("dijit
.themes
", "a11y
/colors7x10
.png
"),
8053 "3x4
": dojo.moduleUrl("dijit
.themes
", "a11y
/colors3x4
.png
")
8056 constructor: function(/*String*/alias, /*Number*/ row, /*Number*/ col){
8057 this._alias = alias;
8060 this.setColor(dojo.Color.named[alias]);
8063 getValue: function(){
8065 // Note that although dijit._Color is initialized with a value like "white
" getValue() always
8066 // returns a hex value
8067 return this.toHex();
8070 fillCell: function(/*DOMNode*/ cell, /*String*/ blankGif){
8071 var html = dojo.string.substitute(this.hc ? this.hcTemplate : this.template, {
8072 // substitution variables for normal mode
8073 color: this.toHex(),
8077 // variables used for high contrast mode
8078 image: this._imagePaths[this.palette].toString(),
8079 left: this._col * -20 - 5,
8080 top: this._row * -20 - 5,
8081 size: this.palette == "7x10
" ? "height
: 145px
; width
: 206px
" : "height
: 64px
; width
: 86px
"
8084 dojo.place(html, cell);
8090 if(!dojo._hasResource["dojo
.dnd
.common
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8091 dojo._hasResource["dojo
.dnd
.common
"] = true;
8092 dojo.provide("dojo
.dnd
.common
");
8094 dojo.getObject("dnd
", true, dojo);
8096 dojo.dnd.getCopyKeyState = dojo.isCopyKey;
8098 dojo.dnd._uniqueId = 0;
8099 dojo.dnd.getUniqueId = function(){
8101 // returns a unique string for use with any DOM element
8104 id = dojo._scopeName + "Unique
" + (++dojo.dnd._uniqueId);
8105 }while(dojo.byId(id));
8109 dojo.dnd._empty = {};
8111 dojo.dnd.isFormElement = function(/*Event*/ e){
8113 // returns true if user clicked on a form element
8115 if(t.nodeType == 3 /*TEXT_NODE*/){
8118 return " button textarea input select option
".indexOf(" " + t.tagName.toLowerCase() + " ") >= 0; // Boolean
8123 if(!dojo._hasResource["dojo
.dnd
.autoscroll
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8124 dojo._hasResource["dojo
.dnd
.autoscroll
"] = true;
8125 dojo.provide("dojo
.dnd
.autoscroll
");
8128 dojo.getObject("dnd
", true, dojo);
8130 dojo.dnd.getViewport = dojo.window.getBox;
8132 dojo.dnd.V_TRIGGER_AUTOSCROLL = 32;
8133 dojo.dnd.H_TRIGGER_AUTOSCROLL = 32;
8135 dojo.dnd.V_AUTOSCROLL_VALUE = 16;
8136 dojo.dnd.H_AUTOSCROLL_VALUE = 16;
8138 dojo.dnd.autoScroll = function(e){
8140 // a handler for onmousemove event, which scrolls the window, if
8143 // onmousemove event
8145 // FIXME: needs more docs!
8146 var v = dojo.window.getBox(), dx = 0, dy = 0;
8147 if(e.clientX < dojo.dnd.H_TRIGGER_AUTOSCROLL){
8148 dx = -dojo.dnd.H_AUTOSCROLL_VALUE;
8149 }else if(e.clientX > v.w - dojo.dnd.H_TRIGGER_AUTOSCROLL){
8150 dx = dojo.dnd.H_AUTOSCROLL_VALUE;
8152 if(e.clientY < dojo.dnd.V_TRIGGER_AUTOSCROLL){
8153 dy = -dojo.dnd.V_AUTOSCROLL_VALUE;
8154 }else if(e.clientY > v.h - dojo.dnd.V_TRIGGER_AUTOSCROLL){
8155 dy = dojo.dnd.V_AUTOSCROLL_VALUE;
8157 window.scrollBy(dx, dy);
8160 dojo.dnd._validNodes = {"div
": 1, "p
": 1, "td
": 1};
8161 dojo.dnd._validOverflow = {"auto
": 1, "scroll
": 1};
8163 dojo.dnd.autoScrollNodes = function(e){
8165 // a handler for onmousemove event, which scrolls the first avaialble
8166 // Dom element, it falls back to dojo.dnd.autoScroll()
8168 // onmousemove event
8170 // FIXME: needs more docs!
8171 for(var n = e.target; n;){
8172 if(n.nodeType == 1 && (n.tagName.toLowerCase() in dojo.dnd._validNodes)){
8173 var s = dojo.getComputedStyle(n);
8174 if(s.overflow.toLowerCase() in dojo.dnd._validOverflow){
8175 var b = dojo._getContentBox(n, s), t = dojo.position(n, true);
8176 //console.log(b.l, b.t, t.x, t.y, n.scrollLeft, n.scrollTop);
8177 var w = Math.min(dojo.dnd.H_TRIGGER_AUTOSCROLL, b.w / 2),
8178 h = Math.min(dojo.dnd.V_TRIGGER_AUTOSCROLL, b.h / 2),
8179 rx = e.pageX - t.x, ry = e.pageY - t.y, dx = 0, dy = 0;
8180 if(dojo.isWebKit || dojo.isOpera){
8181 // FIXME: this code should not be here, it should be taken into account
8182 // either by the event fixing code, or the dojo.position()
8183 // FIXME: this code doesn't work on Opera 9.5 Beta
8184 rx += dojo.body().scrollLeft;
8185 ry += dojo.body().scrollTop;
8187 if(rx > 0 && rx < b.w){
8190 }else if(rx > b.w - w){
8194 //console.log("ry
=", ry, "b
.h
=", b.h, "h
=", h);
8195 if(ry > 0 && ry < b.h){
8198 }else if(ry > b.h - h){
8202 var oldLeft = n.scrollLeft, oldTop = n.scrollTop;
8203 n.scrollLeft = n.scrollLeft + dx;
8204 n.scrollTop = n.scrollTop + dy;
8205 if(oldLeft != n.scrollLeft || oldTop != n.scrollTop){ return; }
8214 dojo.dnd.autoScroll(e);
8219 if(!dojo._hasResource["dojo
.dnd
.Mover
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8220 dojo._hasResource["dojo
.dnd
.Mover
"] = true;
8221 dojo.provide("dojo
.dnd
.Mover
");
8226 dojo.declare("dojo
.dnd
.Mover
", null, {
8227 constructor: function(node, e, host){
8229 // an object which makes a node follow the mouse, or touch-drag on touch devices.
8230 // Used as a default mover, and as a base class for custom movers.
8232 // a node (or node's id) to be moved
8234 // a mouse event, which started the move;
8235 // only pageX and pageY properties are used
8237 // object which implements the functionality of the move,
8238 // and defines proper events (onMoveStart and onMoveStop)
8239 this.node = dojo.byId(node);
8240 var pos = e.touches ? e.touches[0] : e;
8241 this.marginBox = {l: pos.pageX, t: pos.pageY};
8242 this.mouseButton = e.button;
8243 var h = (this.host = host), d = node.ownerDocument;
8245 // At the start of a drag, onFirstMove is called, and then the following two
8246 // connects are disconnected
8247 dojo.connect(d, "onmousemove
", this, "onFirstMove
"),
8248 dojo.connect(d, "ontouchmove
", this, "onFirstMove
"),
8250 // These are called continually during the drag
8251 dojo.connect(d, "onmousemove
", this, "onMouseMove
"),
8252 dojo.connect(d, "ontouchmove
", this, "onMouseMove
"),
8254 // And these are called at the end of the drag
8255 dojo.connect(d, "onmouseup
", this, "onMouseUp
"),
8256 dojo.connect(d, "ontouchend
", this, "onMouseUp
"),
8258 // cancel text selection and text dragging
8259 dojo.connect(d, "ondragstart
", dojo.stopEvent),
8260 dojo.connect(d.body, "onselectstart
", dojo.stopEvent)
8262 // notify that the move has started
8263 if(h && h.onMoveStart){
8264 h.onMoveStart(this);
8267 // mouse event processors
8268 onMouseMove: function(e){
8270 // event processor for onmousemove/ontouchmove
8272 // mouse/touch event
8273 dojo.dnd.autoScroll(e);
8274 var m = this.marginBox,
8275 pos = e.touches ? e.touches[0] : e;
8276 this.host.onMove(this, {l: m.l + pos.pageX, t: m.t + pos.pageY}, e);
8279 onMouseUp: function(e){
8280 if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ?
8281 e.button == 0 : this.mouseButton == e.button){ // TODO Should condition be met for touch devices, too?
8287 onFirstMove: function(e){
8289 // makes the node absolute; it is meant to be called only once.
8290 // relative and absolutely positioned nodes are assumed to use pixel units
8291 var s = this.node.style, l, t, h = this.host;
8295 // assume that left and top values are in pixels already
8296 l = Math.round(parseFloat(s.left)) || 0;
8297 t = Math.round(parseFloat(s.top)) || 0;
8300 s.position = "absolute
"; // enforcing the absolute mode
8301 var m = dojo.marginBox(this.node);
8302 // event.pageX/pageY (which we used to generate the initial
8303 // margin box) includes padding and margin set on the body.
8304 // However, setting the node's position to absolute and then
8305 // doing dojo.marginBox on it *doesn't* take that additional
8306 // space into account - so we need to subtract the combined
8307 // padding and margin. We use getComputedStyle and
8308 // _getMarginBox/_getContentBox to avoid the extra lookup of
8309 // the computed style.
8310 var b = dojo.doc.body;
8311 var bs = dojo.getComputedStyle(b);
8312 var bm = dojo._getMarginBox(b, bs);
8313 var bc = dojo._getContentBox(b, bs);
8314 l = m.l - (bc.l - bm.l);
8315 t = m.t - (bc.t - bm.t);
8318 this.marginBox.l = l - this.marginBox.l;
8319 this.marginBox.t = t - this.marginBox.t;
8320 if(h && h.onFirstMove){
8321 h.onFirstMove(this, e);
8324 // Disconnect onmousemove and ontouchmove events that call this function
8325 dojo.disconnect(this.events.shift());
8326 dojo.disconnect(this.events.shift());
8328 destroy: function(){
8330 // stops the move, deletes all references, so the object can be garbage-collected
8331 dojo.forEach(this.events, dojo.disconnect);
8332 // undo global settings
8334 if(h && h.onMoveStop){
8338 this.events = this.node = this.host = null;
8344 if(!dojo._hasResource["dojo
.dnd
.Moveable
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8345 dojo._hasResource["dojo
.dnd
.Moveable
"] = true;
8346 dojo.provide("dojo
.dnd
.Moveable
");
8351 dojo.declare("dojo
.dnd
.__MoveableArgs
", [], {
8352 // handle: Node||String
8353 // A node (or node's id), which is used as a mouse handle.
8354 // If omitted, the node itself is used as a handle.
8358 // delay move by this number of pixels
8362 // skip move of form elements
8366 // a constructor of custom Mover
8367 mover: dojo.dnd.Mover
8371 dojo.declare("dojo
.dnd
.Moveable
", null, {
8372 // object attributes (for markup)
8377 constructor: function(node, params){
8379 // an object, which makes a node moveable
8381 // a node (or node's id) to be moved
8382 // params: dojo.dnd.__MoveableArgs?
8383 // optional parameters
8384 this.node = dojo.byId(node);
8385 if(!params){ params = {}; }
8386 this.handle = params.handle ? dojo.byId(params.handle) : null;
8387 if(!this.handle){ this.handle = this.node; }
8388 this.delay = params.delay > 0 ? params.delay : 0;
8389 this.skip = params.skip;
8390 this.mover = params.mover ? params.mover : dojo.dnd.Mover;
8392 dojo.connect(this.handle, "onmousedown
", this, "onMouseDown
"),
8393 dojo.connect(this.handle, "ontouchstart
", this, "onMouseDown
"),
8394 // cancel text selection and text dragging
8395 dojo.connect(this.handle, "ondragstart
", this, "onSelectStart
"),
8396 dojo.connect(this.handle, "onselectstart
", this, "onSelectStart
")
8401 markupFactory: function(params, node){
8402 return new dojo.dnd.Moveable(node, params);
8406 destroy: function(){
8408 // stops watching for possible move, deletes all references, so the object can be garbage-collected
8409 dojo.forEach(this.events, dojo.disconnect);
8410 this.events = this.node = this.handle = null;
8413 // mouse event processors
8414 onMouseDown: function(e){
8416 // event processor for onmousedown/ontouchstart, creates a Mover for the node
8418 // mouse/touch event
8419 if(this.skip && dojo.dnd.isFormElement(e)){ return; }
8422 dojo.connect(this.handle, "onmousemove
", this, "onMouseMove
"),
8423 dojo.connect(this.handle, "ontouchmove
", this, "onMouseMove
"),
8424 dojo.connect(this.handle, "onmouseup
", this, "onMouseUp
"),
8425 dojo.connect(this.handle, "ontouchend
", this, "onMouseUp
")
8427 var pos = e.touches ? e.touches[0] : e;
8428 this._lastX = pos.pageX;
8429 this._lastY = pos.pageY;
8431 this.onDragDetected(e);
8435 onMouseMove: function(e){
8437 // event processor for onmousemove/ontouchmove, used only for delayed drags
8439 // mouse/touch event
8440 var pos = e.touches ? e.touches[0] : e;
8441 if(Math.abs(pos.pageX - this._lastX) > this.delay || Math.abs(pos.pageY - this._lastY) > this.delay){
8443 this.onDragDetected(e);
8447 onMouseUp: function(e){
8449 // event processor for onmouseup, used only for delayed drags
8452 for(var i = 0; i < 2; ++i){
8453 dojo.disconnect(this.events.pop());
8457 onSelectStart: function(e){
8459 // event processor for onselectevent and ondragevent
8462 if(!this.skip || !dojo.dnd.isFormElement(e)){
8468 onDragDetected: function(/* Event */ e){
8470 // called when the drag is detected;
8471 // responsible for creation of the mover
8472 new this.mover(this.node, e, this);
8474 onMoveStart: function(/* dojo.dnd.Mover */ mover){
8476 // called before every move operation
8477 dojo.publish("/dnd/move/start
", [mover]);
8478 dojo.addClass(dojo.body(), "dojoMove
");
8479 dojo.addClass(this.node, "dojoMoveItem
");
8481 onMoveStop: function(/* dojo.dnd.Mover */ mover){
8483 // called after every move operation
8484 dojo.publish("/dnd/move/stop
", [mover]);
8485 dojo.removeClass(dojo.body(), "dojoMove
");
8486 dojo.removeClass(this.node, "dojoMoveItem
");
8488 onFirstMove: function(/* dojo.dnd.Mover */ mover, /* Event */ e){
8490 // called during the very first move notification;
8491 // can be used to initialize coordinates, can be overwritten.
8493 // default implementation does nothing
8495 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop, /* Event */ e){
8497 // called during every move notification;
8498 // should actually move the node; can be overwritten.
8499 this.onMoving(mover, leftTop);
8500 var s = mover.node.style;
8501 s.left = leftTop.l + "px
";
8502 s.top = leftTop.t + "px
";
8503 this.onMoved(mover, leftTop);
8505 onMoving: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8507 // called before every incremental move; can be overwritten.
8509 // default implementation does nothing
8511 onMoved: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8513 // called after every incremental move; can be overwritten.
8515 // default implementation does nothing
8521 if(!dojo._hasResource["dojo
.dnd
.move"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8522 dojo._hasResource["dojo
.dnd
.move"] = true;
8523 dojo.provide("dojo
.dnd
.move");
8529 dojo.declare("dojo
.dnd
.move.__constrainedMoveableArgs
", [dojo.dnd.__MoveableArgs], {
8530 // constraints: Function
8531 // Calculates a constraint box.
8532 // It is called in a context of the moveable object.
8533 constraints: function(){},
8536 // restrict move within boundaries.
8541 dojo.declare("dojo
.dnd
.move.constrainedMoveable
", dojo.dnd.Moveable, {
8542 // object attributes (for markup)
8543 constraints: function(){},
8547 markupFactory: function(params, node){
8548 return new dojo.dnd.move.constrainedMoveable(node, params);
8551 constructor: function(node, params){
8553 // an object that makes a node moveable
8555 // a node (or node's id) to be moved
8556 // params: dojo.dnd.move.__constrainedMoveableArgs?
8557 // an optional object with additional parameters;
8558 // the rest is passed to the base class
8559 if(!params){ params = {}; }
8560 this.constraints = params.constraints;
8561 this.within = params.within;
8563 onFirstMove: function(/* dojo.dnd.Mover */ mover){
8565 // called during the very first move notification;
8566 // can be used to initialize coordinates, can be overwritten.
8567 var c = this.constraintBox = this.constraints.call(this, mover);
8571 var mb = dojo._getMarginSize(mover.node);
8576 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8578 // called during every move notification;
8579 // should actually move the node; can be overwritten.
8580 var c = this.constraintBox, s = mover.node.style;
8581 this.onMoving(mover, leftTop);
8582 leftTop.l = leftTop.l < c.l ? c.l : c.r < leftTop.l ? c.r : leftTop.l;
8583 leftTop.t = leftTop.t < c.t ? c.t : c.b < leftTop.t ? c.b : leftTop.t;
8584 s.left = leftTop.l + "px
";
8585 s.top = leftTop.t + "px
";
8586 this.onMoved(mover, leftTop);
8591 dojo.declare("dojo
.dnd
.move.__boxConstrainedMoveableArgs
", [dojo.dnd.move.__constrainedMoveableArgs], {
8598 dojo.declare("dojo
.dnd
.move.boxConstrainedMoveable
", dojo.dnd.move.constrainedMoveable, {
8600 // object attributes (for markup)
8604 markupFactory: function(params, node){
8605 return new dojo.dnd.move.boxConstrainedMoveable(node, params);
8608 constructor: function(node, params){
8610 // an object, which makes a node moveable
8612 // a node (or node's id) to be moved
8613 // params: dojo.dnd.move.__boxConstrainedMoveableArgs?
8614 // an optional object with parameters
8615 var box = params && params.box;
8616 this.constraints = function(){ return box; };
8621 dojo.declare("dojo
.dnd
.move.__parentConstrainedMoveableArgs
", [dojo.dnd.move.__constrainedMoveableArgs], {
8623 // A parent's area to restrict the move.
8624 // Can be "margin
", "border
", "padding
", or "content
".
8629 dojo.declare("dojo
.dnd
.move.parentConstrainedMoveable
", dojo.dnd.move.constrainedMoveable, {
8631 // object attributes (for markup)
8635 markupFactory: function(params, node){
8636 return new dojo.dnd.move.parentConstrainedMoveable(node, params);
8639 constructor: function(node, params){
8641 // an object, which makes a node moveable
8643 // a node (or node's id) to be moved
8644 // params: dojo.dnd.move.__parentConstrainedMoveableArgs?
8645 // an optional object with parameters
8646 var area = params && params.area;
8647 this.constraints = function(){
8648 var n = this.node.parentNode,
8649 s = dojo.getComputedStyle(n),
8650 mb = dojo._getMarginBox(n, s);
8651 if(area == "margin
"){
8652 return mb; // Object
8654 var t = dojo._getMarginExtents(n, s);
8655 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8656 if(area == "border
"){
8657 return mb; // Object
8659 t = dojo._getBorderExtents(n, s);
8660 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8661 if(area == "padding
"){
8662 return mb; // Object
8664 t = dojo._getPadExtents(n, s);
8665 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8666 return mb; // Object
8671 // patching functions one level up for compatibility
8673 dojo.dnd.constrainedMover = dojo.dnd.move.constrainedMover;
8674 dojo.dnd.boxConstrainedMover = dojo.dnd.move.boxConstrainedMover;
8675 dojo.dnd.parentConstrainedMover = dojo.dnd.move.parentConstrainedMover;
8679 if(!dojo._hasResource["dojo
.dnd
.TimedMoveable
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8680 dojo._hasResource["dojo
.dnd
.TimedMoveable
"] = true;
8681 dojo.provide("dojo
.dnd
.TimedMoveable
");
8686 dojo.declare("dojo
.dnd
.__TimedMoveableArgs
", [dojo.dnd.__MoveableArgs], {
8688 // delay move by this number of ms,
8689 // accumulating position changes during the timeout
8695 // precalculate long expressions
8696 var oldOnMove = dojo.dnd.Moveable.prototype.onMove;
8698 dojo.declare("dojo
.dnd
.TimedMoveable
", dojo.dnd.Moveable, {
8700 // A specialized version of Moveable to support an FPS throttling.
8701 // This class puts an upper restriction on FPS, which may reduce
8702 // the CPU load. The additional parameter "timeout
" regulates
8703 // the delay before actually moving the moveable object.
8705 // object attributes (for markup)
8706 timeout: 40, // in ms, 40ms corresponds to 25 fps
8708 constructor: function(node, params){
8710 // an object that makes a node moveable with a timer
8711 // node: Node||String
8712 // a node (or node's id) to be moved
8713 // params: dojo.dnd.__TimedMoveableArgs
8714 // object with additional parameters.
8716 // sanitize parameters
8717 if(!params){ params = {}; }
8718 if(params.timeout && typeof params.timeout == "number
" && params.timeout >= 0){
8719 this.timeout = params.timeout;
8724 markupFactory: function(params, node){
8725 return new dojo.dnd.TimedMoveable(node, params);
8728 onMoveStop: function(/* dojo.dnd.Mover */ mover){
8731 clearTimeout(mover._timer)
8732 // reflect the last received position
8733 oldOnMove.call(this, mover, mover._leftTop)
8735 dojo.dnd.Moveable.prototype.onMoveStop.apply(this, arguments);
8737 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8738 mover._leftTop = leftTop;
8740 var _t = this; // to avoid using dojo.hitch()
8741 mover._timer = setTimeout(function(){
8742 // we don't have any pending requests
8743 mover._timer = null;
8744 // reflect the last received position
8745 oldOnMove.call(_t, mover, mover._leftTop);
8754 if(!dojo._hasResource["dijit
.form
._FormMixin
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8755 dojo._hasResource["dijit
.form
._FormMixin
"] = true;
8756 dojo.provide("dijit
.form
._FormMixin
");
8760 dojo.declare("dijit
.form
._FormMixin
", null, {
8762 // Mixin for containers of form widgets (i.e. widgets that represent a single value
8763 // and can be children of a <form> node or dijit.form.Form widget)
8765 // Can extract all the form widgets
8766 // values and combine them into a single javascript object, or alternately
8767 // take such an object and set the values for all the contained
8772 // Name/value hash for each child widget with a name and value.
8773 // Child widgets without names are not part of the hash.
8775 // If there are multiple child widgets w/the same name, value is an array,
8776 // unless they are radio buttons in which case value is a scalar (since only
8777 // one radio button can be checked at a time).
8779 // If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure.
8782 // | { name: "John Smith
", interests: ["sports
", "movies
"] }
8785 // state: [readonly] String
8786 // Will be "Error
" if one or more of the child widgets has an invalid value,
8787 // "Incomplete
" if not all of the required child widgets are filled in. Otherwise, "",
8788 // which indicates that the form is ready to be submitted.
8793 // * better handling for arrays. Often form elements have names with [] like
8794 // * people[3].sex (for a list of people [{name: Bill, sex: M}, ...])
8799 dojo.forEach(this.getDescendants(), function(widget){
8806 validate: function(){
8808 // returns if the form is valid - same as isValid - but
8809 // provides a few additional (ui-specific) features.
8810 // 1 - it will highlight any sub-widgets that are not
8812 // 2 - it will call focus() on the first invalid
8814 var didFocus = false;
8815 return dojo.every(dojo.map(this.getDescendants(), function(widget){
8816 // Need to set this so that "required
" widgets get their
8818 widget._hasBeenBlurred = true;
8819 var valid = widget.disabled || !widget.validate || widget.validate();
8820 if(!valid && !didFocus){
8821 // Set focus of the first non-valid widget
8822 dojo.window.scrollIntoView(widget.containerNode || widget.domNode);
8827 }), function(item){ return item; });
8830 setValues: function(val){
8831 dojo.deprecated(this.declaredClass+"::setValues() is deprecated
. Use
set('value', val
) instead
.", "", "2.0");
8832 return this.set('value', val);
8834 _setValueAttr: function(/*Object*/ obj){
8836 // Fill in form values from according to an Object (in the format returned by get('value'))
8838 // generate map from name --> [list of widgets with that name]
8840 dojo.forEach(this.getDescendants(), function(widget){
8841 if(!widget.name){ return; }
8842 var entry = map[widget.name] || (map[widget.name] = [] );
8846 for(var name in map){
8847 if(!map.hasOwnProperty(name)){
8850 var widgets = map[name], // array of widgets w/this name
8851 values = dojo.getObject(name, false, obj); // list of values for those widgets
8853 if(values === undefined){
8856 if(!dojo.isArray(values)){
8857 values = [ values ];
8859 if(typeof widgets[0].checked == 'boolean'){
8860 // for checkbox/radio, values is a list of which widgets should be checked
8861 dojo.forEach(widgets, function(w, i){
8862 w.set('value', dojo.indexOf(values, w.value) != -1);
8864 }else if(widgets[0].multiple){
8865 // it takes an array (e.g. multi-select)
8866 widgets[0].set('value', values);
8868 // otherwise, values is a list of values to be assigned sequentially to each widget
8869 dojo.forEach(widgets, function(w, i){
8870 w.set('value', values[i]);
8876 * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets)
8878 dojo.forEach(this.containerNode.elements, function(element){
8879 if(element.name == ''){return}; // like "continue"
8880 var namePath = element.name.split(".");
8882 var name=namePath[namePath.length-1];
8883 for(var j=1,len2=namePath.length;j<len2;++j){
8884 var p=namePath[j - 1];
8885 // repeater support block
8886 var nameA=p.split("[");
8887 if(nameA.length > 1){
8888 if(typeof(myObj[nameA[0]]) == "undefined"){
8889 myObj[nameA[0]]=[ ];
8892 nameIndex=parseInt(nameA[1]);
8893 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
8894 myObj[nameA[0]][nameIndex] = { };
8896 myObj=myObj[nameA[0]][nameIndex];
8898 } // repeater support ends
8900 if(typeof(myObj[p]) == "undefined"){
8907 if(typeof(myObj) == "undefined"){
8908 return; // like "continue"
8910 if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){
8911 return; // like "continue"
8914 // TODO: widget values (just call set('value', ...) on the widget)
8916 // TODO: maybe should call dojo.getNodeProp() instead
8917 switch(element.type){
8919 element.checked = (name in myObj) &&
8920 dojo.some(myObj[name], function(val){ return val == element.value; });
8923 element.checked = (name in myObj) && myObj[name] == element.value;
8925 case "select
-multiple
":
8926 element.selectedIndex=-1;
8927 dojo.forEach(element.options, function(option){
8928 option.selected = dojo.some(myObj[name], function(val){ return option.value == val; });
8932 element.selectedIndex="0";
8933 dojo.forEach(element.options, function(option){
8934 option.selected = option.value == myObj[name];
8941 element.value = myObj[name] || "";
8947 // Note: no need to call this._set("value
", ...) as the child updates will trigger onChange events
8948 // which I am monitoring.
8951 getValues: function(){
8952 dojo.deprecated(this.declaredClass+"::getValues() is deprecated
. Use
get('value') instead
.", "", "2.0");
8953 return this.get('value');
8955 _getValueAttr: function(){
8957 // Returns Object representing form values. See description of `value` for details.
8960 // The value is updated into this.value every time a child has an onChange event,
8961 // so in the common case this function could just return this.value. However,
8962 // that wouldn't work when:
8964 // 1. User presses return key to submit a form. That doesn't fire an onchange event,
8965 // and even if it did it would come too late due to the setTimout(..., 0) in _handleOnChange()
8967 // 2. app for some reason calls this.get("value
") while the user is typing into a
8968 // form field. Not sure if that case needs to be supported or not.
8970 // get widget values
8972 dojo.forEach(this.getDescendants(), function(widget){
8973 var name = widget.name;
8974 if(!name || widget.disabled){ return; }
8976 // Single value widget (checkbox, radio, or plain <input> type widget)
8977 var value = widget.get('value');
8979 // Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays
8980 if(typeof widget.checked == 'boolean'){
8981 if(/Radio/.test(widget.declaredClass)){
8983 if(value !== false){
8984 dojo.setObject(name, value, obj);
8986 // give radio widgets a default of null
8987 value = dojo.getObject(name, false, obj);
8988 if(value === undefined){
8989 dojo.setObject(name, null, obj);
8993 // checkbox/toggle button
8994 var ary=dojo.getObject(name, false, obj);
8997 dojo.setObject(name, ary, obj);
8999 if(value !== false){
9004 var prev=dojo.getObject(name, false, obj);
9005 if(typeof prev != "undefined"){
9006 if(dojo.isArray(prev)){
9009 dojo.setObject(name, [prev, value], obj);
9013 dojo.setObject(name, value, obj);
9019 * code for plain input boxes (see also dojo.formToObject, can we use that instead of this code?
9020 * but it doesn't understand [] notation, presumably)
9022 dojo.forEach(this.containerNode.elements, function(elm){
9024 return; // like "continue"
9026 var namePath = elm.name.split(".");
9028 var name=namePath[namePath.length-1];
9029 for(var j=1,len2=namePath.length;j<len2;++j){
9030 var nameIndex = null;
9031 var p=namePath[j - 1];
9032 var nameA=p.split("[");
9033 if(nameA.length > 1){
9034 if(typeof(myObj[nameA[0]]) == "undefined"){
9035 myObj[nameA[0]]=[ ];
9037 nameIndex=parseInt(nameA[1]);
9038 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
9039 myObj[nameA[0]][nameIndex] = { };
9041 } else if(typeof(myObj[nameA[0]]) == "undefined"){
9042 myObj[nameA[0]] = { }
9045 if(nameA.length == 1){
9046 myObj=myObj[nameA[0]];
9048 myObj=myObj[nameA[0]][nameIndex];
9052 if((elm.type != "select
-multiple
" && elm.type != "checkbox
" && elm.type != "radio
") || (elm.type == "radio
" && elm.checked)){
9053 if(name == name.split("[")[0]){
9054 myObj[name]=elm.value;
9056 // can not set value when there is no name
9058 } else if(elm.type == "checkbox
" && elm.checked){
9059 if(typeof(myObj[name]) == 'undefined'){
9062 myObj[name].push(elm.value);
9063 } else if(elm.type == "select
-multiple
"){
9064 if(typeof(myObj[name]) == 'undefined'){
9067 for(var jdx=0,len3=elm.options.length; jdx<len3; ++jdx){
9068 if(elm.options[jdx].selected){
9069 myObj[name].push(elm.options[jdx].value);
9079 isValid: function(){
9081 // Returns true if all of the widgets are valid.
9082 // Deprecated, will be removed in 2.0. Use get("state
") instead.
9084 return this.state == "";
9087 onValidStateChange: function(isValid){
9089 // Stub function to connect to if you want to do something
9090 // (like disable/enable a submit button) when the valid
9091 // state changes on the form as a whole.
9093 // Deprecated. Will be removed in 2.0. Use watch("state
", ...) instead.
9096 _getState: function(){
9098 // Compute what this.state should be based on state of children
9099 var states = dojo.map(this._descendants, function(w){
9100 return w.get("state
") || "";
9103 return dojo.indexOf(states, "Error
") >= 0 ? "Error
" :
9104 dojo.indexOf(states, "Incomplete
") >= 0 ? "Incomplete
" : "";
9107 disconnectChildren: function(){
9109 // Remove connections to monitor changes to children's value, error state, and disabled state,
9110 // in order to update Form.value and Form.state.
9111 dojo.forEach(this._childConnections || [], dojo.hitch(this, "disconnect
"));
9112 dojo.forEach(this._childWatches || [], function(w){ w.unwatch(); });
9115 connectChildren: function(/*Boolean*/ inStartup){
9117 // Setup connections to monitor changes to children's value, error state, and disabled state,
9118 // in order to update Form.value and Form.state.
9120 // You can call this function directly, ex. in the event that you
9121 // programmatically add a widget to the form *after* the form has been
9126 // Remove old connections, if any
9127 this.disconnectChildren();
9129 this._descendants = this.getDescendants();
9131 // (Re)set this.value and this.state. Send watch() notifications but not on startup.
9132 var set = inStartup ? function(name, val){ _this[name] = val; } : dojo.hitch(this, "_set
");
9133 set("value
", this.get("value
"));
9134 set("state
", this._getState());
9136 // Monitor changes to error state and disabled state in order to update
9138 var conns = (this._childConnections = []),
9139 watches = (this._childWatches = []);
9140 dojo.forEach(dojo.filter(this._descendants,
9141 function(item){ return item.validate; }
9144 // We are interested in whenever the widget changes validity state - or
9145 // whenever the disabled attribute on that widget is changed.
9146 dojo.forEach(["state
", "disabled
"], function(attr){
9147 watches.push(widget.watch(attr, function(attr, oldVal, newVal){
9148 _this.set("state
", _this._getState());
9153 // And monitor calls to child.onChange so we can update this.value
9154 var onChange = function(){
9156 // Called when child's value or disabled state changes
9158 // Use setTimeout() to collapse value changes in multiple children into a single
9159 // update to my value. Multiple updates will occur on:
9162 // 3. user selecting a radio button (which will de-select another radio button,
9163 // causing two onChange events)
9164 if(_this._onChangeDelayTimer){
9165 clearTimeout(_this._onChangeDelayTimer);
9167 _this._onChangeDelayTimer = setTimeout(function(){
9168 delete _this._onChangeDelayTimer;
9169 _this._set("value
", _this.get("value
"));
9173 dojo.filter(this._descendants, function(item){ return item.onChange; } ),
9175 // When a child widget's value changes,
9176 // the efficient thing to do is to just update that one attribute in this.value,
9177 // but that gets a little complicated when a checkbox is checked/unchecked
9178 // since this.value["checkboxName
"] contains an array of all the checkboxes w/the same name.
9179 // Doing simple thing for now.
9180 conns.push(_this.connect(widget, "onChange
", onChange));
9182 // Disabling/enabling a child widget should remove it's value from this.value.
9183 // Again, this code could be more efficient, doing simple thing for now.
9184 watches.push(widget.watch("disabled
", onChange));
9189 startup: function(){
9190 this.inherited(arguments);
9192 // Initialize value and valid/invalid state tracking. Needs to be done in startup()
9193 // so that children are initialized.
9194 this.connectChildren(true);
9196 // Make state change call onValidStateChange(), will be removed in 2.0
9197 this.watch("state
", function(attr, oldVal, newVal){ this.onValidStateChange(newVal == ""); });
9200 destroy: function(){
9201 this.disconnectChildren();
9202 this.inherited(arguments);
9209 if(!dojo._hasResource["dijit
._DialogMixin
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9210 dojo._hasResource["dijit
._DialogMixin
"] = true;
9211 dojo.provide("dijit
._DialogMixin
");
9215 dojo.declare("dijit
._DialogMixin
", null,
9218 // This provides functions useful to Dialog and TooltipDialog
9220 attributeMap: dijit._Widget.prototype.attributeMap,
9222 execute: function(/*Object*/ formContents){
9224 // Callback when the user hits the submit button.
9225 // Override this method to handle Dialog execution.
9227 // After the user has pressed the submit button, the Dialog
9228 // first calls onExecute() to notify the container to hide the
9229 // dialog and restore focus to wherever it used to be.
9231 // *Then* this method is called.
9236 onCancel: function(){
9238 // Called when user has pressed the Dialog's cancel button, to notify container.
9240 // Developer shouldn't override or connect to this method;
9241 // it's a private communication device between the TooltipDialog
9242 // and the thing that opened it (ex: `dijit.form.DropDownButton`)
9247 onExecute: function(){
9249 // Called when user has pressed the dialog's OK button, to notify container.
9251 // Developer shouldn't override or connect to this method;
9252 // it's a private communication device between the TooltipDialog
9253 // and the thing that opened it (ex: `dijit.form.DropDownButton`)
9258 _onSubmit: function(){
9260 // Callback when user hits submit button
9263 this.onExecute(); // notify container that we are about to execute
9264 this.execute(this.get('value'));
9267 _getFocusItems: function(){
9269 // Finds focusable items in dialog,
9270 // and sets this._firstFocusItem and this._lastFocusItem
9274 var elems = dijit._getTabNavigable(this.containerNode);
9275 this._firstFocusItem = elems.lowest || elems.first || this.closeButtonNode || this.domNode;
9276 this._lastFocusItem = elems.last || elems.highest || this._firstFocusItem;
9283 if(!dojo._hasResource["dijit
.DialogUnderlay
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9284 dojo._hasResource["dijit
.DialogUnderlay
"] = true;
9285 dojo.provide("dijit
.DialogUnderlay
");
9292 "dijit
.DialogUnderlay
",
9293 [dijit._Widget, dijit._Templated],
9296 // The component that blocks the screen behind a `dijit.Dialog`
9299 // A component used to block input behind a `dijit.Dialog`. Only a single
9300 // instance of this widget is created by `dijit.Dialog`, and saved as
9301 // a reference to be shared between all Dialogs as `dijit._underlay`
9303 // The underlay itself can be styled based on and id:
9304 // | #myDialog_underlay { background-color:red; }
9306 // In the case of `dijit.Dialog`, this id is based on the id of the Dialog,
9307 // suffixed with _underlay.
9309 // Template has two divs; outer div is used for fade-in/fade-out, and also to hold background iframe.
9310 // Inner div has opacity specified in CSS file.
9311 templateString: "<div
class='dijitDialogUnderlayWrapper'><div
class='dijitDialogUnderlay' dojoAttachPoint
='node'></div></div>",
9313 // Parameters on creation or updatable later
9316 // Id of the dialog.... DialogUnderlay's id is based on this id
9320 // This class name is used on the DialogUnderlay node, in addition to dijitDialogUnderlay
9323 attributeMap: { id: "domNode
" },
9325 _setDialogIdAttr: function(id){
9326 dojo.attr(this.node, "id
", id + "_underlay
");
9327 this._set("dialogId
", id);
9330 _setClassAttr: function(clazz){
9331 this.node.className = "dijitDialogUnderlay
" + clazz;
9332 this._set("class", clazz);
9335 postCreate: function(){
9337 // Append the underlay to the body
9338 dojo.body().appendChild(this.domNode);
9343 // Sets the background to the size of the viewport
9346 // Sets the background to the size of the viewport (rather than the size
9347 // of the document) since we need to cover the whole browser window, even
9348 // if the document is only a few lines long.
9352 var is = this.node.style,
9353 os = this.domNode.style;
9355 // hide the background temporarily, so that the background itself isn't
9356 // causing scrollbars to appear (might happen when user shrinks browser
9357 // window and then we are called to resize)
9358 os.display = "none
";
9360 // then resize and show
9361 var viewport = dojo.window.getBox();
9362 os.top = viewport.t + "px
";
9363 os.left = viewport.l + "px
";
9364 is.width = viewport.w + "px
";
9365 is.height = viewport.h + "px
";
9366 os.display = "block
";
9371 // Show the dialog underlay
9372 this.domNode.style.display = "block
";
9374 this.bgIframe = new dijit.BackgroundIframe(this.domNode);
9379 // Hides the dialog underlay
9380 this.bgIframe.destroy();
9381 delete this.bgIframe;
9382 this.domNode.style.display = "none
";
9389 if(!dojo._hasResource["dijit
.layout
._ContentPaneResizeMixin
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9390 dojo._hasResource["dijit
.layout
._ContentPaneResizeMixin
"] = true;
9391 dojo.provide("dijit
.layout
._ContentPaneResizeMixin
");
9396 dojo.declare("dijit
.layout
._ContentPaneResizeMixin
", null, {
9398 // Resize() functionality of ContentPane. If there's a single layout widget
9399 // child then it will call resize() with the same dimensions as the ContentPane.
9400 // Otherwise just calls resize on each child.
9402 // Also implements basic startup() functionality, where starting the parent
9403 // will start the children
9405 // doLayout: Boolean
9406 // - false - don't adjust size of children
9407 // - true - if there is a single visible child widget, set it's size to
9408 // however big the ContentPane is
9411 // isContainer: [protected] Boolean
9412 // Indicates that this widget acts as a "parent
" to the descendant widgets.
9413 // When the parent is started it will call startup() on the child widgets.
9414 // See also `isLayoutContainer`.
9417 // isLayoutContainer: [protected] Boolean
9418 // Indicates that this widget will call resize() on it's child widgets
9419 // when they become visible.
9420 isLayoutContainer: true,
9422 _startChildren: function(){
9424 // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
9426 // This starts all the widgets
9427 dojo.forEach(this.getChildren(), function(child){
9429 child._started = true;
9433 startup: function(){
9435 // See `dijit.layout._LayoutWidget.startup` for description.
9436 // Although ContentPane doesn't extend _LayoutWidget, it does implement
9439 if(this._started){ return; }
9441 var parent = dijit._Contained.prototype.getParent.call(this);
9442 this._childOfLayoutWidget = parent && parent.isLayoutContainer;
9444 // I need to call resize() on my child/children (when I become visible), unless
9445 // I'm the child of a layout widget in which case my parent will call resize() on me and I'll do it then.
9446 this._needLayout = !this._childOfLayoutWidget;
9448 this.inherited(arguments);
9450 this._startChildren();
9452 if(this._isShown()){
9456 if(!this._childOfLayoutWidget){
9457 // If my parent isn't a layout container, since my style *may be* width=height=100%
9458 // or something similar (either set directly or via a CSS class),
9459 // monitor when my size changes so that I can re-layout.
9460 // For browsers where I can't directly monitor when my size changes,
9461 // monitor when the viewport changes size, which *may* indicate a size change for me.
9462 this.connect(dojo.isIE ? this.domNode : dojo.global, 'onresize', function(){
9463 // Using function(){} closure to ensure no arguments to resize.
9464 this._needLayout = !this._childOfLayoutWidget;
9470 _checkIfSingleChild: function(){
9472 // Test if we have exactly one visible widget as a child,
9473 // and if so assume that we are a container for that widget,
9474 // and should propagate startup() and resize() calls to it.
9475 // Skips over things like data stores since they aren't visible.
9477 var childNodes = dojo.query("> *", this.containerNode).filter(function(node){
9478 return node.tagName !== "SCRIPT
"; // or a regexp for hidden elements like script|area|map|etc..
9480 childWidgetNodes = childNodes.filter(function(node){
9481 return dojo.hasAttr(node, "data
-dojo
-type
") || dojo.hasAttr(node, "dojoType
") || dojo.hasAttr(node, "widgetId
");
9483 candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){
9484 return widget && widget.domNode && widget.resize;
9488 // all child nodes are widgets
9489 childNodes.length == childWidgetNodes.length &&
9491 // all but one are invisible (like dojo.data)
9492 candidateWidgets.length == 1
9494 this._singleChild = candidateWidgets[0];
9496 delete this._singleChild;
9499 // So we can set overflow: hidden to avoid a safari bug w/scrollbars showing up (#9449)
9500 dojo.toggleClass(this.containerNode, this.baseClass + "SingleChild
", !!this._singleChild);
9503 resize: function(changeSize, resultSize){
9505 // See `dijit.layout._LayoutWidget.resize` for description.
9506 // Although ContentPane doesn't extend _LayoutWidget, it does implement
9509 // For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is
9510 // never called, so resize() is our trigger to do the initial href download (see [20099]).
9511 // However, don't load href for closed TitlePanes.
9512 if(!this._wasShown && this.open !== false){
9516 this._resizeCalled = true;
9518 this._scheduleLayout(changeSize, resultSize);
9521 _scheduleLayout: function(changeSize, resultSize){
9523 // Resize myself, and call resize() on each of my child layout widgets, either now
9524 // (if I'm currently visible) or when I become visible
9525 if(this._isShown()){
9526 this._layout(changeSize, resultSize);
9528 this._needLayout = true;
9529 this._changeSize = changeSize;
9530 this._resultSize = resultSize;
9534 _layout: function(changeSize, resultSize){
9536 // Resize myself according to optional changeSize/resultSize parameters, like a layout widget.
9537 // Also, since I am a Container widget, each of my children expects me to
9538 // call resize() or layout() on them.
9540 // Should be called on initialization and also whenever we get new content
9541 // (from an href, or from set('content', ...))... but deferred until
9542 // the ContentPane is visible
9544 // Set margin box size, unless it wasn't specified, in which case use current size.
9546 dojo.marginBox(this.domNode, changeSize);
9549 // Compute content box size of containerNode in case we [later] need to size our single child.
9550 var cn = this.containerNode;
9551 if(cn === this.domNode){
9552 // If changeSize or resultSize was passed to this method and this.containerNode ==
9553 // this.domNode then we can compute the content-box size without querying the node,
9554 // which is more reliable (similar to LayoutWidget.resize) (see for example #9449).
9555 var mb = resultSize || {};
9556 dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize
9557 if(!("h
" in mb) || !("w
" in mb)){
9558 mb = dojo.mixin(dojo.marginBox(cn), mb); // just use dojo.marginBox() to fill in missing values
9560 this._contentBox = dijit.layout.marginBox2contentBox(cn, mb);
9562 this._contentBox = dojo.contentBox(cn);
9565 this._layoutChildren();
9567 delete this._needLayout;
9570 _layoutChildren: function(){
9571 // Call _checkIfSingleChild() again in case app has manually mucked w/the content
9572 // of the ContentPane (rather than changing it through the set("content
", ...) API.
9574 this._checkIfSingleChild();
9577 if(this._singleChild && this._singleChild.resize){
9578 var cb = this._contentBox || dojo.contentBox(this.containerNode);
9580 // note: if widget has padding this._contentBox will have l and t set,
9581 // but don't pass them to resize() or it will doubly-offset the child
9582 this._singleChild.resize({w: cb.w, h: cb.h});
9584 // All my child widgets are independently sized (rather than matching my size),
9585 // but I still need to call resize() on each child to make it layout.
9586 dojo.forEach(this.getChildren(), function(widget){
9594 _isShown: function(){
9596 // Returns true if the content is currently shown.
9598 // If I am a child of a layout widget then it actually returns true if I've ever been visible,
9599 // not whether I'm currently visible, since that's much faster than tracing up the DOM/widget
9600 // tree every call, and at least solves the performance problem on page load by deferring loading
9601 // hidden ContentPanes until they are first shown
9603 if(this._childOfLayoutWidget){
9604 // If we are TitlePane, etc - we return that only *IF* we've been resized
9605 if(this._resizeCalled && "open
" in this){
9608 return this._resizeCalled;
9609 }else if("open
" in this){
9610 return this.open; // for TitlePane, etc.
9612 var node = this.domNode, parent = this.domNode.parentNode;
9613 return (node.style.display != 'none') && (node.style.visibility != 'hidden') && !dojo.hasClass(node, "dijitHidden
") &&
9614 parent && parent.style && (parent.style.display != 'none');
9618 _onShow: function(){
9620 // Called when the ContentPane is made visible
9622 // For a plain ContentPane, this is called on initialization, from startup().
9623 // If the ContentPane is a hidden pane of a TabContainer etc., then it's
9624 // called whenever the pane is made visible.
9626 // Does layout/resize of child widget(s)
9628 if(this._needLayout){
9629 // If a layout has been scheduled for when we become visible, do it now
9630 this._layout(this._changeSize, this._resultSize);
9633 this.inherited(arguments);
9635 // Need to keep track of whether ContentPane has been shown (which is different than
9636 // whether or not it's currently visible).
9637 this._wasShown = true;
9643 if(!dojo._hasResource["dojo
.html
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9644 dojo._hasResource["dojo
.html
"] = true;
9645 dojo.provide("dojo
.html
");
9648 dojo.getObject("html
", true, dojo);
9650 // the parser might be needed..
9651 (function(){ // private scope, sort of a namespace
9653 // idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes
9657 dojo.html._secureForInnerHtml = function(/*String*/ cont){
9659 // removes !DOCTYPE and title elements from the html string.
9661 // khtml is picky about dom faults, you can't attach a style or <title> node as child of body
9662 // must go into head, so we need to cut out those tags
9664 // An html string for insertion into the dom
9666 return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String
9670 dojo.html._emptyNode = function(node){
9672 // removes all child nodes from the given node
9674 // the parent element
9677 dojo.html._emptyNode = dojo.empty;
9679 dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){
9681 // inserts the given content into the given node
9683 // the parent element
9685 // the content to be set on the parent element.
9686 // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
9692 if(typeof cont == "string
") {
9693 cont = d._toDom(cont, node.ownerDocument);
9695 if(!cont.nodeType && d.isArrayLike(cont)) {
9696 // handle as enumerable, but it may shrink as we enumerate it
9697 for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0) {
9698 d.place( cont[i], node, "last
");
9701 // pass nodes, documentFragments and unknowns through to dojo.place
9702 d.place(cont, node, "last
");
9710 // we wrap up the content-setting operation in a object
9711 dojo.declare("dojo
.html
._ContentSetter
", null,
9713 // node: DomNode|String
9714 // An node which will be the parent element that we set content into
9717 // content: String|DomNode|DomNode[]
9718 // The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes
9722 // Usually only used internally, and auto-generated with each instance
9725 // cleanContent: Boolean
9726 // Should the content be treated as a full html document,
9727 // and the real content stripped of <html>, <body> wrapper before injection
9728 cleanContent: false,
9730 // extractContent: Boolean
9731 // Should the content be treated as a full html document, and the real content stripped of <html>, <body> wrapper before injection
9732 extractContent: false,
9734 // parseContent: Boolean
9735 // Should the node by passed to the parser after the new content is set
9736 parseContent: false,
9738 // parserScope: String
9739 // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo,
9740 // will search for data-dojo-type (or dojoType). For backwards compatibility
9741 // reasons defaults to dojo._scopeName (which is "dojo
" except when
9742 // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
9743 parserScope: dojo._scopeName,
9746 // Start the child widgets after parsing them. Only obeyed if parseContent is true.
9750 constructor: function(/* Object */params, /* String|DomNode */node){
9752 // Provides a configurable, extensible object to wrap the setting on content on a node
9753 // call the set() method to actually set the content..
9755 // the original params are mixed directly into the instance "this"
9756 dojo.mixin(this, params || {});
9758 // give precedence to params.node vs. the node argument
9759 // and ensure its a node, not an id string
9760 node = this.node = dojo.byId( this.node || node );
9765 (node) ? node.id || node.tagName : "",
9770 set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){
9772 // front-end to the set-content sequence
9774 // An html string, node or enumerable list of nodes for insertion into the dom
9775 // If not provided, the object's content property will be used
9776 if(undefined !== cont){
9777 this.content = cont;
9779 // in the re-use scenario, set needs to be able to mixin new configuration
9781 this._mixin(params);
9790 setContent: function(){
9792 // sets the content on the node
9794 var node = this.node;
9797 throw new Error(this.declaredClass + ": setContent given no node
");
9800 node = dojo.html._setNodeContent(node, this.content);
9802 // check if a domfault occurs when we are appending this.errorMessage
9803 // like for instance if domNode is a UL and we try append a DIV
9805 // FIXME: need to allow the user to provide a content error message string
9806 var errMess = this.onContentError(e);
9808 node.innerHTML = errMess;
9810 console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e);
9813 // always put back the node for the next method
9814 this.node = node; // DomNode
9819 // cleanly empty out existing content
9821 // destroy any widgets from a previous run
9822 // NOTE: if you dont want this you'll need to empty
9823 // the parseResults array property yourself to avoid bad things happenning
9824 if(this.parseResults && this.parseResults.length) {
9825 dojo.forEach(this.parseResults, function(w) {
9830 delete this.parseResults;
9832 // this is fast, but if you know its already empty or safe, you could
9833 // override empty to skip this step
9834 dojo.html._emptyNode(this.node);
9837 onBegin: function(){
9839 // Called after instantiation, but before set();
9840 // It allows modification of any of the object properties
9841 // - including the node and content provided - before the set operation actually takes place
9842 // This default implementation checks for cleanContent and extractContent flags to
9843 // optionally pre-process html string content
9844 var cont = this.content;
9846 if(dojo.isString(cont)){
9847 if(this.cleanContent){
9848 cont = dojo.html._secureForInnerHtml(cont);
9851 if(this.extractContent){
9852 var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
9853 if(match){ cont = match[1]; }
9857 // clean out the node and any cruft associated with it - like widgets
9860 this.content = cont;
9861 return this.node; /* DomNode */
9866 // Called after set(), when the new content has been pushed into the node
9867 // It provides an opportunity for post-processing before handing back the node to the caller
9868 // This default implementation checks a parseContent flag to optionally run the dojo parser over the new content
9869 if(this.parseContent){
9870 // populates this.parseResults if you need those..
9873 return this.node; /* DomNode */
9876 tearDown: function(){
9878 // manually reset the Setter instance if its being re-used for example for another set()
9880 // tearDown() is not called automatically.
9881 // In normal use, the Setter instance properties are simply allowed to fall out of scope
9882 // but the tearDown method can be called to explicitly reset this instance.
9883 delete this.parseResults;
9885 delete this.content;
9888 onContentError: function(err){
9889 return "Error occured setting content
: " + err;
9892 _mixin: function(params){
9893 // mix properties/methods into the instance
9894 // TODO: the intention with tearDown is to put the Setter's state
9895 // back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params)
9896 // so we could do something here to move the original properties aside for later restoration
9897 var empty = {}, key;
9899 if(key in empty){ continue; }
9900 // TODO: here's our opportunity to mask the properties we dont consider configurable/overridable
9901 // .. but history shows we'll almost always guess wrong
9902 this[key] = params[key];
9907 // runs the dojo parser over the node contents, storing any results in this.parseResults
9908 // Any errors resulting from parsing are passed to _onError for handling
9910 var rootNode = this.node;
9912 // store the results (widgets, whatever) for potential retrieval
9914 dojo.forEach(["dir
", "lang
", "textDir
"], function(name){
9916 inherited[name] = this[name];
9919 this.parseResults = dojo.parser.parse({
9921 noStart: !this.startup,
9922 inherited: inherited,
9923 scope: this.parserScope
9926 this._onError('Content', e, "Error parsing
in _ContentSetter
#"+this.id);
9930 _onError: function(type, err, consoleText){
9932 // shows user the string that is returned by on[type]Error
9933 // overide/implement on[type]Error and return your own string to customize
9934 var errText = this['on' + type + 'Error'].call(this, err);
9936 console.error(consoleText, err);
9937 }else if(errText){ // a empty string won't change current content
9938 dojo.html._setNodeContent(this.node, errText, true);
9941 }); // end dojo.declare()
9943 dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){
9945 // inserts (replaces) the given content into the given node. dojo.place(cont, node, "only
")
9946 // may be a better choice for simple HTML insertion.
9948 // Unless you need to use the params capabilities of this method, you should use
9949 // dojo.place(cont, node, "only
"). dojo.place() has more robust support for injecting
9950 // an HTML string into the DOM, but it only handles inserting an HTML string as DOM
9951 // elements, or inserting a DOM node. dojo.place does not handle NodeList insertions
9952 // or the other capabilities as defined by the params object for this method.
9954 // the parent element that will receive the content
9956 // the content to be set on the parent element.
9957 // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
9959 // Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter
9961 // A safe string/node/nodelist content replacement/injection with hooks for extension
9963 // dojo.html.set(node, "some string
");
9964 // dojo.html.set(node, contentNode, {options});
9965 // dojo.html.set(node, myNode.childNodes, {options});
9966 if(undefined == cont){
9967 console.warn("dojo
.html
.set: no cont argument provided
, using empty string
");
9972 return dojo.html._setNodeContent(node, cont, true);
9974 // more options but slower
9975 // note the arguments are reversed in order, to match the convention for instantiation via the parser
9976 var op = new dojo.html._ContentSetter(dojo.mixin(
9978 { content: cont, node: node }
9987 if(!dojo._hasResource["dijit
.layout
.ContentPane
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9988 dojo._hasResource["dijit
.layout
.ContentPane
"] = true;
9989 dojo.provide("dijit
.layout
.ContentPane
");
9998 "dijit
.layout
.ContentPane
", [dijit._Widget, dijit.layout._ContentPaneResizeMixin],
10001 // A widget containing an HTML fragment, specified inline
10002 // or by uri. Fragment may include widgets.
10005 // This widget embeds a document fragment in the page, specified
10006 // either by uri, javascript generated markup or DOM reference.
10007 // Any widgets within this content are instantiated and managed,
10008 // but laid out according to the HTML structure. Unlike IFRAME,
10009 // ContentPane embeds a document fragment as would be found
10010 // inside the BODY tag of a full HTML document. It should not
10011 // contain the HTML, HEAD, or BODY tags.
10012 // For more advanced functionality with scripts and
10013 // stylesheets, see dojox.layout.ContentPane. This widget may be
10014 // used stand alone or as a base class for other widgets.
10015 // ContentPane is useful as a child of other layout containers
10016 // such as BorderContainer or TabContainer, but note that those
10017 // widgets can contain any widget as a child.
10020 // Some quick samples:
10021 // To change the innerHTML: cp.set('content', '<b>new content</b>')
10023 // Or you can send it a NodeList: cp.set('content', dojo.query('div [class=selected]', userSelection))
10025 // To do an ajax update: cp.set('href', url)
10028 // The href of the content that displays now.
10029 // Set this at construction if you want to load data externally when the
10030 // pane is shown. (Set preload=true to load it immediately.)
10031 // Changing href after creation doesn't have any effect; Use set('href', ...);
10035 // content: String || DomNode || NodeList || dijit._Widget
10036 // The innerHTML of the ContentPane.
10037 // Note that the initialization parameter / argument to set("content
", ...)
10038 // can be a String, DomNode, Nodelist, or _Widget.
10042 // extractContent: Boolean
10043 // Extract visible content from inside of <body> .... </body>.
10044 // I.e., strip <html> and <head> (and it's contents) from the href
10045 extractContent: false,
10047 // parseOnLoad: Boolean
10048 // Parse content and create the widgets, if any.
10051 // parserScope: String
10052 // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo,
10053 // will search for data-dojo-type (or dojoType). For backwards compatibility
10054 // reasons defaults to dojo._scopeName (which is "dojo
" except when
10055 // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
10056 parserScope: dojo._scopeName,
10058 // preventCache: Boolean
10059 // Prevent caching of data from href's by appending a timestamp to the href.
10060 preventCache: false,
10062 // preload: Boolean
10063 // Force load of data on initialization even if pane is hidden.
10066 // refreshOnShow: Boolean
10067 // Refresh (re-download) content when pane goes from hidden to shown
10068 refreshOnShow: false,
10070 // loadingMessage: String
10071 // Message that shows while downloading
10072 loadingMessage: "<span
class='dijitContentPaneLoading'>${loadingState}
</span
>",
10074 // errorMessage: String
10075 // Message that shows if an error occurs
10076 errorMessage: "<span
class='dijitContentPaneError'>${errorState}
</span
>",
10078 // isLoaded: [readonly] Boolean
10079 // True if the ContentPane has data in it, either specified
10080 // during initialization (via href or inline content), or set
10081 // via set('content', ...) / set('href', ...)
10083 // False if it doesn't have any content, or if ContentPane is
10084 // still in the process of downloading href.
10087 baseClass: "dijitContentPane
",
10090 // Parameters to pass to xhrGet() request, for example:
10091 // | <div dojoType="dijit
.layout
.ContentPane
" href="./bar
" ioArgs="{timeout
: 500}">
10094 // onLoadDeferred: [readonly] dojo.Deferred
10095 // This is the `dojo.Deferred` returned by set('href', ...) and refresh().
10096 // Calling onLoadDeferred.addCallback() or addErrback() registers your
10097 // callback to be called only once, when the prior set('href', ...) call or
10098 // the initial href parameter to the constructor finishes loading.
10100 // This is different than an onLoad() handler which gets called any time any href
10101 // or content is loaded.
10102 onLoadDeferred: null,
10104 // Override _Widget's attributeMap because we don't want the title attribute (used to specify
10105 // tab labels) to be copied to ContentPane.domNode... otherwise a tooltip shows up over the
10107 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
10111 // Flag to parser that I'll parse my contents, so it shouldn't.
10114 // template: [private] Boolean
10115 // Flag from the parser that this ContentPane is inside a template
10116 // so the contents are pre-parsed.
10117 // (TODO: this declaration can be commented out in 2.0)
10120 create: function(params, srcNodeRef){
10121 // Convert a srcNodeRef argument into a content parameter, so that the original contents are
10122 // processed in the same way as contents set via set("content
", ...), calling the parser etc.
10123 // Avoid modifying original params object since that breaks NodeList instantiation, see #11906.
10124 if((!params || !params.template) && srcNodeRef && !("href
" in params) && !("content
" in params)){
10125 var df = dojo.doc.createDocumentFragment();
10126 srcNodeRef = dojo.byId(srcNodeRef)
10127 while(srcNodeRef.firstChild){
10128 df.appendChild(srcNodeRef.firstChild);
10130 params = dojo.delegate(params, {content: df});
10132 this.inherited(arguments, [params, srcNodeRef]);
10135 postMixInProperties: function(){
10136 this.inherited(arguments);
10137 var messages = dojo.i18n.getLocalization("dijit
", "loading
", this.lang);
10138 this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages);
10139 this.errorMessage = dojo.string.substitute(this.errorMessage, messages);
10142 buildRendering: function(){
10143 this.inherited(arguments);
10145 // Since we have no template we need to set this.containerNode ourselves, to make getChildren() work.
10146 // For subclasses of ContentPane that do have a template, does nothing.
10147 if(!this.containerNode){
10148 this.containerNode = this.domNode;
10151 // remove the title attribute so it doesn't show up when hovering
10152 // over a node (TODO: remove in 2.0, no longer needed after #11490)
10153 this.domNode.title = "";
10155 if(!dojo.attr(this.domNode,"role
")){
10156 dijit.setWaiRole(this.domNode, "group
");
10160 _startChildren: function(){
10162 // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
10164 // This starts all the widgets
10165 this.inherited(arguments);
10167 // And this catches stuff like dojo.dnd.Source
10168 if(this._contentSetter){
10169 dojo.forEach(this._contentSetter.parseResults, function(obj){
10170 if(!obj._started && !obj._destroyed && dojo.isFunction(obj.startup)){
10172 obj._started = true;
10178 setHref: function(/*String|Uri*/ href){
10180 // Deprecated. Use set('href', ...) instead.
10181 dojo.deprecated("dijit
.layout
.ContentPane
.setHref() is deprecated
. Use
set('href', ...) instead
.", "", "2.0");
10182 return this.set("href
", href);
10184 _setHrefAttr: function(/*String|Uri*/ href){
10186 // Hook so set("href
", ...) works.
10188 // Reset the (external defined) content of this pane and replace with new url
10189 // Note: It delays the download until widget is shown if preload is false.
10191 // url to the page you want to get, must be within the same domain as your mainpage
10193 // Cancel any in-flight requests (a set('href', ...) will cancel any in-flight set('href', ...))
10196 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel
"));
10197 this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad
"));
10199 this._set("href
", href);
10201 // _setHrefAttr() is called during creation and by the user, after creation.
10202 // Assuming preload == false, only in the second case do we actually load the URL;
10203 // otherwise it's done in startup(), and only if this widget is shown.
10204 if(this.preload || (this._created && this._isShown())){
10207 // Set flag to indicate that href needs to be loaded the next time the
10208 // ContentPane is made visible
10209 this._hrefChanged = true;
10212 return this.onLoadDeferred; // dojo.Deferred
10215 setContent: function(/*String|DomNode|Nodelist*/data){
10217 // Deprecated. Use set('content', ...) instead.
10218 dojo.deprecated("dijit
.layout
.ContentPane
.setContent() is deprecated
. Use
set('content', ...) instead
.", "", "2.0");
10219 this.set("content
", data);
10221 _setContentAttr: function(/*String|DomNode|Nodelist*/data){
10223 // Hook to make set("content
", ...) work.
10224 // Replaces old content with data content, include style classes from old content
10226 // the new Content may be String, DomNode or NodeList
10228 // if data is a NodeList (or an array of nodes) nodes are copied
10229 // so you can import nodes from another document implicitly
10231 // clear href so we can't run refresh and clear content
10232 // refresh should only work if we downloaded the content
10233 this._set("href
", "");
10235 // Cancel any in-flight requests (a set('content', ...) will cancel any in-flight set('href', ...))
10238 // Even though user is just setting content directly, still need to define an onLoadDeferred
10239 // because the _onLoadHandler() handler is still getting called from setContent()
10240 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel
"));
10242 // For back-compat reasons, call onLoad() for set('content', ...)
10243 // calls but not for content specified in srcNodeRef (ie: <div dojoType=ContentPane>...</div>)
10244 // or as initialization parameter (ie: new ContentPane({content: ...})
10245 this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad
"));
10248 this._setContent(data || "");
10250 this._isDownloaded = false; // mark that content is from a set('content') not a set('href')
10252 return this.onLoadDeferred; // dojo.Deferred
10254 _getContentAttr: function(){
10256 // Hook to make get("content
") work
10257 return this.containerNode.innerHTML;
10260 cancel: function(){
10262 // Cancels an in-flight download of content
10263 if(this._xhrDfd && (this._xhrDfd.fired == -1)){
10264 this._xhrDfd.cancel();
10266 delete this._xhrDfd; // garbage collect
10268 this.onLoadDeferred = null;
10271 uninitialize: function(){
10272 if(this._beingDestroyed){
10275 this.inherited(arguments);
10278 destroyRecursive: function(/*Boolean*/ preserveDom){
10280 // Destroy the ContentPane and its contents
10282 // if we have multiple controllers destroying us, bail after the first
10283 if(this._beingDestroyed){
10286 this.inherited(arguments);
10289 _onShow: function(){
10291 // Called when the ContentPane is made visible
10293 // For a plain ContentPane, this is called on initialization, from startup().
10294 // If the ContentPane is a hidden pane of a TabContainer etc., then it's
10295 // called whenever the pane is made visible.
10297 // Does necessary processing, including href download and layout/resize of
10300 this.inherited(arguments);
10303 if(!this._xhrDfd && // if there's an href that isn't already being loaded
10304 (!this.isLoaded || this._hrefChanged || this.refreshOnShow)
10306 return this.refresh(); // If child has an href, promise that fires when the load is complete
10311 refresh: function(){
10313 // [Re]download contents of href and display
10315 // 1. cancels any currently in-flight requests
10316 // 2. posts "loading
..." message
10317 // 3. sends XHR to download new data
10319 // Cancel possible prior in-flight request
10322 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel
"));
10323 this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad
"));
10325 return this.onLoadDeferred; // If child has an href, promise that fires when refresh is complete
10330 // Load/reload the href specified in this.href
10332 // display loading message
10333 this._setContent(this.onDownloadStart(), true);
10337 preventCache: (this.preventCache || this.refreshOnShow),
10341 if(dojo.isObject(this.ioArgs)){
10342 dojo.mixin(getArgs, this.ioArgs);
10345 var hand = (this._xhrDfd = (this.ioMethod || dojo.xhrGet)(getArgs));
10347 hand.addCallback(function(html){
10349 self._isDownloaded = true;
10350 self._setContent(html, false);
10351 self.onDownloadEnd();
10353 self._onError('Content', err); // onContentError
10355 delete self._xhrDfd;
10359 hand.addErrback(function(err){
10360 if(!hand.canceled){
10361 // show error message in the pane
10362 self._onError('Download', err); // onDownloadError
10364 delete self._xhrDfd;
10368 // Remove flag saying that a load is needed
10369 delete this._hrefChanged;
10372 _onLoadHandler: function(data){
10374 // This is called whenever new content is being loaded
10375 this._set("isLoaded
", true);
10377 this.onLoadDeferred.callback(data);
10379 console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message);
10383 _onUnloadHandler: function(){
10385 // This is called whenever the content is being unloaded
10386 this._set("isLoaded
", false);
10390 console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message);
10394 destroyDescendants: function(){
10396 // Destroy all the widgets inside the ContentPane and empty containerNode
10398 // Make sure we call onUnload (but only when the ContentPane has real content)
10400 this._onUnloadHandler();
10403 // Even if this.isLoaded == false there might still be a "Loading
..." message
10404 // to erase, so continue...
10406 // For historical reasons we need to delete all widgets under this.containerNode,
10407 // even ones that the user has created manually.
10408 var setter = this._contentSetter;
10409 dojo.forEach(this.getChildren(), function(widget){
10410 if(widget.destroyRecursive){
10411 widget.destroyRecursive();
10415 // Most of the widgets in setter.parseResults have already been destroyed, but
10416 // things like Menu that have been moved to <body> haven't yet
10417 dojo.forEach(setter.parseResults, function(widget){
10418 if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == dojo.body()){
10419 widget.destroyRecursive();
10422 delete setter.parseResults;
10425 // And then clear away all the DOM nodes
10426 dojo.html._emptyNode(this.containerNode);
10428 // Delete any state information we have about current contents
10429 delete this._singleChild;
10432 _setContent: function(/*String|DocumentFragment*/ cont, /*Boolean*/ isFakeContent){
10434 // Insert the content into the container node
10436 // first get rid of child widgets
10437 this.destroyDescendants();
10439 // dojo.html.set will take care of the rest of the details
10440 // we provide an override for the error handling to ensure the widget gets the errors
10441 // configure the setter instance with only the relevant widget instance properties
10442 // NOTE: unless we hook into attr, or provide property setters for each property,
10443 // we need to re-configure the ContentSetter with each use
10444 var setter = this._contentSetter;
10445 if(! (setter && setter instanceof dojo.html._ContentSetter)){
10446 setter = this._contentSetter = new dojo.html._ContentSetter({
10447 node: this.containerNode,
10448 _onError: dojo.hitch(this, this._onError),
10449 onContentError: dojo.hitch(this, function(e){
10450 // fires if a domfault occurs when we are appending this.errorMessage
10451 // like for instance if domNode is a UL and we try append a DIV
10452 var errMess = this.onContentError(e);
10454 this.containerNode.innerHTML = errMess;
10456 console.error('Fatal '+this.id+' could not change content due to '+e.message, e);
10463 var setterParams = dojo.mixin({
10464 cleanContent: this.cleanContent,
10465 extractContent: this.extractContent,
10466 parseContent: this.parseOnLoad,
10467 parserScope: this.parserScope,
10471 }, this._contentSetterParams || {});
10473 setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams );
10475 // setter params must be pulled afresh from the ContentPane each time
10476 delete this._contentSetterParams;
10479 this._checkIfSingleChild();
10482 if(!isFakeContent){
10484 // Startup each top level child widget (and they will start their children, recursively)
10485 this._startChildren();
10487 // Call resize() on each of my child layout widgets,
10488 // or resize() on my single child layout widget...
10489 // either now (if I'm currently visible) or when I become visible
10490 this._scheduleLayout();
10493 this._onLoadHandler(cont);
10497 _onError: function(type, err, consoleText){
10498 this.onLoadDeferred.errback(err);
10500 // shows user the string that is returned by on[type]Error
10501 // override on[type]Error and return your own string to customize
10502 var errText = this['on' + type + 'Error'].call(this, err);
10504 console.error(consoleText, err);
10505 }else if(errText){// a empty string won't change current content
10506 this._setContent(errText, true);
10510 // EVENT's, should be overide-able
10511 onLoad: function(data){
10513 // Event hook, is called after everything is loaded and widgetified
10518 onUnload: function(){
10520 // Event hook, is called before old content is cleared
10525 onDownloadStart: function(){
10527 // Called before download starts.
10529 // The string returned by this function will be the html
10530 // that tells the user we are loading something.
10531 // Override with your own function if you want to change text.
10534 return this.loadingMessage;
10537 onContentError: function(/*Error*/ error){
10539 // Called on DOM faults, require faults etc. in content.
10541 // In order to display an error message in the pane, return
10542 // the error message from this method, as an HTML string.
10544 // By default (if this method is not overriden), it returns
10545 // nothing, so the error message is just printed to the console.
10550 onDownloadError: function(/*Error*/ error){
10552 // Called when download error occurs.
10554 // In order to display an error message in the pane, return
10555 // the error message from this method, as an HTML string.
10557 // Default behavior (if this method is not overriden) is to display
10558 // the error message inside the pane.
10561 return this.errorMessage;
10564 onDownloadEnd: function(){
10566 // Called when download is finished.
10574 if(!dojo._hasResource["dijit
.TooltipDialog
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
10575 dojo._hasResource["dijit
.TooltipDialog
"] = true;
10576 dojo.provide("dijit
.TooltipDialog
");
10584 "dijit
.TooltipDialog
",
10585 [dijit.layout.ContentPane, dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin],
10588 // Pops up a dialog that appears like a Tooltip
10591 // Description of tooltip dialog (required for a11y)
10594 // doLayout: [protected] Boolean
10595 // Don't change this parameter from the default value.
10596 // This ContentPane parameter doesn't make sense for TooltipDialog, since TooltipDialog
10597 // is never a child of a layout container, nor can you specify the size of
10598 // TooltipDialog in order to control the size of an inner widget.
10601 // autofocus: Boolean
10602 // A Toggle to modify the default focus behavior of a Dialog, which
10603 // is to focus on the first dialog element after opening the dialog.
10604 // False will disable autofocusing. Default: true
10607 // baseClass: [protected] String
10608 // The root className to use for the various states of this widget
10609 baseClass: "dijitTooltipDialog
",
10611 // _firstFocusItem: [private] [readonly] DomNode
10612 // The pointer to the first focusable node in the dialog.
10613 // Set by `dijit._DialogMixin._getFocusItems`.
10614 _firstFocusItem: null,
10616 // _lastFocusItem: [private] [readonly] DomNode
10617 // The pointer to which node has focus prior to our dialog.
10618 // Set by `dijit._DialogMixin._getFocusItems`.
10619 _lastFocusItem: null,
10621 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"),
10623 _setTitleAttr: function(/*String*/ title){
10624 this.containerNode.title = title;
10625 this._set("title
", title)
10628 postCreate: function(){
10629 this.inherited(arguments);
10630 this.connect(this.containerNode, "onkeypress
", "_onKey
");
10633 orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ corner){
10635 // Configure widget to be displayed in given position relative to the button.
10636 // This is called from the dijit.popup code, and should not be called
10640 var newC = "dijitTooltipAB
" + (corner.charAt(1) == 'L' ? "Left
" : "Right
")
10642 + (corner.charAt(0) == 'T' ? "Below
" : "Above
");
10644 dojo.replaceClass(this.domNode, newC, this._currentOrientClass || "");
10645 this._currentOrientClass = newC;
10650 // Focus on first field
10651 this._getFocusItems(this.containerNode);
10652 dijit.focus(this._firstFocusItem);
10655 onOpen: function(/*Object*/ pos){
10657 // Called when dialog is displayed.
10658 // This is called from the dijit.popup code, and should not be called directly.
10662 this.orient(this.domNode,pos.aroundCorner, pos.corner);
10663 this._onShow(); // lazy load trigger
10666 onClose: function(){
10668 // Called when dialog is hidden.
10669 // This is called from the dijit.popup code, and should not be called directly.
10675 _onKey: function(/*Event*/ evt){
10677 // Handler for keyboard events
10679 // Keep keyboard focus in dialog; close dialog on escape key
10683 var node = evt.target;
10684 var dk = dojo.keys;
10685 if(evt.charOrCode === dk.TAB){
10686 this._getFocusItems(this.containerNode);
10688 var singleFocusItem = (this._firstFocusItem == this._lastFocusItem);
10689 if(evt.charOrCode == dk.ESCAPE){
10690 // Use setTimeout to avoid crash on IE, see #10396.
10691 setTimeout(dojo.hitch(this, "onCancel
"), 0);
10692 dojo.stopEvent(evt);
10693 }else if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){
10694 if(!singleFocusItem){
10695 dijit.focus(this._lastFocusItem); // send focus to last item in dialog
10697 dojo.stopEvent(evt);
10698 }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){
10699 if(!singleFocusItem){
10700 dijit.focus(this._firstFocusItem); // send focus to first item in dialog
10702 dojo.stopEvent(evt);
10703 }else if(evt.charOrCode === dk.TAB){
10704 // we want the browser's default tab handling to move focus
10705 // but we don't want the tab to propagate upwards
10706 evt.stopPropagation();
10714 if(!dojo._hasResource["dijit
.Dialog
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
10715 dojo._hasResource["dijit
.Dialog
"] = true;
10716 dojo.provide("dijit
.Dialog
");
10732 // dijit/TooltipDialog required for back-compat. TODO: remove in 2.0
10735 dijit._underlay = function(kwArgs){
10737 // A shared instance of a `dijit.DialogUnderlay`
10740 // A shared instance of a `dijit.DialogUnderlay` created and
10741 // used by `dijit.Dialog`, though never created until some Dialog
10742 // or subclass thereof is shown.
10746 "dijit
._DialogBase
",
10747 [dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin, dijit._CssStateMixin],
10750 // A modal dialog Widget
10753 // Pops up a modal dialog window, blocking access to the screen
10754 // and also graying out the screen Dialog is extended from
10755 // ContentPane so it supports all the same parameters (href, etc.)
10758 // | <div dojoType="dijit
.Dialog
" href="test
.html
"></div>
10761 // | var foo = new dijit.Dialog({ title: "test dialog
", content: "test content
" };
10762 // | dojo.body().appendChild(foo.domNode);
10763 // | foo.startup();
10765 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"),
10767 baseClass: "dijitDialog
",
10770 closeButtonNode: "dijitDialogCloseIcon
"
10773 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
10775 { node: "titleNode
", type: "innerHTML
" },
10776 { node: "titleBar
", type: "attribute
" }
10778 "aria
-describedby
":""
10781 // open: [readonly] Boolean
10782 // True if Dialog is currently displayed on screen.
10785 // duration: Integer
10786 // The time in milliseconds it takes the dialog to fade in and out
10787 duration: dijit.defaultDuration,
10789 // refocus: Boolean
10790 // A Toggle to modify the default focus behavior of a Dialog, which
10791 // is to re-focus the element which had focus before being opened.
10792 // False will disable refocusing. Default: true
10795 // autofocus: Boolean
10796 // A Toggle to modify the default focus behavior of a Dialog, which
10797 // is to focus on the first dialog element after opening the dialog.
10798 // False will disable autofocusing. Default: true
10801 // _firstFocusItem: [private readonly] DomNode
10802 // The pointer to the first focusable node in the dialog.
10803 // Set by `dijit._DialogMixin._getFocusItems`.
10804 _firstFocusItem: null,
10806 // _lastFocusItem: [private readonly] DomNode
10807 // The pointer to which node has focus prior to our dialog.
10808 // Set by `dijit._DialogMixin._getFocusItems`.
10809 _lastFocusItem: null,
10811 // doLayout: [protected] Boolean
10812 // Don't change this parameter from the default value.
10813 // This ContentPane parameter doesn't make sense for Dialog, since Dialog
10814 // is never a child of a layout container, nor can you specify the size of
10815 // Dialog in order to control the size of an inner widget.
10818 // draggable: Boolean
10819 // Toggles the moveable aspect of the Dialog. If true, Dialog
10820 // can be dragged by it's title. If false it will remain centered
10821 // in the viewport.
10824 //aria-describedby: String
10825 // Allows the user to add an aria-describedby attribute onto the dialog. The value should
10826 // be the id of the container element of text that describes the dialog purpose (usually
10827 // the first text in the dialog).
10828 // <div dojoType="dijit
.Dialog
" aria-describedby="intro
" .....>
10829 // <div id="intro
">Introductory text</div>
10830 // <div>rest of dialog contents</div>
10832 "aria
-describedby
":"",
10834 postMixInProperties: function(){
10835 var _nlsResources = dojo.i18n.getLocalization("dijit
", "common
");
10836 dojo.mixin(this, _nlsResources);
10837 this.inherited(arguments);
10840 postCreate: function(){
10841 dojo.style(this.domNode, {
10843 position:"absolute
"
10845 dojo.body().appendChild(this.domNode);
10847 this.inherited(arguments);
10849 this.connect(this, "onExecute
", "hide
");
10850 this.connect(this, "onCancel
", "hide
");
10851 this._modalconnects = [];
10854 onLoad: function(){
10856 // Called when data has been loaded from an href.
10857 // Unlike most other callbacks, this function can be connected to (via `dojo.connect`)
10858 // but should *not* be overridden.
10862 // when href is specified we need to reposition the dialog after the data is loaded
10863 // and find the focusable elements
10865 if(this.autofocus && dijit._DialogLevelManager.isTop(this)){
10866 this._getFocusItems(this.domNode);
10867 dijit.focus(this._firstFocusItem);
10869 this.inherited(arguments);
10872 _endDrag: function(e){
10874 // Called after dragging the Dialog. Saves the position of the dialog in the viewport.
10877 if(e && e.node && e.node === this.domNode){
10878 this._relativePosition = dojo.position(e.node);
10882 _setup: function(){
10884 // Stuff we need to do before showing the Dialog for the first
10885 // time (but we defer it until right beforehand, for
10886 // performance reasons).
10890 var node = this.domNode;
10892 if(this.titleBar && this.draggable){
10893 this._moveable = (dojo.isIE == 6) ?
10894 new dojo.dnd.TimedMoveable(node, { handle: this.titleBar }) : // prevent overload, see #5285
10895 new dojo.dnd.Moveable(node, { handle: this.titleBar, timeout: 0 });
10896 this._dndListener = dojo.subscribe("/dnd/move/stop
",this,"_endDrag
");
10898 dojo.addClass(node,"dijitDialogFixed
");
10901 this.underlayAttrs = {
10903 "class": dojo.map(this["class"].split(/\s/), function(s){ return s+"_underlay
"; }).join(" ")
10909 // If necessary, shrink dialog contents so dialog fits in viewport
10913 this._checkIfSingleChild();
10915 // If we resized the dialog contents earlier, reset them back to original size, so
10916 // that if the user later increases the viewport size, the dialog can display w/out a scrollbar.
10917 // Need to do this before the dojo.marginBox(this.domNode) call below.
10918 if(this._singleChild){
10919 if(this._singleChildOriginalStyle){
10920 this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle;
10922 delete this._singleChildOriginalStyle;
10924 dojo.style(this.containerNode, {
10930 var mb = dojo._getMarginSize(this.domNode);
10931 var viewport = dojo.window.getBox();
10932 if(mb.w >= viewport.w || mb.h >= viewport.h){
10933 // Reduce size of dialog contents so that dialog fits in viewport
10935 var w = Math.min(mb.w, Math.floor(viewport.w * 0.75)),
10936 h = Math.min(mb.h, Math.floor(viewport.h * 0.75));
10938 if(this._singleChild && this._singleChild.resize){
10939 this._singleChildOriginalStyle = this._singleChild.domNode.style.cssText;
10940 this._singleChild.resize({w: w, h: h});
10942 dojo.style(this.containerNode, {
10946 position: "relative
" // workaround IE bug moving scrollbar or dragging dialog
10950 if(this._singleChild && this._singleChild.resize){
10951 this._singleChild.resize();
10956 _position: function(){
10958 // Position modal dialog in the viewport. If no relative offset
10959 // in the viewport has been determined (by dragging, for instance),
10960 // center the node. Otherwise, use the Dialog's stored relative offset,
10961 // and position the node to top: left: values based on the viewport.
10964 if(!dojo.hasClass(dojo.body(),"dojoMove
")){
10965 var node = this.domNode,
10966 viewport = dojo.window.getBox(),
10967 p = this._relativePosition,
10968 bb = p ? null : dojo._getBorderBox(node),
10969 l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)),
10970 t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2))
10979 _onKey: function(/*Event*/ evt){
10981 // Handles the keyboard events for accessibility reasons
10985 if(evt.charOrCode){
10986 var dk = dojo.keys;
10987 var node = evt.target;
10988 if(evt.charOrCode === dk.TAB){
10989 this._getFocusItems(this.domNode);
10991 var singleFocusItem = (this._firstFocusItem == this._lastFocusItem);
10992 // see if we are shift-tabbing from first focusable item on dialog
10993 if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){
10994 if(!singleFocusItem){
10995 dijit.focus(this._lastFocusItem); // send focus to last item in dialog
10997 dojo.stopEvent(evt);
10998 }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){
10999 if(!singleFocusItem){
11000 dijit.focus(this._firstFocusItem); // send focus to first item in dialog
11002 dojo.stopEvent(evt);
11004 // see if the key is for the dialog
11006 if(node == this.domNode || dojo.hasClass(node, "dijitPopup
")){
11007 if(evt.charOrCode == dk.ESCAPE){
11010 return; // just let it go
11013 node = node.parentNode;
11015 // this key is for the disabled document window
11016 if(evt.charOrCode !== dk.TAB){ // allow tabbing into the dialog for a11y
11017 dojo.stopEvent(evt);
11018 // opera won't tab to a div
11019 }else if(!dojo.isOpera){
11021 this._firstFocusItem.focus();
11022 }catch(e){ /*squelch*/ }
11030 // Display the dialog
11031 // returns: dojo.Deferred
11032 // Deferred object that resolves when the display animation is complete
11034 if(this.open){ return; }
11036 if(!this._started){
11040 // first time we show the dialog, there's some initialization stuff to do
11041 if(!this._alreadyInitialized){
11043 this._alreadyInitialized=true;
11046 if(this._fadeOutDeferred){
11047 this._fadeOutDeferred.cancel();
11050 this._modalconnects.push(dojo.connect(window, "onscroll
", this, "layout
"));
11051 this._modalconnects.push(dojo.connect(window, "onresize
", this, function(){
11052 // IE gives spurious resize events and can actually get stuck
11053 // in an infinite loop if we don't ignore them
11054 var viewport = dojo.window.getBox();
11055 if(!this._oldViewport ||
11056 viewport.h != this._oldViewport.h ||
11057 viewport.w != this._oldViewport.w){
11059 this._oldViewport = viewport;
11062 this._modalconnects.push(dojo.connect(this.domNode, "onkeypress
", this, "_onKey
"));
11064 dojo.style(this.domNode, {
11069 this._set("open
", true);
11070 this._onShow(); // lazy load trigger
11075 // fade-in Animation object, setup below
11078 this._fadeInDeferred = new dojo.Deferred(dojo.hitch(this, function(){
11080 delete this._fadeInDeferred;
11083 fadeIn = dojo.fadeIn({
11084 node: this.domNode,
11085 duration: this.duration,
11086 beforeBegin: dojo.hitch(this, function(){
11087 dijit._DialogLevelManager.show(this, this.underlayAttrs);
11089 onEnd: dojo.hitch(this, function(){
11090 if(this.autofocus && dijit._DialogLevelManager.isTop(this)){
11091 // find focusable items each time dialog is shown since if dialog contains a widget the
11092 // first focusable items can change
11093 this._getFocusItems(this.domNode);
11094 dijit.focus(this._firstFocusItem);
11096 this._fadeInDeferred.callback(true);
11097 delete this._fadeInDeferred;
11101 return this._fadeInDeferred;
11107 // returns: dojo.Deferred
11108 // Deferred object that resolves when the hide animation is complete
11110 // if we haven't been initialized yet then we aren't showing and we can just return
11111 if(!this._alreadyInitialized){
11114 if(this._fadeInDeferred){
11115 this._fadeInDeferred.cancel();
11118 // fade-in Animation object, setup below
11121 this._fadeOutDeferred = new dojo.Deferred(dojo.hitch(this, function(){
11123 delete this._fadeOutDeferred;
11126 fadeOut = dojo.fadeOut({
11127 node: this.domNode,
11128 duration: this.duration,
11129 onEnd: dojo.hitch(this, function(){
11130 this.domNode.style.display = "none
";
11131 dijit._DialogLevelManager.hide(this);
11133 this._fadeOutDeferred.callback(true);
11134 delete this._fadeOutDeferred;
11138 if(this._scrollConnected){
11139 this._scrollConnected = false;
11141 dojo.forEach(this._modalconnects, dojo.disconnect);
11142 this._modalconnects = [];
11144 if(this._relativePosition){
11145 delete this._relativePosition;
11147 this._set("open
", false);
11149 return this._fadeOutDeferred;
11152 layout: function(){
11154 // Position the Dialog and the underlay
11157 if(this.domNode.style.display != "none
"){
11158 if(dijit._underlay){ // avoid race condition during show()
11159 dijit._underlay.layout();
11165 destroy: function(){
11166 if(this._fadeInDeferred){
11167 this._fadeInDeferred.cancel();
11169 if(this._fadeOutDeferred){
11170 this._fadeOutDeferred.cancel();
11172 if(this._moveable){
11173 this._moveable.destroy();
11175 if(this._dndListener){
11176 dojo.unsubscribe(this._dndListener);
11178 dojo.forEach(this._modalconnects, dojo.disconnect);
11180 dijit._DialogLevelManager.hide(this);
11182 this.inherited(arguments);
11189 [dijit.layout.ContentPane, dijit._DialogBase],
11193 dijit._DialogLevelManager = {
11195 // Controls the various active "levels
" on the page, starting with the
11196 // stuff initially visible on the page (at z-index 0), and then having an entry for
11197 // each Dialog shown.
11199 show: function(/*dijit._Widget*/ dialog, /*Object*/ underlayAttrs){
11201 // Call right before fade-in animation for new dialog.
11202 // Saves current focus, displays/adjusts underlay for new dialog,
11203 // and sets the z-index of the dialog itself.
11205 // New dialog will be displayed on top of all currently displayed dialogs.
11207 // Caller is responsible for setting focus in new dialog after the fade-in
11208 // animation completes.
11210 var ds = dijit._dialogStack;
11212 // Save current focus
11213 ds[ds.length-1].focus = dijit.getFocus(dialog);
11215 // Display the underlay, or if already displayed then adjust for this new dialog
11216 var underlay = dijit._underlay;
11217 if(!underlay || underlay._destroyed){
11218 underlay = dijit._underlay = new dijit.DialogUnderlay(underlayAttrs);
11220 underlay.set(dialog.underlayAttrs);
11223 // Set z-index a bit above previous dialog
11224 var zIndex = ds[ds.length-1].dialog ? ds[ds.length-1].zIndex + 2 : 950;
11225 if(ds.length == 1){ // first dialog
11228 dojo.style(dijit._underlay.domNode, 'zIndex', zIndex - 1);
11231 dojo.style(dialog.domNode, 'zIndex', zIndex);
11233 ds.push({dialog: dialog, underlayAttrs: underlayAttrs, zIndex: zIndex});
11236 hide: function(/*dijit._Widget*/ dialog){
11238 // Called when the specified dialog is hidden/destroyed, after the fade-out
11239 // animation ends, in order to reset page focus, fix the underlay, etc.
11240 // If the specified dialog isn't open then does nothing.
11242 // Caller is responsible for either setting display:none on the dialog domNode,
11243 // or calling dijit.popup.hide(), or removing it from the page DOM.
11245 var ds = dijit._dialogStack;
11247 if(ds[ds.length-1].dialog == dialog){
11248 // Removing the top (or only) dialog in the stack, return focus
11249 // to previous dialog
11253 var pd = ds[ds.length-1]; // the new active dialog (or the base page itself)
11256 if(ds.length == 1){
11257 // Returning to original page.
11258 // Hide the underlay, unless the underlay widget has already been destroyed
11259 // because we are being called during page unload (when all widgets are destroyed)
11260 if(!dijit._underlay._destroyed){
11261 dijit._underlay.hide();
11264 // Popping back to previous dialog, adjust underlay
11265 dojo.style(dijit._underlay.domNode, 'zIndex', pd.zIndex - 1);
11266 dijit._underlay.set(pd.underlayAttrs);
11270 if(dialog.refocus){
11271 // If we are returning control to a previous dialog but for some reason
11272 // that dialog didn't have a focused field, set focus to first focusable item.
11273 // This situation could happen if two dialogs appeared at nearly the same time,
11274 // since a dialog doesn't set it's focus until the fade-in is finished.
11275 var focus = pd.focus;
11276 if(!focus || (pd.dialog && !dojo.isDescendant(focus.node, pd.dialog.domNode))){
11277 pd.dialog._getFocusItems(pd.dialog.domNode);
11278 focus = pd.dialog._firstFocusItem;
11282 dijit.focus(focus);
11284 /* focus() will fail if user opened the dialog by clicking a non-focusable element */
11288 // Removing a dialog out of order (#9944, #10705).
11289 // Don't need to mess with underlay or z-index or anything.
11290 var idx = dojo.indexOf(dojo.map(ds, function(elem){return elem.dialog}), dialog);
11297 isTop: function(/*dijit._Widget*/ dialog){
11299 // Returns true if specified Dialog is the top in the task
11300 var ds = dijit._dialogStack;
11301 return ds[ds.length-1].dialog == dialog;
11305 // Stack representing the various active "levels
" on the page, starting with the
11306 // stuff initially visible on the page (at z-index 0), and then having an entry for
11307 // each Dialog shown.
11308 // Each element in stack has form {
11309 // dialog: dialogWidget,
11310 // focus: returnFromGetFocus(),
11311 // underlayAttrs: attributes to set on underlay (when this widget is active)
11313 dijit._dialogStack = [
11314 {dialog: null, focus: null, underlayAttrs: null} // entry for stuff at z-index: 0
11319 if(!dojo._hasResource["dijit
._HasDropDown
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11320 dojo._hasResource["dijit
._HasDropDown
"] = true;
11321 dojo.provide("dijit
._HasDropDown
");
11325 dojo.declare("dijit
._HasDropDown
",
11329 // Mixin for widgets that need drop down ability.
11331 // _buttonNode: [protected] DomNode
11332 // The button/icon/node to click to display the drop down.
11333 // Can be set via a dojoAttachPoint assignment.
11334 // If missing, then either focusNode or domNode (if focusNode is also missing) will be used.
11337 // _arrowWrapperNode: [protected] DomNode
11338 // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending
11339 // on where the drop down is set to be positioned.
11340 // Can be set via a dojoAttachPoint assignment.
11341 // If missing, then _buttonNode will be used.
11342 _arrowWrapperNode: null,
11344 // _popupStateNode: [protected] DomNode
11345 // The node to set the popupActive class on.
11346 // Can be set via a dojoAttachPoint assignment.
11347 // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used.
11348 _popupStateNode: null,
11350 // _aroundNode: [protected] DomNode
11351 // The node to display the popup around.
11352 // Can be set via a dojoAttachPoint assignment.
11353 // If missing, then domNode will be used.
11356 // dropDown: [protected] Widget
11357 // The widget to display as a popup. This widget *must* be
11358 // defined before the startup function is called.
11361 // autoWidth: [protected] Boolean
11362 // Set to true to make the drop down at least as wide as this
11363 // widget. Set to false if the drop down should just be its
11367 // forceWidth: [protected] Boolean
11368 // Set to true to make the drop down exactly as wide as this
11369 // widget. Overrides autoWidth.
11372 // maxHeight: [protected] Integer
11373 // The max height for our dropdown.
11374 // Any dropdown taller than this will have scrollbars.
11375 // Set to 0 for no max height, or -1 to limit height to available space in viewport
11378 // dropDownPosition: [const] String[]
11379 // This variable controls the position of the drop down.
11380 // It's an array of strings with the following values:
11382 // * before: places drop down to the left of the target node/widget, or to the right in
11383 // the case of RTL scripts like Hebrew and Arabic
11384 // * after: places drop down to the right of the target node/widget, or to the left in
11385 // the case of RTL scripts like Hebrew and Arabic
11386 // * above: drop down goes above target node
11387 // * below: drop down goes below target node
11389 // The list is positions is tried, in order, until a position is found where the drop down fits
11390 // within the viewport.
11392 dropDownPosition: ["below
","above
"],
11394 // _stopClickEvents: Boolean
11395 // When set to false, the click events will not be stopped, in
11396 // case you want to use them in your subwidget
11397 _stopClickEvents: true,
11399 _onDropDownMouseDown: function(/*Event*/ e){
11401 // Callback when the user mousedown's on the arrow icon
11403 if(this.disabled || this.readOnly){ return; }
11407 this._docHandler = this.connect(dojo.doc, "onmouseup
", "_onDropDownMouseUp
");
11409 this.toggleDropDown();
11412 _onDropDownMouseUp: function(/*Event?*/ e){
11414 // Callback when the user lifts their mouse after mouse down on the arrow icon.
11415 // If the drop is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our
11416 // dropDown node. If the event is missing, then we are not
11417 // a mouseup event.
11419 // This is useful for the common mouse movement pattern
11420 // with native browser <select> nodes:
11421 // 1. mouse down on the select node (probably on the arrow)
11422 // 2. move mouse to a menu item while holding down the mouse button
11423 // 3. mouse up. this selects the menu item as though the user had clicked it.
11424 if(e && this._docHandler){
11425 this.disconnect(this._docHandler);
11427 var dropDown = this.dropDown, overMenu = false;
11429 if(e && this._opened){
11430 // This code deals with the corner-case when the drop down covers the original widget,
11431 // because it's so large. In that case mouse-up shouldn't select a value from the menu.
11432 // Find out if our target is somewhere in our dropdown widget,
11433 // but not over our _buttonNode (the clickable node)
11434 var c = dojo.position(this._buttonNode, true);
11435 if(!(e.pageX >= c.x && e.pageX <= c.x + c.w) ||
11436 !(e.pageY >= c.y && e.pageY <= c.y + c.h)){
11438 while(t && !overMenu){
11439 if(dojo.hasClass(t, "dijitPopup
")){
11447 if(dropDown.onItemClick){
11449 while(t && !(menuItem = dijit.byNode(t))){
11452 if(menuItem && menuItem.onClick && menuItem.getParent){
11453 menuItem.getParent().onItemClick(menuItem, e);
11460 if(this._opened && dropDown.focus && dropDown.autoFocus !== false){
11461 // Focus the dropdown widget - do it on a delay so that we
11462 // don't steal our own focus.
11463 window.setTimeout(dojo.hitch(dropDown, "focus
"), 1);
11467 _onDropDownClick: function(/*Event*/ e){
11468 // the drop down was already opened on mousedown/keydown; just need to call stopEvent()
11469 if(this._stopClickEvents){
11474 buildRendering: function(){
11475 this.inherited(arguments);
11477 this._buttonNode = this._buttonNode || this.focusNode || this.domNode;
11478 this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode;
11480 // Add a class to the "dijitDownArrowButton
" type class to _buttonNode so theme can set direction of arrow
11481 // based on where drop down will normally appear
11483 "after
" : this.isLeftToRight() ? "Right
" : "Left
",
11484 "before
" : this.isLeftToRight() ? "Left
" : "Right
",
11489 }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down
";
11490 dojo.addClass(this._arrowWrapperNode || this._buttonNode, "dijit
" + defaultPos + "ArrowButton
");
11493 postCreate: function(){
11495 // set up nodes and connect our mouse and keypress events
11497 this.inherited(arguments);
11499 this.connect(this._buttonNode, "onmousedown
", "_onDropDownMouseDown
");
11500 this.connect(this._buttonNode, "onclick
", "_onDropDownClick
");
11501 this.connect(this.focusNode, "onkeypress
", "_onKey
");
11502 this.connect(this.focusNode, "onkeyup
", "_onKeyUp
");
11505 destroy: function(){
11507 // Destroy the drop down, unless it's already been destroyed. This can happen because
11508 // the drop down is a direct child of <body> even though it's logically my child.
11509 if(!this.dropDown._destroyed){
11510 this.dropDown.destroyRecursive();
11512 delete this.dropDown;
11514 this.inherited(arguments);
11517 _onKey: function(/*Event*/ e){
11519 // Callback when the user presses a key while focused on the button node
11521 if(this.disabled || this.readOnly){ return; }
11523 var d = this.dropDown, target = e.target;
11524 if(d && this._opened && d.handleKey){
11525 if(d.handleKey(e) === false){
11526 /* false return code means that the drop down handled the key */
11531 if(d && this._opened && e.charOrCode == dojo.keys.ESCAPE){
11532 this.closeDropDown();
11534 }else if(!this._opened &&
11535 (e.charOrCode == dojo.keys.DOWN_ARROW ||
11536 ( (e.charOrCode == dojo.keys.ENTER || e.charOrCode == " ") &&
11537 //ignore enter and space if the event is for a text input
11538 ((target.tagName || "").toLowerCase() !== 'input' ||
11539 (target.type && target.type.toLowerCase() !== 'text'))))){
11540 // Toggle the drop down, but wait until keyup so that the drop down doesn't
11541 // get a stray keyup event, or in the case of key-repeat (because user held
11542 // down key for too long), stray keydown events
11543 this._toggleOnKeyUp = true;
11548 _onKeyUp: function(){
11549 if(this._toggleOnKeyUp){
11550 delete this._toggleOnKeyUp;
11551 this.toggleDropDown();
11552 var d = this.dropDown; // drop down may not exist until toggleDropDown() call
11554 setTimeout(dojo.hitch(d, "focus
"), 1);
11559 _onBlur: function(){
11561 // Called magically when focus has shifted away from this widget and it's dropdown
11563 // Don't focus on button if the user has explicitly focused on something else (happens
11564 // when user clicks another control causing the current popup to close)..
11565 // But if focus is inside of the drop down then reset focus to me, because IE doesn't like
11566 // it when you display:none a node with focus.
11567 var focusMe = dijit._curFocus && this.dropDown && dojo.isDescendant(dijit._curFocus, this.dropDown.domNode);
11569 this.closeDropDown(focusMe);
11571 this.inherited(arguments);
11574 isLoaded: function(){
11576 // Returns whether or not the dropdown is loaded. This can
11577 // be overridden in order to force a call to loadDropDown().
11584 loadDropDown: function(/* Function */ loadCallback){
11586 // Loads the data for the dropdown, and at some point, calls
11587 // the given callback. This is basically a callback when the
11588 // user presses the down arrow button to open the drop down.
11595 toggleDropDown: function(){
11597 // Callback when the user presses the down arrow button or presses
11598 // the down arrow key to open/close the drop down.
11599 // Toggle the drop-down widget; if it is up, close it, if not, open it
11603 if(this.disabled || this.readOnly){ return; }
11605 // If we aren't loaded, load it first so there isn't a flicker
11606 if(!this.isLoaded()){
11607 this.loadDropDown(dojo.hitch(this, "openDropDown
"));
11610 this.openDropDown();
11613 this.closeDropDown();
11617 openDropDown: function(){
11619 // Opens the dropdown for this widget. To be called only when this.dropDown
11620 // has been created and is ready to display (ie, it's data is loaded).
11622 // return value of dijit.popup.open()
11626 var dropDown = this.dropDown,
11627 ddNode = dropDown.domNode,
11628 aroundNode = this._aroundNode || this.domNode,
11631 // Prepare our popup's height and honor maxHeight if it exists.
11633 // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(),
11634 // ie, dependent on how much space is available (BK)
11636 if(!this._preparedNode){
11637 this._preparedNode = true;
11638 // Check if we have explicitly set width and height on the dropdown widget dom node
11639 if(ddNode.style.width){
11640 this._explicitDDWidth = true;
11642 if(ddNode.style.height){
11643 this._explicitDDHeight = true;
11647 // Code for resizing dropdown (height limitation, or increasing width to match my width)
11648 if(this.maxHeight || this.forceWidth || this.autoWidth){
11651 visibility: "hidden
"
11653 if(!this._explicitDDWidth){
11654 myStyle.width = "";
11656 if(!this._explicitDDHeight){
11657 myStyle.height = "";
11659 dojo.style(ddNode, myStyle);
11661 // Figure out maximum height allowed (if there is a height restriction)
11662 var maxHeight = this.maxHeight;
11663 if(maxHeight == -1){
11664 // limit height to space available in viewport either above or below my domNode
11665 // (whichever side has more room)
11666 var viewport = dojo.window.getBox(),
11667 position = dojo.position(aroundNode, false);
11668 maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h)));
11671 // Attach dropDown to DOM and make make visibility:hidden rather than display:none
11672 // so we call startup() and also get the size
11673 if(dropDown.startup && !dropDown._started){
11674 dropDown.startup();
11677 dijit.popup.moveOffScreen(dropDown);
11678 // Get size of drop down, and determine if vertical scroll bar needed
11679 var mb = dojo._getMarginSize(ddNode);
11680 var overHeight = (maxHeight && mb.h > maxHeight);
11681 dojo.style(ddNode, {
11682 overflowX: "hidden
",
11683 overflowY: overHeight ? "auto
" : "hidden
"
11688 mb.w += 16; // room for vertical scrollbar
11694 // Adjust dropdown width to match or be larger than my width
11695 if(this.forceWidth){
11696 mb.w = aroundNode.offsetWidth;
11697 }else if(this.autoWidth){
11698 mb.w = Math.max(mb.w, aroundNode.offsetWidth);
11703 // And finally, resize the dropdown to calculated height and width
11704 if(dojo.isFunction(dropDown.resize)){
11705 dropDown.resize(mb);
11707 dojo.marginBox(ddNode, mb);
11711 var retVal = dijit.popup.open({
11714 around: aroundNode,
11715 orient: dijit.getPopupAroundAlignment((this.dropDownPosition && this.dropDownPosition.length) ? this.dropDownPosition : ["below
"],this.isLeftToRight()),
11716 onExecute: function(){
11717 self.closeDropDown(true);
11719 onCancel: function(){
11720 self.closeDropDown(true);
11722 onClose: function(){
11723 dojo.attr(self._popupStateNode, "popupActive
", false);
11724 dojo.removeClass(self._popupStateNode, "dijitHasDropDownOpen
");
11725 self._opened = false;
11728 dojo.attr(this._popupStateNode, "popupActive
", "true");
11729 dojo.addClass(self._popupStateNode, "dijitHasDropDownOpen
");
11732 // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown
11736 closeDropDown: function(/*Boolean*/ focus){
11738 // Closes the drop down on this widget
11740 // If true, refocuses the button widget
11745 if(focus){ this.focus(); }
11746 dijit.popup.close(this.dropDown);
11747 this._opened = false;
11756 if(!dojo._hasResource["dijit
.form
.Button
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11757 dojo._hasResource["dijit
.form
.Button
"] = true;
11758 dojo.provide("dijit
.form
.Button
");
11764 dojo.declare("dijit
.form
.Button
",
11765 dijit.form._FormWidget,
11768 // Basically the same thing as a normal HTML button, but with special styling.
11770 // Buttons can display a label, an icon, or both.
11771 // A label should always be specified (through innerHTML) or the label
11772 // attribute. It can be hidden via showLabel=false.
11774 // | <button dojoType="dijit
.form
.Button
" onClick="...">Hello world</button>
11777 // | var button1 = new dijit.form.Button({label: "hello world
", onClick: foo});
11778 // | dojo.body().appendChild(button1.domNode);
11780 // label: HTML String
11781 // Text to display in button.
11782 // If the label is hidden (showLabel=false) then and no title has
11783 // been specified, then label is also set as title attribute of icon.
11786 // showLabel: Boolean
11787 // Set this to true to hide the label text and display only the icon.
11788 // (If showLabel=false then iconClass must be specified.)
11789 // Especially useful for toolbars.
11790 // If showLabel=true, the label will become the title (a.k.a. tooltip/hint) of the icon.
11792 // The exception case is for computers in high-contrast mode, where the label
11793 // will still be displayed, since the icon doesn't appear.
11796 // iconClass: String
11797 // Class to apply to DOMNode in button to make it display an icon
11801 // Defines the type of button. "button
", "submit
", or "reset
".
11804 baseClass: "dijitButton
",
11806 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"),
11808 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
11812 _onClick: function(/*Event*/ e){
11814 // Internal function to handle click actions
11818 this._clicked(); // widget click actions
11819 return this.onClick(e); // user click actions
11822 _onButtonClick: function(/*Event*/ e){
11824 // Handler when the user activates the button portion.
11825 if(this._onClick(e) === false){ // returning nothing is same as true
11826 e.preventDefault(); // needed for checkbox
11827 }else if(this.type == "submit
" && !(this.valueNode||this.focusNode).form){ // see if a nonform widget needs to be signalled
11828 for(var node=this.domNode; node.parentNode/*#5935*/; node=node.parentNode){
11829 var widget=dijit.byNode(node);
11830 if(widget && typeof widget._onSubmit == "function"){
11831 widget._onSubmit(e);
11835 }else if(this.valueNode){
11836 this.valueNode.click();
11837 e.preventDefault(); // cancel BUTTON click and continue with hidden INPUT click
11841 buildRendering: function(){
11842 this.inherited(arguments);
11843 dojo.setSelectable(this.focusNode, false);
11846 _fillContent: function(/*DomNode*/ source){
11847 // Overrides _Templated._fillContent().
11848 // If button label is specified as srcNodeRef.innerHTML rather than
11849 // this.params.label, handle it here.
11850 // TODO: remove the method in 2.0, parser will do it all for me
11851 if(source && (!this.params || !("label
" in this.params))){
11852 this.set('label', source.innerHTML);
11856 _setShowLabelAttr: function(val){
11857 if(this.containerNode){
11858 dojo.toggleClass(this.containerNode, "dijitDisplayNone
", !val);
11860 this._set("showLabel
", val);
11863 onClick: function(/*Event*/ e){
11865 // Callback for when button is clicked.
11866 // If type="submit
", return true to perform submit, or false to cancel it.
11869 return true; // Boolean
11872 _clicked: function(/*Event*/ e){
11874 // Internal overridable function for when the button is clicked
11877 setLabel: function(/*String*/ content){
11879 // Deprecated. Use set('label', ...) instead.
11880 dojo.deprecated("dijit
.form
.Button
.setLabel() is deprecated
. Use
set('label', ...) instead
.", "", "2.0");
11881 this.set("label
", content);
11884 _setLabelAttr: function(/*String*/ content){
11886 // Hook for set('label', ...) to work.
11888 // Set the label (text) of the button; takes an HTML string.
11889 this._set("label
", content);
11890 this.containerNode.innerHTML = content;
11891 if(this.showLabel == false && !this.params.title){
11892 this.titleNode.title = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || '');
11896 _setIconClassAttr: function(/*String*/ val){
11897 // Custom method so that icon node is hidden when not in use, to avoid excess padding/margin
11898 // appearing around it (even if it's a 0x0 sized <img> node)
11900 var oldVal = this.iconClass || "dijitNoIcon
",
11901 newVal = val || "dijitNoIcon
";
11902 dojo.replaceClass(this.iconNode, newVal, oldVal);
11903 this._set("iconClass
", val);
11908 dojo.declare("dijit
.form
.DropDownButton
", [dijit.form.Button, dijit._Container, dijit._HasDropDown], {
11910 // A button with a drop down
11913 // | <button dojoType="dijit
.form
.DropDownButton
" label="Hello world
">
11914 // | <div dojotype="dijit
.Menu
">...</div>
11918 // | var button1 = new dijit.form.DropDownButton({ label: "hi
", dropDown: new dijit.Menu(...) });
11919 // | dojo.body().appendChild(button1);
11922 baseClass : "dijitDropDownButton
",
11924 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"),
11926 _fillContent: function(){
11927 // Overrides Button._fillContent().
11929 // My inner HTML contains both the button contents and a drop down widget, like
11930 // <DropDownButton> <span>push me</span> <Menu> ... </Menu> </DropDownButton>
11931 // The first node is assumed to be the button content. The widget is the popup.
11933 if(this.srcNodeRef){ // programatically created buttons might not define srcNodeRef
11934 //FIXME: figure out how to filter out the widget and use all remaining nodes as button
11935 // content, not just nodes[0]
11936 var nodes = dojo.query("*", this.srcNodeRef);
11937 dijit.form.DropDownButton.superclass._fillContent.call(this, nodes[0]);
11939 // save pointer to srcNode so we can grab the drop down widget after it's instantiated
11940 this.dropDownContainer = this.srcNodeRef;
11944 startup: function(){
11945 if(this._started){ return; }
11947 // the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM,
11948 // make it invisible, and store a reference to pass to the popup code.
11949 if(!this.dropDown && this.dropDownContainer){
11950 var dropDownNode = dojo.query("[widgetId
]", this.dropDownContainer)[0];
11951 this.dropDown = dijit.byNode(dropDownNode);
11952 delete this.dropDownContainer;
11955 dijit.popup.hide(this.dropDown);
11958 this.inherited(arguments);
11961 isLoaded: function(){
11962 // Returns whether or not we are loaded - if our dropdown has an href,
11963 // then we want to check that.
11964 var dropDown = this.dropDown;
11965 return (!!dropDown && (!dropDown.href || dropDown.isLoaded));
11968 loadDropDown: function(){
11969 // Loads our dropdown
11970 var dropDown = this.dropDown;
11971 if(!dropDown){ return; }
11972 if(!this.isLoaded()){
11973 var handler = dojo.connect(dropDown, "onLoad
", this, function(){
11974 dojo.disconnect(handler);
11975 this.openDropDown();
11977 dropDown.refresh();
11979 this.openDropDown();
11983 isFocusable: function(){
11984 // Overridden so that focus is handled by the _HasDropDown mixin, not by
11985 // the _FormWidget mixin.
11986 return this.inherited(arguments) && !this._mouseDown;
11990 dojo.declare("dijit
.form
.ComboButton
", dijit.form.DropDownButton, {
11992 // A combination button and drop-down button.
11993 // Users can click one side to "press
" the button, or click an arrow
11994 // icon to display the drop down.
11997 // | <button dojoType="dijit
.form
.ComboButton
" onClick="...">
11998 // | <span>Hello world</span>
11999 // | <div dojoType="dijit
.Menu
">...</div>
12003 // | var button1 = new dijit.form.ComboButton({label: "hello world
", onClick: foo, dropDown: "myMenu
"});
12004 // | dojo.body().appendChild(button1.domNode);
12007 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"),
12009 attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), {
12011 tabIndex: ["focusNode
", "titleNode
"],
12015 // optionsTitle: String
12016 // Text that describes the options menu (accessibility)
12019 baseClass: "dijitComboButton
",
12021 // Set classes like dijitButtonContentsHover or dijitArrowButtonActive depending on
12022 // mouse action over specified node
12024 "buttonNode
": "dijitButtonNode
",
12025 "titleNode
": "dijitButtonContents
",
12026 "_popupStateNode
": "dijitDownArrowButton
"
12029 _focusedNode: null,
12031 _onButtonKeyPress: function(/*Event*/ evt){
12033 // Handler for right arrow key when focus is on left part of button
12034 if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "RIGHT_ARROW
" : "LEFT_ARROW
"]){
12035 dijit.focus(this._popupStateNode);
12036 dojo.stopEvent(evt);
12040 _onArrowKeyPress: function(/*Event*/ evt){
12042 // Handler for left arrow key when focus is on right part of button
12043 if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "LEFT_ARROW
" : "RIGHT_ARROW
"]){
12044 dijit.focus(this.titleNode);
12045 dojo.stopEvent(evt);
12049 focus: function(/*String*/ position){
12051 // Focuses this widget to according to position, if specified,
12052 // otherwise on arrow node
12054 // "start
" or "end
"
12055 if(!this.disabled){
12056 dijit.focus(position == "start
" ? this.titleNode : this._popupStateNode);
12061 dojo.declare("dijit
.form
.ToggleButton
", dijit.form.Button, {
12063 // A button that can be in two states (checked or not).
12064 // Can be base class for things like tabs or checkbox or radio buttons
12066 baseClass: "dijitToggleButton
",
12068 // checked: Boolean
12069 // Corresponds to the native HTML <input> element's attribute.
12070 // In markup, specified as "checked
='checked'" or just "checked
".
12071 // True if the button is depressed, or the checkbox is checked,
12072 // or the radio button is selected, etc.
12075 attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), {
12076 checked:"focusNode
"
12079 _clicked: function(/*Event*/ evt){
12080 this.set('checked', !this.checked);
12083 _setCheckedAttr: function(/*Boolean*/ value, /*Boolean?*/ priorityChange){
12084 this._set("checked
", value);
12085 dojo.attr(this.focusNode || this.domNode, "checked
", value);
12086 dijit.setWaiState(this.focusNode || this.domNode, "pressed
", value);
12087 this._handleOnChange(value, priorityChange);
12090 setChecked: function(/*Boolean*/ checked){
12092 // Deprecated. Use set('checked', true/false) instead.
12093 dojo.deprecated("setChecked("+checked+") is deprecated
. Use
set('checked',"+checked+") instead
.", "", "2.0");
12094 this.set('checked', checked);
12099 // Reset the widget's value to what it was at initialization time
12101 this._hasBeenBlurred = false;
12103 // set checked state to original setting
12104 this.set('checked', this.params.checked || false);
12110 if(!dojo._hasResource["dijit
.form
.ToggleButton
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12111 dojo._hasResource["dijit
.form
.ToggleButton
"] = true;
12112 dojo.provide("dijit
.form
.ToggleButton
");
12119 if(!dojo._hasResource["dijit
.form
.CheckBox
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12120 dojo._hasResource["dijit
.form
.CheckBox
"] = true;
12121 dojo.provide("dijit
.form
.CheckBox
");
12126 "dijit
.form
.CheckBox
",
12127 dijit.form.ToggleButton,
12130 // Same as an HTML checkbox, but with fancy styling.
12133 // User interacts with real html inputs.
12134 // On onclick (which occurs by mouse click, space-bar, or
12135 // using the arrow keys to switch the selected radio button),
12136 // we update the state of the checkbox/radio.
12138 // There are two modes:
12139 // 1. High contrast mode
12142 // In case 1, the regular html inputs are shown and used by the user.
12143 // In case 2, the regular html inputs are invisible but still used by
12144 // the user. They are turned quasi-invisible and overlay the background-image.
12146 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"),
12148 baseClass: "dijitCheckBox
",
12150 // type: [private] String
12151 // type attribute on <input> node.
12152 // Overrides `dijit.form.Button.type`. Users should not change this value.
12156 // As an initialization parameter, equivalent to value field on normal checkbox
12157 // (if checked, the value is passed as the value when form is submitted).
12159 // However, get('value') will return either the string or false depending on
12160 // whether or not the checkbox is checked.
12162 // set('value', string) will check the checkbox and change the value to the
12163 // specified string
12165 // set('value', boolean) will change the checked state.
12168 // readOnly: Boolean
12169 // Should this widget respond to user input?
12170 // In markup, this is specified as "readOnly
".
12171 // Similar to disabled except readOnly form values are submitted.
12174 // the attributeMap should inherit from dijit.form._FormWidget.prototype.attributeMap
12175 // instead of ToggleButton as the icon mapping has no meaning for a CheckBox
12176 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
12177 readOnly: "focusNode
"
12180 _setReadOnlyAttr: function(/*Boolean*/ value){
12181 this._set("readOnly
", value);
12182 dojo.attr(this.focusNode, 'readOnly', value);
12183 dijit.setWaiState(this.focusNode, "readonly
", value);
12186 _setValueAttr: function(/*String|Boolean*/ newValue, /*Boolean*/ priorityChange){
12188 // Handler for value= attribute to constructor, and also calls to
12189 // set('value', val).
12191 // During initialization, just saves as attribute to the <input type=checkbox>.
12193 // After initialization,
12194 // when passed a boolean, controls whether or not the CheckBox is checked.
12195 // If passed a string, changes the value attribute of the CheckBox (the one
12196 // specified as "value
" when the CheckBox was constructed (ex: <input
12197 // dojoType="dijit
.CheckBox
" value="chicken
">)
12198 if(typeof newValue == "string
"){
12199 this._set("value
", newValue);
12200 dojo.attr(this.focusNode, 'value', newValue);
12204 this.set('checked', newValue, priorityChange);
12207 _getValueAttr: function(){
12209 // Hook so get('value') works.
12211 // If the CheckBox is checked, returns the value attribute.
12212 // Otherwise returns false.
12213 return (this.checked ? this.value : false);
12216 // Override dijit.form.Button._setLabelAttr() since we don't even have a containerNode.
12217 // Normally users won't try to set label, except when CheckBox or RadioButton is the child of a dojox.layout.TabContainer
12218 _setLabelAttr: undefined,
12220 postMixInProperties: function(){
12221 if(this.value == ""){
12225 // Need to set initial checked state as part of template, so that form submit works.
12226 // dojo.attr(node, "checked
", bool) doesn't work on IEuntil node has been attached
12227 // to <body>, see #8666
12228 this.checkedAttrSetting = this.checked ? "checked
" : "";
12230 this.inherited(arguments);
12233 _fillContent: function(/*DomNode*/ source){
12234 // Override Button::_fillContent() since it doesn't make sense for CheckBox,
12235 // since CheckBox doesn't even have a container
12239 // Override ToggleButton.reset()
12241 this._hasBeenBlurred = false;
12243 this.set('checked', this.params.checked || false);
12245 // Handle unlikely event that the <input type=checkbox> value attribute has changed
12246 this._set("value
", this.params.value || "on
");
12247 dojo.attr(this.focusNode, 'value', this.value);
12250 _onFocus: function(){
12252 dojo.query("label
[for='"+this.id+"']").addClass("dijitFocusedLabel
");
12254 this.inherited(arguments);
12257 _onBlur: function(){
12259 dojo.query("label
[for='"+this.id+"']").removeClass("dijitFocusedLabel
");
12261 this.inherited(arguments);
12264 _onClick: function(/*Event*/ e){
12266 // Internal function to handle click actions - need to check
12267 // readOnly, since button no longer does that check.
12272 return this.inherited(arguments);
12278 "dijit
.form
.RadioButton
",
12279 dijit.form.CheckBox,
12282 // Same as an HTML radio, but with fancy styling.
12285 baseClass: "dijitRadio
",
12287 _setCheckedAttr: function(/*Boolean*/ value){
12288 // If I am being checked then have to deselect currently checked radio button
12289 this.inherited(arguments);
12290 if(!this._created){ return; }
12293 // search for radio buttons with the same name that need to be unchecked
12294 dojo.query("INPUT
[type
=radio
]", this.focusNode.form || dojo.doc).forEach( // can't use name= since dojo.query doesn't support [] in the name
12295 function(inputNode){
12296 if(inputNode.name == _this.name && inputNode != _this.focusNode && inputNode.form == _this.focusNode.form){
12297 var widget = dijit.getEnclosingWidget(inputNode);
12298 if(widget && widget.checked){
12299 widget.set('checked', false);
12307 _clicked: function(/*Event*/ e){
12309 this.set('checked', true);
12317 if(!dojo._hasResource["dijit
.form
.DropDownButton
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12318 dojo._hasResource["dijit
.form
.DropDownButton
"] = true;
12319 dojo.provide("dijit
.form
.DropDownButton
");
12326 if(!dojo._hasResource["dojo
.regexp
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12327 dojo._hasResource["dojo
.regexp
"] = true;
12328 dojo.provide("dojo
.regexp
");
12330 dojo.getObject("regexp
", true, dojo);
12334 // summary: Regular expressions and Builder resources
12338 dojo.regexp.escapeString = function(/*String*/str, /*String?*/except){
12340 // Adds escape sequences for special characters in regular expressions
12342 // a String with special characters to be left unescaped
12344 return str.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(ch){
12345 if(except && except.indexOf(ch) != -1){
12352 dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boolean?*/nonCapture){
12354 // Builds a regular expression that groups subexpressions
12356 // A utility function used by some of the RE generators. The
12357 // subexpressions are constructed by the function, re, in the second
12358 // parameter. re builds one subexpression for each elem in the array
12359 // a, in the first parameter. Returns a string for a regular
12360 // expression that groups all the subexpressions.
12362 // A single value or an array of values.
12364 // A function. Takes one parameter and converts it to a regular
12367 // If true, uses non-capturing match, otherwise matches are retained
12368 // by regular expression. Defaults to false
12370 // case 1: a is a single value.
12371 if(!(arr instanceof Array)){
12372 return re(arr); // String
12375 // case 2: a is an array
12377 for(var i = 0; i < arr.length; i++){
12378 // convert each elem to a RE
12379 b.push(re(arr[i]));
12382 // join the REs as alternatives in a RE group.
12383 return dojo.regexp.group(b.join("|"), nonCapture); // String
12386 dojo.regexp.group = function(/*String*/expression, /*Boolean?*/nonCapture){
12388 // adds group match to expression
12390 // If true, uses non-capturing match, otherwise matches are retained
12391 // by regular expression.
12392 return "(" + (nonCapture ? "?:":"") + expression + ")"; // String
12397 if(!dojo._hasResource["dojo
.data
.util
.sorter
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12398 dojo._hasResource["dojo
.data
.util
.sorter
"] = true;
12399 dojo.provide("dojo
.data
.util
.sorter
");
12401 dojo.getObject("data
.util
.sorter
", true, dojo);
12403 dojo.data.util.sorter.basicComparator = function( /*anything*/ a,
12406 // Basic comparision function that compares if an item is greater or less than another item
12408 // returns 1 if a > b, -1 if a < b, 0 if equal.
12409 // 'null' values (null, undefined) are treated as larger values so that they're pushed to the end of the list.
12410 // And compared to each other, null is equivalent to undefined.
12412 //null is a problematic compare, so if null, we set to undefined.
12413 //Makes the check logic simple, compact, and consistent
12414 //And (null == undefined) === true, so the check later against null
12415 //works for undefined and is less bytes.
12425 }else if(a > b || a == null){
12428 return r; //int {-1,0,1}
12431 dojo.data.util.sorter.createSortFunction = function( /* attributes array */sortSpec,
12432 /*dojo.data.core.Read*/ store){
12434 // Helper function to generate the sorting function based off the list of sort attributes.
12436 // The sort function creation will look for a property on the store called 'comparatorMap'. If it exists
12437 // it will look in the mapping for comparisons function for the attributes. If one is found, it will
12438 // use it instead of the basic comparator, which is typically used for strings, ints, booleans, and dates.
12439 // Returns the sorting function for this particular list of attributes and sorting directions.
12442 // A JS object that array that defines out what attribute names to sort on and whether it should be descenting or asending.
12443 // The objects should be formatted as follows:
12445 // attribute: "attributeName
-string
" || attribute,
12446 // descending: true|false; // Default is false.
12449 // The datastore object to look up item values from.
12451 var sortFunctions=[];
12453 function createSortFunction(attr, dir, comp, s){
12454 //Passing in comp and s (comparator and store), makes this
12455 //function much faster.
12456 return function(itemA, itemB){
12457 var a = s.getValue(itemA, attr);
12458 var b = s.getValue(itemB, attr);
12459 return dir * comp(a,b); //int
12463 var map = store.comparatorMap;
12464 var bc = dojo.data.util.sorter.basicComparator;
12465 for(var i = 0; i < sortSpec.length; i++){
12466 sortAttribute = sortSpec[i];
12467 var attr = sortAttribute.attribute;
12469 var dir = (sortAttribute.descending) ? -1 : 1;
12472 if(typeof attr !== "string
" && ("toString
" in attr)){
12473 attr = attr.toString();
12475 comp = map[attr] || bc;
12477 sortFunctions.push(createSortFunction(attr,
12478 dir, comp, store));
12481 return function(rowA, rowB){
12483 while(i < sortFunctions.length){
12484 var ret = sortFunctions[i++](rowA, rowB);
12495 if(!dojo._hasResource["dojo
.data
.util
.simpleFetch
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12496 dojo._hasResource["dojo
.data
.util
.simpleFetch
"] = true;
12497 dojo.provide("dojo
.data
.util
.simpleFetch
");
12500 dojo.getObject("data
.util
.simpleFetch
", true, dojo);
12502 dojo.data.util.simpleFetch.fetch = function(/* Object? */ request){
12504 // The simpleFetch mixin is designed to serve as a set of function(s) that can
12505 // be mixed into other datastore implementations to accelerate their development.
12506 // The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems()
12507 // call by returning an array of all the found items that matched the query. The simpleFetch mixin
12508 // is not designed to work for datastores that respond to a fetch() call by incrementally
12509 // loading items, or sequentially loading partial batches of the result
12510 // set. For datastores that mixin simpleFetch, simpleFetch
12511 // implements a fetch method that automatically handles eight of the fetch()
12512 // arguments -- onBegin, onItem, onComplete, onError, start, count, sort and scope
12513 // The class mixing in simpleFetch should not implement fetch(),
12514 // but should instead implement a _fetchItems() method. The _fetchItems()
12515 // method takes three arguments, the keywordArgs object that was passed
12516 // to fetch(), a callback function to be called when the result array is
12517 // available, and an error callback to be called if something goes wrong.
12518 // The _fetchItems() method should ignore any keywordArgs parameters for
12519 // start, count, onBegin, onItem, onComplete, onError, sort, and scope.
12520 // The _fetchItems() method needs to correctly handle any other keywordArgs
12521 // parameters, including the query parameter and any optional parameters
12522 // (such as includeChildren). The _fetchItems() method should create an array of
12523 // result items and pass it to the fetchHandler along with the original request object
12524 // -- or, the _fetchItems() method may, if it wants to, create an new request object
12525 // with other specifics about the request that are specific to the datastore and pass
12526 // that as the request object to the handler.
12528 // For more information on this specific function, see dojo.data.api.Read.fetch()
12529 request = request || {};
12530 if(!request.store){
12531 request.store = this;
12535 var _errorHandler = function(errorData, requestObject){
12536 if(requestObject.onError){
12537 var scope = requestObject.scope || dojo.global;
12538 requestObject.onError.call(scope, errorData, requestObject);
12542 var _fetchHandler = function(items, requestObject){
12543 var oldAbortFunction = requestObject.abort || null;
12544 var aborted = false;
12546 var startIndex = requestObject.start?requestObject.start:0;
12547 var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length;
12549 requestObject.abort = function(){
12551 if(oldAbortFunction){
12552 oldAbortFunction.call(requestObject);
12556 var scope = requestObject.scope || dojo.global;
12557 if(!requestObject.store){
12558 requestObject.store = self;
12560 if(requestObject.onBegin){
12561 requestObject.onBegin.call(scope, items.length, requestObject);
12563 if(requestObject.sort){
12564 items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self));
12566 if(requestObject.onItem){
12567 for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){
12568 var item = items[i];
12570 requestObject.onItem.call(scope, item, requestObject);
12574 if(requestObject.onComplete && !aborted){
12576 if(!requestObject.onItem){
12577 subset = items.slice(startIndex, endIndex);
12579 requestObject.onComplete.call(scope, subset, requestObject);
12582 this._fetchItems(request, _fetchHandler, _errorHandler);
12583 return request; // Object
12588 if(!dojo._hasResource["dojo
.data
.util
.filter
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12589 dojo._hasResource["dojo
.data
.util
.filter
"] = true;
12590 dojo.provide("dojo
.data
.util
.filter
");
12592 dojo.getObject("data
.util
.filter
", true, dojo);
12594 dojo.data.util.filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/ ignoreCase){
12596 // Helper function to convert a simple pattern to a regular expression for matching.
12598 // Returns a regular expression object that conforms to the defined conversion rules.
12601 // *ca* -> /^.*ca.*$/
12602 // *c\*a* -> /^.*c\*a.*$/
12603 // *c\*a?* -> /^.*c\*a..*$/
12607 // A simple matching pattern to convert that follows basic rules:
12608 // * Means match anything, so ca* means match anything starting with ca
12609 // ? Means match single character. So, b?b will match to bob and bab, and so on.
12610 // \ is an escape character. So for example, \* means do not treat * as a match, but literal character *.
12611 // To use a \ as a character in the string, it must be escaped. So in the pattern it should be
12612 // represented by \\ to be treated as an ordinary \ character instead of an escape.
12615 // An optional flag to indicate if the pattern matching should be treated as case-sensitive or not when comparing
12616 // By default, it is assumed case sensitive.
12620 for(var i = 0; i < pattern.length; i++){
12621 c = pattern.charAt(i);
12626 rxp += pattern.charAt(i);
12629 rxp += ".*"; break;
12644 rxp += "\\"; //fallthrough
12651 return new RegExp(rxp,"mi
"); //RegExp
12653 return new RegExp(rxp,"m
"); //RegExp
12660 if(!dojo._hasResource["dijit
.form
.TextBox
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12661 dojo._hasResource["dijit
.form
.TextBox
"] = true;
12662 dojo.provide("dijit
.form
.TextBox
");
12667 "dijit
.form
.TextBox
",
12668 dijit.form._FormValueWidget,
12671 // A base class for textbox form inputs
12674 // Removes leading and trailing whitespace if true. Default is false.
12677 // uppercase: Boolean
12678 // Converts all characters to uppercase if true. Default is false.
12681 // lowercase: Boolean
12682 // Converts all characters to lowercase if true. Default is false.
12685 // propercase: Boolean
12686 // Converts the first character of each word to uppercase if true.
12689 // maxLength: String
12690 // HTML INPUT tag maxLength declaration.
12693 // selectOnClick: [const] Boolean
12694 // If true, all text will be selected when focused with mouse
12695 selectOnClick: false,
12697 // placeHolder: String
12698 // Defines a hint to help users fill out the input field (as defined in HTML 5).
12699 // This should only contain plain text (no html markup).
12702 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"),
12703 _singleNodeTemplate: '<input class="dijit dijitReset dijitLeft dijitInputField
" dojoAttachPoint="textbox
,focusNode
" autocomplete="off
" type="${type}
" ${!nameAttrSetting} />',
12705 _buttonInputDisabled: dojo.isIE ? "disabled
" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events
12707 baseClass: "dijitTextBox
",
12709 attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, {
12710 maxLength: "focusNode
"
12713 postMixInProperties: function(){
12714 var type = this.type.toLowerCase();
12715 if(this.templateString && this.templateString.toLowerCase() == "input
" || ((type == "hidden
" || type == "file
") && this.templateString == dijit.form.TextBox.prototype.templateString)){
12716 this.templateString = this._singleNodeTemplate;
12718 this.inherited(arguments);
12721 _setPlaceHolderAttr: function(v){
12722 this._set("placeHolder
", v);
12724 this._attachPoints.push('_phspan');
12725 /* dijitInputField class gives placeHolder same padding as the input field
12726 * parent node already has dijitInputField class but it doesn't affect this <span>
12727 * since it's position: absolute.
12729 this._phspan = dojo.create('span',{className:'dijitPlaceHolder dijitInputField'},this.textbox,'after');
12731 this._phspan.innerHTML="";
12732 this._phspan.appendChild(document.createTextNode(v));
12734 this._updatePlaceHolder();
12737 _updatePlaceHolder: function(){
12739 this._phspan.style.display=(this.placeHolder&&!this._focused&&!this.textbox.value)?"":"none
";
12743 _getValueAttr: function(){
12745 // Hook so get('value') works as we like.
12747 // For `dijit.form.TextBox` this basically returns the value of the <input>.
12749 // For `dijit.form.MappedTextBox` subclasses, which have both
12750 // a "displayed value
" and a separate "submit value
",
12751 // This treats the "displayed value
" as the master value, computing the
12752 // submit value from it via this.parse().
12753 return this.parse(this.get('displayedValue'), this.constraints);
12756 _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
12758 // Hook so set('value', ...) works.
12761 // Sets the value of the widget to "value
" which can be of
12762 // any type as determined by the widget.
12765 // The visual element value is also set to a corresponding,
12766 // but not necessarily the same, value.
12769 // If specified, used to set the visual element value,
12770 // otherwise a computed visual value is used.
12773 // If true, an onChange event is fired immediately instead of
12774 // waiting for the next blur event.
12777 if(value !== undefined){
12778 // TODO: this is calling filter() on both the display value and the actual value.
12779 // I added a comment to the filter() definition about this, but it should be changed.
12780 filteredValue = this.filter(value);
12781 if(typeof formattedValue != "string
"){
12782 if(filteredValue !== null && ((typeof filteredValue != "number
") || !isNaN(filteredValue))){
12783 formattedValue = this.filter(this.format(filteredValue, this.constraints));
12784 }else{ formattedValue = ''; }
12787 if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number
" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){
12788 this.textbox.value = formattedValue;
12789 this._set("displayedValue
", this.get("displayedValue
"));
12792 this._updatePlaceHolder();
12794 this.inherited(arguments, [filteredValue, priorityChange]);
12797 // displayedValue: String
12798 // For subclasses like ComboBox where the displayed value
12799 // (ex: Kentucky) and the serialized value (ex: KY) are different,
12800 // this represents the displayed value.
12802 // Setting 'displayedValue' through set('displayedValue', ...)
12803 // updates 'value', and vice-versa. Otherwise 'value' is updated
12804 // from 'displayedValue' periodically, like onBlur etc.
12806 // TODO: move declaration to MappedTextBox?
12807 // Problem is that ComboBox references displayedValue,
12808 // for benefit of FilteringSelect.
12809 displayedValue: "",
12811 getDisplayedValue: function(){
12813 // Deprecated. Use get('displayedValue') instead.
12816 dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated
. Use
set('displayedValue') instead
.", "", "2.0");
12817 return this.get('displayedValue');
12820 _getDisplayedValueAttr: function(){
12822 // Hook so get('displayedValue') works.
12824 // Returns the displayed value (what the user sees on the screen),
12825 // after filtering (ie, trimming spaces etc.).
12827 // For some subclasses of TextBox (like ComboBox), the displayed value
12828 // is different from the serialized value that's actually
12829 // sent to the server (see dijit.form.ValidationTextBox.serialize)
12831 // TODO: maybe we should update this.displayedValue on every keystroke so that we don't need
12833 // TODO: this isn't really the displayed value when the user is typing
12834 return this.filter(this.textbox.value);
12837 setDisplayedValue: function(/*String*/ value){
12839 // Deprecated. Use set('displayedValue', ...) instead.
12842 dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated
. Use
set('displayedValue', ...) instead
.", "", "2.0");
12843 this.set('displayedValue', value);
12846 _setDisplayedValueAttr: function(/*String*/ value){
12848 // Hook so set('displayedValue', ...) works.
12850 // Sets the value of the visual element to the string "value
".
12851 // The widget value is also set to a corresponding,
12852 // but not necessarily the same, value.
12854 if(value === null || value === undefined){ value = '' }
12855 else if(typeof value != "string
"){ value = String(value) }
12857 this.textbox.value = value;
12859 // sets the serialized value to something corresponding to specified displayedValue
12860 // (if possible), and also updates the textbox.value, for example converting "123"
12862 this._setValueAttr(this.get('value'), undefined);
12864 this._set("displayedValue
", this.get('displayedValue'));
12867 format: function(/*String*/ value, /*Object*/ constraints){
12869 // Replacable function to convert a value to a properly formatted string.
12871 // protected extension
12872 return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
12875 parse: function(/*String*/ value, /*Object*/ constraints){
12877 // Replacable function to convert a formatted string to a value
12879 // protected extension
12881 return value; // String
12884 _refreshState: function(){
12886 // After the user types some characters, etc., this method is
12887 // called to check the field for validity etc. The base method
12888 // in `dijit.form.TextBox` does nothing, but subclasses override.
12893 _onInput: function(e){
12894 if(e && e.type && /key/i.test(e.type) && e.keyCode){
12896 case dojo.keys.SHIFT:
12897 case dojo.keys.ALT:
12898 case dojo.keys.CTRL:
12899 case dojo.keys.TAB:
12903 if(this.intermediateChanges){
12905 // the setTimeout allows the key to post to the widget input box
12906 setTimeout(function(){ _this._handleOnChange(_this.get('value'), false); }, 0);
12908 this._refreshState();
12910 // In case someone is watch()'ing for changes to displayedValue
12911 this._set("displayedValue
", this.get("displayedValue
"));
12914 postCreate: function(){
12915 if(dojo.isIE){ // IE INPUT tag fontFamily has to be set directly using STYLE
12916 // the setTimeout gives IE a chance to render the TextBox and to deal with font inheritance
12917 setTimeout(dojo.hitch(this, function(){
12918 var s = dojo.getComputedStyle(this.domNode);
12920 var ff = s.fontFamily;
12922 var inputs = this.domNode.getElementsByTagName("INPUT
");
12924 for(var i=0; i < inputs.length; i++){
12925 inputs[i].style.fontFamily = ff;
12933 // setting the value here is needed since value="" in the template causes "undefined"
12934 // and setting in the DOM (instead of the JS object) helps with form reset actions
12935 this.textbox.setAttribute("value
", this.textbox.value); // DOM and JS values should be the same
12937 this.inherited(arguments);
12939 if(dojo.isMoz || dojo.isOpera){
12940 this.connect(this.textbox, "oninput
", "_onInput
");
12942 this.connect(this.textbox, "onkeydown
", "_onInput
");
12943 this.connect(this.textbox, "onkeyup
", "_onInput
");
12944 this.connect(this.textbox, "onpaste
", "_onInput
");
12945 this.connect(this.textbox, "oncut
", "_onInput
");
12949 _blankValue: '', // if the textbox is blank, what value should be reported
12950 filter: function(val){
12952 // Auto-corrections (such as trimming) that are applied to textbox
12953 // value on blur or form submit.
12955 // For MappedTextBox subclasses, this is called twice
12956 // - once with the display value
12957 // - once the value as set/returned by set('value', ...)
12958 // and get('value'), ex: a Number for NumberTextBox.
12960 // In the latter case it does corrections like converting null to NaN. In
12961 // the former case the NumberTextBox.filter() method calls this.inherited()
12962 // to execute standard trimming code in TextBox.filter().
12964 // TODO: break this into two methods in 2.0
12967 // protected extension
12968 if(val === null){ return this._blankValue; }
12969 if(typeof val != "string
"){ return val; }
12971 val = dojo.trim(val);
12973 if(this.uppercase){
12974 val = val.toUpperCase();
12976 if(this.lowercase){
12977 val = val.toLowerCase();
12979 if(this.propercase){
12980 val = val.replace(/[^\s]+/g, function(word){
12981 return word.substring(0,1).toUpperCase() + word.substring(1);
12987 _setBlurValue: function(){
12988 this._setValueAttr(this.get('value'), true);
12991 _onBlur: function(e){
12992 if(this.disabled){ return; }
12993 this._setBlurValue();
12994 this.inherited(arguments);
12996 if(this._selectOnClickHandle){
12997 this.disconnect(this._selectOnClickHandle);
12999 if(this.selectOnClick && dojo.isMoz){
13000 this.textbox.selectionStart = this.textbox.selectionEnd = undefined; // clear selection so that the next mouse click doesn't reselect
13003 this._updatePlaceHolder();
13006 _onFocus: function(/*String*/ by){
13007 if(this.disabled || this.readOnly){ return; }
13009 // Select all text on focus via click if nothing already selected.
13010 // Since mouse-up will clear the selection need to defer selection until after mouse-up.
13011 // Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event.
13012 if(this.selectOnClick && by == "mouse
"){
13013 this._selectOnClickHandle = this.connect(this.domNode, "onmouseup
", function(){
13014 // Only select all text on first click; otherwise users would have no way to clear
13016 this.disconnect(this._selectOnClickHandle);
13018 // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up)
13019 // and if not, then select all the text
13020 var textIsNotSelected;
13022 var range = dojo.doc.selection.createRange();
13023 var parent = range.parentElement();
13024 textIsNotSelected = parent == this.textbox && range.text.length == 0;
13026 textIsNotSelected = this.textbox.selectionStart == this.textbox.selectionEnd;
13028 if(textIsNotSelected){
13029 dijit.selectInputText(this.textbox);
13034 this._updatePlaceHolder();
13036 // call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport
13037 // (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip
13038 this.inherited(arguments);
13040 this._refreshState();
13044 // Overrides dijit._FormWidget.reset().
13045 // Additionally resets the displayed textbox value to ''
13046 this.textbox.value = '';
13047 this.inherited(arguments);
13052 dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
13054 // Select text in the input element argument, from start (default 0), to stop (default end).
13056 // TODO: use functions in _editor/selection.js?
13057 var _window = dojo.global;
13058 var _document = dojo.doc;
13059 element = dojo.byId(element);
13060 if(isNaN(start)){ start = 0; }
13061 if(isNaN(stop)){ stop = element.value ? element.value.length : 0; }
13062 dijit.focus(element);
13063 if(_document["selection
"] && dojo.body()["createTextRange
"]){ // IE
13064 if(element.createTextRange){
13065 var r = element.createTextRange();
13067 r.moveStart("character
", -99999); // move to 0
13068 r.moveStart("character
", start); // delta from 0 is the correct position
13069 r.moveEnd("character
", stop-start);
13072 }else if(_window["getSelection
"]){
13073 if(element.setSelectionRange){
13074 element.setSelectionRange(start, stop);
13081 if(!dojo._hasResource["dijit
.Tooltip
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
13082 dojo._hasResource["dijit
.Tooltip
"] = true;
13083 dojo.provide("dijit
.Tooltip
");
13089 "dijit
._MasterTooltip
",
13090 [dijit._Widget, dijit._Templated],
13093 // Internal widget that holds the actual tooltip markup,
13094 // which occurs once per page.
13095 // Called by Tooltip widgets which are just containers to hold
13100 // duration: Integer
13101 // Milliseconds to fade in/fade out
13102 duration: dijit.defaultDuration,
13104 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"),
13106 postCreate: function(){
13107 dojo.body().appendChild(this.domNode);
13109 this.bgIframe = new dijit.BackgroundIframe(this.domNode);
13111 // Setup fade-in and fade-out functions.
13112 this.fadeIn = dojo.fadeIn({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onShow
") });
13113 this.fadeOut = dojo.fadeOut({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onHide
") });
13116 show: function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
13118 // Display tooltip w/specified contents to right of specified node
13119 // (To left if there's no space on the right, or if rtl == true)
13121 if(this.aroundNode && this.aroundNode === aroundNode){
13125 // reset width; it may have been set by orient() on a previous tooltip show()
13126 this.domNode.width = "auto
";
13128 if(this.fadeOut.status() == "playing
"){
13129 // previous tooltip is being hidden; wait until the hide completes then show new one
13130 this._onDeck=arguments;
13133 this.containerNode.innerHTML=innerHTML;
13135 var pos = dijit.placeOnScreenAroundElement(this.domNode, aroundNode, dijit.getPopupAroundAlignment((position && position.length) ? position : dijit.Tooltip.defaultPosition, !rtl), dojo.hitch(this, "orient
"));
13138 dojo.style(this.domNode, "opacity
", 0);
13139 this.fadeIn.play();
13140 this.isShowingNow = true;
13141 this.aroundNode = aroundNode;
13144 orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ tooltipCorner, /*Object*/ spaceAvailable, /*Object*/ aroundNodeCoords){
13146 // Private function to set CSS for tooltip node based on which position it's in.
13147 // This is called by the dijit popup code. It will also reduce the tooltip's
13148 // width to whatever width is available
13151 this.connectorNode.style.top = ""; //reset to default
13153 //Adjust the spaceAvailable width, without changing the spaceAvailable object
13154 var tooltipSpaceAvaliableWidth = spaceAvailable.w - this.connectorNode.offsetWidth;
13156 node.className = "dijitTooltip
" +
13158 "BL
-TL
": "dijitTooltipBelow dijitTooltipABLeft
",
13159 "TL
-BL
": "dijitTooltipAbove dijitTooltipABLeft
",
13160 "BR
-TR
": "dijitTooltipBelow dijitTooltipABRight
",
13161 "TR
-BR
": "dijitTooltipAbove dijitTooltipABRight
",
13162 "BR
-BL
": "dijitTooltipRight
",
13163 "BL
-BR
": "dijitTooltipLeft
"
13164 }[aroundCorner + "-" + tooltipCorner];
13166 // reduce tooltip's width to the amount of width available, so that it doesn't overflow screen
13167 this.domNode.style.width = "auto
";
13168 var size = dojo.contentBox(this.domNode);
13170 var width = Math.min((Math.max(tooltipSpaceAvaliableWidth,1)), size.w);
13171 var widthWasReduced = width < size.w;
13173 this.domNode.style.width = width+"px
";
13175 //Adjust width for tooltips that have a really long word or a nowrap setting
13176 if(widthWasReduced){
13177 this.containerNode.style.overflow = "auto
"; //temp change to overflow to detect if our tooltip needs to be wider to support the content
13178 var scrollWidth = this.containerNode.scrollWidth;
13179 this.containerNode.style.overflow = "visible
"; //change it back
13180 if(scrollWidth > width){
13181 scrollWidth = scrollWidth + dojo.style(this.domNode,"paddingLeft
") + dojo.style(this.domNode,"paddingRight
");
13182 this.domNode.style.width = scrollWidth + "px
";
13186 // Reposition the tooltip connector.
13187 if(tooltipCorner.charAt(0) == 'B' && aroundCorner.charAt(0) == 'B'){
13188 var mb = dojo.marginBox(node);
13189 var tooltipConnectorHeight = this.connectorNode.offsetHeight;
13190 if(mb.h > spaceAvailable.h){
13191 // The tooltip starts at the top of the page and will extend past the aroundNode
13192 var aroundNodePlacement = spaceAvailable.h - (aroundNodeCoords.h / 2) - (tooltipConnectorHeight / 2);
13193 this.connectorNode.style.top = aroundNodePlacement + "px
";
13194 this.connectorNode.style.bottom = "";
13196 // Align center of connector with center of aroundNode, except don't let bottom
13197 // of connector extend below bottom of tooltip content, or top of connector
13198 // extend past top of tooltip content
13199 this.connectorNode.style.bottom = Math.min(
13200 Math.max(aroundNodeCoords.h/2 - tooltipConnectorHeight/2, 0),
13201 mb.h - tooltipConnectorHeight) + "px
";
13202 this.connectorNode.style.top = "";
13205 // reset the tooltip back to the defaults
13206 this.connectorNode.style.top = "";
13207 this.connectorNode.style.bottom = "";
13210 return Math.max(0, size.w - tooltipSpaceAvaliableWidth);
13213 _onShow: function(){
13215 // Called at end of fade-in operation
13219 // the arrow won't show up on a node w/an opacity filter
13220 this.domNode.style.filter="";
13224 hide: function(aroundNode){
13226 // Hide the tooltip
13228 if(this._onDeck && this._onDeck[1] == aroundNode){
13229 // this hide request is for a show() that hasn't even started yet;
13230 // just cancel the pending show()
13232 }else if(this.aroundNode === aroundNode){
13233 // this hide request is for the currently displayed tooltip
13234 this.fadeIn.stop();
13235 this.isShowingNow = false;
13236 this.aroundNode = null;
13237 this.fadeOut.play();
13239 // just ignore the call, it's for a tooltip that has already been erased
13243 _onHide: function(){
13245 // Called at end of fade-out operation
13249 this.domNode.style.cssText=""; // to position offscreen again
13250 this.containerNode.innerHTML="";
13252 // a show request has been queued up; do it now
13253 this.show.apply(this, this._onDeck);
13261 dijit.showTooltip = function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
13263 // Display tooltip w/specified contents in specified position.
13264 // See description of dijit.Tooltip.defaultPosition for details on position parameter.
13265 // If position is not specified then dijit.Tooltip.defaultPosition is used.
13266 if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); }
13267 return dijit._masterTT.show(innerHTML, aroundNode, position, rtl);
13270 dijit.hideTooltip = function(aroundNode){
13272 // Hide the tooltip
13273 if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); }
13274 return dijit._masterTT.hide(aroundNode);
13282 // Pops up a tooltip (a help message) when you hover over a node.
13285 // Text to display in the tooltip.
13286 // Specified as innerHTML when creating the widget from markup.
13289 // showDelay: Integer
13290 // Number of milliseconds to wait after hovering over/focusing on the object, before
13291 // the tooltip is displayed.
13294 // connectId: String|String[]
13295 // Id of domNode(s) to attach the tooltip to.
13296 // When user hovers over specified dom node, the tooltip will appear.
13299 // position: String[]
13300 // See description of `dijit.Tooltip.defaultPosition` for details on position parameter.
13303 _setConnectIdAttr: function(/*String*/ newId){
13305 // Connect to node(s) (specified by id)
13307 // Remove connections to old nodes (if there are any)
13308 dojo.forEach(this._connections || [], function(nested){
13309 dojo.forEach(nested, dojo.hitch(this, "disconnect
"));
13312 // Make connections to nodes in newIds.
13313 var ary = dojo.isArrayLike(newId) ? newId : (newId ? [newId] : []);
13314 this._connections = dojo.map(ary, function(id){
13315 var node = dojo.byId(id);
13317 this.connect(node, "onmouseenter
", "_onTargetMouseEnter
"),
13318 this.connect(node, "onmouseleave
", "_onTargetMouseLeave
"),
13319 this.connect(node, "onfocus
", "_onTargetFocus
"),
13320 this.connect(node, "onblur
", "_onTargetBlur
")
13324 this._set("connectId
", newId);
13326 this._connectIds = ary; // save as array
13329 addTarget: function(/*DOMNODE || String*/ node){
13331 // Attach tooltip to specified node if it's not already connected
13333 // TODO: remove in 2.0 and just use set("connectId
", ...) interface
13335 var id = node.id || node;
13336 if(dojo.indexOf(this._connectIds, id) == -1){
13337 this.set("connectId
", this._connectIds.concat(id));
13341 removeTarget: function(/*DOMNODE || String*/ node){
13343 // Detach tooltip from specified node
13345 // TODO: remove in 2.0 and just use set("connectId
", ...) interface
13347 var id = node.id || node, // map from DOMNode back to plain id string
13348 idx = dojo.indexOf(this._connectIds, id);
13350 // remove id (modifies original this._connectIds but that's OK in this case)
13351 this._connectIds.splice(idx, 1);
13352 this.set("connectId
", this._connectIds);
13356 buildRendering: function(){
13357 this.inherited(arguments);
13358 dojo.addClass(this.domNode,"dijitTooltipData
");
13361 startup: function(){
13362 this.inherited(arguments);
13364 // If this tooltip was created in a template, or for some other reason the specified connectId[s]
13365 // didn't exist during the widget's initialization, then connect now.
13366 var ids = this.connectId;
13367 dojo.forEach(dojo.isArrayLike(ids) ? ids : [ids], this.addTarget, this);
13370 _onTargetMouseEnter: function(/*Event*/ e){
13372 // Handler for mouseenter event on the target node
13378 _onTargetMouseLeave: function(/*Event*/ e){
13380 // Handler for mouseleave event on the target node
13383 this._onUnHover(e);
13386 _onTargetFocus: function(/*Event*/ e){
13388 // Handler for focus event on the target node
13392 this._focus = true;
13396 _onTargetBlur: function(/*Event*/ e){
13398 // Handler for blur event on the target node
13402 this._focus = false;
13403 this._onUnHover(e);
13406 _onHover: function(/*Event*/ e){
13408 // Despite the name of this method, it actually handles both hover and focus
13409 // events on the target node, setting a timer to show the tooltip.
13412 if(!this._showTimer){
13413 var target = e.target;
13414 this._showTimer = setTimeout(dojo.hitch(this, function(){this.open(target)}), this.showDelay);
13418 _onUnHover: function(/*Event*/ e){
13420 // Despite the name of this method, it actually handles both mouseleave and blur
13421 // events on the target node, hiding the tooltip.
13425 // keep a tooltip open if the associated element still has focus (even though the
13426 // mouse moved away)
13427 if(this._focus){ return; }
13429 if(this._showTimer){
13430 clearTimeout(this._showTimer);
13431 delete this._showTimer;
13436 open: function(/*DomNode*/ target){
13438 // Display the tooltip; usually not called directly.
13442 if(this._showTimer){
13443 clearTimeout(this._showTimer);
13444 delete this._showTimer;
13446 dijit.showTooltip(this.label || this.domNode.innerHTML, target, this.position, !this.isLeftToRight());
13448 this._connectNode = target;
13449 this.onShow(target, this.position);
13454 // Hide the tooltip or cancel timer for show of tooltip
13458 if(this._connectNode){
13459 // if tooltip is currently shown
13460 dijit.hideTooltip(this._connectNode);
13461 delete this._connectNode;
13464 if(this._showTimer){
13465 // if tooltip is scheduled to be shown (after a brief delay)
13466 clearTimeout(this._showTimer);
13467 delete this._showTimer;
13471 onShow: function(target, position){
13473 // Called when the tooltip is shown
13478 onHide: function(){
13480 // Called when the tooltip is hidden
13485 uninitialize: function(){
13487 this.inherited(arguments);
13492 // dijit.Tooltip.defaultPosition: String[]
13493 // This variable controls the position of tooltips, if the position is not specified to
13494 // the Tooltip widget or *TextBox widget itself. It's an array of strings with the following values:
13496 // * before: places tooltip to the left of the target node/widget, or to the right in
13497 // the case of RTL scripts like Hebrew and Arabic
13498 // * after: places tooltip to the right of the target node/widget, or to the left in
13499 // the case of RTL scripts like Hebrew and Arabic
13500 // * above: tooltip goes above target node
13501 // * below: tooltip goes below target node
13503 // The list is positions is tried, in order, until a position is found where the tooltip fits
13504 // within the viewport.
13506 // Be careful setting this parameter. A value of "above
" may work fine until the user scrolls
13507 // the screen so that there's no room above the target node. Nodes with drop downs, like
13508 // DropDownButton or FilteringSelect, are especially problematic, in that you need to be sure
13509 // that the drop down and tooltip don't overlap, even when the viewport is scrolled so that there
13510 // is only room below (or above) the target node, but not both.
13511 dijit.Tooltip.defaultPosition = ["after
", "before
"];
13515 if(!dojo._hasResource["dijit
.form
.ValidationTextBox
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
13516 dojo._hasResource["dijit
.form
.ValidationTextBox
"] = true;
13517 dojo.provide("dijit
.form
.ValidationTextBox
");
13525 dijit.form.ValidationTextBox.__Constraints = function(){
13527 // locale used for validation, picks up value from this widget's lang attribute
13528 // _flags_: anything
13529 // various flags passed to regExpGen function
13536 "dijit
.form
.ValidationTextBox
",
13537 dijit.form.TextBox,
13540 // Base class for textbox widgets with the ability to validate content of various types and provide user feedback.
13544 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"),
13545 baseClass: "dijitTextBox dijitValidationTextBox
",
13547 // required: Boolean
13548 // User is required to enter data into this field.
13551 // promptMessage: String
13552 // If defined, display this hint string immediately on focus to the textbox, if empty.
13553 // Also displays if the textbox value is Incomplete (not yet valid but will be with additional input).
13554 // Think of this like a tooltip that tells the user what to do, not an error message
13555 // that tells the user what they've done wrong.
13557 // Message disappears when user starts typing.
13560 // invalidMessage: String
13561 // The message to display if value is invalid.
13562 // The translated string value is read from the message file by default.
13563 // Set to "" to use the promptMessage instead.
13564 invalidMessage: "$_unset_
$",
13566 // missingMessage: String
13567 // The message to display if value is empty and the field is required.
13568 // The translated string value is read from the message file by default.
13569 // Set to "" to use the invalidMessage instead.
13570 missingMessage: "$_unset_
$",
13573 // Currently error/prompt message.
13574 // When using the default tooltip implementation, this will only be
13575 // displayed when the field is focused.
13578 // constraints: dijit.form.ValidationTextBox.__Constraints
13579 // user-defined object needed to pass parameters to the validator functions
13582 // regExp: [extension protected] String
13583 // regular expression string used to validate the input
13584 // Do not specify both regExp and regExpGen
13587 regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/ constraints){
13589 // Overridable function used to generate regExp when dependent on constraints.
13590 // Do not specify both regExp and regExpGen.
13592 // extension protected
13593 return this.regExp; // String
13596 // state: [readonly] String
13597 // Shows current state (ie, validation result) of input (""=Normal, Incomplete, or Error)
13600 // tooltipPosition: String[]
13601 // See description of `dijit.Tooltip.defaultPosition` for details on this parameter.
13602 tooltipPosition: [],
13604 _setValueAttr: function(){
13606 // Hook so set('value', ...) works.
13607 this.inherited(arguments);
13608 this.validate(this._focused);
13611 validator: function(/*anything*/ value, /*dijit.form.ValidationTextBox.__Constraints*/ constraints){
13613 // Overridable function used to validate the text input against the regular expression.
13616 return (new RegExp("^(?:" + this.regExpGen(constraints) + ")"+(this.required?"":"?")+"$")).test(value) &&
13617 (!this.required || !this._isEmpty(value)) &&
13618 (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean
13621 _isValidSubset: function(){
13623 // Returns true if the value is either already valid or could be made valid by appending characters.
13624 // This is used for validation while the user [may be] still typing.
13625 return this.textbox.value.search(this._partialre) == 0;
13628 isValid: function(/*Boolean*/ isFocused){
13630 // Tests if value is valid.
13631 // Can override with your own routine in a subclass.
13634 return this.validator(this.textbox.value, this.constraints);
13637 _isEmpty: function(value){
13639 // Checks for whitespace
13640 return (this.trim ? /^\s*$/ : /^$/).test(value); // Boolean
13643 getErrorMessage: function(/*Boolean*/ isFocused){
13645 // Return an error message to show if appropriate
13648 return (this.required && this._isEmpty(this.textbox.value)) ? this.missingMessage : this.invalidMessage; // String
13651 getPromptMessage: function(/*Boolean*/ isFocused){
13653 // Return a hint message to show when widget is first focused
13656 return this.promptMessage; // String
13659 _maskValidSubsetError: true,
13660 validate: function(/*Boolean*/ isFocused){
13662 // Called by oninit, onblur, and onkeypress.
13664 // Show missing or invalid messages if appropriate, and highlight textbox field.
13668 var isValid = this.disabled || this.isValid(isFocused);
13669 if(isValid){ this._maskValidSubsetError = true; }
13670 var isEmpty = this._isEmpty(this.textbox.value);
13671 var isValidSubset = !isValid && isFocused && this._isValidSubset();
13672 this._set("state
", isValid ? "" : (((((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && this._maskValidSubsetError) ? "Incomplete
" : "Error
"));
13673 dijit.setWaiState(this.focusNode, "invalid
", isValid ? "false" : "true");
13675 if(this.state == "Error
"){
13676 this._maskValidSubsetError = isFocused && isValidSubset; // we want the error to show up after a blur and refocus
13677 message = this.getErrorMessage(isFocused);
13678 }else if(this.state == "Incomplete
"){
13679 message = this.getPromptMessage(isFocused); // show the prompt whenever the value is not yet complete
13680 this._maskValidSubsetError = !this._hasBeenBlurred || isFocused; // no Incomplete warnings while focused
13682 message = this.getPromptMessage(isFocused); // show the prompt whenever there's no error and no text
13684 this.set("message
", message);
13689 displayMessage: function(/*String*/ message){
13691 // Overridable method to display validation errors/hints.
13692 // By default uses a tooltip.
13695 dijit.hideTooltip(this.domNode);
13696 if(message && this._focused){
13697 dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
13701 _refreshState: function(){
13702 // Overrides TextBox._refreshState()
13703 this.validate(this._focused);
13704 this.inherited(arguments);
13707 //////////// INITIALIZATION METHODS ///////////////////////////////////////
13709 constructor: function(){
13710 this.constraints = {};
13713 _setConstraintsAttr: function(/*Object*/ constraints){
13714 if(!constraints.locale && this.lang){
13715 constraints.locale = this.lang;
13717 this._set("constraints
", constraints);
13718 this._computePartialRE();
13721 _computePartialRE: function(){
13722 var p = this.regExpGen(this.constraints);
13724 var partialre = "";
13725 // parse the regexp and produce a new regexp that matches valid subsets
13726 // if the regexp is .* then there's no use in matching subsets since everything is valid
13727 if(p != ".*"){ this.regExp.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g,
13729 switch(re.charAt(0)){
13741 partialre += "|$)";
13744 partialre += "(?:"+re+"|$)";
13749 try{ // this is needed for now since the above regexp parsing needs more test verification
13750 "".search(partialre);
13751 }catch(e){ // should never be here unless the original RE is bad or the parsing is bad
13752 partialre = this.regExp;
13753 console.warn('RegExp error in ' + this.declaredClass + ': ' + this.regExp);
13754 } // should never be here unless the original RE is bad or the parsing is bad
13755 this._partialre = "^(?:" + partialre + ")$";
13758 postMixInProperties: function(){
13759 this.inherited(arguments);
13760 this.messages = dojo.i18n.getLocalization("dijit
.form
", "validate
", this.lang);
13761 if(this.invalidMessage == "$_unset_
$"){ this.invalidMessage = this.messages.invalidMessage; }
13762 if(!this.invalidMessage){ this.invalidMessage = this.promptMessage; }
13763 if(this.missingMessage == "$_unset_
$"){ this.missingMessage = this.messages.missingMessage; }
13764 if(!this.missingMessage){ this.missingMessage = this.invalidMessage; }
13765 this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls attachPoints
13768 _setDisabledAttr: function(/*Boolean*/ value){
13769 this.inherited(arguments); // call FormValueWidget._setDisabledAttr()
13770 this._refreshState();
13773 _setRequiredAttr: function(/*Boolean*/ value){
13774 this._set("required
", value);
13775 dijit.setWaiState(this.focusNode, "required
", value);
13776 this._refreshState();
13779 _setMessageAttr: function(/*String*/ message){
13780 this._set("message
", message);
13781 this.displayMessage(message);
13785 // Overrides dijit.form.TextBox.reset() by also
13786 // hiding errors about partial matches
13787 this._maskValidSubsetError = true;
13788 this.inherited(arguments);
13791 _onBlur: function(){
13792 // the message still exists but for back-compat, and to erase the tooltip
13793 // (if the message is being displayed as a tooltip), call displayMessage('')
13794 this.displayMessage('');
13796 this.inherited(arguments);
13802 "dijit
.form
.MappedTextBox
",
13803 dijit.form.ValidationTextBox,
13806 // A dijit.form.ValidationTextBox subclass which provides a base class for widgets that have
13807 // a visible formatted display value, and a serializable
13808 // value in a hidden input field which is actually sent to the server.
13810 // The visible display may
13811 // be locale-dependent and interactive. The value sent to the server is stored in a hidden
13812 // input field which uses the `name` attribute declared by the original widget. That value sent
13813 // to the server is defined by the dijit.form.MappedTextBox.serialize method and is typically
13818 postMixInProperties: function(){
13819 this.inherited(arguments);
13821 // we want the name attribute to go to the hidden <input>, not the displayed <input>,
13822 // so override _FormWidget.postMixInProperties() setting of nameAttrSetting
13823 this.nameAttrSetting = "";
13826 serialize: function(/*anything*/ val, /*Object?*/ options){
13828 // Overridable function used to convert the get('value') result to a canonical
13829 // (non-localized) string. For example, will print dates in ISO format, and
13830 // numbers the same way as they are represented in javascript.
13832 // protected extension
13833 return val.toString ? val.toString() : ""; // String
13836 toString: function(){
13838 // Returns widget as a printable string using the widget's value
13841 var val = this.filter(this.get('value')); // call filter in case value is nonstring and filter has been customized
13842 return val != null ? (typeof val == "string
" ? val : this.serialize(val, this.constraints)) : ""; // String
13845 validate: function(){
13846 // Overrides `dijit.form.TextBox.validate`
13847 this.valueNode.value = this.toString();
13848 return this.inherited(arguments);
13851 buildRendering: function(){
13852 // Overrides `dijit._Templated.buildRendering`
13854 this.inherited(arguments);
13856 // Create a hidden <input> node with the serialized value used for submit
13857 // (as opposed to the displayed value).
13858 // Passing in name as markup rather than calling dojo.create() with an attrs argument
13859 // to make dojo.query(input[name=...]) work on IE. (see #8660)
13860 this.valueNode = dojo.place("<input type
='hidden'" + (this.name ? " name
='" + this.name.replace(/'/g
, """) + "'" : "") + "/>", this.textbox
, "after");
13864 // Overrides `dijit.form.ValidationTextBox.reset` to
13865 // reset the hidden textbox value to ''
13866 this.valueNode
.value
= '';
13867 this.inherited(arguments
);
13873 dijit.form.RangeBoundTextBox.__Constraints = function(){
13875 // Minimum signed value. Default is -Infinity
13877 // Maximum signed value. Default is +Infinity
13884 "dijit.form.RangeBoundTextBox",
13885 dijit
.form
.MappedTextBox
,
13888 // Base class for textbox form widgets which defines a range of valid values.
13890 // rangeMessage: String
13891 // The message to display if value is out-of-range
13895 // constraints: dijit.form.RangeBoundTextBox.__Constraints
13899 rangeCheck: function(/*Number*/ primitive
, /*dijit.form.RangeBoundTextBox.__Constraints*/ constraints
){
13901 // Overridable function used to validate the range of the numeric input value.
13904 return ("min" in constraints
? (this.compare(primitive
,constraints
.min
) >= 0) : true) &&
13905 ("max" in constraints
? (this.compare(primitive
,constraints
.max
) <= 0) : true); // Boolean
13908 isInRange: function(/*Boolean*/ isFocused
){
13910 // Tests if the value is in the min/max range specified in constraints
13913 return this.rangeCheck(this.get('value'), this.constraints
);
13916 _isDefinitelyOutOfRange: function(){
13918 // Returns true if the value is out of range and will remain
13919 // out of range even if the user types more characters
13920 var val
= this.get('value');
13921 var isTooLittle
= false;
13922 var isTooMuch
= false;
13923 if("min" in this.constraints
){
13924 var min
= this.constraints
.min
;
13925 min
= this.compare(val
, ((typeof min
== "number") && min
>= 0 && val
!=0) ? 0 : min
);
13926 isTooLittle
= (typeof min
== "number") && min
< 0;
13928 if("max" in this.constraints
){
13929 var max
= this.constraints
.max
;
13930 max
= this.compare(val
, ((typeof max
!= "number") || max
> 0) ? max
: 0);
13931 isTooMuch
= (typeof max
== "number") && max
> 0;
13933 return isTooLittle
|| isTooMuch
;
13936 _isValidSubset: function(){
13938 // Overrides `dijit.form.ValidationTextBox._isValidSubset`.
13939 // Returns true if the input is syntactically valid, and either within
13940 // range or could be made in range by more typing.
13941 return this.inherited(arguments
) && !this._isDefinitelyOutOfRange();
13944 isValid: function(/*Boolean*/ isFocused
){
13945 // Overrides dijit.form.ValidationTextBox.isValid to check that the value is also in range.
13946 return this.inherited(arguments
) &&
13947 ((this._isEmpty(this.textbox
.value
) && !this.required
) || this.isInRange(isFocused
)); // Boolean
13950 getErrorMessage: function(/*Boolean*/ isFocused
){
13951 // Overrides dijit.form.ValidationTextBox.getErrorMessage to print "out of range" message if appropriate
13952 var v
= this.get('value');
13953 if(v
!== null && v
!== '' && v
!== undefined && (typeof v
!= "number" || !isNaN(v
)) && !this.isInRange(isFocused
)){ // don't check isInRange w/o a real value
13954 return this.rangeMessage
; // String
13956 return this.inherited(arguments
);
13959 postMixInProperties: function(){
13960 this.inherited(arguments
);
13961 if(!this.rangeMessage
){
13962 this.messages
= dojo
.i18n
.getLocalization("dijit.form", "validate", this.lang
);
13963 this.rangeMessage
= this.messages
.rangeMessage
;
13967 _setConstraintsAttr: function(/*Object*/ constraints
){
13968 this.inherited(arguments
);
13969 if(this.focusNode
){ // not set when called from postMixInProperties
13970 if(this.constraints
.min
!== undefined){
13971 dijit
.setWaiState(this.focusNode
, "valuemin", this.constraints
.min
);
13973 dijit
.removeWaiState(this.focusNode
, "valuemin");
13975 if(this.constraints
.max
!== undefined){
13976 dijit
.setWaiState(this.focusNode
, "valuemax", this.constraints
.max
);
13978 dijit
.removeWaiState(this.focusNode
, "valuemax");
13983 _setValueAttr: function(/*Number*/ value
, /*Boolean?*/ priorityChange
){
13985 // Hook so set('value', ...) works.
13987 dijit
.setWaiState(this.focusNode
, "valuenow", value
);
13988 this.inherited(arguments
);
13995 if(!dojo
._hasResource
["dijit.form.ComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
13996 dojo
._hasResource
["dijit.form.ComboBox"] = true;
13997 dojo
.provide("dijit.form.ComboBox");
14010 "dijit.form.ComboBoxMixin",
14011 dijit
._HasDropDown
,
14014 // Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect`
14016 // All widgets that mix in dijit.form.ComboBoxMixin must extend `dijit.form._FormValueWidget`.
14021 // This is the item returned by the dojo.data.store implementation that
14022 // provides the data for this ComboBox, it's the currently selected item.
14025 // pageSize: Integer
14026 // Argument to data provider.
14027 // Specifies number of search results per page (before hitting "next" button)
14028 pageSize
: Infinity
,
14030 // store: [const] Object
14031 // Reference to data provider object used by this ComboBox
14034 // fetchProperties: Object
14035 // Mixin to the dojo.data store's fetch.
14036 // For example, to set the sort order of the ComboBox menu, pass:
14037 // | { sort: [{attribute:"name",descending: true}] }
14038 // To override the default queryOptions so that deep=false, do:
14039 // | { queryOptions: {ignoreCase: true, deep: false} }
14040 fetchProperties
:{},
14043 // A query that can be passed to 'store' to initially filter the items,
14044 // before doing further filtering based on `searchAttr` and the key.
14045 // Any reference to the `searchAttr` is ignored.
14048 // autoComplete: Boolean
14049 // If user types in a partial string, and then tab out of the `<input>` box,
14050 // automatically copy the first entry displayed in the drop down list to
14051 // the `<input>` field
14052 autoComplete
: true,
14054 // highlightMatch: String
14055 // One of: "first", "all" or "none".
14057 // If the ComboBox/FilteringSelect opens with the search results and the searched
14058 // string can be found, it will be highlighted. If set to "all"
14059 // then will probably want to change `queryExpr` parameter to '*${0}*'
14061 // Highlighting is only performed when `labelType` is "text", so as to not
14062 // interfere with any HTML markup an HTML label might contain.
14063 highlightMatch
: "first",
14065 // searchDelay: Integer
14066 // Delay in milliseconds between when user types something and we start
14067 // searching based on that value
14070 // searchAttr: String
14071 // Search for items in the data store where this attribute (in the item)
14072 // matches what the user typed
14073 searchAttr
: "name",
14075 // labelAttr: String?
14076 // The entries in the drop down list come from this attribute in the
14077 // dojo.data items.
14078 // If not specified, the searchAttr attribute is used instead.
14081 // labelType: String
14082 // Specifies how to interpret the labelAttr in the data store items.
14083 // Can be "html" or "text".
14086 // queryExpr: String
14087 // This specifies what query ComboBox/FilteringSelect sends to the data store,
14088 // based on what the user has typed. Changing this expression will modify
14089 // whether the drop down shows only exact matches, a "starting with" match,
14090 // etc. Use it in conjunction with highlightMatch.
14091 // dojo.data query expression pattern.
14092 // `${0}` will be substituted for the user text.
14093 // `*` is used for wildcards.
14094 // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is"
14095 queryExpr
: "${0}*",
14097 // ignoreCase: Boolean
14098 // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items
14101 // hasDownArrow: Boolean
14102 // Set this textbox to have a down arrow button, to display the drop down list.
14103 // Defaults to true.
14104 hasDownArrow
: true,
14106 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"),
14108 baseClass
: "dijitTextBox dijitComboBox",
14110 // dropDownClass: [protected extension] String
14111 // Name of the dropdown widget class used to select a date/time.
14112 // Subclasses should specify this.
14113 dropDownClass
: "dijit.form._ComboBoxMenu",
14115 // Set classes like dijitDownArrowButtonHover depending on
14116 // mouse action over button node
14118 "_buttonNode": "dijitDownArrowButton"
14121 // Flags to _HasDropDown to limit height of drop down to make it fit in viewport
14124 // For backwards compatibility let onClick events propagate, even clicks on the down arrow button
14125 _stopClickEvents
: false,
14127 _getCaretPos: function(/*DomNode*/ element
){
14128 // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
14130 if(typeof(element
.selectionStart
) == "number"){
14131 // FIXME: this is totally borked on Moz < 1.3. Any recourse?
14132 pos
= element
.selectionStart
;
14133 }else if(dojo
.isIE
){
14134 // in the case of a mouse click in a popup being handled,
14135 // then the dojo.doc.selection is not the textarea, but the popup
14136 // var r = dojo.doc.selection.createRange();
14137 // hack to get IE 6 to play nice. What a POS browser.
14138 var tr
= dojo
.doc
.selection
.createRange().duplicate();
14139 var ntr
= element
.createTextRange();
14140 tr
.move("character",0);
14141 ntr
.move("character",0);
14143 // If control doesn't have focus, you get an exception.
14144 // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
14145 // There appears to be no workaround for this - googled for quite a while.
14146 ntr
.setEndPoint("EndToEnd", tr
);
14147 pos
= String(ntr
.text
).replace(/\r/g,"").length
;
14149 // If focus has shifted, 0 is fine for caret pos.
14155 _setCaretPos: function(/*DomNode*/ element
, /*Number*/ location
){
14156 location
= parseInt(location
);
14157 dijit
.selectInputText(element
, location
, location
);
14160 _setDisabledAttr: function(/*Boolean*/ value
){
14161 // Additional code to set disabled state of ComboBox node.
14162 // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr().
14163 this.inherited(arguments
);
14164 dijit
.setWaiState(this.domNode
, "disabled", value
);
14167 _abortQuery: function(){
14168 // stop in-progress query
14169 if(this.searchTimer
){
14170 clearTimeout(this.searchTimer
);
14171 this.searchTimer
= null;
14173 if(this._fetchHandle
){
14174 if(this._fetchHandle
.abort
){ this._fetchHandle
.abort(); }
14175 this._fetchHandle
= null;
14179 _onInput: function(/*Event*/ evt
){
14181 // Handles paste events
14182 if(!this.searchTimer
&& (evt
.type
== 'paste'/*IE|WebKit*/ || evt
.type
== 'input'/*Firefox*/) && this._lastInput
!= this.textbox
.value
){
14183 this.searchTimer
= setTimeout(dojo
.hitch(this, function(){
14184 this._onKey({charOrCode
: 229}); // fake IME key to cause a search
14185 }), 100); // long delay that will probably be preempted by keyboard input
14187 this.inherited(arguments
);
14190 _onKey: function(/*Event*/ evt
){
14192 // Handles keyboard events
14194 var key
= evt
.charOrCode
;
14196 // except for cutting/pasting case - ctrl + x/v
14197 if(evt
.altKey
|| ((evt
.ctrlKey
|| evt
.metaKey
) && (key
!= 'x' && key
!= 'v')) || key
== dojo
.keys
.SHIFT
){
14198 return; // throw out weird key combinations and spurious events
14201 var doSearch
= false;
14202 var pw
= this.dropDown
;
14203 var dk
= dojo
.keys
;
14204 var highlighted
= null;
14205 this._prev_key_backspace
= false;
14206 this._abortQuery();
14208 // _HasDropDown will do some of the work:
14209 // 1. when drop down is not yet shown:
14210 // - if user presses the down arrow key, call loadDropDown()
14211 // 2. when drop down is already displayed:
14212 // - on ESC key, call closeDropDown()
14213 // - otherwise, call dropDown.handleKey() to process the keystroke
14214 this.inherited(arguments
);
14217 highlighted
= pw
.getHighlightedOption();
14221 case dk
.DOWN_ARROW
:
14224 // Keystroke caused ComboBox_menu to move to a different item.
14225 // Copy new item to <input> box.
14227 this._announceOption(highlighted
);
14229 dojo
.stopEvent(evt
);
14233 // prevent submitting form if user presses enter. Also
14234 // prevent accepting the value if either Next or Previous
14237 // only stop event on prev/next
14238 if(highlighted
== pw
.nextButton
){
14239 this._nextSearch(1);
14240 dojo
.stopEvent(evt
);
14242 }else if(highlighted
== pw
.previousButton
){
14243 this._nextSearch(-1);
14244 dojo
.stopEvent(evt
);
14248 // Update 'value' (ex: KY) according to currently displayed text
14249 this._setBlurValue(); // set value if needed
14250 this._setCaretPos(this.focusNode
, this.focusNode
.value
.length
); // move cursor to end and cancel highlighting
14253 // if enter pressed while drop down is open, or for FilteringSelect,
14254 // if we are in the middle of a query to convert a directly typed in value to an item,
14255 // prevent submit, but allow event to bubble
14256 if(this._opened
|| this._fetchHandle
){
14257 evt
.preventDefault();
14262 var newvalue
= this.get('displayedValue');
14263 // if the user had More Choices selected fall into the
14266 newvalue
== pw
._messages
["previousMessage"] ||
14267 newvalue
== pw
._messages
["nextMessage"])
14272 this._selectOption();
14275 this._lastQuery
= null; // in case results come back later
14276 this.closeDropDown();
14282 // user is effectively clicking a choice in the drop down menu
14283 dojo
.stopEvent(evt
);
14284 this._selectOption();
14285 this.closeDropDown();
14287 // user typed a space into the input box, treat as normal character
14294 this._prev_key_backspace
= true;
14299 // Non char keys (F1-F12 etc..) shouldn't open list.
14300 // Ascii characters and IME input (Chinese, Japanese etc.) should.
14301 //IME input produces keycode == 229.
14302 doSearch
= typeof key
== 'string' || key
== 229;
14305 // need to wait a tad before start search so that the event
14306 // bubbles through DOM and we have value visible
14307 this.item
= undefined; // undefined means item needs to be set
14308 this.searchTimer
= setTimeout(dojo
.hitch(this, "_startSearchFromInput"),1);
14312 _autoCompleteText: function(/*String*/ text
){
14314 // Fill in the textbox with the first item from the drop down
14315 // list, and highlight the characters that were
14316 // auto-completed. For example, if user typed "CA" and the
14317 // drop down list appeared, the textbox would be changed to
14318 // "California" and "ifornia" would be highlighted.
14320 var fn
= this.focusNode
;
14322 // IE7: clear selection so next highlight works all the time
14323 dijit
.selectInputText(fn
, fn
.value
.length
);
14324 // does text autoComplete the value in the textbox?
14325 var caseFilter
= this.ignoreCase
? 'toLowerCase' : 'substr';
14326 if(text
[caseFilter
](0).indexOf(this.focusNode
.value
[caseFilter
](0)) == 0){
14327 var cpos
= this._getCaretPos(fn
);
14328 // only try to extend if we added the last character at the end of the input
14329 if((cpos
+1) > fn
.value
.length
){
14330 // only add to input node as we would overwrite Capitalisation of chars
14331 // actually, that is ok
14332 fn
.value
= text
;//.substr(cpos);
14333 // visually highlight the autocompleted characters
14334 dijit
.selectInputText(fn
, cpos
);
14337 // text does not autoComplete; replace the whole value and highlight
14339 dijit
.selectInputText(fn
);
14343 _openResultList: function(/*Object*/ results
, /*Object*/ dataObject
){
14345 // Callback when a search completes.
14347 // 1. generates drop-down list and calls _showResultList() to display it
14348 // 2. if this result list is from user pressing "more choices"/"previous choices"
14349 // then tell screen reader to announce new option
14350 this._fetchHandle
= null;
14351 if( this.disabled
||
14353 (dataObject
.query
[this.searchAttr
] != this._lastQuery
)
14357 var wasSelected
= this.dropDown
._highlighted_option
&& dojo
.hasClass(this.dropDown
._highlighted_option
, "dijitMenuItemSelected");
14358 this.dropDown
.clearResultList();
14359 if(!results
.length
&& !this._maxOptions
){ // if no results and not just the previous choices button
14360 this.closeDropDown();
14364 // Fill in the textbox with the first item from the drop down list,
14365 // and highlight the characters that were auto-completed. For
14366 // example, if user typed "CA" and the drop down list appeared, the
14367 // textbox would be changed to "California" and "ifornia" would be
14370 dataObject
._maxOptions
= this._maxOptions
;
14371 var nodes
= this.dropDown
.createOptions(
14374 dojo
.hitch(this, "_getMenuLabelFromItem")
14377 // show our list (only if we have content, else nothing)
14378 this._showResultList();
14381 // tell the screen reader that the paging callback finished by
14382 // shouting the next choice
14383 if(dataObject
.direction
){
14384 if(1 == dataObject
.direction
){
14385 this.dropDown
.highlightFirstOption();
14386 }else if(-1 == dataObject
.direction
){
14387 this.dropDown
.highlightLastOption();
14390 this._announceOption(this.dropDown
.getHighlightedOption());
14392 }else if(this.autoComplete
&& !this._prev_key_backspace
14393 // when the user clicks the arrow button to show the full list,
14394 // startSearch looks for "*".
14395 // it does not make sense to autocomplete
14396 // if they are just previewing the options available.
14397 && !/^[*]+$/.test(dataObject
.query
[this.searchAttr
])){
14398 this._announceOption(nodes
[1]); // 1st real item
14402 _showResultList: function(){
14404 // Display the drop down if not already displayed, or if it is displayed, then
14405 // reposition it if necessary (reposition may be necessary if drop down's height changed).
14407 this.closeDropDown(true);
14409 // hide the tooltip
14410 this.displayMessage("");
14412 this.openDropDown();
14414 dijit
.setWaiState(this.domNode
, "expanded", "true");
14417 loadDropDown: function(/*Function*/ callback
){
14418 // Overrides _HasDropDown.loadDropDown().
14419 // This is called when user has pressed button icon or pressed the down arrow key
14420 // to open the drop down.
14422 this._startSearchAll();
14425 isLoaded: function(){
14426 // signal to _HasDropDown that it needs to call loadDropDown() to load the
14427 // drop down asynchronously before displaying it
14431 closeDropDown: function(){
14432 // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open).
14433 // This method is the callback when the user types ESC or clicking
14434 // the button icon while the drop down is open. It's also called by other code.
14435 this._abortQuery();
14437 this.inherited(arguments
);
14438 dijit
.setWaiState(this.domNode
, "expanded", "false");
14439 dijit
.removeWaiState(this.focusNode
,"activedescendant");
14443 _setBlurValue: function(){
14444 // if the user clicks away from the textbox OR tabs away, set the
14445 // value to the textbox value
14447 // if value is now more choices or previous choices, revert
14449 var newvalue
= this.get('displayedValue');
14450 var pw
= this.dropDown
;
14452 newvalue
== pw
._messages
["previousMessage"] ||
14453 newvalue
== pw
._messages
["nextMessage"]
14456 this._setValueAttr(this._lastValueReported
, true);
14457 }else if(typeof this.item
== "undefined"){
14458 // Update 'value' (ex: KY) according to currently displayed text
14460 this.set('displayedValue', newvalue
);
14462 if(this.value
!= this._lastValueReported
){
14463 dijit
.form
._FormValueWidget
.prototype._setValueAttr
.call(this, this.value
, true);
14465 this._refreshState();
14469 _onBlur: function(){
14471 // Called magically when focus has shifted away from this widget and it's drop down
14472 this.closeDropDown();
14473 this.inherited(arguments
);
14476 _setItemAttr: function(/*item*/ item
, /*Boolean?*/ priorityChange
, /*String?*/ displayedValue
){
14478 // Set the displayed valued in the input box, and the hidden value
14479 // that gets submitted, based on a dojo.data store item.
14481 // Users shouldn't call this function; they should be calling
14482 // set('item', value)
14485 if(!displayedValue
){
14486 displayedValue
= this.store
.getValue(item
, this.searchAttr
);
14488 var value
= this._getValueField() != this.searchAttr
? this.store
.getIdentity(item
) : displayedValue
;
14489 this._set("item", item
);
14490 dijit
.form
.ComboBox
.superclass
._setValueAttr
.call(this, value
, priorityChange
, displayedValue
);
14493 _announceOption: function(/*Node*/ node
){
14495 // a11y code that puts the highlighted option in the textbox.
14496 // This way screen readers will know what is happening in the
14502 // pull the text value from the item attached to the DOM node
14504 if(node
== this.dropDown
.nextButton
||
14505 node
== this.dropDown
.previousButton
){
14506 newValue
= node
.innerHTML
;
14507 this.item
= undefined;
14510 newValue
= this.store
.getValue(node
.item
, this.searchAttr
).toString();
14511 this.set('item', node
.item
, false, newValue
);
14513 // get the text that the user manually entered (cut off autocompleted text)
14514 this.focusNode
.value
= this.focusNode
.value
.substring(0, this._lastInput
.length
);
14515 // set up ARIA activedescendant
14516 dijit
.setWaiState(this.focusNode
, "activedescendant", dojo
.attr(node
, "id"));
14517 // autocomplete the rest of the option to announce change
14518 this._autoCompleteText(newValue
);
14521 _selectOption: function(/*Event*/ evt
){
14523 // Menu callback function, called when an item in the menu is selected.
14525 this._announceOption(evt
.target
);
14527 this.closeDropDown();
14528 this._setCaretPos(this.focusNode
, this.focusNode
.value
.length
);
14529 dijit
.form
._FormValueWidget
.prototype._setValueAttr
.call(this, this.value
, true); // set this.value and fire onChange
14532 _startSearchAll: function(){
14533 this._startSearch('');
14536 _startSearchFromInput: function(){
14537 this._startSearch(this.focusNode
.value
.replace(/([\\\*\?])/g, "\\$1"));
14540 _getQueryString: function(/*String*/ text
){
14541 return dojo
.string
.substitute(this.queryExpr
, [text
]);
14544 _startSearch: function(/*String*/ key
){
14546 // Starts a search for elements matching key (key=="" means to return all items),
14547 // and calls _openResultList() when the search completes, to display the results.
14548 if(!this.dropDown
){
14549 var popupId
= this.id
+ "_popup",
14550 dropDownConstructor
= dojo
.getObject(this.dropDownClass
, false);
14551 this.dropDown
= new dropDownConstructor({
14552 onChange
: dojo
.hitch(this, this._selectOption
),
14556 dijit
.removeWaiState(this.focusNode
,"activedescendant");
14557 dijit
.setWaiState(this.textbox
,"owns",popupId
); // associate popup with textbox
14559 // create a new query to prevent accidentally querying for a hidden
14560 // value from FilteringSelect's keyField
14561 var query
= dojo
.clone(this.query
); // #5970
14562 this._lastInput
= key
; // Store exactly what was entered by the user.
14563 this._lastQuery
= query
[this.searchAttr
] = this._getQueryString(key
);
14564 // #5970: set _lastQuery, *then* start the timeout
14565 // otherwise, if the user types and the last query returns before the timeout,
14566 // _lastQuery won't be set and their input gets rewritten
14567 this.searchTimer
=setTimeout(dojo
.hitch(this, function(query
, _this
){
14568 this.searchTimer
= null;
14571 ignoreCase
: this.ignoreCase
,
14575 onBegin
: dojo
.hitch(this, "_setMaxOptions"),
14576 onComplete
: dojo
.hitch(this, "_openResultList"),
14577 onError: function(errText
){
14578 _this
._fetchHandle
= null;
14579 console
.error('dijit.form.ComboBox: ' + errText
);
14580 _this
.closeDropDown();
14583 count
: this.pageSize
14585 dojo
.mixin(fetch
, _this
.fetchProperties
);
14586 this._fetchHandle
= _this
.store
.fetch(fetch
);
14588 var nextSearch = function(dataObject
, direction
){
14589 dataObject
.start
+= dataObject
.count
*direction
;
14591 // tell callback the direction of the paging so the screen
14592 // reader knows which menu option to shout
14593 dataObject
.direction
= direction
;
14594 this._fetchHandle
= this.store
.fetch(dataObject
);
14597 this._nextSearch
= this.dropDown
.onPage
= dojo
.hitch(this, nextSearch
, this._fetchHandle
);
14598 }, query
, this), this.searchDelay
);
14601 _setMaxOptions: function(size
, request
){
14602 this._maxOptions
= size
;
14605 _getValueField: function(){
14607 // Helper for postMixInProperties() to set this.value based on data inlined into the markup.
14608 // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value.
14609 return this.searchAttr
;
14612 //////////// INITIALIZATION METHODS ///////////////////////////////////////
14614 constructor: function(){
14616 this.fetchProperties
={};
14619 postMixInProperties: function(){
14621 var srcNodeRef
= this.srcNodeRef
;
14623 // if user didn't specify store, then assume there are option tags
14624 this.store
= new dijit
.form
._ComboBoxDataStore(srcNodeRef
);
14626 // if there is no value set and there is an option list, set
14627 // the value to the first value to be consistent with native
14630 // Firefox and Safari set value
14631 // IE6 and Opera set selectedIndex, which is automatically set
14632 // by the selected attribute of an option tag
14633 // IE6 does not set value, Opera sets value = selectedIndex
14634 if(!("value" in this.params
)){
14635 var item
= (this.item
= this.store
.fetchSelectedItem());
14637 var valueField
= this._getValueField();
14638 this.value
= this.store
.getValue(item
, valueField
);
14643 this.inherited(arguments
);
14646 postCreate: function(){
14648 // Subclasses must call this method from their postCreate() methods
14652 // find any associated label element and add to ComboBox node.
14653 var label
=dojo
.query('label[for="'+this.id
+'"]');
14655 label
[0].id
= (this.id
+"_label");
14656 dijit
.setWaiState(this.domNode
, "labelledby", label
[0].id
);
14659 this.inherited(arguments
);
14662 _setHasDownArrowAttr: function(val
){
14663 this.hasDownArrow
= val
;
14664 this._buttonNode
.style
.display
= val
? "" : "none";
14667 _getMenuLabelFromItem: function(/*Item*/ item
){
14668 var label
= this.labelFunc(item
, this.store
),
14669 labelType
= this.labelType
;
14670 // If labelType is not "text" we don't want to screw any markup ot whatever.
14671 if(this.highlightMatch
!= "none" && this.labelType
== "text" && this._lastInput
){
14672 label
= this.doHighlight(label
, this._escapeHtml(this._lastInput
));
14673 labelType
= "html";
14675 return {html
: labelType
== "html", label
: label
};
14678 doHighlight: function(/*String*/ label
, /*String*/ find
){
14680 // Highlights the string entered by the user in the menu. By default this
14681 // highlights the first occurrence found. Override this method
14682 // to implement your custom highlighting.
14687 // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true
14688 modifiers
= (this.ignoreCase
? "i" : "") + (this.highlightMatch
== "all" ? "g" : ""),
14689 i
= this.queryExpr
.indexOf("${0}");
14690 find
= dojo
.regexp
.escapeString(find
); // escape regexp special chars
14691 return this._escapeHtml(label
).replace(
14692 // prepend ^ when this.queryExpr == "${0}*" and append $ when this.queryExpr == "*${0}"
14693 new RegExp((i
== 0 ? "^" : "") + "("+ find
+")" + (i
== (this.queryExpr
.length
- 4) ? "$" : ""), modifiers
),
14694 '<span class="dijitComboBoxHighlightMatch">$1</span>'
14695 ); // returns String, (almost) valid HTML (entities encoded)
14698 _escapeHtml: function(/*String*/ str
){
14699 // TODO Should become dojo.html.entities(), when exists use instead
14701 // Adds escape sequences for special characters in XML: &<>"'
14702 str
= String(str
).replace(/&/gm, "&").replace(/</gm
, "<")
14703 .replace(/>/gm, ">").replace(/"/gm, ""
;");
14704 return str; // string
14708 // Overrides the _FormWidget.reset().
14709 // Additionally reset the .item (to clean up).
14711 this.inherited(arguments);
14714 labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){
14716 // Computes the label to display based on the dojo.data store item.
14718 // The label that the ComboBox should display
14722 // Use toString() because XMLStore returns an XMLItem whereas this
14723 // method is expected to return a String (#9354)
14724 return store.getValue(item, this.labelAttr || this.searchAttr).toString(); // String
14730 "dijit
.form
._ComboBoxMenu
",
14731 [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
14734 // Focus-less menu for internal use in `dijit.form.ComboBox`
14738 templateString: "<ul
class='dijitReset dijitMenu' dojoAttachEvent
='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' style
='overflow: \"auto\"; overflow-x: \"hidden\";'>"
14739 +"<li
class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint
='previousButton' role
='option'></li
>"
14740 +"<li
class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint
='nextButton' role
='option'></li
>"
14743 // _messages: Object
14744 // Holds "next
" and "previous
" text for paging buttons on drop down
14747 baseClass: "dijitComboBoxMenu
",
14749 postMixInProperties: function(){
14750 this.inherited(arguments);
14751 this._messages = dojo.i18n.getLocalization("dijit
.form
", "ComboBox
", this.lang);
14754 buildRendering: function(){
14755 this.inherited(arguments);
14757 // fill in template with i18n messages
14758 this.previousButton.innerHTML = this._messages["previousMessage
"];
14759 this.nextButton.innerHTML = this._messages["nextMessage
"];
14762 _setValueAttr: function(/*Object*/ value){
14763 this.value = value;
14764 this.onChange(value);
14768 onChange: function(/*Object*/ value){
14770 // Notifies ComboBox/FilteringSelect that user clicked an option in the drop down menu.
14771 // Probably should be called onSelect.
14775 onPage: function(/*Number*/ direction){
14777 // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page.
14782 onClose: function(){
14784 // Callback from dijit.popup code to this widget, notifying it that it closed
14787 this._blurOptionNode();
14790 _createOption: function(/*Object*/ item, labelFunc){
14792 // Creates an option to appear on the popup menu subclassed by
14793 // `dijit.form.FilteringSelect`.
14795 var menuitem = dojo.create("li
", {
14796 "class": "dijitReset dijitMenuItem
" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl
"),
14799 var labelObject = labelFunc(item);
14800 if(labelObject.html){
14801 menuitem.innerHTML = labelObject.label;
14803 menuitem.appendChild(
14804 dojo.doc.createTextNode(labelObject.label)
14807 // #3250: in blank options, assign a normal height
14808 if(menuitem.innerHTML == ""){
14809 menuitem.innerHTML = " 
;";
14811 menuitem.item=item;
14815 createOptions: function(results, dataObject, labelFunc){
14817 // Fills in the items in the drop down list
14819 // Array of dojo.data items
14823 // Function to produce a label in the drop down list from a dojo.data item
14825 //this._dataObject=dataObject;
14826 //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList);
14827 // display "Previous
. . ." button
14828 this.previousButton.style.display = (dataObject.start == 0) ? "none
" : "";
14829 dojo.attr(this.previousButton, "id
", this.id + "_prev
");
14830 // create options using _createOption function defined by parent
14831 // ComboBox (or FilteringSelect) class
14833 // iterate over cache nondestructively
14834 dojo.forEach(results, function(item, i){
14835 var menuitem = this._createOption(item, labelFunc);
14836 dojo.attr(menuitem, "id
", this.id + i);
14837 this.domNode.insertBefore(menuitem, this.nextButton);
14839 // display "Next
. . ." button
14840 var displayMore = false;
14841 //Try to determine if we should show 'more'...
14842 if(dataObject._maxOptions && dataObject._maxOptions != -1){
14843 if((dataObject.start + dataObject.count) < dataObject._maxOptions){
14844 displayMore = true;
14845 }else if((dataObject.start + dataObject.count) > dataObject._maxOptions && dataObject.count == results.length){
14846 //Weird return from a datastore, where a start + count > maxOptions
14847 // implies maxOptions isn't really valid and we have to go into faking it.
14848 //And more or less assume more if count == results.length
14849 displayMore = true;
14851 }else if(dataObject.count == results.length){
14852 //Don't know the size, so we do the best we can based off count alone.
14853 //So, if we have an exact match to count, assume more.
14854 displayMore = true;
14857 this.nextButton.style.display = displayMore ? "" : "none
";
14858 dojo.attr(this.nextButton,"id
", this.id + "_next
");
14859 return this.domNode.childNodes;
14862 clearResultList: function(){
14864 // Clears the entries in the drop down list, but of course keeps the previous and next buttons.
14865 while(this.domNode.childNodes.length>2){
14866 this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]);
14868 this._blurOptionNode();
14871 _onMouseDown: function(/*Event*/ evt){
14872 dojo.stopEvent(evt);
14875 _onMouseUp: function(/*Event*/ evt){
14876 if(evt.target === this.domNode || !this._highlighted_option){
14877 // !this._highlighted_option check to prevent immediate selection when menu appears on top
14878 // of <input>, see #9898. Note that _HasDropDown also has code to prevent this.
14880 }else if(evt.target == this.previousButton){
14881 this._blurOptionNode();
14883 }else if(evt.target == this.nextButton){
14884 this._blurOptionNode();
14887 var tgt = evt.target;
14888 // while the clicked node is inside the div
14890 // recurse to the top
14891 tgt = tgt.parentNode;
14893 this._setValueAttr({ target: tgt }, true);
14897 _onMouseOver: function(/*Event*/ evt){
14898 if(evt.target === this.domNode){ return; }
14899 var tgt = evt.target;
14900 if(!(tgt == this.previousButton || tgt == this.nextButton)){
14901 // while the clicked node is inside the div
14903 // recurse to the top
14904 tgt = tgt.parentNode;
14907 this._focusOptionNode(tgt);
14910 _onMouseOut: function(/*Event*/ evt){
14911 if(evt.target === this.domNode){ return; }
14912 this._blurOptionNode();
14915 _focusOptionNode: function(/*DomNode*/ node){
14917 // Does the actual highlight.
14918 if(this._highlighted_option != node){
14919 this._blurOptionNode();
14920 this._highlighted_option = node;
14921 dojo.addClass(this._highlighted_option, "dijitMenuItemSelected
");
14925 _blurOptionNode: function(){
14927 // Removes highlight on highlighted option.
14928 if(this._highlighted_option){
14929 dojo.removeClass(this._highlighted_option, "dijitMenuItemSelected
");
14930 this._highlighted_option = null;
14934 _highlightNextOption: function(){
14936 // Highlight the item just below the current selection.
14937 // If nothing selected, highlight first option.
14939 // because each press of a button clears the menu,
14940 // the highlighted option sometimes becomes detached from the menu!
14941 // test to see if the option has a parent to see if this is the case.
14942 if(!this.getHighlightedOption()){
14943 var fc = this.domNode.firstChild;
14944 this._focusOptionNode(fc.style.display == "none
" ? fc.nextSibling : fc);
14946 var ns = this._highlighted_option.nextSibling;
14947 if(ns && ns.style.display != "none
"){
14948 this._focusOptionNode(ns);
14950 this.highlightFirstOption();
14953 // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover
14954 dojo.window.scrollIntoView(this._highlighted_option);
14957 highlightFirstOption: function(){
14959 // Highlight the first real item in the list (not Previous Choices).
14960 var first = this.domNode.firstChild;
14961 var second = first.nextSibling;
14962 this._focusOptionNode(second.style.display == "none
" ? first : second); // remotely possible that Previous Choices is the only thing in the list
14963 dojo.window.scrollIntoView(this._highlighted_option);
14966 highlightLastOption: function(){
14968 // Highlight the last real item in the list (not More Choices).
14969 this._focusOptionNode(this.domNode.lastChild.previousSibling);
14970 dojo.window.scrollIntoView(this._highlighted_option);
14973 _highlightPrevOption: function(){
14975 // Highlight the item just above the current selection.
14976 // If nothing selected, highlight last option (if
14977 // you select Previous and try to keep scrolling up the list).
14978 if(!this.getHighlightedOption()){
14979 var lc = this.domNode.lastChild;
14980 this._focusOptionNode(lc.style.display == "none
" ? lc.previousSibling : lc);
14982 var ps = this._highlighted_option.previousSibling;
14983 if(ps && ps.style.display != "none
"){
14984 this._focusOptionNode(ps);
14986 this.highlightLastOption();
14989 dojo.window.scrollIntoView(this._highlighted_option);
14992 _page: function(/*Boolean*/ up){
14994 // Handles page-up and page-down keypresses
14996 var scrollamount = 0;
14997 var oldscroll = this.domNode.scrollTop;
14998 var height = dojo.style(this.domNode, "height
");
14999 // if no item is highlighted, highlight the first option
15000 if(!this.getHighlightedOption()){
15001 this._highlightNextOption();
15003 while(scrollamount<height){
15005 // stop at option 1
15006 if(!this.getHighlightedOption().previousSibling ||
15007 this._highlighted_option.previousSibling.style.display == "none
"){
15010 this._highlightPrevOption();
15012 // stop at last option
15013 if(!this.getHighlightedOption().nextSibling ||
15014 this._highlighted_option.nextSibling.style.display == "none
"){
15017 this._highlightNextOption();
15020 var newscroll=this.domNode.scrollTop;
15021 scrollamount+=(newscroll-oldscroll)*(up ? -1:1);
15022 oldscroll=newscroll;
15026 pageUp: function(){
15028 // Handles pageup keypress.
15029 // TODO: just call _page directly from handleKey().
15035 pageDown: function(){
15037 // Handles pagedown keypress.
15038 // TODO: just call _page directly from handleKey().
15044 getHighlightedOption: function(){
15046 // Returns the highlighted option.
15047 var ho = this._highlighted_option;
15048 return (ho && ho.parentNode) ? ho : null;
15051 handleKey: function(evt){
15053 // Handle keystroke event forwarded from ComboBox, returning false if it's
15054 // a keystroke I recognize and process, true otherwise.
15055 switch(evt.charOrCode){
15056 case dojo.keys.DOWN_ARROW:
15057 this._highlightNextOption();
15059 case dojo.keys.PAGE_DOWN:
15062 case dojo.keys.UP_ARROW:
15063 this._highlightPrevOption();
15065 case dojo.keys.PAGE_UP:
15076 "dijit
.form
.ComboBox
",
15077 [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin],
15080 // Auto-completing text box, and base class for dijit.form.FilteringSelect.
15083 // The drop down box's values are populated from an class called
15084 // a data provider, which returns a list of values based on the characters
15085 // that the user has typed into the input box.
15086 // If OPTION tags are used as the data provider via markup,
15087 // then the OPTION tag's child text node is used as the widget value
15088 // when selected. The OPTION tag's value attribute is ignored.
15089 // To set the default value when using OPTION tags, specify the selected
15090 // attribute on 1 of the child OPTION tags.
15092 // Some of the options to the ComboBox are actually arguments to the data
15095 _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
15097 // Hook so set('value', value) works.
15099 // Sets the value of the select.
15100 this._set("item
", null); // value not looked up in store
15101 if(!value){ value = ''; } // null translates to blank
15102 dijit.form.ValidationTextBox.prototype._setValueAttr.call(this, value, priorityChange, displayedValue);
15107 dojo.declare("dijit
.form
._ComboBoxDataStore
", null, {
15109 // Inefficient but small data store specialized for inlined `dijit.form.ComboBox` data
15112 // Provides a store for inlined data like:
15115 // | <option value="AL
">Alabama</option>
15118 // Actually. just implements the subset of dojo.data.Read/Notification
15119 // needed for ComboBox and FilteringSelect to work.
15121 // Note that an item is just a pointer to the <option> DomNode.
15123 constructor: function( /*DomNode*/ root){
15125 if(root.tagName != "SELECT
" && root.firstChild){
15126 root = dojo.query("select
", root);
15127 if(root.length > 0){ // SELECT is a child of srcNodeRef
15129 }else{ // no select, so create 1 to parent the option tags to define selectedIndex
15130 this.root.innerHTML = "<SELECT
>"+this.root.innerHTML+"</SELECT
>";
15131 root = this.root.firstChild;
15135 dojo.query("> option
", root).forEach(function(node){
15136 // TODO: this was added in #3858 but unclear why/if it's needed; doesn't seem to be.
15137 // If it is needed then can we just hide the select itself instead?
15138 //node.style.display="none
";
15139 node.innerHTML = dojo.trim(node.innerHTML);
15144 getValue: function( /*item*/ item,
15145 /*attribute-name-string*/ attribute,
15146 /*value?*/ defaultValue){
15147 return (attribute == "value
") ? item.value : (item.innerText || item.textContent || '');
15150 isItemLoaded: function(/*anything*/ something){
15154 getFeatures: function(){
15155 return {"dojo
.data
.api
.Read
": true, "dojo
.data
.api
.Identity
": true};
15158 _fetchItems: function( /*Object*/ args,
15159 /*Function*/ findCallback,
15160 /*Function*/ errorCallback){
15162 // See dojo.data.util.simpleFetch.fetch()
15163 if(!args.query){ args.query = {}; }
15164 if(!args.query.name){ args.query.name = ""; }
15165 if(!args.queryOptions){ args.queryOptions = {}; }
15166 var matcher = dojo.data.util.filter.patternToRegExp(args.query.name, args.queryOptions.ignoreCase),
15167 items = dojo.query("> option
", this.root).filter(function(option){
15168 return (option.innerText || option.textContent || '').match(matcher);
15171 items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this));
15173 findCallback(items, args);
15176 close: function(/*dojo.data.api.Request || args || null*/ request){
15180 getLabel: function(/*item*/ item){
15181 return item.innerHTML;
15184 getIdentity: function(/*item*/ item){
15185 return dojo.attr(item, "value
");
15188 fetchItemByIdentity: function(/*Object*/ args){
15190 // Given the identity of an item, this method returns the item that has
15191 // that identity through the onItem callback.
15192 // Refer to dojo.data.api.Identity.fetchItemByIdentity() for more details.
15195 // Given arguments like:
15197 // | {identity: "CA
", onItem: function(item){...}
15199 // Call `onItem()` with the DOM node `<option value="CA
">California</option>`
15200 var item = dojo.query("> option
[value
='" + args.identity + "']", this.root)[0];
15204 fetchSelectedItem: function(){
15206 // Get the option marked as selected, like `<option selected>`.
15207 // Not part of dojo.data API.
15208 var root = this.root,
15209 si = root.selectedIndex;
15210 return typeof si == "number
"
15211 ? dojo.query("> option
:nth
-child(" + (si != -1 ? si+1 : 1) + ")", root)[0]
15212 : null; // dojo.data.Item
15215 //Mix in the simple fetch implementation to this class.
15216 dojo.extend(dijit.form._ComboBoxDataStore,dojo.data.util.simpleFetch);
15220 if(!dojo._hasResource["dijit
.form
.FilteringSelect
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15221 dojo._hasResource["dijit
.form
.FilteringSelect
"] = true;
15222 dojo.provide("dijit
.form
.FilteringSelect
");
15227 "dijit
.form
.FilteringSelect
",
15228 [dijit.form.MappedTextBox, dijit.form.ComboBoxMixin],
15231 // An enhanced version of the HTML SELECT tag, populated dynamically
15234 // An enhanced version of the HTML SELECT tag, populated dynamically. It works
15235 // very nicely with very large data sets because it can load and page data as needed.
15236 // It also resembles ComboBox, but does not allow values outside of the provided ones.
15237 // If OPTION tags are used as the data provider via markup, then the
15238 // OPTION tag's child text node is used as the displayed value when selected
15239 // while the OPTION tag's value attribute is used as the widget value on form submit.
15240 // To set the default value when using OPTION tags, specify the selected
15241 // attribute on 1 of the child OPTION tags.
15243 // Similar features:
15244 // - There is a drop down list of possible values.
15245 // - You can only enter a value from the drop down list. (You can't
15246 // enter an arbitrary value.)
15247 // - The value submitted with the form is the hidden value (ex: CA),
15248 // not the displayed value a.k.a. label (ex: California)
15250 // Enhancements over plain HTML version:
15251 // - If you type in some text then it will filter down the list of
15252 // possible values in the drop down list.
15253 // - List can be specified either as a static list or via a javascript
15254 // function (that can get the list from a server)
15256 // required: Boolean
15257 // True (default) if user is required to enter a value into this field.
15260 _lastDisplayedValue: "",
15262 _isValidSubset: function(){
15263 return this._opened;
15266 isValid: function(){
15267 // Overrides ValidationTextBox.isValid()
15268 return this.item || (!this.required && this.get('displayedValue') == ""); // #5974
15271 _refreshState: function(){
15272 if(!this.searchTimer){ // state will be refreshed after results are returned
15273 this.inherited(arguments);
15277 _callbackSetLabel: function(
15279 /*Object*/ dataObject,
15280 /*Boolean?*/ priorityChange){
15282 // Callback from dojo.data after lookup of user entered value finishes
15284 // setValue does a synchronous lookup,
15285 // so it calls _callbackSetLabel directly,
15286 // and so does not pass dataObject
15287 // still need to test against _lastQuery in case it came too late
15288 if((dataObject && dataObject.query[this.searchAttr] != this._lastQuery) || (!dataObject && result.length && this.store.getIdentity(result[0]) != this._lastQuery)){
15291 if(!result.length){
15292 //#3268: don't modify display value on bad input
15293 //#3285: change CSS to indicate error
15294 this.valueNode.value = "";
15295 dijit.form.TextBox.superclass._setValueAttr.call(this, "", priorityChange || (priorityChange === undefined && !this._focused));
15296 this._set("item
", null);
15297 this.validate(this._focused);
15299 this.set('item', result[0], priorityChange);
15303 _openResultList: function(/*Object*/ results, /*Object*/ dataObject){
15304 // Callback when a data store query completes.
15305 // Overrides ComboBox._openResultList()
15307 // #3285: tap into search callback to see if user's query resembles a match
15308 if(dataObject.query[this.searchAttr] != this._lastQuery){
15311 dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments);
15313 if(this.item === undefined){ // item == undefined for keyboard search
15314 // If the search returned no items that means that the user typed
15315 // in something invalid (and they can't make it valid by typing more characters),
15316 // so flag the FilteringSelect as being in an invalid state
15317 this.validate(true);
15321 _getValueAttr: function(){
15323 // Hook for get('value') to work.
15325 // don't get the textbox value but rather the previously set hidden value.
15326 // Use this.valueNode.value which isn't always set for other MappedTextBox widgets until blur
15327 return this.valueNode.value;
15330 _getValueField: function(){
15331 // Overrides ComboBox._getValueField()
15335 _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){
15337 // Hook so set('value', value) works.
15339 // Sets the value of the select.
15340 // Also sets the label to the corresponding value by reverse lookup.
15341 if(!this._onChangeActive){ priorityChange = null; }
15342 this._lastQuery = value;
15344 if(value === null || value === ''){
15345 this._setDisplayedValueAttr('', priorityChange);
15349 //#3347: fetchItemByIdentity if no keyAttr specified
15351 this.store.fetchItemByIdentity({
15353 onItem: function(item){
15354 self._callbackSetLabel(item? [item] : [], undefined, priorityChange);
15359 _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
15361 // Set the displayed valued in the input box, and the hidden value
15362 // that gets submitted, based on a dojo.data store item.
15364 // Users shouldn't call this function; they should be calling
15365 // set('item', value)
15368 this.inherited(arguments);
15369 this.valueNode.value = this.value;
15370 this._lastDisplayedValue = this.textbox.value;
15373 _getDisplayQueryString: function(/*String*/ text){
15374 return text.replace(/([\\\*\?])/g, "\\$1");
15377 _setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){
15379 // Hook so set('displayedValue', label) works.
15381 // Sets textbox to display label. Also performs reverse lookup
15382 // to set the hidden value. label should corresponding to item.searchAttr.
15384 if(label == null){ label = ''; }
15386 // This is called at initialization along with every custom setter.
15387 // Usually (or always?) the call can be ignored. If it needs to be
15388 // processed then at least make sure that the XHR request doesn't trigger an onChange()
15389 // event, even if it returns after creation has finished
15390 if(!this._created){
15391 if(!("displayedValue
" in this.params)){
15394 priorityChange = false;
15397 // Do a reverse lookup to map the specified displayedValue to the hidden value.
15398 // Note that if there's a custom labelFunc() this code
15400 this.closeDropDown();
15401 var query = dojo.clone(this.query); // #6196: populate query with user-specifics
15402 // escape meta characters of dojo.data.util.filter.patternToRegExp().
15403 this._lastQuery = query[this.searchAttr] = this._getDisplayQueryString(label);
15404 // If the label is not valid, the callback will never set it,
15405 // so the last valid value will get the warning textbox. Set the
15406 // textbox value now so that the impending warning will make
15407 // sense to the user
15408 this.textbox.value = label;
15409 this._lastDisplayedValue = label;
15410 this._set("displayedValue
", label); // for watch("displayedValue
") notification
15415 ignoreCase: this.ignoreCase,
15418 onComplete: function(result, dataObject){
15419 _this._fetchHandle = null;
15420 dojo.hitch(_this, "_callbackSetLabel
")(result, dataObject, priorityChange);
15422 onError: function(errText){
15423 _this._fetchHandle = null;
15424 console.error('dijit.form.FilteringSelect: ' + errText);
15425 dojo.hitch(_this, "_callbackSetLabel
")([], undefined, false);
15428 dojo.mixin(fetch, this.fetchProperties);
15429 this._fetchHandle = this.store.fetch(fetch);
15434 this.set('displayedValue', this._lastDisplayedValue);
15441 if(!dojo._hasResource["dijit
.form
.Form
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15442 dojo._hasResource["dijit
.form
.Form
"] = true;
15443 dojo.provide("dijit
.form
.Form
");
15452 [dijit._Widget, dijit._Templated, dijit.form._FormMixin, dijit.layout._ContentPaneResizeMixin],
15455 // Widget corresponding to HTML form tag, for validation and serialization
15458 // | <form dojoType="dijit
.form
.Form
" id="myForm
">
15459 // | Name: <input type="text
" name="name
" />
15461 // | myObj = {name: "John Doe
"};
15462 // | dijit.byId('myForm').set('value', myObj);
15464 // | myObj=dijit.byId('myForm').get('value');
15466 // HTML <FORM> attributes
15469 // Name of form for scripting.
15473 // Server-side form handler.
15477 // HTTP method used to submit the form, either "GET
" or "POST
".
15480 // encType: String?
15481 // Encoding type for the form, ex: application/x-www-form-urlencoded.
15484 // accept-charset: String?
15485 // List of supported charsets.
15486 "accept
-charset
": "",
15489 // List of MIME types for file upload.
15493 // Target frame for the document to be opened in.
15496 templateString: "<form dojoAttachPoint
='containerNode' dojoAttachEvent
='onreset:_onReset,onsubmit:_onSubmit' ${!nameAttrSetting}
></form
>",
15498 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
15502 "accept
-charset
": "",
15507 postMixInProperties: function(){
15508 // Setup name=foo string to be referenced from the template (but only if a name has been specified)
15509 // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660
15510 this.nameAttrSetting = this.name ? ("name
='" + this.name + "'") : "";
15511 this.inherited(arguments);
15514 execute: function(/*Object*/ formContents){
15516 // Deprecated: use submit()
15521 onExecute: function(){
15523 // Deprecated: use onSubmit()
15528 _setEncTypeAttr: function(/*String*/ value){
15529 this.encType = value;
15530 dojo.attr(this.domNode, "encType
", value);
15531 if(dojo.isIE){ this.domNode.encoding = value; }
15534 postCreate: function(){
15535 // IE tries to hide encType
15536 // TODO: remove in 2.0, no longer necessary with data-dojo-params
15537 if(dojo.isIE && this.srcNodeRef && this.srcNodeRef.attributes){
15538 var item = this.srcNodeRef.attributes.getNamedItem('encType');
15539 if(item && !item.specified && (typeof item.value == "string
")){
15540 this.set('encType', item.value);
15543 this.inherited(arguments);
15546 reset: function(/*Event?*/ e){
15548 // restores all widget values back to their init values,
15549 // calls onReset() which can cancel the reset by returning false
15551 // create fake event so we can know if preventDefault() is called
15553 returnValue: true, // the IE way
15554 preventDefault: function(){ // not IE
15555 this.returnValue = false;
15557 stopPropagation: function(){},
15558 currentTarget: e ? e.target : this.domNode,
15559 target: e ? e.target : this.domNode
15561 // if return value is not exactly false, and haven't called preventDefault(), then reset
15562 if(!(this.onReset(faux) === false) && faux.returnValue){
15563 this.inherited(arguments, []);
15567 onReset: function(/*Event?*/ e){
15569 // Callback when user resets the form. This method is intended
15570 // to be over-ridden. When the `reset` method is called
15571 // programmatically, the return value from `onReset` is used
15572 // to compute whether or not resetting should proceed
15575 return true; // Boolean
15578 _onReset: function(e){
15584 _onSubmit: function(e){
15585 var fp = dijit.form.Form.prototype;
15586 // TODO: remove this if statement beginning with 2.0
15587 if(this.execute != fp.execute || this.onExecute != fp.onExecute){
15588 dojo.deprecated("dijit
.form
.Form
:execute()/onExecute() are deprecated
. Use
onSubmit() instead
.", "", "2.0");
15590 this.execute(this.getValues());
15592 if(this.onSubmit(e) === false){ // only exactly false stops submit
15597 onSubmit: function(/*Event?*/ e){
15599 // Callback when user submits the form.
15601 // This method is intended to be over-ridden, but by default it checks and
15602 // returns the validity of form elements. When the `submit`
15603 // method is called programmatically, the return value from
15604 // `onSubmit` is used to compute whether or not submission
15609 return this.isValid(); // Boolean
15612 submit: function(){
15614 // programmatically submit form if and only if the `onSubmit` returns true
15615 if(!(this.onSubmit() === false)){
15616 this.containerNode.submit();
15624 if(!dojo._hasResource["dijit
.form
.RadioButton
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15625 dojo._hasResource["dijit
.form
.RadioButton
"] = true;
15626 dojo.provide("dijit
.form
.RadioButton
");
15630 // TODO: for 2.0, move the RadioButton code into this file
15634 if(!dojo._hasResource["dijit
.form
._FormSelectWidget
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15635 dojo._hasResource["dijit
.form
._FormSelectWidget
"] = true;
15636 dojo.provide("dijit
.form
._FormSelectWidget
");
15642 dijit.form.__SelectOption = function(){
15644 // The value of the option. Setting to empty (or missing) will
15645 // place a separator at that location
15647 // The label for our option. It can contain html tags.
15648 // selected: Boolean
15649 // Whether or not we are a selected option
15650 // disabled: Boolean
15651 // Whether or not this specific option is disabled
15652 this.value = value;
15653 this.label = label;
15654 this.selected = selected;
15655 this.disabled = disabled;
15659 dojo.declare("dijit
.form
._FormSelectWidget
", dijit.form._FormValueWidget, {
15661 // Extends _FormValueWidget in order to provide "select
-specific
"
15662 // values - i.e., those values that are unique to <select> elements.
15663 // This also provides the mechanism for reading the elements from
15664 // a store, if desired.
15666 // multiple: [const] Boolean
15667 // Whether or not we are multi-valued
15670 // options: dijit.form.__SelectOption[]
15671 // The set of options for our select item. Roughly corresponds to
15672 // the html <option> tag.
15675 // store: dojo.data.api.Identity
15676 // A store which, at the very least impelements dojo.data.api.Identity
15677 // to use for getting our list of options - rather than reading them
15678 // from the <option> html tags.
15682 // A query to use when fetching items from our store
15685 // queryOptions: object
15686 // Query options to use when fetching from the store
15687 queryOptions: null,
15689 // onFetch: Function
15690 // A callback to do with an onFetch - but before any items are actually
15691 // iterated over (i.e. to filter even futher what you want to add)
15694 // sortByLabel: Boolean
15695 // Flag to sort the options returned from a store by the label of
15700 // loadChildrenOnOpen: Boolean
15701 // By default loadChildren is called when the items are fetched from the
15702 // store. This property allows delaying loadChildren (and the creation
15703 // of the options/menuitems) until the user clicks the button to open the
15705 loadChildrenOnOpen: false,
15707 getOptions: function(/*anything*/ valueOrIdx){
15709 // Returns a given option (or options).
15711 // If passed in as a string, that string is used to look up the option
15712 // in the array of options - based on the value property.
15713 // (See dijit.form.__SelectOption).
15715 // If passed in a number, then the option with the given index (0-based)
15716 // within this select will be returned.
15718 // If passed in a dijit.form.__SelectOption, the same option will be
15719 // returned if and only if it exists within this select.
15721 // If passed an array, then an array will be returned with each element
15722 // in the array being looked up.
15724 // If not passed a value, then all options will be returned
15727 // The option corresponding with the given value or index. null
15728 // is returned if any of the following are true:
15729 // - A string value is passed in which doesn't exist
15730 // - An index is passed in which is outside the bounds of the array of options
15731 // - A dijit.form.__SelectOption is passed in which is not a part of the select
15733 // NOTE: the compare for passing in a dijit.form.__SelectOption checks
15734 // if the value property matches - NOT if the exact option exists
15735 // NOTE: if passing in an array, null elements will be placed in the returned
15736 // array when a value is not found.
15737 var lookupValue = valueOrIdx, opts = this.options || [], l = opts.length;
15739 if(lookupValue === undefined){
15740 return opts; // dijit.form.__SelectOption[]
15742 if(dojo.isArray(lookupValue)){
15743 return dojo.map(lookupValue, "return this.getOptions(item
);", this); // dijit.form.__SelectOption[]
15745 if(dojo.isObject(valueOrIdx)){
15746 // We were passed an option - so see if it's in our array (directly),
15747 // and if it's not, try and find it by value.
15748 if(!dojo.some(this.options, function(o, idx){
15749 if(o === lookupValue ||
15750 (o.value && o.value === lookupValue.value)){
15759 if(typeof lookupValue == "string
"){
15760 for(var i=0; i<l; i++){
15761 if(opts[i].value === lookupValue){
15767 if(typeof lookupValue == "number
" && lookupValue >= 0 && lookupValue < l){
15768 return this.options[lookupValue] // dijit.form.__SelectOption
15770 return null; // null
15773 addOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ option){
15775 // Adds an option or options to the end of the select. If value
15776 // of the option is empty or missing, a separator is created instead.
15777 // Passing in an array of options will yield slightly better performance
15778 // since the children are only loaded once.
15779 if(!dojo.isArray(option)){ option = [option]; }
15780 dojo.forEach(option, function(i){
15781 if(i && dojo.isObject(i)){
15782 this.options.push(i);
15785 this._loadChildren();
15788 removeOption: function(/*String|dijit.form.__SelectOption|Number|Array*/ valueOrIdx){
15790 // Removes the given option or options. You can remove by string
15791 // (in which case the value is removed), number (in which case the
15792 // index in the options array is removed), or select option (in
15793 // which case, the select option with a matching value is removed).
15794 // You can also pass in an array of those values for a slightly
15795 // better performance since the children are only loaded once.
15796 if(!dojo.isArray(valueOrIdx)){ valueOrIdx = [valueOrIdx]; }
15797 var oldOpts = this.getOptions(valueOrIdx);
15798 dojo.forEach(oldOpts, function(i){
15799 // We can get null back in our array - if our option was not found. In
15800 // that case, we don't want to blow up...
15802 this.options = dojo.filter(this.options, function(node, idx){
15803 return (node.value !== i.value || node.label !== i.label);
15805 this._removeOptionItem(i);
15808 this._loadChildren();
15811 updateOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ newOption){
15813 // Updates the values of the given option. The option to update
15814 // is matched based on the value of the entered option. Passing
15815 // in an array of new options will yeild better performance since
15816 // the children will only be loaded once.
15817 if(!dojo.isArray(newOption)){ newOption = [newOption]; }
15818 dojo.forEach(newOption, function(i){
15819 var oldOpt = this.getOptions(i), k;
15821 for(k in i){ oldOpt[k] = i[k]; }
15824 this._loadChildren();
15827 setStore: function(/*dojo.data.api.Identity*/ store,
15828 /*anything?*/ selectedValue,
15829 /*Object?*/ fetchArgs){
15831 // Sets the store you would like to use with this select widget.
15832 // The selected value is the value of the new store to set. This
15833 // function returns the original store, in case you want to reuse
15834 // it or something.
15835 // store: dojo.data.api.Identity
15836 // The store you would like to use - it MUST implement Identity,
15837 // and MAY implement Notification.
15838 // selectedValue: anything?
15839 // The value that this widget should set itself to *after* the store
15841 // fetchArgs: Object?
15842 // The arguments that will be passed to the store's fetch() function
15843 var oStore = this.store;
15844 fetchArgs = fetchArgs || {};
15845 if(oStore !== store){
15846 // Our store has changed, so update our notifications
15847 dojo.forEach(this._notifyConnections || [], dojo.disconnect);
15848 delete this._notifyConnections;
15849 if(store && store.getFeatures()["dojo
.data
.api
.Notification
"]){
15850 this._notifyConnections = [
15851 dojo.connect(store, "onNew
", this, "_onNewItem
"),
15852 dojo.connect(store, "onDelete
", this, "_onDeleteItem
"),
15853 dojo.connect(store, "onSet
", this, "_onSetItem
")
15856 this._set("store
", store);
15859 // Turn off change notifications while we make all these changes
15860 this._onChangeActive = false;
15862 // Remove existing options (if there are any)
15863 if(this.options && this.options.length){
15864 this.removeOption(this.options);
15867 // Add our new options
15869 this._loadingStore = true;
15870 store.fetch(dojo.delegate(fetchArgs, {
15871 onComplete: function(items, opts){
15872 if(this.sortByLabel && !fetchArgs.sort && items.length){
15873 items.sort(dojo.data.util.sorter.createSortFunction([{
15874 attribute: store.getLabelAttributes(items[0])[0]
15878 if(fetchArgs.onFetch){
15879 items = fetchArgs.onFetch.call(this, items, opts);
15881 // TODO: Add these guys as a batch, instead of separately
15882 dojo.forEach(items, function(i){
15883 this._addOptionForItem(i);
15886 // Set our value (which might be undefined), and then tweak
15887 // it to send a change event with the real value
15888 this._loadingStore = false;
15889 this.set("value
", "_pendingValue
" in this ? this._pendingValue : selectedValue);
15890 delete this._pendingValue;
15892 if(!this.loadChildrenOnOpen){
15893 this._loadChildren();
15895 this._pseudoLoadChildren(items);
15897 this._fetchedWith = opts;
15898 this._lastValueReported = this.multiple ? [] : null;
15899 this._onChangeActive = true;
15901 this._handleOnChange(this.value);
15906 delete this._fetchedWith;
15908 return oStore; // dojo.data.api.Identity
15911 // TODO: implement set() and watch() for store and query, although not sure how to handle
15912 // setting them individually rather than together (as in setStore() above)
15914 _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
15916 // set the value of the widget.
15917 // If a string is passed, then we set our value from looking it up.
15918 if(this._loadingStore){
15919 // Our store is loading - so save our value, and we'll set it when
15921 this._pendingValue = newValue;
15924 var opts = this.getOptions() || [];
15925 if(!dojo.isArray(newValue)){
15926 newValue = [newValue];
15928 dojo.forEach(newValue, function(i, idx){
15929 if(!dojo.isObject(i)){
15932 if(typeof i === "string
"){
15933 newValue[idx] = dojo.filter(opts, function(node){
15934 return node.value === i;
15935 })[0] || {value: "", label: ""};
15939 // Make sure some sane default is set
15940 newValue = dojo.filter(newValue, function(i){ return i && i.value; });
15941 if(!this.multiple && (!newValue[0] || !newValue[0].value) && opts.length){
15942 newValue[0] = opts[0];
15944 dojo.forEach(opts, function(i){
15945 i.selected = dojo.some(newValue, function(v){ return v.value === i.value; });
15947 var val = dojo.map(newValue, function(i){ return i.value; }),
15948 disp = dojo.map(newValue, function(i){ return i.label; });
15950 this._set("value
", this.multiple ? val : val[0]);
15951 this._setDisplay(this.multiple ? disp : disp[0]);
15952 this._updateSelection();
15953 this._handleOnChange(this.value, priorityChange);
15956 _getDisplayedValueAttr: function(){
15958 // returns the displayed value of the widget
15959 var val = this.get("value
");
15960 if(!dojo.isArray(val)){
15963 var ret = dojo.map(this.getOptions(val), function(v){
15964 if(v && "label
" in v){
15971 return this.multiple ? ret : ret[0];
15974 _loadChildren: function(){
15976 // Loads the children represented by this widget's options.
15977 // reset the menu to make it populatable on the next click
15978 if(this._loadingStore){ return; }
15979 dojo.forEach(this._getChildren(), function(child){
15980 child.destroyRecursive();
15982 // Add each menu item
15983 dojo.forEach(this.options, this._addOptionItem, this);
15986 this._updateSelection();
15989 _updateSelection: function(){
15991 // Sets the "selected
" class on the item for styling purposes
15992 this._set("value
", this._getValueFromOpts());
15993 var val = this.value;
15994 if(!dojo.isArray(val)){
15998 dojo.forEach(this._getChildren(), function(child){
15999 var isSelected = dojo.some(val, function(v){
16000 return child.option && (v === child.option.value);
16002 dojo.toggleClass(child.domNode, this.baseClass + "SelectedOption
", isSelected);
16003 dijit.setWaiState(child.domNode, "selected
", isSelected);
16008 _getValueFromOpts: function(){
16010 // Returns the value of the widget by reading the options for
16011 // the selected flag
16012 var opts = this.getOptions() || [];
16013 if(!this.multiple && opts.length){
16014 // Mirror what a select does - choose the first one
16015 var opt = dojo.filter(opts, function(i){
16018 if(opt && opt.value){
16021 opts[0].selected = true;
16022 return opts[0].value;
16024 }else if(this.multiple){
16025 // Set value to be the sum of all selected
16026 return dojo.map(dojo.filter(opts, function(i){
16035 // Internal functions to call when we have store notifications come in
16036 _onNewItem: function(/*item*/ item, /*Object?*/ parentInfo){
16037 if(!parentInfo || !parentInfo.parent){
16038 // Only add it if we are top-level
16039 this._addOptionForItem(item);
16042 _onDeleteItem: function(/*item*/ item){
16043 var store = this.store;
16044 this.removeOption(store.getIdentity(item));
16046 _onSetItem: function(/*item*/ item){
16047 this.updateOption(this._getOptionObjForItem(item));
16050 _getOptionObjForItem: function(item){
16052 // Returns an option object based off the given item. The "value
"
16053 // of the option item will be the identity of the item, the "label
"
16054 // of the option will be the label of the item. If the item contains
16055 // children, the children value of the item will be set
16056 var store = this.store, label = store.getLabel(item),
16057 value = (label ? store.getIdentity(item) : null);
16058 return {value: value, label: label, item:item}; // dijit.form.__SelectOption
16061 _addOptionForItem: function(/*item*/ item){
16063 // Creates (and adds) the option for the given item
16064 var store = this.store;
16065 if(!store.isItemLoaded(item)){
16066 // We are not loaded - so let's load it and add later
16067 store.loadItem({item: item, onComplete: function(i){
16068 this._addOptionForItem(item);
16073 var newOpt = this._getOptionObjForItem(item);
16074 this.addOption(newOpt);
16077 constructor: function(/*Object*/ keywordArgs){
16079 // Saves off our value, if we have an initial one set so we
16080 // can use it if we have a store as well (see startup())
16081 this._oValue = (keywordArgs || {}).value || null;
16084 buildRendering: function(){
16085 this.inherited(arguments);
16086 dojo.setSelectable(this.focusNode, false);
16089 _fillContent: function(){
16091 // Loads our options and sets up our dropdown correctly. We
16092 // don't want any content, so we don't call any inherit chain
16094 var opts = this.options;
16096 opts = this.options = this.srcNodeRef ? dojo.query(">",
16097 this.srcNodeRef).map(function(node){
16098 if(node.getAttribute("type
") === "separator
"){
16099 return { value: "", label: "", selected: false, disabled: false };
16102 value: (node.getAttribute("data
-" + dojo._scopeName + "-value
") || node.getAttribute("value
")),
16103 label: String(node.innerHTML),
16104 // FIXME: disabled and selected are not valid on complex markup children (which is why we're
16105 // looking for data-dojo-value above. perhaps we should data-dojo-props="" this whole thing?)
16106 // decide before 1.6
16107 selected: node.getAttribute("selected
") || false,
16108 disabled: node.getAttribute("disabled
") || false
16113 this._set("value
", this._getValueFromOpts());
16114 }else if(this.multiple && typeof this.value == "string
"){
16115 this_set("value
", this.value.split(","));
16119 postCreate: function(){
16121 // sets up our event handling that we need for functioning
16123 this.inherited(arguments);
16125 // Make our event connections for updating state
16126 this.connect(this, "onChange
", "_updateSelection
");
16127 this.connect(this, "startup
", "_loadChildren
");
16129 this._setValueAttr(this.value, null);
16132 startup: function(){
16134 // Connects in our store, if we have one defined
16135 this.inherited(arguments);
16136 var store = this.store, fetchArgs = {};
16137 dojo.forEach(["query
", "queryOptions
", "onFetch
"], function(i){
16139 fetchArgs[i] = this[i];
16143 if(store && store.getFeatures()["dojo
.data
.api
.Identity
"]){
16144 // Temporarily set our store to null so that it will get set
16145 // and connected appropriately
16147 this.setStore(store, this._oValue, fetchArgs);
16151 destroy: function(){
16153 // Clean up our connections
16154 dojo.forEach(this._notifyConnections || [], dojo.disconnect);
16155 this.inherited(arguments);
16158 _addOptionItem: function(/*dijit.form.__SelectOption*/ option){
16160 // User-overridable function which, for the given option, adds an
16161 // item to the select. If the option doesn't have a value, then a
16162 // separator is added in that place. Make sure to store the option
16163 // in the created option widget.
16166 _removeOptionItem: function(/*dijit.form.__SelectOption*/ option){
16168 // User-overridable function which, for the given option, removes
16169 // its item from the select.
16172 _setDisplay: function(/*String or String[]*/ newDisplay){
16174 // Overridable function which will set the display for the
16175 // widget. newDisplay is either a string (in the case of
16176 // single selects) or array of strings (in the case of multi-selects)
16179 _getChildren: function(){
16181 // Overridable function to return the children that this widget contains.
16185 _getSelectedOptionsAttr: function(){
16187 // hooks into this.attr to provide a mechanism for getting the
16188 // option items for the current value of the widget.
16189 return this.getOptions(this.get("value
"));
16192 _pseudoLoadChildren: function(/*item[]*/ items){
16194 // a function that will "fake
" loading children, if needed, and
16195 // if we have set to not load children until the widget opens.
16197 // An array of items that will be loaded, when needed
16200 onSetStore: function(){
16202 // a function that can be connected to in order to receive a
16203 // notification that the store has finished loading and all options
16204 // from that store are available
16210 if(!dojo._hasResource["dijit
._KeyNavContainer
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16211 dojo._hasResource["dijit
._KeyNavContainer
"] = true;
16212 dojo.provide("dijit
._KeyNavContainer
");
16216 dojo.declare("dijit
._KeyNavContainer
",
16221 // A _Container with keyboard navigation of its children.
16223 // To use this mixin, call connectKeyNavHandlers() in
16224 // postCreate() and call startupKeyNavChildren() in startup().
16225 // It provides normalized keyboard and focusing code for Container
16228 // focusedChild: [protected] Widget
16229 // The currently focused child widget, or null if there isn't one
16230 focusedChild: null,
16233 // tabIndex: Integer
16234 // Tab index of the container; same as HTML tabIndex attribute.
16235 // Note then when user tabs into the container, focus is immediately
16236 // moved to the first item in the container.
16241 connectKeyNavHandlers: function(/*dojo.keys[]*/ prevKeyCodes, /*dojo.keys[]*/ nextKeyCodes){
16243 // Call in postCreate() to attach the keyboard handlers
16244 // to the container.
16245 // preKeyCodes: dojo.keys[]
16246 // Key codes for navigating to the previous child.
16247 // nextKeyCodes: dojo.keys[]
16248 // Key codes for navigating to the next child.
16252 var keyCodes = (this._keyNavCodes = {});
16253 var prev = dojo.hitch(this, this.focusPrev);
16254 var next = dojo.hitch(this, this.focusNext);
16255 dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; });
16256 dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; });
16257 keyCodes[dojo.keys.HOME] = dojo.hitch(this, "focusFirstChild
");
16258 keyCodes[dojo.keys.END] = dojo.hitch(this, "focusLastChild
");
16259 this.connect(this.domNode, "onkeypress
", "_onContainerKeypress
");
16260 this.connect(this.domNode, "onfocus
", "_onContainerFocus
");
16263 startupKeyNavChildren: function(){
16265 // Call in startup() to set child tabindexes to -1
16268 dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild
"));
16271 addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
16273 // Add a child to our _Container
16274 dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
16275 this._startupChild(widget);
16280 // Default focus() implementation: focus the first child.
16281 this.focusFirstChild();
16284 focusFirstChild: function(){
16286 // Focus the first focusable child in the container.
16289 var child = this._getFirstFocusableChild();
16290 if(child){ // edge case: Menu could be empty or hidden
16291 this.focusChild(child);
16295 focusLastChild: function(){
16297 // Focus the last focusable child in the container.
16300 var child = this._getLastFocusableChild();
16301 if(child){ // edge case: Menu could be empty or hidden
16302 this.focusChild(child);
16306 focusNext: function(){
16308 // Focus the next widget
16311 var child = this._getNextFocusableChild(this.focusedChild, 1);
16312 this.focusChild(child);
16315 focusPrev: function(){
16317 // Focus the last focusable node in the previous widget
16318 // (ex: go to the ComboButton icon section rather than button section)
16321 var child = this._getNextFocusableChild(this.focusedChild, -1);
16322 this.focusChild(child, true);
16325 focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){
16329 // Reference to container's child widget
16331 // If true and if widget has multiple focusable nodes, focus the
16332 // last one instead of the first one
16336 if(this.focusedChild && widget !== this.focusedChild){
16337 this._onChildBlur(this.focusedChild);
16339 widget.set("tabIndex
", this.tabIndex); // for IE focus outline to appear, must set tabIndex before focs
16340 widget.focus(last ? "end
" : "start
");
16341 this._set("focusedChild
", widget);
16344 _startupChild: function(/*dijit._Widget*/ widget){
16346 // Setup for each child widget
16348 // Sets tabIndex=-1 on each child, so that the tab key will
16349 // leave the container rather than visiting each child.
16353 widget.set("tabIndex
", "-1");
16355 this.connect(widget, "_onFocus
", function(){
16356 // Set valid tabIndex so tabbing away from widget goes to right place, see #10272
16357 widget.set("tabIndex
", this.tabIndex);
16359 this.connect(widget, "_onBlur
", function(){
16360 widget.set("tabIndex
", "-1");
16364 _onContainerFocus: function(evt){
16366 // Handler for when the container gets focus
16368 // Initially the container itself has a tabIndex, but when it gets
16369 // focus, switch focus to first child...
16373 // Note that we can't use _onFocus() because switching focus from the
16374 // _onFocus() handler confuses the focus.js code
16375 // (because it causes _onFocusNode() to be called recursively)
16377 // focus bubbles on Firefox,
16378 // so just make sure that focus has really gone to the container
16379 if(evt.target !== this.domNode){ return; }
16381 this.focusFirstChild();
16383 // and then set the container's tabIndex to -1,
16384 // (don't remove as that breaks Safari 4)
16385 // so that tab or shift-tab will go to the fields after/before
16386 // the container, rather than the container itself
16387 dojo.attr(this.domNode, "tabIndex
", "-1");
16390 _onBlur: function(evt){
16391 // When focus is moved away the container, and its descendant (popup) widgets,
16392 // then restore the container's tabIndex so that user can tab to it again.
16393 // Note that using _onBlur() so that this doesn't happen when focus is shifted
16394 // to one of my child widgets (typically a popup)
16396 dojo.attr(this.domNode, "tabIndex
", this.tabIndex);
16398 this.inherited(arguments);
16401 _onContainerKeypress: function(evt){
16403 // When a key is pressed, if it's an arrow key etc. then
16404 // it's handled here.
16407 if(evt.ctrlKey || evt.altKey){ return; }
16408 var func = this._keyNavCodes[evt.charOrCode];
16411 dojo.stopEvent(evt);
16415 _onChildBlur: function(/*dijit._Widget*/ widget){
16417 // Called when focus leaves a child widget to go
16418 // to a sibling widget.
16423 _getFirstFocusableChild: function(){
16425 // Returns first child that can be focused
16426 return this._getNextFocusableChild(null, 1); // dijit._Widget
16429 _getLastFocusableChild: function(){
16431 // Returns last child that can be focused
16432 return this._getNextFocusableChild(null, -1); // dijit._Widget
16435 _getNextFocusableChild: function(child, dir){
16437 // Returns the next or previous focusable child, compared
16440 // The current widget
16445 child = this._getSiblingOfChild(child, dir);
16447 var children = this.getChildren();
16448 for(var i=0; i < children.length; i++){
16450 child = children[(dir>0) ? 0 : (children.length-1)];
16452 if(child.isFocusable()){
16453 return child; // dijit._Widget
16455 child = this._getSiblingOfChild(child, dir);
16457 // no focusable child found
16458 return null; // dijit._Widget
16465 if(!dojo._hasResource["dijit
.MenuItem
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16466 dojo._hasResource["dijit
.MenuItem
"] = true;
16467 dojo.provide("dijit
.MenuItem
");
16474 dojo.declare("dijit
.MenuItem
",
16475 [dijit._Widget, dijit._Templated, dijit._Contained, dijit._CssStateMixin],
16478 // A line item in a Menu Widget
16481 // icon, label, and expand arrow (BiDi-dependent) indicating sub-menu
16482 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"),
16484 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
16485 label: { node: "containerNode
", type: "innerHTML
" },
16486 iconClass: { node: "iconNode
", type: "class" }
16489 baseClass: "dijitMenuItem
",
16495 // iconClass: String
16496 // Class to apply to DOMNode to make it display an icon.
16499 // accelKey: String
16500 // Text for the accelerator (shortcut) key combination.
16501 // Note that although Menu can display accelerator keys there
16502 // is no infrastructure to actually catch and execute these
16506 // disabled: Boolean
16507 // If true, the menu item is disabled.
16508 // If false, the menu item is enabled.
16511 _fillContent: function(/*DomNode*/ source){
16512 // If button label is specified as srcNodeRef.innerHTML rather than
16513 // this.params.label, handle it here.
16514 if(source && !("label
" in this.params)){
16515 this.set('label', source.innerHTML);
16519 buildRendering: function(){
16520 this.inherited(arguments);
16521 var label = this.id+"_text
";
16522 dojo.attr(this.containerNode, "id
", label);
16523 if(this.accelKeyNode){
16524 dojo.attr(this.accelKeyNode, "id
", this.id + "_accel
");
16525 label += " " + this.id + "_accel
";
16527 dijit.setWaiState(this.domNode, "labelledby
", label);
16528 dojo.setSelectable(this.domNode, false);
16531 _onHover: function(){
16533 // Handler when mouse is moved onto menu item
16536 this.getParent().onItemHover(this);
16539 _onUnhover: function(){
16541 // Handler when mouse is moved off of menu item,
16542 // possibly to a child menu, or maybe to a sibling
16543 // menuitem or somewhere else entirely.
16547 // if we are unhovering the currently selected item
16548 // then unselect it
16549 this.getParent().onItemUnhover(this);
16551 // When menu is hidden (collapsed) due to clicking a MenuItem and having it execute,
16552 // FF and IE don't generate an onmouseout event for the MenuItem.
16553 // So, help out _CssStateMixin in this case.
16554 this._set("hovering
", false);
16557 _onClick: function(evt){
16559 // Internal handler for click events on MenuItem.
16562 this.getParent().onItemClick(this, evt);
16563 dojo.stopEvent(evt);
16566 onClick: function(/*Event*/ evt){
16568 // User defined function to handle clicks
16575 // Focus on this MenuItem
16577 if(dojo.isIE == 8){
16578 // needed for IE8 which won't scroll TR tags into view on focus yet calling scrollIntoView creates flicker (#10275)
16579 this.containerNode.focus();
16581 dijit.focus(this.focusNode);
16583 // this throws on IE (at least) in some scenarios
16587 _onFocus: function(){
16589 // This is called by the focus manager when focus
16590 // goes to this MenuItem or a child menu.
16593 this._setSelected(true);
16594 this.getParent()._onItemFocus(this);
16596 this.inherited(arguments);
16599 _setSelected: function(selected){
16601 // Indicate that this node is the currently selected one
16606 * TODO: remove this method and calls to it, when _onBlur() is working for MenuItem.
16607 * Currently _onBlur() gets called when focus is moved from the MenuItem to a child menu.
16608 * That's not supposed to happen, but the problem is:
16609 * In order to allow dijit.popup's getTopPopup() to work,a sub menu's popupParent
16610 * points to the parent Menu, bypassing the parent MenuItem... thus the
16611 * MenuItem is not in the chain of active widgets and gets a premature call to
16615 dojo.toggleClass(this.domNode, "dijitMenuItemSelected
", selected);
16618 setLabel: function(/*String*/ content){
16620 // Deprecated. Use set('label', ...) instead.
16623 dojo.deprecated("dijit
.MenuItem
.setLabel() is deprecated
. Use
set('label', ...) instead
.", "", "2.0");
16624 this.set("label
", content);
16627 setDisabled: function(/*Boolean*/ disabled){
16629 // Deprecated. Use set('disabled', bool) instead.
16632 dojo.deprecated("dijit
.Menu
.setDisabled() is deprecated
. Use
set('disabled', bool
) instead
.", "", "2.0");
16633 this.set('disabled', disabled);
16635 _setDisabledAttr: function(/*Boolean*/ value){
16637 // Hook for attr('disabled', ...) to work.
16638 // Enable or disable this menu item.
16640 dijit.setWaiState(this.focusNode, 'disabled', value ? 'true' : 'false');
16641 this._set("disabled
", value);
16643 _setAccelKeyAttr: function(/*String*/ value){
16645 // Hook for attr('accelKey', ...) to work.
16646 // Set accelKey on this menu item.
16648 this.accelKeyNode.style.display=value?"":"none
";
16649 this.accelKeyNode.innerHTML=value;
16650 //have to use colSpan to make it work in IE
16651 dojo.attr(this.containerNode,'colSpan',value?"1":"2");
16653 this._set("accelKey
", value);
16659 if(!dojo._hasResource["dijit
.PopupMenuItem
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16660 dojo._hasResource["dijit
.PopupMenuItem
"] = true;
16661 dojo.provide("dijit
.PopupMenuItem
");
16665 dojo.declare("dijit
.PopupMenuItem
",
16668 _fillContent: function(){
16670 // When Menu is declared in markup, this code gets the menu label and
16671 // the popup widget from the srcNodeRef.
16673 // srcNodeRefinnerHTML contains both the menu item text and a popup widget
16674 // The first part holds the menu item text and the second part is the popup
16676 // | <div dojoType="dijit
.PopupMenuItem
">
16677 // | <span>pick me</span>
16678 // | <popup> ... </popup>
16683 if(this.srcNodeRef){
16684 var nodes = dojo.query("*", this.srcNodeRef);
16685 dijit.PopupMenuItem.superclass._fillContent.call(this, nodes[0]);
16687 // save pointer to srcNode so we can grab the drop down widget after it's instantiated
16688 this.dropDownContainer = this.srcNodeRef;
16692 startup: function(){
16693 if(this._started){ return; }
16694 this.inherited(arguments);
16696 // we didn't copy the dropdown widget from the this.srcNodeRef, so it's in no-man's
16697 // land now. move it to dojo.doc.body.
16699 var node = dojo.query("[widgetId
]", this.dropDownContainer)[0];
16700 this.popup = dijit.byNode(node);
16702 dojo.body().appendChild(this.popup.domNode);
16703 this.popup.startup();
16705 this.popup.domNode.style.display="none
";
16706 if(this.arrowWrapper){
16707 dojo.style(this.arrowWrapper, "visibility
", "");
16709 dijit.setWaiState(this.focusNode, "haspopup
", "true");
16712 destroyDescendants: function(){
16714 // Destroy the popup, unless it's already been destroyed. This can happen because
16715 // the popup is a direct child of <body> even though it's logically my child.
16716 if(!this.popup._destroyed){
16717 this.popup.destroyRecursive();
16721 this.inherited(arguments);
16727 if(!dojo._hasResource["dijit
.CheckedMenuItem
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16728 dojo._hasResource["dijit
.CheckedMenuItem
"] = true;
16729 dojo.provide("dijit
.CheckedMenuItem
");
16733 dojo.declare("dijit
.CheckedMenuItem
",
16737 // A checkbox-like menu item for toggling on and off
16739 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"),
16741 // checked: Boolean
16742 // Our checked state
16744 _setCheckedAttr: function(/*Boolean*/ checked){
16746 // Hook so attr('checked', bool) works.
16747 // Sets the class and state for the check box.
16748 dojo.toggleClass(this.domNode, "dijitCheckedMenuItemChecked
", checked);
16749 dijit.setWaiState(this.domNode, "checked
", checked);
16750 this._set("checked
", checked);
16753 onChange: function(/*Boolean*/ checked){
16755 // User defined function to handle check/uncheck events
16760 _onClick: function(/*Event*/ e){
16762 // Clicking this item just toggles its state
16765 if(!this.disabled){
16766 this.set("checked
", !this.checked);
16767 this.onChange(this.checked);
16769 this.inherited(arguments);
16775 if(!dojo._hasResource["dijit
.MenuSeparator
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16776 dojo._hasResource["dijit
.MenuSeparator
"] = true;
16777 dojo.provide("dijit
.MenuSeparator
");
16783 dojo.declare("dijit
.MenuSeparator
",
16784 [dijit._Widget, dijit._Templated, dijit._Contained],
16787 // A line between two menu items
16789 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"),
16791 buildRendering: function(){
16792 this.inherited(arguments);
16793 dojo.setSelectable(this.domNode, false);
16796 isFocusable: function(){
16798 // Override to always return false
16802 return false; // Boolean
16808 if(!dojo._hasResource["dijit
.Menu
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16809 dojo._hasResource["dijit
.Menu
"] = true;
16810 dojo.provide("dijit
.Menu
");
16821 // "dijit
/MenuItem", "dijit/PopupMenuItem
", "dijit
/CheckedMenuItem", "dijit/MenuSeparator
" for Back-compat (TODO: remove in 2.0)
16823 dojo.declare("dijit
._MenuBase
",
16824 [dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
16827 // Base class for Menu and MenuBar
16829 // parentMenu: [readonly] Widget
16830 // pointer to menu that displayed me
16833 // popupDelay: Integer
16834 // number of milliseconds before hovering (without clicking) causes the popup to automatically open.
16837 startup: function(){
16838 if(this._started){ return; }
16840 dojo.forEach(this.getChildren(), function(child){ child.startup(); });
16841 this.startupKeyNavChildren();
16843 this.inherited(arguments);
16846 onExecute: function(){
16848 // Attach point for notification about when a menu item has been executed.
16849 // This is an internal mechanism used for Menus to signal to their parent to
16850 // close them, because they are about to execute the onClick handler. In
16851 // general developers should not attach to or override this method.
16856 onCancel: function(/*Boolean*/ closeAll){
16858 // Attach point for notification about when the user cancels the current menu
16859 // This is an internal mechanism used for Menus to signal to their parent to
16860 // close them. In general developers should not attach to or override this method.
16865 _moveToPopup: function(/*Event*/ evt){
16867 // This handles the right arrow key (left arrow key on RTL systems),
16868 // which will either open a submenu, or move to the next item in the
16869 // ancestor MenuBar
16873 if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
16874 this.focusedChild._onClick(evt);
16876 var topMenu = this._getTopMenu();
16877 if(topMenu && topMenu._isMenuBar){
16878 topMenu.focusNext();
16883 _onPopupHover: function(/*Event*/ evt){
16885 // This handler is called when the mouse moves over the popup.
16889 // if the mouse hovers over a menu popup that is in pending-close state,
16890 // then stop the close operation.
16891 // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker)
16892 if(this.currentPopup && this.currentPopup._pendingClose_timer){
16893 var parentMenu = this.currentPopup.parentMenu;
16894 // highlight the parent menu item pointing to this popup
16895 if(parentMenu.focusedChild){
16896 parentMenu.focusedChild._setSelected(false);
16898 parentMenu.focusedChild = this.currentPopup.from_item;
16899 parentMenu.focusedChild._setSelected(true);
16900 // cancel the pending close
16901 this._stopPendingCloseTimer(this.currentPopup);
16905 onItemHover: function(/*MenuItem*/ item){
16907 // Called when cursor is over a MenuItem.
16911 // Don't do anything unless user has "activated
" the menu by:
16913 // 2) opening it from a parent menu (which automatically focuses it)
16915 this.focusChild(item);
16916 if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){
16917 this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup
"), this.popupDelay);
16920 // if the user is mixing mouse and keyboard navigation,
16921 // then the menu may not be active but a menu item has focus,
16922 // but it's not the item that the mouse just hovered over.
16923 // To avoid both keyboard and mouse selections, use the latest.
16924 if(this.focusedChild){
16925 this.focusChild(item);
16927 this._hoveredChild = item;
16930 _onChildBlur: function(item){
16932 // Called when a child MenuItem becomes inactive because focus
16933 // has been removed from the MenuItem *and* it's descendant menus.
16936 this._stopPopupTimer();
16937 item._setSelected(false);
16938 // Close all popups that are open and descendants of this menu
16939 var itemPopup = item.popup;
16941 this._stopPendingCloseTimer(itemPopup);
16942 itemPopup._pendingClose_timer = setTimeout(function(){
16943 itemPopup._pendingClose_timer = null;
16944 if(itemPopup.parentMenu){
16945 itemPopup.parentMenu.currentPopup = null;
16947 dijit.popup.close(itemPopup); // this calls onClose
16948 }, this.popupDelay);
16952 onItemUnhover: function(/*MenuItem*/ item){
16954 // Callback fires when mouse exits a MenuItem
16959 this._stopPopupTimer();
16961 if(this._hoveredChild == item){ this._hoveredChild = null; }
16964 _stopPopupTimer: function(){
16966 // Cancels the popup timer because the user has stop hovering
16967 // on the MenuItem, etc.
16970 if(this.hover_timer){
16971 clearTimeout(this.hover_timer);
16972 this.hover_timer = null;
16976 _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){
16978 // Cancels the pending-close timer because the close has been preempted
16981 if(popup._pendingClose_timer){
16982 clearTimeout(popup._pendingClose_timer);
16983 popup._pendingClose_timer = null;
16987 _stopFocusTimer: function(){
16989 // Cancels the pending-focus timer because the menu was closed before focus occured
16992 if(this._focus_timer){
16993 clearTimeout(this._focus_timer);
16994 this._focus_timer = null;
16998 _getTopMenu: function(){
17000 // Returns the top menu in this chain of Menus
17003 for(var top=this; top.parentMenu; top=top.parentMenu);
17007 onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){
17009 // Handle clicks on an item.
17013 // this can't be done in _onFocus since the _onFocus events occurs asynchronously
17014 if(typeof this.isShowingNow == 'undefined'){ // non-popup menu
17015 this._markActive();
17018 this.focusChild(item);
17020 if(item.disabled){ return false; }
17025 // before calling user defined handler, close hierarchy of menus
17026 // and restore focus to place it was when menu was opened
17029 // user defined handler for click
17034 _openPopup: function(){
17036 // Open the popup to the side of/underneath the current menu item
17040 this._stopPopupTimer();
17041 var from_item = this.focusedChild;
17042 if(!from_item){ return; } // the focused child lost focus since the timer was started
17043 var popup = from_item.popup;
17044 if(popup.isShowingNow){ return; }
17045 if(this.currentPopup){
17046 this._stopPendingCloseTimer(this.currentPopup);
17047 dijit.popup.close(this.currentPopup);
17049 popup.parentMenu = this;
17050 popup.from_item = from_item; // helps finding the parent item that should be focused for this popup
17055 around: from_item.domNode,
17056 orient: this._orient || (this.isLeftToRight() ?
17057 {'TR': 'TL', 'TL': 'TR', 'BR': 'BL', 'BL': 'BR'} :
17058 {'TL': 'TR', 'TR': 'TL', 'BL': 'BR', 'BR': 'BL'}),
17059 onCancel: function(){ // called when the child menu is canceled
17060 // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus
17061 // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro)
17062 self.focusChild(from_item); // put focus back on my node
17063 self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved)
17064 from_item._setSelected(true); // oops, _cleanUp() deselected the item
17065 self.focusedChild = from_item; // and unset focusedChild
17067 onExecute: dojo.hitch(this, "_cleanUp
")
17070 this.currentPopup = popup;
17071 // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items
17072 popup.connect(popup.domNode, "onmouseenter
", dojo.hitch(self, "_onPopupHover
")); // cleaned up when the popped-up widget is destroyed on close
17075 // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar),
17076 // if the cursor happens to collide with the popup, it will generate an onmouseover event
17077 // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that
17078 // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742)
17079 popup._focus_timer = setTimeout(dojo.hitch(popup, function(){
17080 this._focus_timer = null;
17086 _markActive: function(){
17088 // Mark this menu's state as active.
17089 // Called when this Menu gets focus from:
17090 // 1) clicking it (mouse or via space/arrow key)
17091 // 2) being opened by a parent menu.
17092 // This is not called just from mouse hover.
17093 // Focusing a menu via TAB does NOT automatically set isActive
17094 // since TAB is a navigation operation and not a selection one.
17095 // For Windows apps, pressing the ALT key focuses the menubar
17096 // menus (similar to TAB navigation) but the menu is not active
17097 // (ie no dropdown) until an item is clicked.
17098 this.isActive = true;
17099 dojo.replaceClass(this.domNode, "dijitMenuActive
", "dijitMenuPassive
");
17102 onOpen: function(/*Event*/ e){
17104 // Callback when this menu is opened.
17105 // This is called by the popup manager as notification that the menu
17110 this.isShowingNow = true;
17111 this._markActive();
17114 _markInactive: function(){
17116 // Mark this menu's state as inactive.
17117 this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here
17118 dojo.replaceClass(this.domNode, "dijitMenuPassive
", "dijitMenuActive
");
17121 onClose: function(){
17123 // Callback when this menu is closed.
17124 // This is called by the popup manager as notification that the menu
17129 this._stopFocusTimer();
17130 this._markInactive();
17131 this.isShowingNow = false;
17132 this.parentMenu = null;
17135 _closeChild: function(){
17137 // Called when submenu is clicked or focus is lost. Close hierarchy of menus.
17140 this._stopPopupTimer();
17142 var fromItem = this.focusedChild && this.focusedChild.from_item;
17144 if(this.currentPopup){
17145 // If focus is on my child menu then move focus to me,
17146 // because IE doesn't like it when you display:none a node with focus
17147 if(dijit._curFocus && dojo.isDescendant(dijit._curFocus, this.currentPopup.domNode)){
17148 this.focusedChild.focusNode.focus();
17150 // Close all popups that are open and descendants of this menu
17151 dijit.popup.close(this.currentPopup);
17152 this.currentPopup = null;
17155 if(this.focusedChild){ // unhighlight the focused item
17156 this.focusedChild._setSelected(false);
17157 this.focusedChild._onUnhover();
17158 this.focusedChild = null;
17162 _onItemFocus: function(/*MenuItem*/ item){
17164 // Called when child of this Menu gets focus from:
17166 // 2) tabbing into it
17167 // 3) being opened by a parent menu.
17168 // This is not called just from mouse hover.
17169 if(this._hoveredChild && this._hoveredChild != item){
17170 this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection
17174 _onBlur: function(){
17176 // Called when focus is moved away from this Menu and it's submenus.
17180 this.inherited(arguments);
17183 _cleanUp: function(){
17185 // Called when the user is done with this menu. Closes hierarchy of menus.
17189 this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close
17190 if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose
17191 this._markInactive();
17196 dojo.declare("dijit
.Menu
",
17200 // A context menu you can assign to multiple elements
17202 // TODO: most of the code in here is just for context menu (right-click menu)
17203 // support. In retrospect that should have been a separate class (dijit.ContextMenu).
17204 // Split them for 2.0
17206 constructor: function(){
17207 this._bindings = [];
17210 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"),
17212 baseClass: "dijitMenu
",
17214 // targetNodeIds: [const] String[]
17215 // Array of dom node ids of nodes to attach to.
17216 // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes.
17219 // contextMenuForWindow: [const] Boolean
17220 // If true, right clicking anywhere on the window will cause this context menu to open.
17221 // If false, must specify targetNodeIds.
17222 contextMenuForWindow: false,
17224 // leftClickToOpen: [const] Boolean
17225 // If true, menu will open on left click instead of right click, similiar to a file menu.
17226 leftClickToOpen: false,
17228 // refocus: Boolean
17229 // When this menu closes, re-focus the element which had focus before it was opened.
17232 postCreate: function(){
17233 if(this.contextMenuForWindow){
17234 this.bindDomNode(dojo.body());
17236 // TODO: should have _setTargetNodeIds() method to handle initialization and a possible
17237 // later set('targetNodeIds', ...) call. There's also a problem that targetNodeIds[]
17238 // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610)
17239 dojo.forEach(this.targetNodeIds, this.bindDomNode, this);
17241 var k = dojo.keys, l = this.isLeftToRight();
17242 this._openSubMenuKey = l ? k.RIGHT_ARROW : k.LEFT_ARROW;
17243 this._closeSubMenuKey = l ? k.LEFT_ARROW : k.RIGHT_ARROW;
17244 this.connectKeyNavHandlers([k.UP_ARROW], [k.DOWN_ARROW]);
17247 _onKeyPress: function(/*Event*/ evt){
17249 // Handle keyboard based menu navigation.
17253 if(evt.ctrlKey || evt.altKey){ return; }
17255 switch(evt.charOrCode){
17256 case this._openSubMenuKey:
17257 this._moveToPopup(evt);
17258 dojo.stopEvent(evt);
17260 case this._closeSubMenuKey:
17261 if(this.parentMenu){
17262 if(this.parentMenu._isMenuBar){
17263 this.parentMenu.focusPrev();
17265 this.onCancel(false);
17268 dojo.stopEvent(evt);
17274 // thanks burstlib!
17275 _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){
17277 // Returns the window reference of the passed iframe
17280 var win = dojo.window.get(this._iframeContentDocument(iframe_el)) ||
17281 // Moz. TODO: is this available when defaultView isn't?
17282 this._iframeContentDocument(iframe_el)['__parent__'] ||
17283 (iframe_el.name && dojo.doc.frames[iframe_el.name]) || null;
17284 return win; // Window
17287 _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){
17289 // Returns a reference to the document object inside iframe_el
17292 var doc = iframe_el.contentDocument // W3
17293 || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE
17294 || (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document)
17296 return doc; // HTMLDocument
17299 bindDomNode: function(/*String|DomNode*/ node){
17301 // Attach menu to given node
17302 node = dojo.byId(node);
17304 var cn; // Connect node
17306 // Support context menus on iframes. Rather than binding to the iframe itself we need
17307 // to bind to the <body> node inside the iframe.
17308 if(node.tagName.toLowerCase() == "iframe
"){
17310 win = this._iframeContentWindow(iframe);
17311 cn = dojo.withGlobal(win, dojo.body);
17314 // To capture these events at the top level, attach to <html>, not <body>.
17315 // Otherwise right-click context menu just doesn't work.
17316 cn = (node == dojo.body() ? dojo.doc.documentElement : node);
17320 // "binding
" is the object to track our connection to the node (ie, the parameter to bindDomNode())
17326 // Save info about binding in _bindings[], and make node itself record index(+1) into
17327 // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may
17328 // start with a number, which fails on FF/safari.
17329 dojo.attr(node, "_dijitMenu
" + this.id, this._bindings.push(binding));
17331 // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished
17332 // loading yet, in which case we need to wait for the onload event first, and then connect
17333 // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so
17334 // we need to monitor keyboard events in addition to the oncontextmenu event.
17335 var doConnects = dojo.hitch(this, function(cn){
17337 // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu,
17338 // rather than shift-F10?
17339 dojo.connect(cn, this.leftClickToOpen ? "onclick
" : "oncontextmenu
", this, function(evt){
17340 // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler
17341 dojo.stopEvent(evt);
17342 this._scheduleOpen(evt.target, iframe, {x: evt.pageX, y: evt.pageY});
17344 dojo.connect(cn, "onkeydown
", this, function(evt){
17345 if(evt.shiftKey && evt.keyCode == dojo.keys.F10){
17346 dojo.stopEvent(evt);
17347 this._scheduleOpen(evt.target, iframe); // no coords - open near target node
17352 binding.connects = cn ? doConnects(cn) : [];
17355 // Setup handler to [re]bind to the iframe when the contents are initially loaded,
17356 // and every time the contents change.
17357 // Need to do this b/c we are actually binding to the iframe's <body> node.
17358 // Note: can't use dojo.connect(), see #9609.
17360 binding.onloadHandler = dojo.hitch(this, function(){
17361 // want to remove old connections, but IE throws exceptions when trying to
17362 // access the <body> node because it's already gone, or at least in a state of limbo
17364 var win = this._iframeContentWindow(iframe);
17365 cn = dojo.withGlobal(win, dojo.body);
17366 binding.connects = doConnects(cn);
17368 if(iframe.addEventListener){
17369 iframe.addEventListener("load
", binding.onloadHandler, false);
17371 iframe.attachEvent("onload
", binding.onloadHandler);
17376 unBindDomNode: function(/*String|DomNode*/ nodeName){
17378 // Detach menu from given node
17382 node = dojo.byId(nodeName);
17384 // On IE the dojo.byId() call will get an exception if the attach point was
17385 // the <body> node of an <iframe> that has since been reloaded (and thus the
17386 // <body> node is in a limbo state of destruction.
17390 // node["_dijitMenu
" + this.id] contains index(+1) into my _bindings[] array
17391 var attrName = "_dijitMenu
" + this.id;
17392 if(node && dojo.hasAttr(node, attrName)){
17393 var bid = dojo.attr(node, attrName)-1, b = this._bindings[bid];
17394 dojo.forEach(b.connects, dojo.disconnect);
17396 // Remove listener for iframe onload events
17397 var iframe = b.iframe;
17399 if(iframe.removeEventListener){
17400 iframe.removeEventListener("load
", b.onloadHandler, false);
17402 iframe.detachEvent("onload
", b.onloadHandler);
17406 dojo.removeAttr(node, attrName);
17407 delete this._bindings[bid];
17411 _scheduleOpen: function(/*DomNode?*/ target, /*DomNode?*/ iframe, /*Object?*/ coords){
17413 // Set timer to display myself. Using a timer rather than displaying immediately solves
17416 // 1. IE: without the delay, focus work in "open
" causes the system
17417 // context menu to appear in spite of stopEvent.
17419 // 2. Avoid double-shows on linux, where shift-F10 generates an oncontextmenu event
17420 // even after a dojo.stopEvent(e). (Shift-F10 on windows doesn't generate the
17421 // oncontextmenu event.)
17423 if(!this._openTimer){
17424 this._openTimer = setTimeout(dojo.hitch(this, function(){
17425 delete this._openTimer;
17435 _openMyself: function(args){
17437 // Internal function for opening myself when the user does a right-click or something similar.
17439 // This is an Object containing:
17441 // The node that is being clicked
17443 // If an <iframe> is being clicked, iframe points to that iframe
17445 // Put menu at specified x/y position in viewport, or if iframe is
17446 // specified, then relative to iframe.
17448 // _openMyself() formerly took the event object, and since various code references
17449 // evt.target (after connecting to _openMyself()), using an Object for parameters
17450 // (so that old code still works).
17452 var target = args.target,
17453 iframe = args.iframe,
17454 coords = args.coords;
17456 // Get coordinates to open menu, either at specified (mouse) position or (if triggered via keyboard)
17457 // then near the node the menu is assigned to.
17460 // Specified coordinates are on <body> node of an <iframe>, convert to match main document
17461 var od = target.ownerDocument,
17462 ifc = dojo.position(iframe, true),
17463 win = this._iframeContentWindow(iframe),
17464 scroll = dojo.withGlobal(win, "_docScroll
", dojo);
17466 var cs = dojo.getComputedStyle(iframe),
17467 tp = dojo._toPixelValue,
17468 left = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingLeft)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderLeftWidth) : 0),
17469 top = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingTop)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderTopWidth) : 0);
17471 coords.x += ifc.x + left - scroll.x;
17472 coords.y += ifc.y + top - scroll.y;
17475 coords = dojo.position(target, true);
17481 var savedFocus = dijit.getFocus(this);
17482 function closeAndRestoreFocus(){
17483 // user has clicked on a menu or popup
17485 dijit.focus(savedFocus);
17487 dijit.popup.close(self);
17493 onExecute: closeAndRestoreFocus,
17494 onCancel: closeAndRestoreFocus,
17495 orient: this.isLeftToRight() ? 'L' : 'R'
17499 this._onBlur = function(){
17500 this.inherited('_onBlur', arguments);
17501 // Usually the parent closes the child widget but if this is a context
17502 // menu then there is no parent
17503 dijit.popup.close(this);
17504 // don't try to restore focus; user has clicked another part of the screen
17505 // and set focus there
17509 uninitialize: function(){
17510 dojo.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this);
17511 this.inherited(arguments);
17518 if(!dojo._hasResource["dijit
.form
.Select
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
17519 dojo._hasResource["dijit
.form
.Select
"] = true;
17520 dojo.provide("dijit
.form
.Select
");
17528 dojo.declare("dijit
.form
._SelectMenu
", dijit.Menu, {
17530 // An internally-used menu for dropdown that allows us a vertical scrollbar
17531 buildRendering: function(){
17533 // Stub in our own changes, so that our domNode is not a table
17534 // otherwise, we won't respond correctly to heights/overflows
17535 this.inherited(arguments);
17536 var o = (this.menuTableNode = this.domNode);
17537 var n = (this.domNode = dojo.create("div
", {style: {overflowX: "hidden
", overflowY: "scroll
"}}));
17539 o.parentNode.replaceChild(n, o);
17541 dojo.removeClass(o, "dijitMenuTable
");
17542 n.className = o.className + " dijitSelectMenu
";
17543 o.className = "dijitReset dijitMenuTable
";
17544 dijit.setWaiRole(o,"listbox
");
17545 dijit.setWaiRole(n,"presentation
");
17549 postCreate: function(){
17551 // stop mousemove from selecting text on IE to be consistent with other browsers
17553 this.inherited(arguments);
17555 this.connect(this.domNode, "onmousemove
", dojo.stopEvent);
17558 resize: function(/*Object*/ mb){
17560 // Overridden so that we are able to handle resizing our
17561 // internal widget. Note that this is not a "full
" resize
17562 // implementation - it only works correctly if you pass it a
17566 // The margin box to set this dropdown to.
17568 dojo.marginBox(this.domNode, mb);
17570 // We've explicitly set the wrapper <div>'s width, so set <table> width to match.
17571 // 100% is safer than a pixel value because there may be a scroll bar with
17572 // browser/OS specific width.
17573 this.menuTableNode.style.width = "100%";
17579 dojo.declare("dijit
.form
.Select
", [dijit.form._FormSelectWidget, dijit._HasDropDown], {
17581 // This is a "styleable
" select box - it is basically a DropDownButton which
17582 // can take a <select> as its input.
17584 baseClass: "dijitSelect
",
17586 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"),
17588 // attributeMap: Object
17589 // Add in our style to be applied to the focus node
17590 attributeMap: dojo.mixin(dojo.clone(dijit.form._FormSelectWidget.prototype.attributeMap),{style:"tableNode
"}),
17592 // required: Boolean
17593 // Can be true or false, default is false.
17597 // Shows current state (ie, validation result) of input (Normal, Warning, or Error)
17601 // Currently displayed error/prompt message
17604 // tooltipPosition: String[]
17605 // See description of dijit.Tooltip.defaultPosition for details on this parameter.
17606 tooltipPosition: [],
17608 // emptyLabel: string
17609 // What to display in an "empty
" dropdown
17610 emptyLabel: " 
;",
17612 // _isLoaded: Boolean
17613 // Whether or not we have been loaded
17616 // _childrenLoaded: Boolean
17617 // Whether or not our children have been loaded
17618 _childrenLoaded: false,
17620 _fillContent: function(){
17622 // Set the value to be the first, or the selected index
17623 this.inherited(arguments);
17624 // set value from selected option
17625 if(this.options.length && !this.value && this.srcNodeRef){
17626 var si = this.srcNodeRef.selectedIndex || 0; // || 0 needed for when srcNodeRef is not a SELECT
17627 this.value = this.options[si >= 0 ? si : 0].value;
17629 // Create the dropDown widget
17630 this.dropDown = new dijit.form._SelectMenu({id: this.id + "_menu
"});
17631 dojo.addClass(this.dropDown.domNode, this.baseClass + "Menu
");
17634 _getMenuItemForOption: function(/*dijit.form.__SelectOption*/ option){
17636 // For the given option, return the menu item that should be
17637 // used to display it. This can be overridden as needed
17638 if(!option.value && !option.label){
17639 // We are a separator (no label set for it)
17640 return new dijit.MenuSeparator();
17642 // Just a regular menu option
17643 var click = dojo.hitch(this, "_setValueAttr
", option);
17644 var item = new dijit.MenuItem({
17646 label: option.label || this.emptyLabel,
17648 disabled: option.disabled || false
17650 dijit.setWaiRole(item.focusNode, "listitem
");
17655 _addOptionItem: function(/*dijit.form.__SelectOption*/ option){
17657 // For the given option, add an option to our dropdown.
17658 // If the option doesn't have a value, then a separator is added
17661 this.dropDown.addChild(this._getMenuItemForOption(option));
17665 _getChildren: function(){
17666 if(!this.dropDown){
17669 return this.dropDown.getChildren();
17672 _loadChildren: function(/*Boolean*/ loadMenuItems){
17674 // Resets the menu and the length attribute of the button - and
17675 // ensures that the label is appropriately set.
17676 // loadMenuItems: Boolean
17677 // actually loads the child menu items - we only do this when we are
17678 // populating for showing the dropdown.
17680 if(loadMenuItems === true){
17681 // this.inherited destroys this.dropDown's child widgets (MenuItems).
17682 // Avoid this.dropDown (Menu widget) having a pointer to a destroyed widget (which will cause
17683 // issues later in _setSelected). (see #10296)
17685 delete this.dropDown.focusedChild;
17687 if(this.options.length){
17688 this.inherited(arguments);
17690 // Drop down menu is blank but add one blank entry just so something appears on the screen
17691 // to let users know that they are no choices (mimicing native select behavior)
17692 dojo.forEach(this._getChildren(), function(child){ child.destroyRecursive(); });
17693 var item = new dijit.MenuItem({label: " 
;"});
17694 this.dropDown.addChild(item);
17697 this._updateSelection();
17700 this._isLoaded = false;
17701 this._childrenLoaded = true;
17703 if(!this._loadingStore){
17704 // Don't call this if we are loading - since we will handle it later
17705 this._setValueAttr(this.value);
17709 _setValueAttr: function(value){
17710 this.inherited(arguments);
17711 dojo.attr(this.valueNode, "value
", this.get("value
"));
17714 _setDisplay: function(/*String*/ newDisplay){
17716 // sets the display for the given value (or values)
17717 var lbl = newDisplay || this.emptyLabel;
17718 this.containerNode.innerHTML = '<span class="dijitReset dijitInline
' + this.baseClass + 'Label
">' + lbl + '</span>';
17719 dijit.setWaiState(this.focusNode, "valuetext
", lbl);
17722 validate: function(/*Boolean*/ isFocused){
17724 // Called by oninit, onblur, and onkeypress.
17726 // Show missing or invalid messages if appropriate, and highlight textbox field.
17727 // Used when a select is initially set to no value and the user is required to
17730 var isValid = this.isValid(isFocused);
17731 this._set("state
", isValid ? "" : "Error
");
17732 dijit.setWaiState(this.focusNode, "invalid
", isValid ? "false" : "true");
17733 var message = isValid ? "" : this._missingMsg;
17734 if(this.message !== message){
17735 this._set("message
", message);
17736 dijit.hideTooltip(this.domNode);
17738 dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
17744 isValid: function(/*Boolean*/ isFocused){
17746 // Whether or not this is a valid value. The only way a Select
17747 // can be invalid is when it's required but nothing is selected.
17748 return (!this.required || this.value === 0 || !(/^\s*$/.test(this.value || ""))); // handle value is null or undefined
17753 // Overridden so that the state will be cleared.
17754 this.inherited(arguments);
17755 dijit.hideTooltip(this.domNode);
17756 this._set("state
", "");
17757 this._set("message
", "")
17760 postMixInProperties: function(){
17762 // set the missing message
17763 this.inherited(arguments);
17764 this._missingMsg = dojo.i18n.getLocalization("dijit
.form
", "validate
",
17765 this.lang).missingMessage;
17768 postCreate: function(){
17770 // stop mousemove from selecting text on IE to be consistent with other browsers
17772 this.inherited(arguments);
17774 this.connect(this.domNode, "onmousemove
", dojo.stopEvent);
17777 _setStyleAttr: function(/*String||Object*/ value){
17778 this.inherited(arguments);
17779 dojo.toggleClass(this.domNode, this.baseClass + "FixedWidth
", !!this.tableNode.style.width);
17782 isLoaded: function(){
17783 return this._isLoaded;
17786 loadDropDown: function(/*Function*/ loadCallback){
17788 // populates the menu
17789 this._loadChildren(true);
17790 this._isLoaded = true;
17794 closeDropDown: function(){
17795 // overriding _HasDropDown.closeDropDown()
17796 this.inherited(arguments);
17798 if(this.dropDown && this.dropDown.menuTableNode){
17799 // Erase possible width: 100% setting from _SelectMenu.resize().
17800 // Leaving it would interfere with the next openDropDown() call, which
17801 // queries the natural size of the drop down.
17802 this.dropDown.menuTableNode.style.width = "";
17806 uninitialize: function(preserveDom){
17807 if(this.dropDown && !this.dropDown._destroyed){
17808 this.dropDown.destroyRecursive(preserveDom);
17809 delete this.dropDown;
17811 this.inherited(arguments);
17817 if(!dojo._hasResource["dijit
.form
.SimpleTextarea
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
17818 dojo._hasResource["dijit
.form
.SimpleTextarea
"] = true;
17819 dojo.provide("dijit
.form
.SimpleTextarea
");
17823 dojo.declare("dijit
.form
.SimpleTextarea
",
17824 dijit.form.TextBox,
17827 // A simple textarea that degrades, and responds to
17828 // minimal LayoutContainer usage, and works with dijit.form.Form.
17829 // Doesn't automatically size according to input, like Textarea.
17832 // | <textarea dojoType="dijit
.form
.SimpleTextarea
" name="foo
" value="bar
" rows=30 cols=40></textarea>
17835 // | new dijit.form.SimpleTextarea({ rows:20, cols:30 }, "foo
");
17837 baseClass: "dijitTextBox dijitTextArea
",
17839 attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, {
17840 rows:"textbox
", cols: "textbox
"
17844 // The number of rows of text.
17848 // The number of characters per line.
17851 templateString: "<textarea ${!nameAttrSetting} dojoAttachPoint
='focusNode,containerNode,textbox' autocomplete
='off'></textarea
>",
17853 postMixInProperties: function(){
17854 // Copy value from srcNodeRef, unless user specified a value explicitly (or there is no srcNodeRef)
17855 // TODO: parser will handle this in 2.0
17856 if(!this.value && this.srcNodeRef){
17857 this.value = this.srcNodeRef.value;
17859 this.inherited(arguments);
17862 buildRendering: function(){
17863 this.inherited(arguments);
17864 if(dojo.isIE && this.cols){ // attribute selectors is not supported in IE6
17865 dojo.addClass(this.textbox, "dijitTextAreaCols
");
17869 filter: function(/*String*/ value){
17870 // Override TextBox.filter to deal with newlines... specifically (IIRC) this is for IE which writes newlines
17871 // as \r\n instead of just \n
17873 value = value.replace(/\r/g,"");
17875 return this.inherited(arguments);
17878 _previousValue: "",
17879 _onInput: function(/*Event?*/ e){
17880 // Override TextBox._onInput() to enforce maxLength restriction
17881 if(this.maxLength){
17882 var maxLength = parseInt(this.maxLength);
17883 var value = this.textbox.value.replace(/\r/g,'');
17884 var overflow = value.length - maxLength;
17886 if(e){ dojo.stopEvent(e); }
17887 var textarea = this.textbox;
17888 if(textarea.selectionStart){
17889 var pos = textarea.selectionStart;
17892 cr = (this.textbox.value.substring(0,pos).match(/\r/g) || []).length;
17894 this.textbox.value = value.substring(0,pos-overflow-cr)+value.substring(pos-cr);
17895 textarea.setSelectionRange(pos-overflow, pos-overflow);
17896 }else if(dojo.doc.selection){ //IE
17898 var range = dojo.doc.selection.createRange();
17899 // delete overflow characters
17900 range.moveStart("character
", -overflow);
17906 this._previousValue = this.textbox.value;
17908 this.inherited(arguments);
17914 if(!dojo._hasResource["dijit
.InlineEditBox
"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
17915 dojo._hasResource["dijit
.InlineEditBox
"] = true;
17916 dojo.provide("dijit
.InlineEditBox
");
17925 dojo.declare("dijit
.InlineEditBox
",
17929 // An element with in-line edit capabilites
17932 // Behavior for an existing node (`<p>`, `<div>`, `<span>`, etc.) so that
17933 // when you click it, an editor shows up in place of the original
17934 // text. Optionally, Save and Cancel button are displayed below the edit widget.
17935 // When Save is clicked, the text is pulled from the edit
17936 // widget and redisplayed and the edit widget is again hidden.
17937 // By default a plain Textarea widget is used as the editor (or for
17938 // inline values a TextBox), but you can specify an editor such as
17939 // dijit.Editor (for editing HTML) or a Slider (for adjusting a number).
17940 // An edit widget must support the following API to be used:
17941 // - displayedValue or value as initialization parameter,
17942 // and available through set('displayedValue') / set('value')
17944 // - DOM-node focusNode = node containing editable text
17946 // editing: [readonly] Boolean
17947 // Is the node currently in edit mode?
17950 // autoSave: Boolean
17951 // Changing the value automatically saves it; don't have to push save button
17952 // (and save button isn't even displayed)
17955 // buttonSave: String
17956 // Save button label
17959 // buttonCancel: String
17960 // Cancel button label
17963 // renderAsHtml: Boolean
17964 // Set this to true if the specified Editor's value should be interpreted as HTML
17965 // rather than plain text (ex: `dijit.Editor`)
17966 renderAsHtml: false,
17968 // editor: String|Function
17969 // Class name (or reference to the Class) for Editor widget
17970 editor: "dijit
.form
.TextBox
",
17972 // editorWrapper: String|Function
17973 // Class name (or reference to the Class) for widget that wraps the editor widget, displaying save/cancel
17975 editorWrapper: "dijit
._InlineEditor
",
17977 // editorParams: Object
17978 // Set of parameters for editor, like {required: true}
17981 // disabled: Boolean
17982 // If true, clicking the InlineEditBox to edit it will have no effect.
17985 onChange: function(value){
17987 // Set this handler to be notified of changes to value.
17992 onCancel: function(){
17994 // Set this handler to be notified when editing is cancelled.
18000 // Width of editor. By default it's width=100% (ie, block mode).
18004 // The display value of the widget in read-only mode
18007 // noValueIndicator: [const] String
18008 // The text that gets displayed when there is no value (so that the user has a place to click to edit)
18009 noValueIndicator: dojo.isIE <= 6 ? // font-family needed on IE6 but it messes up IE8
18010 "<span style
='font-family: wingdings; text-decoration: underline;'> 
; 
; 
; 
;✍
; 
; 
; 
; 
;</span
>" :
18011 "<span style
='text-decoration: underline;'> 
; 
; 
; 
;✍
; 
; 
; 
; 
;</span
>",
18013 constructor: function(){
18015 // Sets up private arrays etc.
18018 this.editorParams = {};
18021 postMixInProperties: function(){
18022 this.inherited(arguments);
18024 // save pointer to original source node, since Widget nulls-out srcNodeRef
18025 this.displayNode = this.srcNodeRef;
18027 // connect handlers to the display node
18029 ondijitclick: "_onClick
",
18030 onmouseover: "_onMouseOver
",
18031 onmouseout: "_onMouseOut
",
18032 onfocus: "_onMouseOver
",
18033 onblur: "_onMouseOut
"
18035 for(var name in events){
18036 this.connect(this.displayNode, name, events[name]);
18038 dijit.setWaiRole(this.displayNode, "button
");
18039 if(!this.displayNode.getAttribute("tabIndex
")){
18040 this.displayNode.setAttribute("tabIndex
", 0);
18043 if(!this.value && !("value
" in this.params)){ // "" is a good value if specified directly so check params){
18044 this.value = dojo.trim(this.renderAsHtml ? this.displayNode.innerHTML :
18045 (this.displayNode.innerText||this.displayNode.textContent||""));
18048 this.displayNode.innerHTML = this.noValueIndicator;
18051 dojo.addClass(this.displayNode, 'dijitInlineEditBoxDisplayMode');
18054 setDisabled: function(/*Boolean*/ disabled){
18056 // Deprecated. Use set('disabled', ...) instead.
18059 dojo.deprecated("dijit
.InlineEditBox
.setDisabled() is deprecated
. Use
set('disabled', bool
) instead
.", "", "2.0");
18060 this.set('disabled', disabled);
18063 _setDisabledAttr: function(/*Boolean*/ disabled){
18065 // Hook to make set("disabled
", ...) work.
18066 // Set disabled state of widget.
18067 dijit.setWaiState(this.domNode, "disabled
", disabled);
18069 this.displayNode.removeAttribute("tabIndex
");
18071 this.displayNode.setAttribute("tabIndex
", 0);
18073 dojo.toggleClass(this.displayNode, "dijitInlineEditBoxDisplayModeDisabled
", disabled);
18074 this._set("disabled
", disabled);
18077 _onMouseOver: function(){
18079 // Handler for onmouseover and onfocus event.
18082 if(!this.disabled){
18083 dojo.addClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover
");
18087 _onMouseOut: function(){
18089 // Handler for onmouseout and onblur event.
18092 dojo.removeClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover
");
18095 _onClick: function(/*Event*/ e){
18097 // Handler for onclick event.
18100 if(this.disabled){ return; }
18101 if(e){ dojo.stopEvent(e); }
18102 this._onMouseOut();
18104 // Since FF gets upset if you move a node while in an event handler for that node...
18105 setTimeout(dojo.hitch(this, "edit
"), 0);
18110 // Display the editor widget in place of the original (read only) markup.
18114 if(this.disabled || this.editing){ return; }
18115 this.editing = true;
18117 // save some display node values that can be restored later
18118 this._savedPosition = dojo.style(this.displayNode, "position
") || "static";
18119 this._savedOpacity = dojo.style(this.displayNode, "opacity
") || "1";
18120 this._savedTabIndex = dojo.attr(this.displayNode, "tabIndex
") || "0";
18122 if(this.wrapperWidget){
18123 var ew = this.wrapperWidget.editWidget;
18124 ew.set("displayedValue
" in ew ? "displayedValue
" : "value
", this.value);
18126 // Placeholder for edit widget
18127 // Put place holder (and eventually editWidget) before the display node so that it's positioned correctly
18128 // when Calendar dropdown appears, which happens automatically on focus.
18129 var placeholder = dojo.create("span
", null, this.domNode, "before
");
18131 // Create the editor wrapper (the thing that holds the editor widget and the save/cancel buttons)
18132 var ewc = typeof this.editorWrapper == "string
" ? dojo.getObject(this.editorWrapper) : this.editorWrapper;
18133 this.wrapperWidget = new ewc({
18135 buttonSave: this.buttonSave,
18136 buttonCancel: this.buttonCancel,
18139 tabIndex: this._savedTabIndex,
18140 editor: this.editor,
18141 inlineEditBox: this,
18142 sourceStyle: dojo.getComputedStyle(this.displayNode),
18143 save: dojo.hitch(this, "save
"),
18144 cancel: dojo.hitch(this, "cancel
")
18146 if(!this._started){
18150 var ww = this.wrapperWidget;
18153 dijit.focus(dijit.getFocus()); // IE (at least 8) needs help with tab order changes
18155 // to avoid screen jitter, we first create the editor with position:absolute, visibility:hidden,
18156 // and then when it's finished rendering, we switch from display mode to editor
18157 // position:absolute releases screen space allocated to the display node
18158 // opacity:0 is the same as visibility:hidden but is still focusable
18159 // visiblity:hidden removes focus outline
18161 dojo.style(this.displayNode, { position: "absolute
", opacity: "0", display: "none
" }); // makes display node invisible, display style used for focus-ability
18162 dojo.style(ww.domNode, { position: this._savedPosition, visibility: "visible
", opacity: "1" });
18163 dojo.attr(this.displayNode, "tabIndex
", "-1"); // needed by WebKit for TAB from editor to skip displayNode
18165 // Replace the display widget with edit widget, leaving them both displayed for a brief time so that
18166 // focus can be shifted without incident. (browser may needs some time to render the editor.)
18167 setTimeout(dojo.hitch(this, function(){
18168 ww.focus(); // both nodes are showing, so we can switch focus safely
18169 ww._resetValue = ww.getValue();
18173 _onBlur: function(){
18175 // Called when focus moves outside the InlineEditBox.
18176 // Performs garbage collection.
18180 this.inherited(arguments);
18182 /* causes IE focus problems, see TooltipDialog_a11y.html...
18183 setTimeout(dojo.hitch(this, function(){
18184 if(this.wrapperWidget){
18185 this.wrapperWidget.destroy();
18186 delete this.wrapperWidget;
18193 destroy: function(){
18194 if(this.wrapperWidget && !this.wrapperWidget._destroyed){
18195 this.wrapperWidget.destroy();
18196 delete this.wrapperWidget;
18198 this.inherited(arguments);
18201 _showText: function(/*Boolean*/ focus){
18203 // Revert to display mode, and optionally focus on display node
18207 var ww = this.wrapperWidget;
18208 dojo.style(ww.domNode, { position: "absolute
", visibility: "hidden
", opacity: "0" }); // hide the editor from mouse/keyboard events
18209 dojo.style(this.displayNode, { position: this._savedPosition, opacity: this._savedOpacity, display: "" }); // make the original text visible
18210 dojo.attr(this.displayNode, "tabIndex
", this._savedTabIndex);
18212 dijit.focus(this.displayNode);
18216 save: function(/*Boolean*/ focus){
18218 // Save the contents of the editor and revert to display mode.
18220 // Focus on the display mode text
18224 if(this.disabled || !this.editing){ return; }
18225 this.editing = false;
18227 var ww = this.wrapperWidget;
18228 var value = ww.getValue();
18229 this.set('value', value); // display changed, formatted value
18231 this._showText(focus); // set focus as needed
18234 setValue: function(/*String*/ val){
18236 // Deprecated. Use set('value', ...) instead.
18239 dojo.deprecated("dijit
.InlineEditBox
.setValue() is deprecated
. Use
set('value', ...) instead
.", "", "2.0");
18240 return this.set("value
", val);
18243 _setValueAttr: function(/*String*/ val){
18245 // Hook to make set("value
", ...) work.
18246 // Inserts specified HTML value into this node, or an "input needed
" character if node is blank.
18248 val = dojo.trim(val);
18249 var renderVal = this.renderAsHtml ? val : val.replace(/&/gm, "&
;").replace(/</gm, "<
;").replace(/>/gm, ">
;").replace(/"/gm, """).replace(/\n/g
, "<br>");
18250 this.displayNode
.innerHTML
= renderVal
|| this.noValueIndicator
;
18251 this._set("value", val
);
18254 // tell the world that we have changed
18255 setTimeout(dojo
.hitch(this, "onChange", val
), 0); // setTimeout prevents browser freeze for long-running event handlers
18259 getValue: function(){
18261 // Deprecated. Use get('value') instead.
18264 dojo
.deprecated("dijit.InlineEditBox.getValue() is deprecated. Use get('value') instead.", "", "2.0");
18265 return this.get("value");
18268 cancel: function(/*Boolean*/ focus
){
18270 // Revert to display mode, discarding any changes made in the editor
18274 if(this.disabled
|| !this.editing
){ return; }
18275 this.editing
= false;
18277 // tell the world that we have no changes
18278 setTimeout(dojo
.hitch(this, "onCancel"), 0); // setTimeout prevents browser freeze for long-running event handlers
18280 this._showText(focus
);
18285 "dijit._InlineEditor",
18286 [dijit
._Widget
, dijit
._Templated
],
18289 // Internal widget used by InlineEditBox, displayed when in editing mode
18290 // to display the editor and maybe save/cancel buttons. Calling code should
18291 // connect to save/cancel methods to detect when editing is finished
18293 // Has mainly the same parameters as InlineEditBox, plus these values:
18296 // Set of CSS attributes of display node, to replicate in editor
18299 // Value as an HTML string or plain text string, depending on renderAsHTML flag
18301 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"),
18302 widgetsInTemplate
: true,
18304 postMixInProperties: function(){
18305 this.inherited(arguments
);
18306 this.messages
= dojo
.i18n
.getLocalization("dijit", "common", this.lang
);
18307 dojo
.forEach(["buttonSave", "buttonCancel"], function(prop
){
18308 if(!this[prop
]){ this[prop
] = this.messages
[prop
]; }
18312 buildRendering: function(){
18313 this.inherited(arguments
);
18315 // Create edit widget in place in the template
18316 var cls
= typeof this.editor
== "string" ? dojo
.getObject(this.editor
) : this.editor
;
18318 // Copy the style from the source
18319 // Don't copy ALL properties though, just the necessary/applicable ones.
18320 // wrapperStyle/destStyle code is to workaround IE bug where getComputedStyle().fontSize
18321 // is a relative value like 200%, rather than an absolute value like 24px, and
18322 // the 200% can refer *either* to a setting on the node or it's ancestor (see #11175)
18323 var srcStyle
= this.sourceStyle
,
18324 editStyle
= "line-height:" + srcStyle
.lineHeight
+ ";",
18325 destStyle
= dojo
.getComputedStyle(this.domNode
);
18326 dojo
.forEach(["Weight","Family","Size","Style"], function(prop
){
18327 var textStyle
= srcStyle
["font"+prop
],
18328 wrapperStyle
= destStyle
["font"+prop
];
18329 if(wrapperStyle
!= textStyle
){
18330 editStyle
+= "font-"+prop
+":"+srcStyle
["font"+prop
]+";";
18333 dojo
.forEach(["marginTop","marginBottom","marginLeft", "marginRight"], function(prop
){
18334 this.domNode
.style
[prop
] = srcStyle
[prop
];
18336 var width
= this.inlineEditBox
.width
;
18337 if(width
== "100%"){
18339 editStyle
+= "width:100%;";
18340 this.domNode
.style
.display
= "block";
18342 // inline-block mode
18343 editStyle
+= "width:" + (width
+ (Number(width
) == width
? "px" : "")) + ";";
18345 var editorParams
= dojo
.delegate(this.inlineEditBox
.editorParams
, {
18350 editorParams
[ "displayedValue" in cls
.prototype ? "displayedValue" : "value"] = this.value
;
18351 this.editWidget
= new cls(editorParams
, this.editorPlaceholder
);
18353 if(this.inlineEditBox
.autoSave
){
18354 // Remove the save/cancel buttons since saving is done by simply tabbing away or
18355 // selecting a value from the drop down list
18356 dojo
.destroy(this.buttonContainer
);
18360 postCreate: function(){
18361 this.inherited(arguments
);
18363 var ew
= this.editWidget
;
18365 if(this.inlineEditBox
.autoSave
){
18366 // Selecting a value from a drop down list causes an onChange event and then we save
18367 this.connect(ew
, "onChange", "_onChange");
18369 // ESC and TAB should cancel and save. Note that edit widgets do a stopEvent() on ESC key (to
18370 // prevent Dialog from closing when the user just wants to revert the value in the edit widget),
18371 // so this is the only way we can see the key press event.
18372 this.connect(ew
, "onKeyPress", "_onKeyPress");
18374 // If possible, enable/disable save button based on whether the user has changed the value
18375 if("intermediateChanges" in ew
){
18376 ew
.set("intermediateChanges", true);
18377 this.connect(ew
, "onChange", "_onIntermediateChange");
18378 this.saveButton
.set("disabled", true);
18383 _onIntermediateChange: function(val
){
18385 // Called for editor widgets that support the intermediateChanges=true flag as a way
18386 // to detect when to enable/disabled the save button
18387 this.saveButton
.set("disabled", (this.getValue() == this._resetValue
) || !this.enableSave());
18390 destroy: function(){
18391 this.editWidget
.destroy(true); // let the parent wrapper widget clean up the DOM
18392 this.inherited(arguments
);
18395 getValue: function(){
18397 // Return the [display] value of the edit widget
18398 var ew
= this.editWidget
;
18399 return String(ew
.get("displayedValue" in ew
? "displayedValue" : "value"));
18402 _onKeyPress: function(e
){
18404 // Handler for keypress in the edit box in autoSave mode.
18406 // For autoSave widgets, if Esc/Enter, call cancel/save.
18410 if(this.inlineEditBox
.autoSave
&& this.inlineEditBox
.editing
){
18411 if(e
.altKey
|| e
.ctrlKey
){ return; }
18412 // If Enter/Esc pressed, treat as save/cancel.
18413 if(e
.charOrCode
== dojo
.keys
.ESCAPE
){
18415 this.cancel(true); // sets editing=false which short-circuits _onBlur processing
18416 }else if(e
.charOrCode
== dojo
.keys
.ENTER
&& e
.target
.tagName
== "INPUT"){
18418 this._onChange(); // fire _onBlur and then save
18421 // _onBlur will handle TAB automatically by allowing
18422 // the TAB to change focus before we mess with the DOM: #6227
18423 // Expounding by request:
18424 // The current focus is on the edit widget input field.
18425 // save() will hide and destroy this widget.
18426 // We want the focus to jump from the currently hidden
18427 // displayNode, but since it's hidden, it's impossible to
18428 // unhide it, focus it, and then have the browser focus
18429 // away from it to the next focusable element since each
18430 // of these events is asynchronous and the focus-to-next-element
18431 // is already queued.
18432 // So we allow the browser time to unqueue the move-focus event
18433 // before we do all the hide/show stuff.
18437 _onBlur: function(){
18439 // Called when focus moves outside the editor
18443 this.inherited(arguments
);
18444 if(this.inlineEditBox
.autoSave
&& this.inlineEditBox
.editing
){
18445 if(this.getValue() == this._resetValue
){
18446 this.cancel(false);
18447 }else if(this.enableSave()){
18453 _onChange: function(){
18455 // Called when the underlying widget fires an onChange event,
18456 // such as when the user selects a value from the drop down list of a ComboBox,
18457 // which means that the user has finished entering the value and we should save.
18461 if(this.inlineEditBox
.autoSave
&& this.inlineEditBox
.editing
&& this.enableSave()){
18462 dojo
.style(this.inlineEditBox
.displayNode
, { display
: "" });
18463 dijit
.focus(this.inlineEditBox
.displayNode
); // fires _onBlur which will save the formatted value
18467 enableSave: function(){
18469 // User overridable function returning a Boolean to indicate
18470 // if the Save button should be enabled or not - usually due to invalid conditions
18474 this.editWidget
.isValid
18475 ? this.editWidget
.isValid()
18482 // Focus the edit widget.
18486 this.editWidget
.focus();
18487 setTimeout(dojo
.hitch(this, function(){
18488 if(this.editWidget
.focusNode
&& this.editWidget
.focusNode
.tagName
== "INPUT"){
18489 dijit
.selectInputText(this.editWidget
.focusNode
);
18497 if(!dojo
._hasResource
["dojo.cookie"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
18498 dojo
._hasResource
["dojo.cookie"] = true;
18499 dojo
.provide("dojo.cookie");
18504 dojo.__cookieProps = function(){
18505 // expires: Date|String|Number?
18506 // If a number, the number of days from today at which the cookie
18507 // will expire. If a date, the date past which the cookie will expire.
18508 // If expires is in the past, the cookie will be deleted.
18509 // If expires is omitted or is 0, the cookie will expire when the browser closes. << FIXME: 0 seems to disappear right away? FF3.
18511 // The path to use for the cookie.
18513 // The domain to use for the cookie.
18514 // secure: Boolean?
18515 // Whether to only send the cookie on secure connections
18516 this.expires = expires;
18518 this.domain = domain;
18519 this.secure = secure;
18524 dojo
.cookie = function(/*String*/name
, /*String?*/value
, /*dojo.__cookieProps?*/props
){
18526 // Get or set a cookie.
18528 // If one argument is passed, returns the value of the cookie
18529 // For two or more arguments, acts as a setter.
18531 // Name of the cookie
18533 // Value for the cookie
18535 // Properties for the cookie
18537 // set a cookie with the JSON-serialized contents of an object which
18538 // will expire 5 days from now:
18539 // | dojo.cookie("configObj", dojo.toJson(config), { expires: 5 });
18542 // de-serialize a cookie back into a JavaScript object:
18543 // | var config = dojo.fromJson(dojo.cookie("configObj"));
18546 // delete a cookie:
18547 // | dojo.cookie("configObj", null, {expires: -1});
18548 var c
= document
.cookie
;
18549 if(arguments
.length
== 1){
18550 var matches
= c
.match(new RegExp("(?:^|; )" + dojo
.regexp
.escapeString(name
) + "=([^;]*)"));
18551 return matches
? decodeURIComponent(matches
[1]) : undefined; // String or undefined
18553 props
= props
|| {};
18554 // FIXME: expires=0 seems to disappear right away, not on close? (FF3) Change docs?
18555 var exp
= props
.expires
;
18556 if(typeof exp
== "number"){
18557 var d
= new Date();
18558 d
.setTime(d
.getTime() + exp
*24*60*60*1000);
18559 exp
= props
.expires
= d
;
18561 if(exp
&& exp
.toUTCString
){ props
.expires
= exp
.toUTCString(); }
18563 value
= encodeURIComponent(value
);
18564 var updatedCookie
= name
+ "=" + value
, propName
;
18565 for(propName
in props
){
18566 updatedCookie
+= "; " + propName
;
18567 var propValue
= props
[propName
];
18568 if(propValue
!== true){ updatedCookie
+= "=" + propValue
; }
18570 document
.cookie
= updatedCookie
;
18574 dojo
.cookie
.isSupported = function(){
18576 // Use to determine if the current browser supports cookies or not.
18578 // Returns true if user allows cookies.
18579 // Returns false if user doesn't allow cookies.
18581 if(!("cookieEnabled" in navigator
)){
18582 this("__djCookieTest__", "CookiesAllowed");
18583 navigator
.cookieEnabled
= this("__djCookieTest__") == "CookiesAllowed";
18584 if(navigator
.cookieEnabled
){
18585 this("__djCookieTest__", "", {expires
: -1});
18588 return navigator
.cookieEnabled
;
18593 if(!dojo
._hasResource
["dijit.layout.StackController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
18594 dojo
._hasResource
["dijit.layout.StackController"] = true;
18595 dojo
.provide("dijit.layout.StackController");
18604 "dijit.layout.StackController",
18605 [dijit
._Widget
, dijit
._Templated
, dijit
._Container
],
18608 // Set of buttons to select a page in a page list.
18610 // Monitors the specified StackContainer, and whenever a page is
18611 // added, deleted, or selected, updates itself accordingly.
18613 templateString
: "<span role='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>",
18615 // containerId: [const] String
18616 // The id of the page container that I point to
18619 // buttonWidget: [const] String
18620 // The name of the button widget to create to correspond to each page
18621 buttonWidget
: "dijit.layout._StackButton",
18623 constructor: function(){
18624 this.pane2button
= {}; // mapping from pane id to buttons
18625 this.pane2connects
= {}; // mapping from pane id to this.connect() handles
18626 this.pane2watches
= {}; // mapping from pane id to watch() handles
18629 buildRendering: function(){
18630 this.inherited(arguments
);
18631 dijit
.setWaiRole(this.domNode
, "tablist"); // TODO: unneeded? it's in template above.
18634 postCreate: function(){
18635 this.inherited(arguments
);
18637 // Listen to notifications from StackContainer
18638 this.subscribe(this.containerId
+"-startup", "onStartup");
18639 this.subscribe(this.containerId
+"-addChild", "onAddChild");
18640 this.subscribe(this.containerId
+"-removeChild", "onRemoveChild");
18641 this.subscribe(this.containerId
+"-selectChild", "onSelectChild");
18642 this.subscribe(this.containerId
+"-containerKeyPress", "onContainerKeyPress");
18645 onStartup: function(/*Object*/ info
){
18647 // Called after StackContainer has finished initializing
18650 dojo
.forEach(info
.children
, this.onAddChild
, this);
18652 // Show button corresponding to selected pane (unless selected
18653 // is null because there are no panes)
18654 this.onSelectChild(info
.selected
);
18658 destroy: function(){
18659 for(var pane
in this.pane2button
){
18660 this.onRemoveChild(dijit
.byId(pane
));
18662 this.inherited(arguments
);
18665 onAddChild: function(/*dijit._Widget*/ page
, /*Integer?*/ insertIndex
){
18667 // Called whenever a page is added to the container.
18668 // Create button corresponding to the page.
18672 // create an instance of the button widget
18673 var cls
= dojo
.getObject(this.buttonWidget
);
18674 var button
= new cls({
18675 id
: this.id
+ "_" + page
.id
,
18679 showLabel
: page
.showTitle
,
18680 iconClass
: page
.iconClass
,
18681 closeButton
: page
.closable
,
18682 title
: page
.tooltip
18684 dijit
.setWaiState(button
.focusNode
,"selected", "false");
18687 // map from page attribute to corresponding tab button attribute
18688 var pageAttrList
= ["title", "showTitle", "iconClass", "closable", "tooltip"],
18689 buttonAttrList
= ["label", "showLabel", "iconClass", "closeButton", "title"];
18691 // watch() so events like page title changes are reflected in tab button
18692 this.pane2watches
[page
.id
] = dojo
.map(pageAttrList
, function(pageAttr
, idx
){
18693 return page
.watch(pageAttr
, function(name
, oldVal
, newVal
){
18694 button
.set(buttonAttrList
[idx
], newVal
);
18698 // connections so that clicking a tab button selects the corresponding page
18699 this.pane2connects
[page
.id
] = [
18700 this.connect(button
, 'onClick', dojo
.hitch(this,"onButtonClick", page
)),
18701 this.connect(button
, 'onClickCloseButton', dojo
.hitch(this,"onCloseButtonClick", page
))
18704 this.addChild(button
, insertIndex
);
18705 this.pane2button
[page
.id
] = button
;
18706 page
.controlButton
= button
; // this value might be overwritten if two tabs point to same container
18707 if(!this._currentChild
){ // put the first child into the tab order
18708 button
.focusNode
.setAttribute("tabIndex", "0");
18709 dijit
.setWaiState(button
.focusNode
, "selected", "true");
18710 this._currentChild
= page
;
18712 // make sure all tabs have the same length
18713 if(!this.isLeftToRight() && dojo
.isIE
&& this._rectifyRtlTabList
){
18714 this._rectifyRtlTabList();
18718 onRemoveChild: function(/*dijit._Widget*/ page
){
18720 // Called whenever a page is removed from the container.
18721 // Remove the button corresponding to the page.
18725 if(this._currentChild
=== page
){ this._currentChild
= null; }
18727 // disconnect/unwatch connections/watches related to page being removed
18728 dojo
.forEach(this.pane2connects
[page
.id
], dojo
.hitch(this, "disconnect"));
18729 delete this.pane2connects
[page
.id
];
18730 dojo
.forEach(this.pane2watches
[page
.id
], function(w
){ w
.unwatch(); });
18731 delete this.pane2watches
[page
.id
];
18733 var button
= this.pane2button
[page
.id
];
18735 this.removeChild(button
);
18736 delete this.pane2button
[page
.id
];
18739 delete page
.controlButton
;
18742 onSelectChild: function(/*dijit._Widget*/ page
){
18744 // Called when a page has been selected in the StackContainer, either by me or by another StackController
18748 if(!page
){ return; }
18750 if(this._currentChild
){
18751 var oldButton
=this.pane2button
[this._currentChild
.id
];
18752 oldButton
.set('checked', false);
18753 dijit
.setWaiState(oldButton
.focusNode
, "selected", "false");
18754 oldButton
.focusNode
.setAttribute("tabIndex", "-1");
18757 var newButton
=this.pane2button
[page
.id
];
18758 newButton
.set('checked', true);
18759 dijit
.setWaiState(newButton
.focusNode
, "selected", "true");
18760 this._currentChild
= page
;
18761 newButton
.focusNode
.setAttribute("tabIndex", "0");
18762 var container
= dijit
.byId(this.containerId
);
18763 dijit
.setWaiState(container
.containerNode
, "labelledby", newButton
.id
);
18766 onButtonClick: function(/*dijit._Widget*/ page
){
18768 // Called whenever one of my child buttons is pressed in an attempt to select a page
18772 var container
= dijit
.byId(this.containerId
);
18773 container
.selectChild(page
);
18776 onCloseButtonClick: function(/*dijit._Widget*/ page
){
18778 // Called whenever one of my child buttons [X] is pressed in an attempt to close a page
18782 var container
= dijit
.byId(this.containerId
);
18783 container
.closeChild(page
);
18784 if(this._currentChild
){
18785 var b
= this.pane2button
[this._currentChild
.id
];
18787 dijit
.focus(b
.focusNode
|| b
.domNode
);
18792 // TODO: this is a bit redundant with forward, back api in StackContainer
18793 adjacent: function(/*Boolean*/ forward
){
18795 // Helper for onkeypress to find next/previous button
18799 if(!this.isLeftToRight() && (!this.tabPosition
|| /top|bottom/.test(this.tabPosition
))){ forward
= !forward
; }
18800 // find currently focused button in children array
18801 var children
= this.getChildren();
18802 var current
= dojo
.indexOf(children
, this.pane2button
[this._currentChild
.id
]);
18803 // pick next button to focus on
18804 var offset
= forward
? 1 : children
.length
- 1;
18805 return children
[ (current
+ offset
) % children
.length
]; // dijit._Widget
18808 onkeypress: function(/*Event*/ e
){
18810 // Handle keystrokes on the page list, for advancing to next/previous button
18811 // and closing the current page if the page is closable.
18815 if(this.disabled
|| e
.altKey
){ return; }
18816 var forward
= null;
18817 if(e
.ctrlKey
|| !e
._djpage
){
18819 switch(e
.charOrCode
){
18822 if(!e
._djpage
){ forward
= false; }
18825 if(e
.ctrlKey
){ forward
= false; }
18827 case k
.RIGHT_ARROW
:
18829 if(!e
._djpage
){ forward
= true; }
18832 if(e
.ctrlKey
){ forward
= true; }
18836 var children
= this.getChildren();
18837 if(children
&& children
.length
){
18838 children
[e
.charOrCode
== k
.HOME
? 0 : children
.length
-1].onClick();
18843 if(this._currentChild
.closable
){
18844 this.onCloseButtonClick(this._currentChild
);
18850 if(e
.charOrCode
=== k
.TAB
){
18851 this.adjacent(!e
.shiftKey
).onClick();
18853 }else if(e
.charOrCode
== "w"){
18854 if(this._currentChild
.closable
){
18855 this.onCloseButtonClick(this._currentChild
);
18857 dojo
.stopEvent(e
); // avoid browser tab closing.
18861 // handle next/previous page navigation (left/right arrow, etc.)
18862 if(forward
!== null){
18863 this.adjacent(forward
).onClick();
18869 onContainerKeyPress: function(/*Object*/ info
){
18871 // Called when there was a keypress on the container
18874 info
.e
._djpage
= info
.page
;
18875 this.onkeypress(info
.e
);
18880 dojo
.declare("dijit.layout._StackButton",
18881 dijit
.form
.ToggleButton
,
18884 // Internal widget used by StackContainer.
18886 // The button-like or tab-like object you click to select or delete a page
18890 // Override _FormWidget.tabIndex.
18891 // StackContainer buttons are not in the tab order by default.
18892 // Probably we should be calling this.startupKeyNavChildren() instead.
18895 buildRendering: function(/*Event*/ evt
){
18896 this.inherited(arguments
);
18897 dijit
.setWaiRole((this.focusNode
|| this.domNode
), "tab");
18900 onClick: function(/*Event*/ evt
){
18902 // This is for TabContainer where the tabs are <span> rather than button,
18903 // so need to set focus explicitly (on some browsers)
18904 // Note that you shouldn't override this method, but you can connect to it.
18905 dijit
.focus(this.focusNode
);
18907 // ... now let StackController catch the event and tell me what to do
18910 onClickCloseButton: function(/*Event*/ evt
){
18912 // StackContainer connects to this function; if your widget contains a close button
18913 // then clicking it should call this function.
18914 // Note that you shouldn't override this method, but you can connect to it.
18915 evt
.stopPropagation();
18921 if(!dojo
._hasResource
["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
18922 dojo
._hasResource
["dijit.layout.StackContainer"] = true;
18923 dojo
.provide("dijit.layout.StackContainer");
18932 "dijit.layout.StackContainer",
18933 dijit
.layout
._LayoutWidget
,
18936 // A container that has multiple children, but shows only
18937 // one child at a time
18940 // A container for widgets (ContentPanes, for example) That displays
18941 // only one Widget at a time.
18943 // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild
18945 // Can be base class for container, Wizard, Show, etc.
18947 // doLayout: Boolean
18948 // If true, change the size of my currently displayed child to match my size
18951 // persist: Boolean
18952 // Remembers the selected child across sessions
18955 baseClass
: "dijitStackContainer",
18958 // selectedChildWidget: [readonly] dijit._Widget
18959 // References the currently selected child widget, if any.
18960 // Adjust selected child with selectChild() method.
18961 selectedChildWidget: null,
18964 buildRendering: function(){
18965 this.inherited(arguments
);
18966 dojo
.addClass(this.domNode
, "dijitLayoutContainer");
18967 dijit
.setWaiRole(this.containerNode
, "tabpanel");
18970 postCreate: function(){
18971 this.inherited(arguments
);
18972 this.connect(this.domNode
, "onkeypress", this._onKeyPress
);
18975 startup: function(){
18976 if(this._started
){ return; }
18978 var children
= this.getChildren();
18980 // Setup each page panel to be initially hidden
18981 dojo
.forEach(children
, this._setupChild
, this);
18983 // Figure out which child to initially display, defaulting to first one
18985 this.selectedChildWidget
= dijit
.byId(dojo
.cookie(this.id
+ "_selectedChild"));
18987 dojo
.some(children
, function(child
){
18988 if(child
.selected
){
18989 this.selectedChildWidget
= child
;
18991 return child
.selected
;
18994 var selected
= this.selectedChildWidget
;
18995 if(!selected
&& children
[0]){
18996 selected
= this.selectedChildWidget
= children
[0];
18997 selected
.selected
= true;
19000 // Publish information about myself so any StackControllers can initialize.
19001 // This needs to happen before this.inherited(arguments) so that for
19002 // TabContainer, this._contentBox doesn't include the space for the tab labels.
19003 dojo
.publish(this.id
+"-startup", [{children
: children
, selected
: selected
}]);
19005 // Startup each child widget, and do initial layout like setting this._contentBox,
19006 // then calls this.resize() which does the initial sizing on the selected child.
19007 this.inherited(arguments
);
19010 resize: function(){
19011 // Resize is called when we are first made visible (it's called from startup()
19012 // if we are initially visible). If this is the first time we've been made
19013 // visible then show our first child.
19014 var selected
= this.selectedChildWidget
;
19015 if(selected
&& !this._hasBeenShown
){
19016 this._hasBeenShown
= true;
19017 this._showChild(selected
);
19019 this.inherited(arguments
);
19022 _setupChild: function(/*dijit._Widget*/ child
){
19023 // Overrides _LayoutWidget._setupChild()
19025 this.inherited(arguments
);
19027 dojo
.replaceClass(child
.domNode
, "dijitHidden", "dijitVisible");
19029 // remove the title attribute so it doesn't show up when i hover
19031 child
.domNode
.title
= "";
19034 addChild: function(/*dijit._Widget*/ child
, /*Integer?*/ insertIndex
){
19035 // Overrides _Container.addChild() to do layout and publish events
19037 this.inherited(arguments
);
19040 dojo
.publish(this.id
+"-addChild", [child
, insertIndex
]);
19042 // in case the tab titles have overflowed from one line to two lines
19043 // (or, if this if first child, from zero lines to one line)
19044 // TODO: w/ScrollingTabController this is no longer necessary, although
19045 // ScrollTabController.resize() does need to get called to show/hide
19046 // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild()
19049 // if this is the first child, then select it
19050 if(!this.selectedChildWidget
){
19051 this.selectChild(child
);
19056 removeChild: function(/*dijit._Widget*/ page
){
19057 // Overrides _Container.removeChild() to do layout and publish events
19059 this.inherited(arguments
);
19062 // this will notify any tablists to remove a button; do this first because it may affect sizing
19063 dojo
.publish(this.id
+ "-removeChild", [page
]);
19066 // If we are being destroyed than don't run the code below (to select another page), because we are deleting
19067 // every page one by one
19068 if(this._beingDestroyed
){ return; }
19070 // Select new page to display, also updating TabController to show the respective tab.
19071 // Do this before layout call because it can affect the height of the TabController.
19072 if(this.selectedChildWidget
=== page
){
19073 this.selectedChildWidget
= undefined;
19075 var children
= this.getChildren();
19076 if(children
.length
){
19077 this.selectChild(children
[0]);
19083 // In case the tab titles now take up one line instead of two lines
19084 // (note though that ScrollingTabController never overflows to multiple lines),
19085 // or the height has changed slightly because of addition/removal of tab which close icon
19090 selectChild: function(/*dijit._Widget|String*/ page
, /*Boolean*/ animate
){
19092 // Show the given widget (which must be one of my children)
19094 // Reference to child widget or id of child widget
19096 page
= dijit
.byId(page
);
19098 if(this.selectedChildWidget
!= page
){
19099 // Deselect old page and select new one
19100 var d
= this._transition(page
, this.selectedChildWidget
, animate
);
19101 this._set("selectedChildWidget", page
);
19102 dojo
.publish(this.id
+"-selectChild", [page
]);
19105 dojo
.cookie(this.id
+ "_selectedChild", this.selectedChildWidget
.id
);
19109 return d
; // If child has an href, promise that fires when the child's href finishes loading
19112 _transition: function(/*dijit._Widget*/ newWidget
, /*dijit._Widget*/ oldWidget
, /*Boolean*/ animate
){
19114 // Hide the old widget and display the new widget.
19115 // Subclasses should override this.
19117 // protected extension
19119 this._hideChild(oldWidget
);
19121 var d
= this._showChild(newWidget
);
19123 // Size the new widget, in case this is the first time it's being shown,
19124 // or I have been resized since the last time it was shown.
19125 // Note that page must be visible for resizing to work.
19126 if(newWidget
.resize
){
19128 newWidget
.resize(this._containerContentBox
|| this._contentBox
);
19130 // the child should pick it's own size but we still need to call resize()
19131 // (with no arguments) to let the widget lay itself out
19132 newWidget
.resize();
19136 return d
; // If child has an href, promise that fires when the child's href finishes loading
19139 _adjacent: function(/*Boolean*/ forward
){
19141 // Gets the next/previous child widget in this container from the current selection.
19142 var children
= this.getChildren();
19143 var index
= dojo
.indexOf(children
, this.selectedChildWidget
);
19144 index
+= forward
? 1 : children
.length
- 1;
19145 return children
[ index
% children
.length
]; // dijit._Widget
19148 forward: function(){
19150 // Advance to next page.
19151 return this.selectChild(this._adjacent(true), true);
19156 // Go back to previous page.
19157 return this.selectChild(this._adjacent(false), true);
19160 _onKeyPress: function(e
){
19161 dojo
.publish(this.id
+"-containerKeyPress", [{ e
: e
, page
: this}]);
19164 layout: function(){
19165 // Implement _LayoutWidget.layout() virtual method.
19166 if(this.doLayout
&& this.selectedChildWidget
&& this.selectedChildWidget
.resize
){
19167 this.selectedChildWidget
.resize(this._containerContentBox
|| this._contentBox
);
19171 _showChild: function(/*dijit._Widget*/ page
){
19173 // Show the specified child by changing it's CSS, and call _onShow()/onShow() so
19174 // it can do any updates it needs regarding loading href's etc.
19176 // Promise that fires when page has finished showing, or true if there's no href
19177 var children
= this.getChildren();
19178 page
.isFirstChild
= (page
== children
[0]);
19179 page
.isLastChild
= (page
== children
[children
.length
-1]);
19180 page
._set("selected", true);
19182 dojo
.replaceClass(page
.domNode
, "dijitVisible", "dijitHidden");
19184 return page
._onShow() || true;
19187 _hideChild: function(/*dijit._Widget*/ page
){
19189 // Hide the specified child by changing it's CSS, and call _onHide() so
19191 page
._set("selected", false);
19192 dojo
.replaceClass(page
.domNode
, "dijitHidden", "dijitVisible");
19197 closeChild: function(/*dijit._Widget*/ page
){
19199 // Callback when user clicks the [X] to remove a page.
19200 // If onClose() returns true then remove and destroy the child.
19203 var remove
= page
.onClose(this, page
);
19205 this.removeChild(page
);
19206 // makes sure we can clean up executeScripts in ContentPane onUnLoad
19207 page
.destroyRecursive();
19211 destroyDescendants: function(/*Boolean*/ preserveDom
){
19212 dojo
.forEach(this.getChildren(), function(child
){
19213 this.removeChild(child
);
19214 child
.destroyRecursive(preserveDom
);
19219 // For back-compat, remove for 2.0
19222 // These arguments can be specified for the children of a StackContainer.
19223 // Since any widget can be specified as a StackContainer child, mix them
19224 // into the base widget class. (This is a hack, but it's effective.)
19225 dojo
.extend(dijit
._Widget
, {
19226 // selected: Boolean
19227 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
19228 // Specifies that this widget should be the initially displayed pane.
19229 // Note: to change the selected child use `dijit.layout.StackContainer.selectChild`
19232 // closable: Boolean
19233 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
19234 // True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
19237 // iconClass: String
19238 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
19239 // CSS Class specifying icon to use in label associated with this pane.
19242 // showTitle: Boolean
19243 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
19244 // When true, display title of this widget as tab label etc., rather than just using
19245 // icon specified in iconClass
19251 if(!dojo
._hasResource
["dijit.layout.AccordionPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
19252 dojo
._hasResource
["dijit.layout.AccordionPane"] = true;
19253 dojo
.provide("dijit.layout.AccordionPane");
19257 dojo
.declare("dijit.layout.AccordionPane", dijit
.layout
.ContentPane
, {
19259 // Deprecated widget. Use `dijit.layout.ContentPane` instead.
19263 constructor: function(){
19264 dojo
.deprecated("dijit.layout.AccordionPane deprecated, use ContentPane instead", "", "2.0");
19267 onSelected: function(){
19269 // called when this pane is selected
19275 if(!dojo
._hasResource
["dijit.layout.AccordionContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
19276 dojo
._hasResource
["dijit.layout.AccordionContainer"] = true;
19277 dojo
.provide("dijit.layout.AccordionContainer");
19286 //dojo.require("dijit.layout.AccordionPane "); // for back compat, remove for 2.0
19290 // An AccordionContainer is a StackContainer, but each child (typically ContentPane)
19291 // is wrapped in a _AccordionInnerContainer. This is hidden from the caller.
19293 // The resulting markup will look like:
19295 // <div class=dijitAccordionContainer>
19296 // <div class=dijitAccordionInnerContainer> (one pane)
19297 // <div class=dijitAccordionTitle> (title bar) ... </div>
19298 // <div class=dijtAccordionChildWrapper> (content pane) </div>
19302 // Normally the dijtAccordionChildWrapper is hidden for all but one child (the shown
19303 // child), so the space for the content pane is all the title bars + the one dijtAccordionChildWrapper,
19304 // which on claro has a 1px border plus a 2px bottom margin.
19306 // During animation there are two dijtAccordionChildWrapper's shown, so we need
19307 // to compensate for that.
19310 "dijit.layout.AccordionContainer",
19311 dijit
.layout
.StackContainer
,
19314 // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time,
19315 // and switching between panes is visualized by sliding the other panes up/down.
19317 // | <div dojoType="dijit.layout.AccordionContainer">
19318 // | <div dojoType="dijit.layout.ContentPane" title="pane 1">
19320 // | <div dojoType="dijit.layout.ContentPane" title="pane 2">
19321 // | <p>This is some text</p>
19325 // duration: Integer
19326 // Amount of time (in ms) it takes to slide panes
19327 duration
: dijit
.defaultDuration
,
19329 // buttonWidget: [const] String
19330 // The name of the widget used to display the title of each pane
19331 buttonWidget
: "dijit.layout._AccordionButton",
19334 // _verticalSpace: Number
19335 // Pixels of space available for the open pane
19336 // (my content box size minus the cumulative size of all the title bars)
19339 baseClass
: "dijitAccordionContainer",
19341 buildRendering: function(){
19342 this.inherited(arguments
);
19343 this.domNode
.style
.overflow
= "hidden"; // TODO: put this in dijit.css
19344 dijit
.setWaiRole(this.domNode
, "tablist"); // TODO: put this in template
19347 startup: function(){
19348 if(this._started
){ return; }
19349 this.inherited(arguments
);
19350 if(this.selectedChildWidget
){
19351 var style
= this.selectedChildWidget
.containerNode
.style
;
19352 style
.display
= "";
19353 style
.overflow
= "auto";
19354 this.selectedChildWidget
._wrapperWidget
.set("selected", true);
19358 layout: function(){
19359 // Implement _LayoutWidget.layout() virtual method.
19360 // Set the height of the open pane based on what room remains.
19362 var openPane
= this.selectedChildWidget
;
19364 if(!openPane
){ return;}
19366 // space taken up by title, plus wrapper div (with border/margin) for open pane
19367 var wrapperDomNode
= openPane
._wrapperWidget
.domNode
,
19368 wrapperDomNodeMargin
= dojo
._getMarginExtents(wrapperDomNode
),
19369 wrapperDomNodePadBorder
= dojo
._getPadBorderExtents(wrapperDomNode
),
19370 wrapperContainerNode
= openPane
._wrapperWidget
.containerNode
,
19371 wrapperContainerNodeMargin
= dojo
._getMarginExtents(wrapperContainerNode
),
19372 wrapperContainerNodePadBorder
= dojo
._getPadBorderExtents(wrapperContainerNode
),
19373 mySize
= this._contentBox
;
19375 // get cumulative height of all the unselected title bars
19376 var totalCollapsedHeight
= 0;
19377 dojo
.forEach(this.getChildren(), function(child
){
19378 if(child
!= openPane
){
19379 totalCollapsedHeight
+= dojo
._getMarginSize(child
._wrapperWidget
.domNode
).h
;
19382 this._verticalSpace
= mySize
.h
- totalCollapsedHeight
- wrapperDomNodeMargin
.h
19383 - wrapperDomNodePadBorder
.h
- wrapperContainerNodeMargin
.h
- wrapperContainerNodePadBorder
.h
19384 - openPane
._buttonWidget
.getTitleHeight();
19386 // Memo size to make displayed child
19387 this._containerContentBox
= {
19388 h
: this._verticalSpace
,
19389 w
: this._contentBox
.w
- wrapperDomNodeMargin
.w
- wrapperDomNodePadBorder
.w
19390 - wrapperContainerNodeMargin
.w
- wrapperContainerNodePadBorder
.w
19394 openPane
.resize(this._containerContentBox
);
19398 _setupChild: function(child
){
19399 // Overrides _LayoutWidget._setupChild().
19400 // Put wrapper widget around the child widget, showing title
19402 child
._wrapperWidget
= new dijit
.layout
._AccordionInnerContainer({
19403 contentWidget
: child
,
19404 buttonWidget
: this.buttonWidget
,
19405 id
: child
.id
+ "_wrapper",
19411 this.inherited(arguments
);
19414 addChild: function(/*dijit._Widget*/ child
, /*Integer?*/ insertIndex
){
19416 // Adding a child to a started Accordion is complicated because children have
19417 // wrapper widgets. Default code path (calling this.inherited()) would add
19418 // the new child inside another child's wrapper.
19420 // First add in child as a direct child of this AccordionContainer
19421 dojo
.place(child
.domNode
, this.containerNode
, insertIndex
);
19423 if(!child
._started
){
19427 // Then stick the wrapper widget around the child widget
19428 this._setupChild(child
);
19430 // Code below copied from StackContainer
19431 dojo
.publish(this.id
+"-addChild", [child
, insertIndex
]);
19433 if(!this.selectedChildWidget
){
19434 this.selectChild(child
);
19437 // We haven't been started yet so just add in the child widget directly,
19438 // and the wrapper will be created on startup()
19439 this.inherited(arguments
);
19443 removeChild: function(child
){
19444 // Overrides _LayoutWidget.removeChild().
19446 // Destroy wrapper widget first, before StackContainer.getChildren() call.
19447 // Replace wrapper widget with true child widget (ContentPane etc.).
19448 // This step only happens if the AccordionContainer has been started; otherwise there's no wrapper.
19449 if(child
._wrapperWidget
){
19450 dojo
.place(child
.domNode
, child
._wrapperWidget
.domNode
, "after");
19451 child
._wrapperWidget
.destroy();
19452 delete child
._wrapperWidget
;
19455 dojo
.removeClass(child
.domNode
, "dijitHidden");
19457 this.inherited(arguments
);
19460 getChildren: function(){
19461 // Overrides _Container.getChildren() to return content panes rather than internal AccordionInnerContainer panes
19462 return dojo
.map(this.inherited(arguments
), function(child
){
19463 return child
.declaredClass
== "dijit.layout._AccordionInnerContainer" ? child
.contentWidget
: child
;
19467 destroy: function(){
19468 if(this._animation
){
19469 this._animation
.stop();
19471 dojo
.forEach(this.getChildren(), function(child
){
19472 // If AccordionContainer has been started, then each child has a wrapper widget which
19473 // also needs to be destroyed.
19474 if(child
._wrapperWidget
){
19475 child
._wrapperWidget
.destroy();
19477 child
.destroyRecursive();
19480 this.inherited(arguments
);
19483 _showChild: function(child
){
19484 // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode
19485 child
._wrapperWidget
.containerNode
.style
.display
="block";
19486 return this.inherited(arguments
);
19489 _hideChild: function(child
){
19490 // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode
19491 child
._wrapperWidget
.containerNode
.style
.display
="none";
19492 this.inherited(arguments
);
19495 _transition: function(/*dijit._Widget?*/ newWidget
, /*dijit._Widget?*/ oldWidget
, /*Boolean*/ animate
){
19496 // Overrides StackContainer._transition() to provide sliding of title bars etc.
19499 // workaround animation bugs by not animating; not worth supporting animation for IE6 & 7
19503 if(this._animation
){
19504 // there's an in-progress animation. speedily end it so we can do the newly requested one
19505 this._animation
.stop(true);
19506 delete this._animation
;
19512 newWidget
._wrapperWidget
.set("selected", true);
19514 var d
= this._showChild(newWidget
); // prepare widget to be slid in
19516 // Size the new widget, in case this is the first time it's being shown,
19517 // or I have been resized since the last time it was shown.
19518 // Note that page must be visible for resizing to work.
19519 if(this.doLayout
&& newWidget
.resize
){
19520 newWidget
.resize(this._containerContentBox
);
19525 oldWidget
._wrapperWidget
.set("selected", false);
19527 this._hideChild(oldWidget
);
19532 var newContents
= newWidget
._wrapperWidget
.containerNode
,
19533 oldContents
= oldWidget
._wrapperWidget
.containerNode
;
19535 // During the animation we will be showing two dijitAccordionChildWrapper nodes at once,
19536 // which on claro takes up 4px extra space (compared to stable AccordionContainer).
19537 // Have to compensate for that by immediately shrinking the pane being closed.
19538 var wrapperContainerNode
= newWidget
._wrapperWidget
.containerNode
,
19539 wrapperContainerNodeMargin
= dojo
._getMarginExtents(wrapperContainerNode
),
19540 wrapperContainerNodePadBorder
= dojo
._getPadBorderExtents(wrapperContainerNode
),
19541 animationHeightOverhead
= wrapperContainerNodeMargin
.h
+ wrapperContainerNodePadBorder
.h
;
19543 oldContents
.style
.height
= (self
._verticalSpace
- animationHeightOverhead
) + "px";
19545 this._animation
= new dojo
.Animation({
19547 duration
: this.duration
,
19548 curve
: [1, this._verticalSpace
- animationHeightOverhead
- 1],
19549 onAnimate: function(value
){
19550 value
= Math
.floor(value
); // avoid fractional values
19551 newContents
.style
.height
= value
+ "px";
19552 oldContents
.style
.height
= (self
._verticalSpace
- animationHeightOverhead
- value
) + "px";
19555 delete self
._animation
;
19556 newContents
.style
.height
= "auto";
19557 oldWidget
._wrapperWidget
.containerNode
.style
.display
= "none";
19558 oldContents
.style
.height
= "auto";
19559 self
._hideChild(oldWidget
);
19562 this._animation
.onStop
= this._animation
.onEnd
;
19563 this._animation
.play();
19566 return d
; // If child has an href, promise that fires when the widget has finished loading
19569 // note: we are treating the container as controller here
19570 _onKeyPress: function(/*Event*/ e
, /*dijit._Widget*/ fromTitle
){
19572 // Handle keypress events
19574 // This is called from a handler on AccordionContainer.domNode
19575 // (setup in StackContainer), and is also called directly from
19576 // the click handler for accordion labels
19577 if(this.disabled
|| e
.altKey
|| !(fromTitle
|| e
.ctrlKey
)){
19582 if((fromTitle
&& (c
== k
.LEFT_ARROW
|| c
== k
.UP_ARROW
)) ||
19583 (e
.ctrlKey
&& c
== k
.PAGE_UP
)){
19584 this._adjacent(false)._buttonWidget
._onTitleClick();
19586 }else if((fromTitle
&& (c
== k
.RIGHT_ARROW
|| c
== k
.DOWN_ARROW
)) ||
19587 (e
.ctrlKey
&& (c
== k
.PAGE_DOWN
|| c
== k
.TAB
))){
19588 this._adjacent(true)._buttonWidget
._onTitleClick();
19595 dojo
.declare("dijit.layout._AccordionInnerContainer",
19596 [dijit
._Widget
, dijit
._CssStateMixin
], {
19598 // Internal widget placed as direct child of AccordionContainer.containerNode.
19599 // When other widgets are added as children to an AccordionContainer they are wrapped in
19603 // buttonWidget: String
19604 // Name of class to use to instantiate title
19605 // (Wish we didn't have a separate widget for just the title but maintaining it
19606 // for backwards compatibility, is it worth it?)
19607 buttonWidget: null,
19611 // contentWidget: dijit._Widget
19612 // Pointer to the real child widget
19613 contentWidget: null,
19616 baseClass
: "dijitAccordionInnerContainer",
19618 // tell nested layout widget that we will take care of sizing
19620 isLayoutContainer
: true,
19622 buildRendering: function(){
19623 // Builds a template like:
19624 // <div class=dijitAccordionInnerContainer>
19626 // <div class=dijitAccordionChildWrapper>
19631 // Create wrapper div, placed where the child is now
19632 this.domNode
= dojo
.place("<div class='" + this.baseClass
+ "'>", this.contentWidget
.domNode
, "after");
19634 // wrapper div's first child is the button widget (ie, the title bar)
19635 var child
= this.contentWidget
,
19636 cls
= dojo
.getObject(this.buttonWidget
);
19637 this.button
= child
._buttonWidget
= (new cls({
19638 contentWidget
: child
,
19639 label
: child
.title
,
19640 title
: child
.tooltip
,
19643 iconClass
: child
.iconClass
,
19644 id
: child
.id
+ "_button",
19645 parent
: this.parent
19646 })).placeAt(this.domNode
);
19648 // and then the actual content widget (changing it from prior-sibling to last-child),
19649 // wrapped by a <div class=dijitAccordionChildWrapper>
19650 this.containerNode
= dojo
.place("<div class='dijitAccordionChildWrapper' style='display:none'>", this.domNode
);
19651 dojo
.place(this.contentWidget
.domNode
, this.containerNode
);
19654 postCreate: function(){
19655 this.inherited(arguments
);
19657 // Map changes in content widget's title etc. to changes in the button
19658 var button
= this.button
;
19659 this._contentWidgetWatches
= [
19660 this.contentWidget
.watch('title', dojo
.hitch(this, function(name
, oldValue
, newValue
){
19661 button
.set("label", newValue
);
19663 this.contentWidget
.watch('tooltip', dojo
.hitch(this, function(name
, oldValue
, newValue
){
19664 button
.set("title", newValue
);
19666 this.contentWidget
.watch('iconClass', dojo
.hitch(this, function(name
, oldValue
, newValue
){
19667 button
.set("iconClass", newValue
);
19672 _setSelectedAttr: function(/*Boolean*/ isSelected
){
19673 this._set("selected", isSelected
);
19674 this.button
.set("selected", isSelected
);
19676 var cw
= this.contentWidget
;
19677 if(cw
.onSelected
){ cw
.onSelected(); }
19681 startup: function(){
19682 // Called by _Container.addChild()
19683 this.contentWidget
.startup();
19686 destroy: function(){
19687 this.button
.destroyRecursive();
19689 dojo
.forEach(this._contentWidgetWatches
|| [], function(w
){ w
.unwatch(); });
19691 delete this.contentWidget
._buttonWidget
;
19692 delete this.contentWidget
._wrapperWidget
;
19694 this.inherited(arguments
);
19697 destroyDescendants: function(){
19698 // since getChildren isn't working for me, have to code this manually
19699 this.contentWidget
.destroyRecursive();
19703 dojo
.declare("dijit.layout._AccordionButton",
19704 [dijit
._Widget
, dijit
._Templated
, dijit
._CssStateMixin
],
19707 // The title bar to click to open up an accordion pane.
19708 // Internal widget used by AccordionContainer.
19712 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"),
19713 attributeMap
: dojo
.mixin(dojo
.clone(dijit
.layout
.ContentPane
.prototype.attributeMap
), {
19714 label
: {node
: "titleTextNode", type
: "innerHTML" },
19715 title
: {node
: "titleTextNode", type
: "attribute", attribute
: "title"},
19716 iconClass
: { node
: "iconNode", type
: "class" }
19719 baseClass
: "dijitAccordionTitle",
19721 getParent: function(){
19723 // Returns the AccordionContainer parent.
19726 return this.parent
;
19729 buildRendering: function(){
19730 this.inherited(arguments
);
19731 var titleTextNodeId
= this.id
.replace(' ','_');
19732 dojo
.attr(this.titleTextNode
, "id", titleTextNodeId
+"_title");
19733 dijit
.setWaiState(this.focusNode
, "labelledby", dojo
.attr(this.titleTextNode
, "id"));
19734 dojo
.setSelectable(this.domNode
, false);
19737 getTitleHeight: function(){
19739 // Returns the height of the title dom node.
19740 return dojo
._getMarginSize(this.domNode
).h
; // Integer
19743 // TODO: maybe the parent should set these methods directly rather than forcing the code
19744 // into the button widget?
19745 _onTitleClick: function(){
19747 // Callback when someone clicks my title.
19748 var parent
= this.getParent();
19749 parent
.selectChild(this.contentWidget
, true);
19750 dijit
.focus(this.focusNode
);
19753 _onTitleKeyPress: function(/*Event*/ evt
){
19754 return this.getParent()._onKeyPress(evt
, this.contentWidget
);
19757 _setSelectedAttr: function(/*Boolean*/ isSelected
){
19758 this._set("selected", isSelected
);
19759 dijit
.setWaiState(this.focusNode
, "expanded", isSelected
);
19760 dijit
.setWaiState(this.focusNode
, "selected", isSelected
);
19761 this.focusNode
.setAttribute("tabIndex", isSelected
? "0" : "-1");
19767 if(!dojo
._hasResource
["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
19768 dojo
._hasResource
["dijit.layout.BorderContainer"] = true;
19769 dojo
.provide("dijit.layout.BorderContainer");
19776 "dijit.layout.BorderContainer",
19777 dijit
.layout
._LayoutWidget
,
19780 // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides.
19783 // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;",
19784 // that contains a child widget marked region="center" and optionally children widgets marked
19785 // region equal to "top", "bottom", "leading", "trailing", "left" or "right".
19786 // Children along the edges will be laid out according to width or height dimensions and may
19787 // include optional splitters (splitter="true") to make them resizable by the user. The remaining
19788 // space is designated for the center region.
19790 // The outer size must be specified on the BorderContainer node. Width must be specified for the sides
19791 // and height for the top and bottom, respectively. No dimensions should be specified on the center;
19792 // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like
19793 // "left" and "right" except that they will be reversed in right-to-left environments.
19795 // For complex layouts, multiple children can be specified for a single region. In this case, the
19796 // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority)
19797 // and which child is closer to the center (high layoutPriority). layoutPriority can also be used
19798 // instead of the design attribute to conrol layout precedence of horizontal vs. vertical panes.
19800 // | <div dojoType="dijit.layout.BorderContainer" design="sidebar" gutters="false"
19801 // | style="width: 400px; height: 300px;">
19802 // | <div dojoType="dijit.layout.ContentPane" region="top">header text</div>
19803 // | <div dojoType="dijit.layout.ContentPane" region="right" splitter="true" style="width: 200px;">table of contents</div>
19804 // | <div dojoType="dijit.layout.ContentPane" region="center">client area</div>
19808 // Which design is used for the layout:
19809 // - "headline" (default) where the top and bottom extend
19810 // the full width of the container
19811 // - "sidebar" where the left and right sides extend from top to bottom.
19812 design
: "headline",
19814 // gutters: [const] Boolean
19815 // Give each pane a border and margin.
19816 // Margin determined by domNode.paddingLeft.
19817 // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing.
19820 // liveSplitters: [const] Boolean
19821 // Specifies whether splitters resize as you drag (true) or only upon mouseup (false)
19822 liveSplitters
: true,
19824 // persist: Boolean
19825 // Save splitter positions in a cookie.
19828 baseClass
: "dijitBorderContainer",
19830 // _splitterClass: String
19831 // Optional hook to override the default Splitter widget used by BorderContainer
19832 _splitterClass
: "dijit.layout._Splitter",
19834 postMixInProperties: function(){
19835 // change class name to indicate that BorderContainer is being used purely for
19836 // layout (like LayoutContainer) rather than for pretty formatting.
19838 this.baseClass
+= "NoGutter";
19840 this.inherited(arguments
);
19843 startup: function(){
19844 if(this._started
){ return; }
19845 dojo
.forEach(this.getChildren(), this._setupChild
, this);
19846 this.inherited(arguments
);
19849 _setupChild: function(/*dijit._Widget*/ child
){
19850 // Override _LayoutWidget._setupChild().
19852 var region
= child
.region
;
19854 this.inherited(arguments
);
19856 dojo
.addClass(child
.domNode
, this.baseClass
+"Pane");
19858 var ltr
= this.isLeftToRight();
19859 if(region
== "leading"){ region
= ltr
? "left" : "right"; }
19860 if(region
== "trailing"){ region
= ltr
? "right" : "left"; }
19862 // Create draggable splitter for resizing pane,
19863 // or alternately if splitter=false but BorderContainer.gutters=true then
19864 // insert dummy div just for spacing
19865 if(region
!= "center" && (child
.splitter
|| this.gutters
) && !child
._splitterWidget
){
19866 var _Splitter
= dojo
.getObject(child
.splitter
? this._splitterClass
: "dijit.layout._Gutter");
19867 var splitter
= new _Splitter({
19868 id
: child
.id
+ "_splitter",
19872 live
: this.liveSplitters
19874 splitter
.isSplitter
= true;
19875 child
._splitterWidget
= splitter
;
19877 dojo
.place(splitter
.domNode
, child
.domNode
, "after");
19879 // Splitters aren't added as Contained children, so we need to call startup explicitly
19880 splitter
.startup();
19882 child
.region
= region
; // TODO: technically wrong since it overwrites "trailing" with "left" etc.
19886 layout: function(){
19887 // Implement _LayoutWidget.layout() virtual method.
19888 this._layoutChildren();
19891 addChild: function(/*dijit._Widget*/ child
, /*Integer?*/ insertIndex
){
19892 // Override _LayoutWidget.addChild().
19893 this.inherited(arguments
);
19895 this.layout(); //OPT
19899 removeChild: function(/*dijit._Widget*/ child
){
19900 // Override _LayoutWidget.removeChild().
19902 var region
= child
.region
;
19903 var splitter
= child
._splitterWidget
19905 splitter
.destroy();
19906 delete child
._splitterWidget
;
19908 this.inherited(arguments
);
19911 this._layoutChildren();
19913 // Clean up whatever style changes we made to the child pane.
19914 // Unclear how height and width should be handled.
19915 dojo
.removeClass(child
.domNode
, this.baseClass
+"Pane");
19916 dojo
.style(child
.domNode
, {
19923 dojo
.style(child
.domNode
, region
== "top" || region
== "bottom" ? "width" : "height", "auto");
19926 getChildren: function(){
19927 // Override _LayoutWidget.getChildren() to only return real children, not the splitters.
19928 return dojo
.filter(this.inherited(arguments
), function(widget
){
19929 return !widget
.isSplitter
;
19933 // TODO: remove in 2.0
19934 getSplitter: function(/*String*/region
){
19936 // Returns the widget responsible for rendering the splitter associated with region
19939 return dojo
.filter(this.getChildren(), function(child
){
19940 return child
.region
== region
;
19941 })[0]._splitterWidget
;
19944 resize: function(newSize
, currentSize
){
19945 // Overrides _LayoutWidget.resize().
19947 // resetting potential padding to 0px to provide support for 100% width/height + padding
19948 // TODO: this hack doesn't respect the box model and is a temporary fix
19949 if(!this.cs
|| !this.pe
){
19950 var node
= this.domNode
;
19951 this.cs
= dojo
.getComputedStyle(node
);
19952 this.pe
= dojo
._getPadExtents(node
, this.cs
);
19953 this.pe
.r
= dojo
._toPixelValue(node
, this.cs
.paddingRight
);
19954 this.pe
.b
= dojo
._toPixelValue(node
, this.cs
.paddingBottom
);
19956 dojo
.style(node
, "padding", "0px");
19959 this.inherited(arguments
);
19962 _layoutChildren: function(/*String?*/ changedChildId
, /*Number?*/ changedChildSize
){
19964 // This is the main routine for setting size/position of each child.
19966 // With no arguments, measures the height of top/bottom panes, the width
19967 // of left/right panes, and then sizes all panes accordingly.
19969 // With changedRegion specified (as "left", "top", "bottom", or "right"),
19970 // it changes that region's width/height to changedRegionSize and
19971 // then resizes other regions that were affected.
19973 // Id of the child which should be resized because splitter was dragged.
19974 // changedChildSize:
19975 // The new width/height (in pixels) to make specified child
19977 if(!this._borderBox
|| !this._borderBox
.h
){
19978 // We are currently hidden, or we haven't been sized by our parent yet.
19979 // Abort. Someone will resize us later.
19983 // Generate list of wrappers of my children in the order that I want layoutChildren()
19984 // to process them (i.e. from the outside to the inside)
19985 var wrappers
= dojo
.map(this.getChildren(), function(child
, idx
){
19989 child
.region
== "center" ? Infinity
: 0,
19990 child
.layoutPriority
,
19991 (this.design
== "sidebar" ? 1 : -1) * (/top|bottom/.test(child
.region
) ? 1 : -1),
19996 wrappers
.sort(function(a
, b
){
19997 var aw
= a
.weight
, bw
= b
.weight
;
19998 for(var i
=0; i
<aw
.length
; i
++){
19999 if(aw
[i
] != bw
[i
]){
20000 return aw
[i
] - bw
[i
];
20006 // Make new list, combining the externally specified children with splitters and gutters
20007 var childrenAndSplitters
= [];
20008 dojo
.forEach(wrappers
, function(wrapper
){
20009 var pane
= wrapper
.pane
;
20010 childrenAndSplitters
.push(pane
);
20011 if(pane
._splitterWidget
){
20012 childrenAndSplitters
.push(pane
._splitterWidget
);
20016 // Compute the box in which to lay out my children
20020 w
: this._borderBox
.w
- this.pe
.w
,
20021 h
: this._borderBox
.h
- this.pe
.h
20024 // Layout the children, possibly changing size due to a splitter drag
20025 dijit
.layout
.layoutChildren(this.domNode
, dim
, childrenAndSplitters
,
20026 changedChildId
, changedChildSize
);
20029 destroyRecursive: function(){
20030 // Destroy splitters first, while getChildren() still works
20031 dojo
.forEach(this.getChildren(), function(child
){
20032 var splitter
= child
._splitterWidget
;
20034 splitter
.destroy();
20036 delete child
._splitterWidget
;
20039 // Then destroy the real children, and myself
20040 this.inherited(arguments
);
20044 // This argument can be specified for the children of a BorderContainer.
20045 // Since any widget can be specified as a LayoutContainer child, mix it
20046 // into the base widget class. (This is a hack, but it's effective.)
20047 dojo
.extend(dijit
._Widget
, {
20048 // region: [const] String
20049 // Parameter for children of `dijit.layout.BorderContainer`.
20050 // Values: "top", "bottom", "leading", "trailing", "left", "right", "center".
20051 // See the `dijit.layout.BorderContainer` description for details.
20054 // layoutPriority: [const] Number
20055 // Parameter for children of `dijit.layout.BorderContainer`.
20056 // Children with a higher layoutPriority will be placed closer to the BorderContainer center,
20057 // between children with a lower layoutPriority.
20060 // splitter: [const] Boolean
20061 // Parameter for child of `dijit.layout.BorderContainer` where region != "center".
20062 // If true, enables user to resize the widget by putting a draggable splitter between
20063 // this widget and the region=center widget.
20066 // minSize: [const] Number
20067 // Parameter for children of `dijit.layout.BorderContainer`.
20068 // Specifies a minimum size (in pixels) for this widget when resized by a splitter.
20071 // maxSize: [const] Number
20072 // Parameter for children of `dijit.layout.BorderContainer`.
20073 // Specifies a maximum size (in pixels) for this widget when resized by a splitter.
20077 dojo
.declare("dijit.layout._Splitter", [ dijit
._Widget
, dijit
._Templated
],
20080 // A draggable spacer between two items in a `dijit.layout.BorderContainer`.
20082 // This is instantiated by `dijit.layout.BorderContainer`. Users should not
20083 // create it directly.
20088 // container: [const] dijit.layout.BorderContainer
20089 // Pointer to the parent BorderContainer
20092 // child: [const] dijit.layout._LayoutWidget
20093 // Pointer to the pane associated with this splitter
20096 // region: [const] String
20097 // Region of pane associated with this splitter.
20098 // "top", "bottom", "left", "right".
20102 // live: [const] Boolean
20103 // If true, the child's size changes and the child widget is redrawn as you drag the splitter;
20104 // otherwise, the size doesn't change until you drop the splitter (by mouse-up)
20107 templateString
: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>',
20109 postMixInProperties: function(){
20110 this.inherited(arguments
);
20112 this.horizontal
= /top|bottom/.test(this.region
);
20113 this._factor
= /top|left/.test(this.region
) ? 1 : -1;
20114 this._cookieName
= this.container
.id
+ "_" + this.region
;
20117 buildRendering: function(){
20118 this.inherited(arguments
);
20120 dojo
.addClass(this.domNode
, "dijitSplitter" + (this.horizontal
? "H" : "V"));
20122 if(this.container
.persist
){
20123 // restore old size
20124 var persistSize
= dojo
.cookie(this._cookieName
);
20126 this.child
.domNode
.style
[this.horizontal
? "height" : "width"] = persistSize
;
20131 _computeMaxSize: function(){
20133 // Return the maximum size that my corresponding pane can be set to
20135 var dim
= this.horizontal
? 'h' : 'w',
20136 childSize
= dojo
.marginBox(this.child
.domNode
)[dim
],
20137 center
= dojo
.filter(this.container
.getChildren(), function(child
){ return child
.region
== "center";})[0],
20138 spaceAvailable
= dojo
.marginBox(center
.domNode
)[dim
]; // can expand until center is crushed to 0
20140 return Math
.min(this.child
.maxSize
, childSize
+ spaceAvailable
);
20143 _startDrag: function(e
){
20145 this.cover
= dojo
.doc
.createElement('div');
20146 dojo
.addClass(this.cover
, "dijitSplitterCover");
20147 dojo
.place(this.cover
, this.child
.domNode
, "after");
20149 dojo
.addClass(this.cover
, "dijitSplitterCoverActive");
20151 // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up.
20152 if(this.fake
){ dojo
.destroy(this.fake
); }
20153 if(!(this._resize
= this.live
)){ //TODO: disable live for IE6?
20154 // create fake splitter to display at old position while we drag
20155 (this.fake
= this.domNode
.cloneNode(true)).removeAttribute("id");
20156 dojo
.addClass(this.domNode
, "dijitSplitterShadow");
20157 dojo
.place(this.fake
, this.domNode
, "after");
20159 dojo
.addClass(this.domNode
, "dijitSplitterActive dijitSplitter" + (this.horizontal
? "H" : "V") + "Active");
20161 dojo
.removeClass(this.fake
, "dijitSplitterHover dijitSplitter" + (this.horizontal
? "H" : "V") + "Hover");
20164 //Performance: load data info local vars for onmousevent function closure
20165 var factor
= this._factor
,
20166 isHorizontal
= this.horizontal
,
20167 axis
= isHorizontal
? "pageY" : "pageX",
20168 pageStart
= e
[axis
],
20169 splitterStyle
= this.domNode
.style
,
20170 dim
= isHorizontal
? 'h' : 'w',
20171 childStart
= dojo
.marginBox(this.child
.domNode
)[dim
],
20172 max
= this._computeMaxSize(),
20173 min
= this.child
.minSize
|| 20,
20174 region
= this.region
,
20175 splitterAttr
= region
== "top" || region
== "bottom" ? "top" : "left", // style attribute of splitter to adjust
20176 splitterStart
= parseInt(splitterStyle
[splitterAttr
], 10),
20177 resize
= this._resize
,
20178 layoutFunc
= dojo
.hitch(this.container
, "_layoutChildren", this.child
.id
),
20181 this._handlers
= (this._handlers
|| []).concat([
20182 dojo
.connect(de
, "onmousemove", this._drag = function(e
, forceResize
){
20183 var delta
= e
[axis
] - pageStart
,
20184 childSize
= factor
* delta
+ childStart
,
20185 boundChildSize
= Math
.max(Math
.min(childSize
, max
), min
);
20187 if(resize
|| forceResize
){
20188 layoutFunc(boundChildSize
);
20190 // TODO: setting style directly (usually) sets content box size, need to set margin box size
20191 splitterStyle
[splitterAttr
] = delta
+ splitterStart
+ factor
*(boundChildSize
- childSize
) + "px";
20193 dojo
.connect(de
, "ondragstart", dojo
.stopEvent
),
20194 dojo
.connect(dojo
.body(), "onselectstart", dojo
.stopEvent
),
20195 dojo
.connect(de
, "onmouseup", this, "_stopDrag")
20200 _onMouse: function(e
){
20201 var o
= (e
.type
== "mouseover" || e
.type
== "mouseenter");
20202 dojo
.toggleClass(this.domNode
, "dijitSplitterHover", o
);
20203 dojo
.toggleClass(this.domNode
, "dijitSplitter" + (this.horizontal
? "H" : "V") + "Hover", o
);
20206 _stopDrag: function(e
){
20209 dojo
.removeClass(this.cover
, "dijitSplitterCoverActive");
20211 if(this.fake
){ dojo
.destroy(this.fake
); }
20212 dojo
.removeClass(this.domNode
, "dijitSplitterActive dijitSplitter"
20213 + (this.horizontal
? "H" : "V") + "Active dijitSplitterShadow");
20214 this._drag(e
); //TODO: redundant with onmousemove?
20215 this._drag(e
, true);
20217 this._cleanupHandlers();
20221 if(this.container
.persist
){
20222 dojo
.cookie(this._cookieName
, this.child
.domNode
.style
[this.horizontal
? "height" : "width"], {expires
:365});
20226 _cleanupHandlers: function(){
20227 dojo
.forEach(this._handlers
, dojo
.disconnect
);
20228 delete this._handlers
;
20231 _onKeyPress: function(/*Event*/ e
){
20232 // should we apply typematic to this?
20233 this._resize
= true;
20234 var horizontal
= this.horizontal
;
20236 var dk
= dojo
.keys
;
20237 switch(e
.charOrCode
){
20238 case horizontal
? dk
.UP_ARROW
: dk
.LEFT_ARROW
:
20241 case horizontal
? dk
.DOWN_ARROW
: dk
.RIGHT_ARROW
:
20244 // this.inherited(arguments);
20247 var childSize
= dojo
._getMarginSize(this.child
.domNode
)[ horizontal
? 'h' : 'w' ] + this._factor
* tick
;
20248 this.container
._layoutChildren(this.child
.id
, Math
.max(Math
.min(childSize
, this._computeMaxSize()), this.child
.minSize
));
20252 destroy: function(){
20253 this._cleanupHandlers();
20255 delete this.container
;
20258 this.inherited(arguments
);
20262 dojo
.declare("dijit.layout._Gutter", [dijit
._Widget
, dijit
._Templated
],
20265 // Just a spacer div to separate side pane from center pane.
20266 // Basically a trick to lookup the gutter/splitter width from the theme.
20268 // Instantiated by `dijit.layout.BorderContainer`. Users should not
20269 // create directly.
20273 templateString
: '<div class="dijitGutter" role="presentation"></div>',
20275 postMixInProperties: function(){
20276 this.inherited(arguments
);
20277 this.horizontal
= /top|bottom/.test(this.region
);
20280 buildRendering: function(){
20281 this.inherited(arguments
);
20282 dojo
.addClass(this.domNode
, "dijitGutter" + (this.horizontal
? "H" : "V"));
20288 if(!dojo
._hasResource
["dijit.layout._TabContainerBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
20289 dojo
._hasResource
["dijit.layout._TabContainerBase"] = true;
20290 dojo
.provide("dijit.layout._TabContainerBase");
20295 dojo
.declare("dijit.layout._TabContainerBase",
20296 [dijit
.layout
.StackContainer
, dijit
._Templated
],
20299 // Abstract base class for TabContainer. Must define _makeController() to instantiate
20300 // and return the widget that displays the tab labels
20302 // A TabContainer is a container that has multiple panes, but shows only
20303 // one pane at a time. There are a set of tabs corresponding to each pane,
20304 // where each tab has the name (aka title) of the pane, and optionally a close button.
20306 // tabPosition: String
20307 // Defines where tabs go relative to tab content.
20308 // "top", "bottom", "left-h", "right-h"
20309 tabPosition
: "top",
20311 baseClass
: "dijitTabContainer",
20313 // tabStrip: [const] Boolean
20314 // Defines whether the tablist gets an extra class for layouting, putting a border/shading
20315 // around the set of tabs. Not supported by claro theme.
20318 // nested: [const] Boolean
20319 // If true, use styling for a TabContainer nested inside another TabContainer.
20320 // For tundra etc., makes tabs look like links, and hides the outer
20321 // border since the outer TabContainer already has a border.
20324 templateString
: dojo
.cache("dijit.layout", "templates/TabContainer.html", "<div class=\"dijitTabContainer\">\n\t<div class=\"dijitTabListWrapper\" dojoAttachPoint=\"tablistNode\"></div>\n\t<div dojoAttachPoint=\"tablistSpacer\" class=\"dijitTabSpacer ${baseClass}-spacer\"></div>\n\t<div class=\"dijitTabPaneWrapper ${baseClass}-container\" dojoAttachPoint=\"containerNode\"></div>\n</div>\n"),
20326 postMixInProperties: function(){
20327 // set class name according to tab position, ex: dijitTabContainerTop
20328 this.baseClass
+= this.tabPosition
.charAt(0).toUpperCase() + this.tabPosition
.substr(1).replace(/-.*/, "");
20330 this.srcNodeRef
&& dojo
.style(this.srcNodeRef
, "visibility", "hidden");
20332 this.inherited(arguments
);
20335 buildRendering: function(){
20336 this.inherited(arguments
);
20338 // Create the tab list that will have a tab (a.k.a. tab button) for each tab panel
20339 this.tablist
= this._makeController(this.tablistNode
);
20341 if(!this.doLayout
){ dojo
.addClass(this.domNode
, "dijitTabContainerNoLayout"); }
20344 /* workaround IE's lack of support for "a > b" selectors by
20345 * tagging each node in the template.
20347 dojo
.addClass(this.domNode
, "dijitTabContainerNested");
20348 dojo
.addClass(this.tablist
.containerNode
, "dijitTabContainerTabListNested");
20349 dojo
.addClass(this.tablistSpacer
, "dijitTabContainerSpacerNested");
20350 dojo
.addClass(this.containerNode
, "dijitTabPaneWrapperNested");
20352 dojo
.addClass(this.domNode
, "tabStrip-" + (this.tabStrip
? "enabled" : "disabled"));
20356 _setupChild: function(/*dijit._Widget*/ tab
){
20357 // Overrides StackContainer._setupChild().
20358 dojo
.addClass(tab
.domNode
, "dijitTabPane");
20359 this.inherited(arguments
);
20362 startup: function(){
20363 if(this._started
){ return; }
20365 // wire up the tablist and its tabs
20366 this.tablist
.startup();
20368 this.inherited(arguments
);
20371 layout: function(){
20372 // Overrides StackContainer.layout().
20373 // Configure the content pane to take up all the space except for where the tabs are
20375 if(!this._contentBox
|| typeof(this._contentBox
.l
) == "undefined"){return;}
20377 var sc
= this.selectedChildWidget
;
20380 // position and size the titles and the container node
20381 var titleAlign
= this.tabPosition
.replace(/-h/, "");
20382 this.tablist
.layoutAlign
= titleAlign
;
20383 var children
= [this.tablist
, {
20384 domNode
: this.tablistSpacer
,
20385 layoutAlign
: titleAlign
20387 domNode
: this.containerNode
,
20388 layoutAlign
: "client"
20390 dijit
.layout
.layoutChildren(this.domNode
, this._contentBox
, children
);
20392 // Compute size to make each of my children.
20393 // children[2] is the margin-box size of this.containerNode, set by layoutChildren() call above
20394 this._containerContentBox
= dijit
.layout
.marginBox2contentBox(this.containerNode
, children
[2]);
20396 if(sc
&& sc
.resize
){
20397 sc
.resize(this._containerContentBox
);
20400 // just layout the tab controller, so it can position left/right buttons etc.
20401 if(this.tablist
.resize
){
20402 //make the tabs zero width so that they don't interfere with width calc, then reset
20403 var s
= this.tablist
.domNode
.style
;
20405 var width
= dojo
.contentBox(this.domNode
).w
;
20407 this.tablist
.resize({w
: width
});
20410 // and call resize() on the selected pane just to tell it that it's been made visible
20411 if(sc
&& sc
.resize
){
20417 destroy: function(){
20419 this.tablist
.destroy();
20421 this.inherited(arguments
);
20427 if(!dojo
._hasResource
["dijit.layout.TabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
20428 dojo
._hasResource
["dijit.layout.TabController"] = true;
20429 dojo
.provide("dijit.layout.TabController");
20436 // Menu is used for an accessible close button, would be nice to have a lighter-weight solution
20439 dojo
.declare("dijit.layout.TabController",
20440 dijit
.layout
.StackController
,
20443 // Set of tabs (the things with titles and a close button, that you click to show a tab panel).
20444 // Used internally by `dijit.layout.TabContainer`.
20446 // Lets the user select the currently shown pane in a TabContainer or StackContainer.
20447 // TabController also monitors the TabContainer, and whenever a pane is
20448 // added or deleted updates itself accordingly.
20452 templateString
: "<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>",
20454 // tabPosition: String
20455 // Defines where tabs go relative to the content.
20456 // "top", "bottom", "left-h", "right-h"
20457 tabPosition
: "top",
20459 // buttonWidget: String
20460 // The name of the tab widget to create to correspond to each page
20461 buttonWidget
: "dijit.layout._TabButton",
20463 _rectifyRtlTabList: function(){
20465 // For left/right TabContainer when page is RTL mode, rectify the width of all tabs to be equal, otherwise the tab widths are different in IE
20467 if(0 >= this.tabPosition
.indexOf('-h')){ return; }
20468 if(!this.pane2button
){ return; }
20471 for(var pane
in this.pane2button
){
20472 var ow
= this.pane2button
[pane
].innerDiv
.scrollWidth
;
20473 maxWidth
= Math
.max(maxWidth
, ow
);
20475 //unify the length of all the tabs
20476 for(pane
in this.pane2button
){
20477 this.pane2button
[pane
].innerDiv
.style
.width
= maxWidth
+ 'px';
20482 dojo
.declare("dijit.layout._TabButton",
20483 dijit
.layout
._StackButton
,
20486 // A tab (the thing you click to select a pane).
20488 // Contains the title of the pane, and optionally a close-button to destroy the pane.
20489 // This is an internal widget and should not be instantiated directly.
20493 // baseClass: String
20494 // The CSS class applied to the domNode.
20495 baseClass
: "dijitTab",
20497 // Apply dijitTabCloseButtonHover when close button is hovered
20499 closeNode
: "dijitTabCloseButton"
20502 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"),
20504 // Override _FormWidget.scrollOnFocus.
20505 // Don't scroll the whole tab container into view when the button is focused.
20506 scrollOnFocus
: false,
20508 buildRendering: function(){
20509 this.inherited(arguments
);
20511 dojo
.setSelectable(this.containerNode
, false);
20514 startup: function(){
20515 this.inherited(arguments
);
20516 var n
= this.domNode
;
20518 // Required to give IE6 a kick, as it initially hides the
20519 // tabs until they are focused on.
20520 setTimeout(function(){
20521 n
.className
= n
.className
;
20525 _setCloseButtonAttr: function(/*Boolean*/ disp
){
20527 // Hide/show close button
20528 this._set("closeButton", disp
);
20529 dojo
.toggleClass(this.innerDiv
, "dijitClosable", disp
);
20530 this.closeNode
.style
.display
= disp
? "" : "none";
20532 var _nlsResources
= dojo
.i18n
.getLocalization("dijit", "common");
20533 if(this.closeNode
){
20534 dojo
.attr(this.closeNode
,"title", _nlsResources
.itemClose
);
20536 // add context menu onto title button
20537 var _nlsResources
= dojo
.i18n
.getLocalization("dijit", "common");
20538 this._closeMenu
= new dijit
.Menu({
20539 id
: this.id
+"_Menu",
20542 targetNodeIds
: [this.domNode
]
20545 this._closeMenu
.addChild(new dijit
.MenuItem({
20546 label
: _nlsResources
.itemClose
,
20549 onClick
: dojo
.hitch(this, "onClickCloseButton")
20552 if(this._closeMenu
){
20553 this._closeMenu
.destroyRecursive();
20554 delete this._closeMenu
;
20558 _setLabelAttr: function(/*String*/ content
){
20560 // Hook for set('label', ...) to work.
20562 // takes an HTML string.
20563 // Inherited ToggleButton implementation will Set the label (text) of the button;
20564 // Need to set the alt attribute of icon on tab buttons if no label displayed
20565 this.inherited(arguments
);
20566 if(this.showLabel
== false && !this.params
.title
){
20567 this.iconNode
.alt
= dojo
.trim(this.containerNode
.innerText
|| this.containerNode
.textContent
|| '');
20571 destroy: function(){
20572 if(this._closeMenu
){
20573 this._closeMenu
.destroyRecursive();
20574 delete this._closeMenu
;
20576 this.inherited(arguments
);
20582 if(!dojo
._hasResource
["dijit.layout.ScrollingTabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
20583 dojo
._hasResource
["dijit.layout.ScrollingTabController"] = true;
20584 dojo
.provide("dijit.layout.ScrollingTabController");
20591 dojo
.declare("dijit.layout.ScrollingTabController",
20592 dijit
.layout
.TabController
,
20595 // Set of tabs with left/right arrow keys and a menu to switch between tabs not
20596 // all fitting on a single row.
20597 // Works only for horizontal tabs (either above or below the content, not to the left
20602 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"),
20604 // useMenu: [const] Boolean
20605 // True if a menu should be used to select tabs when they are too
20606 // wide to fit the TabContainer, false otherwise.
20609 // useSlider: [const] Boolean
20610 // True if a slider should be used to select tabs when they are too
20611 // wide to fit the TabContainer, false otherwise.
20614 // tabStripClass: [const] String
20615 // The css class to apply to the tab strip, if it is visible.
20618 widgetsInTemplate
: true,
20620 // _minScroll: Number
20621 // The distance in pixels from the edge of the tab strip which,
20622 // if a scroll animation is less than, forces the scroll to
20623 // go all the way to the left/right.
20626 attributeMap
: dojo
.delegate(dijit
._Widget
.prototype.attributeMap
, {
20627 "class": "containerNode"
20630 buildRendering: function(){
20631 this.inherited(arguments
);
20632 var n
= this.domNode
;
20634 this.scrollNode
= this.tablistWrapper
;
20635 this._initButtons();
20637 if(!this.tabStripClass
){
20638 this.tabStripClass
= "dijitTabContainer" +
20639 this.tabPosition
.charAt(0).toUpperCase() +
20640 this.tabPosition
.substr(1).replace(/-.*/, "") +
20642 dojo
.addClass(n
, "tabStrip-disabled")
20645 dojo
.addClass(this.tablistWrapper
, this.tabStripClass
);
20648 onStartup: function(){
20649 this.inherited(arguments
);
20651 // Do not show the TabController until the related
20652 // StackController has added it's children. This gives
20653 // a less visually jumpy instantiation.
20654 dojo
.style(this.domNode
, "visibility", "visible");
20655 this._postStartup
= true;
20658 onAddChild: function(page
, insertIndex
){
20659 this.inherited(arguments
);
20661 // changes to the tab button label or iconClass will have changed the width of the
20662 // buttons, so do a resize
20663 dojo
.forEach(["label", "iconClass"], function(attr
){
20664 this.pane2watches
[page
.id
].push(
20665 this.pane2button
[page
.id
].watch(attr
, dojo
.hitch(this, function(name
, oldValue
, newValue
){
20666 if(this._postStartup
&& this._dim
){
20667 this.resize(this._dim
);
20673 // Increment the width of the wrapper when a tab is added
20674 // This makes sure that the buttons never wrap.
20675 // The value 200 is chosen as it should be bigger than most
20676 // Tab button widths.
20677 dojo
.style(this.containerNode
, "width",
20678 (dojo
.style(this.containerNode
, "width") + 200) + "px");
20681 onRemoveChild: function(page
, insertIndex
){
20682 // null out _selectedTab because we are about to delete that dom node
20683 var button
= this.pane2button
[page
.id
];
20684 if(this._selectedTab
=== button
.domNode
){
20685 this._selectedTab
= null;
20688 this.inherited(arguments
);
20691 _initButtons: function(){
20693 // Creates the buttons used to scroll to view tabs that
20694 // may not be visible if the TabContainer is too narrow.
20696 // Make a list of the buttons to display when the tab labels become
20697 // wider than the TabContainer, and hide the other buttons.
20698 // Also gets the total width of the displayed buttons.
20699 this._btnWidth
= 0;
20700 this._buttons
= dojo
.query("> .tabStripButton", this.domNode
).filter(function(btn
){
20701 if((this.useMenu
&& btn
== this._menuBtn
.domNode
) ||
20702 (this.useSlider
&& (btn
== this._rightBtn
.domNode
|| btn
== this._leftBtn
.domNode
))){
20703 this._btnWidth
+= dojo
._getMarginSize(btn
).w
;
20706 dojo
.style(btn
, "display", "none");
20712 _getTabsWidth: function(){
20713 var children
= this.getChildren();
20714 if(children
.length
){
20715 var leftTab
= children
[this.isLeftToRight() ? 0 : children
.length
- 1].domNode
,
20716 rightTab
= children
[this.isLeftToRight() ? children
.length
- 1 : 0].domNode
;
20717 return rightTab
.offsetLeft
+ dojo
.style(rightTab
, "width") - leftTab
.offsetLeft
;
20723 _enableBtn: function(width
){
20725 // Determines if the tabs are wider than the width of the TabContainer, and
20726 // thus that we need to display left/right/menu navigation buttons.
20727 var tabsWidth
= this._getTabsWidth();
20728 width
= width
|| dojo
.style(this.scrollNode
, "width");
20729 return tabsWidth
> 0 && width
< tabsWidth
;
20732 resize: function(dim
){
20734 // Hides or displays the buttons used to scroll the tab list and launch the menu
20735 // that selects tabs.
20737 if(this.domNode
.offsetWidth
== 0){
20741 // Save the dimensions to be used when a child is renamed.
20744 // Set my height to be my natural height (tall enough for one row of tab labels),
20745 // and my content-box width based on margin-box width specified in dim parameter.
20746 // But first reset scrollNode.height in case it was set by layoutChildren() call
20747 // in a previous run of this method.
20748 this.scrollNode
.style
.height
= "auto";
20749 this._contentBox
= dijit
.layout
.marginBox2contentBox(this.domNode
, {h
: 0, w
: dim
.w
});
20750 this._contentBox
.h
= this.scrollNode
.offsetHeight
;
20751 dojo
.contentBox(this.domNode
, this._contentBox
);
20753 // Show/hide the left/right/menu navigation buttons depending on whether or not they
20755 var enable
= this._enableBtn(this._contentBox
.w
);
20756 this._buttons
.style("display", enable
? "" : "none");
20758 // Position and size the navigation buttons and the tablist
20759 this._leftBtn
.layoutAlign
= "left";
20760 this._rightBtn
.layoutAlign
= "right";
20761 this._menuBtn
.layoutAlign
= this.isLeftToRight() ? "right" : "left";
20762 dijit
.layout
.layoutChildren(this.domNode
, this._contentBox
,
20763 [this._menuBtn
, this._leftBtn
, this._rightBtn
, {domNode
: this.scrollNode
, layoutAlign
: "client"}]);
20765 // set proper scroll so that selected tab is visible
20766 if(this._selectedTab
){
20767 if(this._anim
&& this._anim
.status() == "playing"){
20770 var w
= this.scrollNode
,
20771 sl
= this._convertToScrollLeft(this._getScrollForSelectedTab());
20775 // Enable/disabled left right buttons depending on whether or not user can scroll to left or right
20776 this._setButtonClass(this._getScroll());
20778 this._postResize
= true;
20780 // Return my size so layoutChildren() can use it.
20781 // Also avoids IE9 layout glitch on browser resize when scroll buttons present
20782 return {h
: this._contentBox
.h
, w
: dim
.w
};
20785 _getScroll: function(){
20787 // Returns the current scroll of the tabs where 0 means
20788 // "scrolled all the way to the left" and some positive number, based on #
20789 // of pixels of possible scroll (ex: 1000) means "scrolled all the way to the right"
20790 var sl
= (this.isLeftToRight() || dojo
.isIE
< 8 || (dojo
.isIE
&& dojo
.isQuirks
) || dojo
.isWebKit
) ? this.scrollNode
.scrollLeft
:
20791 dojo
.style(this.containerNode
, "width") - dojo
.style(this.scrollNode
, "width")
20792 + (dojo
.isIE
== 8 ? -1 : 1) * this.scrollNode
.scrollLeft
;
20796 _convertToScrollLeft: function(val
){
20798 // Given a scroll value where 0 means "scrolled all the way to the left"
20799 // and some positive number, based on # of pixels of possible scroll (ex: 1000)
20800 // means "scrolled all the way to the right", return value to set this.scrollNode.scrollLeft
20801 // to achieve that scroll.
20803 // This method is to adjust for RTL funniness in various browsers and versions.
20804 if(this.isLeftToRight() || dojo
.isIE
< 8 || (dojo
.isIE
&& dojo
.isQuirks
) || dojo
.isWebKit
){
20807 var maxScroll
= dojo
.style(this.containerNode
, "width") - dojo
.style(this.scrollNode
, "width");
20808 return (dojo
.isIE
== 8 ? -1 : 1) * (val
- maxScroll
);
20812 onSelectChild: function(/*dijit._Widget*/ page
){
20814 // Smoothly scrolls to a tab when it is selected.
20816 var tab
= this.pane2button
[page
.id
];
20817 if(!tab
|| !page
){return;}
20819 // Scroll to the selected tab, except on startup, when scrolling is handled in resize()
20820 var node
= tab
.domNode
;
20821 if(this._postResize
&& node
!= this._selectedTab
){
20822 this._selectedTab
= node
;
20824 var sl
= this._getScroll();
20826 if(sl
> node
.offsetLeft
||
20827 sl
+ dojo
.style(this.scrollNode
, "width") <
20828 node
.offsetLeft
+ dojo
.style(node
, "width")){
20829 this.createSmoothScroll().play();
20833 this.inherited(arguments
);
20836 _getScrollBounds: function(){
20838 // Returns the minimum and maximum scroll setting to show the leftmost and rightmost
20839 // tabs (respectively)
20840 var children
= this.getChildren(),
20841 scrollNodeWidth
= dojo
.style(this.scrollNode
, "width"), // about 500px
20842 containerWidth
= dojo
.style(this.containerNode
, "width"), // 50,000px
20843 maxPossibleScroll
= containerWidth
- scrollNodeWidth
, // scrolling until right edge of containerNode visible
20844 tabsWidth
= this._getTabsWidth();
20846 if(children
.length
&& tabsWidth
> scrollNodeWidth
){
20847 // Scrolling should happen
20849 min
: this.isLeftToRight() ? 0 : children
[children
.length
-1].domNode
.offsetLeft
,
20850 max
: this.isLeftToRight() ?
20851 (children
[children
.length
-1].domNode
.offsetLeft
+ dojo
.style(children
[children
.length
-1].domNode
, "width")) - scrollNodeWidth
:
20855 // No scrolling needed, all tabs visible, we stay either scrolled to far left or far right (depending on dir)
20856 var onlyScrollPosition
= this.isLeftToRight() ? 0 : maxPossibleScroll
;
20858 min
: onlyScrollPosition
,
20859 max
: onlyScrollPosition
20864 _getScrollForSelectedTab: function(){
20866 // Returns the scroll value setting so that the selected tab
20867 // will appear in the center
20868 var w
= this.scrollNode
,
20869 n
= this._selectedTab
,
20870 scrollNodeWidth
= dojo
.style(this.scrollNode
, "width"),
20871 scrollBounds
= this._getScrollBounds();
20873 // TODO: scroll minimal amount (to either right or left) so that
20874 // selected tab is fully visible, and just return if it's already visible?
20875 var pos
= (n
.offsetLeft
+ dojo
.style(n
, "width")/2) - scrollNodeWidth/2;
20876 pos
= Math
.min(Math
.max(pos
, scrollBounds
.min
), scrollBounds
.max
);
20879 // If scrolling close to the left side or right side, scroll
20880 // all the way to the left or right. See this._minScroll.
20881 // (But need to make sure that doesn't scroll the tab out of view...)
20885 createSmoothScroll: function(x
){
20887 // Creates a dojo._Animation object that smoothly scrolls the tab list
20888 // either to a fixed horizontal pixel value, or to the selected tab.
20890 // If an number argument is passed to the function, that horizontal
20891 // pixel position is scrolled to. Otherwise the currently selected
20892 // tab is scrolled to.
20894 // An optional pixel value to scroll to, indicating distance from left.
20896 // Calculate position to scroll to
20897 if(arguments
.length
> 0){
20898 // position specified by caller, just make sure it's within bounds
20899 var scrollBounds
= this._getScrollBounds();
20900 x
= Math
.min(Math
.max(x
, scrollBounds
.min
), scrollBounds
.max
);
20902 // scroll to center the current tab
20903 x
= this._getScrollForSelectedTab();
20906 if(this._anim
&& this._anim
.status() == "playing"){
20911 w
= this.scrollNode
,
20912 anim
= new dojo
._Animation({
20913 beforeBegin: function(){
20914 if(this.curve
){ delete this.curve
; }
20915 var oldS
= w
.scrollLeft
,
20916 newS
= self
._convertToScrollLeft(x
);
20917 anim
.curve
= new dojo
._Line(oldS
, newS
);
20919 onAnimate: function(val
){
20920 w
.scrollLeft
= val
;
20925 // Disable/enable left/right buttons according to new scroll position
20926 this._setButtonClass(x
);
20928 return anim
; // dojo._Animation
20931 _getBtnNode: function(/*Event*/ e
){
20933 // Gets a button DOM node from a mouse click event.
20935 // The mouse click event.
20937 while(n
&& !dojo
.hasClass(n
, "tabStripButton")){
20943 doSlideRight: function(/*Event*/ e
){
20945 // Scrolls the menu to the right.
20947 // The mouse click event.
20948 this.doSlide(1, this._getBtnNode(e
));
20951 doSlideLeft: function(/*Event*/ e
){
20953 // Scrolls the menu to the left.
20955 // The mouse click event.
20956 this.doSlide(-1,this._getBtnNode(e
));
20959 doSlide: function(/*Number*/ direction
, /*DomNode*/ node
){
20961 // Scrolls the tab list to the left or right by 75% of the widget width.
20963 // If the direction is 1, the widget scrolls to the right, if it is
20964 // -1, it scrolls to the left.
20966 if(node
&& dojo
.hasClass(node
, "dijitTabDisabled")){return;}
20968 var sWidth
= dojo
.style(this.scrollNode
, "width");
20969 var d
= (sWidth
* 0.75) * direction
;
20971 var to
= this._getScroll() + d
;
20973 this._setButtonClass(to
);
20975 this.createSmoothScroll(to
).play();
20978 _setButtonClass: function(/*Number*/ scroll
){
20980 // Disables the left scroll button if the tabs are scrolled all the way to the left,
20981 // or the right scroll button in the opposite case.
20983 // amount of horizontal scroll
20985 var scrollBounds
= this._getScrollBounds();
20986 this._leftBtn
.set("disabled", scroll
<= scrollBounds
.min
);
20987 this._rightBtn
.set("disabled", scroll
>= scrollBounds
.max
);
20992 dojo
.declare("dijit.layout._ScrollingTabControllerButtonMixin", null, {
20993 baseClass
: "dijitTab tabStripButton",
20995 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"),
20997 // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be
20998 // able to tab to the left/right/menu buttons
21001 // Similarly, override FormWidget.isFocusable() because clicking a button shouldn't focus it
21002 // either (this override avoids focus() call in FormWidget.js)
21003 isFocusable: function(){ return false; }
21006 dojo
.declare("dijit.layout._ScrollingTabControllerButton",
21007 [dijit
.form
.Button
, dijit
.layout
._ScrollingTabControllerButtonMixin
]);
21010 "dijit.layout._ScrollingTabControllerMenuButton",
21011 [dijit
.form
.Button
, dijit
._HasDropDown
, dijit
.layout
._ScrollingTabControllerButtonMixin
],
21013 // id of the TabContainer itself
21016 // -1 so user can't tab into the button, but so that button can still be focused programatically.
21017 // Because need to move focus to the button (or somewhere) before the menu is hidden or IE6 will crash.
21020 isLoaded: function(){
21021 // recreate menu every time, in case the TabContainer's list of children (or their icons/labels) have changed
21025 loadDropDown: function(callback
){
21026 this.dropDown
= new dijit
.Menu({
21027 id
: this.containerId
+ "_menu",
21031 var container
= dijit
.byId(this.containerId
);
21032 dojo
.forEach(container
.getChildren(), function(page
){
21033 var menuItem
= new dijit
.MenuItem({
21034 id
: page
.id
+ "_stcMi",
21036 iconClass
: page
.iconClass
,
21039 onClick: function(){
21040 container
.selectChild(page
);
21043 this.dropDown
.addChild(menuItem
);
21048 closeDropDown: function(/*Boolean*/ focus
){
21049 this.inherited(arguments
);
21051 this.dropDown
.destroyRecursive();
21052 delete this.dropDown
;
21059 if(!dojo
._hasResource
["dijit.layout.TabContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21060 dojo
._hasResource
["dijit.layout.TabContainer"] = true;
21061 dojo
.provide("dijit.layout.TabContainer");
21067 dojo
.declare("dijit.layout.TabContainer",
21068 dijit
.layout
._TabContainerBase
,
21071 // A Container with tabs to select each child (only one of which is displayed at a time).
21073 // A TabContainer is a container that has multiple panes, but shows only
21074 // one pane at a time. There are a set of tabs corresponding to each pane,
21075 // where each tab has the name (aka title) of the pane, and optionally a close button.
21077 // useMenu: [const] Boolean
21078 // True if a menu should be used to select tabs when they are too
21079 // wide to fit the TabContainer, false otherwise.
21082 // useSlider: [const] Boolean
21083 // True if a slider should be used to select tabs when they are too
21084 // wide to fit the TabContainer, false otherwise.
21087 // controllerWidget: String
21088 // An optional parameter to override the widget used to display the tab labels
21089 controllerWidget
: "",
21091 _makeController: function(/*DomNode*/ srcNode
){
21093 // Instantiate tablist controller widget and return reference to it.
21094 // Callback from _TabContainerBase.postCreate().
21096 // protected extension
21098 var cls
= this.baseClass
+ "-tabs" + (this.doLayout
? "" : " dijitTabNoLayout"),
21099 TabController
= dojo
.getObject(this.controllerWidget
);
21101 return new TabController({
21102 id
: this.id
+ "_tablist",
21105 tabPosition
: this.tabPosition
,
21106 doLayout
: this.doLayout
,
21107 containerId
: this.id
,
21109 nested
: this.nested
,
21110 useMenu
: this.useMenu
,
21111 useSlider
: this.useSlider
,
21112 tabStripClass
: this.tabStrip
? this.baseClass
+ (this.tabStrip
? "":"No") + "Strip": null
21116 postMixInProperties: function(){
21117 this.inherited(arguments
);
21119 // Scrolling controller only works for horizontal non-nested tabs
21120 if(!this.controllerWidget
){
21121 this.controllerWidget
= (this.tabPosition
== "top" || this.tabPosition
== "bottom") && !this.nested
?
21122 "dijit.layout.ScrollingTabController" : "dijit.layout.TabController";
21129 if(!dojo
._hasResource
["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21130 dojo
._hasResource
["dojo.number"] = true;
21131 dojo
.provide("dojo.number");
21137 dojo
.getObject("number", true, dojo
);
21141 // summary: localized formatting and parsing routines for Number
21144 dojo.number.__FormatOptions = function(){
21145 // pattern: String?
21146 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21147 // with this string. Default value is based on locale. Overriding this property will defeat
21148 // localization. Literal characters in patterns are not supported.
21150 // choose a format type based on the locale from the following:
21151 // decimal, scientific (not yet supported), percent, currency. decimal by default.
21153 // fixed number of decimal places to show. This overrides any
21154 // information in the provided pattern.
21156 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
21157 // means do not round.
21159 // override the locale used to determine formatting rules
21160 // fractional: Boolean?
21161 // If false, show no decimal places, overriding places and pattern settings.
21162 this.pattern = pattern;
21164 this.places = places;
21165 this.round = round;
21166 this.locale = locale;
21167 this.fractional = fractional;
21171 dojo
.number
.format = function(/*Number*/value
, /*dojo.number.__FormatOptions?*/options
){
21173 // Format a Number as a String, using locale-specific settings
21175 // Create a string from a Number using a known localized pattern.
21176 // Formatting patterns appropriate to the locale are chosen from the
21177 // [Common Locale Data Repository](http://unicode.org/cldr) as well as the appropriate symbols and
21179 // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null.
21181 // the number to be formatted
21183 options
= dojo
.mixin({}, options
|| {});
21184 var locale
= dojo
.i18n
.normalizeLocale(options
.locale
),
21185 bundle
= dojo
.i18n
.getLocalization("dojo.cldr", "number", locale
);
21186 options
.customs
= bundle
;
21187 var pattern
= options
.pattern
|| bundle
[(options
.type
|| "decimal") + "Format"];
21188 if(isNaN(value
) || Math
.abs(value
) == Infinity
){ return null; } // null
21189 return dojo
.number
._applyPattern(value
, pattern
, options
); // String
21192 //dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
21193 dojo
.number
._numberPatternRE
= /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough
21195 dojo
.number
._applyPattern = function(/*Number*/value
, /*String*/pattern
, /*dojo.number.__FormatOptions?*/options
){
21197 // Apply pattern to format value as a string using options. Gives no
21198 // consideration to local customs.
21200 // the number to be formatted.
21202 // a pattern string as described by
21203 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21204 // options: dojo.number.__FormatOptions?
21205 // _applyPattern is usually called via `dojo.number.format()` which
21206 // populates an extra property in the options parameter, "customs".
21207 // The customs object specifies group and decimal parameters if set.
21209 //TODO: support escapes
21210 options
= options
|| {};
21211 var group
= options
.customs
.group
,
21212 decimal = options
.customs
.decimal,
21213 patternList
= pattern
.split(';'),
21214 positivePattern
= patternList
[0];
21215 pattern
= patternList
[(value
< 0) ? 1 : 0] || ("-" + positivePattern
);
21217 //TODO: only test against unescaped
21218 if(pattern
.indexOf('%') != -1){
21220 }else if(pattern
.indexOf('\u2030') != -1){
21221 value
*= 1000; // per mille
21222 }else if(pattern
.indexOf('\u00a4') != -1){
21223 group
= options
.customs
.currencyGroup
|| group
;//mixins instead?
21224 decimal = options
.customs
.currencyDecimal
|| decimal;// Should these be mixins instead?
21225 pattern
= pattern
.replace(/\u00a4{1,3}/, function(match
){
21226 var prop
= ["symbol", "currency", "displayName"][match
.length
-1];
21227 return options
[prop
] || options
.currency
|| "";
21229 }else if(pattern
.indexOf('E') != -1){
21230 throw new Error("exponential notation not supported");
21233 //TODO: support @ sig figs?
21234 var numberPatternRE
= dojo
.number
._numberPatternRE
;
21235 var numberPattern
= positivePattern
.match(numberPatternRE
);
21236 if(!numberPattern
){
21237 throw new Error("unable to find a number expression in pattern: "+pattern
);
21239 if(options
.fractional
=== false){ options
.places
= 0; }
21240 return pattern
.replace(numberPatternRE
,
21241 dojo
.number
._formatAbsolute(value
, numberPattern
[0], {decimal: decimal, group
: group
, places
: options
.places
, round
: options
.round
}));
21244 dojo
.number
.round = function(/*Number*/value
, /*Number?*/places
, /*Number?*/increment
){
21246 // Rounds to the nearest value with the given number of decimal places, away from zero
21248 // Rounds to the nearest value with the given number of decimal places, away from zero if equal.
21249 // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by
21250 // fractional increments also, such as the nearest quarter.
21251 // NOTE: Subject to floating point errors. See dojox.math.round for experimental workaround.
21253 // The number to round
21255 // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding.
21256 // Must be non-negative.
21258 // Rounds next place to nearest value of increment/10. 10 by default.
21260 // >>> dojo.number.round(-0.5)
21262 // >>> dojo.number.round(162.295, 2)
21263 // 162.29 // note floating point error. Should be 162.3
21264 // >>> dojo.number.round(10.71, 0, 2.5)
21266 var factor
= 10 / (increment
|| 10);
21267 return (factor
* +value
).toFixed(places
) / factor
; // Number
21270 if((0.9).toFixed() == 0){
21271 // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit
21272 // is just after the rounding place and is >=5
21274 var round
= dojo
.number
.round
;
21275 dojo
.number
.round = function(v
, p
, m
){
21276 var d
= Math
.pow(10, -p
|| 0), a
= Math
.abs(v
);
21277 if(!v
|| a
>= d
|| a
* Math
.pow(10, p
+ 1) < 5){
21280 return round(v
, p
, m
) + (v
> 0 ? d
: -d
);
21286 dojo.number.__FormatAbsoluteOptions = function(){
21287 // decimal: String?
21288 // the decimal separator
21290 // the group separator
21291 // places: Number?|String?
21292 // number of decimal places. the range "n,m" will format to m places.
21294 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
21295 // means don't round.
21296 this.decimal = decimal;
21297 this.group = group;
21298 this.places = places;
21299 this.round = round;
21303 dojo
.number
._formatAbsolute = function(/*Number*/value
, /*String*/pattern
, /*dojo.number.__FormatAbsoluteOptions?*/options
){
21305 // Apply numeric pattern to absolute value using options. Gives no
21306 // consideration to local customs.
21308 // the number to be formatted, ignores sign
21310 // the number portion of a pattern (e.g. `#,##0.00`)
21311 options
= options
|| {};
21312 if(options
.places
=== true){options
.places
=0;}
21313 if(options
.places
=== Infinity
){options
.places
=6;} // avoid a loop; pick a limit
21315 var patternParts
= pattern
.split("."),
21316 comma
= typeof options
.places
== "string" && options
.places
.indexOf(","),
21317 maxPlaces
= options
.places
;
21319 maxPlaces
= options
.places
.substring(comma
+ 1);
21320 }else if(!(maxPlaces
>= 0)){
21321 maxPlaces
= (patternParts
[1] || []).length
;
21323 if(!(options
.round
< 0)){
21324 value
= dojo
.number
.round(value
, maxPlaces
, options
.round
);
21327 var valueParts
= String(Math
.abs(value
)).split("."),
21328 fractional
= valueParts
[1] || "";
21329 if(patternParts
[1] || options
.places
){
21331 options
.places
= options
.places
.substring(0, comma
);
21333 // Pad fractional with trailing zeros
21334 var pad
= options
.places
!== undefined ? options
.places
: (patternParts
[1] && patternParts
[1].lastIndexOf("0") + 1);
21335 if(pad
> fractional
.length
){
21336 valueParts
[1] = dojo
.string
.pad(fractional
, pad
, '0', true);
21339 // Truncate fractional
21340 if(maxPlaces
< fractional
.length
){
21341 valueParts
[1] = fractional
.substr(0, maxPlaces
);
21344 if(valueParts
[1]){ valueParts
.pop(); }
21347 // Pad whole with leading zeros
21348 var patternDigits
= patternParts
[0].replace(',', '');
21349 pad
= patternDigits
.indexOf("0");
21351 pad
= patternDigits
.length
- pad
;
21352 if(pad
> valueParts
[0].length
){
21353 valueParts
[0] = dojo
.string
.pad(valueParts
[0], pad
);
21357 if(patternDigits
.indexOf("#") == -1){
21358 valueParts
[0] = valueParts
[0].substr(valueParts
[0].length
- pad
);
21362 // Add group separators
21363 var index
= patternParts
[0].lastIndexOf(','),
21364 groupSize
, groupSize2
;
21366 groupSize
= patternParts
[0].length
- index
- 1;
21367 var remainder
= patternParts
[0].substr(0, index
);
21368 index
= remainder
.lastIndexOf(',');
21370 groupSize2
= remainder
.length
- index
- 1;
21374 for(var whole
= valueParts
[0]; whole
;){
21375 var off
= whole
.length
- groupSize
;
21376 pieces
.push((off
> 0) ? whole
.substr(off
) : whole
);
21377 whole
= (off
> 0) ? whole
.slice(0, off
) : "";
21379 groupSize
= groupSize2
;
21383 valueParts
[0] = pieces
.reverse().join(options
.group
|| ",");
21385 return valueParts
.join(options
.decimal || ".");
21389 dojo.number.__RegexpOptions = function(){
21390 // pattern: String?
21391 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21392 // with this string. Default value is based on locale. Overriding this property will defeat
21395 // choose a format type based on the locale from the following:
21396 // decimal, scientific (not yet supported), percent, currency. decimal by default.
21398 // override the locale used to determine formatting rules
21399 // strict: Boolean?
21400 // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
21401 // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
21402 // places: Number|String?
21403 // number of decimal places to accept: Infinity, a positive number, or
21404 // a range "n,m". Defined by pattern or Infinity if pattern not provided.
21405 this.pattern = pattern;
21407 this.locale = locale;
21408 this.strict = strict;
21409 this.places = places;
21412 dojo
.number
.regexp = function(/*dojo.number.__RegexpOptions?*/options
){
21414 // Builds the regular needed to parse a number
21416 // Returns regular expression with positive and negative match, group
21417 // and decimal separators
21418 return dojo
.number
._parseInfo(options
).regexp
; // String
21421 dojo
.number
._parseInfo = function(/*Object?*/options
){
21422 options
= options
|| {};
21423 var locale
= dojo
.i18n
.normalizeLocale(options
.locale
),
21424 bundle
= dojo
.i18n
.getLocalization("dojo.cldr", "number", locale
),
21425 pattern
= options
.pattern
|| bundle
[(options
.type
|| "decimal") + "Format"],
21427 group
= bundle
.group
,
21428 decimal = bundle
.decimal,
21431 if(pattern
.indexOf('%') != -1){
21433 }else if(pattern
.indexOf('\u2030') != -1){
21434 factor
/= 1000; // per mille
21436 var isCurrency
= pattern
.indexOf('\u00a4') != -1;
21438 group
= bundle
.currencyGroup
|| group
;
21439 decimal = bundle
.currencyDecimal
|| decimal;
21443 //TODO: handle quoted escapes
21444 var patternList
= pattern
.split(';');
21445 if(patternList
.length
== 1){
21446 patternList
.push("-" + patternList
[0]);
21449 var re
= dojo
.regexp
.buildGroupRE(patternList
, function(pattern
){
21450 pattern
= "(?:"+dojo
.regexp
.escapeString(pattern
, '.')+")";
21451 return pattern
.replace(dojo
.number
._numberPatternRE
, function(format
){
21454 separator
: options
.strict
? group
: [group
,""],
21455 fractional
: options
.fractional
,
21460 parts
= format
.split('.'),
21461 places
= options
.places
;
21463 // special condition for percent (factor != 1)
21464 // allow decimal places even if not specified in pattern
21465 if(parts
.length
== 1 && factor
!= 1){
21468 if(parts
.length
== 1 || places
=== 0){
21469 flags
.fractional
= false;
21471 if(places
=== undefined){ places
= options
.pattern
? parts
[1].lastIndexOf('0') + 1 : Infinity
; }
21472 if(places
&& options
.fractional
== undefined){flags
.fractional
= true;} // required fractional, unless otherwise specified
21473 if(!options
.places
&& (places
< parts
[1].length
)){ places
+= "," + parts
[1].length
; }
21474 flags
.places
= places
;
21476 var groups
= parts
[0].split(',');
21477 if(groups
.length
> 1){
21478 flags
.groupSize
= groups
.pop().length
;
21479 if(groups
.length
> 1){
21480 flags
.groupSize2
= groups
.pop().length
;
21483 return "("+dojo
.number
._realNumberRegexp(flags
)+")";
21488 // substitute the currency symbol for the placeholder in the pattern
21489 re
= re
.replace(/([\s\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match
, before
, target
, after
){
21490 var prop
= ["symbol", "currency", "displayName"][target
.length
-1],
21491 symbol
= dojo
.regexp
.escapeString(options
[prop
] || options
.currency
|| "");
21492 before
= before
? "[\\s\\xa0]" : "";
21493 after
= after
? "[\\s\\xa0]" : "";
21494 if(!options
.strict
){
21495 if(before
){before
+= "*";}
21496 if(after
){after
+= "*";}
21497 return "(?:"+before
+symbol
+after
+")?";
21499 return before
+symbol
+after
;
21503 //TODO: substitute localized sign/percent/permille/etc.?
21505 // normalize whitespace and return
21506 return {regexp
: re
.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group
: group
, decimal: decimal, factor
: factor
}; // Object
21510 dojo.number.__ParseOptions = function(){
21511 // pattern: String?
21512 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21513 // with this string. Default value is based on locale. Overriding this property will defeat
21514 // localization. Literal characters in patterns are not supported.
21516 // choose a format type based on the locale from the following:
21517 // decimal, scientific (not yet supported), percent, currency. decimal by default.
21519 // override the locale used to determine formatting rules
21520 // strict: Boolean?
21521 // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
21522 // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
21523 // fractional: Boolean?|Array?
21524 // Whether to include the fractional portion, where the number of decimal places are implied by pattern
21525 // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional.
21526 this.pattern = pattern;
21528 this.locale = locale;
21529 this.strict = strict;
21530 this.fractional = fractional;
21533 dojo
.number
.parse = function(/*String*/expression
, /*dojo.number.__ParseOptions?*/options
){
21535 // Convert a properly formatted string to a primitive Number, using
21536 // locale-specific settings.
21538 // Create a Number from a string using a known localized pattern.
21539 // Formatting patterns are chosen appropriate to the locale
21540 // and follow the syntax described by
21541 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21542 // Note that literal characters in patterns are not supported.
21544 // A string representation of a Number
21545 var info
= dojo
.number
._parseInfo(options
),
21546 results
= (new RegExp("^"+info
.regexp
+"$")).exec(expression
);
21550 var absoluteMatch
= results
[1]; // match for the positive expression
21555 // matched the negative pattern
21556 absoluteMatch
=results
[2];
21560 // Transform it to something Javascript can parse as a number. Normalize
21561 // decimal point and strip out group separators or alternate forms of whitespace
21562 absoluteMatch
= absoluteMatch
.
21563 replace(new RegExp("["+info
.group
+ "\\s\\xa0"+"]", "g"), "").
21564 replace(info
.decimal, ".");
21565 // Adjust for negative sign, percent, etc. as necessary
21566 return absoluteMatch
* info
.factor
; //Number
21570 dojo.number.__RealNumberRegexpFlags = function(){
21572 // The integer number of decimal places or a range given as "n,m". If
21573 // not given, the decimal part is optional and the number of places is
21575 // decimal: String?
21576 // A string for the character used as the decimal point. Default
21578 // fractional: Boolean?|Array?
21579 // Whether decimal places are used. Can be true, false, or [true,
21580 // false]. Default is [true, false] which means optional.
21581 // exponent: Boolean?|Array?
21582 // Express in exponential notation. Can be true, false, or [true,
21583 // false]. Default is [true, false], (i.e. will match if the
21584 // exponential part is present are not).
21585 // eSigned: Boolean?|Array?
21586 // The leading plus-or-minus sign on the exponent. Can be true,
21587 // false, or [true, false]. Default is [true, false], (i.e. will
21588 // match if it is signed or unsigned). flags in regexp.integer can be
21590 this.places = places;
21591 this.decimal = decimal;
21592 this.fractional = fractional;
21593 this.exponent = exponent;
21594 this.eSigned = eSigned;
21598 dojo
.number
._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags
){
21600 // Builds a regular expression to match a real number in exponential
21603 // assign default values to missing parameters
21604 flags
= flags
|| {};
21605 //TODO: use mixin instead?
21606 if(!("places" in flags
)){ flags
.places
= Infinity
; }
21607 if(typeof flags
.decimal != "string"){ flags
.decimal = "."; }
21608 if(!("fractional" in flags
) || /^0/.test(flags
.places
)){ flags
.fractional
= [true, false]; }
21609 if(!("exponent" in flags
)){ flags
.exponent
= [true, false]; }
21610 if(!("eSigned" in flags
)){ flags
.eSigned
= [true, false]; }
21612 var integerRE
= dojo
.number
._integerRegexp(flags
),
21613 decimalRE
= dojo
.regexp
.buildGroupRE(flags
.fractional
,
21616 if(q
&& (flags
.places
!==0)){
21617 re
= "\\" + flags
.decimal;
21618 if(flags
.places
== Infinity
){
21619 re
= "(?:" + re
+ "\\d+)?";
21621 re
+= "\\d{" + flags
.places
+ "}";
21629 var exponentRE
= dojo
.regexp
.buildGroupRE(flags
.exponent
,
21631 if(q
){ return "([eE]" + dojo
.number
._integerRegexp({ signed
: flags
.eSigned
}) + ")"; }
21636 var realRE
= integerRE
+ decimalRE
;
21637 // allow for decimals without integers, e.g. .25
21638 if(decimalRE
){realRE
= "(?:(?:"+ realRE
+ ")|(?:" + decimalRE
+ "))";}
21639 return realRE
+ exponentRE
; // String
21643 dojo.number.__IntegerRegexpFlags = function(){
21644 // signed: Boolean?
21645 // The leading plus-or-minus sign. Can be true, false, or `[true,false]`.
21646 // Default is `[true, false]`, (i.e. will match if it is signed
21648 // separator: String?
21649 // The character used as the thousands separator. Default is no
21650 // separator. For more than one symbol use an array, e.g. `[",", ""]`,
21651 // makes ',' optional.
21652 // groupSize: Number?
21653 // group size between separators
21654 // groupSize2: Number?
21655 // second grouping, where separators 2..n have a different interval than the first separator (for India)
21656 this.signed = signed;
21657 this.separator = separator;
21658 this.groupSize = groupSize;
21659 this.groupSize2 = groupSize2;
21663 dojo
.number
._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags
){
21665 // Builds a regular expression that matches an integer
21667 // assign default values to missing parameters
21668 flags
= flags
|| {};
21669 if(!("signed" in flags
)){ flags
.signed
= [true, false]; }
21670 if(!("separator" in flags
)){
21671 flags
.separator
= "";
21672 }else if(!("groupSize" in flags
)){
21673 flags
.groupSize
= 3;
21676 var signRE
= dojo
.regexp
.buildGroupRE(flags
.signed
,
21677 function(q
){ return q
? "[-+]" : ""; },
21681 var numberRE
= dojo
.regexp
.buildGroupRE(flags
.separator
,
21687 sep
= dojo
.regexp
.escapeString(sep
);
21688 if(sep
== " "){ sep
= "\\s"; }
21689 else if(sep
== "\xa0"){ sep
= "\\s\\xa0"; }
21691 var grp
= flags
.groupSize
, grp2
= flags
.groupSize2
;
21692 //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933
21694 var grp2RE
= "(?:0|[1-9]\\d{0," + (grp2
-1) + "}(?:[" + sep
+ "]\\d{" + grp2
+ "})*[" + sep
+ "]\\d{" + grp
+ "})";
21695 return ((grp
-grp2
) > 0) ? "(?:" + grp2RE
+ "|(?:0|[1-9]\\d{0," + (grp
-1) + "}))" : grp2RE
;
21697 return "(?:0|[1-9]\\d{0," + (grp
-1) + "}(?:[" + sep
+ "]\\d{" + grp
+ "})*)";
21702 return signRE
+ numberRE
; // String
21707 if(!dojo
._hasResource
["dijit.ProgressBar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21708 dojo
._hasResource
["dijit.ProgressBar"] = true;
21709 dojo
.provide("dijit.ProgressBar");
21716 dojo
.declare("dijit.ProgressBar", [dijit
._Widget
, dijit
._Templated
], {
21718 // A progress indication widget, showing the amount completed
21719 // (often the percentage completed) of a task.
21722 // | <div dojoType="ProgressBar"
21724 // | value="..." maximum="...">
21727 // progress: [const] String (Percentage or Number)
21728 // Number or percentage indicating amount of task completed.
21729 // Deprecated. Use "value" instead.
21732 // value: String (Percentage or Number)
21733 // Number or percentage indicating amount of task completed.
21734 // With "%": percentage value, 0% <= progress <= 100%, or
21735 // without "%": absolute value, 0 <= progress <= maximum.
21736 // Infinity means that the progress bar is indeterminate.
21739 // maximum: [const] Float
21740 // Max sample number
21743 // places: [const] Number
21744 // Number of places to show in values; 0 by default
21747 // indeterminate: [const] Boolean
21748 // If false: show progress value (number or percentage).
21749 // If true: show that a process is underway but that the amount completed is unknown.
21750 // Deprecated. Use "value" instead.
21751 indeterminate
: false,
21754 // Label on progress bar. Defaults to percentage for determinate progress bar and
21755 // blank for indeterminate progress bar.
21759 // this is the field name (for a form) if set. This needs to be set if you want to use
21760 // this widget in a dijit.form.Form widget (such as dijit.Dialog)
21763 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"),
21765 // _indeterminateHighContrastImagePath: [private] dojo._URL
21766 // URL to image to use for indeterminate progress bar when display is in high contrast mode
21767 _indeterminateHighContrastImagePath
:
21768 dojo
.moduleUrl("dijit", "themes/a11y/indeterminate_progress.gif"),
21770 postMixInProperties: function(){
21771 this.inherited(arguments
);
21772 if(!("value" in this.params
)){
21773 this.value
= this.indeterminate
? Infinity
: this.progress
;
21777 buildRendering: function(){
21778 this.inherited(arguments
);
21779 this.indeterminateHighContrastImage
.setAttribute("src",
21780 this._indeterminateHighContrastImagePath
.toString());
21784 update: function(/*Object?*/attributes
){
21786 // Internal method to change attributes of ProgressBar, similar to set(hash). Users should call
21787 // set("value", ...) rather than calling this method directly.
21789 // May provide progress and/or maximum properties on this parameter;
21790 // see attribute specs for details.
21792 // | myProgressBar.update({'indeterminate': true});
21793 // | myProgressBar.update({'progress': 80});
21794 // | myProgressBar.update({'indeterminate': true, label:"Loading ..." })
21798 // TODO: deprecate this method and use set() instead
21800 dojo
.mixin(this, attributes
|| {});
21801 var tip
= this.internalProgress
, ap
= this.domNode
;
21803 if(this.indeterminate
){
21804 dijit
.removeWaiState(ap
, "valuenow");
21805 dijit
.removeWaiState(ap
, "valuemin");
21806 dijit
.removeWaiState(ap
, "valuemax");
21808 if(String(this.progress
).indexOf("%") != -1){
21809 percent
= Math
.min(parseFloat(this.progress
)/100, 1);
21810 this.progress
= percent
* this.maximum
;
21812 this.progress
= Math
.min(this.progress
, this.maximum
);
21813 percent
= this.progress
/ this.maximum
;
21816 dijit
.setWaiState(ap
, "describedby", this.labelNode
.id
);
21817 dijit
.setWaiState(ap
, "valuenow", this.progress
);
21818 dijit
.setWaiState(ap
, "valuemin", 0);
21819 dijit
.setWaiState(ap
, "valuemax", this.maximum
);
21821 this.labelNode
.innerHTML
= this.report(percent
);
21823 dojo
.toggleClass(this.domNode
, "dijitProgressBarIndeterminate", this.indeterminate
);
21824 tip
.style
.width
= (percent
* 100) + "%";
21828 _setValueAttr: function(v
){
21829 this._set("value", v
);
21831 this.update({indeterminate
:true});
21833 this.update({indeterminate
:false, progress
:v
});
21837 _setLabelAttr: function(label
){
21838 this._set("label", label
);
21842 _setIndeterminateAttr: function(indeterminate
){
21843 // Deprecated, use set("value", ...) instead
21844 this.indeterminate
= indeterminate
;
21848 report: function(/*float*/percent
){
21850 // Generates message to show inside progress bar (normally indicating amount of task completed).
21851 // May be overridden.
21855 return this.label
? this.label
:
21856 (this.indeterminate
? " " : dojo
.number
.format(percent
, { type
: "percent", places
: this.places
, locale
: this.lang
}));
21859 onChange: function(){
21861 // Callback fired when progress updates.
21869 if(!dojo
._hasResource
["dijit.ToolbarSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21870 dojo
._hasResource
["dijit.ToolbarSeparator"] = true;
21871 dojo
.provide("dijit.ToolbarSeparator");
21876 dojo
.declare("dijit.ToolbarSeparator",
21877 [ dijit
._Widget
, dijit
._Templated
],
21880 // A spacer between two `dijit.Toolbar` items
21881 templateString
: '<div class="dijitToolbarSeparator dijitInline" role="presentation"></div>',
21882 buildRendering: function(){
21883 this.inherited(arguments
);
21884 dojo
.setSelectable(this.domNode
, false);
21886 isFocusable: function(){
21888 // This widget isn't focusable, so pass along that fact.
21898 if(!dojo
._hasResource
["dijit.Toolbar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21899 dojo
._hasResource
["dijit.Toolbar"] = true;
21900 dojo
.provide("dijit.Toolbar");
21907 // Note: require of ToolbarSeparator is for back-compat, remove for 2.0
21909 dojo
.declare("dijit.Toolbar",
21910 [dijit
._Widget
, dijit
._Templated
, dijit
._KeyNavContainer
],
21913 // A Toolbar widget, used to hold things like `dijit.Editor` buttons
21916 '<div class="dijit" role="toolbar" tabIndex="${tabIndex}" dojoAttachPoint="containerNode">' +
21917 // '<table style="table-layout: fixed" class="dijitReset dijitToolbarTable">' + // factor out style
21918 // '<tr class="dijitReset" dojoAttachPoint="containerNode"></tr>'+
21922 baseClass
: "dijitToolbar",
21924 postCreate: function(){
21925 this.inherited(arguments
);
21927 this.connectKeyNavHandlers(
21928 this.isLeftToRight() ? [dojo
.keys
.LEFT_ARROW
] : [dojo
.keys
.RIGHT_ARROW
],
21929 this.isLeftToRight() ? [dojo
.keys
.RIGHT_ARROW
] : [dojo
.keys
.LEFT_ARROW
]
21933 startup: function(){
21934 if(this._started
){ return; }
21936 this.startupKeyNavChildren();
21938 this.inherited(arguments
);
21945 if(!dojo
._hasResource
["dojo.DeferredList"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21946 dojo
._hasResource
["dojo.DeferredList"] = true;
21947 dojo
.provide("dojo.DeferredList");
21950 dojo
.DeferredList = function(/*Array*/ list
, /*Boolean?*/ fireOnOneCallback
, /*Boolean?*/ fireOnOneErrback
, /*Boolean?*/ consumeErrors
, /*Function?*/ canceller
){
21952 // Provides event handling for a group of Deferred objects.
21954 // DeferredList takes an array of existing deferreds and returns a new deferred of its own
21955 // this new deferred will typically have its callback fired when all of the deferreds in
21956 // the given list have fired their own deferreds. The parameters `fireOnOneCallback` and
21957 // fireOnOneErrback, will fire before all the deferreds as appropriate
21960 // The list of deferreds to be synchronizied with this DeferredList
21961 // fireOnOneCallback:
21962 // Will cause the DeferredLists callback to be fired as soon as any
21963 // of the deferreds in its list have been fired instead of waiting until
21964 // the entire list has finished
21965 // fireonOneErrback:
21966 // Will cause the errback to fire upon any of the deferreds errback
21968 // A deferred canceller function, see dojo.Deferred
21969 var resultList
= [];
21970 dojo
.Deferred
.call(this);
21972 if(list
.length
=== 0 && !fireOnOneCallback
){
21973 this.resolve([0, []]);
21976 dojo
.forEach(list
, function(item
, i
){
21977 item
.then(function(result
){
21978 if(fireOnOneCallback
){
21979 self
.resolve([i
, result
]);
21981 addResult(true, result
);
21984 if(fireOnOneErrback
){
21985 self
.reject(error
);
21987 addResult(false, error
);
21994 function addResult(succeeded
, result
){
21995 resultList
[i
] = [succeeded
, result
];
21997 if(finished
=== list
.length
){
21998 self
.resolve(resultList
);
22004 dojo
.DeferredList
.prototype = new dojo
.Deferred();
22006 dojo
.DeferredList
.prototype.gatherResults= function(deferredList
){
22008 // Gathers the results of the deferreds for packaging
22009 // as the parameters to the Deferred Lists' callback
22011 var d
= new dojo
.DeferredList(deferredList
, false, true, false);
22012 d
.addCallback(function(results
){
22014 dojo
.forEach(results
, function(result
){
22015 ret
.push(result
[1]);
22024 if(!dojo
._hasResource
["dijit.tree.TreeStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
22025 dojo
._hasResource
["dijit.tree.TreeStoreModel"] = true;
22026 dojo
.provide("dijit.tree.TreeStoreModel");
22030 "dijit.tree.TreeStoreModel",
22034 // Implements dijit.Tree.model connecting to a store with a single
22035 // root item. Any methods passed into the constructor will override
22036 // the ones defined here.
22038 // store: dojo.data.Store
22039 // Underlying store
22042 // childrenAttrs: String[]
22043 // One or more attribute names (attributes in the dojo.data item) that specify that item's children
22044 childrenAttrs
: ["children"],
22046 // newItemIdAttr: String
22047 // Name of attribute in the Object passed to newItem() that specifies the id.
22049 // If newItemIdAttr is set then it's used when newItem() is called to see if an
22050 // item with the same id already exists, and if so just links to the old item
22051 // (so that the old item ends up with two parents).
22053 // Setting this to null or "" will make every drop create a new item.
22054 newItemIdAttr
: "id",
22056 // labelAttr: String
22057 // If specified, get label for tree node from this attribute, rather
22058 // than by calling store.getLabel()
22061 // root: [readonly] dojo.data.Item
22062 // Pointer to the root item (read only, not a parameter)
22066 // Specifies datastore query to return the root item for the tree.
22067 // Must only return a single item. Alternately can just pass in pointer
22073 // deferItemLoadingUntilExpand: Boolean
22074 // Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes
22075 // until they are expanded. This allows for lazying loading where only one
22076 // loadItem (and generally one network call, consequently) per expansion
22077 // (rather than one for each child).
22078 // This relies on partial loading of the children items; each children item of a
22079 // fully loaded item should contain the label and info about having children.
22080 deferItemLoadingUntilExpand
: false,
22082 constructor: function(/* Object */ args
){
22084 // Passed the arguments listed above (store, etc)
22088 dojo
.mixin(this, args
);
22090 this.connects
= [];
22092 var store
= this.store
;
22093 if(!store
.getFeatures()['dojo.data.api.Identity']){
22094 throw new Error("dijit.Tree: store must support dojo.data.Identity");
22097 // if the store supports Notification, subscribe to the notification events
22098 if(store
.getFeatures()['dojo.data.api.Notification']){
22099 this.connects
= this.connects
.concat([
22100 dojo
.connect(store
, "onNew", this, "onNewItem"),
22101 dojo
.connect(store
, "onDelete", this, "onDeleteItem"),
22102 dojo
.connect(store
, "onSet", this, "onSetItem")
22107 destroy: function(){
22108 dojo
.forEach(this.connects
, dojo
.disconnect
);
22109 // TODO: should cancel any in-progress processing of getRoot(), getChildren()
22112 // =======================================================================
22113 // Methods for traversing hierarchy
22115 getRoot: function(onItem
, onError
){
22117 // Calls onItem with the root item for the tree, possibly a fabricated item.
22118 // Calls onError on error.
22124 onComplete
: dojo
.hitch(this, function(items
){
22125 if(items
.length
!= 1){
22126 throw new Error(this.declaredClass
+ ": query " + dojo
.toJson(this.query
) + " returned " + items
.length
+
22127 " items, but must return exactly one item");
22129 this.root
= items
[0];
22137 mayHaveChildren: function(/*dojo.data.Item*/ item
){
22139 // Tells if an item has or may have children. Implementing logic here
22140 // avoids showing +/- expando icon for nodes that we know don't have children.
22141 // (For efficiency reasons we may not want to check if an element actually
22142 // has children until user clicks the expando node)
22143 return dojo
.some(this.childrenAttrs
, function(attr
){
22144 return this.store
.hasAttribute(item
, attr
);
22148 getChildren: function(/*dojo.data.Item*/ parentItem
, /*function(items)*/ onComplete
, /*function*/ onError
){
22150 // Calls onComplete() with array of child items of given parent item, all loaded.
22152 var store
= this.store
;
22153 if(!store
.isItemLoaded(parentItem
)){
22154 // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand
22155 // mode, so we will load it and just return the children (without loading each
22157 var getChildren
= dojo
.hitch(this, arguments
.callee
);
22160 onItem: function(parentItem
){
22161 getChildren(parentItem
, onComplete
, onError
);
22167 // get children of specified item
22168 var childItems
= [];
22169 for(var i
=0; i
<this.childrenAttrs
.length
; i
++){
22170 var vals
= store
.getValues(parentItem
, this.childrenAttrs
[i
]);
22171 childItems
= childItems
.concat(vals
);
22174 // count how many items need to be loaded
22175 var _waitCount
= 0;
22176 if(!this.deferItemLoadingUntilExpand
){
22177 dojo
.forEach(childItems
, function(item
){ if(!store
.isItemLoaded(item
)){ _waitCount
++; } });
22180 if(_waitCount
== 0){
22181 // all items are already loaded (or we aren't loading them). proceed...
22182 onComplete(childItems
);
22184 // still waiting for some or all of the items to load
22185 dojo
.forEach(childItems
, function(item
, idx
){
22186 if(!store
.isItemLoaded(item
)){
22189 onItem: function(item
){
22190 childItems
[idx
] = item
;
22191 if(--_waitCount
== 0){
22192 // all nodes have been loaded, send them to the tree
22193 onComplete(childItems
);
22203 // =======================================================================
22204 // Inspecting items
22206 isItem: function(/* anything */ something
){
22207 return this.store
.isItem(something
); // Boolean
22210 fetchItemByIdentity: function(/* object */ keywordArgs
){
22211 this.store
.fetchItemByIdentity(keywordArgs
);
22214 getIdentity: function(/* item */ item
){
22215 return this.store
.getIdentity(item
); // Object
22218 getLabel: function(/*dojo.data.Item*/ item
){
22220 // Get the label for an item
22221 if(this.labelAttr
){
22222 return this.store
.getValue(item
,this.labelAttr
); // String
22224 return this.store
.getLabel(item
); // String
22228 // =======================================================================
22231 newItem: function(/* dojo.dnd.Item */ args
, /*Item*/ parent
, /*int?*/ insertIndex
){
22233 // Creates a new item. See `dojo.data.api.Write` for details on args.
22234 // Used in drag & drop when item from external source dropped onto tree.
22236 // Developers will need to override this method if new items get added
22237 // to parents with multiple children attributes, in order to define which
22238 // children attribute points to the new item.
22240 var pInfo
= {parent
: parent
, attribute
: this.childrenAttrs
[0]}, LnewItem
;
22242 if(this.newItemIdAttr
&& args
[this.newItemIdAttr
]){
22243 // Maybe there's already a corresponding item in the store; if so, reuse it.
22244 this.fetchItemByIdentity({identity
: args
[this.newItemIdAttr
], scope
: this, onItem: function(item
){
22246 // There's already a matching item in store, use it
22247 this.pasteItem(item
, null, parent
, true, insertIndex
);
22249 // Create new item in the tree, based on the drag source.
22250 LnewItem
=this.store
.newItem(args
, pInfo
);
22251 if (LnewItem
&& (insertIndex
!=undefined)){
22252 // Move new item to desired position
22253 this.pasteItem(LnewItem
, parent
, parent
, false, insertIndex
);
22258 // [as far as we know] there is no id so we must assume this is a new item
22259 LnewItem
=this.store
.newItem(args
, pInfo
);
22260 if (LnewItem
&& (insertIndex
!=undefined)){
22261 // Move new item to desired position
22262 this.pasteItem(LnewItem
, parent
, parent
, false, insertIndex
);
22267 pasteItem: function(/*Item*/ childItem
, /*Item*/ oldParentItem
, /*Item*/ newParentItem
, /*Boolean*/ bCopy
, /*int?*/ insertIndex
){
22269 // Move or copy an item from one parent item to another.
22270 // Used in drag & drop
22271 var store
= this.store
,
22272 parentAttr
= this.childrenAttrs
[0]; // name of "children" attr in parent item
22274 // remove child from source item, and record the attribute that child occurred in
22276 dojo
.forEach(this.childrenAttrs
, function(attr
){
22277 if(store
.containsValue(oldParentItem
, attr
, childItem
)){
22279 var values
= dojo
.filter(store
.getValues(oldParentItem
, attr
), function(x
){
22280 return x
!= childItem
;
22282 store
.setValues(oldParentItem
, attr
, values
);
22289 // modify target item's children attribute to include this item
22291 if(typeof insertIndex
== "number"){
22292 // call slice() to avoid modifying the original array, confusing the data store
22293 var childItems
= store
.getValues(newParentItem
, parentAttr
).slice();
22294 childItems
.splice(insertIndex
, 0, childItem
);
22295 store
.setValues(newParentItem
, parentAttr
, childItems
);
22297 store
.setValues(newParentItem
, parentAttr
,
22298 store
.getValues(newParentItem
, parentAttr
).concat(childItem
));
22303 // =======================================================================
22306 onChange: function(/*dojo.data.Item*/ item
){
22308 // Callback whenever an item has changed, so that Tree
22309 // can update the label, icon, etc. Note that changes
22310 // to an item's children or parent(s) will trigger an
22311 // onChildrenChange() so you can ignore those changes here.
22316 onChildrenChange: function(/*dojo.data.Item*/ parent
, /*dojo.data.Item[]*/ newChildrenList
){
22318 // Callback to do notifications about new, updated, or deleted items.
22323 onDelete: function(/*dojo.data.Item*/ parent
, /*dojo.data.Item[]*/ newChildrenList
){
22325 // Callback when an item has been deleted.
22327 // Note that there will also be an onChildrenChange() callback for the parent
22333 // =======================================================================
22334 // Events from data store
22336 onNewItem: function(/* dojo.data.Item */ item
, /* Object */ parentInfo
){
22338 // Handler for when new items appear in the store, either from a drop operation
22339 // or some other way. Updates the tree view (if necessary).
22341 // If the new item is a child of an existing item,
22342 // calls onChildrenChange() with the new list of children
22343 // for that existing item.
22348 // We only care about the new item if it has a parent that corresponds to a TreeNode
22349 // we are currently displaying
22354 // Call onChildrenChange() on parent (ie, existing) item with new list of children
22355 // In the common case, the new list of children is simply parentInfo.newValue or
22356 // [ parentInfo.newValue ], although if items in the store has multiple
22357 // child attributes (see `childrenAttr`), then it's a superset of parentInfo.newValue,
22358 // so call getChildren() to be sure to get right answer.
22359 this.getChildren(parentInfo
.item
, dojo
.hitch(this, function(children
){
22360 this.onChildrenChange(parentInfo
.item
, children
);
22364 onDeleteItem: function(/*Object*/ item
){
22366 // Handler for delete notifications from underlying store
22367 this.onDelete(item
);
22370 onSetItem: function(/* item */ item
,
22371 /* attribute-name-string */ attribute
,
22372 /* object | array */ oldValue
,
22373 /* object | array */ newValue
){
22375 // Updates the tree view according to changes in the data store.
22377 // Handles updates to an item's children by calling onChildrenChange(), and
22378 // other updates to an item by calling onChange().
22380 // See `onNewItem` for more details on handling updates to an item's children.
22384 if(dojo
.indexOf(this.childrenAttrs
, attribute
) != -1){
22385 // item's children list changed
22386 this.getChildren(item
, dojo
.hitch(this, function(children
){
22387 // See comments in onNewItem() about calling getChildren()
22388 this.onChildrenChange(item
, children
);
22391 // item's label/icon/etc. changed.
22392 this.onChange(item
);
22399 if(!dojo
._hasResource
["dijit.tree.ForestStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
22400 dojo
._hasResource
["dijit.tree.ForestStoreModel"] = true;
22401 dojo
.provide("dijit.tree.ForestStoreModel");
22405 dojo
.declare("dijit.tree.ForestStoreModel", dijit
.tree
.TreeStoreModel
, {
22407 // Interface between a dijit.Tree and a dojo.data store that doesn't have a root item,
22408 // a.k.a. a store that has multiple "top level" items.
22411 // Use this class to wrap a dojo.data store, making all the items matching the specified query
22412 // appear as children of a fabricated "root item". If no query is specified then all the
22413 // items returned by fetch() on the underlying store become children of the root item.
22414 // This class allows dijit.Tree to assume a single root item, even if the store doesn't have one.
22416 // When using this class the developer must override a number of methods according to their app and
22417 // data, including:
22424 // Parameters to constructor
22427 // ID of fabricated root item
22430 // rootLabel: String
22431 // Label of fabricated root item
22435 // Specifies the set of children of the root item.
22437 // | {type:'continent'}
22440 // End of parameters to constructor
22442 constructor: function(params
){
22444 // Sets up variables, etc.
22448 // Make dummy root item
22453 label
: params
.rootLabel
,
22454 children
: params
.rootChildren
// optional param
22458 // =======================================================================
22459 // Methods for traversing hierarchy
22461 mayHaveChildren: function(/*dojo.data.Item*/ item
){
22463 // Tells if an item has or may have children. Implementing logic here
22464 // avoids showing +/- expando icon for nodes that we know don't have children.
22465 // (For efficiency reasons we may not want to check if an element actually
22466 // has children until user clicks the expando node)
22469 return item
=== this.root
|| this.inherited(arguments
);
22472 getChildren: function(/*dojo.data.Item*/ parentItem
, /*function(items)*/ callback
, /*function*/ onError
){
22474 // Calls onComplete() with array of child items of given parent item, all loaded.
22475 if(parentItem
=== this.root
){
22476 if(this.root
.children
){
22477 // already loaded, just return
22478 callback(this.root
.children
);
22482 onComplete
: dojo
.hitch(this, function(items
){
22483 this.root
.children
= items
;
22490 this.inherited(arguments
);
22494 // =======================================================================
22495 // Inspecting items
22497 isItem: function(/* anything */ something
){
22498 return (something
=== this.root
) ? true : this.inherited(arguments
);
22501 fetchItemByIdentity: function(/* object */ keywordArgs
){
22502 if(keywordArgs
.identity
== this.root
.id
){
22503 var scope
= keywordArgs
.scope
?keywordArgs
.scope
:dojo
.global
;
22504 if(keywordArgs
.onItem
){
22505 keywordArgs
.onItem
.call(scope
, this.root
);
22508 this.inherited(arguments
);
22512 getIdentity: function(/* item */ item
){
22513 return (item
=== this.root
) ? this.root
.id
: this.inherited(arguments
);
22516 getLabel: function(/* item */ item
){
22517 return (item
=== this.root
) ? this.root
.label
: this.inherited(arguments
);
22520 // =======================================================================
22523 newItem: function(/* dojo.dnd.Item */ args
, /*Item*/ parent
, /*int?*/ insertIndex
){
22525 // Creates a new item. See dojo.data.api.Write for details on args.
22526 // Used in drag & drop when item from external source dropped onto tree.
22527 if(parent
=== this.root
){
22528 this.onNewRootItem(args
);
22529 return this.store
.newItem(args
);
22531 return this.inherited(arguments
);
22535 onNewRootItem: function(args
){
22537 // User can override this method to modify a new element that's being
22538 // added to the root of the tree, for example to add a flag like root=true
22541 pasteItem: function(/*Item*/ childItem
, /*Item*/ oldParentItem
, /*Item*/ newParentItem
, /*Boolean*/ bCopy
, /*int?*/ insertIndex
){
22543 // Move or copy an item from one parent item to another.
22544 // Used in drag & drop
22545 if(oldParentItem
=== this.root
){
22547 // It's onLeaveRoot()'s responsibility to modify the item so it no longer matches
22548 // this.query... thus triggering an onChildrenChange() event to notify the Tree
22549 // that this element is no longer a child of the root node
22550 this.onLeaveRoot(childItem
);
22553 dijit
.tree
.TreeStoreModel
.prototype.pasteItem
.call(this, childItem
,
22554 oldParentItem
=== this.root
? null : oldParentItem
,
22555 newParentItem
=== this.root
? null : newParentItem
,
22559 if(newParentItem
=== this.root
){
22560 // It's onAddToRoot()'s responsibility to modify the item so it matches
22561 // this.query... thus triggering an onChildrenChange() event to notify the Tree
22562 // that this element is now a child of the root node
22563 this.onAddToRoot(childItem
);
22567 // =======================================================================
22568 // Handling for top level children
22570 onAddToRoot: function(/* item */ item
){
22572 // Called when item added to root of tree; user must override this method
22573 // to modify the item so that it matches the query for top level items
22575 // | store.setValue(item, "root", true);
22578 console
.log(this, ": item ", item
, " added to root");
22581 onLeaveRoot: function(/* item */ item
){
22583 // Called when item removed from root of tree; user must override this method
22584 // to modify the item so it doesn't match the query for top level items
22586 // | store.unsetAttribute(item, "root");
22589 console
.log(this, ": item ", item
, " removed from root");
22592 // =======================================================================
22593 // Events from data store
22595 _requeryTop: function(){
22596 // reruns the query for the children of the root node,
22597 // sending out an onSet notification if those children have changed
22598 var oldChildren
= this.root
.children
|| [];
22601 onComplete
: dojo
.hitch(this, function(newChildren
){
22602 this.root
.children
= newChildren
;
22604 // If the list of children or the order of children has changed...
22605 if(oldChildren
.length
!= newChildren
.length
||
22606 dojo
.some(oldChildren
, function(item
, idx
){ return newChildren
[idx
] != item
;})){
22607 this.onChildrenChange(this.root
, newChildren
);
22613 onNewItem: function(/* dojo.data.Item */ item
, /* Object */ parentInfo
){
22615 // Handler for when new items appear in the store. Developers should override this
22616 // method to be more efficient based on their app/data.
22618 // Note that the default implementation requeries the top level items every time
22619 // a new item is created, since any new item could be a top level item (even in
22620 // addition to being a child of another item, since items can have multiple parents).
22622 // If developers can detect which items are possible top level items (based on the item and the
22623 // parentInfo parameters), they should override this method to only call _requeryTop() for top
22624 // level items. Often all top level items have parentInfo==null, but
22625 // that will depend on which store you use and what your data is like.
22628 this._requeryTop();
22630 this.inherited(arguments
);
22633 onDeleteItem: function(/*Object*/ item
){
22635 // Handler for delete notifications from underlying store
22637 // check if this was a child of root, and if so send notification that root's children
22639 if(dojo
.indexOf(this.root
.children
, item
) != -1){
22640 this._requeryTop();
22643 this.inherited(arguments
);
22646 onSetItem: function(/* item */ item
,
22647 /* attribute-name-string */ attribute
,
22648 /* object | array */ oldValue
,
22649 /* object | array */ newValue
){
22651 // Updates the tree view according to changes to an item in the data store.
22652 // Developers should override this method to be more efficient based on their app/data.
22654 // Handles updates to an item's children by calling onChildrenChange(), and
22655 // other updates to an item by calling onChange().
22657 // Also, any change to any item re-executes the query for the tree's top-level items,
22658 // since this modified item may have started/stopped matching the query for top level items.
22660 // If possible, developers should override this function to only call _requeryTop() when
22661 // the change to the item has caused it to stop/start being a top level item in the tree.
22665 this._requeryTop();
22666 this.inherited(arguments
);
22673 if(!dojo
._hasResource
["dojo.dnd.Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
22674 dojo
._hasResource
["dojo.dnd.Container"] = true;
22675 dojo
.provide("dojo.dnd.Container");
22683 "Over" - mouse over a container
22684 Container item states:
22686 "Over" - mouse over a container item
22690 dojo.declare("dojo.dnd.__ContainerArgs", [], {
22691 creator: function(){
22693 // a creator function, which takes a data item, and returns an object like that:
22694 // {node: newNode, data: usedData, type: arrayOfStrings}
22697 // skipForm: Boolean
22698 // don't start the drag operation, if clicked on form elements
22701 // dropParent: Node||String
22702 // node or node's id to use as the parent node for dropped items
22703 // (must be underneath the 'node' parameter in the DOM)
22706 // _skipStartup: Boolean
22707 // skip startup(), which collects children, for deferred initialization
22708 // (this is used in the markup mode)
22709 _skipStartup: false
22712 dojo.dnd.Item = function(){
22714 // Represents (one of) the source node(s) being dragged.
22715 // Contains (at least) the "type" and "data" attributes.
22717 // Type(s) of this item, by default this is ["text"]
22719 // Logical representation of the object being dragged.
22720 // If the drag object's type is "text" then data is a String,
22721 // if it's another type then data could be a different Object,
22722 // perhaps a name/value hash.
22729 dojo
.declare("dojo.dnd.Container", null, {
22731 // a Container object, which knows when mouse hovers over it,
22732 // and over which element it hovers
22734 // object attributes (for markup)
22738 // current: DomNode
22739 // The DOM node the mouse is currently hovered over
22742 // map: Hash<String, dojo.dnd.Item>
22743 // Map from an item's id (which is also the DOMNode's id) to
22744 // the dojo.dnd.Item itself.
22748 constructor: function(node
, params
){
22750 // a constructor of the Container
22752 // node or node's id to build the container on
22753 // params: dojo.dnd.__ContainerArgs
22754 // a dictionary of parameters
22755 this.node
= dojo
.byId(node
);
22756 if(!params
){ params
= {}; }
22757 this.creator
= params
.creator
|| null;
22758 this.skipForm
= params
.skipForm
;
22759 this.parent
= params
.dropParent
&& dojo
.byId(params
.dropParent
);
22761 // class-specific variables
22763 this.current
= null;
22766 this.containerState
= "";
22767 dojo
.addClass(this.node
, "dojoDndContainer");
22769 // mark up children
22770 if(!(params
&& params
._skipStartup
)){
22776 dojo
.connect(this.node
, "onmouseover", this, "onMouseOver"),
22777 dojo
.connect(this.node
, "onmouseout", this, "onMouseOut"),
22778 // cancel text selection and text dragging
22779 dojo
.connect(this.node
, "ondragstart", this, "onSelectStart"),
22780 dojo
.connect(this.node
, "onselectstart", this, "onSelectStart")
22784 // object attributes (for markup)
22785 creator: function(){
22787 // creator function, dummy at the moment
22790 // abstract access to the map
22791 getItem: function(/*String*/ key
){
22793 // returns a data item by its key (id)
22794 return this.map
[key
]; // dojo.dnd.Item
22796 setItem: function(/*String*/ key
, /*dojo.dnd.Item*/ data
){
22798 // associates a data item with its key (id)
22799 this.map
[key
] = data
;
22801 delItem: function(/*String*/ key
){
22803 // removes a data item from the map by its key (id)
22804 delete this.map
[key
];
22806 forInItems: function(/*Function*/ f
, /*Object?*/ o
){
22808 // iterates over a data map skipping members that
22809 // are present in the empty object (IE and/or 3rd-party libraries).
22810 o
= o
|| dojo
.global
;
22811 var m
= this.map
, e
= dojo
.dnd
._empty
;
22813 if(i
in e
){ continue; }
22814 f
.call(o
, m
[i
], i
, this);
22816 return o
; // Object
22818 clearItems: function(){
22820 // removes all data items from the map
22825 getAllNodes: function(){
22827 // returns a list (an array) of all valid child nodes
22828 return dojo
.query("> .dojoDndItem", this.parent
); // NodeList
22832 // sync up the node list with the data map
22834 this.getAllNodes().forEach(function(node
){
22836 var item
= this.getItem(node
.id
);
22838 map
[node
.id
] = item
;
22842 node
.id
= dojo
.dnd
.getUniqueId();
22844 var type
= node
.getAttribute("dndType"),
22845 data
= node
.getAttribute("dndData");
22847 data
: data
|| node
.innerHTML
,
22848 type
: type
? type
.split(/\s*,\s*/) : ["text"]
22852 return this; // self
22854 insertNodes: function(data
, before
, anchor
){
22856 // inserts an array of new nodes before/after an anchor node
22858 // a list of data items, which should be processed by the creator function
22860 // insert before the anchor, if true, and after the anchor otherwise
22862 // the anchor node to be used as a point of insertion
22863 if(!this.parent
.firstChild
){
22867 anchor
= this.parent
.firstChild
;
22871 anchor
= anchor
.nextSibling
;
22875 for(var i
= 0; i
< data
.length
; ++i
){
22876 var t
= this._normalizedCreator(data
[i
]);
22877 this.setItem(t
.node
.id
, {data
: t
.data
, type
: t
.type
});
22878 this.parent
.insertBefore(t
.node
, anchor
);
22881 for(var i
= 0; i
< data
.length
; ++i
){
22882 var t
= this._normalizedCreator(data
[i
]);
22883 this.setItem(t
.node
.id
, {data
: t
.data
, type
: t
.type
});
22884 this.parent
.appendChild(t
.node
);
22887 return this; // self
22889 destroy: function(){
22891 // prepares this object to be garbage-collected
22892 dojo
.forEach(this.events
, dojo
.disconnect
);
22894 this.node
= this.parent
= this.current
= null;
22898 markupFactory: function(params
, node
){
22899 params
._skipStartup
= true;
22900 return new dojo
.dnd
.Container(node
, params
);
22902 startup: function(){
22904 // collects valid child items and populate the map
22906 // set up the real parent node
22908 // use the standard algorithm, if not assigned
22909 this.parent
= this.node
;
22910 if(this.parent
.tagName
.toLowerCase() == "table"){
22911 var c
= this.parent
.getElementsByTagName("tbody");
22912 if(c
&& c
.length
){ this.parent
= c
[0]; }
22915 this.defaultCreator
= dojo
.dnd
._defaultCreator(this.parent
);
22917 // process specially marked children
22922 onMouseOver: function(e
){
22924 // event processor for onmouseover
22927 var n
= e
.relatedTarget
;
22929 if(n
== this.node
){ break; }
22937 this._changeState("Container", "Over");
22938 this.onOverEvent();
22940 n
= this._getChildByEvent(e
);
22941 if(this.current
== n
){ return; }
22942 if(this.current
){ this._removeItemClass(this.current
, "Over"); }
22943 if(n
){ this._addItemClass(n
, "Over"); }
22946 onMouseOut: function(e
){
22948 // event processor for onmouseout
22951 for(var n
= e
.relatedTarget
; n
;){
22952 if(n
== this.node
){ return; }
22960 this._removeItemClass(this.current
, "Over");
22961 this.current
= null;
22963 this._changeState("Container", "");
22966 onSelectStart: function(e
){
22968 // event processor for onselectevent and ondragevent
22971 if(!this.skipForm
|| !dojo
.dnd
.isFormElement(e
)){
22977 onOverEvent: function(){
22979 // this function is called once, when mouse is over our container
22981 onOutEvent: function(){
22983 // this function is called once, when mouse is out of our container
22985 _changeState: function(type
, newState
){
22987 // changes a named state to new state value
22989 // a name of the state to change
22990 // newState: String
22992 var prefix
= "dojoDnd" + type
;
22993 var state
= type
.toLowerCase() + "State";
22994 //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
22995 dojo
.replaceClass(this.node
, prefix
+ newState
, prefix
+ this[state
]);
22996 this[state
] = newState
;
22998 _addItemClass: function(node
, type
){
23000 // adds a class with prefix "dojoDndItem"
23004 // a variable suffix for a class name
23005 dojo
.addClass(node
, "dojoDndItem" + type
);
23007 _removeItemClass: function(node
, type
){
23009 // removes a class with prefix "dojoDndItem"
23013 // a variable suffix for a class name
23014 dojo
.removeClass(node
, "dojoDndItem" + type
);
23016 _getChildByEvent: function(e
){
23018 // gets a child, which is under the mouse at the moment, or null
23021 var node
= e
.target
;
23023 for(var parent
= node
.parentNode
; parent
; node
= parent
, parent
= node
.parentNode
){
23024 if(parent
== this.parent
&& dojo
.hasClass(node
, "dojoDndItem")){ return node
; }
23029 _normalizedCreator: function(/*dojo.dnd.Item*/ item
, /*String*/ hint
){
23031 // adds all necessary data to the output of the user-supplied creator function
23032 var t
= (this.creator
|| this.defaultCreator
).call(this, item
, hint
);
23033 if(!dojo
.isArray(t
.type
)){ t
.type
= ["text"]; }
23034 if(!t
.node
.id
){ t
.node
.id
= dojo
.dnd
.getUniqueId(); }
23035 dojo
.addClass(t
.node
, "dojoDndItem");
23040 dojo
.dnd
._createNode = function(tag
){
23042 // returns a function, which creates an element of given tag
23043 // (SPAN by default) and sets its innerHTML to given text
23045 // a tag name or empty for SPAN
23046 if(!tag
){ return dojo
.dnd
._createSpan
; }
23047 return function(text
){ // Function
23048 return dojo
.create(tag
, {innerHTML
: text
}); // Node
23052 dojo
.dnd
._createTrTd = function(text
){
23054 // creates a TR/TD structure with given text as an innerHTML of TD
23057 var tr
= dojo
.create("tr");
23058 dojo
.create("td", {innerHTML
: text
}, tr
);
23062 dojo
.dnd
._createSpan = function(text
){
23064 // creates a SPAN element with given text as its innerHTML
23067 return dojo
.create("span", {innerHTML
: text
}); // Node
23070 // dojo.dnd._defaultCreatorNodes: Object
23071 // a dictionary that maps container tag names to child tag names
23072 dojo
.dnd
._defaultCreatorNodes
= {ul
: "li", ol
: "li", div
: "div", p
: "div"};
23074 dojo
.dnd
._defaultCreator = function(node
){
23076 // takes a parent node, and returns an appropriate creator function
23078 // a container node
23079 var tag
= node
.tagName
.toLowerCase();
23080 var c
= tag
== "tbody" || tag
== "thead" ? dojo
.dnd
._createTrTd
:
23081 dojo
.dnd
._createNode(dojo
.dnd
._defaultCreatorNodes
[tag
]);
23082 return function(item
, hint
){ // Function
23083 var isObj
= item
&& dojo
.isObject(item
), data
, type
, n
;
23084 if(isObj
&& item
.tagName
&& item
.nodeType
&& item
.getAttribute
){
23085 // process a DOM node
23086 data
= item
.getAttribute("dndData") || item
.innerHTML
;
23087 type
= item
.getAttribute("dndType");
23088 type
= type
? type
.split(/\s*,\s*/) : ["text"];
23089 n
= item
; // this node is going to be moved rather than copied
23091 // process a DnD item object or a string
23092 data
= (isObj
&& item
.data
) ? item
.data
: item
;
23093 type
= (isObj
&& item
.type
) ? item
.type
: ["text"];
23094 n
= (hint
== "avatar" ? dojo
.dnd
._createSpan
: c
)(String(data
));
23097 n
.id
= dojo
.dnd
.getUniqueId();
23099 return {node
: n
, data
: data
, type
: type
};
23105 if(!dojo
._hasResource
["dijit.tree._dndContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
23106 dojo
._hasResource
["dijit.tree._dndContainer"] = true;
23107 dojo
.provide("dijit.tree._dndContainer");
23112 dojo
.getObject("tree", true, dojo
);
23114 dijit
.tree
._compareNodes = function(n1
, n2
){
23119 if('sourceIndex' in document
.documentElement
){ //IE
23120 //TODO: does not yet work if n1 and/or n2 is a text node
23121 return n1
.sourceIndex
- n2
.sourceIndex
;
23122 }else if('compareDocumentPosition' in document
.documentElement
){ //FF, Opera
23123 return n1
.compareDocumentPosition(n2
) & 2 ? 1: -1;
23124 }else if(document
.createRange
){ //Webkit
23125 var r1
= doc
.createRange();
23126 r1
.setStartBefore(n1
);
23128 var r2
= doc
.createRange();
23129 r2
.setStartBefore(n2
);
23131 return r1
.compareBoundaryPoints(r1
.END_TO_END
, r2
);
23133 throw Error("dijit.tree._compareNodes don't know how to compare two different nodes in this browser");
23137 dojo
.declare("dijit.tree._dndContainer",
23142 // This is a base class for `dijit.tree._dndSelector`, and isn't meant to be used directly.
23143 // It's modeled after `dojo.dnd.Container`.
23148 // current: DomNode
23149 // The currently hovered TreeNode.rowNode (which is the DOM node
23150 // associated w/a given node in the tree, excluding it's descendants)
23154 constructor: function(tree
, params
){
23156 // A constructor of the Container
23158 // Node or node's id to build the container on
23159 // params: dijit.tree.__SourceArgs
23160 // A dict of parameters, which gets mixed into the object
23164 this.node
= tree
.domNode
; // TODO: rename; it's not a TreeNode but the whole Tree
23165 dojo
.mixin(this, params
);
23167 // class-specific variables
23169 this.current
= null; // current TreeNode's DOM node
23172 this.containerState
= "";
23173 dojo
.addClass(this.node
, "dojoDndContainer");
23177 // container level events
23178 dojo
.connect(this.node
, "onmouseenter", this, "onOverEvent"),
23179 dojo
.connect(this.node
, "onmouseleave", this, "onOutEvent"),
23181 // switching between TreeNodes
23182 dojo
.connect(this.tree
, "_onNodeMouseEnter", this, "onMouseOver"),
23183 dojo
.connect(this.tree
, "_onNodeMouseLeave", this, "onMouseOut"),
23185 // cancel text selection and text dragging
23186 dojo
.connect(this.node
, "ondragstart", dojo
, "stopEvent"),
23187 dojo
.connect(this.node
, "onselectstart", dojo
, "stopEvent")
23191 getItem: function(/*String*/ key
){
23193 // Returns the dojo.dnd.Item (representing a dragged node) by it's key (id).
23194 // Called by dojo.dnd.Source.checkAcceptance().
23198 var widget
= this.selection
[key
],
23204 return ret
; // dojo.dnd.Item
23207 destroy: function(){
23209 // Prepares this object to be garbage-collected
23211 dojo
.forEach(this.events
, dojo
.disconnect
);
23212 // this.clearItems();
23213 this.node
= this.parent
= null;
23217 onMouseOver: function(/*TreeNode*/ widget
, /*Event*/ evt
){
23219 // Called when mouse is moved over a TreeNode
23222 this.current
= widget
;
23225 onMouseOut: function(/*TreeNode*/ widget
, /*Event*/ evt
){
23227 // Called when mouse is moved away from a TreeNode
23230 this.current
= null;
23233 _changeState: function(type
, newState
){
23235 // Changes a named state to new state value
23237 // A name of the state to change
23238 // newState: String
23240 var prefix
= "dojoDnd" + type
;
23241 var state
= type
.toLowerCase() + "State";
23242 //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
23243 dojo
.replaceClass(this.node
, prefix
+ newState
, prefix
+ this[state
]);
23244 this[state
] = newState
;
23247 _addItemClass: function(node
, type
){
23249 // Adds a class with prefix "dojoDndItem"
23253 // A variable suffix for a class name
23254 dojo
.addClass(node
, "dojoDndItem" + type
);
23257 _removeItemClass: function(node
, type
){
23259 // Removes a class with prefix "dojoDndItem"
23263 // A variable suffix for a class name
23264 dojo
.removeClass(node
, "dojoDndItem" + type
);
23267 onOverEvent: function(){
23269 // This function is called once, when mouse is over our container
23272 this._changeState("Container", "Over");
23275 onOutEvent: function(){
23277 // This function is called once, when mouse is out of our container
23280 this._changeState("Container", "");
23286 if(!dojo
._hasResource
["dijit.tree._dndSelector"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
23287 dojo
._hasResource
["dijit.tree._dndSelector"] = true;
23288 dojo
.provide("dijit.tree._dndSelector");
23293 dojo
.declare("dijit.tree._dndSelector",
23294 dijit
.tree
._dndContainer
,
23297 // This is a base class for `dijit.tree.dndSource` , and isn't meant to be used directly.
23298 // It's based on `dojo.dnd.Selector`.
23303 // selection: Hash<String, DomNode>
23304 // (id, DomNode) map for every TreeNode that's currently selected.
23305 // The DOMNode is the TreeNode.rowNode.
23309 constructor: function(tree
, params
){
23316 this.anchor
= null;
23318 dijit
.setWaiState(this.tree
.domNode
, "multiselect", !this.singular
);
23321 dojo
.connect(this.tree
.domNode
, "onmousedown", this,"onMouseDown"),
23322 dojo
.connect(this.tree
.domNode
, "onmouseup", this,"onMouseUp"),
23323 dojo
.connect(this.tree
.domNode
, "onmousemove", this,"onMouseMove")
23327 // singular: Boolean
23328 // Allows selection of only one element, if true.
23329 // Tree hasn't been tested in singular=true mode, unclear if it works.
23333 getSelectedTreeNodes: function(){
23335 // Returns a list of selected node(s).
23336 // Used by dndSource on the start of a drag.
23339 var nodes
=[], sel
= this.selection
;
23341 nodes
.push(sel
[i
]);
23346 selectNone: function(){
23348 // Unselects all items
23352 this.setSelection([]);
23353 return this; // self
23356 destroy: function(){
23358 // Prepares the object to be garbage-collected
23359 this.inherited(arguments
);
23360 this.selection
= this.anchor
= null;
23362 addTreeNode: function(/*dijit._TreeNode*/node
, /*Boolean?*/isAnchor
){
23364 // add node to current selection
23367 // isAnchor: Boolean
23368 // Whether the node should become anchor.
23370 this.setSelection(this.getSelectedTreeNodes().concat( [node
] ));
23371 if(isAnchor
){ this.anchor
= node
; }
23374 removeTreeNode: function(/*dijit._TreeNode*/node
){
23376 // remove node from current selection
23379 this.setSelection(this._setDifference(this.getSelectedTreeNodes(), [node
]))
23382 isTreeNodeSelected: function(/*dijit._TreeNode*/node
){
23384 // return true if node is currently selected
23386 // the node to check whether it's in the current selection
23388 return node
.id
&& !!this.selection
[node
.id
];
23390 setSelection: function(/*dijit._treeNode[]*/ newSelection
){
23392 // set the list of selected nodes to be exactly newSelection. All changes to the
23393 // selection should be passed through this function, which ensures that derived
23394 // attributes are kept up to date. Anchor will be deleted if it has been removed
23395 // from the selection, but no new anchor will be added by this function.
23396 // newSelection: Node[]
23397 // list of tree nodes to make selected
23398 var oldSelection
= this.getSelectedTreeNodes();
23399 dojo
.forEach(this._setDifference(oldSelection
, newSelection
), dojo
.hitch(this, function(node
){
23400 node
.setSelected(false);
23401 if(this.anchor
== node
){
23402 delete this.anchor
;
23404 delete this.selection
[node
.id
];
23406 dojo
.forEach(this._setDifference(newSelection
, oldSelection
), dojo
.hitch(this, function(node
){
23407 node
.setSelected(true);
23408 this.selection
[node
.id
] = node
;
23410 this._updateSelectionProperties();
23412 _setDifference: function(xs
,ys
){
23414 // Returns a copy of xs which lacks any objects
23415 // occurring in ys. Checks for membership by
23416 // modifying and then reading the object, so it will
23417 // not properly handle sets of numbers or strings.
23419 dojo
.forEach(ys
, function(y
){ y
.__exclude__
= true; });
23420 var ret
= dojo
.filter(xs
, function(x
){ return !x
.__exclude__
; });
23422 // clean up after ourselves.
23423 dojo
.forEach(ys
, function(y
){ delete y
['__exclude__'] });
23426 _updateSelectionProperties: function() {
23428 // Update the following tree properties from the current selection:
23429 // path[s], selectedItem[s], selectedNode[s]
23431 var selected
= this.getSelectedTreeNodes();
23432 var paths
= [], nodes
= [];
23433 dojo
.forEach(selected
, function(node
) {
23435 paths
.push(node
.getTreePath());
23437 var items
= dojo
.map(nodes
,function(node
) { return node
.item
; });
23438 this.tree
._set("paths", paths
);
23439 this.tree
._set("path", paths
[0] || []);
23440 this.tree
._set("selectedNodes", nodes
);
23441 this.tree
._set("selectedNode", nodes
[0] || null);
23442 this.tree
._set("selectedItems", items
);
23443 this.tree
._set("selectedItem", items
[0] || null);
23446 onMouseDown: function(e
){
23448 // Event processor for onmousedown
23454 // ignore click on expando node
23455 if(!this.current
|| this.tree
.isExpandoNode( e
.target
, this.current
)){ return; }
23457 if(e
.button
== dojo
.mouseButtons
.RIGHT
){ return; } // ignore right-click
23461 var treeNode
= this.current
,
23462 copy
= dojo
.isCopyKey(e
), id
= treeNode
.id
;
23464 // if shift key is not pressed, and the node is already in the selection,
23465 // delay deselection until onmouseup so in the case of DND, deselection
23466 // will be canceled by onmousemove.
23467 if(!this.singular
&& !e
.shiftKey
&& this.selection
[id
]){
23468 this._doDeselect
= true;
23471 this._doDeselect
= false;
23473 this.userSelect(treeNode
, copy
, e
.shiftKey
);
23476 onMouseUp: function(e
){
23478 // Event processor for onmouseup
23484 // _doDeselect is the flag to indicate that the user wants to either ctrl+click on
23485 // a already selected item (to deselect the item), or click on a not-yet selected item
23486 // (which should remove all current selection, and add the clicked item). This can not
23487 // be done in onMouseDown, because the user may start a drag after mousedown. By moving
23488 // the deselection logic here, the user can drags an already selected item.
23489 if(!this._doDeselect
){ return; }
23490 this._doDeselect
= false;
23491 this.userSelect(this.current
, dojo
.isCopyKey( e
), e
.shiftKey
);
23493 onMouseMove: function(e
){
23495 // event processor for onmousemove
23498 this._doDeselect
= false;
23501 userSelect: function(node
, multi
, range
){
23503 // Add or remove the given node from selection, responding
23504 // to a user action such as a click or keypress.
23506 // Indicates whether this is meant to be a multi-select action (e.g. ctrl-click)
23508 // Indicates whether this is meant to be a ranged action (e.g. shift-click)
23513 if(this.anchor
== node
&& multi
){
23516 this.setSelection([node
]);
23517 this.anchor
= node
;
23520 if(range
&& this.anchor
){
23521 var cr
= dijit
.tree
._compareNodes(this.anchor
.rowNode
, node
.rowNode
),
23522 begin
, end
, anchor
= this.anchor
;
23524 if(cr
< 0){ //current is after anchor
23527 }else{ //current is before anchor
23532 //add everything betweeen begin and end inclusively
23533 while(begin
!= end
) {
23535 begin
= this.tree
._getNextNode(begin
);
23539 this.setSelection(nodes
);
23541 if( this.selection
[ node
.id
] && multi
) {
23542 this.removeTreeNode( node
);
23544 this.addTreeNode(node
, true);
23546 this.setSelection([node
]);
23547 this.anchor
= node
;
23553 forInSelectedItems: function(/*Function*/ f
, /*Object?*/ o
){
23555 // Iterates over selected items;
23556 // see `dojo.dnd.Container.forInItems()` for details
23557 o
= o
|| dojo
.global
;
23558 for(var id
in this.selection
){
23559 // console.log("selected item id: " + id);
23560 f
.call(o
, this.getItem(id
), id
, this);
23567 if(!dojo
._hasResource
["dijit.Tree"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
23568 dojo
._hasResource
["dijit.Tree"] = true;
23569 dojo
.provide("dijit.Tree");
23585 [dijit
._Widget
, dijit
._Templated
, dijit
._Container
, dijit
._Contained
, dijit
._CssStateMixin
],
23588 // Single node within a tree. This class is used internally
23589 // by Tree and should not be accessed directly.
23593 // item: [const] dojo.data.Item
23594 // the dojo.data entry this tree represents
23597 // isTreeNode: [protected] Boolean
23598 // Indicates that this is a TreeNode. Used by `dijit.Tree` only,
23599 // should not be accessed directly.
23603 // Text of this tree node
23606 // isExpandable: [private] Boolean
23607 // This node has children, so show the expando node (+ sign)
23608 isExpandable
: null,
23610 // isExpanded: [readonly] Boolean
23611 // This node is currently expanded (ie, opened)
23614 // state: [private] String
23615 // Dynamic loading-related stuff.
23616 // When an empty folder node appears, it is "UNCHECKED" first,
23617 // then after dojo.data query it becomes "LOADING" and, finally "LOADED"
23618 state
: "UNCHECKED",
23620 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"),
23622 baseClass
: "dijitTreeNode",
23624 // For hover effect for tree node, and focus effect for label
23626 rowNode
: "dijitTreeRow",
23627 labelNode
: "dijitTreeLabel"
23630 attributeMap
: dojo
.delegate(dijit
._Widget
.prototype.attributeMap
, {
23631 label
: {node
: "labelNode", type
: "innerText"},
23632 tooltip
: {node
: "rowNode", type
: "attribute", attribute
: "title"}
23635 buildRendering: function(){
23636 this.inherited(arguments
);
23638 // set expand icon for leaf
23639 this._setExpando();
23641 // set icon and label class based on item
23642 this._updateItemClasses(this.item
);
23644 if(this.isExpandable
){
23645 dijit
.setWaiState(this.labelNode
, "expanded", this.isExpanded
);
23648 //aria-selected should be false on all selectable elements.
23649 this.setSelected(false);
23652 _setIndentAttr: function(indent
){
23654 // Tell this node how many levels it should be indented
23656 // 0 for top level nodes, 1 for their children, 2 for their
23657 // grandchildren, etc.
23659 // Math.max() is to prevent negative padding on hidden root node (when indent == -1)
23660 var pixels
= (Math
.max(indent
, 0) * this.tree
._nodePixelIndent
) + "px";
23662 dojo
.style(this.domNode
, "backgroundPosition", pixels
+ " 0px");
23663 dojo
.style(this.rowNode
, this.isLeftToRight() ? "paddingLeft" : "paddingRight", pixels
);
23665 dojo
.forEach(this.getChildren(), function(child
){
23666 child
.set("indent", indent
+1);
23669 this._set("indent", indent
);
23672 markProcessing: function(){
23674 // Visually denote that tree is loading data, etc.
23677 this.state
= "LOADING";
23678 this._setExpando(true);
23681 unmarkProcessing: function(){
23683 // Clear markup from markProcessing() call
23686 this._setExpando(false);
23689 _updateItemClasses: function(item
){
23691 // Set appropriate CSS classes for icon and label dom node
23692 // (used to allow for item updates to change respective CSS)
23695 var tree
= this.tree
, model
= tree
.model
;
23696 if(tree
._v10Compat
&& item
=== model
.root
){
23697 // For back-compat with 1.0, need to use null to specify root item (TODO: remove in 2.0)
23700 this._applyClassAndStyle(item
, "icon", "Icon");
23701 this._applyClassAndStyle(item
, "label", "Label");
23702 this._applyClassAndStyle(item
, "row", "Row");
23705 _applyClassAndStyle: function(item
, lower
, upper
){
23707 // Set the appropriate CSS classes and styles for labels, icons and rows.
23713 // The lower case attribute to use, e.g. 'icon', 'label' or 'row'.
23716 // The upper case attribute to use, e.g. 'Icon', 'Label' or 'Row'.
23721 var clsName
= "_" + lower
+ "Class";
23722 var nodeName
= lower
+ "Node";
23723 var oldCls
= this[clsName
];
23725 this[clsName
] = this.tree
["get" + upper
+ "Class"](item
, this.isExpanded
);
23726 dojo
.replaceClass(this[nodeName
], this[clsName
] || "", oldCls
|| "");
23728 dojo
.style(this[nodeName
], this.tree
["get" + upper
+ "Style"](item
, this.isExpanded
) || {});
23731 _updateLayout: function(){
23733 // Set appropriate CSS classes for this.domNode
23736 var parent
= this.getParent();
23737 if(!parent
|| parent
.rowNode
.style
.display
== "none"){
23738 /* if we are hiding the root node then make every first level child look like a root node */
23739 dojo
.addClass(this.domNode
, "dijitTreeIsRoot");
23741 dojo
.toggleClass(this.domNode
, "dijitTreeIsLast", !this.getNextSibling());
23745 _setExpando: function(/*Boolean*/ processing
){
23747 // Set the right image for the expando node
23751 var styles
= ["dijitTreeExpandoLoading", "dijitTreeExpandoOpened",
23752 "dijitTreeExpandoClosed", "dijitTreeExpandoLeaf"],
23753 _a11yStates
= ["*","-","+","*"],
23754 idx
= processing
? 0 : (this.isExpandable
? (this.isExpanded
? 1 : 2) : 3);
23756 // apply the appropriate class to the expando node
23757 dojo
.replaceClass(this.expandoNode
, styles
[idx
], styles
);
23759 // provide a non-image based indicator for images-off mode
23760 this.expandoNodeText
.innerHTML
= _a11yStates
[idx
];
23764 expand: function(){
23766 // Show my children
23768 // Deferred that fires when expansion is complete
23770 // If there's already an expand in progress or we are already expanded, just return
23771 if(this._expandDeferred
){
23772 return this._expandDeferred
; // dojo.Deferred
23775 // cancel in progress collapse operation
23776 this._wipeOut
&& this._wipeOut
.stop();
23778 // All the state information for when a node is expanded, maybe this should be
23779 // set when the animation completes instead
23780 this.isExpanded
= true;
23781 dijit
.setWaiState(this.labelNode
, "expanded", "true");
23782 if(this.tree
.showRoot
|| this !== this.tree
.rootNode
){
23783 dijit
.setWaiRole(this.containerNode
, "group");
23785 dojo
.addClass(this.contentNode
,'dijitTreeContentExpanded');
23786 this._setExpando();
23787 this._updateItemClasses(this.item
);
23788 if(this == this.tree
.rootNode
){
23789 dijit
.setWaiState(this.tree
.domNode
, "expanded", "true");
23793 wipeIn
= dojo
.fx
.wipeIn({
23794 node
: this.containerNode
, duration
: dijit
.defaultDuration
,
23796 def
.callback(true);
23800 // Deferred that fires when expand is complete
23801 def
= (this._expandDeferred
= new dojo
.Deferred(function(){
23808 return def
; // dojo.Deferred
23811 collapse: function(){
23813 // Collapse this node (if it's expanded)
23815 if(!this.isExpanded
){ return; }
23817 // cancel in progress expand operation
23818 if(this._expandDeferred
){
23819 this._expandDeferred
.cancel();
23820 delete this._expandDeferred
;
23823 this.isExpanded
= false;
23824 dijit
.setWaiState(this.labelNode
, "expanded", "false");
23825 if(this == this.tree
.rootNode
){
23826 dijit
.setWaiState(this.tree
.domNode
, "expanded", "false");
23828 dojo
.removeClass(this.contentNode
,'dijitTreeContentExpanded');
23829 this._setExpando();
23830 this._updateItemClasses(this.item
);
23832 if(!this._wipeOut
){
23833 this._wipeOut
= dojo
.fx
.wipeOut({
23834 node
: this.containerNode
, duration
: dijit
.defaultDuration
23837 this._wipeOut
.play();
23841 // Levels from this node to the root node
23844 setChildItems: function(/* Object[] */ items
){
23846 // Sets the child items of this node, removing/adding nodes
23847 // from current children to match specified items[] array.
23848 // Also, if this.persist == true, expands any children that were previously
23851 // Deferred object that fires after all previously opened children
23852 // have been expanded again (or fires instantly if there are no such children).
23854 var tree
= this.tree
,
23855 model
= tree
.model
,
23856 defs
= []; // list of deferreds that need to fire before I am complete
23859 // Orphan all my existing children.
23860 // If items contains some of the same items as before then we will reattach them.
23861 // Don't call this.removeChild() because that will collapse the tree etc.
23862 dojo
.forEach(this.getChildren(), function(child
){
23863 dijit
._Container
.prototype.removeChild
.call(this, child
);
23866 this.state
= "LOADED";
23868 if(items
&& items
.length
> 0){
23869 this.isExpandable
= true;
23871 // Create _TreeNode widget for each specified tree node, unless one already
23872 // exists and isn't being used (presumably it's from a DnD move and was recently
23874 dojo
.forEach(items
, function(item
){
23875 var id
= model
.getIdentity(item
),
23876 existingNodes
= tree
._itemNodesMap
[id
],
23879 for(var i
=0;i
<existingNodes
.length
;i
++){
23880 if(existingNodes
[i
] && !existingNodes
[i
].getParent()){
23881 node
= existingNodes
[i
];
23882 node
.set('indent', this.indent
+1);
23888 node
= this.tree
._createTreeNode({
23891 isExpandable
: model
.mayHaveChildren(item
),
23892 label
: tree
.getLabel(item
),
23893 tooltip
: tree
.getTooltip(item
),
23896 indent
: this.indent
+ 1
23899 existingNodes
.push(node
);
23901 tree
._itemNodesMap
[id
] = [node
];
23904 this.addChild(node
);
23906 // If node was previously opened then open it again now (this may trigger
23907 // more data store accesses, recursively)
23908 if(this.tree
.autoExpand
|| this.tree
._state(item
)){
23909 defs
.push(tree
._expandNode(node
));
23913 // note that updateLayout() needs to be called on each child after
23914 // _all_ the children exist
23915 dojo
.forEach(this.getChildren(), function(child
, idx
){
23916 child
._updateLayout();
23919 this.isExpandable
=false;
23922 if(this._setExpando
){
23923 // change expando to/from dot or + icon, as appropriate
23924 this._setExpando(false);
23927 // Set leaf icon or folder icon, as appropriate
23928 this._updateItemClasses(this.item
);
23930 // On initial tree show, make the selected TreeNode as either the root node of the tree,
23931 // or the first child, if the root node is hidden
23932 if(this == tree
.rootNode
){
23933 var fc
= this.tree
.showRoot
? this : this.getChildren()[0];
23935 fc
.setFocusable(true);
23936 tree
.lastFocused
= fc
;
23938 // fallback: no nodes in tree so focus on Tree <div> itself
23939 tree
.domNode
.setAttribute("tabIndex", "0");
23943 return new dojo
.DeferredList(defs
); // dojo.Deferred
23946 getTreePath: function(){
23949 while(node
&& node
!== this.tree
.rootNode
){
23950 path
.unshift(node
.item
);
23951 node
= node
.getParent();
23953 path
.unshift(this.tree
.rootNode
.item
);
23958 getIdentity: function() {
23959 return this.tree
.model
.getIdentity(this.item
);
23962 removeChild: function(/* treeNode */ node
){
23963 this.inherited(arguments
);
23965 var children
= this.getChildren();
23966 if(children
.length
== 0){
23967 this.isExpandable
= false;
23971 dojo
.forEach(children
, function(child
){
23972 child
._updateLayout();
23976 makeExpandable: function(){
23978 // if this node wasn't already showing the expando node,
23979 // turn it into one and call _setExpando()
23981 // TODO: hmm this isn't called from anywhere, maybe should remove it for 2.0
23983 this.isExpandable
= true;
23984 this._setExpando(false);
23987 _onLabelFocus: function(evt
){
23989 // Called when this row is focused (possibly programatically)
23990 // Note that we aren't using _onFocus() builtin to dijit
23991 // because it's called when focus is moved to a descendant TreeNode.
23994 this.tree
._onNodeFocus(this);
23997 setSelected: function(/*Boolean*/ selected
){
23999 // A Tree has a (single) currently selected node.
24000 // Mark that this node is/isn't that currently selected node.
24002 // In particular, setting a node as selected involves setting tabIndex
24003 // so that when user tabs to the tree, focus will go to that node (only).
24004 dijit
.setWaiState(this.labelNode
, "selected", selected
);
24005 dojo
.toggleClass(this.rowNode
, "dijitTreeRowSelected", selected
);
24008 setFocusable: function(/*Boolean*/ selected
){
24010 // A Tree has a (single) node that's focusable.
24011 // Mark that this node is/isn't that currently focsuable node.
24013 // In particular, setting a node as selected involves setting tabIndex
24014 // so that when user tabs to the tree, focus will go to that node (only).
24016 this.labelNode
.setAttribute("tabIndex", selected
? "0" : "-1");
24019 _onClick: function(evt
){
24021 // Handler for onclick event on a node
24024 this.tree
._onClick(this, evt
);
24026 _onDblClick: function(evt
){
24028 // Handler for ondblclick event on a node
24031 this.tree
._onDblClick(this, evt
);
24034 _onMouseEnter: function(evt
){
24036 // Handler for onmouseenter event on a node
24039 this.tree
._onNodeMouseEnter(this, evt
);
24042 _onMouseLeave: function(evt
){
24044 // Handler for onmouseenter event on a node
24047 this.tree
._onNodeMouseLeave(this, evt
);
24053 [dijit
._Widget
, dijit
._Templated
],
24056 // This widget displays hierarchical data from a store.
24058 // store: [deprecated] String||dojo.data.Store
24059 // Deprecated. Use "model" parameter instead.
24060 // The store to get data to display in the tree.
24063 // model: dijit.Tree.model
24064 // Interface to read tree data, get notifications of changes to tree data,
24065 // and for handling drop operations (i.e drag and drop onto the tree)
24068 // query: [deprecated] anything
24069 // Deprecated. User should specify query to the model directly instead.
24070 // Specifies datastore query to return the root item or top items for the tree.
24073 // label: [deprecated] String
24074 // Deprecated. Use dijit.tree.ForestStoreModel directly instead.
24075 // Used in conjunction with query parameter.
24076 // If a query is specified (rather than a root node id), and a label is also specified,
24077 // then a fake root node is created and displayed, with this label.
24080 // showRoot: [const] Boolean
24081 // Should the root node be displayed, or hidden?
24084 // childrenAttr: [deprecated] String[]
24085 // Deprecated. This information should be specified in the model.
24086 // One ore more attributes that holds children of a tree node
24087 childrenAttr
: ["children"],
24089 // paths: String[][] or Item[][]
24090 // Full paths from rootNode to selected nodes expressed as array of items or array of ids.
24091 // Since setting the paths may be asynchronous (because ofwaiting on dojo.data), set("paths", ...)
24092 // returns a Deferred to indicate when the set is complete.
24095 // path: String[] or Item[]
24096 // Backward compatible singular variant of paths.
24099 // selectedItems: [readonly] Item[]
24100 // The currently selected items in this tree.
24101 // This property can only be set (via set('selectedItems', ...)) when that item is already
24102 // visible in the tree. (I.e. the tree has already been expanded to show that node.)
24103 // Should generally use `paths` attribute to set the selected items instead.
24104 selectedItems
: null,
24106 // selectedItem: [readonly] Item
24107 // Backward compatible singular variant of selectedItems.
24108 selectedItem
: null,
24110 // openOnClick: Boolean
24111 // If true, clicking a folder node's label will open it, rather than calling onClick()
24112 openOnClick
: false,
24114 // openOnDblClick: Boolean
24115 // If true, double-clicking a folder node's label will open it, rather than calling onDblClick()
24116 openOnDblClick
: false,
24118 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"),
24120 // persist: Boolean
24121 // Enables/disables use of cookies for state saving.
24124 // autoExpand: Boolean
24125 // Fully expand the tree on load. Overrides `persist`.
24128 // dndController: [protected] String
24129 // Class name to use as as the dnd controller. Specifying this class enables DnD.
24130 // Generally you should specify this as "dijit.tree.dndSource".
24131 // Default of "dijit.tree._dndSelector" handles selection only (no actual DnD).
24132 dndController
: "dijit.tree._dndSelector",
24134 // parameters to pull off of the tree and pass on to the dndController as its params
24135 dndParams
: ["onDndDrop","itemCreator","onDndCancel","checkAcceptance", "checkItemAcceptance", "dragThreshold", "betweenThreshold"],
24137 //declare the above items so they can be pulled from the tree's markup
24139 // onDndDrop: [protected] Function
24140 // Parameter to dndController, see `dijit.tree.dndSource.onDndDrop`.
24141 // Generally this doesn't need to be set.
24145 itemCreator: function(nodes, target, source){
24147 // Returns objects passed to `Tree.model.newItem()` based on DnD nodes
24148 // dropped onto the tree. Developer must override this method to enable
24149 // dropping from external sources onto this Tree, unless the Tree.model's items
24150 // happen to look like {id: 123, name: "Apple" } with no other attributes.
24152 // For each node in nodes[], which came from source, create a hash of name/value
24153 // pairs to be passed to Tree.model.newItem(). Returns array of those hashes.
24154 // nodes: DomNode[]
24155 // The DOMNodes dragged from the source container
24157 // The target TreeNode.rowNode
24158 // source: dojo.dnd.Source
24159 // The source container the nodes were dragged from, perhaps another Tree or a plain dojo.dnd.Source
24160 // returns: Object[]
24161 // Array of name/value hashes for each new item to be added to the Tree, like:
24163 // | { id: 123, label: "apple", foo: "bar" },
24164 // | { id: 456, label: "pear", zaz: "bam" }
24173 // onDndCancel: [protected] Function
24174 // Parameter to dndController, see `dijit.tree.dndSource.onDndCancel`.
24175 // Generally this doesn't need to be set.
24179 checkAcceptance: function(source, nodes){
24181 // Checks if the Tree itself can accept nodes from this source
24182 // source: dijit.tree._dndSource
24183 // The source which provides items
24184 // nodes: DOMNode[]
24185 // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if
24186 // source is a dijit.Tree.
24189 return true; // Boolean
24192 checkAcceptance
: null,
24195 checkItemAcceptance: function(target, source, position){
24197 // Stub function to be overridden if one wants to check for the ability to drop at the node/item level
24199 // In the base case, this is called to check if target can become a child of source.
24200 // When betweenThreshold is set, position="before" or "after" means that we
24201 // are asking if the source node can be dropped before/after the target node.
24203 // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to
24204 // Use dijit.getEnclosingWidget(target) to get the TreeNode.
24205 // source: dijit.tree.dndSource
24206 // The (set of) nodes we are dropping
24207 // position: String
24208 // "over", "before", or "after"
24211 return true; // Boolean
24214 checkItemAcceptance
: null,
24216 // dragThreshold: Integer
24217 // Number of pixels mouse moves before it's considered the start of a drag operation
24220 // betweenThreshold: Integer
24221 // Set to a positive value to allow drag and drop "between" nodes.
24223 // If during DnD mouse is over a (target) node but less than betweenThreshold
24224 // pixels from the bottom edge, dropping the the dragged node will make it
24225 // the next sibling of the target node, rather than the child.
24227 // Similarly, if mouse is over a target node but less that betweenThreshold
24228 // pixels from the top edge, dropping the dragged node will make it
24229 // the target node's previous sibling rather than the target node's child.
24230 betweenThreshold
: 0,
24232 // _nodePixelIndent: Integer
24233 // Number of pixels to indent tree nodes (relative to parent node).
24234 // Default is 19 but can be overridden by setting CSS class dijitTreeIndent
24235 // and calling resize() or startup() on tree after it's in the DOM.
24236 _nodePixelIndent
: 19,
24238 _publish: function(/*String*/ topicName
, /*Object*/ message
){
24240 // Publish a message for this widget/topic
24241 dojo
.publish(this.id
, [dojo
.mixin({tree
: this, event
: topicName
}, message
|| {})]);
24244 postMixInProperties: function(){
24247 if(this.autoExpand
){
24248 // There's little point in saving opened/closed state of nodes for a Tree
24249 // that initially opens all it's nodes.
24250 this.persist
= false;
24253 this._itemNodesMap
={};
24255 if(!this.cookieName
){
24256 this.cookieName
= this.id
+ "SaveStateCookie";
24259 this._loadDeferred
= new dojo
.Deferred();
24261 this.inherited(arguments
);
24264 postCreate: function(){
24267 // Create glue between store and Tree, if not specified directly by user
24269 this._store2model();
24272 // monitor changes to items
24273 this.connect(this.model
, "onChange", "_onItemChange");
24274 this.connect(this.model
, "onChildrenChange", "_onItemChildrenChange");
24275 this.connect(this.model
, "onDelete", "_onItemDelete");
24279 this.inherited(arguments
);
24281 if(this.dndController
){
24282 if(dojo
.isString(this.dndController
)){
24283 this.dndController
= dojo
.getObject(this.dndController
);
24286 for(var i
=0; i
<this.dndParams
.length
;i
++){
24287 if(this[this.dndParams
[i
]]){
24288 params
[this.dndParams
[i
]] = this[this.dndParams
[i
]];
24291 this.dndController
= new this.dndController(this, params
);
24295 _store2model: function(){
24297 // User specified a store&query rather than model, so create model from store/query
24298 this._v10Compat
= true;
24299 dojo
.deprecated("Tree: from version 2.0, should specify a model object rather than a store/query");
24301 var modelParams
= {
24302 id
: this.id
+ "_ForestStoreModel",
24305 childrenAttrs
: this.childrenAttr
24308 // Only override the model's mayHaveChildren() method if the user has specified an override
24309 if(this.params
.mayHaveChildren
){
24310 modelParams
.mayHaveChildren
= dojo
.hitch(this, "mayHaveChildren");
24313 if(this.params
.getItemChildren
){
24314 modelParams
.getChildren
= dojo
.hitch(this, function(item
, onComplete
, onError
){
24315 this.getItemChildren((this._v10Compat
&& item
=== this.model
.root
) ? null : item
, onComplete
, onError
);
24318 this.model
= new dijit
.tree
.ForestStoreModel(modelParams
);
24320 // For backwards compatibility, the visibility of the root node is controlled by
24321 // whether or not the user has specified a label
24322 this.showRoot
= Boolean(this.label
);
24325 onLoad: function(){
24327 // Called when tree finishes loading and expanding.
24329 // If persist == true the loading may encompass many levels of fetches
24330 // from the data store, each asynchronous. Waits for all to finish.
24337 // Initial load of the tree.
24338 // Load root node (possibly hidden) and it's children.
24339 this.model
.getRoot(
24340 dojo
.hitch(this, function(item
){
24341 var rn
= (this.rootNode
= this.tree
._createTreeNode({
24344 isExpandable
: true,
24345 label
: this.label
|| this.getLabel(item
),
24346 indent
: this.showRoot
? 0 : -1
24348 if(!this.showRoot
){
24349 rn
.rowNode
.style
.display
="none";
24350 // if root is not visible, move tree role to the invisible
24351 // root node's containerNode, see #12135
24352 dijit
.setWaiRole(this.domNode
, 'presentation');
24354 dijit
.setWaiRole(rn
.labelNode
, 'presentation');
24355 dijit
.setWaiRole(rn
.containerNode
, 'tree');
24357 this.domNode
.appendChild(rn
.domNode
);
24358 var identity
= this.model
.getIdentity(item
);
24359 if(this._itemNodesMap
[identity
]){
24360 this._itemNodesMap
[identity
].push(rn
);
24362 this._itemNodesMap
[identity
] = [rn
];
24365 rn
._updateLayout(); // sets "dijitTreeIsRoot" CSS classname
24367 // load top level children and then fire onLoad() event
24368 this._expandNode(rn
).addCallback(dojo
.hitch(this, function(){
24369 this._loadDeferred
.callback(true);
24374 console
.error(this, ": error loading root: ", err
);
24379 getNodesByItem: function(/*dojo.data.Item or id*/ item
){
24381 // Returns all tree nodes that refer to an item
24383 // Array of tree nodes that refer to passed item
24385 if(!item
){ return []; }
24386 var identity
= dojo
.isString(item
) ? item
: this.model
.getIdentity(item
);
24387 // return a copy so widget don't get messed up by changes to returned array
24388 return [].concat(this._itemNodesMap
[identity
]);
24391 _setSelectedItemAttr: function(/*dojo.data.Item or id*/ item
){
24392 this.set('selectedItems', [item
]);
24395 _setSelectedItemsAttr: function(/*dojo.data.Items or ids*/ items
){
24397 // Select tree nodes related to passed items.
24398 // WARNING: if model use multi-parented items or desired tree node isn't already loaded
24399 // behavior is undefined. Use set('paths', ...) instead.
24401 this._loadDeferred
.addCallback( dojo
.hitch(this, function(){
24402 var identities
= dojo
.map(items
, function(item
){
24403 return (!item
|| dojo
.isString(item
)) ? item
: tree
.model
.getIdentity(item
);
24406 dojo
.forEach(identities
, function(id
){
24407 nodes
= nodes
.concat(tree
._itemNodesMap
[id
] || []);
24409 this.set('selectedNodes', nodes
);
24413 _setPathAttr: function(/*Item[] || String[]*/ path
){
24415 // Singular variant of _setPathsAttr
24417 return this.set("paths", [path
]);
24419 //Empty list is interpreted as "select nothing"
24420 return this.set("paths", []);
24424 _setPathsAttr: function(/*Item[][] || String[][]*/ paths
){
24426 // Select the tree nodes identified by passed paths.
24428 // Array of arrays of items or item id's
24430 // Deferred to indicate when the set is complete
24433 // We may need to wait for some nodes to expand, so setting
24434 // each path will involve a Deferred. We bring those deferreds
24435 // together witha DeferredList.
24436 return new dojo
.DeferredList(dojo
.map(paths
, function(path
){
24437 var d
= new dojo
.Deferred();
24439 // normalize path to use identity
24440 path
= dojo
.map(path
, function(item
){
24441 return dojo
.isString(item
) ? item
: tree
.model
.getIdentity(item
);
24445 // Wait for the tree to load, if it hasn't already.
24446 tree
._loadDeferred
.addCallback(function(){ selectPath(path
, [tree
.rootNode
], d
); });
24448 d
.errback("Empty path");
24451 })).addCallback(setNodes
);
24453 function selectPath(path
, nodes
, def
){
24454 // Traverse path; the next path component should be among "nodes".
24455 var nextPath
= path
.shift();
24456 var nextNode
= dojo
.filter(nodes
, function(node
){
24457 return node
.getIdentity() == nextPath
;
24461 tree
._expandNode(nextNode
).addCallback(function(){ selectPath(path
, nextNode
.getChildren(), def
); });
24463 //Successfully reached the end of this path
24464 def
.callback(nextNode
);
24467 def
.errback("Could not expand path at " + nextPath
);
24471 function setNodes(newNodes
){
24472 //After all expansion is finished, set the selection to
24473 //the set of nodes successfully found.
24474 tree
.set("selectedNodes", dojo
.map(
24475 dojo
.filter(newNodes
,function(x
){return x
[0];}),
24476 function(x
){return x
[1];}));
24480 _setSelectedNodeAttr: function(node
){
24481 this.set('selectedNodes', [node
]);
24483 _setSelectedNodesAttr: function(nodes
){
24484 this._loadDeferred
.addCallback( dojo
.hitch(this, function(){
24485 this.dndController
.setSelection(nodes
);
24490 ////////////// Data store related functions //////////////////////
24491 // These just get passed to the model; they are here for back-compat
24493 mayHaveChildren: function(/*dojo.data.Item*/ item
){
24495 // Deprecated. This should be specified on the model itself.
24497 // Overridable function to tell if an item has or may have children.
24498 // Controls whether or not +/- expando icon is shown.
24499 // (For efficiency reasons we may not want to check if an element actually
24500 // has children until user clicks the expando node)
24505 getItemChildren: function(/*dojo.data.Item*/ parentItem
, /*function(items)*/ onComplete
){
24507 // Deprecated. This should be specified on the model itself.
24509 // Overridable function that return array of child items of given parent item,
24510 // or if parentItem==null then return top items in tree
24515 ///////////////////////////////////////////////////////
24516 // Functions for converting an item to a TreeNode
24517 getLabel: function(/*dojo.data.Item*/ item
){
24519 // Overridable function to get the label for a tree node (given the item)
24522 return this.model
.getLabel(item
); // String
24525 getIconClass: function(/*dojo.data.Item*/ item
, /*Boolean*/ opened
){
24527 // Overridable function to return CSS class name to display icon
24530 return (!item
|| this.model
.mayHaveChildren(item
)) ? (opened
? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf"
24533 getLabelClass: function(/*dojo.data.Item*/ item
, /*Boolean*/ opened
){
24535 // Overridable function to return CSS class name to display label
24540 getRowClass: function(/*dojo.data.Item*/ item
, /*Boolean*/ opened
){
24542 // Overridable function to return CSS class name to display row
24547 getIconStyle: function(/*dojo.data.Item*/ item
, /*Boolean*/ opened
){
24549 // Overridable function to return CSS styles to display icon
24551 // Object suitable for input to dojo.style() like {backgroundImage: "url(...)"}
24556 getLabelStyle: function(/*dojo.data.Item*/ item
, /*Boolean*/ opened
){
24558 // Overridable function to return CSS styles to display label
24560 // Object suitable for input to dojo.style() like {color: "red", background: "green"}
24565 getRowStyle: function(/*dojo.data.Item*/ item
, /*Boolean*/ opened
){
24567 // Overridable function to return CSS styles to display row
24569 // Object suitable for input to dojo.style() like {background-color: "#bbb"}
24574 getTooltip: function(/*dojo.data.Item*/ item
){
24576 // Overridable function to get the tooltip for a tree node (given the item)
24579 return ""; // String
24582 /////////// Keyboard and Mouse handlers ////////////////////
24584 _onKeyPress: function(/*Event*/ e
){
24586 // Translates keypress events into commands for the controller
24587 if(e
.altKey
){ return; }
24588 var dk
= dojo
.keys
;
24589 var treeNode
= dijit
.getEnclosingWidget(e
.target
);
24590 if(!treeNode
){ return; }
24592 var key
= e
.charOrCode
;
24593 if(typeof key
== "string" && key
!= " "){ // handle printables (letter navigation)
24594 // Check for key navigation.
24595 if(!e
.altKey
&& !e
.ctrlKey
&& !e
.shiftKey
&& !e
.metaKey
){
24596 this._onLetterKeyNav( { node
: treeNode
, key
: key
.toLowerCase() } );
24599 }else{ // handle non-printables (arrow keys)
24600 // clear record of recent printables (being saved for multi-char letter navigation),
24601 // because "a", down-arrow, "b" shouldn't search for "ab"
24602 if(this._curSearch
){
24603 clearTimeout(this._curSearch
.timer
);
24604 delete this._curSearch
;
24607 var map
= this._keyHandlerMap
;
24609 // setup table mapping keys to events
24611 map
[dk
.ENTER
]="_onEnterKey";
24612 //On WebKit based browsers, the combination ctrl-enter
24613 //does not get passed through. To allow accessible
24614 //multi-select on those browsers, the space key is
24615 //also used for selection.
24616 map
[dk
.SPACE
]= map
[" "] = "_onEnterKey";
24617 map
[this.isLeftToRight() ? dk
.LEFT_ARROW
: dk
.RIGHT_ARROW
]="_onLeftArrow";
24618 map
[this.isLeftToRight() ? dk
.RIGHT_ARROW
: dk
.LEFT_ARROW
]="_onRightArrow";
24619 map
[dk
.UP_ARROW
]="_onUpArrow";
24620 map
[dk
.DOWN_ARROW
]="_onDownArrow";
24621 map
[dk
.HOME
]="_onHomeKey";
24622 map
[dk
.END
]="_onEndKey";
24623 this._keyHandlerMap
= map
;
24625 if(this._keyHandlerMap
[key
]){
24626 this[this._keyHandlerMap
[key
]]( { node
: treeNode
, item
: treeNode
.item
, evt
: e
} );
24632 _onEnterKey: function(/*Object*/ message
){
24633 this._publish("execute", { item
: message
.item
, node
: message
.node
} );
24634 this.dndController
.userSelect(message
.node
, dojo
.isCopyKey( message
.evt
), message
.evt
.shiftKey
);
24635 this.onClick(message
.item
, message
.node
, message
.evt
);
24638 _onDownArrow: function(/*Object*/ message
){
24640 // down arrow pressed; get next visible node, set focus there
24641 var node
= this._getNextNode(message
.node
);
24642 if(node
&& node
.isTreeNode
){
24643 this.focusNode(node
);
24647 _onUpArrow: function(/*Object*/ message
){
24649 // Up arrow pressed; move to previous visible node
24651 var node
= message
.node
;
24653 // if younger siblings
24654 var previousSibling
= node
.getPreviousSibling();
24655 if(previousSibling
){
24656 node
= previousSibling
;
24657 // if the previous node is expanded, dive in deep
24658 while(node
.isExpandable
&& node
.isExpanded
&& node
.hasChildren()){
24659 // move to the last child
24660 var children
= node
.getChildren();
24661 node
= children
[children
.length
-1];
24664 // if this is the first child, return the parent
24665 // unless the parent is the root of a tree with a hidden root
24666 var parent
= node
.getParent();
24667 if(!(!this.showRoot
&& parent
=== this.rootNode
)){
24672 if(node
&& node
.isTreeNode
){
24673 this.focusNode(node
);
24677 _onRightArrow: function(/*Object*/ message
){
24679 // Right arrow pressed; go to child node
24680 var node
= message
.node
;
24682 // if not expanded, expand, else move to 1st child
24683 if(node
.isExpandable
&& !node
.isExpanded
){
24684 this._expandNode(node
);
24685 }else if(node
.hasChildren()){
24686 node
= node
.getChildren()[0];
24687 if(node
&& node
.isTreeNode
){
24688 this.focusNode(node
);
24693 _onLeftArrow: function(/*Object*/ message
){
24695 // Left arrow pressed.
24696 // If not collapsed, collapse, else move to parent.
24698 var node
= message
.node
;
24700 if(node
.isExpandable
&& node
.isExpanded
){
24701 this._collapseNode(node
);
24703 var parent
= node
.getParent();
24704 if(parent
&& parent
.isTreeNode
&& !(!this.showRoot
&& parent
=== this.rootNode
)){
24705 this.focusNode(parent
);
24710 _onHomeKey: function(){
24712 // Home key pressed; get first visible node, and set focus there
24713 var node
= this._getRootOrFirstNode();
24715 this.focusNode(node
);
24719 _onEndKey: function(/*Object*/ message
){
24721 // End key pressed; go to last visible node.
24723 var node
= this.rootNode
;
24724 while(node
.isExpanded
){
24725 var c
= node
.getChildren();
24726 node
= c
[c
.length
- 1];
24729 if(node
&& node
.isTreeNode
){
24730 this.focusNode(node
);
24734 // multiCharSearchDuration: Number
24735 // If multiple characters are typed where each keystroke happens within
24736 // multiCharSearchDuration of the previous keystroke,
24737 // search for nodes matching all the keystrokes.
24739 // For example, typing "ab" will search for entries starting with
24740 // "ab" unless the delay between "a" and "b" is greater than multiCharSearchDuration.
24741 multiCharSearchDuration
: 250,
24743 _onLetterKeyNav: function(message
){
24745 // Called when user presses a prinatable key; search for node starting with recently typed letters.
24747 // Like { node: TreeNode, key: 'a' } where key is the key the user pressed.
24749 // Branch depending on whether this key starts a new search, or modifies an existing search
24750 var cs
= this._curSearch
;
24752 // We are continuing a search. Ex: user has pressed 'a', and now has pressed
24753 // 'b', so we want to search for nodes starting w/"ab".
24754 cs
.pattern
= cs
.pattern
+ message
.key
;
24755 clearTimeout(cs
.timer
);
24757 // We are starting a new search
24758 cs
= this._curSearch
= {
24759 pattern
: message
.key
,
24760 startNode
: message
.node
24764 // set/reset timer to forget recent keystrokes
24766 cs
.timer
= setTimeout(function(){
24767 delete self
._curSearch
;
24768 }, this.multiCharSearchDuration
);
24770 // Navigate to TreeNode matching keystrokes [entered so far].
24771 var node
= cs
.startNode
;
24773 node
= this._getNextNode(node
);
24774 //check for last node, jump to first node if necessary
24776 node
= this._getRootOrFirstNode();
24778 }while(node
!== cs
.startNode
&& (node
.label
.toLowerCase().substr(0, cs
.pattern
.length
) != cs
.pattern
));
24779 if(node
&& node
.isTreeNode
){
24780 // no need to set focus if back where we started
24781 if(node
!== cs
.startNode
){
24782 this.focusNode(node
);
24787 isExpandoNode: function(node
, widget
){
24789 // check whether a dom node is the expandoNode for a particular TreeNode widget
24790 return dojo
.isDescendant(node
, widget
.expandoNode
);
24792 _onClick: function(/*TreeNode*/ nodeWidget
, /*Event*/ e
){
24794 // Translates click events into commands for the controller to process
24796 var domElement
= e
.target
,
24797 isExpandoClick
= this.isExpandoNode(domElement
, nodeWidget
);
24799 if( (this.openOnClick
&& nodeWidget
.isExpandable
) || isExpandoClick
){
24800 // expando node was clicked, or label of a folder node was clicked; open it
24801 if(nodeWidget
.isExpandable
){
24802 this._onExpandoClick({node
:nodeWidget
});
24805 this._publish("execute", { item
: nodeWidget
.item
, node
: nodeWidget
, evt
: e
} );
24806 this.onClick(nodeWidget
.item
, nodeWidget
, e
);
24807 this.focusNode(nodeWidget
);
24811 _onDblClick: function(/*TreeNode*/ nodeWidget
, /*Event*/ e
){
24813 // Translates double-click events into commands for the controller to process
24815 var domElement
= e
.target
,
24816 isExpandoClick
= (domElement
== nodeWidget
.expandoNode
|| domElement
== nodeWidget
.expandoNodeText
);
24818 if( (this.openOnDblClick
&& nodeWidget
.isExpandable
) ||isExpandoClick
){
24819 // expando node was clicked, or label of a folder node was clicked; open it
24820 if(nodeWidget
.isExpandable
){
24821 this._onExpandoClick({node
:nodeWidget
});
24824 this._publish("execute", { item
: nodeWidget
.item
, node
: nodeWidget
, evt
: e
} );
24825 this.onDblClick(nodeWidget
.item
, nodeWidget
, e
);
24826 this.focusNode(nodeWidget
);
24831 _onExpandoClick: function(/*Object*/ message
){
24833 // User clicked the +/- icon; expand or collapse my children.
24834 var node
= message
.node
;
24836 // If we are collapsing, we might be hiding the currently focused node.
24837 // Also, clicking the expando node might have erased focus from the current node.
24838 // For simplicity's sake just focus on the node with the expando.
24839 this.focusNode(node
);
24841 if(node
.isExpanded
){
24842 this._collapseNode(node
);
24844 this._expandNode(node
);
24848 onClick: function(/* dojo.data */ item
, /*TreeNode*/ node
, /*Event*/ evt
){
24850 // Callback when a tree node is clicked
24854 onDblClick: function(/* dojo.data */ item
, /*TreeNode*/ node
, /*Event*/ evt
){
24856 // Callback when a tree node is double-clicked
24860 onOpen: function(/* dojo.data */ item
, /*TreeNode*/ node
){
24862 // Callback when a node is opened
24866 onClose: function(/* dojo.data */ item
, /*TreeNode*/ node
){
24868 // Callback when a node is closed
24873 _getNextNode: function(node
){
24875 // Get next visible node
24877 if(node
.isExpandable
&& node
.isExpanded
&& node
.hasChildren()){
24878 // if this is an expanded node, get the first child
24879 return node
.getChildren()[0]; // _TreeNode
24881 // find a parent node with a sibling
24882 while(node
&& node
.isTreeNode
){
24883 var returnNode
= node
.getNextSibling();
24885 return returnNode
; // _TreeNode
24887 node
= node
.getParent();
24893 _getRootOrFirstNode: function(){
24895 // Get first visible node
24896 return this.showRoot
? this.rootNode
: this.rootNode
.getChildren()[0];
24899 _collapseNode: function(/*_TreeNode*/ node
){
24901 // Called when the user has requested to collapse the node
24903 if(node
._expandNodeDeferred
){
24904 delete node
._expandNodeDeferred
;
24907 if(node
.isExpandable
){
24908 if(node
.state
== "LOADING"){
24909 // ignore clicks while we are in the process of loading data
24914 this.onClose(node
.item
, node
);
24917 this._state(node
.item
,false);
24923 _expandNode: function(/*_TreeNode*/ node
, /*Boolean?*/ recursive
){
24925 // Called when the user has requested to expand the node
24927 // Internal flag used when _expandNode() calls itself, don't set.
24929 // Deferred that fires when the node is loaded and opened and (if persist=true) all it's descendants
24930 // that were previously opened too
24932 if(node
._expandNodeDeferred
&& !recursive
){
24933 // there's already an expand in progress (or completed), so just return
24934 return node
._expandNodeDeferred
; // dojo.Deferred
24937 var model
= this.model
,
24941 switch(node
.state
){
24943 // need to load all the children, and then expand
24944 node
.markProcessing();
24946 // Setup deferred to signal when the load and expand are finished.
24947 // Save that deferred in this._expandDeferred as a flag that operation is in progress.
24948 var def
= (node
._expandNodeDeferred
= new dojo
.Deferred());
24950 // Get the children
24954 node
.unmarkProcessing();
24956 // Display the children and also start expanding any children that were previously expanded
24957 // (if this.persist == true). The returned Deferred will fire when those expansions finish.
24958 var scid
= node
.setChildItems(items
);
24960 // Call _expandNode() again but this time it will just to do the animation (default branch).
24961 // The returned Deferred will fire when the animation completes.
24962 // TODO: seems like I can avoid recursion and just use a deferred to sequence the events?
24963 var ed
= _this
._expandNode(node
, true);
24965 // After the above two tasks (setChildItems() and recursive _expandNode()) finish,
24966 // signal that I am done.
24967 scid
.addCallback(function(){
24968 ed
.addCallback(function(){
24974 console
.error(_this
, ": error loading root children: ", err
);
24979 default: // "LOADED"
24980 // data is already loaded; just expand node
24981 def
= (node
._expandNodeDeferred
= node
.expand());
24983 this.onOpen(node
.item
, node
);
24986 this._state(item
, true);
24991 return def
; // dojo.Deferred
24994 ////////////////// Miscellaneous functions ////////////////
24996 focusNode: function(/* _tree.Node */ node
){
24998 // Focus on the specified node (which must be visible)
25002 // set focus so that the label will be voiced using screen readers
25003 dijit
.focus(node
.labelNode
);
25006 _onNodeFocus: function(/*dijit._Widget*/ node
){
25008 // Called when a TreeNode gets focus, either by user clicking
25009 // it, or programatically by arrow key handling code.
25011 // It marks that the current node is the selected one, and the previously
25012 // selected node no longer is.
25014 if(node
&& node
!= this.lastFocused
){
25015 if(this.lastFocused
&& !this.lastFocused
._destroyed
){
25016 // mark that the previously focsable node is no longer focusable
25017 this.lastFocused
.setFocusable(false);
25020 // mark that the new node is the currently selected one
25021 node
.setFocusable(true);
25022 this.lastFocused
= node
;
25026 _onNodeMouseEnter: function(/*dijit._Widget*/ node
){
25028 // Called when mouse is over a node (onmouseenter event),
25029 // this is monitored by the DND code
25032 _onNodeMouseLeave: function(/*dijit._Widget*/ node
){
25034 // Called when mouse leaves a node (onmouseleave event),
25035 // this is monitored by the DND code
25038 //////////////// Events from the model //////////////////////////
25040 _onItemChange: function(/*Item*/ item
){
25042 // Processes notification of a change to an item's scalar values like label
25043 var model
= this.model
,
25044 identity
= model
.getIdentity(item
),
25045 nodes
= this._itemNodesMap
[identity
];
25048 var label
= this.getLabel(item
),
25049 tooltip
= this.getTooltip(item
);
25050 dojo
.forEach(nodes
, function(node
){
25052 item
: item
, // theoretically could be new JS Object representing same item
25056 node
._updateItemClasses(item
);
25061 _onItemChildrenChange: function(/*dojo.data.Item*/ parent
, /*dojo.data.Item[]*/ newChildrenList
){
25063 // Processes notification of a change to an item's children
25064 var model
= this.model
,
25065 identity
= model
.getIdentity(parent
),
25066 parentNodes
= this._itemNodesMap
[identity
];
25069 dojo
.forEach(parentNodes
,function(parentNode
){
25070 parentNode
.setChildItems(newChildrenList
);
25075 _onItemDelete: function(/*Item*/ item
){
25077 // Processes notification of a deletion of an item
25078 var model
= this.model
,
25079 identity
= model
.getIdentity(item
),
25080 nodes
= this._itemNodesMap
[identity
];
25083 dojo
.forEach(nodes
,function(node
){
25084 // Remove node from set of selected nodes (if it's selected)
25085 this.dndController
.removeTreeNode(node
);
25087 var parent
= node
.getParent();
25089 // if node has not already been orphaned from a _onSetItem(parent, "children", ..) call...
25090 parent
.removeChild(node
);
25092 node
.destroyRecursive();
25094 delete this._itemNodesMap
[identity
];
25098 /////////////// Miscellaneous funcs
25100 _initState: function(){
25102 // Load in which nodes should be opened automatically
25104 var cookie
= dojo
.cookie(this.cookieName
);
25105 this._openedItemIds
= {};
25107 dojo
.forEach(cookie
.split(','), function(item
){
25108 this._openedItemIds
[item
] = true;
25113 _state: function(item
,expanded
){
25115 // Query or set expanded state for an item,
25119 var id
=this.model
.getIdentity(item
);
25120 if(arguments
.length
=== 1){
25121 return this._openedItemIds
[id
];
25124 this._openedItemIds
[id
] = true;
25126 delete this._openedItemIds
[id
];
25129 _saveState: function(){
25131 // Create and save a cookie with the currently expanded nodes identifiers
25136 for(var id
in this._openedItemIds
){
25139 dojo
.cookie(this.cookieName
, ary
.join(","), {expires
:365});
25142 destroy: function(){
25143 if(this._curSearch
){
25144 clearTimeout(this._curSearch
.timer
);
25145 delete this._curSearch
;
25148 this.rootNode
.destroyRecursive();
25150 if(this.dndController
&& !dojo
.isString(this.dndController
)){
25151 this.dndController
.destroy();
25153 this.rootNode
= null;
25154 this.inherited(arguments
);
25157 destroyRecursive: function(){
25158 // A tree is treated as a leaf, not as a node with children (like a grid),
25159 // but defining destroyRecursive for back-compat.
25163 resize: function(changeSize
){
25165 dojo
.marginBox(this.domNode
, changeSize
);
25168 // The only JS sizing involved w/tree is the indentation, which is specified
25169 // in CSS and read in through this dummy indentDetector node (tree must be
25170 // visible and attached to the DOM to read this)
25171 this._nodePixelIndent
= dojo
._getMarginSize(this.tree
.indentDetector
).w
;
25173 if(this.tree
.rootNode
){
25174 // If tree has already loaded, then reset indent for all the nodes
25175 this.tree
.rootNode
.set('indent', this.showRoot
? 0 : -1);
25179 _createTreeNode: function(/*Object*/ args
){
25181 // creates a TreeNode
25183 // Developers can override this method to define their own TreeNode class;
25184 // However it will probably be removed in a future release in favor of a way
25185 // of just specifying a widget for the label, rather than one that contains
25186 // the children too.
25187 return new dijit
._TreeNode(args
);
25191 // For back-compat. TODO: remove in 2.0
25195 if(!dojo
._hasResource
["dojo.dnd.Avatar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
25196 dojo
._hasResource
["dojo.dnd.Avatar"] = true;
25197 dojo
.provide("dojo.dnd.Avatar");
25201 dojo
.declare("dojo.dnd.Avatar", null, {
25203 // Object that represents transferred DnD items visually
25205 // a DnD manager object
25207 constructor: function(manager
){
25208 this.manager
= manager
;
25213 construct: function(){
25215 // constructor function;
25216 // it is separate so it can be (dynamically) overwritten in case of need
25217 this.isA11y
= dojo
.hasClass(dojo
.body(),"dijit_a11y");
25218 var a
= dojo
.create("table", {
25219 "class": "dojoDndAvatar",
25221 position
: "absolute",
25226 source
= this.manager
.source
, node
,
25227 b
= dojo
.create("tbody", null, a
),
25228 tr
= dojo
.create("tr", null, b
),
25229 td
= dojo
.create("td", null, tr
),
25230 icon
= this.isA11y
? dojo
.create("span", {
25232 innerHTML
: this.manager
.copy
? '+' : "<"
25234 span
= dojo
.create("span", {
25235 innerHTML
: source
.generateText
? this._generateText() : ""
25237 k
= Math
.min(5, this.manager
.nodes
.length
), i
= 0;
25238 // we have to set the opacity on IE only after the node is live
25240 "class": "dojoDndAvatarHeader",
25241 style
: {opacity
: 0.9}
25244 if(source
.creator
){
25245 // create an avatar representation of the node
25246 node
= source
._normalizedCreator(source
.getItem(this.manager
.nodes
[i
].id
).data
, "avatar").node
;
25248 // or just clone the node and hope it works
25249 node
= this.manager
.nodes
[i
].cloneNode(true);
25250 if(node
.tagName
.toLowerCase() == "tr"){
25251 // insert extra table nodes
25252 var table
= dojo
.create("table"),
25253 tbody
= dojo
.create("tbody", null, table
);
25254 tbody
.appendChild(node
);
25259 tr
= dojo
.create("tr", null, b
);
25260 td
= dojo
.create("td", null, tr
);
25261 td
.appendChild(node
);
25263 "class": "dojoDndAvatarItem",
25264 style
: {opacity
: (9 - i
) / 10}
25269 destroy: function(){
25271 // destructor for the avatar; called to remove all references so it can be garbage-collected
25272 dojo
.destroy(this.node
);
25275 update: function(){
25277 // updates the avatar to reflect the current DnD state
25278 dojo
[(this.manager
.canDropFlag
? "add" : "remove") + "Class"](this.node
, "dojoDndAvatarCanDrop");
25280 var icon
= dojo
.byId("a11yIcon");
25281 var text
= '+'; // assume canDrop && copy
25282 if (this.manager
.canDropFlag
&& !this.manager
.copy
) {
25283 text
= '< '; // canDrop && move
25284 }else if (!this.manager
.canDropFlag
&& !this.manager
.copy
) {
25285 text
= "o"; //!canDrop && move
25286 }else if(!this.manager
.canDropFlag
){
25287 text
= 'x'; // !canDrop && copy
25289 icon
.innerHTML
=text
;
25292 dojo
.query(("tr.dojoDndAvatarHeader td span" +(this.isA11y
? " span" : "")), this.node
).forEach(
25294 node
.innerHTML
= this._generateText();
25297 _generateText: function(){
25298 // summary: generates a proper text to reflect copying or moving of items
25299 return this.manager
.nodes
.length
.toString();
25305 if(!dojo
._hasResource
["dojo.dnd.Manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
25306 dojo
._hasResource
["dojo.dnd.Manager"] = true;
25307 dojo
.provide("dojo.dnd.Manager");
25313 dojo
.declare("dojo.dnd.Manager", null, {
25315 // the manager of DnD operations (usually a singleton)
25316 constructor: function(){
25317 this.avatar
= null;
25318 this.source
= null;
25321 this.target
= null;
25322 this.canDropFlag
= false;
25326 // avatar's offset from the mouse
25331 overSource: function(source
){
25333 // called when a source detected a mouse-over condition
25337 this.target
= (source
&& source
.targetState
!= "Disabled") ? source
: null;
25338 this.canDropFlag
= Boolean(this.target
);
25339 this.avatar
.update();
25341 dojo
.publish("/dnd/source/over", [source
]);
25343 outSource: function(source
){
25345 // called when a source detected a mouse-out condition
25349 if(this.target
== source
){
25350 this.target
= null;
25351 this.canDropFlag
= false;
25352 this.avatar
.update();
25353 dojo
.publish("/dnd/source/over", [null]);
25356 dojo
.publish("/dnd/source/over", [null]);
25359 startDrag: function(source
, nodes
, copy
){
25361 // called to initiate the DnD operation
25363 // the source which provides items
25365 // the list of transferred items
25367 // copy items, if true, move items otherwise
25368 this.source
= source
;
25369 this.nodes
= nodes
;
25370 this.copy
= Boolean(copy
); // normalizing to true boolean
25371 this.avatar
= this.makeAvatar();
25372 dojo
.body().appendChild(this.avatar
.node
);
25373 dojo
.publish("/dnd/start", [source
, nodes
, this.copy
]);
25375 dojo
.connect(dojo
.doc
, "onmousemove", this, "onMouseMove"),
25376 dojo
.connect(dojo
.doc
, "onmouseup", this, "onMouseUp"),
25377 dojo
.connect(dojo
.doc
, "onkeydown", this, "onKeyDown"),
25378 dojo
.connect(dojo
.doc
, "onkeyup", this, "onKeyUp"),
25379 // cancel text selection and text dragging
25380 dojo
.connect(dojo
.doc
, "ondragstart", dojo
.stopEvent
),
25381 dojo
.connect(dojo
.body(), "onselectstart", dojo
.stopEvent
)
25383 var c
= "dojoDnd" + (copy
? "Copy" : "Move");
25384 dojo
.addClass(dojo
.body(), c
);
25386 canDrop: function(flag
){
25388 // called to notify if the current target can accept items
25389 var canDropFlag
= Boolean(this.target
&& flag
);
25390 if(this.canDropFlag
!= canDropFlag
){
25391 this.canDropFlag
= canDropFlag
;
25392 this.avatar
.update();
25395 stopDrag: function(){
25397 // stop the DnD in progress
25398 dojo
.removeClass(dojo
.body(), ["dojoDndCopy", "dojoDndMove"]);
25399 dojo
.forEach(this.events
, dojo
.disconnect
);
25401 this.avatar
.destroy();
25402 this.avatar
= null;
25403 this.source
= this.target
= null;
25406 makeAvatar: function(){
25408 // makes the avatar; it is separate to be overwritten dynamically, if needed
25409 return new dojo
.dnd
.Avatar(this);
25411 updateAvatar: function(){
25413 // updates the avatar; it is separate to be overwritten dynamically, if needed
25414 this.avatar
.update();
25417 // mouse event processors
25418 onMouseMove: function(e
){
25420 // event processor for onmousemove
25423 var a
= this.avatar
;
25425 dojo
.dnd
.autoScrollNodes(e
);
25426 //dojo.dnd.autoScroll(e);
25427 var s
= a
.node
.style
;
25428 s
.left
= (e
.pageX
+ this.OFFSET_X
) + "px";
25429 s
.top
= (e
.pageY
+ this.OFFSET_Y
) + "px";
25430 var copy
= Boolean(this.source
.copyState(dojo
.isCopyKey(e
)));
25431 if(this.copy
!= copy
){
25432 this._setCopyStatus(copy
);
25436 onMouseUp: function(e
){
25438 // event processor for onmouseup
25442 if(this.target
&& this.canDropFlag
){
25443 var copy
= Boolean(this.source
.copyState(dojo
.isCopyKey(e
))),
25444 params
= [this.source
, this.nodes
, copy
, this.target
, e
];
25445 dojo
.publish("/dnd/drop/before", params
);
25446 dojo
.publish("/dnd/drop", params
);
25448 dojo
.publish("/dnd/cancel");
25454 // keyboard event processors
25455 onKeyDown: function(e
){
25457 // event processor for onkeydown:
25458 // watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag
25463 case dojo
.keys
.CTRL
:
25464 var copy
= Boolean(this.source
.copyState(true));
25465 if(this.copy
!= copy
){
25466 this._setCopyStatus(copy
);
25469 case dojo
.keys
.ESCAPE
:
25470 dojo
.publish("/dnd/cancel");
25476 onKeyUp: function(e
){
25478 // event processor for onkeyup, watching for CTRL for copy/move status
25481 if(this.avatar
&& e
.keyCode
== dojo
.keys
.CTRL
){
25482 var copy
= Boolean(this.source
.copyState(false));
25483 if(this.copy
!= copy
){
25484 this._setCopyStatus(copy
);
25490 _setCopyStatus: function(copy
){
25492 // changes the copy status
25496 this.source
._markDndStatus(this.copy
);
25497 this.updateAvatar();
25498 dojo
.replaceClass(dojo
.body(),
25499 "dojoDnd" + (this.copy
? "Copy" : "Move"),
25500 "dojoDnd" + (this.copy
? "Move" : "Copy"));
25504 // dojo.dnd._manager:
25505 // The manager singleton variable. Can be overwritten if needed.
25506 dojo
.dnd
._manager
= null;
25508 dojo
.dnd
.manager = function(){
25510 // Returns the current DnD manager. Creates one if it is not created yet.
25511 if(!dojo
.dnd
._manager
){
25512 dojo
.dnd
._manager
= new dojo
.dnd
.Manager();
25514 return dojo
.dnd
._manager
; // Object
25519 if(!dojo
._hasResource
["dijit.tree.dndSource"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
25520 dojo
._hasResource
["dijit.tree.dndSource"] = true;
25521 dojo
.provide("dijit.tree.dndSource");
25527 dijit.tree.__SourceArgs = function(){
25529 // A dict of parameters for Tree source configuration.
25530 // isSource: Boolean?
25531 // Can be used as a DnD source. Defaults to true.
25532 // accept: String[]
25533 // List of accepted types (text strings) for a target; defaults to
25534 // ["text", "treeNode"]
25535 // copyOnly: Boolean?
25536 // Copy items, if true, use a state of Ctrl key otherwise,
25537 // dragThreshold: Number
25538 // The move delay in pixels before detecting a drag; 0 by default
25539 // betweenThreshold: Integer
25540 // Distance from upper/lower edge of node to allow drop to reorder nodes
25541 this.isSource = isSource;
25542 this.accept = accept;
25543 this.autoSync = autoSync;
25544 this.copyOnly = copyOnly;
25545 this.dragThreshold = dragThreshold;
25546 this.betweenThreshold = betweenThreshold;
25550 dojo
.declare("dijit.tree.dndSource", dijit
.tree
._dndSelector
, {
25552 // Handles drag and drop operations (as a source or a target) for `dijit.Tree`
25554 // isSource: [private] Boolean
25555 // Can be used as a DnD source.
25558 // accept: String[]
25559 // List of accepted types (text strings) for the Tree; defaults to
25561 accept
: ["text", "treeNode"],
25563 // copyOnly: [private] Boolean
25564 // Copy items, if true, use a state of Ctrl key otherwise
25567 // dragThreshold: Number
25568 // The move delay in pixels before detecting a drag; 5 by default
25571 // betweenThreshold: Integer
25572 // Distance from upper/lower edge of node to allow drop to reorder nodes
25573 betweenThreshold
: 0,
25575 constructor: function(/*dijit.Tree*/ tree
, /*dijit.tree.__SourceArgs*/ params
){
25577 // a constructor of the Tree DnD Source
25580 if(!params
){ params
= {}; }
25581 dojo
.mixin(this, params
);
25582 this.isSource
= typeof params
.isSource
== "undefined" ? true : params
.isSource
;
25583 var type
= params
.accept
instanceof Array
? params
.accept
: ["text", "treeNode"];
25584 this.accept
= null;
25587 for(var i
= 0; i
< type
.length
; ++i
){
25588 this.accept
[type
[i
]] = 1;
25592 // class-specific variables
25593 this.isDragging
= false;
25594 this.mouseDown
= false;
25595 this.targetAnchor
= null; // DOMNode corresponding to the currently moused over TreeNode
25596 this.targetBox
= null; // coordinates of this.targetAnchor
25597 this.dropPosition
= ""; // whether mouse is over/after/before this.targetAnchor
25602 this.sourceState
= "";
25604 dojo
.addClass(this.node
, "dojoDndSource");
25606 this.targetState
= "";
25608 dojo
.addClass(this.node
, "dojoDndTarget");
25613 dojo
.subscribe("/dnd/source/over", this, "onDndSourceOver"),
25614 dojo
.subscribe("/dnd/start", this, "onDndStart"),
25615 dojo
.subscribe("/dnd/drop", this, "onDndDrop"),
25616 dojo
.subscribe("/dnd/cancel", this, "onDndCancel")
25621 checkAcceptance: function(source
, nodes
){
25623 // Checks if the target can accept nodes from this source
25624 // source: dijit.tree.dndSource
25625 // The source which provides items
25626 // nodes: DOMNode[]
25627 // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if
25628 // source is a dijit.Tree.
25631 return true; // Boolean
25634 copyState: function(keyPressed
){
25636 // Returns true, if we need to copy items, false to move.
25637 // It is separated to be overwritten dynamically, if needed.
25638 // keyPressed: Boolean
25639 // The "copy" control key was pressed
25642 return this.copyOnly
|| keyPressed
; // Boolean
25644 destroy: function(){
25646 // Prepares the object to be garbage-collected.
25647 this.inherited("destroy",arguments
);
25648 dojo
.forEach(this.topics
, dojo
.unsubscribe
);
25649 this.targetAnchor
= null;
25652 _onDragMouse: function(e
){
25654 // Helper method for processing onmousemove/onmouseover events while drag is in progress.
25655 // Keeps track of current drop target.
25657 var m
= dojo
.dnd
.manager(),
25658 oldTarget
= this.targetAnchor
, // the TreeNode corresponding to TreeNode mouse was previously over
25659 newTarget
= this.current
, // TreeNode corresponding to TreeNode mouse is currently over
25660 oldDropPosition
= this.dropPosition
; // the previous drop position (over/before/after)
25662 // calculate if user is indicating to drop the dragged node before, after, or over
25663 // (i.e., to become a child of) the target node
25664 var newDropPosition
= "Over";
25665 if(newTarget
&& this.betweenThreshold
> 0){
25666 // If mouse is over a new TreeNode, then get new TreeNode's position and size
25667 if(!this.targetBox
|| oldTarget
!= newTarget
){
25668 this.targetBox
= dojo
.position(newTarget
.rowNode
, true);
25670 if((e
.pageY
- this.targetBox
.y
) <= this.betweenThreshold
){
25671 newDropPosition
= "Before";
25672 }else if((e
.pageY
- this.targetBox
.y
) >= (this.targetBox
.h
- this.betweenThreshold
)){
25673 newDropPosition
= "After";
25677 if(newTarget
!= oldTarget
|| newDropPosition
!= oldDropPosition
){
25679 this._removeItemClass(oldTarget
.rowNode
, oldDropPosition
);
25682 this._addItemClass(newTarget
.rowNode
, newDropPosition
);
25685 // Check if it's ok to drop the dragged node on/before/after the target node.
25688 }else if(newTarget
== this.tree
.rootNode
&& newDropPosition
!= "Over"){
25689 // Can't drop before or after tree's root node; the dropped node would just disappear (at least visually)
25691 }else if(m
.source
== this && (newTarget
.id
in this.selection
)){
25692 // Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140)
25694 }else if(this.checkItemAcceptance(newTarget
.rowNode
, m
.source
, newDropPosition
.toLowerCase())
25695 && !this._isParentChildDrop(m
.source
, newTarget
.rowNode
)){
25701 this.targetAnchor
= newTarget
;
25702 this.dropPosition
= newDropPosition
;
25706 onMouseMove: function(e
){
25708 // Called for any onmousemove events over the Tree
25710 // onmousemouse event
25713 if(this.isDragging
&& this.targetState
== "Disabled"){ return; }
25714 this.inherited(arguments
);
25715 var m
= dojo
.dnd
.manager();
25716 if(this.isDragging
){
25717 this._onDragMouse(e
);
25719 if(this.mouseDown
&& this.isSource
&&
25720 (Math
.abs(e
.pageX
-this._lastX
)>=this.dragThreshold
|| Math
.abs(e
.pageY
-this._lastY
)>=this.dragThreshold
)){
25721 var nodes
= this.getSelectedTreeNodes();
25723 if(nodes
.length
> 1){
25724 //filter out all selected items which has one of their ancestor selected as well
25725 var seen
= this.selection
, i
= 0, r
= [], n
, p
;
25726 nextitem
: while((n
= nodes
[i
++])){
25727 for(p
= n
.getParent(); p
&& p
!== this.tree
; p
= p
.getParent()){
25728 if(seen
[p
.id
]){ //parent is already selected, skip this node
25732 //this node does not have any ancestors selected, add it
25737 nodes
= dojo
.map(nodes
, function(n
){return n
.domNode
});
25738 m
.startDrag(this, nodes
, this.copyState(dojo
.isCopyKey(e
)));
25744 onMouseDown: function(e
){
25746 // Event processor for onmousedown
25748 // onmousedown event
25751 this.mouseDown
= true;
25752 this.mouseButton
= e
.button
;
25753 this._lastX
= e
.pageX
;
25754 this._lastY
= e
.pageY
;
25755 this.inherited(arguments
);
25758 onMouseUp: function(e
){
25760 // Event processor for onmouseup
25765 if(this.mouseDown
){
25766 this.mouseDown
= false;
25767 this.inherited(arguments
);
25771 onMouseOut: function(){
25773 // Event processor for when mouse is moved away from a TreeNode
25776 this.inherited(arguments
);
25777 this._unmarkTargetAnchor();
25780 checkItemAcceptance: function(target
, source
, position
){
25782 // Stub function to be overridden if one wants to check for the ability to drop at the node/item level
25784 // In the base case, this is called to check if target can become a child of source.
25785 // When betweenThreshold is set, position="before" or "after" means that we
25786 // are asking if the source node can be dropped before/after the target node.
25788 // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to
25789 // Use dijit.getEnclosingWidget(target) to get the TreeNode.
25790 // source: dijit.tree.dndSource
25791 // The (set of) nodes we are dropping
25792 // position: String
25793 // "over", "before", or "after"
25799 // topic event processors
25800 onDndSourceOver: function(source
){
25802 // Topic event processor for /dnd/source/over, called when detected a current source.
25804 // The dijit.tree.dndSource / dojo.dnd.Source which has the mouse over it
25807 if(this != source
){
25808 this.mouseDown
= false;
25809 this._unmarkTargetAnchor();
25810 }else if(this.isDragging
){
25811 var m
= dojo
.dnd
.manager();
25815 onDndStart: function(source
, nodes
, copy
){
25817 // Topic event processor for /dnd/start, called to initiate the DnD operation
25819 // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
25820 // nodes: DomNode[]
25821 // The list of transferred items, dndTreeNode nodes if dragging from a Tree
25823 // Copy items, if true, move items otherwise
25828 this._changeState("Source", this == source
? (copy
? "Copied" : "Moved") : "");
25830 var accepted
= this.checkAcceptance(source
, nodes
);
25832 this._changeState("Target", accepted
? "" : "Disabled");
25834 if(this == source
){
25835 dojo
.dnd
.manager().overSource(this);
25838 this.isDragging
= true;
25841 itemCreator: function(/*DomNode[]*/ nodes
, target
, /*dojo.dnd.Source*/ source
){
25843 // Returns objects passed to `Tree.model.newItem()` based on DnD nodes
25844 // dropped onto the tree. Developer must override this method to enable
25845 // dropping from external sources onto this Tree, unless the Tree.model's items
25846 // happen to look like {id: 123, name: "Apple" } with no other attributes.
25848 // For each node in nodes[], which came from source, create a hash of name/value
25849 // pairs to be passed to Tree.model.newItem(). Returns array of those hashes.
25850 // returns: Object[]
25851 // Array of name/value hashes for each new item to be added to the Tree, like:
25853 // | { id: 123, label: "apple", foo: "bar" },
25854 // | { id: 456, label: "pear", zaz: "bam" }
25859 // TODO: for 2.0 refactor so itemCreator() is called once per drag node, and
25860 // make signature itemCreator(sourceItem, node, target) (or similar).
25862 return dojo
.map(nodes
, function(node
){
25865 "name": node
.textContent
|| node
.innerText
|| ""
25870 onDndDrop: function(source
, nodes
, copy
){
25872 // Topic event processor for /dnd/drop, called to finish the DnD operation.
25874 // Updates data store items according to where node was dragged from and dropped
25875 // to. The tree will then respond to those data store updates and redraw itself.
25877 // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
25878 // nodes: DomNode[]
25879 // The list of transferred items, dndTreeNode nodes if dragging from a Tree
25881 // Copy items, if true, move items otherwise
25884 if(this.containerState
== "Over"){
25885 var tree
= this.tree
,
25886 model
= tree
.model
,
25887 target
= this.targetAnchor
,
25888 requeryRoot
= false; // set to true iff top level items change
25890 this.isDragging
= false;
25892 // Compute the new parent item
25893 var targetWidget
= target
;
25896 newParentItem
= (targetWidget
&& targetWidget
.item
) || tree
.item
;
25897 if(this.dropPosition
== "Before" || this.dropPosition
== "After"){
25898 // TODO: if there is no parent item then disallow the drop.
25899 // Actually this should be checked during onMouseMove too, to make the drag icon red.
25900 newParentItem
= (targetWidget
.getParent() && targetWidget
.getParent().item
) || tree
.item
;
25901 // Compute the insert index for reordering
25902 insertIndex
= targetWidget
.getIndexInParent();
25903 if(this.dropPosition
== "After"){
25904 insertIndex
= targetWidget
.getIndexInParent() + 1;
25907 newParentItem
= (targetWidget
&& targetWidget
.item
) || tree
.item
;
25910 // If necessary, use this variable to hold array of hashes to pass to model.newItem()
25911 // (one entry in the array for each dragged node).
25912 var newItemsParams
;
25914 dojo
.forEach(nodes
, function(node
, idx
){
25915 // dojo.dnd.Item representing the thing being dropped.
25916 // Don't confuse the use of item here (meaning a DnD item) with the
25917 // uses below where item means dojo.data item.
25918 var sourceItem
= source
.getItem(node
.id
);
25920 // Information that's available if the source is another Tree
25921 // (possibly but not necessarily this tree, possibly but not
25922 // necessarily the same model as this Tree)
25923 if(dojo
.indexOf(sourceItem
.type
, "treeNode") != -1){
25924 var childTreeNode
= sourceItem
.data
,
25925 childItem
= childTreeNode
.item
,
25926 oldParentItem
= childTreeNode
.getParent().item
;
25929 if(source
== this){
25930 // This is a node from my own tree, and we are moving it, not copying.
25931 // Remove item from old parent's children attribute.
25932 // TODO: dijit.tree.dndSelector should implement deleteSelectedNodes()
25933 // and this code should go there.
25935 if(typeof insertIndex
== "number"){
25936 if(newParentItem
== oldParentItem
&& childTreeNode
.getIndexInParent() < insertIndex
){
25940 model
.pasteItem(childItem
, oldParentItem
, newParentItem
, copy
, insertIndex
);
25941 }else if(model
.isItem(childItem
)){
25942 // Item from same model
25943 // (maybe we should only do this branch if the source is a tree?)
25944 model
.pasteItem(childItem
, oldParentItem
, newParentItem
, copy
, insertIndex
);
25946 // Get the hash to pass to model.newItem(). A single call to
25947 // itemCreator() returns an array of hashes, one for each drag source node.
25948 if(!newItemsParams
){
25949 newItemsParams
= this.itemCreator(nodes
, target
.rowNode
, source
);
25952 // Create new item in the tree, based on the drag source.
25953 model
.newItem(newItemsParams
[idx
], newParentItem
, insertIndex
);
25957 // Expand the target node (if it's currently collapsed) so the user can see
25958 // where their node was dropped. In particular since that node is still selected.
25959 this.tree
._expandNode(targetWidget
);
25961 this.onDndCancel();
25964 onDndCancel: function(){
25966 // Topic event processor for /dnd/cancel, called to cancel the DnD operation
25969 this._unmarkTargetAnchor();
25970 this.isDragging
= false;
25971 this.mouseDown
= false;
25972 delete this.mouseButton
;
25973 this._changeState("Source", "");
25974 this._changeState("Target", "");
25977 // When focus moves in/out of the entire Tree
25978 onOverEvent: function(){
25980 // This method is called when mouse is moved over our container (like onmouseenter)
25983 this.inherited(arguments
);
25984 dojo
.dnd
.manager().overSource(this);
25986 onOutEvent: function(){
25988 // This method is called when mouse is moved out of our container (like onmouseleave)
25991 this._unmarkTargetAnchor();
25992 var m
= dojo
.dnd
.manager();
25993 if(this.isDragging
){
25998 this.inherited(arguments
);
26001 _isParentChildDrop: function(source
, targetRow
){
26003 // Checks whether the dragged items are parent rows in the tree which are being
26004 // dragged into their own children.
26007 // The DragSource object.
26010 // The tree row onto which the dragged nodes are being dropped.
26015 // If the dragged object is not coming from the tree this widget belongs to,
26016 // it cannot be invalid.
26017 if(!source
.tree
|| source
.tree
!= this.tree
){
26022 var root
= source
.tree
.domNode
;
26023 var ids
= source
.selection
;
26025 var node
= targetRow
.parentNode
;
26027 // Iterate up the DOM hierarchy from the target drop row,
26028 // checking of any of the dragged nodes have the same ID.
26029 while(node
!= root
&& !ids
[node
.id
]){
26030 node
= node
.parentNode
;
26033 return node
.id
&& ids
[node
.id
];
26036 _unmarkTargetAnchor: function(){
26038 // Removes hover class of the current target anchor
26041 if(!this.targetAnchor
){ return; }
26042 this._removeItemClass(this.targetAnchor
.rowNode
, this.dropPosition
);
26043 this.targetAnchor
= null;
26044 this.targetBox
= null;
26045 this.dropPosition
= null;
26048 _markDndStatus: function(copy
){
26050 // Changes source's state based on "copy" status
26051 this._changeState("Source", copy
? "Copied" : "Moved");
26057 if(!dojo
._hasResource
["dojo.data.ItemFileReadStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
26058 dojo
._hasResource
["dojo.data.ItemFileReadStore"] = true;
26059 dojo
.provide("dojo.data.ItemFileReadStore");
26065 dojo
.declare("dojo.data.ItemFileReadStore", null,{
26067 // The ItemFileReadStore implements the dojo.data.api.Read API and reads
26068 // data from JSON files that have contents in this format --
26070 // { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]},
26071 // { name:'Fozzie Bear', wears:['hat', 'tie']},
26072 // { name:'Miss Piggy', pets:'Foo-Foo'}
26074 // Note that it can also contain an 'identifer' property that specified which attribute on the items
26075 // in the array of items that acts as the unique identifier for that item.
26077 constructor: function(/* Object */ keywordParameters
){
26078 // summary: constructor
26079 // keywordParameters: {url: String}
26080 // keywordParameters: {data: jsonObject}
26081 // keywordParameters: {typeMap: object)
26082 // The structure of the typeMap object is as follows:
26084 // type0: function || object,
26085 // type1: function || object,
26087 // typeN: function || object
26089 // Where if it is a function, it is assumed to be an object constructor that takes the
26090 // value of _value as the initialization parameters. If it is an object, then it is assumed
26091 // to be an object of general form:
26093 // type: function, //constructor.
26094 // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately.
26097 this._arrayOfAllItems
= [];
26098 this._arrayOfTopLevelItems
= [];
26099 this._loadFinished
= false;
26100 this._jsonFileUrl
= keywordParameters
.url
;
26101 this._ccUrl
= keywordParameters
.url
;
26102 this.url
= keywordParameters
.url
;
26103 this._jsonData
= keywordParameters
.data
;
26105 this._datatypeMap
= keywordParameters
.typeMap
|| {};
26106 if(!this._datatypeMap
['Date']){
26107 //If no default mapping for dates, then set this as default.
26108 //We use the dojo.date.stamp here because the ISO format is the 'dojo way'
26109 //of generically representing dates.
26110 this._datatypeMap
['Date'] = {
26112 deserialize: function(value
){
26113 return dojo
.date
.stamp
.fromISOString(value
);
26117 this._features
= {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true};
26118 this._itemsByIdentity
= null;
26119 this._storeRefPropName
= "_S"; // Default name for the store reference to attach to every item.
26120 this._itemNumPropName
= "_0"; // Default Item Id for isItem to attach to every item.
26121 this._rootItemPropName
= "_RI"; // Default Item Id for isItem to attach to every item.
26122 this._reverseRefMap
= "_RRM"; // Default attribute for constructing a reverse reference map for use with reference integrity
26123 this._loadInProgress
= false; //Got to track the initial load to prevent duelling loads of the dataset.
26124 this._queuedFetches
= [];
26125 if(keywordParameters
.urlPreventCache
!== undefined){
26126 this.urlPreventCache
= keywordParameters
.urlPreventCache
?true:false;
26128 if(keywordParameters
.hierarchical
!== undefined){
26129 this.hierarchical
= keywordParameters
.hierarchical
?true:false;
26131 if(keywordParameters
.clearOnClose
){
26132 this.clearOnClose
= true;
26134 if("failOk" in keywordParameters
){
26135 this.failOk
= keywordParameters
.failOk
?true:false;
26139 url
: "", // use "" rather than undefined for the benefit of the parser (#3539)
26141 //Internal var, crossCheckUrl. Used so that setting either url or _jsonFileUrl, can still trigger a reload
26142 //when clearOnClose and close is used.
26145 data
: null, // define this so that the parser can populate it
26147 typeMap
: null, //Define so parser can populate.
26149 //Parameter to allow users to specify if a close call should force a reload or not.
26150 //By default, it retains the old behavior of not clearing if close is called. But
26151 //if set true, the store will be reset to default state. Note that by doing this,
26152 //all item handles will become invalid and a new fetch must be issued.
26153 clearOnClose
: false,
26155 //Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url.
26156 //Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option.
26157 //Added for tracker: #6072
26158 urlPreventCache
: false,
26160 //Parameter for specifying that it is OK for the xhrGet call to fail silently.
26163 //Parameter to indicate to process data from the url as hierarchical
26164 //(data items can contain other data items in js form). Default is true
26165 //for backwards compatibility. False means only root items are processed
26166 //as items, all child objects outside of type-mapped objects and those in
26167 //specific reference format, are left straight JS data objects.
26168 hierarchical
: true,
26170 _assertIsItem: function(/* item */ item
){
26172 // This function tests whether the item passed in is indeed an item in the store.
26174 // The item to test for being contained by the store.
26175 if(!this.isItem(item
)){
26176 throw new Error("dojo.data.ItemFileReadStore: Invalid item argument.");
26180 _assertIsAttribute: function(/* attribute-name-string */ attribute
){
26182 // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
26184 // The attribute to test for being contained by the store.
26185 if(typeof attribute
!== "string"){
26186 throw new Error("dojo.data.ItemFileReadStore: Invalid attribute argument.");
26190 getValue: function( /* item */ item
,
26191 /* attribute-name-string */ attribute
,
26192 /* value? */ defaultValue
){
26194 // See dojo.data.api.Read.getValue()
26195 var values
= this.getValues(item
, attribute
);
26196 return (values
.length
> 0)?values
[0]:defaultValue
; // mixed
26199 getValues: function(/* item */ item
,
26200 /* attribute-name-string */ attribute
){
26202 // See dojo.data.api.Read.getValues()
26204 this._assertIsItem(item
);
26205 this._assertIsAttribute(attribute
);
26206 // Clone it before returning. refs: #10474
26207 return (item
[attribute
] || []).slice(0); // Array
26210 getAttributes: function(/* item */ item
){
26212 // See dojo.data.api.Read.getAttributes()
26213 this._assertIsItem(item
);
26214 var attributes
= [];
26215 for(var key
in item
){
26216 // Save off only the real item attributes, not the special id marks for O(1) isItem.
26217 if((key
!== this._storeRefPropName
) && (key
!== this._itemNumPropName
) && (key
!== this._rootItemPropName
) && (key
!== this._reverseRefMap
)){
26218 attributes
.push(key
);
26221 return attributes
; // Array
26224 hasAttribute: function( /* item */ item
,
26225 /* attribute-name-string */ attribute
){
26227 // See dojo.data.api.Read.hasAttribute()
26228 this._assertIsItem(item
);
26229 this._assertIsAttribute(attribute
);
26230 return (attribute
in item
);
26233 containsValue: function(/* item */ item
,
26234 /* attribute-name-string */ attribute
,
26235 /* anything */ value
){
26237 // See dojo.data.api.Read.containsValue()
26238 var regexp
= undefined;
26239 if(typeof value
=== "string"){
26240 regexp
= dojo
.data
.util
.filter
.patternToRegExp(value
, false);
26242 return this._containsValue(item
, attribute
, value
, regexp
); //boolean.
26245 _containsValue: function( /* item */ item
,
26246 /* attribute-name-string */ attribute
,
26247 /* anything */ value
,
26248 /* RegExp?*/ regexp
){
26250 // Internal function for looking at the values contained by the item.
26252 // Internal function for looking at the values contained by the item. This
26253 // function allows for denoting if the comparison should be case sensitive for
26254 // strings or not (for handling filtering cases where string case should not matter)
26257 // The data item to examine for attribute values.
26259 // The attribute to inspect.
26261 // The value to match.
26263 // Optional regular expression generated off value if value was of string type to handle wildcarding.
26264 // If present and attribute values are string, then it can be used for comparison instead of 'value'
26265 return dojo
.some(this.getValues(item
, attribute
), function(possibleValue
){
26266 if(possibleValue
!== null && !dojo
.isObject(possibleValue
) && regexp
){
26267 if(possibleValue
.toString().match(regexp
)){
26268 return true; // Boolean
26270 }else if(value
=== possibleValue
){
26271 return true; // Boolean
26276 isItem: function(/* anything */ something
){
26278 // See dojo.data.api.Read.isItem()
26279 if(something
&& something
[this._storeRefPropName
] === this){
26280 if(this._arrayOfAllItems
[something
[this._itemNumPropName
]] === something
){
26284 return false; // Boolean
26287 isItemLoaded: function(/* anything */ something
){
26289 // See dojo.data.api.Read.isItemLoaded()
26290 return this.isItem(something
); //boolean
26293 loadItem: function(/* object */ keywordArgs
){
26295 // See dojo.data.api.Read.loadItem()
26296 this._assertIsItem(keywordArgs
.item
);
26299 getFeatures: function(){
26301 // See dojo.data.api.Read.getFeatures()
26302 return this._features
; //Object
26305 getLabel: function(/* item */ item
){
26307 // See dojo.data.api.Read.getLabel()
26308 if(this._labelAttr
&& this.isItem(item
)){
26309 return this.getValue(item
,this._labelAttr
); //String
26311 return undefined; //undefined
26314 getLabelAttributes: function(/* item */ item
){
26316 // See dojo.data.api.Read.getLabelAttributes()
26317 if(this._labelAttr
){
26318 return [this._labelAttr
]; //array
26320 return null; //null
26323 _fetchItems: function( /* Object */ keywordArgs
,
26324 /* Function */ findCallback
,
26325 /* Function */ errorCallback
){
26327 // See dojo.data.util.simpleFetch.fetch()
26329 filter = function(requestArgs
, arrayOfItems
){
26332 if(requestArgs
.query
){
26334 ignoreCase
= requestArgs
.queryOptions
? requestArgs
.queryOptions
.ignoreCase
: false;
26336 //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
26337 //same value for each item examined. Much more efficient.
26338 var regexpList
= {};
26339 for(key
in requestArgs
.query
){
26340 value
= requestArgs
.query
[key
];
26341 if(typeof value
=== "string"){
26342 regexpList
[key
] = dojo
.data
.util
.filter
.patternToRegExp(value
, ignoreCase
);
26343 }else if(value
instanceof RegExp
){
26344 regexpList
[key
] = value
;
26347 for(i
= 0; i
< arrayOfItems
.length
; ++i
){
26349 var candidateItem
= arrayOfItems
[i
];
26350 if(candidateItem
=== null){
26353 for(key
in requestArgs
.query
){
26354 value
= requestArgs
.query
[key
];
26355 if(!self
._containsValue(candidateItem
, key
, value
, regexpList
[key
])){
26361 items
.push(candidateItem
);
26364 findCallback(items
, requestArgs
);
26366 // We want a copy to pass back in case the parent wishes to sort the array.
26367 // We shouldn't allow resort of the internal list, so that multiple callers
26368 // can get lists and sort without affecting each other. We also need to
26369 // filter out any null values that have been left as a result of deleteItem()
26370 // calls in ItemFileWriteStore.
26371 for(i
= 0; i
< arrayOfItems
.length
; ++i
){
26372 var item
= arrayOfItems
[i
];
26377 findCallback(items
, requestArgs
);
26381 if(this._loadFinished
){
26382 filter(keywordArgs
, this._getItemsArray(keywordArgs
.queryOptions
));
26384 //Do a check on the JsonFileUrl and crosscheck it.
26385 //If it doesn't match the cross-check, it needs to be updated
26386 //This allows for either url or _jsonFileUrl to he changed to
26387 //reset the store load location. Done this way for backwards
26388 //compatibility. People use _jsonFileUrl (even though officially
26390 if(this._jsonFileUrl
!== this._ccUrl
){
26391 dojo
.deprecated("dojo.data.ItemFileReadStore: ",
26392 "To change the url, set the url property of the store," +
26393 " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
26394 this._ccUrl
= this._jsonFileUrl
;
26395 this.url
= this._jsonFileUrl
;
26396 }else if(this.url
!== this._ccUrl
){
26397 this._jsonFileUrl
= this.url
;
26398 this._ccUrl
= this.url
;
26401 //See if there was any forced reset of data.
26402 if(this.data
!= null){
26403 this._jsonData
= this.data
;
26407 if(this._jsonFileUrl
){
26408 //If fetches come in before the loading has finished, but while
26409 //a load is in progress, we have to defer the fetching to be
26410 //invoked in the callback.
26411 if(this._loadInProgress
){
26412 this._queuedFetches
.push({args
: keywordArgs
, filter
: filter
});
26414 this._loadInProgress
= true;
26416 url
: self
._jsonFileUrl
,
26417 handleAs
: "json-comment-optional",
26418 preventCache
: this.urlPreventCache
,
26419 failOk
: this.failOk
26421 var getHandler
= dojo
.xhrGet(getArgs
);
26422 getHandler
.addCallback(function(data
){
26424 self
._getItemsFromLoadedData(data
);
26425 self
._loadFinished
= true;
26426 self
._loadInProgress
= false;
26428 filter(keywordArgs
, self
._getItemsArray(keywordArgs
.queryOptions
));
26429 self
._handleQueuedFetches();
26431 self
._loadFinished
= true;
26432 self
._loadInProgress
= false;
26433 errorCallback(e
, keywordArgs
);
26436 getHandler
.addErrback(function(error
){
26437 self
._loadInProgress
= false;
26438 errorCallback(error
, keywordArgs
);
26441 //Wire up the cancel to abort of the request
26442 //This call cancel on the deferred if it hasn't been called
26443 //yet and then will chain to the simple abort of the
26444 //simpleFetch keywordArgs
26445 var oldAbort
= null;
26446 if(keywordArgs
.abort
){
26447 oldAbort
= keywordArgs
.abort
;
26449 keywordArgs
.abort = function(){
26450 var df
= getHandler
;
26451 if(df
&& df
.fired
=== -1){
26456 oldAbort
.call(keywordArgs
);
26460 }else if(this._jsonData
){
26462 this._loadFinished
= true;
26463 this._getItemsFromLoadedData(this._jsonData
);
26464 this._jsonData
= null;
26465 filter(keywordArgs
, this._getItemsArray(keywordArgs
.queryOptions
));
26467 errorCallback(e
, keywordArgs
);
26470 errorCallback(new Error("dojo.data.ItemFileReadStore: No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs
);
26475 _handleQueuedFetches: function(){
26477 // Internal function to execute delayed request in the store.
26478 //Execute any deferred fetches now.
26479 if(this._queuedFetches
.length
> 0){
26480 for(var i
= 0; i
< this._queuedFetches
.length
; i
++){
26481 var fData
= this._queuedFetches
[i
],
26482 delayedQuery
= fData
.args
,
26483 delayedFilter
= fData
.filter
;
26485 delayedFilter(delayedQuery
, this._getItemsArray(delayedQuery
.queryOptions
));
26487 this.fetchItemByIdentity(delayedQuery
);
26490 this._queuedFetches
= [];
26494 _getItemsArray: function(/*object?*/queryOptions
){
26496 // Internal function to determine which list of items to search over.
26497 // queryOptions: The query options parameter, if any.
26498 if(queryOptions
&& queryOptions
.deep
){
26499 return this._arrayOfAllItems
;
26501 return this._arrayOfTopLevelItems
;
26504 close: function(/*dojo.data.api.Request || keywordArgs || null */ request
){
26506 // See dojo.data.api.Read.close()
26507 if(this.clearOnClose
&&
26508 this._loadFinished
&&
26509 !this._loadInProgress
){
26510 //Reset all internalsback to default state. This will force a reload
26511 //on next fetch. This also checks that the data or url param was set
26512 //so that the store knows it can get data. Without one of those being set,
26513 //the next fetch will trigger an error.
26515 if(((this._jsonFileUrl
== "" || this._jsonFileUrl
== null) &&
26516 (this.url
== "" || this.url
== null)
26517 ) && this.data
== null){
26518 console
.debug("dojo.data.ItemFileReadStore: WARNING! Data reload " +
26519 " information has not been provided." +
26520 " Please set 'url' or 'data' to the appropriate value before" +
26521 " the next fetch");
26523 this._arrayOfAllItems
= [];
26524 this._arrayOfTopLevelItems
= [];
26525 this._loadFinished
= false;
26526 this._itemsByIdentity
= null;
26527 this._loadInProgress
= false;
26528 this._queuedFetches
= [];
26532 _getItemsFromLoadedData: function(/* Object */ dataObject
){
26534 // Function to parse the loaded data into item format and build the internal items array.
26536 // Function to parse the loaded data into item format and build the internal items array.
26539 // The JS data object containing the raw data to convery into item format.
26542 // Array of items in store item format.
26544 // First, we define a couple little utility functions...
26545 var addingArrays
= false,
26548 function valueIsAnItem(/* anything */ aValue
){
26550 // Given any sort of value that could be in the raw json data,
26551 // return true if we should interpret the value as being an
26552 // item itself, rather than a literal value or a reference.
26554 // | false == valueIsAnItem("Kermit");
26555 // | false == valueIsAnItem(42);
26556 // | false == valueIsAnItem(new Date());
26557 // | false == valueIsAnItem({_type:'Date', _value:'1802-05-14'});
26558 // | false == valueIsAnItem({_reference:'Kermit'});
26559 // | true == valueIsAnItem({name:'Kermit', color:'green'});
26560 // | true == valueIsAnItem({iggy:'pop'});
26561 // | true == valueIsAnItem({foo:42});
26563 (aValue
!== null) &&
26564 (typeof aValue
=== "object") &&
26565 (!dojo
.isArray(aValue
) || addingArrays
) &&
26566 (!dojo
.isFunction(aValue
)) &&
26567 (aValue
.constructor == Object
|| dojo
.isArray(aValue
)) &&
26568 (typeof aValue
._reference
=== "undefined") &&
26569 (typeof aValue
._type
=== "undefined") &&
26570 (typeof aValue
._value
=== "undefined") &&
26576 function addItemAndSubItemsToArrayOfAllItems(/* Item */ anItem
){
26577 self
._arrayOfAllItems
.push(anItem
);
26578 for(var attribute
in anItem
){
26579 var valueForAttribute
= anItem
[attribute
];
26580 if(valueForAttribute
){
26581 if(dojo
.isArray(valueForAttribute
)){
26582 var valueArray
= valueForAttribute
;
26583 for(var k
= 0; k
< valueArray
.length
; ++k
){
26584 var singleValue
= valueArray
[k
];
26585 if(valueIsAnItem(singleValue
)){
26586 addItemAndSubItemsToArrayOfAllItems(singleValue
);
26590 if(valueIsAnItem(valueForAttribute
)){
26591 addItemAndSubItemsToArrayOfAllItems(valueForAttribute
);
26598 this._labelAttr
= dataObject
.label
;
26600 // We need to do some transformations to convert the data structure
26601 // that we read from the file into a format that will be convenient
26602 // to work with in memory.
26604 // Step 1: Walk through the object hierarchy and build a list of all items
26607 this._arrayOfAllItems
= [];
26608 this._arrayOfTopLevelItems
= dataObject
.items
;
26610 for(i
= 0; i
< this._arrayOfTopLevelItems
.length
; ++i
){
26611 item
= this._arrayOfTopLevelItems
[i
];
26612 if(dojo
.isArray(item
)){
26613 addingArrays
= true;
26615 addItemAndSubItemsToArrayOfAllItems(item
);
26616 item
[this._rootItemPropName
]=true;
26619 // Step 2: Walk through all the attribute values of all the items,
26620 // and replace single values with arrays. For example, we change this:
26621 // { name:'Miss Piggy', pets:'Foo-Foo'}
26623 // { name:['Miss Piggy'], pets:['Foo-Foo']}
26625 // We also store the attribute names so we can validate our store
26626 // reference and item id special properties for the O(1) isItem
26627 var allAttributeNames
= {},
26630 for(i
= 0; i
< this._arrayOfAllItems
.length
; ++i
){
26631 item
= this._arrayOfAllItems
[i
];
26633 if(key
!== this._rootItemPropName
){
26634 var value
= item
[key
];
26635 if(value
!== null){
26636 if(!dojo
.isArray(value
)){
26637 item
[key
] = [value
];
26640 item
[key
] = [null];
26643 allAttributeNames
[key
]=key
;
26647 // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName
26648 // This should go really fast, it will generally never even run the loop.
26649 while(allAttributeNames
[this._storeRefPropName
]){
26650 this._storeRefPropName
+= "_";
26652 while(allAttributeNames
[this._itemNumPropName
]){
26653 this._itemNumPropName
+= "_";
26655 while(allAttributeNames
[this._reverseRefMap
]){
26656 this._reverseRefMap
+= "_";
26659 // Step 4: Some data files specify an optional 'identifier', which is
26660 // the name of an attribute that holds the identity of each item.
26661 // If this data file specified an identifier attribute, then build a
26662 // hash table of items keyed by the identity of the items.
26665 var identifier
= dataObject
.identifier
;
26667 this._itemsByIdentity
= {};
26668 this._features
['dojo.data.api.Identity'] = identifier
;
26669 for(i
= 0; i
< this._arrayOfAllItems
.length
; ++i
){
26670 item
= this._arrayOfAllItems
[i
];
26671 arrayOfValues
= item
[identifier
];
26672 var identity
= arrayOfValues
[0];
26673 if(!Object
.hasOwnProperty
.call(this._itemsByIdentity
, identity
)){
26674 this._itemsByIdentity
[identity
] = item
;
26676 if(this._jsonFileUrl
){
26677 throw new Error("dojo.data.ItemFileReadStore: The json data as specified by: [" + this._jsonFileUrl
+ "] is malformed. Items within the list have identifier: [" + identifier
+ "]. Value collided: [" + identity
+ "]");
26678 }else if(this._jsonData
){
26679 throw new Error("dojo.data.ItemFileReadStore: The json data provided by the creation arguments is malformed. Items within the list have identifier: [" + identifier
+ "]. Value collided: [" + identity
+ "]");
26684 this._features
['dojo.data.api.Identity'] = Number
;
26687 // Step 5: Walk through all the items, and set each item's properties
26688 // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true.
26689 for(i
= 0; i
< this._arrayOfAllItems
.length
; ++i
){
26690 item
= this._arrayOfAllItems
[i
];
26691 item
[this._storeRefPropName
] = this;
26692 item
[this._itemNumPropName
] = i
;
26695 // Step 6: We walk through all the attribute values of all the items,
26696 // looking for type/value literals and item-references.
26698 // We replace item-references with pointers to items. For example, we change:
26699 // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
26701 // { name:['Kermit'], friends:[miss_piggy] }
26702 // (where miss_piggy is the object representing the 'Miss Piggy' item).
26704 // We replace type/value pairs with typed-literals. For example, we change:
26705 // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'1918-07-18'}] }
26707 // { name:['Kermit'], born:(new Date(1918, 6, 18)) }
26709 // We also generate the associate map for all items for the O(1) isItem function.
26710 for(i
= 0; i
< this._arrayOfAllItems
.length
; ++i
){
26711 item
= this._arrayOfAllItems
[i
]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
26713 arrayOfValues
= item
[key
]; // example: [{_reference:{name:'Miss Piggy'}}]
26714 for(var j
= 0; j
< arrayOfValues
.length
; ++j
){
26715 value
= arrayOfValues
[j
]; // example: {_reference:{name:'Miss Piggy'}}
26716 if(value
!== null && typeof value
== "object"){
26717 if(("_type" in value
) && ("_value" in value
)){
26718 var type
= value
._type
; // examples: 'Date', 'Color', or 'ComplexNumber'
26719 var mappingObj
= this._datatypeMap
[type
]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}}
26721 throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type
+ "'");
26722 }else if(dojo
.isFunction(mappingObj
)){
26723 arrayOfValues
[j
] = new mappingObj(value
._value
);
26724 }else if(dojo
.isFunction(mappingObj
.deserialize
)){
26725 arrayOfValues
[j
] = mappingObj
.deserialize(value
._value
);
26727 throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function");
26730 if(value
._reference
){
26731 var referenceDescription
= value
._reference
; // example: {name:'Miss Piggy'}
26732 if(!dojo
.isObject(referenceDescription
)){
26733 // example: 'Miss Piggy'
26734 // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]}
26735 arrayOfValues
[j
] = this._getItemByIdentity(referenceDescription
);
26737 // example: {name:'Miss Piggy'}
26738 // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
26739 for(var k
= 0; k
< this._arrayOfAllItems
.length
; ++k
){
26740 var candidateItem
= this._arrayOfAllItems
[k
],
26742 for(var refKey
in referenceDescription
){
26743 if(candidateItem
[refKey
] != referenceDescription
[refKey
]){
26748 arrayOfValues
[j
] = candidateItem
;
26752 if(this.referenceIntegrity
){
26753 var refItem
= arrayOfValues
[j
];
26754 if(this.isItem(refItem
)){
26755 this._addReferenceToMap(refItem
, item
, key
);
26758 }else if(this.isItem(value
)){
26759 //It's a child item (not one referenced through _reference).
26760 //We need to treat this as a referenced item, so it can be cleaned up
26761 //in a write store easily.
26762 if(this.referenceIntegrity
){
26763 this._addReferenceToMap(value
, item
, key
);
26772 _addReferenceToMap: function(/*item*/ refItem
, /*item*/ parentItem
, /*string*/ attribute
){
26774 // Method to add an reference map entry for an item and attribute.
26776 // Method to add an reference map entry for an item and attribute. //
26778 // The item that is referenced.
26780 // The item that holds the new reference to refItem.
26782 // The attribute on parentItem that contains the new reference.
26784 //Stub function, does nothing. Real processing is in ItemFileWriteStore.
26787 getIdentity: function(/* item */ item
){
26789 // See dojo.data.api.Identity.getIdentity()
26790 var identifier
= this._features
['dojo.data.api.Identity'];
26791 if(identifier
=== Number
){
26792 return item
[this._itemNumPropName
]; // Number
26794 var arrayOfValues
= item
[identifier
];
26796 return arrayOfValues
[0]; // Object || String
26799 return null; // null
26802 fetchItemByIdentity: function(/* Object */ keywordArgs
){
26804 // See dojo.data.api.Identity.fetchItemByIdentity()
26806 // Hasn't loaded yet, we have to trigger the load.
26809 if(!this._loadFinished
){
26811 //Do a check on the JsonFileUrl and crosscheck it.
26812 //If it doesn't match the cross-check, it needs to be updated
26813 //This allows for either url or _jsonFileUrl to he changed to
26814 //reset the store load location. Done this way for backwards
26815 //compatibility. People use _jsonFileUrl (even though officially
26817 if(this._jsonFileUrl
!== this._ccUrl
){
26818 dojo
.deprecated("dojo.data.ItemFileReadStore: ",
26819 "To change the url, set the url property of the store," +
26820 " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
26821 this._ccUrl
= this._jsonFileUrl
;
26822 this.url
= this._jsonFileUrl
;
26823 }else if(this.url
!== this._ccUrl
){
26824 this._jsonFileUrl
= this.url
;
26825 this._ccUrl
= this.url
;
26828 //See if there was any forced reset of data.
26829 if(this.data
!= null && this._jsonData
== null){
26830 this._jsonData
= this.data
;
26834 if(this._jsonFileUrl
){
26836 if(this._loadInProgress
){
26837 this._queuedFetches
.push({args
: keywordArgs
});
26839 this._loadInProgress
= true;
26841 url
: self
._jsonFileUrl
,
26842 handleAs
: "json-comment-optional",
26843 preventCache
: this.urlPreventCache
,
26844 failOk
: this.failOk
26846 var getHandler
= dojo
.xhrGet(getArgs
);
26847 getHandler
.addCallback(function(data
){
26848 var scope
= keywordArgs
.scope
?keywordArgs
.scope
:dojo
.global
;
26850 self
._getItemsFromLoadedData(data
);
26851 self
._loadFinished
= true;
26852 self
._loadInProgress
= false;
26853 item
= self
._getItemByIdentity(keywordArgs
.identity
);
26854 if(keywordArgs
.onItem
){
26855 keywordArgs
.onItem
.call(scope
, item
);
26857 self
._handleQueuedFetches();
26859 self
._loadInProgress
= false;
26860 if(keywordArgs
.onError
){
26861 keywordArgs
.onError
.call(scope
, error
);
26865 getHandler
.addErrback(function(error
){
26866 self
._loadInProgress
= false;
26867 if(keywordArgs
.onError
){
26868 var scope
= keywordArgs
.scope
?keywordArgs
.scope
:dojo
.global
;
26869 keywordArgs
.onError
.call(scope
, error
);
26874 }else if(this._jsonData
){
26875 // Passed in data, no need to xhr.
26876 self
._getItemsFromLoadedData(self
._jsonData
);
26877 self
._jsonData
= null;
26878 self
._loadFinished
= true;
26879 item
= self
._getItemByIdentity(keywordArgs
.identity
);
26880 if(keywordArgs
.onItem
){
26881 scope
= keywordArgs
.scope
?keywordArgs
.scope
:dojo
.global
;
26882 keywordArgs
.onItem
.call(scope
, item
);
26886 // Already loaded. We can just look it up and call back.
26887 item
= this._getItemByIdentity(keywordArgs
.identity
);
26888 if(keywordArgs
.onItem
){
26889 scope
= keywordArgs
.scope
?keywordArgs
.scope
:dojo
.global
;
26890 keywordArgs
.onItem
.call(scope
, item
);
26895 _getItemByIdentity: function(/* Object */ identity
){
26897 // Internal function to look an item up by its identity map.
26899 if(this._itemsByIdentity
&&
26900 Object
.hasOwnProperty
.call(this._itemsByIdentity
, identity
)){
26901 item
= this._itemsByIdentity
[identity
];
26902 }else if (Object
.hasOwnProperty
.call(this._arrayOfAllItems
, identity
)){
26903 item
= this._arrayOfAllItems
[identity
];
26905 if(item
=== undefined){
26908 return item
; // Object
26911 getIdentityAttributes: function(/* item */ item
){
26913 // See dojo.data.api.Identity.getIdentityAttributes()
26915 var identifier
= this._features
['dojo.data.api.Identity'];
26916 if(identifier
=== Number
){
26917 // If (identifier === Number) it means getIdentity() just returns
26918 // an integer item-number for each item. The dojo.data.api.Identity
26919 // spec says we need to return null if the identity is not composed
26921 return null; // null
26923 return [identifier
]; // Array
26927 _forceLoad: function(){
26929 // Internal function to force a load of the store if it hasn't occurred yet. This is required
26930 // for specific functions to work properly.
26932 //Do a check on the JsonFileUrl and crosscheck it.
26933 //If it doesn't match the cross-check, it needs to be updated
26934 //This allows for either url or _jsonFileUrl to he changed to
26935 //reset the store load location. Done this way for backwards
26936 //compatibility. People use _jsonFileUrl (even though officially
26938 if(this._jsonFileUrl
!== this._ccUrl
){
26939 dojo
.deprecated("dojo.data.ItemFileReadStore: ",
26940 "To change the url, set the url property of the store," +
26941 " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
26942 this._ccUrl
= this._jsonFileUrl
;
26943 this.url
= this._jsonFileUrl
;
26944 }else if(this.url
!== this._ccUrl
){
26945 this._jsonFileUrl
= this.url
;
26946 this._ccUrl
= this.url
;
26949 //See if there was any forced reset of data.
26950 if(this.data
!= null){
26951 this._jsonData
= this.data
;
26955 if(this._jsonFileUrl
){
26957 url
: this._jsonFileUrl
,
26958 handleAs
: "json-comment-optional",
26959 preventCache
: this.urlPreventCache
,
26960 failOk
: this.failOk
,
26963 var getHandler
= dojo
.xhrGet(getArgs
);
26964 getHandler
.addCallback(function(data
){
26966 //Check to be sure there wasn't another load going on concurrently
26967 //So we don't clobber data that comes in on it. If there is a load going on
26968 //then do not save this data. It will potentially clobber current data.
26969 //We mainly wanted to sync/wait here.
26970 //TODO: Revisit the loading scheme of this store to improve multi-initial
26971 //request handling.
26972 if(self
._loadInProgress
!== true && !self
._loadFinished
){
26973 self
._getItemsFromLoadedData(data
);
26974 self
._loadFinished
= true;
26975 }else if(self
._loadInProgress
){
26976 //Okay, we hit an error state we can't recover from. A forced load occurred
26977 //while an async load was occurring. Since we cannot block at this point, the best
26978 //that can be managed is to throw an error.
26979 throw new Error("dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress.");
26986 getHandler
.addErrback(function(error
){
26989 }else if(this._jsonData
){
26990 self
._getItemsFromLoadedData(self
._jsonData
);
26991 self
._jsonData
= null;
26992 self
._loadFinished
= true;
26996 //Mix in the simple fetch implementation to this class.
26997 dojo
.extend(dojo
.data
.ItemFileReadStore
,dojo
.data
.util
.simpleFetch
);
27001 if(!dojo
._hasResource
["dojo.data.ItemFileWriteStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
27002 dojo
._hasResource
["dojo.data.ItemFileWriteStore"] = true;
27003 dojo
.provide("dojo.data.ItemFileWriteStore");
27007 dojo
.declare("dojo.data.ItemFileWriteStore", dojo
.data
.ItemFileReadStore
, {
27008 constructor: function(/* object */ keywordParameters
){
27009 // keywordParameters: {typeMap: object)
27010 // The structure of the typeMap object is as follows:
27012 // type0: function || object,
27013 // type1: function || object,
27015 // typeN: function || object
27017 // Where if it is a function, it is assumed to be an object constructor that takes the
27018 // value of _value as the initialization parameters. It is serialized assuming object.toString()
27019 // serialization. If it is an object, then it is assumed
27020 // to be an object of general form:
27022 // type: function, //constructor.
27023 // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately.
27024 // serialize: function(object) //The function that converts the object back into the proper file format form.
27027 // ItemFileWriteStore extends ItemFileReadStore to implement these additional dojo.data APIs
27028 this._features
['dojo.data.api.Write'] = true;
27029 this._features
['dojo.data.api.Notification'] = true;
27031 // For keeping track of changes so that we can implement isDirty and revert
27038 if(!this._datatypeMap
['Date'].serialize
){
27039 this._datatypeMap
['Date'].serialize = function(obj
){
27040 return dojo
.date
.stamp
.toISOString(obj
, {zulu
:true});
27043 //Disable only if explicitly set to false.
27044 if(keywordParameters
&& (keywordParameters
.referenceIntegrity
=== false)){
27045 this.referenceIntegrity
= false;
27048 // this._saveInProgress is set to true, briefly, from when save() is first called to when it completes
27049 this._saveInProgress
= false;
27052 referenceIntegrity
: true, //Flag that defaultly enabled reference integrity tracking. This way it can also be disabled pogrammatially or declaratively.
27054 _assert: function(/* boolean */ condition
){
27056 throw new Error("assertion failed in ItemFileWriteStore");
27060 _getIdentifierAttribute: function(){
27061 var identifierAttribute
= this.getFeatures()['dojo.data.api.Identity'];
27062 // this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute)));
27063 return identifierAttribute
;
27067 /* dojo.data.api.Write */
27069 newItem: function(/* Object? */ keywordArgs
, /* Object? */ parentInfo
){
27070 // summary: See dojo.data.api.Write.newItem()
27072 this._assert(!this._saveInProgress
);
27074 if(!this._loadFinished
){
27075 // We need to do this here so that we'll be able to find out what
27076 // identifierAttribute was specified in the data file.
27080 if(typeof keywordArgs
!= "object" && typeof keywordArgs
!= "undefined"){
27081 throw new Error("newItem() was passed something other than an object");
27083 var newIdentity
= null;
27084 var identifierAttribute
= this._getIdentifierAttribute();
27085 if(identifierAttribute
=== Number
){
27086 newIdentity
= this._arrayOfAllItems
.length
;
27088 newIdentity
= keywordArgs
[identifierAttribute
];
27089 if(typeof newIdentity
=== "undefined"){
27090 throw new Error("newItem() was not passed an identity for the new item");
27092 if(dojo
.isArray(newIdentity
)){
27093 throw new Error("newItem() was not passed an single-valued identity");
27097 // make sure this identity is not already in use by another item, if identifiers were
27098 // defined in the file. Otherwise it would be the item count,
27099 // which should always be unique in this case.
27100 if(this._itemsByIdentity
){
27101 this._assert(typeof this._itemsByIdentity
[newIdentity
] === "undefined");
27103 this._assert(typeof this._pending
._newItems
[newIdentity
] === "undefined");
27104 this._assert(typeof this._pending
._deletedItems
[newIdentity
] === "undefined");
27107 newItem
[this._storeRefPropName
] = this;
27108 newItem
[this._itemNumPropName
] = this._arrayOfAllItems
.length
;
27109 if(this._itemsByIdentity
){
27110 this._itemsByIdentity
[newIdentity
] = newItem
;
27111 //We have to set the identifier now, otherwise we can't look it
27112 //up at calls to setValueorValues in parentInfo handling.
27113 newItem
[identifierAttribute
] = [newIdentity
];
27115 this._arrayOfAllItems
.push(newItem
);
27117 //We need to construct some data for the onNew call too...
27120 // Now we need to check to see where we want to assign this thingm if any.
27121 if(parentInfo
&& parentInfo
.parent
&& parentInfo
.attribute
){
27123 item
: parentInfo
.parent
,
27124 attribute
: parentInfo
.attribute
,
27125 oldValue
: undefined
27128 //See if it is multi-valued or not and handle appropriately
27129 //Generally, all attributes are multi-valued for this store
27130 //So, we only need to append if there are already values present.
27131 var values
= this.getValues(parentInfo
.parent
, parentInfo
.attribute
);
27132 if(values
&& values
.length
> 0){
27133 var tempValues
= values
.slice(0, values
.length
);
27134 if(values
.length
=== 1){
27135 pInfo
.oldValue
= values
[0];
27137 pInfo
.oldValue
= values
.slice(0, values
.length
);
27139 tempValues
.push(newItem
);
27140 this._setValueOrValues(parentInfo
.parent
, parentInfo
.attribute
, tempValues
, false);
27141 pInfo
.newValue
= this.getValues(parentInfo
.parent
, parentInfo
.attribute
);
27143 this._setValueOrValues(parentInfo
.parent
, parentInfo
.attribute
, newItem
, false);
27144 pInfo
.newValue
= newItem
;
27147 //Toplevel item, add to both top list as well as all list.
27148 newItem
[this._rootItemPropName
]=true;
27149 this._arrayOfTopLevelItems
.push(newItem
);
27152 this._pending
._newItems
[newIdentity
] = newItem
;
27154 //Clone over the properties to the new item
27155 for(var key
in keywordArgs
){
27156 if(key
=== this._storeRefPropName
|| key
=== this._itemNumPropName
){
27157 // Bummer, the user is trying to do something like
27158 // newItem({_S:"foo"}). Unfortunately, our superclass,
27159 // ItemFileReadStore, is already using _S in each of our items
27160 // to hold private info. To avoid a naming collision, we
27161 // need to move all our private info to some other property
27162 // of all the items/objects. So, we need to iterate over all
27163 // the items and do something like:
27164 // item.__S = item._S;
27165 // item._S = undefined;
27166 // But first we have to make sure the new "__S" variable is
27167 // not in use, which means we have to iterate over all the
27168 // items checking for that.
27169 throw new Error("encountered bug in ItemFileWriteStore.newItem");
27171 var value
= keywordArgs
[key
];
27172 if(!dojo
.isArray(value
)){
27175 newItem
[key
] = value
;
27176 if(this.referenceIntegrity
){
27177 for(var i
= 0; i
< value
.length
; i
++){
27178 var val
= value
[i
];
27179 if(this.isItem(val
)){
27180 this._addReferenceToMap(val
, newItem
, key
);
27185 this.onNew(newItem
, pInfo
); // dojo.data.api.Notification call
27186 return newItem
; // item
27189 _removeArrayElement: function(/* Array */ array
, /* anything */ element
){
27190 var index
= dojo
.indexOf(array
, element
);
27192 array
.splice(index
, 1);
27198 deleteItem: function(/* item */ item
){
27199 // summary: See dojo.data.api.Write.deleteItem()
27200 this._assert(!this._saveInProgress
);
27201 this._assertIsItem(item
);
27203 // Remove this item from the _arrayOfAllItems, but leave a null value in place
27204 // of the item, so as not to change the length of the array, so that in newItem()
27205 // we can still safely do: newIdentity = this._arrayOfAllItems.length;
27206 var indexInArrayOfAllItems
= item
[this._itemNumPropName
];
27207 var identity
= this.getIdentity(item
);
27209 //If we have reference integrity on, we need to do reference cleanup for the deleted item
27210 if(this.referenceIntegrity
){
27211 //First scan all the attributes of this items for references and clean them up in the map
27212 //As this item is going away, no need to track its references anymore.
27214 //Get the attributes list before we generate the backup so it
27215 //doesn't pollute the attributes list.
27216 var attributes
= this.getAttributes(item
);
27218 //Backup the map, we'll have to restore it potentially, in a revert.
27219 if(item
[this._reverseRefMap
]){
27220 item
["backup_" + this._reverseRefMap
] = dojo
.clone(item
[this._reverseRefMap
]);
27223 //TODO: This causes a reversion problem. This list won't be restored on revert since it is
27224 //attached to the 'value'. item, not ours. Need to back tese up somehow too.
27225 //Maybe build a map of the backup of the entries and attach it to the deleted item to be restored
27226 //later. Or just record them and call _addReferenceToMap on them in revert.
27227 dojo
.forEach(attributes
, function(attribute
){
27228 dojo
.forEach(this.getValues(item
, attribute
), function(value
){
27229 if(this.isItem(value
)){
27230 //We have to back up all the references we had to others so they can be restored on a revert.
27231 if(!item
["backupRefs_" + this._reverseRefMap
]){
27232 item
["backupRefs_" + this._reverseRefMap
] = [];
27234 item
["backupRefs_" + this._reverseRefMap
].push({id
: this.getIdentity(value
), attr
: attribute
});
27235 this._removeReferenceFromMap(value
, item
, attribute
);
27240 //Next, see if we have references to this item, if we do, we have to clean them up too.
27241 var references
= item
[this._reverseRefMap
];
27243 //Look through all the items noted as references to clean them up.
27244 for(var itemId
in references
){
27245 var containingItem
= null;
27246 if(this._itemsByIdentity
){
27247 containingItem
= this._itemsByIdentity
[itemId
];
27249 containingItem
= this._arrayOfAllItems
[itemId
];
27251 //We have a reference to a containing item, now we have to process the
27252 //attributes and clear all references to the item being deleted.
27253 if(containingItem
){
27254 for(var attribute
in references
[itemId
]){
27255 var oldValues
= this.getValues(containingItem
, attribute
) || [];
27256 var newValues
= dojo
.filter(oldValues
, function(possibleItem
){
27257 return !(this.isItem(possibleItem
) && this.getIdentity(possibleItem
) == identity
);
27259 //Remove the note of the reference to the item and set the values on the modified attribute.
27260 this._removeReferenceFromMap(item
, containingItem
, attribute
);
27261 if(newValues
.length
< oldValues
.length
){
27262 this._setValueOrValues(containingItem
, attribute
, newValues
, true);
27270 this._arrayOfAllItems
[indexInArrayOfAllItems
] = null;
27272 item
[this._storeRefPropName
] = null;
27273 if(this._itemsByIdentity
){
27274 delete this._itemsByIdentity
[identity
];
27276 this._pending
._deletedItems
[identity
] = item
;
27278 //Remove from the toplevel items, if necessary...
27279 if(item
[this._rootItemPropName
]){
27280 this._removeArrayElement(this._arrayOfTopLevelItems
, item
);
27282 this.onDelete(item
); // dojo.data.api.Notification call
27286 setValue: function(/* item */ item
, /* attribute-name-string */ attribute
, /* almost anything */ value
){
27287 // summary: See dojo.data.api.Write.set()
27288 return this._setValueOrValues(item
, attribute
, value
, true); // boolean
27291 setValues: function(/* item */ item
, /* attribute-name-string */ attribute
, /* array */ values
){
27292 // summary: See dojo.data.api.Write.setValues()
27293 return this._setValueOrValues(item
, attribute
, values
, true); // boolean
27296 unsetAttribute: function(/* item */ item
, /* attribute-name-string */ attribute
){
27297 // summary: See dojo.data.api.Write.unsetAttribute()
27298 return this._setValueOrValues(item
, attribute
, [], true);
27301 _setValueOrValues: function(/* item */ item
, /* attribute-name-string */ attribute
, /* anything */ newValueOrValues
, /*boolean?*/ callOnSet
){
27302 this._assert(!this._saveInProgress
);
27304 // Check for valid arguments
27305 this._assertIsItem(item
);
27306 this._assert(dojo
.isString(attribute
));
27307 this._assert(typeof newValueOrValues
!== "undefined");
27309 // Make sure the user isn't trying to change the item's identity
27310 var identifierAttribute
= this._getIdentifierAttribute();
27311 if(attribute
== identifierAttribute
){
27312 throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier.");
27315 // To implement the Notification API, we need to make a note of what
27316 // the old attribute value was, so that we can pass that info when
27317 // we call the onSet method.
27318 var oldValueOrValues
= this._getValueOrValues(item
, attribute
);
27320 var identity
= this.getIdentity(item
);
27321 if(!this._pending
._modifiedItems
[identity
]){
27322 // Before we actually change the item, we make a copy of it to
27323 // record the original state, so that we'll be able to revert if
27324 // the revert method gets called. If the item has already been
27325 // modified then there's no need to do this now, since we already
27326 // have a record of the original state.
27327 var copyOfItemState
= {};
27328 for(var key
in item
){
27329 if((key
=== this._storeRefPropName
) || (key
=== this._itemNumPropName
) || (key
=== this._rootItemPropName
)){
27330 copyOfItemState
[key
] = item
[key
];
27331 }else if(key
=== this._reverseRefMap
){
27332 copyOfItemState
[key
] = dojo
.clone(item
[key
]);
27334 copyOfItemState
[key
] = item
[key
].slice(0, item
[key
].length
);
27337 // Now mark the item as dirty, and save the copy of the original state
27338 this._pending
._modifiedItems
[identity
] = copyOfItemState
;
27341 // Okay, now we can actually change this attribute on the item
27342 var success
= false;
27344 if(dojo
.isArray(newValueOrValues
) && newValueOrValues
.length
=== 0){
27346 // If we were passed an empty array as the value, that counts
27347 // as "unsetting" the attribute, so we need to remove this
27348 // attribute from the item.
27349 success
= delete item
[attribute
];
27350 newValueOrValues
= undefined; // used in the onSet Notification call below
27352 if(this.referenceIntegrity
&& oldValueOrValues
){
27353 var oldValues
= oldValueOrValues
;
27354 if(!dojo
.isArray(oldValues
)){
27355 oldValues
= [oldValues
];
27357 for(var i
= 0; i
< oldValues
.length
; i
++){
27358 var value
= oldValues
[i
];
27359 if(this.isItem(value
)){
27360 this._removeReferenceFromMap(value
, item
, attribute
);
27366 if(dojo
.isArray(newValueOrValues
)){
27367 var newValues
= newValueOrValues
;
27368 // Unfortunately, it's not safe to just do this:
27369 // newValueArray = newValues;
27370 // Instead, we need to copy the array, which slice() does very nicely.
27371 // This is so that our internal data structure won't
27372 // get corrupted if the user mucks with the values array *after*
27373 // calling setValues().
27374 newValueArray
= newValueOrValues
.slice(0, newValueOrValues
.length
);
27376 newValueArray
= [newValueOrValues
];
27379 //We need to handle reference integrity if this is on.
27380 //In the case of set, we need to see if references were added or removed
27381 //and update the reference tracking map accordingly.
27382 if(this.referenceIntegrity
){
27383 if(oldValueOrValues
){
27384 var oldValues
= oldValueOrValues
;
27385 if(!dojo
.isArray(oldValues
)){
27386 oldValues
= [oldValues
];
27388 //Use an associative map to determine what was added/removed from the list.
27389 //Should be O(n) performant. First look at all the old values and make a list of them
27390 //Then for any item not in the old list, we add it. If it was already present, we remove it.
27391 //Then we pass over the map and any references left it it need to be removed (IE, no match in
27392 //the new values list).
27394 dojo
.forEach(oldValues
, function(possibleItem
){
27395 if(this.isItem(possibleItem
)){
27396 var id
= this.getIdentity(possibleItem
);
27397 map
[id
.toString()] = true;
27400 dojo
.forEach(newValueArray
, function(possibleItem
){
27401 if(this.isItem(possibleItem
)){
27402 var id
= this.getIdentity(possibleItem
);
27403 if(map
[id
.toString()]){
27404 delete map
[id
.toString()];
27406 this._addReferenceToMap(possibleItem
, item
, attribute
);
27410 for(var rId
in map
){
27412 if(this._itemsByIdentity
){
27413 removedItem
= this._itemsByIdentity
[rId
];
27415 removedItem
= this._arrayOfAllItems
[rId
];
27417 this._removeReferenceFromMap(removedItem
, item
, attribute
);
27420 //Everything is new (no old values) so we have to just
27421 //insert all the references, if any.
27422 for(var i
= 0; i
< newValueArray
.length
; i
++){
27423 var value
= newValueArray
[i
];
27424 if(this.isItem(value
)){
27425 this._addReferenceToMap(value
, item
, attribute
);
27430 item
[attribute
] = newValueArray
;
27434 // Now we make the dojo.data.api.Notification call
27436 this.onSet(item
, attribute
, oldValueOrValues
, newValueOrValues
);
27438 return success
; // boolean
27441 _addReferenceToMap: function(/*item*/ refItem
, /*item*/ parentItem
, /*string*/ attribute
){
27443 // Method to add an reference map entry for an item and attribute.
27445 // Method to add an reference map entry for an item and attribute. //
27447 // The item that is referenced.
27449 // The item that holds the new reference to refItem.
27451 // The attribute on parentItem that contains the new reference.
27453 var parentId
= this.getIdentity(parentItem
);
27454 var references
= refItem
[this._reverseRefMap
];
27457 references
= refItem
[this._reverseRefMap
] = {};
27459 var itemRef
= references
[parentId
];
27461 itemRef
= references
[parentId
] = {};
27463 itemRef
[attribute
] = true;
27466 _removeReferenceFromMap: function(/* item */ refItem
, /* item */ parentItem
, /*strin*/ attribute
){
27468 // Method to remove an reference map entry for an item and attribute.
27470 // Method to remove an reference map entry for an item and attribute. This will
27471 // also perform cleanup on the map such that if there are no more references at all to
27472 // the item, its reference object and entry are removed.
27475 // The item that is referenced.
27477 // The item holding a reference to refItem.
27479 // The attribute on parentItem that contains the reference.
27480 var identity
= this.getIdentity(parentItem
);
27481 var references
= refItem
[this._reverseRefMap
];
27484 for(itemId
in references
){
27485 if(itemId
== identity
){
27486 delete references
[itemId
][attribute
];
27487 if(this._isEmpty(references
[itemId
])){
27488 delete references
[itemId
];
27492 if(this._isEmpty(references
)){
27493 delete refItem
[this._reverseRefMap
];
27498 _dumpReferenceMap: function(){
27500 // Function to dump the reverse reference map of all items in the store for debug purposes.
27502 // Function to dump the reverse reference map of all items in the store for debug purposes.
27504 for(i
= 0; i
< this._arrayOfAllItems
.length
; i
++){
27505 var item
= this._arrayOfAllItems
[i
];
27506 if(item
&& item
[this._reverseRefMap
]){
27507 console
.log("Item: [" + this.getIdentity(item
) + "] is referenced by: " + dojo
.toJson(item
[this._reverseRefMap
]));
27512 _getValueOrValues: function(/* item */ item
, /* attribute-name-string */ attribute
){
27513 var valueOrValues
= undefined;
27514 if(this.hasAttribute(item
, attribute
)){
27515 var valueArray
= this.getValues(item
, attribute
);
27516 if(valueArray
.length
== 1){
27517 valueOrValues
= valueArray
[0];
27519 valueOrValues
= valueArray
;
27522 return valueOrValues
;
27525 _flatten: function(/* anything */ value
){
27526 if(this.isItem(value
)){
27528 // Given an item, return an serializable object that provides a
27529 // reference to the item.
27530 // For example, given kermit:
27531 // var kermit = store.newItem({id:2, name:"Kermit"});
27532 // we want to return
27534 var identity
= this.getIdentity(item
);
27535 var referenceObject
= {_reference
: identity
};
27536 return referenceObject
;
27538 if(typeof value
=== "object"){
27539 for(var type
in this._datatypeMap
){
27540 var typeMap
= this._datatypeMap
[type
];
27541 if(dojo
.isObject(typeMap
) && !dojo
.isFunction(typeMap
)){
27542 if(value
instanceof typeMap
.type
){
27543 if(!typeMap
.serialize
){
27544 throw new Error("ItemFileWriteStore: No serializer defined for type mapping: [" + type
+ "]");
27546 return {_type
: type
, _value
: typeMap
.serialize(value
)};
27548 } else if(value
instanceof typeMap
){
27549 //SImple mapping, therefore, return as a toString serialization.
27550 return {_type
: type
, _value
: value
.toString()};
27558 _getNewFileContentString: function(){
27560 // Generate a string that can be saved to a file.
27561 // The result should look similar to:
27562 // http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json
27563 var serializableStructure
= {};
27565 var identifierAttribute
= this._getIdentifierAttribute();
27566 if(identifierAttribute
!== Number
){
27567 serializableStructure
.identifier
= identifierAttribute
;
27569 if(this._labelAttr
){
27570 serializableStructure
.label
= this._labelAttr
;
27572 serializableStructure
.items
= [];
27573 for(var i
= 0; i
< this._arrayOfAllItems
.length
; ++i
){
27574 var item
= this._arrayOfAllItems
[i
];
27576 var serializableItem
= {};
27577 for(var key
in item
){
27578 if(key
!== this._storeRefPropName
&& key
!== this._itemNumPropName
&& key
!== this._reverseRefMap
&& key
!== this._rootItemPropName
){
27579 var attribute
= key
;
27580 var valueArray
= this.getValues(item
, attribute
);
27581 if(valueArray
.length
== 1){
27582 serializableItem
[attribute
] = this._flatten(valueArray
[0]);
27584 var serializableArray
= [];
27585 for(var j
= 0; j
< valueArray
.length
; ++j
){
27586 serializableArray
.push(this._flatten(valueArray
[j
]));
27587 serializableItem
[attribute
] = serializableArray
;
27592 serializableStructure
.items
.push(serializableItem
);
27595 var prettyPrint
= true;
27596 return dojo
.toJson(serializableStructure
, prettyPrint
);
27599 _isEmpty: function(something
){
27601 // Function to determine if an array or object has no properties or values.
27603 // The array or object to examine.
27605 if(dojo
.isObject(something
)){
27607 for(i
in something
){
27611 }else if(dojo
.isArray(something
)){
27612 if(something
.length
> 0){
27616 return empty
; //boolean
27619 save: function(/* object */ keywordArgs
){
27620 // summary: See dojo.data.api.Write.save()
27621 this._assert(!this._saveInProgress
);
27623 // this._saveInProgress is set to true, briefly, from when save is first called to when it completes
27624 this._saveInProgress
= true;
27627 var saveCompleteCallback = function(){
27634 self
._saveInProgress
= false; // must come after this._pending is cleared, but before any callbacks
27635 if(keywordArgs
&& keywordArgs
.onComplete
){
27636 var scope
= keywordArgs
.scope
|| dojo
.global
;
27637 keywordArgs
.onComplete
.call(scope
);
27640 var saveFailedCallback = function(err
){
27641 self
._saveInProgress
= false;
27642 if(keywordArgs
&& keywordArgs
.onError
){
27643 var scope
= keywordArgs
.scope
|| dojo
.global
;
27644 keywordArgs
.onError
.call(scope
, err
);
27648 if(this._saveEverything
){
27649 var newFileContentString
= this._getNewFileContentString();
27650 this._saveEverything(saveCompleteCallback
, saveFailedCallback
, newFileContentString
);
27652 if(this._saveCustom
){
27653 this._saveCustom(saveCompleteCallback
, saveFailedCallback
);
27655 if(!this._saveEverything
&& !this._saveCustom
){
27656 // Looks like there is no user-defined save-handler function.
27657 // That's fine, it just means the datastore is acting as a "mock-write"
27658 // store -- changes get saved in memory but don't get saved to disk.
27659 saveCompleteCallback();
27663 revert: function(){
27664 // summary: See dojo.data.api.Write.revert()
27665 this._assert(!this._saveInProgress
);
27668 for(identity
in this._pending
._modifiedItems
){
27669 // find the original item and the modified item that replaced it
27670 var copyOfItemState
= this._pending
._modifiedItems
[identity
];
27671 var modifiedItem
= null;
27672 if(this._itemsByIdentity
){
27673 modifiedItem
= this._itemsByIdentity
[identity
];
27675 modifiedItem
= this._arrayOfAllItems
[identity
];
27678 // Restore the original item into a full-fledged item again, we want to try to
27679 // keep the same object instance as if we don't it, causes bugs like #9022.
27680 copyOfItemState
[this._storeRefPropName
] = this;
27681 for(key
in modifiedItem
){
27682 delete modifiedItem
[key
];
27684 dojo
.mixin(modifiedItem
, copyOfItemState
);
27687 for(identity
in this._pending
._deletedItems
){
27688 deletedItem
= this._pending
._deletedItems
[identity
];
27689 deletedItem
[this._storeRefPropName
] = this;
27690 var index
= deletedItem
[this._itemNumPropName
];
27692 //Restore the reverse refererence map, if any.
27693 if(deletedItem
["backup_" + this._reverseRefMap
]){
27694 deletedItem
[this._reverseRefMap
] = deletedItem
["backup_" + this._reverseRefMap
];
27695 delete deletedItem
["backup_" + this._reverseRefMap
];
27697 this._arrayOfAllItems
[index
] = deletedItem
;
27698 if(this._itemsByIdentity
){
27699 this._itemsByIdentity
[identity
] = deletedItem
;
27701 if(deletedItem
[this._rootItemPropName
]){
27702 this._arrayOfTopLevelItems
.push(deletedItem
);
27705 //We have to pass through it again and restore the reference maps after all the
27706 //undeletes have occurred.
27707 for(identity
in this._pending
._deletedItems
){
27708 deletedItem
= this._pending
._deletedItems
[identity
];
27709 if(deletedItem
["backupRefs_" + this._reverseRefMap
]){
27710 dojo
.forEach(deletedItem
["backupRefs_" + this._reverseRefMap
], function(reference
){
27712 if(this._itemsByIdentity
){
27713 refItem
= this._itemsByIdentity
[reference
.id
];
27715 refItem
= this._arrayOfAllItems
[reference
.id
];
27717 this._addReferenceToMap(refItem
, deletedItem
, reference
.attr
);
27719 delete deletedItem
["backupRefs_" + this._reverseRefMap
];
27723 for(identity
in this._pending
._newItems
){
27724 var newItem
= this._pending
._newItems
[identity
];
27725 newItem
[this._storeRefPropName
] = null;
27726 // null out the new item, but don't change the array index so
27727 // so we can keep using _arrayOfAllItems.length.
27728 this._arrayOfAllItems
[newItem
[this._itemNumPropName
]] = null;
27729 if(newItem
[this._rootItemPropName
]){
27730 this._removeArrayElement(this._arrayOfTopLevelItems
, newItem
);
27732 if(this._itemsByIdentity
){
27733 delete this._itemsByIdentity
[identity
];
27742 return true; // boolean
27745 isDirty: function(/* item? */ item
){
27746 // summary: See dojo.data.api.Write.isDirty()
27748 // return true if the item is dirty
27749 var identity
= this.getIdentity(item
);
27750 return new Boolean(this._pending
._newItems
[identity
] ||
27751 this._pending
._modifiedItems
[identity
] ||
27752 this._pending
._deletedItems
[identity
]).valueOf(); // boolean
27754 // return true if the store is dirty -- which means return true
27755 // if there are any new items, dirty items, or modified items
27756 if(!this._isEmpty(this._pending
._newItems
) ||
27757 !this._isEmpty(this._pending
._modifiedItems
) ||
27758 !this._isEmpty(this._pending
._deletedItems
)){
27761 return false; // boolean
27765 /* dojo.data.api.Notification */
27767 onSet: function(/* item */ item
,
27768 /*attribute-name-string*/ attribute
,
27769 /*object | array*/ oldValue
,
27770 /*object | array*/ newValue
){
27771 // summary: See dojo.data.api.Notification.onSet()
27773 // No need to do anything. This method is here just so that the
27774 // client code can connect observers to it.
27777 onNew: function(/* item */ newItem
, /*object?*/ parentInfo
){
27778 // summary: See dojo.data.api.Notification.onNew()
27780 // No need to do anything. This method is here just so that the
27781 // client code can connect observers to it.
27784 onDelete: function(/* item */ deletedItem
){
27785 // summary: See dojo.data.api.Notification.onDelete()
27787 // No need to do anything. This method is here just so that the
27788 // client code can connect observers to it.
27791 close: function(/* object? */ request
){
27793 // Over-ride of base close function of ItemFileReadStore to add in check for store state.
27795 // Over-ride of base close function of ItemFileReadStore to add in check for store state.
27796 // If the store is still dirty (unsaved changes), then an error will be thrown instead of
27797 // clearing the internal state for reload from the url.
27799 //Clear if not dirty ... or throw an error
27800 if(this.clearOnClose
){
27801 if(!this.isDirty()){
27802 this.inherited(arguments
);
27804 //Only throw an error if the store was dirty and we were loading from a url (cannot reload from url until state is saved).
27805 throw new Error("dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close.");
27814 dojo
.i18n
._preloadLocalizations("dojo.nls.tt-rss-layer", ["ROOT","ar","ca","cs","da","de","de-de","el","en","en-gb","en-us","es","es-es","fi","fi-fi","fr","fr-fr","he","he-il","hu","it","it-it","ja","ja-jp","ko","ko-kr","nb","nl","nl-nl","pl","pt","pt-br","pt-pt","ru","sk","sl","sv","th","tr","xx","zh","zh-cn","zh-tw"]);