]> git.wh0rd.org - tt-rss.git/blob - lib/dojo/tt-rss-layer.js.uncompressed.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dojo / tt-rss-layer.js.uncompressed.js
1 /*
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
5 */
6
7 /*
8 This is an optimized version of Dojo, built for deployment and not for
9 development. To get sources and documentation, please visit:
10
11 http://dojotoolkit.org
12 */
13
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");
18
19 dojo.getObject("date.stamp", true, dojo);
20
21 // Methods to convert dates to or from a wire (string) format using well-known conventions
22
23 dojo.date.stamp.fromISOString = function(/*String*/formattedString, /*Number?*/defaultTime){
24 // summary:
25 // Returns a Date object given a string formatted according to a subset of the ISO-8601 standard.
26 //
27 // description:
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:
32 //
33 // * dates only
34 // | * yyyy
35 // | * yyyy-MM
36 // | * yyyy-MM-dd
37 // * times only, with an optional time zone appended
38 // | * THH:mm
39 // | * THH:mm:ss
40 // | * THH:mm:ss.SSS
41 // * and "datetimes" which could be any combination of the above
42 //
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.
48 //
49 // formattedString:
50 // A string such as 2005-06-30T08:05:00-07:00 or 2005-06-30 or T08:05:00
51 //
52 // defaultTime:
53 // Used for defaults for fields omitted in the formattedString.
54 // Uses 1970-01-01T00:00:00.0Z by default.
55
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)?)?$/;
60 }
61
62 var match = dojo.date.stamp._isoRegExp.exec(formattedString),
63 result = null;
64
65 if(match){
66 match.shift();
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
69
70 if(defaultTime){
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;
77 });
78 }
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
80 if(match[0] < 100){
81 result.setFullYear(match[0] || 1970);
82 }
83
84 var offset = 0,
85 zoneSign = match[7] && match[7].charAt(0);
86 if(zoneSign != 'Z'){
87 offset = ((match[8] || 0) * 60) + (Number(match[9]) || 0);
88 if(zoneSign != '-'){ offset *= -1; }
89 }
90 if(zoneSign){
91 offset -= result.getTimezoneOffset();
92 }
93 if(offset){
94 result.setTime(result.getTime() + offset * 60000);
95 }
96 }
97
98 return result; // Date or null
99 };
100
101 /*=====
102 dojo.date.stamp.__Options = function(){
103 // selector: String
104 // "date" or "time" for partial formatting of the Date object.
105 // Both date and time will be formatted by default.
106 // zulu: Boolean
107 // if true, UTC/GMT is used for a timezone
108 // milliseconds: Boolean
109 // if true, output milliseconds
110 this.selector = selector;
111 this.zulu = zulu;
112 this.milliseconds = milliseconds;
113 }
114 =====*/
115
116 dojo.date.stamp.toISOString = function(/*Date*/dateObject, /*dojo.date.stamp.__Options?*/options){
117 // summary:
118 // Format a Date object as a string according a subset of the ISO-8601 standard
119 //
120 // description:
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.
124 //
125 // dateObject:
126 // A Date object
127
128 var _ = function(n){ return (n < 10) ? "0" + n : n; };
129 options = options || {};
130 var formattedDate = [],
131 getter = options.zulu ? "getUTC" : "get",
132 date = "";
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('-');
136 }
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);
143 }
144 if(options.zulu){
145 time += "Z";
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);
151 }
152 formattedDate.push(time);
153 }
154 return formattedDate.join('T'); // String
155 };
156
157 }
158
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");
162
163
164
165 new Date("X"); // workaround for #11279, new Date("") == NaN
166
167 dojo.parser = new function(){
168 // summary:
169 // The Dom/Widget parsing package
170
171 var d = dojo;
172
173 function val2type(/*Object*/ value){
174 // summary:
175 // Returns name of type of given value.
176
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"; }
184 return "object";
185 }
186
187 function str2obj(/*String*/ value, /*String*/ type){
188 // summary:
189 // Convert given string value to given type
190 switch(type){
191 case "string":
192 return value;
193 case "number":
194 return value.length ? Number(value) : NaN;
195 case "boolean":
196 // for checked/disabled value might be "" or "checked". interpret as true.
197 return typeof value == "boolean" ? value : !(value.toLowerCase()=="false");
198 case "function":
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));
205 }
206 try{
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);
210 }else{
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);
214 }
215 }catch(e){ return new Function(); }
216 case "array":
217 return value ? value.split(/\s*,\s*/) : [];
218 case "date":
219 switch(value){
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);
223 }
224 case "url":
225 return d.baseUrl + value;
226 default:
227 return d.fromJson(value);
228 }
229 }
230
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"} }
234 };
235
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 = {};
242 });
243
244 function getProtoInfo(cls, params){
245 // cls: A prototype
246 // The prototype of the class to check props on
247 // params: Object
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]);
253 }
254 return params;
255 }
256
257 function getClassInfo(/*String*/ className, /*Boolean*/ skipParamsLookup){
258 // summary:
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
261 // className:
262 // fully qualified name (like "dijit.form.Button")
263 // returns:
264 // structure like
265 // {
266 // cls: dijit.Button,
267 // params: { label: "string", disabled: "boolean"}
268 // }
269
270 var c = instanceClasses[className];
271 if(!c){
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, {})
277 }
278 c = { cls: cls, params: params };
279
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, {});
286 }
287
288 return c;
289 }
290
291 this._functionFromScript = function(script, attrData){
292 // summary:
293 // Convert a <script type="dojo/method" args="a, b, c"> ... </script>
294 // into a function
295 // script: DOMNode
296 // The <script> DOMNode
297 // attrData: String
298 // For HTML5 compliance, searches for attrData + "args" (typically
299 // "data-dojo-args") instead of "args"
300 var preamble = "";
301 var suffix = "";
302 var argsStr = (script.getAttribute(attrData + "args") || script.getAttribute("args"));
303 if(argsStr){
304 d.forEach(argsStr.split(/\s*,\s*/), function(part, idx){
305 preamble += "var "+part+" = arguments["+idx+"]; ";
306 });
307 }
308 var withStr = script.getAttribute("with");
309 if(withStr && withStr.length){
310 d.forEach(withStr.split(/\s*,\s*/), function(part){
311 preamble += "with("+part+"){";
312 suffix += "}";
313 });
314 }
315 return new Function(preamble+script.innerHTML+suffix);
316 };
317
318 this.instantiate = function(/* Array */nodes, /* Object? */mixin, /* Object? */args){
319 // summary:
320 // Takes array of nodes, and turns them into class instances and
321 // potentially calls a startup method to allow them to connect with
322 // any children.
323 // nodes: Array
324 // Array of nodes or objects like
325 // | {
326 // | type: "dijit.form.Button",
327 // | node: DOMNode,
328 // | scripts: [ ... ], // array of <script type="dojo/..."> children of node
329 // | inherited: { ... } // settings inherited from ancestors like dir, theme, etc.
330 // | }
331 // mixin: Object?
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
334 // exist.
335 // args: Object?
336 // An object used to hold kwArgs for instantiation.
337 // See parse.args argument for details.
338
339 var thelist = [],
340 mixin = mixin||{};
341 args = args||{};
342
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-"
346
347 d.forEach(nodes, function(obj){
348 if(!obj){ return; }
349
350 // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc.
351 var node, type, clsInfo, clazz, scripts, fastpath;
352 if(obj.node){
353 // new format of nodes[] array, object w/lots of properties pre-computed for me
354 node = obj.node;
355 type = obj.type;
356 fastpath = obj.fastpath;
357 clsInfo = obj.clsInfo || (type && getClassInfo(type, fastpath));
358 clazz = clsInfo && clsInfo.cls;
359 scripts = obj.scripts;
360 }else{
361 // old (backwards compatible) format of nodes[] array, simple array of DOMNodes. no fastpath/data-dojo-type support here.
362 node = obj;
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));
368 }
369 if(!clsInfo){
370 throw new Error("Could not load class '" + type);
371 }
372
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.
376 var params = {};
377
378 if(args.defaults){
379 // settings for the document itself (or whatever subtree is being parsed)
380 d._mixin(params, args.defaults);
381 }
382 if(obj.inherited){
383 // settings from dir=rtl or lang=... on a node above this node
384 d._mixin(params, obj.inherited);
385 }
386
387 // mix things found in data-dojo-props into the params
388 if(fastpath){
389 var extra = node.getAttribute(attrData + "props");
390 if(extra && extra.length){
391 try{
392 extra = d.fromJson.call(args.propsThis, "{" + extra + "}");
393 d._mixin(params, extra);
394 }catch(e){
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 + "'");
397 }
398 }
399
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");
403 if(attachPoint){
404 params.dojoAttachPoint = attachPoint;
405 }
406 var attachEvent = node.getAttribute(attrData + "attach-event");
407 if(attachEvent){
408 params.dojoAttachEvent = attachEvent;
409 }
410 dojo.mixin(params, mixin);
411 }else{
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
415
416 var attributes = node.attributes;
417
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'
424 switch(name){
425 case "class":
426 value = "className" in mixin ? mixin.className : node.className;
427 break;
428 case "style":
429 value = "style" in mixin ? mixin.style : (node.style && node.style.cssText); // FIXME: Opera?
430 }
431 var _type = clsInfo.params[name];
432 if(typeof value == "string"){
433 params[name] = str2obj(value, _type);
434 }else{
435 params[name] = value;
436 }
437 }
438 }
439
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
448
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);
455 if(event){
456 if(type == "dojo/connect"){
457 connects.push({event: event, func: nf});
458 }else{
459 params[event] = nf;
460 }
461 }else{
462 calls.push(nf);
463 }
464 });
465
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);
470
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"));
474 if(jsname){
475 d.setObject(jsname, instance);
476 }
477
478 // process connections and startup functions
479 d.forEach(connects, function(connect){
480 d.connect(instance, connect.event, null, connect.func);
481 });
482 d.forEach(calls, function(func){
483 func.call(instance);
484 });
485 });
486
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
490 if(!mixin._started){
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())
501 ){
502 instance.startup();
503 }
504 });
505 }
506 return thelist;
507 };
508
509 this.parse = function(rootNode, args){
510 // summary:
511 // Scan the DOM for class instances, and instantiate them.
512 //
513 // description:
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`
518 //
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.
523 //
524 // Using `dojoType`:
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.
529 //
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.
535 //
536 // args: Object
537 // a kwArgs object passed along to instantiate()
538 //
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
552 // * scope: String
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`
560 //
561 // example:
562 // Parse all widgets on a page:
563 // | dojo.parser.parse();
564 //
565 // example:
566 // Parse all classes within the node with id="foo"
567 // | dojo.parser.parse(dojo.byId('foo'));
568 //
569 // example:
570 // Parse all classes in a page, but do not call .startup() on any
571 // child
572 // | dojo.parser.parse({ noStart: true })
573 //
574 // example:
575 // Parse all classes in a node, but do not call .startup()
576 // | dojo.parser.parse(someNode, { noStart:true });
577 // | // or
578 // | dojo.parser.parse({ noStart:true, rootNode: someNode });
579
580 // determine the root node based on the passed arguments.
581 var root;
582 if(!args && rootNode && rootNode.rootNode){
583 args = rootNode;
584 root = args.rootNode;
585 }else{
586 root = rootNode;
587 }
588 root = root ? dojo.byId(root) : dojo.body();
589 args = args || {};
590
591 var attrName = (args.scope || d._scopeName) + "Type", // typically "dojoType"
592 attrData = "data-" + (args.scope || d._scopeName) + "-"; // typically "data-dojo-"
593
594 function scan(parent, list){
595 // summary:
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[].
599 // parent: Object
600 // Object representing the parent node, like
601 // | {
602 // | node: DomNode, // scan children of this node
603 // | inherited: {dir: "rtl"}, // dir/lang setting inherited from above node
604 // |
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, ...}
608 // | }
609 // list: DomNode[]
610 // Output array of objects (same format as parent) representing nodes to be turned into widgets
611
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);
617 if(val){
618 inherited[name] = val;
619 }
620 });
621
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;
624
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);
627
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");
633 if(html5){
634 type = html5;
635 }else{
636 // fallback to backward compatible mode, using dojoType. remove in 2.0
637 type = recurse && child.getAttribute(attrName);
638 }
639
640 var fastpath = html5 == type;
641
642 if(type){
643 // if dojoType/data-dojo-type specified, add to output array of nodes to instantiate
644 var params = {
645 "type": type,
646 fastpath: fastpath,
647 clsInfo: getClassInfo(type, fastpath), // note: won't find classes declared via dojo.Declaration
648 node: child,
649 scripts: [], // <script> nodes that are parent's children
650 inherited: inherited // dir & lang attributes inherited from parent
651 };
652 list.push(params);
653
654 // Recurse, collecting <script type="dojo/..."> children, and also looking for
655 // descendant nodes with dojoType specified (unless the widget has the stopParser flag),
656 scan(params, list);
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)) {
661 scripts.push(child);
662 }
663 }else if(recurse){
664 // Recurse, looking for grandchild nodes with dojoType specified
665 scan({
666 node: child,
667 inherited: inherited
668 }, list);
669 }
670 }
671 }
672 }
673
674 // Ignore bogus entries in inherited hash like {dir: ""}
675 var inherited = {};
676 if(args && args.inherited){
677 for(var key in args.inherited){
678 if(args.inherited[key]){ inherited[key] = args.inherited[key]; }
679 }
680 }
681
682 // Make list of all nodes on page w/dojoType specified
683 var list = [];
684 scan({
685 node: root,
686 inherited: inherited
687 }, list);
688
689 // go build the object instances
690 var mixin = args && args.template ? {template: true} : null;
691 return this.instantiate(list, mixin, args); // Array
692 };
693 }();
694
695 //Register the parser callback. It should be the first callback
696 //after the a11y test.
697
698 (function(){
699 var parseRunner = function(){
700 if(dojo.config.parseOnLoad){
701 dojo.parser.parse();
702 }
703 };
704
705 // FIXME: need to clobber cross-dependency!!
706 if(dojo.getObject("dijit.wai.onload") === dojo._loaders[0]){
707 dojo._loaders.splice(1, 0, parseRunner);
708 }else{
709 dojo._loaders.unshift(parseRunner);
710 }
711 })();
712
713 }
714
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");
718
719 dojo.getObject("window", true, dojo);
720
721 dojo.window.getBox = function(){
722 // summary:
723 // Returns the dimensions and scroll position of the viewable area of a browser window
724
725 var scrollRoot = (dojo.doc.compatMode == 'BackCompat') ? dojo.body() : dojo.doc.documentElement;
726
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 };
730 };
731
732 dojo.window.get = function(doc){
733 // summary:
734 // Get window object associated with document doc
735
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){
741 /*
742 In IE 6, only the variable "window" can be used to connect events (others
743 may be only copies).
744 */
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
751 }
752
753 return doc.parentWindow || doc.defaultView; // Window
754 };
755
756 dojo.window.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
757 // summary:
758 // Scroll the passed node into view, if it is not already.
759
760 // don't rely on node.scrollIntoView working just because the function is there
761
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
771 return;
772 }
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"));
785 };
786 if(isFixed(node)){ return; } // nothing to do
787
788 while(el){
789 if(el == body){ el = scrollRoot; }
790 var elPos = dojo.position(el),
791 fixedPos = isFixed(el);
792
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; }
798 }else{
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;
806 }
807 clientSize = el.clientHeight;
808 scrollBarSize = elPos.h - clientSize;
809 if(clientSize > 0 && scrollBarSize > 0){
810 elPos.h = clientSize;
811 }
812 }
813 if(fixedPos){ // bounded by viewport, not parents
814 if(elPos.y < 0){
815 elPos.h += elPos.y; elPos.y = 0;
816 }
817 if(elPos.x < 0){
818 elPos.w += elPos.x; elPos.x = 0;
819 }
820 if(elPos.y + elPos.h > rootHeight){
821 elPos.h = rootHeight - elPos.y;
822 }
823 if(elPos.x + elPos.w > rootWidth){
824 elPos.w = rootWidth - elPos.x;
825 }
826 }
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
832 if(r * l > 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;
836 el.scrollLeft += s;
837 nodePos.x -= el.scrollLeft;
838 }
839 if(bot * t > 0){
840 nodePos.y += el.scrollTop;
841 el.scrollTop += Math[t < 0? "max" : "min"](t, bot);
842 nodePos.y -= el.scrollTop;
843 }
844 el = (el != scrollRoot) && !fixedPos && el.parentNode;
845 }
846 }catch(error){
847 console.error('scrollIntoView: ' + error);
848 node.scrollIntoView(false);
849 }
850 };
851
852 }
853
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");
857
858
859 dojo.declare("dijit.WidgetSet", null, {
860 // summary:
861 // A set of widgets indexed by id. A default instance of this class is
862 // available as `dijit.registry`
863 //
864 // example:
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(); });
871 //
872 // example:
873 // Using dijit.registry:
874 // | dijit.registry.forEach(function(w){ /* do something */ });
875
876 constructor: function(){
877 this._hash = {};
878 this.length = 0;
879 },
880
881 add: function(/*dijit._Widget*/ widget){
882 // summary:
883 // Add a widget to this list. If a duplicate ID is detected, a error is thrown.
884 //
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");
889 }
890 this._hash[widget.id] = widget;
891 this.length++;
892 },
893
894 remove: function(/*String*/ id){
895 // summary:
896 // Remove a widget from this WidgetSet. Does not destroy the widget; simply
897 // removes the reference.
898 if(this._hash[id]){
899 delete this._hash[id];
900 this.length--;
901 }
902 },
903
904 forEach: function(/*Function*/ func, /* Object? */thisObj){
905 // summary:
906 // Call specified function for each widget in this set.
907 //
908 // func:
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`.
911 //
912 // thisObj:
913 // An optional scope parameter
914 //
915 // example:
916 // Using the default `dijit.registry` instance:
917 // | dijit.registry.forEach(function(widget){
918 // | console.log(widget.declaredClass);
919 // | });
920 //
921 // returns:
922 // Returns self, in order to allow for further chaining.
923
924 thisObj = thisObj || dojo.global;
925 var i = 0, id;
926 for(id in this._hash){
927 func.call(thisObj, this._hash[id], i++, this._hash);
928 }
929 return this; // dijit.WidgetSet
930 },
931
932 filter: function(/*Function*/ filter, /* Object? */thisObj){
933 // summary:
934 // Filter down this WidgetSet to a smaller new WidgetSet
935 // Works the same as `dojo.filter` and `dojo.NodeList.filter`
936 //
937 // filter:
938 // Callback function to test truthiness. Is passed the widget
939 // reference and the pseudo-index in the object.
940 //
941 // thisObj: Object?
942 // Option scope to use for the filter function.
943 //
944 // example:
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 */ });
949
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)){
955 res.add(w);
956 }
957 }
958 return res; // dijit.WidgetSet
959 },
960
961 byId: function(/*String*/ id){
962 // summary:
963 // Find a widget in this list by it's id.
964 // example:
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
970
971 return this._hash[id]; // dijit._Widget
972 },
973
974 byClass: function(/*String*/ cls){
975 // summary:
976 // Reduce this widgetset to a new WidgetSet of a particular `declaredClass`
977 //
978 // cls: String
979 // The Class to scan for. Full dot-notated string.
980 //
981 // example:
982 // Find all `dijit.TitlePane`s in a page:
983 // | dijit.registry.byClass("dijit.TitlePane").forEach(function(tp){ tp.close(); });
984
985 var res = new dijit.WidgetSet(), id, widget;
986 for(id in this._hash){
987 widget = this._hash[id];
988 if(widget.declaredClass == cls){
989 res.add(widget);
990 }
991 }
992 return res; // dijit.WidgetSet
993 },
994
995 toArray: function(){
996 // summary:
997 // Convert this WidgetSet into a true Array
998 //
999 // example:
1000 // Work with the widget .domNodes in a real Array
1001 // | dojo.map(dijit.registry.toArray(), function(w){ return w.domNode; });
1002
1003 var ar = [];
1004 for(var id in this._hash){
1005 ar.push(this._hash[id]);
1006 }
1007 return ar; // dijit._Widget[]
1008 },
1009
1010 map: function(/* Function */func, /* Object? */thisObj){
1011 // summary:
1012 // Create a new Array from this WidgetSet, following the same rules as `dojo.map`
1013 // example:
1014 // | var nodes = dijit.registry.map(function(w){ return w.domNode; });
1015 //
1016 // returns:
1017 // A new array of the returned values.
1018 return dojo.map(this.toArray(), func, thisObj); // Array
1019 },
1020
1021 every: function(func, thisObj){
1022 // summary:
1023 // A synthetic clone of `dojo.every` acting explicitly on this WidgetSet
1024 //
1025 // func: Function
1026 // A callback function run for every widget in this list. Exits loop
1027 // when the first false return is encountered.
1028 //
1029 // thisObj: Object?
1030 // Optional scope parameter to use for the callback
1031
1032 thisObj = thisObj || dojo.global;
1033 var x = 0, i;
1034 for(i in this._hash){
1035 if(!func.call(thisObj, this._hash[i], x++, this._hash)){
1036 return false; // Boolean
1037 }
1038 }
1039 return true; // Boolean
1040 },
1041
1042 some: function(func, thisObj){
1043 // summary:
1044 // A synthetic clone of `dojo.some` acting explictly on this WidgetSet
1045 //
1046 // func: Function
1047 // A callback function run for every widget in this list. Exits loop
1048 // when the first true return is encountered.
1049 //
1050 // thisObj: Object?
1051 // Optional scope parameter to use for the callback
1052
1053 thisObj = thisObj || dojo.global;
1054 var x = 0, i;
1055 for(i in this._hash){
1056 if(func.call(thisObj, this._hash[i], x++, this._hash)){
1057 return true; // Boolean
1058 }
1059 }
1060 return false; // Boolean
1061 }
1062
1063 });
1064
1065 (function(){
1066
1067 /*=====
1068 dijit.registry = {
1069 // summary:
1070 // A list of widgets on a page.
1071 // description:
1072 // Is an instance of `dijit.WidgetSet`
1073 };
1074 =====*/
1075 dijit.registry = new dijit.WidgetSet();
1076
1077 var hash = dijit.registry._hash,
1078 attr = dojo.attr,
1079 hasAttr = dojo.hasAttr,
1080 style = dojo.style;
1081
1082 dijit.byId = function(/*String|dijit._Widget*/ id){
1083 // summary:
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
1086 };
1087
1088 var _widgetTypeCtr = {};
1089 dijit.getUniqueId = function(/*String*/widgetType){
1090 // summary:
1091 // Generates a unique id for a given widgetType
1092
1093 var id;
1094 do{
1095 id = widgetType + "_" +
1096 (widgetType in _widgetTypeCtr ?
1097 ++_widgetTypeCtr[widgetType] : _widgetTypeCtr[widgetType] = 0);
1098 }while(hash[id]);
1099 return dijit._scopeName == "dijit" ? id : dijit._scopeName + "_" + id; // String
1100 };
1101
1102 dijit.findWidgets = function(/*DomNode*/ root){
1103 // summary:
1104 // Search subtree under root returning widgets found.
1105 // Doesn't search for nested widgets (ie, widgets inside other widgets).
1106
1107 var outAry = [];
1108
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");
1113 if(widgetId){
1114 var widget = hash[widgetId];
1115 if(widget){ // may be null on page w/multiple dojo's loaded
1116 outAry.push(widget);
1117 }
1118 }else{
1119 getChildrenHelper(node);
1120 }
1121 }
1122 }
1123 }
1124
1125 getChildrenHelper(root);
1126 return outAry;
1127 };
1128
1129 dijit._destroyAll = function(){
1130 // summary:
1131 // Code to destroy all widgets and do other cleanup on page unload
1132
1133 // Clean up focus manager lingering references to widgets and nodes
1134 dijit._curFocus = null;
1135 dijit._prevFocus = null;
1136 dijit._activeStack = [];
1137
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){
1146 widget.destroy();
1147 }
1148 }
1149 });
1150 };
1151
1152 if(dojo.isIE){
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();
1157 });
1158 }
1159
1160 dijit.byNode = function(/*DOMNode*/ node){
1161 // summary:
1162 // Returns the widget corresponding to the given DOMNode
1163 return hash[node.getAttribute("widgetId")]; // dijit._Widget
1164 };
1165
1166 dijit.getEnclosingWidget = function(/*DOMNode*/ node){
1167 // summary:
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
1170 while(node){
1171 var id = node.getAttribute && node.getAttribute("widgetId");
1172 if(id){
1173 return hash[id];
1174 }
1175 node = node.parentNode;
1176 }
1177 return null;
1178 };
1179
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");
1186 });
1187
1188 dijit.hasDefaultTabStop = function(/*Element*/ elem){
1189 // summary:
1190 // Tests if element is tab-navigable even without an explicit tabIndex setting
1191
1192 // No explicit tabIndex setting, need to investigate node type
1193 switch(elem.nodeName.toLowerCase()){
1194 case "a":
1195 // An <a> w/out a tabindex is only navigable if it has an href
1196 return hasAttr(elem, "href");
1197 case "area":
1198 case "button":
1199 case "input":
1200 case "object":
1201 case "select":
1202 case "textarea":
1203 // These are navigable by default
1204 return true;
1205 case "iframe":
1206 // If it's an editor <iframe> then it's tab navigable.
1207 var body;
1208 try{
1209 // non-IE
1210 var contentDocument = elem.contentDocument;
1211 if("designMode" in contentDocument && contentDocument.designMode == "on"){
1212 return true;
1213 }
1214 body = contentDocument.body;
1215 }catch(e1){
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
1219 try{
1220 body = elem.contentWindow.document.body;
1221 }catch(e2){
1222 return false;
1223 }
1224 }
1225 return body.contentEditable == 'true' || (body.firstChild && body.firstChild.contentEditable == 'true');
1226 default:
1227 return elem.contentEditable == 'true';
1228 }
1229 };
1230
1231 var isTabNavigable = (dijit.isTabNavigable = function(/*Element*/ elem){
1232 // summary:
1233 // Tests if an element is tab-navigable
1234
1235 // TODO: convert (and rename method) to return effective tabIndex; will save time in _getTabNavigable()
1236 if(attr(elem, "disabled")){
1237 return false;
1238 }else if(hasAttr(elem, "tabIndex")){
1239 // Explicit tab index setting
1240 return attr(elem, "tabIndex") >= 0; // boolean
1241 }else{
1242 // No explicit tabIndex setting, so depends on node type
1243 return dijit.hasDefaultTabStop(elem);
1244 }
1245 });
1246
1247 dijit._getTabNavigable = function(/*DOMNode*/ root){
1248 // summary:
1249 // Finds descendants of the specified root node.
1250 //
1251 // description:
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();
1267 }
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)){
1273 return;
1274 }
1275
1276 if(isTabNavigable(child)){
1277 var tabindex = attr(child, "tabIndex");
1278 if(!hasAttr(child, "tabIndex") || tabindex == 0){
1279 if(!first){ first = child; }
1280 last = child;
1281 }else if(tabindex > 0){
1282 if(!lowest || tabindex < lowestTabindex){
1283 lowestTabindex = tabindex;
1284 lowest = child;
1285 }
1286 if(!highest || tabindex >= highestTabindex){
1287 highestTabindex = tabindex;
1288 highest = child;
1289 }
1290 }
1291 var rn = radioName(child);
1292 if(dojo.attr(child, "checked") && rn) {
1293 radioSelected[rn] = child;
1294 }
1295 }
1296 if(child.nodeName.toUpperCase() != 'SELECT'){
1297 walkTree(child);
1298 }
1299 });
1300 };
1301 if(shown(root)){ walkTree(root) }
1302 function rs(node) {
1303 // substitute checked radio button for unchecked one, if there is a checked one with the same name.
1304 return radioSelected[radioName(node)] || node;
1305 }
1306 return { first: rs(first), last: rs(last), lowest: rs(lowest), highest: rs(highest) };
1307 }
1308 dijit.getFirstInTabbingOrder = function(/*String|DOMNode*/ root){
1309 // summary:
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
1314 };
1315
1316 dijit.getLastInTabbingOrder = function(/*String|DOMNode*/ root){
1317 // summary:
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
1322 };
1323
1324 /*=====
1325 dojo.mixin(dijit, {
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
1332 });
1333 =====*/
1334
1335 dijit.defaultDuration = dojo.config["defaultDuration"] || 200;
1336
1337 })();
1338
1339 }
1340
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");
1344
1345
1346
1347
1348 // summary:
1349 // These functions are used to query or set the focus and selection.
1350 //
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.
1360
1361 dojo.mixin(dijit, {
1362 // _curFocus: DomNode
1363 // Currently focused item on screen
1364 _curFocus: null,
1365
1366 // _prevFocus: DomNode
1367 // Previously focused item on screen
1368 _prevFocus: null,
1369
1370 isCollapsed: function(){
1371 // summary:
1372 // Returns true if there is no text selected
1373 return dijit.getBookmark().isCollapsed;
1374 },
1375
1376 getBookmark: function(){
1377 // summary:
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;
1380
1381 if(dojo.global.getSelection){
1382 //W3C Range API for selections.
1383 sel = dojo.global.getSelection();
1384 if(sel){
1385 if(sel.isCollapsed){
1386 tg = cf? cf.tagName : "";
1387 if(tg){
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"))){
1392 sel = {
1393 start: cf.selectionStart,
1394 end: cf.selectionEnd,
1395 node: cf,
1396 pRange: true
1397 };
1398 return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object.
1399 }
1400 }
1401 bm = {isCollapsed:true};
1402 if(sel.rangeCount){
1403 bm.mark = sel.getRangeAt(0).cloneRange();
1404 }
1405 }else{
1406 rg = sel.getRangeAt(0);
1407 bm = {isCollapsed: false, mark: rg.cloneRange()};
1408 }
1409 }
1410 }else if(sel){
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"){
1418 return {
1419 isCollapsed: true,
1420 mark: null
1421 }
1422 }else{
1423 rg = sel.createRange();
1424 return {
1425 isCollapsed: rg.text && rg.text.length?false:true,
1426 mark: {
1427 range: rg,
1428 pRange: true
1429 }
1430 };
1431 }
1432 }
1433 bm = {};
1434
1435 //'IE' way for selections.
1436 try{
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);
1441 }catch(e){
1442 bm.isCollapsed = true;
1443 return bm;
1444 }
1445 if(sel.type.toUpperCase() == 'CONTROL'){
1446 if(rg.length){
1447 bm.mark=[];
1448 var i=0,len=rg.length;
1449 while(i<len){
1450 bm.mark.push(rg.item(i++));
1451 }
1452 }else{
1453 bm.isCollapsed = true;
1454 bm.mark = null;
1455 }
1456 }else{
1457 bm.mark = rg.getBookmark();
1458 }
1459 }else{
1460 console.warn("No idea how to store the current selection for this browser!");
1461 }
1462 return bm; // Object
1463 },
1464
1465 moveToBookmark: function(/*Object*/bookmark){
1466 // summary:
1467 // Moves current selection to a bookmark
1468 // bookmark:
1469 // This should be a returned object from dijit.getBookmark()
1470
1471 var _doc = dojo.doc,
1472 mark = bookmark.mark;
1473 if(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){
1478 if(mark.pRange){
1479 var r = mark;
1480 var n = r.node;
1481 n.selectionStart = r.start;
1482 n.selectionEnd = r.end;
1483 }else{
1484 sel.removeAllRanges();
1485 sel.addRange(mark);
1486 }
1487 }else{
1488 console.warn("No idea how to restore selection for this browser!");
1489 }
1490 }else if(_doc.selection && mark){
1491 //'IE' way.
1492 var rg;
1493 if(mark.pRange){
1494 rg = mark.range;
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){
1500 rg.addElement(n);
1501 });
1502 }else{
1503 rg = _doc.body.createTextRange();
1504 rg.moveToBookmark(mark);
1505 }
1506 rg.select();
1507 }
1508 }
1509 },
1510
1511 getFocus: function(/*Widget?*/ menu, /*Window?*/ openedForWindow){
1512 // summary:
1513 // Called as getFocus(), this returns an Object showing the current focus
1514 // and selected text.
1515 //
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.
1521 //
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.
1526 //
1527 // openedForWindow:
1528 // iframe in which menu was opened
1529 //
1530 // returns:
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;
1533 return {
1534 node: node,
1535 bookmark: (node == dijit._curFocus) && dojo.withGlobal(openedForWindow || dojo.global, dijit.getBookmark),
1536 openedForWindow: openedForWindow
1537 }; // Object
1538 },
1539
1540 focus: function(/*Object || DomNode */ handle){
1541 // summary:
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.
1544 // handle:
1545 // object returned by get(), or a DomNode
1546
1547 if(!handle){ return; }
1548
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;
1553
1554 // Set the focus
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
1557 if(node){
1558 var focusNode = (node.tagName.toLowerCase() == "iframe") ? node.contentWindow : node;
1559 if(focusNode && focusNode.focus){
1560 try{
1561 // Gecko throws sometimes if setting focus is impossible,
1562 // node not displayed or something like that
1563 focusNode.focus();
1564 }catch(e){/*quiet*/}
1565 }
1566 dijit._onFocusNode(node);
1567 }
1568
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();
1576 }
1577 try{
1578 dojo.withGlobal(openedForWindow || dojo.global, dijit.moveToBookmark, null, [bookmark]);
1579 }catch(e2){
1580 /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */
1581 }
1582 }
1583 },
1584
1585 // _activeStack: dijit._Widget[]
1586 // List of currently active widgets (focused widget and it's ancestors)
1587 _activeStack: [],
1588
1589 registerIframe: function(/*DomNode*/ iframe){
1590 // summary:
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.
1594 // description:
1595 // Currently only used by editor.
1596 // returns:
1597 // Handle to pass to unregisterIframe()
1598 return dijit.registerWin(iframe.contentWindow, iframe);
1599 },
1600
1601 unregisterIframe: function(/*Object*/ handle){
1602 // summary:
1603 // Unregisters listeners on the specified iframe created by registerIframe.
1604 // After calling be sure to delete or null out the handle itself.
1605 // handle:
1606 // Handle returned by registerIframe()
1607
1608 dijit.unregisterWin(handle);
1609 },
1610
1611 registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){
1612 // summary:
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.
1616 // description:
1617 // Users should call registerIframe() instead of this method.
1618 // targetWindow:
1619 // If specified this is the window associated with the iframe,
1620 // i.e. iframe.contentWindow.
1621 // effectiveNode:
1622 // If specified, report any focus events inside targetWindow as
1623 // an event on effectiveNode, rather than on evt.target.
1624 // returns:
1625 // Handle to pass to unregisterWin()
1626
1627 // TODO: make this function private in 2.0; Editor/users should call registerIframe(),
1628
1629 var mousedownListener = function(evt){
1630 dijit._justMouseDowned = true;
1631 setTimeout(function(){ dijit._justMouseDowned = false; }, 0);
1632
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){
1636 return;
1637 }
1638
1639 dijit._onTouchNode(effectiveNode || evt.target || evt.srcElement, "mouse");
1640 };
1641 //dojo.connect(targetWindow, "onscroll", ???);
1642
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
1646 // fire.
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;
1650 if(doc){
1651 if(dojo.isIE){
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);
1659 }else{
1660 dijit._onTouchNode(effectiveNode || evt.srcElement);
1661 }
1662 };
1663 doc.attachEvent('onactivate', activateListener);
1664 var deactivateListener = function(evt){
1665 dijit._onBlurNode(effectiveNode || evt.srcElement);
1666 };
1667 doc.attachEvent('ondeactivate', deactivateListener);
1668
1669 return function(){
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)
1674 };
1675 }else{
1676 doc.body.addEventListener('mousedown', mousedownListener, true);
1677 var focusListener = function(evt){
1678 dijit._onFocusNode(effectiveNode || evt.target);
1679 };
1680 doc.addEventListener('focus', focusListener, true);
1681 var blurListener = function(evt){
1682 dijit._onBlurNode(effectiveNode || evt.target);
1683 };
1684 doc.addEventListener('blur', blurListener, true);
1685
1686 return function(){
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)
1691 };
1692 }
1693 }
1694 },
1695
1696 unregisterWin: function(/*Handle*/ handle){
1697 // summary:
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.
1701
1702 // Currently our handle is actually a function
1703 handle && handle();
1704 },
1705
1706 _onBlurNode: function(/*DomNode*/ node){
1707 // summary:
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;
1714
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.
1718 return;
1719 }
1720
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);
1724 }
1725 dijit._clearActiveWidgetsTimer = setTimeout(function(){
1726 delete dijit._clearActiveWidgetsTimer;
1727 dijit._setStack([]);
1728 dijit._prevFocus = null;
1729 }, 100);
1730 },
1731
1732 _onTouchNode: function(/*DomNode*/ node, /*String*/ by){
1733 // summary:
1734 // Callback when node is focused or mouse-downed
1735 // node:
1736 // The node that was touched.
1737 // by:
1738 // "mouse" if the focus/touch was caused by a mouse down event
1739
1740 // ignore the recent blurNode event
1741 if(dijit._clearActiveWidgetsTimer){
1742 clearTimeout(dijit._clearActiveWidgetsTimer);
1743 delete dijit._clearActiveWidgetsTimer;
1744 }
1745
1746 // compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem)
1747 var newStack=[];
1748 try{
1749 while(node){
1750 var popupParent = dojo.attr(node, "dijitPopupParent");
1751 if(popupParent){
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
1757 break;
1758 }
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;
1762 }else{
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);
1770 }
1771 node=node.parentNode;
1772 }
1773 }
1774 }catch(e){ /* squelch */ }
1775
1776 dijit._setStack(newStack, by);
1777 },
1778
1779 _onFocusNode: function(/*DomNode*/ node){
1780 // summary:
1781 // Callback when node is focused
1782
1783 if(!node){
1784 return;
1785 }
1786
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)
1791 return;
1792 }
1793
1794 dijit._onTouchNode(node);
1795
1796 if(node == dijit._curFocus){ return; }
1797 if(dijit._curFocus){
1798 dijit._prevFocus = dijit._curFocus;
1799 }
1800 dijit._curFocus = node;
1801 dojo.publish("focusNode", [node]);
1802 },
1803
1804 _setStack: function(/*String[]*/ newStack, /*String*/ by){
1805 // summary:
1806 // The stack of active widgets has changed. Send out appropriate events and records new stack.
1807 // newStack:
1808 // array of widget id's, starting from the top (outermost) widget
1809 // by:
1810 // "mouse" if the focus/touch was caused by a mouse down event
1811
1812 var oldStack = dijit._activeStack;
1813 dijit._activeStack = newStack;
1814
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]){
1818 break;
1819 }
1820 }
1821
1822 var widget;
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]);
1826 if(widget){
1827 widget._focused = false;
1828 widget.set("focused", false);
1829 widget._hasBeenBlurred = true;
1830 if(widget._onBlur){
1831 widget._onBlur(by);
1832 }
1833 dojo.publish("widgetBlur", [widget, by]);
1834 }
1835 }
1836
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]);
1840 if(widget){
1841 widget._focused = true;
1842 widget.set("focused", true);
1843 if(widget._onFocus){
1844 widget._onFocus(by);
1845 }
1846 dojo.publish("widgetFocus", [widget, by]);
1847 }
1848 }
1849 }
1850 });
1851
1852 // register top window and all the iframes it contains
1853 dojo.addOnLoad(function(){
1854 var handle = dijit.registerWin(window);
1855 if(dojo.isIE){
1856 dojo.addOnWindowUnload(function(){
1857 dijit.unregisterWin(handle);
1858 handle = null;
1859 })
1860 }
1861 });
1862
1863 }
1864
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");
1868
1869
1870 dojo.AdapterRegistry = function(/*Boolean?*/ returnWrappers){
1871 // summary:
1872 // A registry to make contextual calling/searching easier.
1873 // description:
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.
1878 // example:
1879 // | // create a new registry
1880 // | var reg = new dojo.AdapterRegistry();
1881 // | reg.register("handleString",
1882 // | dojo.isString,
1883 // | function(str){
1884 // | // do something with the string here
1885 // | }
1886 // | );
1887 // | reg.register("handleArr",
1888 // | dojo.isArray,
1889 // | function(arr){
1890 // | // do something with the array here
1891 // | }
1892 // | );
1893 // |
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
1898
1899 this.pairs = [];
1900 this.returnWrappers = returnWrappers || false; // Boolean
1901 };
1902
1903 dojo.extend(dojo.AdapterRegistry, {
1904 register: function(/*String*/ name, /*Function*/ check, /*Function*/ wrap, /*Boolean?*/ directReturn, /*Boolean?*/ override){
1905 // summary:
1906 // register a check function to determine if the wrap function or
1907 // object gets selected
1908 // name:
1909 // a way to identify this matcher.
1910 // check:
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.
1914 // directReturn:
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.
1921 // override:
1922 // If override is given and true, the check function will be given
1923 // highest priority. Otherwise, it will be the lowest priority
1924 // adapter.
1925 this.pairs[((override) ? "unshift" : "push")]([name, check, wrap, directReturn]);
1926 },
1927
1928 match: function(/* ... */){
1929 // summary:
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)){
1938 return pair[2];
1939 }else{
1940 return pair[2].apply(this, arguments);
1941 }
1942 }
1943 }
1944 throw new Error("No match found");
1945 },
1946
1947 unregister: function(name){
1948 // summary: Remove a named adapter from the registry
1949
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);
1957 return true;
1958 }
1959 }
1960 return false;
1961 }
1962 });
1963
1964 }
1965
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");
1969
1970
1971
1972
1973 dijit.getViewport = function(){
1974 // summary:
1975 // Returns the dimensions and scroll position of the viewable area of a browser window
1976
1977 return dojo.window.getBox();
1978 };
1979
1980 /*=====
1981 dijit.__Position = function(){
1982 // x: Integer
1983 // horizontal coordinate in pixels, relative to document body
1984 // y: Integer
1985 // vertical coordinate in pixels, relative to document body
1986
1987 thix.x = x;
1988 this.y = y;
1989 }
1990 =====*/
1991
1992
1993 dijit.placeOnScreen = function(
1994 /* DomNode */ node,
1995 /* dijit.__Position */ pos,
1996 /* String[] */ corners,
1997 /* dijit.__Position? */ padding){
1998 // summary:
1999 // Positions one of the node's corners at specified position
2000 // such that node is fully visible in viewport.
2001 // description:
2002 // NOTE: node is assumed to be absolutely or relatively positioned.
2003 // pos:
2004 // Object like {x: 10, y: 20}
2005 // corners:
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
2012 // padding:
2013 // set padding to put some buffer around the element you want to position.
2014 // example:
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"])
2019
2020 var choices = dojo.map(corners, function(corner){
2021 var c = { corner: corner, pos: {x:pos.x,y:pos.y} };
2022 if(padding){
2023 c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x;
2024 c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y;
2025 }
2026 return c;
2027 });
2028
2029 return dijit._place(node, choices);
2030 }
2031
2032 dijit._place = function(/*DomNode*/ node, choices, layoutNode, /*Object*/ aroundNodeCoords){
2033 // summary:
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
2036 // choices: Array
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}
2048
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();
2052
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);
2058 }
2059
2060 var best = null;
2061 dojo.some(choices, function(choice){
2062 var corner = choice.corner;
2063 var pos = choice.pos;
2064 var overflow = 0;
2065
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
2070 };
2071
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)
2075 if(layoutNode){
2076 var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords);
2077 overflow = typeof res == "undefined" ? 0 : res;
2078 }
2079
2080 // get node's size
2081 var style = node.style;
2082 var oldDisplay = style.display;
2083 var oldVis = style.visibility;
2084 style.visibility = "hidden";
2085 style.display = "";
2086 var mb = dojo.marginBox(node);
2087 style.display = oldDisplay;
2088 style.visibility = oldVis;
2089
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;
2098
2099 overflow += (mb.w - width) + (mb.h - height);
2100
2101 if(best == null || overflow < best.overflow){
2102 best = {
2103 corner: corner,
2104 aroundCorner: choice.aroundCorner,
2105 x: startX,
2106 y: startY,
2107 w: width,
2108 h: height,
2109 overflow: overflow,
2110 spaceAvailable: spaceAvailable
2111 };
2112 }
2113
2114 return !overflow;
2115 });
2116
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);
2121 }
2122
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).
2127 //
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(),
2131 s = node.style;
2132 s.top = best.y + "px";
2133 s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px";
2134
2135 return best;
2136 }
2137
2138 dijit.placeOnScreenAroundNode = function(
2139 /* DomNode */ node,
2140 /* DomNode */ aroundNode,
2141 /* Object */ aroundCorners,
2142 /* Function? */ layoutNode){
2143
2144 // summary:
2145 // Position node adjacent or kitty-corner to aroundNode
2146 // such that it's fully visible in viewport.
2147 //
2148 // description:
2149 // Place node such that corner of node touches a corner of
2150 // aroundNode, and that node is fully visible.
2151 //
2152 // aroundCorners:
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:
2157 //
2158 // | { aroundNodeCorner1: nodeCorner1, aroundNodeCorner2: nodeCorner2, ...}
2159 //
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
2165 //
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.
2169 //
2170 // example:
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)
2177 //
2178
2179 // get coordinates of aroundNode
2180 aroundNode = dojo.byId(aroundNode);
2181 var aroundNodePos = dojo.position(aroundNode, true);
2182
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);
2187 };
2188
2189 /*=====
2190 dijit.__Rectangle = function(){
2191 // x: Integer
2192 // horizontal offset in pixels, relative to document body
2193 // y: Integer
2194 // vertical offset in pixels, relative to document body
2195 // width: Integer
2196 // width in pixels
2197 // height: Integer
2198 // height in pixels
2199
2200 this.x = x;
2201 this.y = y;
2202 this.width = width;
2203 this.height = height;
2204 }
2205 =====*/
2206
2207
2208 dijit.placeOnScreenAroundRectangle = function(
2209 /* DomNode */ node,
2210 /* dijit.__Rectangle */ aroundRect,
2211 /* Object */ aroundCorners,
2212 /* Function */ layoutNode){
2213
2214 // summary:
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.
2218
2219 return dijit._placeOnScreenAroundRect(node,
2220 aroundRect.x, aroundRect.y, aroundRect.width, aroundRect.height, // rectangle
2221 aroundCorners, layoutNode);
2222 };
2223
2224 dijit._placeOnScreenAroundRect = function(
2225 /* DomNode */ node,
2226 /* Number */ x,
2227 /* Number */ y,
2228 /* Number */ width,
2229 /* Number */ height,
2230 /* Object */ aroundCorners,
2231 /* Function */ layoutNode){
2232
2233 // summary:
2234 // Like dijit.placeOnScreenAroundNode(), except it accepts coordinates
2235 // of a rectangle to place node adjacent to.
2236
2237 // TODO: combine with placeOnScreenAroundRectangle()
2238
2239 // Generate list of possible positions for node
2240 var choices = [];
2241 for(var nodeCorner in aroundCorners){
2242 choices.push( {
2243 aroundCorner: nodeCorner,
2244 corner: aroundCorners[nodeCorner],
2245 pos: {
2246 x: x + (nodeCorner.charAt(1) == 'L' ? 0 : width),
2247 y: y + (nodeCorner.charAt(0) == 'T' ? 0 : height)
2248 }
2249 });
2250 }
2251
2252 return dijit._place(node, choices, layoutNode, {w: width, h: height});
2253 };
2254
2255 dijit.placementRegistry= new dojo.AdapterRegistry();
2256 dijit.placementRegistry.register("node",
2257 function(n, x){
2258 return typeof x == "object" &&
2259 typeof x.offsetWidth != "undefined" && typeof x.offsetHeight != "undefined";
2260 },
2261 dijit.placeOnScreenAroundNode);
2262 dijit.placementRegistry.register("rect",
2263 function(n, x){
2264 return typeof x == "object" &&
2265 "x" in x && "y" in x && "width" in x && "height" in x;
2266 },
2267 dijit.placeOnScreenAroundRectangle);
2268
2269 dijit.placeOnScreenAroundElement = function(
2270 /* DomNode */ node,
2271 /* Object */ aroundElement,
2272 /* Object */ aroundCorners,
2273 /* Function */ layoutNode){
2274
2275 // summary:
2276 // Like dijit.placeOnScreenAroundNode(), except it accepts an arbitrary object
2277 // for the "around" argument and finds a proper processor to place a node.
2278
2279 return dijit.placementRegistry.match.apply(dijit.placementRegistry, arguments);
2280 };
2281
2282 dijit.getPopupAroundAlignment = function(/*Array*/ position, /*Boolean*/ leftToRight){
2283 // summary:
2284 // Transforms the passed array of preferred positions into a format suitable for passing as the aroundCorners argument to dijit.placeOnScreenAroundElement.
2285 //
2286 // position: String[]
2287 // This variable controls the position of the drop down.
2288 // It's an array of strings with the following values:
2289 //
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
2296 //
2297 // The list is positions is tried, in order, until a position is found where the drop down fits
2298 // within the viewport.
2299 //
2300 // leftToRight: Boolean
2301 // Whether the popup will be displaying in leftToRight mode.
2302 //
2303 var align = {};
2304 dojo.forEach(position, function(pos){
2305 switch(pos){
2306 case "after":
2307 align[leftToRight ? "BR" : "BL"] = leftToRight ? "BL" : "BR";
2308 break;
2309 case "before":
2310 align[leftToRight ? "BL" : "BR"] = leftToRight ? "BR" : "BL";
2311 break;
2312 case "below-alt":
2313 leftToRight = !leftToRight;
2314 // fall through
2315 case "below":
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";
2319 break;
2320 case "above-alt":
2321 leftToRight = !leftToRight;
2322 // fall through
2323 case "above":
2324 default:
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";
2328 break;
2329 }
2330 });
2331 return align;
2332 };
2333
2334 }
2335
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");
2339
2340
2341
2342 dijit.getDocumentWindow = function(doc){
2343 return dojo.window.get(doc);
2344 };
2345
2346 }
2347
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");
2351
2352
2353
2354
2355
2356 /*=====
2357 dijit.popup.__OpenArgs = function(){
2358 // popup: Widget
2359 // widget to display
2360 // parent: Widget
2361 // the button etc. that is displaying this popup
2362 // around: DomNode
2363 // DOM node (typically a button); place popup relative to this node. (Specify this *or* "x" and "y" parameters.)
2364 // x: Integer
2365 // Absolute horizontal position (in pixels) to place node at. (Specify this *or* "around" parameter.)
2366 // y: Integer
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.
2373 //
2374 // The default value is {BL:'TL', TL:'BL'}, which represents a list of two tuples:
2375 // 1. (BL, TL)
2376 // 2. (TL, BL)
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.
2381 //
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.
2398 this.popup = popup;
2399 this.parent = parent;
2400 this.around = around;
2401 this.x = x;
2402 this.y = y;
2403 this.orient = orient;
2404 this.onCancel = onCancel;
2405 this.onClose = onClose;
2406 this.onExecute = onExecute;
2407 this.padding = padding;
2408 }
2409 =====*/
2410
2411 dijit.popup = {
2412 // summary:
2413 // This singleton is used to show/hide widgets as popups.
2414
2415 // _stack: dijit._Widget[]
2416 // Stack of currently popped up widgets.
2417 // (someone opened _stack[0], and then it opened _stack[1], etc.)
2418 _stack: [],
2419
2420 // _beginZIndex: Number
2421 // Z-index of the first popup. (If first popup opens other
2422 // popups they get a higher z-index.)
2423 _beginZIndex: 1000,
2424
2425 _idGen: 1,
2426
2427 _createWrapper: function(/*Widget || DomNode*/ widget){
2428 // summary:
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.
2432
2433 var wrapper = widget.declaredClass ? widget._popupWrapper : (widget.parentNode && dojo.hasClass(widget.parentNode, "dijitPopup")),
2434 node = widget.domNode || widget;
2435
2436 if(!wrapper){
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"
2444 }, dojo.body());
2445 wrapper.appendChild(node);
2446
2447 var s = node.style;
2448 s.display = "";
2449 s.visibility = "";
2450 s.position = "";
2451 s.top = "0px";
2452
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;
2458 });
2459 }
2460 }
2461
2462 return wrapper;
2463 },
2464
2465 moveOffScreen: function(/*Widget || DomNode*/ widget){
2466 // summary:
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.
2471
2472 // Create wrapper if not already there
2473 var wrapper = this._createWrapper(widget);
2474
2475 dojo.style(wrapper, {
2476 visibility: "hidden",
2477 top: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
2478 display: ""
2479 });
2480 },
2481
2482 hide: function(/*dijit._Widget*/ widget){
2483 // summary:
2484 // Hide this popup widget (until it is ready to be shown).
2485 // Initialization for widgets that will be used as popups
2486 //
2487 // Also puts widget inside a wrapper DIV (if not already in one)
2488 //
2489 // If popup widget needs to layout it should
2490 // do so when it is made visible, and popup._onShow() is called.
2491
2492 // Create wrapper if not already there
2493 var wrapper = this._createWrapper(widget);
2494
2495 dojo.style(wrapper, "display", "none");
2496 },
2497
2498 getTopPopup: function(){
2499 // summary:
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 */
2505 }
2506 return stack[pi];
2507 },
2508
2509 open: function(/*dijit.popup.__OpenArgs*/ args){
2510 // summary:
2511 // Popup the widget at the specified position
2512 //
2513 // example:
2514 // opening at the mouse position
2515 // | dijit.popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
2516 //
2517 // example:
2518 // opening the widget as a dropdown
2519 // | dijit.popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
2520 //
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.
2523
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'}
2530 ),
2531 around = args.around,
2532 id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++);
2533
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);
2539 }
2540
2541 // Get pointer to popup wrapper, and create wrapper if it doesn't exist
2542 var wrapper = this._createWrapper(widget);
2543
2544
2545 dojo.attr(wrapper, {
2546 id: id,
2547 style: {
2548 zIndex: this._beginZIndex + stack.length
2549 },
2550 "class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup",
2551 dijitPopupParent: args.parent ? args.parent.id : ""
2552 });
2553
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);
2558 }
2559 }
2560
2561 // position the wrapper node and make it visible
2562 var best = around ?
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);
2565
2566 wrapper.style.display = "";
2567 wrapper.style.visibility = "visible";
2568 widget.domNode.style.visibility = "visible"; // counteract effects from _HasDropDown
2569
2570 var handlers = [];
2571
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);
2577 args.onCancel();
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();
2583 }
2584 }
2585 }));
2586
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));
2591 }
2592
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();
2597 }
2598 }));
2599
2600 stack.push({
2601 widget: widget,
2602 parent: args.parent,
2603 onExecute: args.onExecute,
2604 onCancel: args.onCancel,
2605 onClose: args.onClose,
2606 handlers: handlers
2607 });
2608
2609 if(widget.onOpen){
2610 // TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here)
2611 widget.onOpen(best);
2612 }
2613
2614 return best;
2615 },
2616
2617 close: function(/*dijit._Widget?*/ popup){
2618 // summary:
2619 // Close specified popup and any popups that it parented.
2620 // If no popup is specified, closes all popups.
2621
2622 var stack = this._stack;
2623
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;
2634
2635 if(widget.onClose){
2636 // TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here)
2637 widget.onClose();
2638 }
2639 dojo.forEach(top.handlers, dojo.disconnect);
2640
2641 // Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc.
2642 if(widget && widget.domNode){
2643 this.hide(widget);
2644 }
2645
2646 if(onClose){
2647 onClose();
2648 }
2649 }
2650 }
2651 };
2652
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(){
2656 // summary:
2657 // cache of iframes
2658
2659 var queue = [];
2660
2661 this.pop = function(){
2662 var iframe;
2663 if(queue.length){
2664 iframe = queue.pop();
2665 iframe.style.display="";
2666 }else{
2667 if(dojo.isIE < 9){
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);
2673 }else{
2674 iframe = dojo.create("iframe");
2675 iframe.src = 'javascript:""';
2676 iframe.className = "dijitBackgroundIframe";
2677 dojo.style(iframe, "opacity", 0.1);
2678 }
2679 iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didn't work.
2680 dijit.setWaiRole(iframe,"presentation");
2681 }
2682 return iframe;
2683 };
2684
2685 this.push = function(iframe){
2686 iframe.style.display="none";
2687 queue.push(iframe);
2688 }
2689 }();
2690
2691
2692 dijit.BackgroundIframe = function(/*DomNode*/ node){
2693 // summary:
2694 // For IE/FF z-index schenanigans. id attribute is required.
2695 //
2696 // description:
2697 // new dijit.BackgroundIframe(node)
2698 // Makes a background iframe as a child of node, that fills
2699 // area (and position) of node
2700
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){
2706 this.resize(node);
2707 this._conn = dojo.connect(node, 'onresize', this, function(){
2708 this.resize(node);
2709 });
2710 }else{
2711 dojo.style(iframe, {
2712 width: '100%',
2713 height: '100%'
2714 });
2715 }
2716 }
2717 };
2718
2719 dojo.extend(dijit.BackgroundIframe, {
2720 resize: function(node){
2721 // summary:
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.
2724 if(this.iframe){
2725 dojo.style(this.iframe, {
2726 width: node.offsetWidth + 'px',
2727 height: node.offsetHeight + 'px'
2728 });
2729 }
2730 },
2731 destroy: function(){
2732 // summary:
2733 // destroy the iframe
2734 if(this._conn){
2735 dojo.disconnect(this._conn);
2736 this._conn = null;
2737 }
2738 if(this.iframe){
2739 dijit._frames.push(this.iframe);
2740 delete this.iframe;
2741 }
2742 }
2743 });
2744
2745 }
2746
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");
2750
2751
2752
2753 dijit.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
2754 // summary:
2755 // Scroll the passed node into view, if it is not already.
2756 // Deprecated, use `dojo.window.scrollIntoView` instead.
2757
2758 dojo.window.scrollIntoView(node, pos);
2759 };
2760
2761 }
2762
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");
2766
2767
2768 (function(){
2769 // summary:
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)
2775 //
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.
2778
2779 var d = dojo,
2780 html = d.doc.documentElement,
2781 ie = d.isIE,
2782 opera = d.isOpera,
2783 maj = Math.floor,
2784 ff = d.isFF,
2785 boxModel = d.boxModel.replace(/-/,''),
2786
2787 classes = {
2788 dj_ie: ie,
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,
2795
2796 // NOTE: Opera not supported by dijit
2797 dj_opera: opera,
2798
2799 dj_khtml: d.isKhtml,
2800
2801 dj_webkit: d.isWebKit,
2802 dj_safari: d.isSafari,
2803 dj_chrome: d.isChrome,
2804
2805 dj_gecko: d.isMozilla,
2806 dj_ff3: maj(ff) == 3
2807 }; // no dojo unsupported browsers
2808
2809 classes["dj_" + boxModel] = true;
2810
2811 // apply browser, browser version, and box model class names
2812 var classStr = "";
2813 for(var clz in classes){
2814 if(classes[clz]){
2815 classStr += clz + " ";
2816 }
2817 }
2818 html.className = d.trim(html.className + " " + classStr);
2819
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);
2827 }
2828 });
2829 })();
2830
2831 }
2832
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");
2836
2837
2838
2839 // summary:
2840 // Applies pre-set CSS classes to the top-level HTML node, see
2841 // `dojo.uacss` for details.
2842 //
2843 // Simply doing a require on this module will
2844 // establish this CSS. Modified version of Morris' CSS hack.
2845
2846 }
2847
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");
2851
2852
2853 dijit.typematic = {
2854 // summary:
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.
2859
2860 _fireEventAndReload: function(){
2861 this._timer = null;
2862 this._callback(++this._count, this._node, this._evt);
2863
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)),
2869 this._minDelay);
2870 this._timer = setTimeout(dojo.hitch(this, "_fireEventAndReload"), this._currentTimeout);
2871 },
2872
2873 trigger: function(/*Event*/ evt, /*Object*/ _this, /*DOMNode*/ node, /*Function*/ callback, /*Object*/ obj, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
2874 // summary:
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.
2879 // evt:
2880 // key or mouse event object to pass to the user callback
2881 // _this:
2882 // pointer to the user's widget space.
2883 // node:
2884 // the DOM node object to pass the the callback function
2885 // callback:
2886 // function to call until the sequence is stopped called with 3 parameters:
2887 // count:
2888 // integer representing number of repeated calls (0..n) with -1 indicating the iteration has stopped
2889 // node:
2890 // the DOM node object passed in
2891 // evt:
2892 // key or mouse event object
2893 // obj:
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){
2903 this.stop();
2904 this._initialDelay = initialDelay || 500;
2905 this._subsequentDelay = subsequentDelay || 0.90;
2906 this._minDelay = minDelay || 10;
2907 this._obj = obj;
2908 this._evt = evt;
2909 this._node = node;
2910 this._currentTimeout = -1;
2911 this._count = -1;
2912 this._callback = dojo.hitch(_this, callback);
2913 this._fireEventAndReload();
2914 this._evt = dojo.mixin({faux: true}, evt);
2915 }
2916 },
2917
2918 stop: function(){
2919 // summary:
2920 // Stop an ongoing timed, repeating callback sequence.
2921 if(this._timer){
2922 clearTimeout(this._timer);
2923 this._timer = null;
2924 }
2925 if(this._obj){
2926 this._callback(-1, this._node, this._evt);
2927 this._obj = null;
2928 }
2929 },
2930
2931 addKeyListener: function(/*DOMNode*/ node, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
2932 // summary:
2933 // Start listening for a specific typematic key.
2934 // See also the trigger method for other parameters.
2935 // keyObject:
2936 // an object defining the key to listen for:
2937 // charOrCode:
2938 // the printable character (string) or keyCode (number) to listen for.
2939 // keyCode:
2940 // (deprecated - use charOrCode) the keyCode (number) to listen for (implies charCode = 0).
2941 // charCode:
2942 // (deprecated - use charOrCode) the charCode (number) to listen for.
2943 // ctrlKey:
2944 // desired ctrl key state to initiate the callback sequence:
2945 // - pressed (true)
2946 // - released (false)
2947 // - either (unspecified)
2948 // altKey:
2949 // same as ctrlKey but for the alt key
2950 // shiftKey:
2951 // same as ctrlKey but for the shift key
2952 // returns:
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");
2960 }
2961 return [
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();
2972 }
2973 }),
2974 dojo.connect(node, "onkeyup", this, function(evt){
2975 if(dijit.typematic._obj == keyObject){
2976 dijit.typematic.stop();
2977 }
2978 })
2979 ];
2980 },
2981
2982 addMouseListener: function(/*DOMNode*/ node, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
2983 // summary:
2984 // Start listening for a typematic mouse click.
2985 // See the trigger method for other parameters.
2986 // returns:
2987 // an array of dojo.connect handles
2988 var dc = dojo.connect;
2989 return [
2990 dc(node, "mousedown", this, function(evt){
2991 dojo.stopEvent(evt);
2992 dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
2993 }),
2994 dc(node, "mouseup", this, function(evt){
2995 dojo.stopEvent(evt);
2996 dijit.typematic.stop();
2997 }),
2998 dc(node, "mouseout", this, function(evt){
2999 dojo.stopEvent(evt);
3000 dijit.typematic.stop();
3001 }),
3002 dc(node, "mousemove", this, function(evt){
3003 evt.preventDefault();
3004 }),
3005 dc(node, "dblclick", this, function(evt){
3006 dojo.stopEvent(evt);
3007 if(dojo.isIE){
3008 dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
3009 setTimeout(dojo.hitch(this, dijit.typematic.stop), 50);
3010 }
3011 })
3012 ];
3013 },
3014
3015 addListener: function(/*Node*/ mouseNode, /*Node*/ keyNode, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
3016 // summary:
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.
3020 // mouseNode:
3021 // the DOM node object to listen on for mouse events.
3022 // keyNode:
3023 // the DOM node object to listen on for key events.
3024 // returns:
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));
3028 }
3029 };
3030
3031 }
3032
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");
3036
3037
3038 dijit.wai = {
3039 onload: function(){
3040 // summary:
3041 // Detects if we are in high-contrast mode or not
3042
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.
3047
3048 // create div for testing if high contrast mode is on or images are turned off
3049 var div = dojo.create("div",{
3050 id: "a11yTestNode",
3051 style:{
3052 cssText:'border: 1px solid;'
3053 + 'border-color:red green;'
3054 + 'position: absolute;'
3055 + 'height: 5px;'
3056 + 'top: -999px;'
3057 + 'background-image: url("' + (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")) + '");'
3058 }
3059 }, dojo.body());
3060
3061 // test it
3062 var cs = dojo.getComputedStyle(div);
3063 if(cs){
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");
3067 if(dojo.isIE){
3068 div.outerHTML = ""; // prevent mixed-content warning, see http://support.microsoft.com/kb/925014
3069 }else{
3070 dojo.body().removeChild(div);
3071 }
3072 }
3073 }
3074 };
3075
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);
3080 }
3081
3082 dojo.mixin(dijit, {
3083 hasWaiRole: function(/*Element*/ elem, /*String?*/ role){
3084 // summary:
3085 // Determines if an element has a particular role.
3086 // returns:
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);
3092 },
3093
3094 getWaiRole: function(/*Element*/ elem){
3095 // summary:
3096 // Gets the role for an element (which should be a wai role).
3097 // returns:
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:",""));
3101 },
3102
3103 setWaiRole: function(/*Element*/ elem, /*String*/ role){
3104 // summary:
3105 // Sets the role on an element.
3106 // description:
3107 // Replace existing role attribute with new role.
3108
3109 dojo.attr(elem, "role", role);
3110 },
3111
3112 removeWaiRole: function(/*Element*/ elem, /*String*/ role){
3113 // summary:
3114 // Removes the specified role from an element.
3115 // Removes role attribute if no specific role provided (for backwards compat.)
3116
3117 var roleValue = dojo.attr(elem, "role");
3118 if(!roleValue){ return; }
3119 if(role){
3120 var t = dojo.trim((" " + roleValue + " ").replace(" " + role + " ", " "));
3121 dojo.attr(elem, "role", t);
3122 }else{
3123 elem.removeAttribute("role");
3124 }
3125 },
3126
3127 hasWaiState: function(/*Element*/ elem, /*String*/ state){
3128 // summary:
3129 // Determines if an element has a given state.
3130 // description:
3131 // Checks for an attribute called "aria-"+state.
3132 // returns:
3133 // true if elem has a value for the given state and
3134 // false if it does not.
3135
3136 return elem.hasAttribute ? elem.hasAttribute("aria-"+state) : !!elem.getAttribute("aria-"+state);
3137 },
3138
3139 getWaiState: function(/*Element*/ elem, /*String*/ state){
3140 // summary:
3141 // Gets the value of a state on an element.
3142 // description:
3143 // Checks for an attribute called "aria-"+state.
3144 // returns:
3145 // The value of the requested state on elem
3146 // or an empty string if elem has no value for state.
3147
3148 return elem.getAttribute("aria-"+state) || "";
3149 },
3150
3151 setWaiState: function(/*Element*/ elem, /*String*/ state, /*String*/ value){
3152 // summary:
3153 // Sets a state on an element.
3154 // description:
3155 // Sets an attribute called "aria-"+state.
3156
3157 elem.setAttribute("aria-"+state, value);
3158 },
3159
3160 removeWaiState: function(/*Element*/ elem, /*String*/ state){
3161 // summary:
3162 // Removes a state from an element.
3163 // description:
3164 // Sets an attribute called "aria-"+state.
3165
3166 elem.removeAttribute("aria-"+state);
3167 }
3168 });
3169
3170 }
3171
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");
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187 }
3188
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");
3192
3193
3194 dojo.declare("dojo.Stateful", null, {
3195 // summary:
3196 // Base class for objects that provide named properties with optional getter/setter
3197 // control and the ability to watch for property changes
3198 // example:
3199 // | var obj = new dojo.Stateful();
3200 // | obj.watch("foo", function(){
3201 // | console.log("foo changed to " + this.get("foo"));
3202 // | });
3203 // | obj.set("foo","bar");
3204 postscript: function(mixin){
3205 if(mixin){
3206 dojo.mixin(this, mixin);
3207 }
3208 },
3209
3210 get: function(/*String*/name){
3211 // summary:
3212 // Get a property on a Stateful instance.
3213 // name:
3214 // The property to get.
3215 // description:
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.
3219 // For example:
3220 // | stateful = new dojo.Stateful({foo: 3});
3221 // | stateful.get("foo") // returns 3
3222 // | stateful.foo // returns 3
3223
3224 return this[name];
3225 },
3226 set: function(/*String*/name, /*Object*/value){
3227 // summary:
3228 // Set a property on a Stateful instance
3229 // name:
3230 // The property to set.
3231 // value:
3232 // The value to set in the property.
3233 // description:
3234 // Sets named properties on a stateful object and notifies any watchers of
3235 // the property. A programmatic setter may be defined in subclasses.
3236 // For example:
3237 // | stateful = new dojo.Stateful();
3238 // | stateful.watch(function(name, oldValue, value){
3239 // | // this will be called on the set below
3240 // | }
3241 // | stateful.set(foo, 5);
3242 //
3243 // set() may also be called with a hash of name/value pairs, ex:
3244 // | myObj.set({
3245 // | foo: "Howdy",
3246 // | bar: 3
3247 // | })
3248 // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
3249 if(typeof name === "object"){
3250 for(var x in name){
3251 this.set(x, name[x]);
3252 }
3253 return this;
3254 }
3255 var oldValue = this[name];
3256 this[name] = value;
3257 if(this._watchCallbacks){
3258 this._watchCallbacks(name, oldValue, value);
3259 }
3260 return this;
3261 },
3262 watch: function(/*String?*/name, /*Function*/callback){
3263 // summary:
3264 // Watches a property for changes
3265 // name:
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
3268 // returns:
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
3273 // callback:
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.
3278
3279 var callbacks = this._watchCallbacks;
3280 if(!callbacks){
3281 var self = this;
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++){
3287 try{
3288 propertyCallbacks[i].call(self, name, oldValue, value);
3289 }catch(e){
3290 console.error(e);
3291 }
3292 }
3293 }
3294 };
3295 notify(callbacks['_' + name]);
3296 if(!ignoreCatchall){
3297 notify(callbacks["*"]); // the catch-all
3298 }
3299 }; // we use a function instead of an object so it will be ignored by JSON conversion
3300 }
3301 if(!callback && typeof name === "function"){
3302 callback = name;
3303 name = "*";
3304 }else{
3305 // prepend with dash to prevent name conflicts with function (like "name" property)
3306 name = '_' + name;
3307 }
3308 var propertyCallbacks = callbacks[name];
3309 if(typeof propertyCallbacks !== "object"){
3310 propertyCallbacks = callbacks[name] = [];
3311 }
3312 propertyCallbacks.push(callback);
3313 return {
3314 unwatch: function(){
3315 propertyCallbacks.splice(dojo.indexOf(propertyCallbacks, callback), 1);
3316 }
3317 };
3318 }
3319
3320 });
3321
3322 }
3323
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");
3327
3328
3329
3330
3331 (function(){
3332
3333 dojo.declare("dijit._WidgetBase", dojo.Stateful, {
3334 // summary:
3335 // Future base class for all Dijit widgets.
3336 // _Widget extends this class adding support for various features needed by desktop.
3337
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
3342 // used instead.
3343 id: "",
3344
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).
3350 lang: "",
3351
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.
3356 dir: "",
3357
3358 // class: String
3359 // HTML class attribute
3360 "class": "",
3361
3362 // style: String||Object
3363 // HTML style attributes as cssText string or name/value hash
3364 style: "",
3365
3366 // title: String
3367 // HTML title attribute.
3368 //
3369 // For form widgets this specifies a tooltip to display when hovering over
3370 // the widget (just like the native HTML title attribute).
3371 //
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.
3374 title: "",
3375
3376 // tooltip: String
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.
3379 tooltip: "",
3380
3381 // baseClass: [protected] String
3382 // Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate
3383 // widget state.
3384 baseClass: "",
3385
3386 // srcNodeRef: [readonly] DomNode
3387 // pointer to original DOM node
3388 srcNodeRef: null,
3389
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.
3395 domNode: null,
3396
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:
3401 //
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>
3406 // | </div>
3407 //
3408 // containerNode would point to:
3409 //
3410 // | <b> here's a plain DOM node
3411 // | <span dojoType=subWidget>and a widget</span>
3412 // | <i> and another plain DOM node </i>
3413 //
3414 // In templated widgets, "containerNode" is set via a
3415 // dojoAttachPoint assignment.
3416 //
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,
3421
3422 /*=====
3423 // _started: Boolean
3424 // startup() has completed.
3425 _started: false,
3426 =====*/
3427
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.
3433 //
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.
3437 //
3438 // attributeMap is a hash where the key is an attribute of the widget,
3439 // and the value reflects a binding to a:
3440 //
3441 // - DOM node attribute
3442 // | focus: {node: "focusNode", type: "attribute"}
3443 // Maps this.focus to this.focusNode.focus
3444 //
3445 // - DOM node innerHTML
3446 // | title: { node: "titleNode", type: "innerHTML" }
3447 // Maps this.title to this.titleNode.innerHTML
3448 //
3449 // - DOM node innerText
3450 // | title: { node: "titleNode", type: "innerText" }
3451 // Maps this.title to this.titleNode.innerText
3452 //
3453 // - DOM node CSS class
3454 // | myClass: { node: "domNode", type: "class" }
3455 // Maps this.myClass to this.domNode.className
3456 //
3457 // If the value is an array, then each element in the array matches one of the
3458 // formats of the above list.
3459 //
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:""},
3465
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(),
3470
3471 //////////// INITIALIZATION METHODS ///////////////////////////////////////
3472
3473 postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
3474 // summary:
3475 // Kicks off widget instantiation. See create() for details.
3476 // tags:
3477 // private
3478 this.create(params, srcNodeRef);
3479 },
3480
3481 create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){
3482 // summary:
3483 // Kick off the life-cycle of a widget
3484 // params:
3485 // Hash of initialization parameters for widget, including
3486 // scalar values (like title, duration etc.) and functions,
3487 // typically callbacks like onClick.
3488 // srcNodeRef:
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
3494 // tree
3495 // description:
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.
3499 //
3500 // Of course, adventurous developers could override create entirely, but this should
3501 // only be done as a last resort.
3502 // tags:
3503 // private
3504
3505 // store pointer to original DOM tree
3506 this.srcNodeRef = dojo.byId(srcNodeRef);
3507
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 = [];
3511
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 = [];
3515
3516 // mix in our passed parameters
3517 if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; }
3518 if(params){
3519 this.params = params;
3520 dojo._mixin(this, params);
3521 }
3522 this.postMixInProperties();
3523
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.)
3527 if(!this.id){
3528 this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
3529 }
3530 dijit.registry.add(this);
3531
3532 this.buildRendering();
3533
3534 if(this.domNode){
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();
3538
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);
3546 }
3547 }
3548
3549 if(this.domNode){
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);
3553 }
3554 this.postCreate();
3555
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;
3559 }
3560
3561 this._created = true;
3562 },
3563
3564 _applyAttributes: function(){
3565 // summary:
3566 // Step during widget creation to copy all widget attributes to the
3567 // DOM as per attributeMap and _setXXXAttr functions.
3568 // description:
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.
3572 //
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
3576 // tags:
3577 // private
3578 var condAttrApply = function(attr, scope){
3579 if((scope.params && attr in scope.params) || scope[attr]){
3580 scope.set(attr, scope[attr]);
3581 }
3582 };
3583
3584 // Do the attributes in attributeMap
3585 for(var attr in this.attributeMap){
3586 condAttrApply(attr, this);
3587 }
3588
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);
3593 }
3594 }, this);
3595 },
3596
3597 _getSetterAttributes: function(){
3598 // summary:
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 = []),
3603 attrs,
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));
3608 }
3609 }
3610 }
3611 return ctor._setterAttrs; // String[]
3612 },
3613
3614 postMixInProperties: function(){
3615 // summary:
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
3619 // template.
3620 // tags:
3621 // protected
3622 },
3623
3624 buildRendering: function(){
3625 // summary:
3626 // Construct the UI for this widget, setting this.domNode
3627 // description:
3628 // Most widgets will mixin `dijit._Templated`, which implements this
3629 // method.
3630 // tags:
3631 // protected
3632
3633 if(!this.domNode){
3634 // Create root node if it wasn't created by _Templated
3635 this.domNode = this.srcNodeRef || dojo.create('div');
3636 }
3637
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
3641 if(this.baseClass){
3642 var classes = this.baseClass.split(" ");
3643 if(!this.isLeftToRight()){
3644 classes = classes.concat( dojo.map(classes, function(name){ return name+"Rtl"; }));
3645 }
3646 dojo.addClass(this.domNode, classes);
3647 }
3648 },
3649
3650 postCreate: function(){
3651 // summary:
3652 // Processing after the DOM fragment is created
3653 // description:
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.
3657 // tags:
3658 // protected
3659 },
3660
3661 startup: function(){
3662 // summary:
3663 // Processing after the DOM fragment is added to the document
3664 // description:
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;
3670 },
3671
3672 //////////// DESTROY FUNCTIONS ////////////////////////////////
3673
3674 destroyRecursive: function(/*Boolean?*/ preserveDom){
3675 // summary:
3676 // Destroy this widget and its descendants
3677 // description:
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.
3681 // preserveDom:
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.
3685
3686 this._beingDestroyed = true;
3687 this.destroyDescendants(preserveDom);
3688 this.destroy(preserveDom);
3689 },
3690
3691 destroy: function(/*Boolean*/ preserveDom){
3692 // summary:
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
3698
3699 this._beingDestroyed = true;
3700 this.uninitialize();
3701 var d = dojo,
3702 dfe = d.forEach,
3703 dun = d.unsubscribe;
3704 dfe(this._connects, function(array){
3705 dfe(array, d.disconnect);
3706 });
3707 dfe(this._subscribes, function(handle){
3708 dun(handle);
3709 });
3710
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){
3716 w.destroy();
3717 }
3718 });
3719
3720 this.destroyRendering(preserveDom);
3721 dijit.registry.remove(this.id);
3722 this._destroyed = true;
3723 },
3724
3725 destroyRendering: function(/*Boolean?*/ preserveDom){
3726 // summary:
3727 // Destroys the DOM nodes associated with this widget
3728 // preserveDom:
3729 // If true, this method will leave the original DOM structure alone
3730 // during tear-down. Note: this will not work with _Templated
3731 // widgets yet.
3732 // tags:
3733 // protected
3734
3735 if(this.bgIframe){
3736 this.bgIframe.destroy(preserveDom);
3737 delete this.bgIframe;
3738 }
3739
3740 if(this.domNode){
3741 if(preserveDom){
3742 dojo.removeAttr(this.domNode, "widgetId");
3743 }else{
3744 dojo.destroy(this.domNode);
3745 }
3746 delete this.domNode;
3747 }
3748
3749 if(this.srcNodeRef){
3750 if(!preserveDom){
3751 dojo.destroy(this.srcNodeRef);
3752 }
3753 delete this.srcNodeRef;
3754 }
3755 },
3756
3757 destroyDescendants: function(/*Boolean?*/ preserveDom){
3758 // summary:
3759 // Recursively destroy the children of this widget and their
3760 // descendants.
3761 // preserveDom:
3762 // If true, the preserveDom attribute is passed to all descendant
3763 // widget's .destroy() method. Not for use with _Templated
3764 // widgets.
3765
3766 // get all direct descendants and destroy them recursively
3767 dojo.forEach(this.getChildren(), function(widget){
3768 if(widget.destroyRecursive){
3769 widget.destroyRecursive(preserveDom);
3770 }
3771 });
3772 },
3773
3774 uninitialize: function(){
3775 // summary:
3776 // Stub function. Override to implement custom widget tear-down
3777 // behavior.
3778 // tags:
3779 // protected
3780 return false;
3781 },
3782
3783 ////////////////// GET/SET, CUSTOM SETTERS, ETC. ///////////////////
3784
3785 _setClassAttr: function(/*String*/ value){
3786 // summary:
3787 // Custom setter for the CSS "class" attribute
3788 // tags:
3789 // protected
3790 var mapNode = this[this.attributeMap["class"] || 'domNode'];
3791 dojo.replaceClass(mapNode, value, this["class"]);
3792 this._set("class", value);
3793 },
3794
3795 _setStyleAttr: function(/*String||Object*/ value){
3796 // summary:
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
3800 // description:
3801 // Determines which node to set the style on based on style setting
3802 // in attributeMap.
3803 // tags:
3804 // protected
3805
3806 var mapNode = this[this.attributeMap.style || 'domNode'];
3807
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.
3810
3811 if(dojo.isObject(value)){
3812 dojo.style(mapNode, value);
3813 }else{
3814 if(mapNode.style.cssText){
3815 mapNode.style.cssText += "; " + value;
3816 }else{
3817 mapNode.style.cssText = value;
3818 }
3819 }
3820
3821 this._set("style", value);
3822 },
3823
3824 _attrToDom: function(/*String*/ attr, /*String*/ value){
3825 // summary:
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.
3830 //
3831 // tags:
3832 // private
3833
3834 var commands = this.attributeMap[attr];
3835 dojo.forEach(dojo.isArray(commands) ? commands : [commands], function(command){
3836
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
3840
3841 switch(type){
3842 case "attribute":
3843 if(dojo.isFunction(value)){ // functions execute in the context of the widget
3844 value = dojo.hitch(this, value);
3845 }
3846
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);
3852
3853 dojo.attr(mapNode, attrName, value);
3854 break;
3855 case "innerText":
3856 mapNode.innerHTML = "";
3857 mapNode.appendChild(dojo.doc.createTextNode(value));
3858 break;
3859 case "innerHTML":
3860 mapNode.innerHTML = value;
3861 break;
3862 case "class":
3863 dojo.replaceClass(mapNode, value, this[attr]);
3864 break;
3865 }
3866 }, this);
3867 },
3868
3869 get: function(name){
3870 // summary:
3871 // Get a property from a widget.
3872 // name:
3873 // The property to get.
3874 // description:
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();
3883 // and:
3884 // | myWidget.get("bar");
3885 // would be equivalent to writing:
3886 // | widget.bar;
3887 var names = this._getAttrNames(name);
3888 return this[names.g] ? this[names.g]() : this[name];
3889 },
3890
3891 set: function(name, value){
3892 // summary:
3893 // Set a property on a widget
3894 // name:
3895 // The property to set.
3896 // value:
3897 // The value to set in the property.
3898 // description:
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!");
3906 // and:
3907 // | myWidget.set("bar", 3);
3908 // would be equivalent to writing:
3909 // | widget.bar = 3;
3910 //
3911 // set() may also be called with a hash of name/value pairs, ex:
3912 // | myWidget.set({
3913 // | foo: "Howdy",
3914 // | bar: 3
3915 // | })
3916 // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
3917
3918 if(typeof name === "object"){
3919 for(var x in name){
3920 this.set(x, name[x]);
3921 }
3922 return this;
3923 }
3924 var names = this._getAttrNames(name);
3925 if(this[names.s]){
3926 // use the explicit setter
3927 var result = this[names.s].apply(this, Array.prototype.slice.call(arguments, 1));
3928 }else{
3929 // if param is specified as DOM node attribute, copy it
3930 if(name in this.attributeMap){
3931 this._attrToDom(name, value);
3932 }
3933 this._set(name, value);
3934 }
3935 return result || this;
3936 },
3937
3938 _attrPairNames: {}, // shared between all widgets
3939 _getAttrNames: function(name){
3940 // summary:
3941 // Helper function for get() and set().
3942 // Caches attribute name values so we don't do the string ops every time.
3943 // tags:
3944 // private
3945
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] = {
3950 n: name+"Node",
3951 s: "_set"+uc+"Attr",
3952 g: "_get"+uc+"Attr"
3953 });
3954 },
3955
3956 _set: function(/*String*/ name, /*anything*/ value){
3957 // summary:
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];
3961 this[name] = value;
3962 if(this._watchCallbacks && this._created && value !== oldValue){
3963 this._watchCallbacks(name, oldValue, value);
3964 }
3965 },
3966
3967 toString: function(){
3968 // summary:
3969 // Returns a string that represents the widget
3970 // description:
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
3973 // serialization.
3974 return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String
3975 },
3976
3977 getDescendants: function(){
3978 // summary:
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.
3982
3983 return this.containerNode ? dojo.query('[widgetId]', this.containerNode).map(dijit.byNode) : []; // dijit._Widget[]
3984 },
3985
3986 getChildren: function(){
3987 // summary:
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[]
3991 },
3992
3993 connect: function(
3994 /*Object|null*/ obj,
3995 /*String|Function*/ event,
3996 /*String|Function*/ method){
3997 // summary:
3998 // Connects specified obj/event to specified method of this object
3999 // and registers for disconnect() on widget destroy.
4000 // description:
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
4004 // destruction.
4005 // returns:
4006 // A handle that can be passed to `disconnect` in order to disconnect before
4007 // the widget is destroyed.
4008 // example:
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());
4014 // | });
4015 // tags:
4016 // protected
4017
4018 var handles = [dojo._connect(obj, event, this, method)];
4019 this._connects.push(handles);
4020 return handles; // _Widget.Handle
4021 },
4022
4023 disconnect: function(/* _Widget.Handle */ handles){
4024 // summary:
4025 // Disconnects handle created by `connect`.
4026 // Also removes handle from this widget's list of connects.
4027 // tags:
4028 // protected
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);
4033 return;
4034 }
4035 }
4036 },
4037
4038 subscribe: function(
4039 /*String*/ topic,
4040 /*String|Function*/ method){
4041 // summary:
4042 // Subscribes to the specified topic and calls the specified method
4043 // of this object and registers for unsubscribe() on widget destroy.
4044 // description:
4045 // Provide widget-specific analog to dojo.subscribe, except with the
4046 // implicit use of this widget as the target object.
4047 // example:
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);
4053 // | });
4054 var handle = dojo.subscribe(topic, this, method);
4055
4056 // return handles for Any widget that may need them
4057 this._subscribes.push(handle);
4058 return handle;
4059 },
4060
4061 unsubscribe: function(/*Object*/ handle){
4062 // summary:
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);
4069 return;
4070 }
4071 }
4072 },
4073
4074 isLeftToRight: function(){
4075 // summary:
4076 // Return this widget's explicit or implicit orientation (true for LTR, false for RTL)
4077 // tags:
4078 // protected
4079 return this.dir ? (this.dir == "ltr") : dojo._isBodyLtr(); //Boolean
4080 },
4081
4082 placeAt: function(/* String|DomNode|_Widget */reference, /* String?|Int? */position){
4083 // summary:
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.
4087 //
4088 // description:
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.
4092 //
4093 // reference:
4094 // The String id of a domNode, a domNode reference, or a reference to a Widget posessing
4095 // an addChild method.
4096 //
4097 // position:
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".
4101 //
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.
4105 //
4106 // returns:
4107 // dijit._Widget
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
4110 // to a variable.
4111 //
4112 // example:
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'); });
4117 //
4118 // example:
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");
4121 //
4122 // example:
4123 // | // place a new button as the first element of some div
4124 // | var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first");
4125 //
4126 // example:
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)
4130
4131 if(reference.declaredClass && reference.addChild){
4132 reference.addChild(this, position);
4133 }else{
4134 dojo.place(this.domNode, reference, position);
4135 }
4136 return this;
4137 }
4138 });
4139
4140 })();
4141
4142 }
4143
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");
4147
4148
4149
4150
4151
4152 ////////////////// DEFERRED CONNECTS ///////////////////
4153
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);
4160 }
4161 });
4162
4163 dijit._connectOnUseEventHandler = function(/*Event*/ event){};
4164
4165 ////////////////// ONDIJITCLICK SUPPORT ///////////////////
4166
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;
4174 if(dojo.isIE){
4175 (function(){
4176 var keydownCallback = function(evt){
4177 dijit._lastKeyDownNode = evt.srcElement;
4178 };
4179 dojo.doc.attachEvent('onkeydown', keydownCallback);
4180 dojo.addOnWindowUnload(function(){
4181 dojo.doc.detachEvent('onkeydown', keydownCallback);
4182 });
4183 })();
4184 }else{
4185 dojo.doc.addEventListener('keydown', function(evt){
4186 dijit._lastKeyDownNode = evt.target;
4187 }, true);
4188 }
4189
4190 (function(){
4191
4192 dojo.declare("dijit._Widget", dijit._WidgetBase, {
4193 // summary:
4194 // Base class for all Dijit widgets.
4195 //
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)
4200 // - ondijitclick
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()
4207 //
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)
4211
4212
4213 ////////////////// DEFERRED CONNECTS ///////////////////
4214
4215 // _deferredConnects: [protected] Object
4216 // attributeMap addendum for event handlers that should be connected only on first use
4217 _deferredConnects: {
4218 onClick: "",
4219 onDblClick: "",
4220 onKeyDown: "",
4221 onKeyPress: "",
4222 onKeyUp: "",
4223 onMouseMove: "",
4224 onMouseDown: "",
4225 onMouseOut: "",
4226 onMouseOver: "",
4227 onMouseLeave: "",
4228 onMouseEnter: "",
4229 onMouseUp: ""
4230 },
4231
4232 onClick: dijit._connectOnUseEventHandler,
4233 /*=====
4234 onClick: function(event){
4235 // summary:
4236 // Connect to this function to receive notifications of mouse click events.
4237 // event:
4238 // mouse Event
4239 // tags:
4240 // callback
4241 },
4242 =====*/
4243 onDblClick: dijit._connectOnUseEventHandler,
4244 /*=====
4245 onDblClick: function(event){
4246 // summary:
4247 // Connect to this function to receive notifications of mouse double click events.
4248 // event:
4249 // mouse Event
4250 // tags:
4251 // callback
4252 },
4253 =====*/
4254 onKeyDown: dijit._connectOnUseEventHandler,
4255 /*=====
4256 onKeyDown: function(event){
4257 // summary:
4258 // Connect to this function to receive notifications of keys being pressed down.
4259 // event:
4260 // key Event
4261 // tags:
4262 // callback
4263 },
4264 =====*/
4265 onKeyPress: dijit._connectOnUseEventHandler,
4266 /*=====
4267 onKeyPress: function(event){
4268 // summary:
4269 // Connect to this function to receive notifications of printable keys being typed.
4270 // event:
4271 // key Event
4272 // tags:
4273 // callback
4274 },
4275 =====*/
4276 onKeyUp: dijit._connectOnUseEventHandler,
4277 /*=====
4278 onKeyUp: function(event){
4279 // summary:
4280 // Connect to this function to receive notifications of keys being released.
4281 // event:
4282 // key Event
4283 // tags:
4284 // callback
4285 },
4286 =====*/
4287 onMouseDown: dijit._connectOnUseEventHandler,
4288 /*=====
4289 onMouseDown: function(event){
4290 // summary:
4291 // Connect to this function to receive notifications of when the mouse button is pressed down.
4292 // event:
4293 // mouse Event
4294 // tags:
4295 // callback
4296 },
4297 =====*/
4298 onMouseMove: dijit._connectOnUseEventHandler,
4299 /*=====
4300 onMouseMove: function(event){
4301 // summary:
4302 // Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget.
4303 // event:
4304 // mouse Event
4305 // tags:
4306 // callback
4307 },
4308 =====*/
4309 onMouseOut: dijit._connectOnUseEventHandler,
4310 /*=====
4311 onMouseOut: function(event){
4312 // summary:
4313 // Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget.
4314 // event:
4315 // mouse Event
4316 // tags:
4317 // callback
4318 },
4319 =====*/
4320 onMouseOver: dijit._connectOnUseEventHandler,
4321 /*=====
4322 onMouseOver: function(event){
4323 // summary:
4324 // Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget.
4325 // event:
4326 // mouse Event
4327 // tags:
4328 // callback
4329 },
4330 =====*/
4331 onMouseLeave: dijit._connectOnUseEventHandler,
4332 /*=====
4333 onMouseLeave: function(event){
4334 // summary:
4335 // Connect to this function to receive notifications of when the mouse moves off of this widget.
4336 // event:
4337 // mouse Event
4338 // tags:
4339 // callback
4340 },
4341 =====*/
4342 onMouseEnter: dijit._connectOnUseEventHandler,
4343 /*=====
4344 onMouseEnter: function(event){
4345 // summary:
4346 // Connect to this function to receive notifications of when the mouse moves onto this widget.
4347 // event:
4348 // mouse Event
4349 // tags:
4350 // callback
4351 },
4352 =====*/
4353 onMouseUp: dijit._connectOnUseEventHandler,
4354 /*=====
4355 onMouseUp: function(event){
4356 // summary:
4357 // Connect to this function to receive notifications of when the mouse button is released.
4358 // event:
4359 // mouse Event
4360 // tags:
4361 // callback
4362 },
4363 =====*/
4364
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
4373 }
4374 for(attr in this._deferredConnects){
4375 if(this[attr] !== dijit._connectOnUseEventHandler){
4376 delete this._deferredConnects[attr]; // redefined, probably dojoAttachEvent exists
4377 }
4378 }
4379
4380 this.inherited(arguments);
4381
4382 if(this.domNode){
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);
4388 }
4389 }
4390 },
4391
4392 _onConnect: function(/*String*/ event){
4393 // summary:
4394 // Called when someone connects to one of my handlers.
4395 // "Turn on" that handler if it isn't active yet.
4396 //
4397 // This is also called for every single initialization parameter
4398 // so need to do nothing for parameters like "id".
4399 // tags:
4400 // private
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];
4405 }
4406 },
4407
4408 ////////////////// FOCUS RELATED ///////////////////
4409 // _onFocus() and _onBlur() are called by the focus manager
4410
4411 // focused: [readonly] Boolean
4412 // This widget or a widget it contains has focus, or is "active" because
4413 // it was recently clicked.
4414 focused: false,
4415
4416 isFocusable: function(){
4417 // summary:
4418 // Return true if this widget can currently be focused
4419 // and false if not
4420 return this.focus && (dojo.style(this.domNode, "display") != "none");
4421 },
4422
4423 onFocus: function(){
4424 // summary:
4425 // Called when the widget becomes "active" because
4426 // it or a widget inside of it either has focus, or has recently
4427 // been clicked.
4428 // tags:
4429 // callback
4430 },
4431
4432 onBlur: function(){
4433 // summary:
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
4437 // hidden.
4438 // tags:
4439 // callback
4440 },
4441
4442 _onFocus: function(e){
4443 // summary:
4444 // This is where widgets do processing for when they are active,
4445 // such as changing CSS classes. See onFocus() for more details.
4446 // tags:
4447 // protected
4448 this.onFocus();
4449 },
4450
4451 _onBlur: function(){
4452 // summary:
4453 // This is where widgets do processing for when they stop being active,
4454 // such as changing CSS classes. See onBlur() for more details.
4455 // tags:
4456 // protected
4457 this.onBlur();
4458 },
4459
4460 ////////////////// DEPRECATED METHODS ///////////////////
4461
4462 setAttribute: function(/*String*/ attr, /*anything*/ value){
4463 // summary:
4464 // Deprecated. Use set() instead.
4465 // tags:
4466 // deprecated
4467 dojo.deprecated(this.declaredClass+"::setAttribute(attr, value) is deprecated. Use set() instead.", "", "2.0");
4468 this.set(attr, value);
4469 },
4470
4471 attr: function(/*String|Object*/name, /*Object?*/value){
4472 // summary:
4473 // Set or get properties on a widget instance.
4474 // name:
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.
4478 // value:
4479 // Optional. If provided, attr() operates as a setter. If omitted,
4480 // the current value of the named property is returned.
4481 // description:
4482 // This method is deprecated, use get() or set() directly.
4483
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 " +
4490 caller, "", "2.0");
4491 alreadyCalledHash[caller] = true;
4492 }
4493 }
4494
4495 var args = arguments.length;
4496 if(args >= 2 || typeof name === "object"){ // setter
4497 return this.set.apply(this, arguments);
4498 }else{ // getter
4499 return this.get(name);
4500 }
4501 },
4502
4503 ////////////////// ONDIJITCLICK SUPPORT ///////////////////
4504
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"],
4509
4510 connect: function(
4511 /*Object|null*/ obj,
4512 /*String|Function*/ event,
4513 /*String|Function*/ method){
4514 // summary:
4515 // Connects specified obj/event to specified method of this object
4516 // and registers for disconnect() on widget destroy.
4517 // description:
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
4523 // destruction.
4524 // returns:
4525 // A handle that can be passed to `disconnect` in order to disconnect before
4526 // the widget is destroyed.
4527 // example:
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());
4533 // | });
4534 // tags:
4535 // protected
4536
4537 var d = dojo,
4538 dc = d._connect,
4539 handles = this.inherited(arguments, [obj, event == "ondijitclick" ? "onclick" : event, method]);
4540
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);
4547 handles.push(
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;
4554
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)){
4559 e.preventDefault();
4560 }
4561 }
4562 }),
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;
4570 return m(e);
4571 }
4572 })
4573 );
4574 }
4575 }
4576
4577 return handles; // _Widget.Handle
4578 },
4579
4580 ////////////////// MISCELLANEOUS METHODS ///////////////////
4581
4582 _onShow: function(){
4583 // summary:
4584 // Internal method called when this widget is made visible.
4585 // See `onShow` for details.
4586 this.onShow();
4587 },
4588
4589 onShow: function(){
4590 // summary:
4591 // Called when this widget becomes the selected pane in a
4592 // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`,
4593 // `dijit.layout.AccordionContainer`, etc.
4594 //
4595 // Also called to indicate display of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
4596 // tags:
4597 // callback
4598 },
4599
4600 onHide: function(){
4601 // summary:
4602 // Called when another widget becomes the selected pane in a
4603 // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`,
4604 // `dijit.layout.AccordionContainer`, etc.
4605 //
4606 // Also called to indicate hide of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
4607 // tags:
4608 // callback
4609 },
4610
4611 onClose: function(){
4612 // summary:
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.
4616 //
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.
4619 // tags:
4620 // extension
4621
4622 return true; // Boolean
4623 }
4624 });
4625
4626 })();
4627
4628 }
4629
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");
4633
4634 dojo.getObject("string", true, dojo);
4635
4636 /*=====
4637 dojo.string = {
4638 // summary: String utilities for Dojo
4639 };
4640 =====*/
4641
4642 dojo.string.rep = function(/*String*/str, /*Integer*/num){
4643 // summary:
4644 // Efficiently replicate a string `n` times.
4645 // str:
4646 // the string to replicate
4647 // num:
4648 // number of times to replicate the string
4649
4650 if(num <= 0 || !str){ return ""; }
4651
4652 var buf = [];
4653 for(;;){
4654 if(num & 1){
4655 buf.push(str);
4656 }
4657 if(!(num >>= 1)){ break; }
4658 str += str;
4659 }
4660 return buf.join(""); // String
4661 };
4662
4663 dojo.string.pad = function(/*String*/text, /*Integer*/size, /*String?*/ch, /*Boolean?*/end){
4664 // summary:
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.
4668 // text:
4669 // the string to pad
4670 // size:
4671 // length to provide padding
4672 // ch:
4673 // character to pad, defaults to '0'
4674 // end:
4675 // adds padding at the end if true, otherwise pads at start
4676 // example:
4677 // | // Fill the string to length 10 with "+" characters on the right. Yields "Dojo++++++".
4678 // | dojo.string.pad("Dojo", 10, "+", true);
4679
4680 if(!ch){
4681 ch = '0';
4682 }
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
4686 };
4687
4688 dojo.string.substitute = function( /*String*/ template,
4689 /*Object|Array*/map,
4690 /*Function?*/ transform,
4691 /*Object?*/ thisObject){
4692 // summary:
4693 // Performs parameterized substitutions on a string. Throws an
4694 // exception if any parameter is unmatched.
4695 // template:
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.
4698 // map:
4699 // hash to search for substitutions
4700 // transform:
4701 // a function to process all parameters before substitution takes
4702 // place, e.g. mylib.encodeXML
4703 // thisObject:
4704 // where to look for optional format function; default to the global
4705 // namespace
4706 // example:
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"]
4713 // | );
4714 // |
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" } }
4721 // | );
4722 // example:
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"],
4728 // | function(str){
4729 // | // try to figure out the type
4730 // | var prefix = (str.charAt(0) == "/") ? "directory": "file";
4731 // | return prefix + " '" + str + "'";
4732 // | }
4733 // | );
4734 // example:
4735 // Use a formatter
4736 // | // returns "thinger -- howdy"
4737 // | dojo.string.substitute(
4738 // | "${0:postfix}", ["thinger"], null, {
4739 // | postfix: function(value, key){
4740 // | return value + " -- howdy";
4741 // | }
4742 // | }
4743 // | );
4744
4745 thisObject = thisObject || dojo.global;
4746 transform = transform ?
4747 dojo.hitch(thisObject, transform) : function(v){ return v; };
4748
4749 return template.replace(/\$\{([^\s\:\}]+)(?:\:([^\s\:\}]+))?\}/g,
4750 function(match, key, format){
4751 var value = dojo.getObject(key, false, map);
4752 if(format){
4753 value = dojo.getObject(format, false, thisObject).call(thisObject, value, key);
4754 }
4755 return transform(value, key).toString();
4756 }); // String
4757 };
4758
4759 /*=====
4760 dojo.string.trim = function(str){
4761 // summary:
4762 // Trims whitespace from both sides of the string
4763 // str: String
4764 // String to be trimmed
4765 // returns: String
4766 // Returns the trimmed string
4767 // description:
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
4772 }
4773 =====*/
4774
4775 dojo.string.trim = String.prototype.trim ?
4776 dojo.trim : // aliasing to the native function
4777 function(str){
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);
4782 break;
4783 }
4784 }
4785 return str;
4786 };
4787
4788 }
4789
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");
4793
4794
4795 /*=====
4796 dojo.cache = {
4797 // summary:
4798 // A way to cache string content that is fetchable via `dojo.moduleUrl`.
4799 };
4800 =====*/
4801
4802 var cache = {};
4803 dojo.cache = function(/*String||Object*/module, /*String*/url, /*String||Object?*/value){
4804 // summary:
4805 // A getter and setter for storing the string content associated with the
4806 // module and url arguments.
4807 // description:
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.
4819 // url: String
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.
4830 // example:
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");
4838 // example:
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});
4846 // example:
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});
4852
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);
4858 }else{
4859 pathObj = module;
4860 value = url;
4861 }
4862 var key = pathObj.toString();
4863
4864 var val = value;
4865 if(value != undefined && !dojo.isString(value)){
4866 val = ("value" in value ? value.value : undefined);
4867 }
4868
4869 var sanitize = value && value.sanitize ? true : false;
4870
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
4876 delete cache[key];
4877 }else{
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;
4883 }
4884 val = cache[key];
4885 }
4886 return val; //String
4887 };
4888
4889 dojo.cache._sanitize = function(/*String*/val){
4890 // summary:
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.
4894 // description:
4895 // Copied from dijit._Templated._sanitizeTemplateString.
4896 if(val){
4897 val = val.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, "");
4898 var matches = val.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
4899 if(matches){
4900 val = matches[1];
4901 }
4902 }else{
4903 val = "";
4904 }
4905 return val; //String
4906 };
4907
4908 }
4909
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");
4913
4914
4915
4916
4917
4918
4919 dojo.declare("dijit._Templated",
4920 null,
4921 {
4922 // summary:
4923 // Mixin for widgets that are instantiated from a template
4924
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.
4930 //
4931 // Use in conjunction with dojo.cache() to load from a file.
4932 templateString: null,
4933
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.
4937 templatePath: null,
4938
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,
4943
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,
4949
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,
4958
4959 /*=====
4960 // _attachPoints: [private] String[]
4961 // List of widget attribute names associated with dojoAttachPoint=... in the
4962 // template, ex: ["containerNode", "labelNode"]
4963 _attachPoints: [],
4964 =====*/
4965
4966 /*=====
4967 // _attachEvents: [private] Handle[]
4968 // List of connections associated with dojoAttachEvent=... in the
4969 // template
4970 _attachEvents: [],
4971 =====*/
4972
4973 constructor: function(){
4974 this._attachPoints = [];
4975 this._attachEvents = [];
4976 },
4977
4978 _stringRepl: function(tmpl){
4979 // summary:
4980 // Does substitution of ${foo} type properties in template string
4981 // tags:
4982 // private
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 ""; }
4990
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,"&quot;"); //TODO: add &amp? use encodeXML method?
4997 }, this);
4998 },
4999
5000 buildRendering: function(){
5001 // summary:
5002 // Construct the UI for this widget from a template, setting this.domNode.
5003 // tags:
5004 // protected
5005
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);
5010
5011 var node;
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);
5017 }
5018 }else{
5019 // if it's a node, all we have to do is clone it
5020 node = cached.cloneNode(true);
5021 }
5022
5023 this.domNode = node;
5024
5025 // Call down to _Widget.buildRendering() to get base classes assigned
5026 // TODO: change the baseClass assignment to attributeMap
5027 this.inherited(arguments);
5028
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);
5032
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,
5037 template: true,
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
5041 }));
5042
5043 this._supportingWidgets = dijit.findWidgets(node);
5044
5045 this._attachTemplateNodes(cw, function(n,p){
5046 return n[p];
5047 });
5048 }
5049
5050 this._fillContent(this.srcNodeRef);
5051 },
5052
5053 _fillContent: function(/*DomNode*/ source){
5054 // summary:
5055 // Relocate source contents to templated container node.
5056 // this.containerNode must be able to receive children, or exceptions will be thrown.
5057 // tags:
5058 // protected
5059 var dest = this.containerNode;
5060 if(source && dest){
5061 while(source.hasChildNodes()){
5062 dest.appendChild(source.firstChild);
5063 }
5064 }
5065 },
5066
5067 _attachTemplateNodes: function(rootNode, getAttrFunc){
5068 // summary:
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.
5072 // description:
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
5078 // * waiRole
5079 // * waiState
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
5084 // DomNode/Widget
5085 // tags:
5086 // private
5087
5088 getAttrFunc = getAttrFunc || function(n,p){ return n.getAttribute(p); };
5089
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"))){
5095 continue;
5096 }
5097 // Process dojoAttachPoint
5098 var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point");
5099 if(attachPoint){
5100 var point, points = attachPoint.split(/\s*,\s*/);
5101 while((point = points.shift())){
5102 if(dojo.isArray(this[point])){
5103 this[point].push(baseNode);
5104 }else{
5105 this[point]=baseNode;
5106 }
5107 this._attachPoints.push(point);
5108 }
5109 }
5110
5111 // Process dojoAttachEvent
5112 var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event");;
5113 if(attachEvent){
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())){
5119 if(event){
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]);
5126 }else{
5127 event = trim(event);
5128 }
5129 if(!thisFunc){
5130 thisFunc = event;
5131 }
5132 this._attachEvents.push(this.connect(baseNode, event, thisFunc));
5133 }
5134 }
5135 }
5136
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");
5140 if(role){
5141 dijit.setWaiRole(baseNode, role);
5142 }
5143 var values = getAttrFunc(baseNode, "waiState");
5144 if(values){
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]);
5149 }
5150 });
5151 }
5152 }
5153 },
5154
5155 startup: function(){
5156 dojo.forEach(this._startupWidgets, function(w){
5157 if(w && !w._started && w.startup){
5158 w.startup();
5159 }
5160 });
5161 this.inherited(arguments);
5162 },
5163
5164 destroyRendering: function(){
5165 // Delete all attach points to prevent IE6 memory leaks.
5166 dojo.forEach(this._attachPoints, function(point){
5167 delete this[point];
5168 }, this);
5169 this._attachPoints = [];
5170
5171 // And same for event handlers
5172 dojo.forEach(this._attachEvents, this.disconnect, this);
5173 this._attachEvents = [];
5174
5175 this.inherited(arguments);
5176 }
5177 }
5178 );
5179
5180 // key is either templatePath or templateString; object is either string or DOM tree
5181 dijit._Templated._templateCache = {};
5182
5183 dijit._Templated.getCachedTemplate = function(templatePath, templateString, alwaysUseString){
5184 // summary:
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
5192 // returns: Mixed
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)
5195
5196 // is it already cached?
5197 var tmplts = dijit._Templated._templateCache;
5198 var key = templateString || templatePath;
5199 var cached = tmplts[key];
5200 if(cached){
5201 try{
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
5205 return cached;
5206 }
5207 }catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded
5208 dojo.destroy(cached);
5209 }
5210
5211 // If necessary, load template string from template path
5212 if(!templateString){
5213 templateString = dojo.cache(templatePath, {sanitize: true});
5214 }
5215 templateString = dojo.string.trim(templateString);
5216
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
5220 }else{
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);
5225 }
5226 return (tmplts[key] = node); //Node
5227 }
5228 };
5229
5230 if(dojo.isIE){
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);
5237 }
5238 delete cache[key];
5239 }
5240 });
5241 }
5242
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: "",
5249 waiRole: "",
5250 waiState:""
5251 });
5252
5253 }
5254
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");
5258
5259
5260 dojo.declare("dijit._Container",
5261 null,
5262 {
5263 // summary:
5264 // Mixin for widgets that contain a set of widget children.
5265 // description:
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.
5269 //
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.
5275
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`.
5280 isContainer: true,
5281
5282 buildRendering: function(){
5283 this.inherited(arguments);
5284 if(!this.containerNode){
5285 // all widgets with descendants must set containerNode
5286 this.containerNode = this.domNode;
5287 }
5288 },
5289
5290 addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
5291 // summary:
5292 // Makes the given widget a child of this widget.
5293 // description:
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).
5296
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";
5303 }
5304 }
5305 dojo.place(widget.domNode, refNode, insertIndex);
5306
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){
5312 widget.startup();
5313 }
5314 },
5315
5316 removeChild: function(/*Widget or int*/ widget){
5317 // summary:
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
5321
5322 if(typeof widget == "number"){
5323 widget = this.getChildren()[widget];
5324 }
5325
5326 if(widget){
5327 var node = widget.domNode;
5328 if(node && node.parentNode){
5329 node.parentNode.removeChild(node); // detach but don't destroy
5330 }
5331 }
5332 },
5333
5334 hasChildren: function(){
5335 // summary:
5336 // Returns true if widget has children, i.e. if this.containerNode contains something.
5337 return this.getChildren().length > 0; // Boolean
5338 },
5339
5340 destroyDescendants: function(/*Boolean*/ preserveDom){
5341 // summary:
5342 // Destroys all the widgets inside this.containerNode,
5343 // but not this widget itself
5344 dojo.forEach(this.getChildren(), function(child){ child.destroyRecursive(preserveDom); });
5345 },
5346
5347 _getSiblingOfChild: function(/*dijit._Widget*/ child, /*int*/ dir){
5348 // summary:
5349 // Get the next or previous widget sibling of child
5350 // dir:
5351 // if 1, get the next sibling
5352 // if -1, get the previous sibling
5353 // tags:
5354 // private
5355 var node = child.domNode,
5356 which = (dir>0 ? "nextSibling" : "previousSibling");
5357 do{
5358 node = node[which];
5359 }while(node && (node.nodeType != 1 || !dijit.byNode(node)));
5360 return node && dijit.byNode(node); // dijit._Widget
5361 },
5362
5363 getIndexOfChild: function(/*dijit._Widget*/ child){
5364 // summary:
5365 // Gets the index of the child in this container or -1 if not found
5366 return dojo.indexOf(this.getChildren(), child); // int
5367 },
5368
5369 startup: function(){
5370 // summary:
5371 // Called after all the widgets have been instantiated and their
5372 // dom nodes have been inserted somewhere under dojo.doc.body.
5373 //
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.
5377 //
5378 // startup() in subclasses shouldn't do anything
5379 // size related because the size of the widget hasn't been set yet.
5380
5381 if(this._started){ return; }
5382
5383 // Startup all children of this widget
5384 dojo.forEach(this.getChildren(), function(child){ child.startup(); });
5385
5386 this.inherited(arguments);
5387 }
5388 }
5389 );
5390
5391 }
5392
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");
5396
5397
5398 dojo.declare("dijit._Contained",
5399 null,
5400 {
5401 // summary:
5402 // Mixin for widgets that are children of a container widget
5403 //
5404 // example:
5405 // | // make a basic custom widget that knows about it's parents
5406 // | dojo.declare("my.customClass",[dijit._Widget,dijit._Contained],{});
5407
5408 getParent: function(){
5409 // summary:
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;
5414 },
5415
5416 _getSibling: function(/*String*/ which){
5417 // summary:
5418 // Returns next or previous sibling
5419 // which:
5420 // Either "next" or "previous"
5421 // tags:
5422 // private
5423 var node = this.domNode;
5424 do{
5425 node = node[which+"Sibling"];
5426 }while(node && node.nodeType != 1);
5427 return node && dijit.byNode(node); // dijit._Widget
5428 },
5429
5430 getPreviousSibling: function(){
5431 // summary:
5432 // Returns null if this is the first child of the parent,
5433 // otherwise returns the next element sibling to the "left".
5434
5435 return this._getSibling("previous"); // dijit._Widget
5436 },
5437
5438 getNextSibling: function(){
5439 // summary:
5440 // Returns null if this is the last child of the parent,
5441 // otherwise returns the next element sibling to the "right".
5442
5443 return this._getSibling("next"); // dijit._Widget
5444 },
5445
5446 getIndexInParent: function(){
5447 // summary:
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
5451
5452 var p = this.getParent();
5453 if(!p || !p.getIndexOfChild){
5454 return -1; // int
5455 }
5456 return p.getIndexOfChild(this); // int
5457 }
5458 }
5459 );
5460
5461 }
5462
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");
5466
5467
5468
5469
5470
5471 dojo.declare("dijit.layout._LayoutWidget",
5472 [dijit._Widget, dijit._Container, dijit._Contained],
5473 {
5474 // summary:
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.
5477
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",
5483
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,
5488
5489 buildRendering: function(){
5490 this.inherited(arguments);
5491 dojo.addClass(this.domNode, "dijitContainer");
5492 },
5493
5494 startup: function(){
5495 // summary:
5496 // Called after all the widgets have been instantiated and their
5497 // dom nodes have been inserted somewhere under dojo.doc.body.
5498 //
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.
5502 //
5503 // startup() in subclasses shouldn't do anything
5504 // size related because the size of the widget hasn't been set yet.
5505
5506 if(this._started){ return; }
5507
5508 // Need to call inherited first - so that child widgets get started
5509 // up correctly
5510 this.inherited(arguments);
5511
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)
5517 this.resize();
5518
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.
5526 this.resize();
5527 });
5528 }
5529 },
5530
5531 resize: function(changeSize, resultSize){
5532 // summary:
5533 // Call this to resize a widget, or after its size has changed.
5534 // description:
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.
5539 //
5540 // If resultSize is specified it indicates the size the widget will
5541 // become after changeSize has been applied.
5542 //
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.
5547 //
5548 // If resultSize is also specified it indicates the size the widget has
5549 // become.
5550 //
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).
5555 //
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}
5560 //
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}
5566
5567 var node = this.domNode;
5568
5569 // set margin box size, unless it wasn't specified, in which case use current size
5570 if(changeSize){
5571 dojo.marginBox(node, changeSize);
5572
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"; }
5576 }
5577
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
5585 }
5586
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)
5595 });
5596 var pe = dojo._getPadExtents(node, cs);
5597 this._contentBox = {
5598 l: dojo._toPixelValue(node, cs.paddingLeft),
5599 t: dojo._toPixelValue(node, cs.paddingTop),
5600 w: bb.w - pe.w,
5601 h: bb.h - pe.h
5602 };
5603
5604 // Callback for widget to adjust size of its children
5605 this.layout();
5606 },
5607
5608 layout: function(){
5609 // summary:
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()).
5612 //
5613 // This is called after startup(), and also when the widget's size has been
5614 // changed.
5615 // tags:
5616 // protected extension
5617 },
5618
5619 _setupChild: function(/*dijit._Widget*/child){
5620 // summary:
5621 // Common setup for initial children and children which are added after startup
5622 // tags:
5623 // protected extension
5624
5625 var cls = this.baseClass + "-child "
5626 + (child.baseClass ? this.baseClass + "-" + child.baseClass : "");
5627 dojo.addClass(child.domNode, cls);
5628 },
5629
5630 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
5631 // Overrides _Container.addChild() to call _setupChild()
5632 this.inherited(arguments);
5633 if(this._started){
5634 this._setupChild(child);
5635 }
5636 },
5637
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);
5644
5645 this.inherited(arguments);
5646 }
5647 }
5648 );
5649
5650 dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
5651 // summary:
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);
5658 return {
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)
5663 };
5664 };
5665
5666 (function(){
5667 var capitalize = function(word){
5668 return word.substring(0,1).toUpperCase() + word.substring(1);
5669 };
5670
5671 var size = function(widget, dim){
5672 // size the child
5673 var newSize = widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim);
5674
5675 // record child's size
5676 if(newSize){
5677 // if the child returned it's new size then use that
5678 dojo.mixin(widget, newSize);
5679 }else{
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);
5684 }
5685 };
5686
5687 dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Widget[]*/ children,
5688 /*String?*/ changedRegionId, /*Number?*/ changedRegionSize){
5689 // summary
5690 // Layout a bunch of child dom nodes within a parent dom node
5691 // container:
5692 // parent node
5693 // dim:
5694 // {l, t, w, h} object specifying dimensions of container into which to place children
5695 // 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.
5701 // changedRegionId:
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.
5706
5707 // copy dim because we are going to modify it
5708 dim = dojo.mixin({}, dim);
5709
5710 dojo.addClass(container, "dijitLayoutContainer");
5711
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"; }));
5717
5718 // set positions/sizes
5719 dojo.forEach(children, function(child){
5720 var elm = child.domNode,
5721 pos = (child.region || child.layoutAlign);
5722
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";
5728
5729 dojo.addClass(elm, "dijitAlign" + capitalize(pos));
5730
5731 // Size adjustments to make to this child widget
5732 var sizeSetting = {};
5733
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;
5738 }
5739
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);
5745 dim.h -= child.h;
5746 if(pos == "top"){
5747 dim.t += child.h;
5748 }else{
5749 elmStyle.top = dim.t + dim.h + "px";
5750 }
5751 }else if(pos == "left" || pos == "right"){
5752 sizeSetting.h = dim.h;
5753 size(child, sizeSetting);
5754 dim.w -= child.w;
5755 if(pos == "left"){
5756 dim.l += child.w;
5757 }else{
5758 elmStyle.left = dim.l + dim.w + "px";
5759 }
5760 }else if(pos == "client" || pos == "center"){
5761 size(child, dim);
5762 }
5763 });
5764 };
5765
5766 })();
5767
5768 }
5769
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");
5773
5774
5775 dojo.declare("dijit._CssStateMixin", [], {
5776 // summary:
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.
5779 //
5780 // description:
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.
5785 //
5786 // It also sets CSS like dijitButtonDisabled based on widget semantic state.
5787 //
5788 // By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons
5789 // within the widget).
5790
5791 // cssStateNodes: [protected] Object
5792 // List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus
5793 //.
5794 // Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names
5795 // (like "dijitUpArrowButton"). Example:
5796 // | {
5797 // | "upArrowButton": "dijitUpArrowButton",
5798 // | "downArrowButton": "dijitDownArrowButton"
5799 // | }
5800 // The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it
5801 // is hovered, etc.
5802 cssStateNodes: {},
5803
5804 // hovering: [readonly] Boolean
5805 // True if cursor is over this widget
5806 hovering: false,
5807
5808 // active: [readonly] Boolean
5809 // True if mouse was pressed while over this widget, and hasn't been released yet
5810 active: false,
5811
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.
5816
5817 this.inherited(arguments);
5818
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");
5822 }, this);
5823
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"));
5827 }, this);
5828
5829 // Events on sub nodes within the widget
5830 for(var ap in this.cssStateNodes){
5831 this._trackMouseState(this[ap], this.cssStateNodes[ap]);
5832 }
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();
5836 },
5837
5838 _cssMouseEvent: function(/*Event*/ event){
5839 // summary:
5840 // Sets hovering and active properties depending on mouse state,
5841 // which triggers _setStateClass() to set appropriate CSS classes for this.domNode.
5842
5843 if(!this.disabled){
5844 switch(event.type){
5845 case "mouseenter":
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);
5849 break;
5850
5851 case "mouseleave":
5852 case "mouseout": // generated on non-IE browsers even though we connected to mouseleave
5853 this._set("hovering", false);
5854 this._set("active", false);
5855 break;
5856
5857 case "mousedown" :
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);
5867 });
5868 break;
5869 }
5870 }
5871 },
5872
5873 _setStateClass: function(){
5874 // summary:
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).
5878 //
5879 // description:
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".
5884 //
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
5891 //
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
5898
5899 // Compute new set of classes
5900 var newStateClasses = this.baseClass.split(" ");
5901
5902 function multiply(modifier){
5903 newStateClasses = newStateClasses.concat(dojo.map(newStateClasses, function(c){ return c+modifier; }), "dijit"+modifier);
5904 }
5905
5906 if(!this.isLeftToRight()){
5907 // For RTL mode we need to set an addition class like dijitTextBoxRtl.
5908 multiply("Rtl");
5909 }
5910
5911 if(this.checked){
5912 multiply("Checked");
5913 }
5914 if(this.state){
5915 multiply(this.state);
5916 }
5917 if(this.selected){
5918 multiply("Selected");
5919 }
5920
5921 if(this.disabled){
5922 multiply("Disabled");
5923 }else if(this.readOnly){
5924 multiply("ReadOnly");
5925 }else{
5926 if(this.active){
5927 multiply("Active");
5928 }else if(this.hovering){
5929 multiply("Hover");
5930 }
5931 }
5932
5933 if(this._focused){
5934 multiply("Focused");
5935 }
5936
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
5941
5942 dojo.forEach(tn.className.split(" "), function(c){ classHash[c] = true; });
5943
5944 if("_stateClasses" in this){
5945 dojo.forEach(this._stateClasses, function(c){ delete classHash[c]; });
5946 }
5947
5948 dojo.forEach(newStateClasses, function(c){ classHash[c] = true; });
5949
5950 var newClasses = [];
5951 for(var c in classHash){
5952 newClasses.push(c);
5953 }
5954 tn.className = newClasses.join(" ");
5955
5956 this._stateClasses = newStateClasses;
5957 },
5958
5959 _trackMouseState: function(/*DomNode*/ node, /*String*/ clazz){
5960 // summary:
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.
5963 // description:
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
5968 //
5969 // Note that it won't set any classes if the widget is disabled.
5970 // node: DomNode
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.
5973 // clazz: String
5974 // CSS class name (ex: dijitSliderUpArrow).
5975
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;
5979
5980 var self = this,
5981 cn = dojo.hitch(this, "connect", node);
5982
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);
5988 }
5989
5990 // Mouse
5991 cn("onmouseenter", function(){
5992 hovering = true;
5993 setClass();
5994 });
5995 cn("onmouseleave", function(){
5996 hovering = false;
5997 active = false;
5998 setClass();
5999 });
6000 cn("onmousedown", function(){
6001 active = true;
6002 setClass();
6003 });
6004 cn("onmouseup", function(){
6005 active = false;
6006 setClass();
6007 });
6008
6009 // Focus
6010 cn("onfocus", function(){
6011 focused = true;
6012 setClass();
6013 });
6014 cn("onblur", function(){
6015 focused = false;
6016 setClass();
6017 });
6018
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);
6023 }
6024 });
6025
6026 }
6027
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");
6031
6032
6033
6034
6035
6036
6037 dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
6038 {
6039 // summary:
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.
6042 //
6043 // description:
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`.
6047 //
6048 // They also share some common methods.
6049
6050 // name: [const] String
6051 // Name used when submitting form; same as "name" attribute or plain HTML elements
6052 name: "",
6053
6054 // alt: String
6055 // Corresponds to the native HTML <input> element's attribute.
6056 alt: "",
6057
6058 // value: String
6059 // Corresponds to the native HTML <input> element's attribute.
6060 value: "",
6061
6062 // type: String
6063 // Corresponds to the native HTML <input> element's attribute.
6064 type: "text",
6065
6066 // tabIndex: Integer
6067 // Order fields are traversed when user hits the tab key
6068 tabIndex: "0",
6069
6070 // disabled: Boolean
6071 // Should this widget respond to user input?
6072 // In markup, this is specified as "disabled='disabled'", or just "disabled".
6073 disabled: false,
6074
6075 // intermediateChanges: Boolean
6076 // Fires onChange for each value change or only on demand
6077 intermediateChanges: false,
6078
6079 // scrollOnFocus: Boolean
6080 // On focus, should this widget scroll into view?
6081 scrollOnFocus: true,
6082
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, {
6085 value: "focusNode",
6086 id: "focusNode",
6087 tabIndex: "focusNode",
6088 alt: "focusNode",
6089 title: "focusNode"
6090 }),
6091
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, "&quot;") + '"') : '';
6098 this.inherited(arguments);
6099 },
6100
6101 postCreate: function(){
6102 this.inherited(arguments);
6103 this.connect(this.domNode, "onmousedown", "_onMouseDown");
6104 },
6105
6106 _setDisabledAttr: function(/*Boolean*/ value){
6107 this._set("disabled", value);
6108 dojo.attr(this.focusNode, 'disabled', value);
6109 if(this.valueNode){
6110 dojo.attr(this.valueNode, 'disabled', value);
6111 }
6112 dijit.setWaiState(this.focusNode, "disabled", value);
6113
6114 if(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);
6119
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");
6127 }else{
6128 node.removeAttribute('tabIndex');
6129 }
6130 }, this);
6131 }else{
6132 if(this.tabIndex != ""){
6133 this.focusNode.setAttribute('tabIndex', this.tabIndex);
6134 }
6135 }
6136 },
6137
6138 setDisabled: function(/*Boolean*/ disabled){
6139 // summary:
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);
6143 },
6144
6145 _onFocus: function(e){
6146 if(this.scrollOnFocus){
6147 dojo.window.scrollIntoView(this.domNode);
6148 }
6149 this.inherited(arguments);
6150 },
6151
6152 isFocusable: function(){
6153 // summary:
6154 // Tells if this widget is focusable or not. Used internally by dijit.
6155 // tags:
6156 // protected
6157 return !this.disabled && this.focusNode && (dojo.style(this.domNode, "display") != "none");
6158 },
6159
6160 focus: function(){
6161 // summary:
6162 // Put focus on this widget
6163 if(!this.disabled){
6164 dijit.focus(this.focusNode);
6165 }
6166 },
6167
6168 compare: function(/*anything*/ val1, /*anything*/ val2){
6169 // summary:
6170 // Compare 2 values (as returned by get('value') for this widget).
6171 // tags:
6172 // protected
6173 if(typeof val1 == "number" && typeof val2 == "number"){
6174 return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
6175 }else if(val1 > val2){
6176 return 1;
6177 }else if(val1 < val2){
6178 return -1;
6179 }else{
6180 return 0;
6181 }
6182 },
6183
6184 onChange: function(newValue){
6185 // summary:
6186 // Callback when this widget's value is changed.
6187 // tags:
6188 // callback
6189 },
6190
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,
6196
6197 _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
6198 // summary:
6199 // Called when the value of the widget is set. Calls onChange() if appropriate
6200 // newValue:
6201 // the new value
6202 // priorityChange:
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.
6206 // tags:
6207 // private
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;
6212 }
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);
6222 }
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,
6226 function(){
6227 this._onChangeHandle = null;
6228 this.onChange(newValue);
6229 }), 0); // try to collapse multiple onChange's fired faster than can be processed
6230 }
6231 }
6232 },
6233
6234 create: function(){
6235 // Overrides _Widget.create()
6236 this.inherited(arguments);
6237 this._onChangeActive = true;
6238 },
6239
6240 destroy: function(){
6241 if(this._onChangeHandle){ // destroy called before last onChange has fired
6242 clearTimeout(this._onChangeHandle);
6243 this.onChange(this._lastValueReported);
6244 }
6245 this.inherited(arguments);
6246 },
6247
6248 setValue: function(/*String*/ value){
6249 // summary:
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);
6253 },
6254
6255 getValue: function(){
6256 // summary:
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');
6260 },
6261
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()) {
6272 this.focus();
6273 }
6274 this.disconnect(mouseUpConnector);
6275 });
6276 }
6277 }
6278 });
6279
6280 dojo.declare("dijit.form._FormValueWidget", dijit.form._FormWidget,
6281 {
6282 // summary:
6283 // Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values.
6284 // description:
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.
6288
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)
6296
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.
6301 readOnly: false,
6302
6303 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
6304 value: "",
6305 readOnly: "focusNode"
6306 }),
6307
6308 _setReadOnlyAttr: function(/*Boolean*/ value){
6309 dojo.attr(this.focusNode, 'readOnly', value);
6310 dijit.setWaiState(this.focusNode, "readonly", value);
6311 this._set("readOnly", value);
6312 },
6313
6314 postCreate: function(){
6315 this.inherited(arguments);
6316
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);
6319 }
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;
6324 }
6325 },
6326
6327 _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
6328 // summary:
6329 // Hook so set('value', value) works.
6330 // description:
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);
6335 },
6336
6337 _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
6338 // summary:
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);
6343 },
6344
6345 undo: function(){
6346 // summary:
6347 // Restore the value to the last value passed to onChange
6348 this._setValueAttr(this._lastValueReported, false);
6349 },
6350
6351 reset: function(){
6352 // summary:
6353 // Reset the widget's value to what it was at initialization time
6354 this._hasBeenBlurred = false;
6355 this._setValueAttr(this._resetValue, true);
6356 },
6357
6358 _onKeyDown: function(e){
6359 if(e.keyCode == dojo.keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){
6360 var te;
6361 if(dojo.isIE){
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);
6367 }
6368 }
6369 },
6370
6371 _layoutHackIE7: function(){
6372 // summary:
6373 // Work around table sizing bugs on IE7 by forcing redraw
6374
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
6380 var _this = this;
6381 while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet
6382 (function ping(){
6383 var disconnectHandle = _this.connect(parent, "onscroll",
6384 function(e){
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
6388 }
6389 );
6390 })();
6391 parent = parent.parentNode;
6392 }
6393 }
6394 }
6395 });
6396
6397 }
6398
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");
6402
6403
6404
6405
6406
6407
6408
6409
6410
6411 /*=====
6412 dijit.dijit = {
6413 // summary:
6414 // A roll-up for common dijit methods
6415 // description:
6416 // A rollup file for the build system including the core and common
6417 // dijit files.
6418 //
6419 // example:
6420 // | <script type="text/javascript" src="js/dojo/dijit/dijit.js"></script>
6421 //
6422 };
6423 =====*/
6424
6425 // All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require)
6426
6427 // And some other stuff that we tend to pull in all the time anyway
6428
6429 }
6430
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");
6434
6435
6436 dojo.declare("dojo.fx.Toggler", null, {
6437 // summary:
6438 // A simple `dojo.Animation` toggler API.
6439 //
6440 // description:
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`).
6446 //
6447 // example:
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"
6454 // | });
6455 // | t.show(100); // delay showing for 100ms
6456 // | // ...time passes...
6457 // | t.hide();
6458
6459 // node: DomNode
6460 // the node to target for the showing and hiding animations
6461 node: null,
6462
6463 // showFunc: Function
6464 // The function that returns the `dojo.Animation` to show the node
6465 showFunc: dojo.fadeIn,
6466
6467 // hideFunc: Function
6468 // The function that returns the `dojo.Animation` to hide the node
6469 hideFunc: dojo.fadeOut,
6470
6471 // showDuration:
6472 // Time in milliseconds to run the show Animation
6473 showDuration: 200,
6474
6475 // hideDuration:
6476 // Time in milliseconds to run the hide Animation
6477 hideDuration: 200,
6478
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
6481 // middle.
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
6485
6486 /*=====
6487 _showArgs: null,
6488 _showAnim: null,
6489
6490 _hideArgs: null,
6491 _hideAnim: null,
6492
6493 _isShowing: false,
6494 _isHiding: false,
6495 =====*/
6496
6497 constructor: function(args){
6498 var _t = this;
6499
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);
6506
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);
6511
6512 dojo.connect(_t.showAnim, "beforeBegin", dojo.hitch(_t.hideAnim, "stop", true));
6513 dojo.connect(_t.hideAnim, "beforeBegin", dojo.hitch(_t.showAnim, "stop", true));
6514 },
6515
6516 show: function(delay){
6517 // summary: Toggle the node to showing
6518 // delay: Integer?
6519 // Ammount of time to stall playing the show animation
6520 return this.showAnim.play(delay || 0);
6521 },
6522
6523 hide: function(delay){
6524 // summary: Toggle the node to hidden
6525 // delay: Integer?
6526 // Ammount of time to stall playing the hide animation
6527 return this.hideAnim.play(delay || 0);
6528 }
6529 });
6530
6531 }
6532
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");
6536
6537
6538
6539 /*=====
6540 dojo.fx = {
6541 // summary: Effects library on top of Base animations
6542 };
6543 =====*/
6544 (function(){
6545
6546 var d = dojo,
6547 _baseObj = {
6548 _fire: function(evt, args){
6549 if(this[evt]){
6550 this[evt].apply(this, args||[]);
6551 }
6552 return this;
6553 }
6554 };
6555
6556 var _chain = function(animations){
6557 this._index = -1;
6558 this._animations = animations||[];
6559 this._current = this._onAnimateCtx = this._onEndCtx = null;
6560
6561 this.duration = 0;
6562 d.forEach(this._animations, function(a){
6563 this.duration += a.duration;
6564 if(a.delay){ this.duration += a.delay; }
6565 }, this);
6566 };
6567 d.extend(_chain, {
6568 _onAnimate: function(){
6569 this._fire("onAnimate", arguments);
6570 },
6571 _onEnd: function(){
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");
6577 }else{
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);
6583 }
6584 },
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");
6590 }),
6591 onBegin = d.connect(this._current, "onBegin", this, function(arg){
6592 this._fire("onBegin", arguments);
6593 }),
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);
6599 });
6600 if(this._onAnimateCtx){
6601 d.disconnect(this._onAnimateCtx);
6602 }
6603 this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate");
6604 if(this._onEndCtx){
6605 d.disconnect(this._onEndCtx);
6606 }
6607 this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd");
6608 this._current.play.apply(this._current, arguments);
6609 return this;
6610 },
6611 pause: function(){
6612 if(this._current){
6613 var e = d.connect(this._current, "onPause", this, function(arg){
6614 this._fire("onPause", arguments);
6615 d.disconnect(e);
6616 });
6617 this._current.pause();
6618 }
6619 return this;
6620 },
6621 gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){
6622 this.pause();
6623 var offset = this.duration * percent;
6624 this._current = null;
6625 d.some(this._animations, function(a){
6626 if(a.duration <= offset){
6627 this._current = a;
6628 return true;
6629 }
6630 offset -= a.duration;
6631 return false;
6632 });
6633 if(this._current){
6634 this._current.gotoPercent(offset / this._current.duration, andPlay);
6635 }
6636 return this;
6637 },
6638 stop: function(/*boolean?*/ gotoEnd){
6639 if(this._current){
6640 if(gotoEnd){
6641 for(; this._index + 1 < this._animations.length; ++this._index){
6642 this._animations[this._index].stop(true);
6643 }
6644 this._current = this._animations[this._index];
6645 }
6646 var e = d.connect(this._current, "onStop", this, function(arg){
6647 this._fire("onStop", arguments);
6648 d.disconnect(e);
6649 });
6650 this._current.stop();
6651 }
6652 return this;
6653 },
6654 status: function(){
6655 return this._current ? this._current.status() : "stopped";
6656 },
6657 destroy: function(){
6658 if(this._onAnimateCtx){ d.disconnect(this._onAnimateCtx); }
6659 if(this._onEndCtx){ d.disconnect(this._onEndCtx); }
6660 }
6661 });
6662 d.extend(_chain, _baseObj);
6663
6664 dojo.fx.chain = function(/*dojo.Animation[]*/ animations){
6665 // summary:
6666 // Chain a list of `dojo.Animation`s to run in sequence
6667 //
6668 // description:
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)
6674 //
6675 // example:
6676 // Once `node` is faded out, fade in `otherNode`
6677 // | dojo.fx.chain([
6678 // | dojo.fadeIn({ node:node }),
6679 // | dojo.fadeOut({ node:otherNode })
6680 // | ]).play();
6681 //
6682 return new _chain(animations) // dojo.Animation
6683 };
6684
6685 var _combine = function(animations){
6686 this._animations = animations||[];
6687 this._connects = [];
6688 this._finished = 0;
6689
6690 this.duration = 0;
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"));
6696 }, this);
6697
6698 this._pseudoAnimation = new d.Animation({curve: [0, 1], duration: this.duration});
6699 var self = this;
6700 d.forEach(["beforeBegin", "onBegin", "onPlay", "onAnimate", "onPause", "onStop", "onEnd"],
6701 function(evt){
6702 self._connects.push(d.connect(self._pseudoAnimation, evt,
6703 function(){ self._fire(evt, arguments); }
6704 ));
6705 }
6706 );
6707 };
6708 d.extend(_combine, {
6709 _doAction: function(action, args){
6710 d.forEach(this._animations, function(a){
6711 a[action].apply(a, args);
6712 });
6713 return this;
6714 },
6715 _onEnd: function(){
6716 if(++this._finished > this._animations.length){
6717 this._fire("onEnd");
6718 }
6719 },
6720 _call: function(action, args){
6721 var t = this._pseudoAnimation;
6722 t[action].apply(t, args);
6723 },
6724 play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
6725 this._finished = 0;
6726 this._doAction("play", arguments);
6727 this._call("play", arguments);
6728 return this;
6729 },
6730 pause: function(){
6731 this._doAction("pause", arguments);
6732 this._call("pause", arguments);
6733 return this;
6734 },
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);
6739 });
6740 this._call("gotoPercent", arguments);
6741 return this;
6742 },
6743 stop: function(/*boolean?*/ gotoEnd){
6744 this._doAction("stop", arguments);
6745 this._call("stop", arguments);
6746 return this;
6747 },
6748 status: function(){
6749 return this._pseudoAnimation.status();
6750 },
6751 destroy: function(){
6752 d.forEach(this._connects, dojo.disconnect);
6753 }
6754 });
6755 d.extend(_combine, _baseObj);
6756
6757 dojo.fx.combine = function(/*dojo.Animation[]*/ animations){
6758 // summary:
6759 // Combine a list of `dojo.Animation`s to run in parallel
6760 //
6761 // description:
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.
6765 //
6766 // example:
6767 // Fade out `node` while fading in `otherNode` simultaneously
6768 // | dojo.fx.combine([
6769 // | dojo.fadeIn({ node:node }),
6770 // | dojo.fadeOut({ node:otherNode })
6771 // | ]).play();
6772 //
6773 // example:
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 })
6778 // | ]);
6779 // | dojo.connect(anim, "onEnd", function(){
6780 // | // overall animation is done.
6781 // | });
6782 // | anim.play(); // play the animation
6783 //
6784 return new _combine(animations); // dojo.Animation
6785 };
6786
6787 dojo.fx.wipeIn = function(/*Object*/ args){
6788 // summary:
6789 // Expand a node to it's natural height.
6790 //
6791 // description:
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.
6796 //
6797 // args: Object
6798 // A hash-map of standard `dojo.Animation` constructor properties
6799 // (such as easing: node: duration: and so on)
6800 //
6801 // example:
6802 // | dojo.fx.wipeIn({
6803 // | node:"someId"
6804 // | }).play()
6805 var node = args.node = d.byId(args.node), s = node.style, o;
6806
6807 var anim = d.animateProperty(d.mixin({
6808 properties: {
6809 height: {
6810 // wrapped in functions so we wait till the last second to query (in case value has changed)
6811 start: function(){
6812 // start at current [computed] height, but use 1px rather than 0
6813 // because 0 causes IE to display the whole panel
6814 o = s.overflow;
6815 s.overflow = "hidden";
6816 if(s.visibility == "hidden" || s.display == "none"){
6817 s.height = "1px";
6818 s.display = "";
6819 s.visibility = "";
6820 return 1;
6821 }else{
6822 var height = d.style(node, "height");
6823 return Math.max(height, 1);
6824 }
6825 },
6826 end: function(){
6827 return node.scrollHeight;
6828 }
6829 }
6830 }
6831 }, args));
6832
6833 d.connect(anim, "onEnd", function(){
6834 s.height = "auto";
6835 s.overflow = o;
6836 });
6837
6838 return anim; // dojo.Animation
6839 };
6840
6841 dojo.fx.wipeOut = function(/*Object*/ args){
6842 // summary:
6843 // Shrink a node to nothing and hide it.
6844 //
6845 // description:
6846 // Returns an animation that will shrink node defined in "args"
6847 // from it's current height to 1px, and then hide it.
6848 //
6849 // args: Object
6850 // A hash-map of standard `dojo.Animation` constructor properties
6851 // (such as easing: node: duration: and so on)
6852 //
6853 // example:
6854 // | dojo.fx.wipeOut({ node:"someId" }).play()
6855
6856 var node = args.node = d.byId(args.node), s = node.style, o;
6857
6858 var anim = d.animateProperty(d.mixin({
6859 properties: {
6860 height: {
6861 end: 1 // 0 causes IE to display the whole panel
6862 }
6863 }
6864 }, args));
6865
6866 d.connect(anim, "beforeBegin", function(){
6867 o = s.overflow;
6868 s.overflow = "hidden";
6869 s.display = "";
6870 });
6871 d.connect(anim, "onEnd", function(){
6872 s.overflow = o;
6873 s.height = "auto";
6874 s.display = "none";
6875 });
6876
6877 return anim; // dojo.Animation
6878 };
6879
6880 dojo.fx.slideTo = function(/*Object*/ args){
6881 // summary:
6882 // Slide a node to a new top/left position
6883 //
6884 // description:
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).
6888 //
6889 // args: Object
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.
6893 //
6894 // example:
6895 // | dojo.fx.slideTo({ node: node, left:"40", top:"50", units:"px" }).play()
6896
6897 var node = args.node = d.byId(args.node),
6898 top = null, left = null;
6899
6900 var init = (function(n){
6901 return function(){
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);
6908 top = ret.y;
6909 left = ret.x;
6910 n.style.position="absolute";
6911 n.style.top=top+"px";
6912 n.style.left=left+"px";
6913 }
6914 };
6915 })(node);
6916 init();
6917
6918 var anim = d.animateProperty(d.mixin({
6919 properties: {
6920 top: args.top || 0,
6921 left: args.left || 0
6922 }
6923 }, args));
6924 d.connect(anim, "beforeBegin", anim, init);
6925
6926 return anim; // dojo.Animation
6927 };
6928
6929 })();
6930
6931 }
6932
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");
6936
6937
6938
6939 /*=====
6940 dojo["NodeList-fx"] = {
6941 // summary: Adds dojo.fx animation support to dojo.query()
6942 };
6943 =====*/
6944
6945 dojo.extend(dojo.NodeList, {
6946 _anim: function(obj, method, args){
6947 args = 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);
6953 })
6954 );
6955 return args.auto ? a.play() && this : a; // dojo.Animation|dojo.NodeList
6956 },
6957
6958 wipeIn: function(args){
6959 // summary:
6960 // wipe in all elements of this NodeList via `dojo.fx.wipeIn`
6961 //
6962 // args: Object?
6963 // Additional dojo.Animation arguments to mix into this set with the addition of
6964 // an `auto` parameter.
6965 //
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
6970 //
6971 // example:
6972 // Fade in all tables with class "blah":
6973 // | dojo.query("table.blah").wipeIn().play();
6974 //
6975 // example:
6976 // Utilizing `auto` to get the NodeList back:
6977 // | dojo.query(".titles").wipeIn({ auto:true }).onclick(someFunction);
6978 //
6979 return this._anim(dojo.fx, "wipeIn", args); // dojo.Animation|dojo.NodeList
6980 },
6981
6982 wipeOut: function(args){
6983 // summary:
6984 // wipe out all elements of this NodeList via `dojo.fx.wipeOut`
6985 //
6986 // args: Object?
6987 // Additional dojo.Animation arguments to mix into this set with the addition of
6988 // an `auto` parameter.
6989 //
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
6994 //
6995 // example:
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
6999 },
7000
7001 slideTo: function(args){
7002 // summary:
7003 // slide all elements of the node list to the specified place via `dojo.fx.slideTo`
7004 //
7005 // args: Object?
7006 // Additional dojo.Animation arguments to mix into this set with the addition of
7007 // an `auto` parameter.
7008 //
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
7013 //
7014 // example:
7015 // | Move all tables with class "blah" to 300/300:
7016 // | dojo.query("table.blah").slideTo({
7017 // | left: 40,
7018 // | top: 50
7019 // | }).play();
7020 return this._anim(dojo.fx, "slideTo", args); // dojo.Animation|dojo.NodeList
7021 },
7022
7023
7024 fadeIn: function(args){
7025 // summary:
7026 // fade in all elements of this NodeList via `dojo.fadeIn`
7027 //
7028 // args: Object?
7029 // Additional dojo.Animation arguments to mix into this set with the addition of
7030 // an `auto` parameter.
7031 //
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
7036 //
7037 // example:
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
7041 },
7042
7043 fadeOut: function(args){
7044 // summary:
7045 // fade out all elements of this NodeList via `dojo.fadeOut`
7046 //
7047 // args: Object?
7048 // Additional dojo.Animation arguments to mix into this set with the addition of
7049 // an `auto` parameter.
7050 //
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
7055 //
7056 // example:
7057 // Fade out all elements with class "zork":
7058 // | dojo.query(".zork").fadeOut().play();
7059 // example:
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(){ /*...*/ });
7063 // | fo.play();
7064 // example:
7065 // Using `auto`:
7066 // | dojo.query("li").fadeOut({ auto:true }).filter(filterFn).forEach(doit);
7067 //
7068 return this._anim(dojo, "fadeOut", args); // dojo.Animation|dojo.NodeList
7069 },
7070
7071 animateProperty: function(args){
7072 // summary:
7073 // Animate all elements of this NodeList across the properties specified.
7074 // syntax identical to `dojo.animateProperty`
7075 //
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
7080 //
7081 // example:
7082 // | dojo.query(".zork").animateProperty({
7083 // | duration: 500,
7084 // | properties: {
7085 // | color: { start: "black", end: "white" },
7086 // | left: { end: 300 }
7087 // | }
7088 // | }).play();
7089 //
7090 // example:
7091 // | dojo.query(".grue").animateProperty({
7092 // | auto:true,
7093 // | properties: {
7094 // | height:240
7095 // | }
7096 // | }).onclick(handler);
7097 return this._anim(dojo, "animateProperty", args); // dojo.Animation|dojo.NodeList
7098 },
7099
7100 anim: function( /*Object*/ properties,
7101 /*Integer?*/ duration,
7102 /*Function?*/ easing,
7103 /*Function?*/ onEnd,
7104 /*Integer?*/ delay){
7105 // summary:
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.
7116 // onEnd: Function?
7117 // A function to be called when the animation ends
7118 // delay:
7119 // how long to delay playing the returned animation
7120 // example:
7121 // Another way to fade out:
7122 // | dojo.query(".thinger").anim({ opacity: 0 });
7123 // example:
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({
7130 node: item,
7131 properties: properties,
7132 duration: duration||350,
7133 easing: easing
7134 });
7135 })
7136 );
7137 if(onEnd){
7138 dojo.connect(canim, "onEnd", onEnd);
7139 }
7140 return canim.play(delay||0); // dojo.Animation
7141 }
7142 });
7143
7144 }
7145
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");
7149
7150 dojo.getObject("colors", true, dojo);
7151
7152 //TODO: this module appears to break naming conventions
7153
7154 /*=====
7155 dojo.colors = {
7156 // summary: Color utilities
7157 }
7158 =====*/
7159
7160 (function(){
7161 // this is a standard conversion prescribed by the CSS3 Color Module
7162 var hue2rgb = function(m1, m2, h){
7163 if(h < 0){ ++h; }
7164 if(h > 1){ --h; }
7165 var h6 = 6 * 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; }
7169 return m1;
7170 };
7171
7172 dojo.colorFromRgb = function(/*String*/ color, /*dojo.Color?*/ obj){
7173 // summary:
7174 // get rgb(a) array from css-style color declarations
7175 // description:
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]+)\)/);
7179 if(m){
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)){
7182 var r = c[0];
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;
7187 });
7188 if(l == 4){ a[3] = c[3]; }
7189 return dojo.colorFromArray(a, obj); // dojo.Color
7190 }
7191 return dojo.colorFromArray(c, obj); // dojo.Color
7192 }
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,
7201 m1 = 2 * L - m2;
7202 a = [
7203 hue2rgb(m1, m2, H + 1 / 3) * 256,
7204 hue2rgb(m1, m2, H) * 256,
7205 hue2rgb(m1, m2, H - 1 / 3) * 256,
7206 1
7207 ];
7208 if(l == 4){ a[3] = c[3]; }
7209 return dojo.colorFromArray(a, obj); // dojo.Color
7210 }
7211 }
7212 return null; // dojo.Color
7213 };
7214
7215 var confine = function(c, low, high){
7216 // summary:
7217 // sanitize a color component by making sure it is a number,
7218 // and clamping it to valid values
7219 c = Number(c);
7220 return isNaN(c) ? high : c < low ? low : c > high ? high : c; // Number
7221 };
7222
7223 dojo.Color.prototype.sanitize = function(){
7224 // summary: makes sure that the object has correct attributes
7225 var t = this;
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
7231 };
7232 })();
7233
7234
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]);
7238 };
7239
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],
7250 brown: [165,42,42],
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],
7259 cyan: [0,255,255],
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],
7271 darkred: [139,0,0],
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],
7289 gold: [255,215,0],
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],
7296 indigo: [75,0,130],
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],
7346 peru: [205,133,63],
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],
7365 tan: [210,180,140],
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]
7374 });
7375
7376 }
7377
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");
7381
7382 dojo.getObject("i18n", true, dojo);
7383
7384 /*=====
7385 dojo.i18n = {
7386 // summary: Utility classes to enable loading of resources for internationalization (i18n)
7387 };
7388 =====*/
7389
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){
7392 // summary:
7393 // Returns an Object containing the localization for a given resource
7394 // bundle in a package, matching the specified locale.
7395 // description:
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.
7406 // packageName:
7407 // package which is associated with this resource
7408 // bundleName:
7409 // the base filename of the resource bundle (without the ".js" suffix)
7410 // locale:
7411 // the variant to load (optional). By default, the locale defined by
7412 // the host environment: dojo.locale
7413
7414 locale = dojo.i18n.normalizeLocale(locale);
7415
7416 // look for nearest locale match
7417 var elements = locale.split('-');
7418 var module = [packageName,"nls",bundleName].join('.');
7419 var bundle = dojo._loadedModules[module];
7420 if(bundle){
7421 var localization;
7422 for(var i = elements.length; i > 0; i--){
7423 var loc = elements.slice(0, i).join('_');
7424 if(bundle[loc]){
7425 localization = bundle[loc];
7426 break;
7427 }
7428 }
7429 if(!localization){
7430 localization = bundle.ROOT;
7431 }
7432
7433 // make a singleton prototype so that the caller won't accidentally change the values globally
7434 if(localization){
7435 var clazz = function(){};
7436 clazz.prototype = localization;
7437 return new clazz(); // Object
7438 }
7439 }
7440
7441 throw new Error("Bundle not found: " + bundleName + " in " + packageName+" , locale=" + locale);
7442 };
7443
7444 dojo.i18n.normalizeLocale = function(/*String?*/locale){
7445 // summary:
7446 // Returns canonical form of locale, as used by Dojo.
7447 //
7448 // description:
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.
7452
7453 var result = locale ? locale.toLowerCase() : dojo.locale;
7454 if(result == "root"){
7455 result = "ROOT";
7456 }
7457 return result; // String
7458 };
7459
7460 dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundleName, /*String?*/locale, /*String?*/availableFlatLocales){
7461 // summary:
7462 // See dojo.requireLocalization()
7463 // description:
7464 // Called by the bootstrap, but factored out so that it is only
7465 // included in the build when needed.
7466
7467 var targetLocale = dojo.i18n.normalizeLocale(locale);
7468 var bundlePackage = [moduleName, "nls", bundleName].join(".");
7469 // NOTE:
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.
7479
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];
7491 }
7492 }
7493 }
7494 if(!bestLocale){
7495 bestLocale = "ROOT";
7496 }
7497 }
7498
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;
7503 if(bundle){
7504 if(dojo.config.localizationComplete && bundle._built){return;}
7505 var jsLoc = tempLocale.replace(/-/g, '_');
7506 var translationPackage = bundlePackage+"."+jsLoc;
7507 localizedBundle = dojo._loadedModules[translationPackage];
7508 }
7509
7510 if(!localizedBundle){
7511 bundle = dojo["provide"](bundlePackage);
7512 var syms = dojo._getModuleSymbols(moduleName);
7513 var modpath = syms.concat("nls").join("/");
7514 var parent;
7515
7516 dojo.i18n._searchLocalePath(tempLocale, availableFlatLocales, function(loc){
7517 var jsLoc = loc.replace(/-/g, '_');
7518 var translationPackage = bundlePackage + "." + jsLoc;
7519 var loaded = false;
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]; }
7534 });
7535 }else{
7536 loaded = true;
7537 }
7538 if(loaded && bundle[jsLoc]){
7539 parent = bundle[jsLoc];
7540 }else{
7541 bundle[jsLoc] = parent;
7542 }
7543
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.
7547 return true;
7548 }
7549 });
7550 }
7551
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, '_')];
7556 }
7557 };
7558
7559 (function(){
7560 // If other locales are used, dojo.requireLocalization should load them as
7561 // well, by default.
7562 //
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.
7566
7567 var extra = dojo.config.extraLocale;
7568 if(extra){
7569 if(!extra instanceof Array){
7570 extra = [extra];
7571 }
7572
7573 var req = dojo.i18n._requireLocalization;
7574 dojo.i18n._requireLocalization = function(m, b, locale, availableFlatLocales){
7575 req(m,b,locale, availableFlatLocales);
7576 if(locale){return;}
7577 for(var i=0; i<extra.length; i++){
7578 req(m,b,extra[i], availableFlatLocales);
7579 }
7580 };
7581 }
7582 })();
7583
7584 dojo.i18n._searchLocalePath = function(/*String*/locale, /*Boolean*/down, /*Function*/searchFunc){
7585 // summary:
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".
7590
7591 locale = dojo.i18n.normalizeLocale(locale);
7592
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('-'));
7597 }
7598 searchlist.push(false);
7599 if(down){searchlist.reverse();}
7600
7601 for(var j = searchlist.length - 1; j >= 0; j--){
7602 var loc = searchlist[j] || "ROOT";
7603 var stop = searchFunc(loc);
7604 if(stop){ break; }
7605 }
7606 };
7607
7608 dojo.i18n._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated){
7609 // summary:
7610 // Load built, flattened resource bundles, if available for all
7611 // locales used in the page. Only called by built layer files.
7612
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
7620 }
7621 }
7622 return false; // Boolean
7623 });
7624 }
7625 preload();
7626 var extra = dojo.config.extraLocale||[];
7627 for(var i=0; i<extra.length; i++){
7628 preload(extra[i]);
7629 }
7630 };
7631
7632 }
7633
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");
7637
7638
7639
7640 dojo.declare("dijit._PaletteMixin",
7641 [dijit._CssStateMixin],
7642 {
7643 // summary:
7644 // A keyboard accessible palette, for picking a color/emoticon/etc.
7645 // description:
7646 // A mixin for a grid showing various entities, so the user can pick a certain entity.
7647
7648 // defaultTimeout: Number
7649 // Number of milliseconds before a held key or button becomes typematic
7650 defaultTimeout: 500,
7651
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,
7657
7658 // value: String
7659 // Currently selected color/emoticon/etc.
7660 value: null,
7661
7662 // _selectedCell: [private] Integer
7663 // Index of the currently selected cell. Initially, none selected
7664 _selectedCell: -1,
7665
7666 /*=====
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,
7672 =====*/
7673
7674 /*=====
7675 // _xDim: [protected] Integer
7676 // This is the number of cells horizontally across.
7677 _xDim: null,
7678 =====*/
7679
7680 /*=====
7681 // _yDim: [protected] Integer
7682 // This is the number of cells vertically down.
7683 _yDim: null,
7684 =====*/
7685
7686 // tabIndex: String
7687 // Widget tab index.
7688 tabIndex: "0",
7689
7690 // cellClass: [protected] String
7691 // CSS class applied to each cell in the palette
7692 cellClass: "dijitPaletteCell",
7693
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
7697 dyeClass: '',
7698
7699 _preparePalette: function(choices, titles, dyeClassObj) {
7700 // summary:
7701 // Subclass must call _preparePalette() from postCreate(), passing in the tooltip
7702 // for each cell
7703 // choices: String[][]
7704 // id's for each cell of the palette, used to create Dye JS object for each cell
7705 // titles: String[]
7706 // Localized tooltip for each cell
7707 // dyeClassObj: Constructor?
7708 // If specified, use this constructor rather than this.dyeClass
7709
7710 this._cells = [];
7711 var url = this._blankGif;
7712
7713 dyeClassObj = dyeClassObj || dojo.getObject(this.dyeClass);
7714
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];
7719 if(value){
7720 var cellObject = new dyeClassObj(value, row, col);
7721
7722 var cellNode = dojo.create("td", {
7723 "class": this.cellClass,
7724 tabIndex: "-1",
7725 title: titles[value]
7726 });
7727
7728 // prepare cell inner structure
7729 cellObject.fillCell(cellNode, url);
7730
7731 this.connect(cellNode, "ondijitclick", "_onCellClick");
7732 this._trackMouseState(cellNode, this.cellClass);
7733
7734 dojo.place(cellNode, rowNode);
7735
7736 cellNode.index = this._cells.length;
7737
7738 // save cell info into _cells
7739 this._cells.push({node:cellNode, dye:cellObject});
7740 }
7741 }
7742 }
7743 this._xDim = choices[0].length;
7744 this._yDim = choices.length;
7745
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.
7751
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
7759 };
7760 for(var key in keyIncrementMap){
7761 this._connects.push(
7762 dijit.typematic.addKeyListener(
7763 this.domNode,
7764 {charOrCode:dojo.keys[key], ctrlKey:false, altKey:false, shiftKey:false},
7765 this,
7766 function(){
7767 var increment = keyIncrementMap[key];
7768 return function(count){ this._navigateByKey(increment, count); };
7769 }(),
7770 this.timeoutChangeRate,
7771 this.defaultTimeout
7772 )
7773 );
7774 }
7775 },
7776
7777 postCreate: function(){
7778 this.inherited(arguments);
7779
7780 // Set initial navigable node.
7781 this._setCurrent(this._cells[0].node);
7782 },
7783
7784 focus: function(){
7785 // summary:
7786 // Focus this widget. Puts focus on the most recently focused cell.
7787
7788 // The cell already has tabIndex set, just need to set CSS and focus it
7789 dijit.focus(this._currentFocus);
7790 },
7791
7792 _onCellClick: function(/*Event*/ evt){
7793 // summary:
7794 // Handler for click, enter key & space key. Selects the cell.
7795 // evt:
7796 // The event.
7797 // tags:
7798 // private
7799
7800 var target = evt.currentTarget,
7801 value = this._getDye(target).getValue();
7802
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);
7812 }));
7813
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");
7817
7818 dojo.stopEvent(evt);
7819 },
7820
7821 _setCurrent: function(/*DomNode*/ node){
7822 // summary:
7823 // Sets which node is the focused cell.
7824 // description:
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.
7828 //
7829 // After calling this method, arrow key handlers and mouse click handlers
7830 // should focus the cell in a setTimeout().
7831 // tags:
7832 // protected
7833 if("_currentFocus" in this){
7834 // Remove tabIndex on old cell
7835 dojo.attr(this._currentFocus, "tabIndex", "-1");
7836 }
7837
7838 // Set tabIndex of new cell
7839 this._currentFocus = node;
7840 if(node){
7841 dojo.attr(node, "tabIndex", this.tabIndex);
7842 }
7843 },
7844
7845 _setValueAttr: function(value, priorityChange){
7846 // summary:
7847 // This selects a cell. It triggers the onChange event.
7848 // value: String value of the cell to select
7849 // tags:
7850 // protected
7851 // priorityChange:
7852 // Optional parameter used to tell the select whether or not to fire
7853 // onChange event.
7854
7855 // clear old selected cell
7856 if(this._selectedCell >= 0){
7857 dojo.removeClass(this._cells[this._selectedCell].node, "dijitPaletteCellSelected");
7858 }
7859 this._selectedCell = -1;
7860
7861 // search for cell matching specified value
7862 if(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");
7867 break;
7868 }
7869 }
7870 }
7871
7872 // record new value, or null if no matching cell
7873 this._set("value", this._selectedCell >= 0 ? value : null);
7874
7875 if(priorityChange || priorityChange === undefined){
7876 this.onChange(value);
7877 }
7878 },
7879
7880 onChange: function(value){
7881 // summary:
7882 // Callback when a cell is selected.
7883 // value: String
7884 // Value corresponding to cell.
7885 },
7886
7887 _navigateByKey: function(increment, typeCount){
7888 // summary:
7889 // This is the callback for typematic.
7890 // It changes the focus and the highlighed cell.
7891 // increment:
7892 // How much the key is navigated.
7893 // typeCount:
7894 // How many times typematic has fired.
7895 // tags:
7896 // private
7897
7898 // typecount == -1 means the key is released.
7899 if(typeCount == -1){ return; }
7900
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);
7905
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);
7909 }
7910 },
7911
7912 _getDye: function(/*DomNode*/ cell){
7913 // summary:
7914 // Get JS object for given cell DOMNode
7915
7916 return this._cells[cell.index].dye;
7917 }
7918 });
7919
7920 /*=====
7921 dojo.declare("dijit.Dye",
7922 null,
7923 {
7924 // summary:
7925 // Interface for the JS Object associated with a palette cell (i.e. DOMNode)
7926
7927 constructor: function(alias, row, col){
7928 // summary:
7929 // Initialize according to value or alias like "white"
7930 // alias: String
7931 },
7932
7933 getValue: function(){
7934 // summary:
7935 // Return "value" of cell; meaning of "value" varies by subclass.
7936 // description:
7937 // For example color hex value, emoticon ascii value etc, entity hex value.
7938 },
7939
7940 fillCell: function(cell, blankGif){
7941 // summary:
7942 // Add cell DOMNode inner structure
7943 // cell: DomNode
7944 // The surrounding cell
7945 // blankGif: String
7946 // URL for blank cell image
7947 }
7948 }
7949 );
7950 =====*/
7951
7952 }
7953
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");
7957
7958
7959
7960
7961
7962
7963
7964
7965
7966 dojo.declare("dijit.ColorPalette",
7967 [dijit._Widget, dijit._Templated, dijit._PaletteMixin],
7968 {
7969 // summary:
7970 // A keyboard accessible color-picking widget
7971 // description:
7972 // Grid showing various colors, so the user can pick a certain color.
7973 // Can be used standalone, or as a popup.
7974 //
7975 // example:
7976 // | <div dojoType="dijit.ColorPalette"></div>
7977 //
7978 // example:
7979 // | var picker = new dijit.ColorPalette({ },srcNode);
7980 // | picker.startup();
7981
7982
7983 // palette: [const] String
7984 // Size of grid, either "7x10" or "3x4".
7985 palette: "7x10",
7986
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.
7991 _palettes: {
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"]],
7999
8000 "3x4": [["white", "lime", "green", "blue"],
8001 ["silver", "yellow", "fuchsia", "navy"],
8002 ["gray", "red", "purple", "black"]]
8003 },
8004
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"),
8008
8009 baseClass: "dijitColorPalette",
8010
8011 buildRendering: function(){
8012 // Instantiate the template, which makes a skeleton into which we'll insert a bunch of
8013 // <img> nodes
8014 this.inherited(arguments);
8015
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
8024 })
8025 );
8026 }
8027 });
8028
8029 dojo.declare("dijit._Color", dojo.Color, {
8030 // summary:
8031 // Object associated with each cell in a ColorPalette palette.
8032 // Implements dijit.Dye.
8033
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.
8037 template:
8038 "<span class='dijitInline dijitPaletteImg'>" +
8039 "<img src='${blankGif}' alt='${alt}' class='dijitColorPaletteSwatch' style='background-color: ${color}'/>" +
8040 "</span>",
8041
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
8044 hcTemplate:
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}'/>" +
8047 "</span>",
8048
8049 // _imagePaths: [protected] Map
8050 // This is stores the path to the palette images used for high-contrast mode display
8051 _imagePaths: {
8052 "7x10": dojo.moduleUrl("dijit.themes", "a11y/colors7x10.png"),
8053 "3x4": dojo.moduleUrl("dijit.themes", "a11y/colors3x4.png")
8054 },
8055
8056 constructor: function(/*String*/alias, /*Number*/ row, /*Number*/ col){
8057 this._alias = alias;
8058 this._row = row;
8059 this._col = col;
8060 this.setColor(dojo.Color.named[alias]);
8061 },
8062
8063 getValue: function(){
8064 // summary:
8065 // Note that although dijit._Color is initialized with a value like "white" getValue() always
8066 // returns a hex value
8067 return this.toHex();
8068 },
8069
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(),
8074 blankGif: blankGif,
8075 alt: this._alias,
8076
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"
8082 });
8083
8084 dojo.place(html, cell);
8085 }
8086 });
8087
8088 }
8089
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");
8093
8094 dojo.getObject("dnd", true, dojo);
8095
8096 dojo.dnd.getCopyKeyState = dojo.isCopyKey;
8097
8098 dojo.dnd._uniqueId = 0;
8099 dojo.dnd.getUniqueId = function(){
8100 // summary:
8101 // returns a unique string for use with any DOM element
8102 var id;
8103 do{
8104 id = dojo._scopeName + "Unique" + (++dojo.dnd._uniqueId);
8105 }while(dojo.byId(id));
8106 return id;
8107 };
8108
8109 dojo.dnd._empty = {};
8110
8111 dojo.dnd.isFormElement = function(/*Event*/ e){
8112 // summary:
8113 // returns true if user clicked on a form element
8114 var t = e.target;
8115 if(t.nodeType == 3 /*TEXT_NODE*/){
8116 t = t.parentNode;
8117 }
8118 return " button textarea input select option ".indexOf(" " + t.tagName.toLowerCase() + " ") >= 0; // Boolean
8119 };
8120
8121 }
8122
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");
8126
8127
8128 dojo.getObject("dnd", true, dojo);
8129
8130 dojo.dnd.getViewport = dojo.window.getBox;
8131
8132 dojo.dnd.V_TRIGGER_AUTOSCROLL = 32;
8133 dojo.dnd.H_TRIGGER_AUTOSCROLL = 32;
8134
8135 dojo.dnd.V_AUTOSCROLL_VALUE = 16;
8136 dojo.dnd.H_AUTOSCROLL_VALUE = 16;
8137
8138 dojo.dnd.autoScroll = function(e){
8139 // summary:
8140 // a handler for onmousemove event, which scrolls the window, if
8141 // necesary
8142 // e: Event
8143 // onmousemove event
8144
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;
8151 }
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;
8156 }
8157 window.scrollBy(dx, dy);
8158 };
8159
8160 dojo.dnd._validNodes = {"div": 1, "p": 1, "td": 1};
8161 dojo.dnd._validOverflow = {"auto": 1, "scroll": 1};
8162
8163 dojo.dnd.autoScrollNodes = function(e){
8164 // summary:
8165 // a handler for onmousemove event, which scrolls the first avaialble
8166 // Dom element, it falls back to dojo.dnd.autoScroll()
8167 // e: Event
8168 // onmousemove event
8169
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;
8186 }
8187 if(rx > 0 && rx < b.w){
8188 if(rx < w){
8189 dx = -w;
8190 }else if(rx > b.w - w){
8191 dx = w;
8192 }
8193 }
8194 //console.log("ry =", ry, "b.h =", b.h, "h =", h);
8195 if(ry > 0 && ry < b.h){
8196 if(ry < h){
8197 dy = -h;
8198 }else if(ry > b.h - h){
8199 dy = h;
8200 }
8201 }
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; }
8206 }
8207 }
8208 try{
8209 n = n.parentNode;
8210 }catch(x){
8211 n = null;
8212 }
8213 }
8214 dojo.dnd.autoScroll(e);
8215 };
8216
8217 }
8218
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");
8222
8223
8224
8225
8226 dojo.declare("dojo.dnd.Mover", null, {
8227 constructor: function(node, e, host){
8228 // summary:
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.
8231 // node: Node
8232 // a node (or node's id) to be moved
8233 // e: Event
8234 // a mouse event, which started the move;
8235 // only pageX and pageY properties are used
8236 // host: Object?
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;
8244 this.events = [
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"),
8249
8250 // These are called continually during the drag
8251 dojo.connect(d, "onmousemove", this, "onMouseMove"),
8252 dojo.connect(d, "ontouchmove", this, "onMouseMove"),
8253
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"),
8257
8258 // cancel text selection and text dragging
8259 dojo.connect(d, "ondragstart", dojo.stopEvent),
8260 dojo.connect(d.body, "onselectstart", dojo.stopEvent)
8261 ];
8262 // notify that the move has started
8263 if(h && h.onMoveStart){
8264 h.onMoveStart(this);
8265 }
8266 },
8267 // mouse event processors
8268 onMouseMove: function(e){
8269 // summary:
8270 // event processor for onmousemove/ontouchmove
8271 // e: Event
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);
8277 dojo.stopEvent(e);
8278 },
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?
8282 this.destroy();
8283 }
8284 dojo.stopEvent(e);
8285 },
8286 // utilities
8287 onFirstMove: function(e){
8288 // summary:
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;
8292 switch(s.position){
8293 case "relative":
8294 case "absolute":
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;
8298 break;
8299 default:
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);
8316 break;
8317 }
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);
8322 }
8323
8324 // Disconnect onmousemove and ontouchmove events that call this function
8325 dojo.disconnect(this.events.shift());
8326 dojo.disconnect(this.events.shift());
8327 },
8328 destroy: function(){
8329 // summary:
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
8333 var h = this.host;
8334 if(h && h.onMoveStop){
8335 h.onMoveStop(this);
8336 }
8337 // destroy objects
8338 this.events = this.node = this.host = null;
8339 }
8340 });
8341
8342 }
8343
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");
8347
8348
8349
8350 /*=====
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.
8355 handle: null,
8356
8357 // delay: Number
8358 // delay move by this number of pixels
8359 delay: 0,
8360
8361 // skip: Boolean
8362 // skip move of form elements
8363 skip: false,
8364
8365 // mover: Object
8366 // a constructor of custom Mover
8367 mover: dojo.dnd.Mover
8368 });
8369 =====*/
8370
8371 dojo.declare("dojo.dnd.Moveable", null, {
8372 // object attributes (for markup)
8373 handle: "",
8374 delay: 0,
8375 skip: false,
8376
8377 constructor: function(node, params){
8378 // summary:
8379 // an object, which makes a node moveable
8380 // node: Node
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;
8391 this.events = [
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")
8397 ];
8398 },
8399
8400 // markup methods
8401 markupFactory: function(params, node){
8402 return new dojo.dnd.Moveable(node, params);
8403 },
8404
8405 // methods
8406 destroy: function(){
8407 // summary:
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;
8411 },
8412
8413 // mouse event processors
8414 onMouseDown: function(e){
8415 // summary:
8416 // event processor for onmousedown/ontouchstart, creates a Mover for the node
8417 // e: Event
8418 // mouse/touch event
8419 if(this.skip && dojo.dnd.isFormElement(e)){ return; }
8420 if(this.delay){
8421 this.events.push(
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")
8426 );
8427 var pos = e.touches ? e.touches[0] : e;
8428 this._lastX = pos.pageX;
8429 this._lastY = pos.pageY;
8430 }else{
8431 this.onDragDetected(e);
8432 }
8433 dojo.stopEvent(e);
8434 },
8435 onMouseMove: function(e){
8436 // summary:
8437 // event processor for onmousemove/ontouchmove, used only for delayed drags
8438 // e: Event
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){
8442 this.onMouseUp(e);
8443 this.onDragDetected(e);
8444 }
8445 dojo.stopEvent(e);
8446 },
8447 onMouseUp: function(e){
8448 // summary:
8449 // event processor for onmouseup, used only for delayed drags
8450 // e: Event
8451 // mouse event
8452 for(var i = 0; i < 2; ++i){
8453 dojo.disconnect(this.events.pop());
8454 }
8455 dojo.stopEvent(e);
8456 },
8457 onSelectStart: function(e){
8458 // summary:
8459 // event processor for onselectevent and ondragevent
8460 // e: Event
8461 // mouse event
8462 if(!this.skip || !dojo.dnd.isFormElement(e)){
8463 dojo.stopEvent(e);
8464 }
8465 },
8466
8467 // local events
8468 onDragDetected: function(/* Event */ e){
8469 // summary:
8470 // called when the drag is detected;
8471 // responsible for creation of the mover
8472 new this.mover(this.node, e, this);
8473 },
8474 onMoveStart: function(/* dojo.dnd.Mover */ mover){
8475 // summary:
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");
8480 },
8481 onMoveStop: function(/* dojo.dnd.Mover */ mover){
8482 // summary:
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");
8487 },
8488 onFirstMove: function(/* dojo.dnd.Mover */ mover, /* Event */ e){
8489 // summary:
8490 // called during the very first move notification;
8491 // can be used to initialize coordinates, can be overwritten.
8492
8493 // default implementation does nothing
8494 },
8495 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop, /* Event */ e){
8496 // summary:
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);
8504 },
8505 onMoving: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8506 // summary:
8507 // called before every incremental move; can be overwritten.
8508
8509 // default implementation does nothing
8510 },
8511 onMoved: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8512 // summary:
8513 // called after every incremental move; can be overwritten.
8514
8515 // default implementation does nothing
8516 }
8517 });
8518
8519 }
8520
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");
8524
8525
8526
8527
8528 /*=====
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(){},
8534
8535 // within: Boolean
8536 // restrict move within boundaries.
8537 within: false
8538 });
8539 =====*/
8540
8541 dojo.declare("dojo.dnd.move.constrainedMoveable", dojo.dnd.Moveable, {
8542 // object attributes (for markup)
8543 constraints: function(){},
8544 within: false,
8545
8546 // markup methods
8547 markupFactory: function(params, node){
8548 return new dojo.dnd.move.constrainedMoveable(node, params);
8549 },
8550
8551 constructor: function(node, params){
8552 // summary:
8553 // an object that makes a node moveable
8554 // node: Node
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;
8562 },
8563 onFirstMove: function(/* dojo.dnd.Mover */ mover){
8564 // summary:
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);
8568 c.r = c.l + c.w;
8569 c.b = c.t + c.h;
8570 if(this.within){
8571 var mb = dojo._getMarginSize(mover.node);
8572 c.r -= mb.w;
8573 c.b -= mb.h;
8574 }
8575 },
8576 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8577 // summary:
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);
8587 }
8588 });
8589
8590 /*=====
8591 dojo.declare("dojo.dnd.move.__boxConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], {
8592 // box: Object
8593 // a constraint box
8594 box: {}
8595 });
8596 =====*/
8597
8598 dojo.declare("dojo.dnd.move.boxConstrainedMoveable", dojo.dnd.move.constrainedMoveable, {
8599 // box:
8600 // object attributes (for markup)
8601 box: {},
8602
8603 // markup methods
8604 markupFactory: function(params, node){
8605 return new dojo.dnd.move.boxConstrainedMoveable(node, params);
8606 },
8607
8608 constructor: function(node, params){
8609 // summary:
8610 // an object, which makes a node moveable
8611 // node: Node
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; };
8617 }
8618 });
8619
8620 /*=====
8621 dojo.declare("dojo.dnd.move.__parentConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], {
8622 // area: String
8623 // A parent's area to restrict the move.
8624 // Can be "margin", "border", "padding", or "content".
8625 area: ""
8626 });
8627 =====*/
8628
8629 dojo.declare("dojo.dnd.move.parentConstrainedMoveable", dojo.dnd.move.constrainedMoveable, {
8630 // area:
8631 // object attributes (for markup)
8632 area: "content",
8633
8634 // markup methods
8635 markupFactory: function(params, node){
8636 return new dojo.dnd.move.parentConstrainedMoveable(node, params);
8637 },
8638
8639 constructor: function(node, params){
8640 // summary:
8641 // an object, which makes a node moveable
8642 // node: Node
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
8653 }
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
8658 }
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
8663 }
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
8667 };
8668 }
8669 });
8670
8671 // patching functions one level up for compatibility
8672
8673 dojo.dnd.constrainedMover = dojo.dnd.move.constrainedMover;
8674 dojo.dnd.boxConstrainedMover = dojo.dnd.move.boxConstrainedMover;
8675 dojo.dnd.parentConstrainedMover = dojo.dnd.move.parentConstrainedMover;
8676
8677 }
8678
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");
8682
8683
8684
8685 /*=====
8686 dojo.declare("dojo.dnd.__TimedMoveableArgs", [dojo.dnd.__MoveableArgs], {
8687 // timeout: Number
8688 // delay move by this number of ms,
8689 // accumulating position changes during the timeout
8690 timeout: 0
8691 });
8692 =====*/
8693
8694 (function(){
8695 // precalculate long expressions
8696 var oldOnMove = dojo.dnd.Moveable.prototype.onMove;
8697
8698 dojo.declare("dojo.dnd.TimedMoveable", dojo.dnd.Moveable, {
8699 // summary:
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.
8704
8705 // object attributes (for markup)
8706 timeout: 40, // in ms, 40ms corresponds to 25 fps
8707
8708 constructor: function(node, params){
8709 // summary:
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.
8715
8716 // sanitize parameters
8717 if(!params){ params = {}; }
8718 if(params.timeout && typeof params.timeout == "number" && params.timeout >= 0){
8719 this.timeout = params.timeout;
8720 }
8721 },
8722
8723 // markup methods
8724 markupFactory: function(params, node){
8725 return new dojo.dnd.TimedMoveable(node, params);
8726 },
8727
8728 onMoveStop: function(/* dojo.dnd.Mover */ mover){
8729 if(mover._timer){
8730 // stop timer
8731 clearTimeout(mover._timer)
8732 // reflect the last received position
8733 oldOnMove.call(this, mover, mover._leftTop)
8734 }
8735 dojo.dnd.Moveable.prototype.onMoveStop.apply(this, arguments);
8736 },
8737 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8738 mover._leftTop = leftTop;
8739 if(!mover._timer){
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);
8746 }, this.timeout);
8747 }
8748 }
8749 });
8750 })();
8751
8752 }
8753
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");
8757
8758
8759
8760 dojo.declare("dijit.form._FormMixin", null, {
8761 // summary:
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)
8764 // description:
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
8768 // form widgets
8769
8770 /*=====
8771 // value: Object
8772 // Name/value hash for each child widget with a name and value.
8773 // Child widgets without names are not part of the hash.
8774 //
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).
8778 //
8779 // If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure.
8780 //
8781 // Example:
8782 // | { name: "John Smith", interests: ["sports", "movies"] }
8783 =====*/
8784
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.
8789 state: "",
8790
8791 // TODO:
8792 // * Repeater
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}, ...])
8795 //
8796 //
8797
8798 reset: function(){
8799 dojo.forEach(this.getDescendants(), function(widget){
8800 if(widget.reset){
8801 widget.reset();
8802 }
8803 });
8804 },
8805
8806 validate: function(){
8807 // summary:
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
8811 // valid
8812 // 2 - it will call focus() on the first invalid
8813 // sub-widget
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
8817 // state set.
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);
8823 widget.focus();
8824 didFocus = true;
8825 }
8826 return valid;
8827 }), function(item){ return item; });
8828 },
8829
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);
8833 },
8834 _setValueAttr: function(/*Object*/ obj){
8835 // summary:
8836 // Fill in form values from according to an Object (in the format returned by get('value'))
8837
8838 // generate map from name --> [list of widgets with that name]
8839 var map = { };
8840 dojo.forEach(this.getDescendants(), function(widget){
8841 if(!widget.name){ return; }
8842 var entry = map[widget.name] || (map[widget.name] = [] );
8843 entry.push(widget);
8844 });
8845
8846 for(var name in map){
8847 if(!map.hasOwnProperty(name)){
8848 continue;
8849 }
8850 var widgets = map[name], // array of widgets w/this name
8851 values = dojo.getObject(name, false, obj); // list of values for those widgets
8852
8853 if(values === undefined){
8854 continue;
8855 }
8856 if(!dojo.isArray(values)){
8857 values = [ values ];
8858 }
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);
8863 });
8864 }else if(widgets[0].multiple){
8865 // it takes an array (e.g. multi-select)
8866 widgets[0].set('value', values);
8867 }else{
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]);
8871 });
8872 }
8873 }
8874
8875 /***
8876 * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets)
8877
8878 dojo.forEach(this.containerNode.elements, function(element){
8879 if(element.name == ''){return}; // like "continue"
8880 var namePath = element.name.split(".");
8881 var myObj=obj;
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]]=[ ];
8890 } // if
8891
8892 nameIndex=parseInt(nameA[1]);
8893 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
8894 myObj[nameA[0]][nameIndex] = { };
8895 }
8896 myObj=myObj[nameA[0]][nameIndex];
8897 continue;
8898 } // repeater support ends
8899
8900 if(typeof(myObj[p]) == "undefined"){
8901 myObj=undefined;
8902 break;
8903 };
8904 myObj=myObj[p];
8905 }
8906
8907 if(typeof(myObj) == "undefined"){
8908 return; // like "continue"
8909 }
8910 if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){
8911 return; // like "continue"
8912 }
8913
8914 // TODO: widget values (just call set('value', ...) on the widget)
8915
8916 // TODO: maybe should call dojo.getNodeProp() instead
8917 switch(element.type){
8918 case "checkbox":
8919 element.checked = (name in myObj) &&
8920 dojo.some(myObj[name], function(val){ return val == element.value; });
8921 break;
8922 case "radio":
8923 element.checked = (name in myObj) && myObj[name] == element.value;
8924 break;
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; });
8929 });
8930 break;
8931 case "select-one":
8932 element.selectedIndex="0";
8933 dojo.forEach(element.options, function(option){
8934 option.selected = option.value == myObj[name];
8935 });
8936 break;
8937 case "hidden":
8938 case "text":
8939 case "textarea":
8940 case "password":
8941 element.value = myObj[name] || "";
8942 break;
8943 }
8944 });
8945 */
8946
8947 // Note: no need to call this._set("value", ...) as the child updates will trigger onChange events
8948 // which I am monitoring.
8949 },
8950
8951 getValues: function(){
8952 dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0");
8953 return this.get('value');
8954 },
8955 _getValueAttr: function(){
8956 // summary:
8957 // Returns Object representing form values. See description of `value` for details.
8958 // description:
8959
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:
8963 //
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()
8966 //
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.
8969
8970 // get widget values
8971 var obj = { };
8972 dojo.forEach(this.getDescendants(), function(widget){
8973 var name = widget.name;
8974 if(!name || widget.disabled){ return; }
8975
8976 // Single value widget (checkbox, radio, or plain <input> type widget)
8977 var value = widget.get('value');
8978
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)){
8982 // radio button
8983 if(value !== false){
8984 dojo.setObject(name, value, obj);
8985 }else{
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);
8990 }
8991 }
8992 }else{
8993 // checkbox/toggle button
8994 var ary=dojo.getObject(name, false, obj);
8995 if(!ary){
8996 ary=[];
8997 dojo.setObject(name, ary, obj);
8998 }
8999 if(value !== false){
9000 ary.push(value);
9001 }
9002 }
9003 }else{
9004 var prev=dojo.getObject(name, false, obj);
9005 if(typeof prev != "undefined"){
9006 if(dojo.isArray(prev)){
9007 prev.push(value);
9008 }else{
9009 dojo.setObject(name, [prev, value], obj);
9010 }
9011 }else{
9012 // unique name
9013 dojo.setObject(name, value, obj);
9014 }
9015 }
9016 });
9017
9018 /***
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)
9021 var obj = { };
9022 dojo.forEach(this.containerNode.elements, function(elm){
9023 if(!elm.name) {
9024 return; // like "continue"
9025 }
9026 var namePath = elm.name.split(".");
9027 var myObj=obj;
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]]=[ ];
9036 } // if
9037 nameIndex=parseInt(nameA[1]);
9038 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
9039 myObj[nameA[0]][nameIndex] = { };
9040 }
9041 } else if(typeof(myObj[nameA[0]]) == "undefined"){
9042 myObj[nameA[0]] = { }
9043 } // if
9044
9045 if(nameA.length == 1){
9046 myObj=myObj[nameA[0]];
9047 } else{
9048 myObj=myObj[nameA[0]][nameIndex];
9049 } // if
9050 } // for
9051
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;
9055 } else{
9056 // can not set value when there is no name
9057 }
9058 } else if(elm.type == "checkbox" && elm.checked){
9059 if(typeof(myObj[name]) == 'undefined'){
9060 myObj[name]=[ ];
9061 }
9062 myObj[name].push(elm.value);
9063 } else if(elm.type == "select-multiple"){
9064 if(typeof(myObj[name]) == 'undefined'){
9065 myObj[name]=[ ];
9066 }
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);
9070 }
9071 }
9072 } // if
9073 name=undefined;
9074 }); // forEach
9075 ***/
9076 return obj;
9077 },
9078
9079 isValid: function(){
9080 // summary:
9081 // Returns true if all of the widgets are valid.
9082 // Deprecated, will be removed in 2.0. Use get("state") instead.
9083
9084 return this.state == "";
9085 },
9086
9087 onValidStateChange: function(isValid){
9088 // summary:
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.
9092 //
9093 // Deprecated. Will be removed in 2.0. Use watch("state", ...) instead.
9094 },
9095
9096 _getState: function(){
9097 // summary:
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") || "";
9101 });
9102
9103 return dojo.indexOf(states, "Error") >= 0 ? "Error" :
9104 dojo.indexOf(states, "Incomplete") >= 0 ? "Incomplete" : "";
9105 },
9106
9107 disconnectChildren: function(){
9108 // summary:
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(); });
9113 },
9114
9115 connectChildren: function(/*Boolean*/ inStartup){
9116 // summary:
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.
9119 //
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
9122 // initialized.
9123
9124 var _this = this;
9125
9126 // Remove old connections, if any
9127 this.disconnectChildren();
9128
9129 this._descendants = this.getDescendants();
9130
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());
9135
9136 // Monitor changes to error state and disabled state in order to update
9137 // Form.state
9138 var conns = (this._childConnections = []),
9139 watches = (this._childWatches = []);
9140 dojo.forEach(dojo.filter(this._descendants,
9141 function(item){ return item.validate; }
9142 ),
9143 function(widget){
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());
9149 }));
9150 });
9151 });
9152
9153 // And monitor calls to child.onChange so we can update this.value
9154 var onChange = function(){
9155 // summary:
9156 // Called when child's value or disabled state changes
9157
9158 // Use setTimeout() to collapse value changes in multiple children into a single
9159 // update to my value. Multiple updates will occur on:
9160 // 1. Form.set()
9161 // 2. Form.reset()
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);
9166 }
9167 _this._onChangeDelayTimer = setTimeout(function(){
9168 delete _this._onChangeDelayTimer;
9169 _this._set("value", _this.get("value"));
9170 }, 10);
9171 };
9172 dojo.forEach(
9173 dojo.filter(this._descendants, function(item){ return item.onChange; } ),
9174 function(widget){
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));
9181
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));
9185 }
9186 );
9187 },
9188
9189 startup: function(){
9190 this.inherited(arguments);
9191
9192 // Initialize value and valid/invalid state tracking. Needs to be done in startup()
9193 // so that children are initialized.
9194 this.connectChildren(true);
9195
9196 // Make state change call onValidStateChange(), will be removed in 2.0
9197 this.watch("state", function(attr, oldVal, newVal){ this.onValidStateChange(newVal == ""); });
9198 },
9199
9200 destroy: function(){
9201 this.disconnectChildren();
9202 this.inherited(arguments);
9203 }
9204
9205 });
9206
9207 }
9208
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");
9212
9213
9214
9215 dojo.declare("dijit._DialogMixin", null,
9216 {
9217 // summary:
9218 // This provides functions useful to Dialog and TooltipDialog
9219
9220 attributeMap: dijit._Widget.prototype.attributeMap,
9221
9222 execute: function(/*Object*/ formContents){
9223 // summary:
9224 // Callback when the user hits the submit button.
9225 // Override this method to handle Dialog execution.
9226 // description:
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.
9230 //
9231 // *Then* this method is called.
9232 // type:
9233 // callback
9234 },
9235
9236 onCancel: function(){
9237 // summary:
9238 // Called when user has pressed the Dialog's cancel button, to notify container.
9239 // description:
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`)
9243 // type:
9244 // protected
9245 },
9246
9247 onExecute: function(){
9248 // summary:
9249 // Called when user has pressed the dialog's OK button, to notify container.
9250 // description:
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`)
9254 // type:
9255 // protected
9256 },
9257
9258 _onSubmit: function(){
9259 // summary:
9260 // Callback when user hits submit button
9261 // type:
9262 // protected
9263 this.onExecute(); // notify container that we are about to execute
9264 this.execute(this.get('value'));
9265 },
9266
9267 _getFocusItems: function(){
9268 // summary:
9269 // Finds focusable items in dialog,
9270 // and sets this._firstFocusItem and this._lastFocusItem
9271 // tags:
9272 // protected
9273
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;
9277 }
9278 }
9279 );
9280
9281 }
9282
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");
9286
9287
9288
9289
9290
9291 dojo.declare(
9292 "dijit.DialogUnderlay",
9293 [dijit._Widget, dijit._Templated],
9294 {
9295 // summary:
9296 // The component that blocks the screen behind a `dijit.Dialog`
9297 //
9298 // description:
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`
9302 //
9303 // The underlay itself can be styled based on and id:
9304 // | #myDialog_underlay { background-color:red; }
9305 //
9306 // In the case of `dijit.Dialog`, this id is based on the id of the Dialog,
9307 // suffixed with _underlay.
9308
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>",
9312
9313 // Parameters on creation or updatable later
9314
9315 // dialogId: String
9316 // Id of the dialog.... DialogUnderlay's id is based on this id
9317 dialogId: "",
9318
9319 // class: String
9320 // This class name is used on the DialogUnderlay node, in addition to dijitDialogUnderlay
9321 "class": "",
9322
9323 attributeMap: { id: "domNode" },
9324
9325 _setDialogIdAttr: function(id){
9326 dojo.attr(this.node, "id", id + "_underlay");
9327 this._set("dialogId", id);
9328 },
9329
9330 _setClassAttr: function(clazz){
9331 this.node.className = "dijitDialogUnderlay " + clazz;
9332 this._set("class", clazz);
9333 },
9334
9335 postCreate: function(){
9336 // summary:
9337 // Append the underlay to the body
9338 dojo.body().appendChild(this.domNode);
9339 },
9340
9341 layout: function(){
9342 // summary:
9343 // Sets the background to the size of the viewport
9344 //
9345 // description:
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.
9349 // tags:
9350 // private
9351
9352 var is = this.node.style,
9353 os = this.domNode.style;
9354
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";
9359
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";
9367 },
9368
9369 show: function(){
9370 // summary:
9371 // Show the dialog underlay
9372 this.domNode.style.display = "block";
9373 this.layout();
9374 this.bgIframe = new dijit.BackgroundIframe(this.domNode);
9375 },
9376
9377 hide: function(){
9378 // summary:
9379 // Hides the dialog underlay
9380 this.bgIframe.destroy();
9381 delete this.bgIframe;
9382 this.domNode.style.display = "none";
9383 }
9384 }
9385 );
9386
9387 }
9388
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");
9392
9393
9394
9395
9396 dojo.declare("dijit.layout._ContentPaneResizeMixin", null, {
9397 // summary:
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.
9401 //
9402 // Also implements basic startup() functionality, where starting the parent
9403 // will start the children
9404
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
9409 doLayout: true,
9410
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`.
9415 isContainer: true,
9416
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,
9421
9422 _startChildren: function(){
9423 // summary:
9424 // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
9425
9426 // This starts all the widgets
9427 dojo.forEach(this.getChildren(), function(child){
9428 child.startup();
9429 child._started = true;
9430 });
9431 },
9432
9433 startup: function(){
9434 // summary:
9435 // See `dijit.layout._LayoutWidget.startup` for description.
9436 // Although ContentPane doesn't extend _LayoutWidget, it does implement
9437 // the same API.
9438
9439 if(this._started){ return; }
9440
9441 var parent = dijit._Contained.prototype.getParent.call(this);
9442 this._childOfLayoutWidget = parent && parent.isLayoutContainer;
9443
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;
9447
9448 this.inherited(arguments);
9449
9450 this._startChildren();
9451
9452 if(this._isShown()){
9453 this._onShow();
9454 }
9455
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;
9465 this.resize();
9466 });
9467 }
9468 },
9469
9470 _checkIfSingleChild: function(){
9471 // summary:
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.
9476
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..
9479 }),
9480 childWidgetNodes = childNodes.filter(function(node){
9481 return dojo.hasAttr(node, "data-dojo-type") || dojo.hasAttr(node, "dojoType") || dojo.hasAttr(node, "widgetId");
9482 }),
9483 candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){
9484 return widget && widget.domNode && widget.resize;
9485 });
9486
9487 if(
9488 // all child nodes are widgets
9489 childNodes.length == childWidgetNodes.length &&
9490
9491 // all but one are invisible (like dojo.data)
9492 candidateWidgets.length == 1
9493 ){
9494 this._singleChild = candidateWidgets[0];
9495 }else{
9496 delete this._singleChild;
9497 }
9498
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);
9501 },
9502
9503 resize: function(changeSize, resultSize){
9504 // summary:
9505 // See `dijit.layout._LayoutWidget.resize` for description.
9506 // Although ContentPane doesn't extend _LayoutWidget, it does implement
9507 // the same API.
9508
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){
9513 this._onShow();
9514 }
9515
9516 this._resizeCalled = true;
9517
9518 this._scheduleLayout(changeSize, resultSize);
9519 },
9520
9521 _scheduleLayout: function(changeSize, resultSize){
9522 // summary:
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);
9527 }else{
9528 this._needLayout = true;
9529 this._changeSize = changeSize;
9530 this._resultSize = resultSize;
9531 }
9532 },
9533
9534 _layout: function(changeSize, resultSize){
9535 // summary:
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.
9539 //
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
9543
9544 // Set margin box size, unless it wasn't specified, in which case use current size.
9545 if(changeSize){
9546 dojo.marginBox(this.domNode, changeSize);
9547 }
9548
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
9559 }
9560 this._contentBox = dijit.layout.marginBox2contentBox(cn, mb);
9561 }else{
9562 this._contentBox = dojo.contentBox(cn);
9563 }
9564
9565 this._layoutChildren();
9566
9567 delete this._needLayout;
9568 },
9569
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.
9573 if(this.doLayout){
9574 this._checkIfSingleChild();
9575 }
9576
9577 if(this._singleChild && this._singleChild.resize){
9578 var cb = this._contentBox || dojo.contentBox(this.containerNode);
9579
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});
9583 }else{
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){
9587 if(widget.resize){
9588 widget.resize();
9589 }
9590 });
9591 }
9592 },
9593
9594 _isShown: function(){
9595 // summary:
9596 // Returns true if the content is currently shown.
9597 // description:
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
9602
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){
9606 return this.open;
9607 }
9608 return this._resizeCalled;
9609 }else if("open" in this){
9610 return this.open; // for TitlePane, etc.
9611 }else{
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');
9615 }
9616 },
9617
9618 _onShow: function(){
9619 // summary:
9620 // Called when the ContentPane is made visible
9621 // description:
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.
9625 //
9626 // Does layout/resize of child widget(s)
9627
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);
9631 }
9632
9633 this.inherited(arguments);
9634
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;
9638 }
9639 });
9640
9641 }
9642
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");
9646
9647
9648 dojo.getObject("html", true, dojo);
9649
9650 // the parser might be needed..
9651 (function(){ // private scope, sort of a namespace
9652
9653 // idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes
9654 var idCounter = 0,
9655 d = dojo;
9656
9657 dojo.html._secureForInnerHtml = function(/*String*/ cont){
9658 // summary:
9659 // removes !DOCTYPE and title elements from the html string.
9660 //
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
9663 // cont:
9664 // An html string for insertion into the dom
9665 //
9666 return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String
9667 };
9668
9669 /*====
9670 dojo.html._emptyNode = function(node){
9671 // summary:
9672 // removes all child nodes from the given node
9673 // node: DOMNode
9674 // the parent element
9675 };
9676 =====*/
9677 dojo.html._emptyNode = dojo.empty;
9678
9679 dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){
9680 // summary:
9681 // inserts the given content into the given node
9682 // node:
9683 // the parent element
9684 // content:
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
9687
9688 // always empty
9689 d.empty(node);
9690
9691 if(cont) {
9692 if(typeof cont == "string") {
9693 cont = d._toDom(cont, node.ownerDocument);
9694 }
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");
9699 }
9700 } else {
9701 // pass nodes, documentFragments and unknowns through to dojo.place
9702 d.place(cont, node, "last");
9703 }
9704 }
9705
9706 // return DomNode
9707 return node;
9708 };
9709
9710 // we wrap up the content-setting operation in a object
9711 dojo.declare("dojo.html._ContentSetter", null,
9712 {
9713 // node: DomNode|String
9714 // An node which will be the parent element that we set content into
9715 node: "",
9716
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
9719 content: "",
9720
9721 // id: String?
9722 // Usually only used internally, and auto-generated with each instance
9723 id: "",
9724
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,
9729
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,
9733
9734 // parseContent: Boolean
9735 // Should the node by passed to the parser after the new content is set
9736 parseContent: false,
9737
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,
9744
9745 // startup: Boolean
9746 // Start the child widgets after parsing them. Only obeyed if parseContent is true.
9747 startup: true,
9748
9749 // lifecyle methods
9750 constructor: function(/* Object */params, /* String|DomNode */node){
9751 // summary:
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..
9754
9755 // the original params are mixed directly into the instance "this"
9756 dojo.mixin(this, params || {});
9757
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 );
9761
9762 if(!this.id){
9763 this.id = [
9764 "Setter",
9765 (node) ? node.id || node.tagName : "",
9766 idCounter++
9767 ].join("_");
9768 }
9769 },
9770 set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){
9771 // summary:
9772 // front-end to the set-content sequence
9773 // cont:
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;
9778 }
9779 // in the re-use scenario, set needs to be able to mixin new configuration
9780 if(params){
9781 this._mixin(params);
9782 }
9783
9784 this.onBegin();
9785 this.setContent();
9786 this.onEnd();
9787
9788 return this.node;
9789 },
9790 setContent: function(){
9791 // summary:
9792 // sets the content on the node
9793
9794 var node = this.node;
9795 if(!node) {
9796 // can't proceed
9797 throw new Error(this.declaredClass + ": setContent given no node");
9798 }
9799 try{
9800 node = dojo.html._setNodeContent(node, this.content);
9801 }catch(e){
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
9804
9805 // FIXME: need to allow the user to provide a content error message string
9806 var errMess = this.onContentError(e);
9807 try{
9808 node.innerHTML = errMess;
9809 }catch(e){
9810 console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e);
9811 }
9812 }
9813 // always put back the node for the next method
9814 this.node = node; // DomNode
9815 },
9816
9817 empty: function() {
9818 // summary
9819 // cleanly empty out existing content
9820
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) {
9826 if(w.destroy){
9827 w.destroy();
9828 }
9829 });
9830 delete this.parseResults;
9831 }
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);
9835 },
9836
9837 onBegin: function(){
9838 // summary
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;
9845
9846 if(dojo.isString(cont)){
9847 if(this.cleanContent){
9848 cont = dojo.html._secureForInnerHtml(cont);
9849 }
9850
9851 if(this.extractContent){
9852 var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
9853 if(match){ cont = match[1]; }
9854 }
9855 }
9856
9857 // clean out the node and any cruft associated with it - like widgets
9858 this.empty();
9859
9860 this.content = cont;
9861 return this.node; /* DomNode */
9862 },
9863
9864 onEnd: function(){
9865 // summary
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..
9871 this._parse();
9872 }
9873 return this.node; /* DomNode */
9874 },
9875
9876 tearDown: function(){
9877 // summary
9878 // manually reset the Setter instance if its being re-used for example for another set()
9879 // description
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;
9884 delete this.node;
9885 delete this.content;
9886 },
9887
9888 onContentError: function(err){
9889 return "Error occured setting content: " + err;
9890 },
9891
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;
9898 for(key in params){
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];
9903 }
9904 },
9905 _parse: function(){
9906 // summary:
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
9909
9910 var rootNode = this.node;
9911 try{
9912 // store the results (widgets, whatever) for potential retrieval
9913 var inherited = {};
9914 dojo.forEach(["dir", "lang", "textDir"], function(name){
9915 if(this[name]){
9916 inherited[name] = this[name];
9917 }
9918 }, this);
9919 this.parseResults = dojo.parser.parse({
9920 rootNode: rootNode,
9921 noStart: !this.startup,
9922 inherited: inherited,
9923 scope: this.parserScope
9924 });
9925 }catch(e){
9926 this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id);
9927 }
9928 },
9929
9930 _onError: function(type, err, consoleText){
9931 // summary:
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);
9935 if(consoleText){
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);
9939 }
9940 }
9941 }); // end dojo.declare()
9942
9943 dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){
9944 // summary:
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.
9947 // description:
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.
9953 // node:
9954 // the parent element that will receive the content
9955 // cont:
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
9958 // params:
9959 // Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter
9960 // example:
9961 // A safe string/node/nodelist content replacement/injection with hooks for extension
9962 // Example Usage:
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");
9968 cont = "";
9969 }
9970 if(!params){
9971 // simple and fast
9972 return dojo.html._setNodeContent(node, cont, true);
9973 }else{
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(
9977 params,
9978 { content: cont, node: node }
9979 ));
9980 return op.set();
9981 }
9982 };
9983 })();
9984
9985 }
9986
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");
9990
9991
9992
9993
9994
9995
9996
9997 dojo.declare(
9998 "dijit.layout.ContentPane", [dijit._Widget, dijit.layout._ContentPaneResizeMixin],
9999 {
10000 // summary:
10001 // A widget containing an HTML fragment, specified inline
10002 // or by uri. Fragment may include widgets.
10003 //
10004 // description:
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.
10018 //
10019 // example:
10020 // Some quick samples:
10021 // To change the innerHTML: cp.set('content', '<b>new content</b>')
10022 //
10023 // Or you can send it a NodeList: cp.set('content', dojo.query('div [class=selected]', userSelection))
10024 //
10025 // To do an ajax update: cp.set('href', url)
10026
10027 // href: String
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', ...);
10032 href: "",
10033
10034 /*=====
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.
10039 content: "",
10040 =====*/
10041
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,
10046
10047 // parseOnLoad: Boolean
10048 // Parse content and create the widgets, if any.
10049 parseOnLoad: true,
10050
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,
10057
10058 // preventCache: Boolean
10059 // Prevent caching of data from href's by appending a timestamp to the href.
10060 preventCache: false,
10061
10062 // preload: Boolean
10063 // Force load of data on initialization even if pane is hidden.
10064 preload: false,
10065
10066 // refreshOnShow: Boolean
10067 // Refresh (re-download) content when pane goes from hidden to shown
10068 refreshOnShow: false,
10069
10070 // loadingMessage: String
10071 // Message that shows while downloading
10072 loadingMessage: "<span class='dijitContentPaneLoading'>${loadingState}</span>",
10073
10074 // errorMessage: String
10075 // Message that shows if an error occurs
10076 errorMessage: "<span class='dijitContentPaneError'>${errorState}</span>",
10077
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', ...)
10082 //
10083 // False if it doesn't have any content, or if ContentPane is
10084 // still in the process of downloading href.
10085 isLoaded: false,
10086
10087 baseClass: "dijitContentPane",
10088
10089 // ioArgs: Object
10090 // Parameters to pass to xhrGet() request, for example:
10091 // | <div dojoType="dijit.layout.ContentPane" href="./bar" ioArgs="{timeout: 500}">
10092 ioArgs: {},
10093
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.
10099 //
10100 // This is different than an onLoad() handler which gets called any time any href
10101 // or content is loaded.
10102 onLoadDeferred: null,
10103
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
10106 // entire pane.
10107 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
10108 title: []
10109 }),
10110
10111 // Flag to parser that I'll parse my contents, so it shouldn't.
10112 stopParser: true,
10113
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)
10118 template: false,
10119
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);
10129 }
10130 params = dojo.delegate(params, {content: df});
10131 }
10132 this.inherited(arguments, [params, srcNodeRef]);
10133 },
10134
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);
10140 },
10141
10142 buildRendering: function(){
10143 this.inherited(arguments);
10144
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;
10149 }
10150
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 = "";
10154
10155 if(!dojo.attr(this.domNode,"role")){
10156 dijit.setWaiRole(this.domNode, "group");
10157 }
10158 },
10159
10160 _startChildren: function(){
10161 // summary:
10162 // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
10163
10164 // This starts all the widgets
10165 this.inherited(arguments);
10166
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)){
10171 obj.startup();
10172 obj._started = true;
10173 }
10174 }, this);
10175 }
10176 },
10177
10178 setHref: function(/*String|Uri*/ href){
10179 // summary:
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);
10183 },
10184 _setHrefAttr: function(/*String|Uri*/ href){
10185 // summary:
10186 // Hook so set("href", ...) works.
10187 // description:
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.
10190 // href:
10191 // url to the page you want to get, must be within the same domain as your mainpage
10192
10193 // Cancel any in-flight requests (a set('href', ...) will cancel any in-flight set('href', ...))
10194 this.cancel();
10195
10196 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
10197 this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad"));
10198
10199 this._set("href", href);
10200
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())){
10205 this._load();
10206 }else{
10207 // Set flag to indicate that href needs to be loaded the next time the
10208 // ContentPane is made visible
10209 this._hrefChanged = true;
10210 }
10211
10212 return this.onLoadDeferred; // dojo.Deferred
10213 },
10214
10215 setContent: function(/*String|DomNode|Nodelist*/data){
10216 // summary:
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);
10220 },
10221 _setContentAttr: function(/*String|DomNode|Nodelist*/data){
10222 // summary:
10223 // Hook to make set("content", ...) work.
10224 // Replaces old content with data content, include style classes from old content
10225 // data:
10226 // the new Content may be String, DomNode or NodeList
10227 //
10228 // if data is a NodeList (or an array of nodes) nodes are copied
10229 // so you can import nodes from another document implicitly
10230
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", "");
10234
10235 // Cancel any in-flight requests (a set('content', ...) will cancel any in-flight set('href', ...))
10236 this.cancel();
10237
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"));
10241 if(this._created){
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"));
10246 }
10247
10248 this._setContent(data || "");
10249
10250 this._isDownloaded = false; // mark that content is from a set('content') not a set('href')
10251
10252 return this.onLoadDeferred; // dojo.Deferred
10253 },
10254 _getContentAttr: function(){
10255 // summary:
10256 // Hook to make get("content") work
10257 return this.containerNode.innerHTML;
10258 },
10259
10260 cancel: function(){
10261 // summary:
10262 // Cancels an in-flight download of content
10263 if(this._xhrDfd && (this._xhrDfd.fired == -1)){
10264 this._xhrDfd.cancel();
10265 }
10266 delete this._xhrDfd; // garbage collect
10267
10268 this.onLoadDeferred = null;
10269 },
10270
10271 uninitialize: function(){
10272 if(this._beingDestroyed){
10273 this.cancel();
10274 }
10275 this.inherited(arguments);
10276 },
10277
10278 destroyRecursive: function(/*Boolean*/ preserveDom){
10279 // summary:
10280 // Destroy the ContentPane and its contents
10281
10282 // if we have multiple controllers destroying us, bail after the first
10283 if(this._beingDestroyed){
10284 return;
10285 }
10286 this.inherited(arguments);
10287 },
10288
10289 _onShow: function(){
10290 // summary:
10291 // Called when the ContentPane is made visible
10292 // description:
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.
10296 //
10297 // Does necessary processing, including href download and layout/resize of
10298 // child widget(s)
10299
10300 this.inherited(arguments);
10301
10302 if(this.href){
10303 if(!this._xhrDfd && // if there's an href that isn't already being loaded
10304 (!this.isLoaded || this._hrefChanged || this.refreshOnShow)
10305 ){
10306 return this.refresh(); // If child has an href, promise that fires when the load is complete
10307 }
10308 }
10309 },
10310
10311 refresh: function(){
10312 // summary:
10313 // [Re]download contents of href and display
10314 // description:
10315 // 1. cancels any currently in-flight requests
10316 // 2. posts "loading..." message
10317 // 3. sends XHR to download new data
10318
10319 // Cancel possible prior in-flight request
10320 this.cancel();
10321
10322 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
10323 this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad"));
10324 this._load();
10325 return this.onLoadDeferred; // If child has an href, promise that fires when refresh is complete
10326 },
10327
10328 _load: function(){
10329 // summary:
10330 // Load/reload the href specified in this.href
10331
10332 // display loading message
10333 this._setContent(this.onDownloadStart(), true);
10334
10335 var self = this;
10336 var getArgs = {
10337 preventCache: (this.preventCache || this.refreshOnShow),
10338 url: this.href,
10339 handleAs: "text"
10340 };
10341 if(dojo.isObject(this.ioArgs)){
10342 dojo.mixin(getArgs, this.ioArgs);
10343 }
10344
10345 var hand = (this._xhrDfd = (this.ioMethod || dojo.xhrGet)(getArgs));
10346
10347 hand.addCallback(function(html){
10348 try{
10349 self._isDownloaded = true;
10350 self._setContent(html, false);
10351 self.onDownloadEnd();
10352 }catch(err){
10353 self._onError('Content', err); // onContentError
10354 }
10355 delete self._xhrDfd;
10356 return html;
10357 });
10358
10359 hand.addErrback(function(err){
10360 if(!hand.canceled){
10361 // show error message in the pane
10362 self._onError('Download', err); // onDownloadError
10363 }
10364 delete self._xhrDfd;
10365 return err;
10366 });
10367
10368 // Remove flag saying that a load is needed
10369 delete this._hrefChanged;
10370 },
10371
10372 _onLoadHandler: function(data){
10373 // summary:
10374 // This is called whenever new content is being loaded
10375 this._set("isLoaded", true);
10376 try{
10377 this.onLoadDeferred.callback(data);
10378 }catch(e){
10379 console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message);
10380 }
10381 },
10382
10383 _onUnloadHandler: function(){
10384 // summary:
10385 // This is called whenever the content is being unloaded
10386 this._set("isLoaded", false);
10387 try{
10388 this.onUnload();
10389 }catch(e){
10390 console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message);
10391 }
10392 },
10393
10394 destroyDescendants: function(){
10395 // summary:
10396 // Destroy all the widgets inside the ContentPane and empty containerNode
10397
10398 // Make sure we call onUnload (but only when the ContentPane has real content)
10399 if(this.isLoaded){
10400 this._onUnloadHandler();
10401 }
10402
10403 // Even if this.isLoaded == false there might still be a "Loading..." message
10404 // to erase, so continue...
10405
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();
10412 }
10413 });
10414 if(setter){
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();
10420 }
10421 });
10422 delete setter.parseResults;
10423 }
10424
10425 // And then clear away all the DOM nodes
10426 dojo.html._emptyNode(this.containerNode);
10427
10428 // Delete any state information we have about current contents
10429 delete this._singleChild;
10430 },
10431
10432 _setContent: function(/*String|DocumentFragment*/ cont, /*Boolean*/ isFakeContent){
10433 // summary:
10434 // Insert the content into the container node
10435
10436 // first get rid of child widgets
10437 this.destroyDescendants();
10438
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);
10453 try{
10454 this.containerNode.innerHTML = errMess;
10455 }catch(e){
10456 console.error('Fatal '+this.id+' could not change content due to '+e.message, e);
10457 }
10458 })/*,
10459 _onError */
10460 });
10461 };
10462
10463 var setterParams = dojo.mixin({
10464 cleanContent: this.cleanContent,
10465 extractContent: this.extractContent,
10466 parseContent: this.parseOnLoad,
10467 parserScope: this.parserScope,
10468 startup: false,
10469 dir: this.dir,
10470 lang: this.lang
10471 }, this._contentSetterParams || {});
10472
10473 setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams );
10474
10475 // setter params must be pulled afresh from the ContentPane each time
10476 delete this._contentSetterParams;
10477
10478 if(this.doLayout){
10479 this._checkIfSingleChild();
10480 }
10481
10482 if(!isFakeContent){
10483 if(this._started){
10484 // Startup each top level child widget (and they will start their children, recursively)
10485 this._startChildren();
10486
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();
10491 }
10492
10493 this._onLoadHandler(cont);
10494 }
10495 },
10496
10497 _onError: function(type, err, consoleText){
10498 this.onLoadDeferred.errback(err);
10499
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);
10503 if(consoleText){
10504 console.error(consoleText, err);
10505 }else if(errText){// a empty string won't change current content
10506 this._setContent(errText, true);
10507 }
10508 },
10509
10510 // EVENT's, should be overide-able
10511 onLoad: function(data){
10512 // summary:
10513 // Event hook, is called after everything is loaded and widgetified
10514 // tags:
10515 // callback
10516 },
10517
10518 onUnload: function(){
10519 // summary:
10520 // Event hook, is called before old content is cleared
10521 // tags:
10522 // callback
10523 },
10524
10525 onDownloadStart: function(){
10526 // summary:
10527 // Called before download starts.
10528 // description:
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.
10532 // tags:
10533 // extension
10534 return this.loadingMessage;
10535 },
10536
10537 onContentError: function(/*Error*/ error){
10538 // summary:
10539 // Called on DOM faults, require faults etc. in content.
10540 //
10541 // In order to display an error message in the pane, return
10542 // the error message from this method, as an HTML string.
10543 //
10544 // By default (if this method is not overriden), it returns
10545 // nothing, so the error message is just printed to the console.
10546 // tags:
10547 // extension
10548 },
10549
10550 onDownloadError: function(/*Error*/ error){
10551 // summary:
10552 // Called when download error occurs.
10553 //
10554 // In order to display an error message in the pane, return
10555 // the error message from this method, as an HTML string.
10556 //
10557 // Default behavior (if this method is not overriden) is to display
10558 // the error message inside the pane.
10559 // tags:
10560 // extension
10561 return this.errorMessage;
10562 },
10563
10564 onDownloadEnd: function(){
10565 // summary:
10566 // Called when download is finished.
10567 // tags:
10568 // callback
10569 }
10570 });
10571
10572 }
10573
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");
10577
10578
10579
10580
10581
10582
10583 dojo.declare(
10584 "dijit.TooltipDialog",
10585 [dijit.layout.ContentPane, dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin],
10586 {
10587 // summary:
10588 // Pops up a dialog that appears like a Tooltip
10589
10590 // title: String
10591 // Description of tooltip dialog (required for a11y)
10592 title: "",
10593
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.
10599 doLayout: false,
10600
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
10605 autofocus: true,
10606
10607 // baseClass: [protected] String
10608 // The root className to use for the various states of this widget
10609 baseClass: "dijitTooltipDialog",
10610
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,
10615
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,
10620
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"),
10622
10623 _setTitleAttr: function(/*String*/ title){
10624 this.containerNode.title = title;
10625 this._set("title", title)
10626 },
10627
10628 postCreate: function(){
10629 this.inherited(arguments);
10630 this.connect(this.containerNode, "onkeypress", "_onKey");
10631 },
10632
10633 orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ corner){
10634 // summary:
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
10637 // directly.
10638 // tags:
10639 // protected
10640 var newC = "dijitTooltipAB" + (corner.charAt(1) == 'L' ? "Left" : "Right")
10641 + " dijitTooltip"
10642 + (corner.charAt(0) == 'T' ? "Below" : "Above");
10643
10644 dojo.replaceClass(this.domNode, newC, this._currentOrientClass || "");
10645 this._currentOrientClass = newC;
10646 },
10647
10648 focus: function(){
10649 // summary:
10650 // Focus on first field
10651 this._getFocusItems(this.containerNode);
10652 dijit.focus(this._firstFocusItem);
10653 },
10654
10655 onOpen: function(/*Object*/ pos){
10656 // summary:
10657 // Called when dialog is displayed.
10658 // This is called from the dijit.popup code, and should not be called directly.
10659 // tags:
10660 // protected
10661
10662 this.orient(this.domNode,pos.aroundCorner, pos.corner);
10663 this._onShow(); // lazy load trigger
10664 },
10665
10666 onClose: function(){
10667 // summary:
10668 // Called when dialog is hidden.
10669 // This is called from the dijit.popup code, and should not be called directly.
10670 // tags:
10671 // protected
10672 this.onHide();
10673 },
10674
10675 _onKey: function(/*Event*/ evt){
10676 // summary:
10677 // Handler for keyboard events
10678 // description:
10679 // Keep keyboard focus in dialog; close dialog on escape key
10680 // tags:
10681 // private
10682
10683 var node = evt.target;
10684 var dk = dojo.keys;
10685 if(evt.charOrCode === dk.TAB){
10686 this._getFocusItems(this.containerNode);
10687 }
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
10696 }
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
10701 }
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();
10707 }
10708 }
10709 }
10710 );
10711
10712 }
10713
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");
10717
10718
10719
10720
10721
10722
10723
10724
10725
10726
10727
10728
10729
10730
10731
10732 // dijit/TooltipDialog required for back-compat. TODO: remove in 2.0
10733
10734 /*=====
10735 dijit._underlay = function(kwArgs){
10736 // summary:
10737 // A shared instance of a `dijit.DialogUnderlay`
10738 //
10739 // description:
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.
10743 };
10744 =====*/
10745 dojo.declare(
10746 "dijit._DialogBase",
10747 [dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin, dijit._CssStateMixin],
10748 {
10749 // summary:
10750 // A modal dialog Widget
10751 //
10752 // description:
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.)
10756 //
10757 // example:
10758 // | <div dojoType="dijit.Dialog" href="test.html"></div>
10759 //
10760 // example:
10761 // | var foo = new dijit.Dialog({ title: "test dialog", content: "test content" };
10762 // | dojo.body().appendChild(foo.domNode);
10763 // | foo.startup();
10764
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"),
10766
10767 baseClass: "dijitDialog",
10768
10769 cssStateNodes: {
10770 closeButtonNode: "dijitDialogCloseIcon"
10771 },
10772
10773 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
10774 title: [
10775 { node: "titleNode", type: "innerHTML" },
10776 { node: "titleBar", type: "attribute" }
10777 ],
10778 "aria-describedby":""
10779 }),
10780
10781 // open: [readonly] Boolean
10782 // True if Dialog is currently displayed on screen.
10783 open: false,
10784
10785 // duration: Integer
10786 // The time in milliseconds it takes the dialog to fade in and out
10787 duration: dijit.defaultDuration,
10788
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
10793 refocus: true,
10794
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
10799 autofocus: true,
10800
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,
10805
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,
10810
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.
10816 doLayout: false,
10817
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.
10822 draggable: true,
10823
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>
10831 // </div>
10832 "aria-describedby":"",
10833
10834 postMixInProperties: function(){
10835 var _nlsResources = dojo.i18n.getLocalization("dijit", "common");
10836 dojo.mixin(this, _nlsResources);
10837 this.inherited(arguments);
10838 },
10839
10840 postCreate: function(){
10841 dojo.style(this.domNode, {
10842 display: "none",
10843 position:"absolute"
10844 });
10845 dojo.body().appendChild(this.domNode);
10846
10847 this.inherited(arguments);
10848
10849 this.connect(this, "onExecute", "hide");
10850 this.connect(this, "onCancel", "hide");
10851 this._modalconnects = [];
10852 },
10853
10854 onLoad: function(){
10855 // summary:
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.
10859 // tags:
10860 // callback
10861
10862 // when href is specified we need to reposition the dialog after the data is loaded
10863 // and find the focusable elements
10864 this._position();
10865 if(this.autofocus && dijit._DialogLevelManager.isTop(this)){
10866 this._getFocusItems(this.domNode);
10867 dijit.focus(this._firstFocusItem);
10868 }
10869 this.inherited(arguments);
10870 },
10871
10872 _endDrag: function(e){
10873 // summary:
10874 // Called after dragging the Dialog. Saves the position of the dialog in the viewport.
10875 // tags:
10876 // private
10877 if(e && e.node && e.node === this.domNode){
10878 this._relativePosition = dojo.position(e.node);
10879 }
10880 },
10881
10882 _setup: function(){
10883 // summary:
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).
10887 // tags:
10888 // private
10889
10890 var node = this.domNode;
10891
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");
10897 }else{
10898 dojo.addClass(node,"dijitDialogFixed");
10899 }
10900
10901 this.underlayAttrs = {
10902 dialogId: this.id,
10903 "class": dojo.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" ")
10904 };
10905 },
10906
10907 _size: function(){
10908 // summary:
10909 // If necessary, shrink dialog contents so dialog fits in viewport
10910 // tags:
10911 // private
10912
10913 this._checkIfSingleChild();
10914
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;
10921 }
10922 delete this._singleChildOriginalStyle;
10923 }else{
10924 dojo.style(this.containerNode, {
10925 width:"auto",
10926 height:"auto"
10927 });
10928 }
10929
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
10934
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));
10937
10938 if(this._singleChild && this._singleChild.resize){
10939 this._singleChildOriginalStyle = this._singleChild.domNode.style.cssText;
10940 this._singleChild.resize({w: w, h: h});
10941 }else{
10942 dojo.style(this.containerNode, {
10943 width: w + "px",
10944 height: h + "px",
10945 overflow: "auto",
10946 position: "relative" // workaround IE bug moving scrollbar or dragging dialog
10947 });
10948 }
10949 }else{
10950 if(this._singleChild && this._singleChild.resize){
10951 this._singleChild.resize();
10952 }
10953 }
10954 },
10955
10956 _position: function(){
10957 // summary:
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.
10962 // tags:
10963 // private
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))
10971 ;
10972 dojo.style(node,{
10973 left: l + "px",
10974 top: t + "px"
10975 });
10976 }
10977 },
10978
10979 _onKey: function(/*Event*/ evt){
10980 // summary:
10981 // Handles the keyboard events for accessibility reasons
10982 // tags:
10983 // private
10984
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);
10990 }
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
10996 }
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
11001 }
11002 dojo.stopEvent(evt);
11003 }else{
11004 // see if the key is for the dialog
11005 while(node){
11006 if(node == this.domNode || dojo.hasClass(node, "dijitPopup")){
11007 if(evt.charOrCode == dk.ESCAPE){
11008 this.onCancel();
11009 }else{
11010 return; // just let it go
11011 }
11012 }
11013 node = node.parentNode;
11014 }
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){
11020 try{
11021 this._firstFocusItem.focus();
11022 }catch(e){ /*squelch*/ }
11023 }
11024 }
11025 }
11026 },
11027
11028 show: function(){
11029 // summary:
11030 // Display the dialog
11031 // returns: dojo.Deferred
11032 // Deferred object that resolves when the display animation is complete
11033
11034 if(this.open){ return; }
11035
11036 if(!this._started){
11037 this.startup();
11038 }
11039
11040 // first time we show the dialog, there's some initialization stuff to do
11041 if(!this._alreadyInitialized){
11042 this._setup();
11043 this._alreadyInitialized=true;
11044 }
11045
11046 if(this._fadeOutDeferred){
11047 this._fadeOutDeferred.cancel();
11048 }
11049
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){
11058 this.layout();
11059 this._oldViewport = viewport;
11060 }
11061 }));
11062 this._modalconnects.push(dojo.connect(this.domNode, "onkeypress", this, "_onKey"));
11063
11064 dojo.style(this.domNode, {
11065 opacity:0,
11066 display:""
11067 });
11068
11069 this._set("open", true);
11070 this._onShow(); // lazy load trigger
11071
11072 this._size();
11073 this._position();
11074
11075 // fade-in Animation object, setup below
11076 var fadeIn;
11077
11078 this._fadeInDeferred = new dojo.Deferred(dojo.hitch(this, function(){
11079 fadeIn.stop();
11080 delete this._fadeInDeferred;
11081 }));
11082
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);
11088 }),
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);
11095 }
11096 this._fadeInDeferred.callback(true);
11097 delete this._fadeInDeferred;
11098 })
11099 }).play();
11100
11101 return this._fadeInDeferred;
11102 },
11103
11104 hide: function(){
11105 // summary:
11106 // Hide the dialog
11107 // returns: dojo.Deferred
11108 // Deferred object that resolves when the hide animation is complete
11109
11110 // if we haven't been initialized yet then we aren't showing and we can just return
11111 if(!this._alreadyInitialized){
11112 return;
11113 }
11114 if(this._fadeInDeferred){
11115 this._fadeInDeferred.cancel();
11116 }
11117
11118 // fade-in Animation object, setup below
11119 var fadeOut;
11120
11121 this._fadeOutDeferred = new dojo.Deferred(dojo.hitch(this, function(){
11122 fadeOut.stop();
11123 delete this._fadeOutDeferred;
11124 }));
11125
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);
11132 this.onHide();
11133 this._fadeOutDeferred.callback(true);
11134 delete this._fadeOutDeferred;
11135 })
11136 }).play();
11137
11138 if(this._scrollConnected){
11139 this._scrollConnected = false;
11140 }
11141 dojo.forEach(this._modalconnects, dojo.disconnect);
11142 this._modalconnects = [];
11143
11144 if(this._relativePosition){
11145 delete this._relativePosition;
11146 }
11147 this._set("open", false);
11148
11149 return this._fadeOutDeferred;
11150 },
11151
11152 layout: function(){
11153 // summary:
11154 // Position the Dialog and the underlay
11155 // tags:
11156 // private
11157 if(this.domNode.style.display != "none"){
11158 if(dijit._underlay){ // avoid race condition during show()
11159 dijit._underlay.layout();
11160 }
11161 this._position();
11162 }
11163 },
11164
11165 destroy: function(){
11166 if(this._fadeInDeferred){
11167 this._fadeInDeferred.cancel();
11168 }
11169 if(this._fadeOutDeferred){
11170 this._fadeOutDeferred.cancel();
11171 }
11172 if(this._moveable){
11173 this._moveable.destroy();
11174 }
11175 if(this._dndListener){
11176 dojo.unsubscribe(this._dndListener);
11177 }
11178 dojo.forEach(this._modalconnects, dojo.disconnect);
11179
11180 dijit._DialogLevelManager.hide(this);
11181
11182 this.inherited(arguments);
11183 }
11184 }
11185 );
11186
11187 dojo.declare(
11188 "dijit.Dialog",
11189 [dijit.layout.ContentPane, dijit._DialogBase],
11190 {}
11191 );
11192
11193 dijit._DialogLevelManager = {
11194 // summary:
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.
11198
11199 show: function(/*dijit._Widget*/ dialog, /*Object*/ underlayAttrs){
11200 // summary:
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.
11204 //
11205 // New dialog will be displayed on top of all currently displayed dialogs.
11206 //
11207 // Caller is responsible for setting focus in new dialog after the fade-in
11208 // animation completes.
11209
11210 var ds = dijit._dialogStack;
11211
11212 // Save current focus
11213 ds[ds.length-1].focus = dijit.getFocus(dialog);
11214
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);
11219 }else{
11220 underlay.set(dialog.underlayAttrs);
11221 }
11222
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
11226 underlay.show();
11227 }
11228 dojo.style(dijit._underlay.domNode, 'zIndex', zIndex - 1);
11229
11230 // Dialog
11231 dojo.style(dialog.domNode, 'zIndex', zIndex);
11232
11233 ds.push({dialog: dialog, underlayAttrs: underlayAttrs, zIndex: zIndex});
11234 },
11235
11236 hide: function(/*dijit._Widget*/ dialog){
11237 // summary:
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.
11241 //
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.
11244
11245 var ds = dijit._dialogStack;
11246
11247 if(ds[ds.length-1].dialog == dialog){
11248 // Removing the top (or only) dialog in the stack, return focus
11249 // to previous dialog
11250
11251 ds.pop();
11252
11253 var pd = ds[ds.length-1]; // the new active dialog (or the base page itself)
11254
11255 // Adjust underlay
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();
11262 }
11263 }else{
11264 // Popping back to previous dialog, adjust underlay
11265 dojo.style(dijit._underlay.domNode, 'zIndex', pd.zIndex - 1);
11266 dijit._underlay.set(pd.underlayAttrs);
11267 }
11268
11269 // Adjust focus
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;
11279 }
11280
11281 try{
11282 dijit.focus(focus);
11283 }catch(e){
11284 /* focus() will fail if user opened the dialog by clicking a non-focusable element */
11285 }
11286 }
11287 }else{
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);
11291 if(idx != -1){
11292 ds.splice(idx, 1);
11293 }
11294 }
11295 },
11296
11297 isTop: function(/*dijit._Widget*/ dialog){
11298 // summary:
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;
11302 }
11303 };
11304
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)
11312 // }
11313 dijit._dialogStack = [
11314 {dialog: null, focus: null, underlayAttrs: null} // entry for stuff at z-index: 0
11315 ];
11316
11317 }
11318
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");
11322
11323
11324
11325 dojo.declare("dijit._HasDropDown",
11326 null,
11327 {
11328 // summary:
11329 // Mixin for widgets that need drop down ability.
11330
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.
11335 _buttonNode: null,
11336
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,
11343
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,
11349
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.
11354 _aroundNode: null,
11355
11356 // dropDown: [protected] Widget
11357 // The widget to display as a popup. This widget *must* be
11358 // defined before the startup function is called.
11359 dropDown: null,
11360
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
11364 // default width
11365 autoWidth: true,
11366
11367 // forceWidth: [protected] Boolean
11368 // Set to true to make the drop down exactly as wide as this
11369 // widget. Overrides autoWidth.
11370 forceWidth: false,
11371
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
11376 maxHeight: 0,
11377
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:
11381 //
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
11388 //
11389 // The list is positions is tried, in order, until a position is found where the drop down fits
11390 // within the viewport.
11391 //
11392 dropDownPosition: ["below","above"],
11393
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,
11398
11399 _onDropDownMouseDown: function(/*Event*/ e){
11400 // summary:
11401 // Callback when the user mousedown's on the arrow icon
11402
11403 if(this.disabled || this.readOnly){ return; }
11404
11405 dojo.stopEvent(e);
11406
11407 this._docHandler = this.connect(dojo.doc, "onmouseup", "_onDropDownMouseUp");
11408
11409 this.toggleDropDown();
11410 },
11411
11412 _onDropDownMouseUp: function(/*Event?*/ e){
11413 // summary:
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.
11418 //
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);
11426 }
11427 var dropDown = this.dropDown, overMenu = false;
11428
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)){
11437 var t = e.target;
11438 while(t && !overMenu){
11439 if(dojo.hasClass(t, "dijitPopup")){
11440 overMenu = true;
11441 }else{
11442 t = t.parentNode;
11443 }
11444 }
11445 if(overMenu){
11446 t = e.target;
11447 if(dropDown.onItemClick){
11448 var menuItem;
11449 while(t && !(menuItem = dijit.byNode(t))){
11450 t = t.parentNode;
11451 }
11452 if(menuItem && menuItem.onClick && menuItem.getParent){
11453 menuItem.getParent().onItemClick(menuItem, e);
11454 }
11455 }
11456 return;
11457 }
11458 }
11459 }
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);
11464 }
11465 },
11466
11467 _onDropDownClick: function(/*Event*/ e){
11468 // the drop down was already opened on mousedown/keydown; just need to call stopEvent()
11469 if(this._stopClickEvents){
11470 dojo.stopEvent(e);
11471 }
11472 },
11473
11474 buildRendering: function(){
11475 this.inherited(arguments);
11476
11477 this._buttonNode = this._buttonNode || this.focusNode || this.domNode;
11478 this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode;
11479
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
11482 var defaultPos = {
11483 "after" : this.isLeftToRight() ? "Right" : "Left",
11484 "before" : this.isLeftToRight() ? "Left" : "Right",
11485 "above" : "Up",
11486 "below" : "Down",
11487 "left" : "Left",
11488 "right" : "Right"
11489 }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down";
11490 dojo.addClass(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton");
11491 },
11492
11493 postCreate: function(){
11494 // summary:
11495 // set up nodes and connect our mouse and keypress events
11496
11497 this.inherited(arguments);
11498
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");
11503 },
11504
11505 destroy: function(){
11506 if(this.dropDown){
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();
11511 }
11512 delete this.dropDown;
11513 }
11514 this.inherited(arguments);
11515 },
11516
11517 _onKey: function(/*Event*/ e){
11518 // summary:
11519 // Callback when the user presses a key while focused on the button node
11520
11521 if(this.disabled || this.readOnly){ return; }
11522
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 */
11527 dojo.stopEvent(e);
11528 return;
11529 }
11530 }
11531 if(d && this._opened && e.charOrCode == dojo.keys.ESCAPE){
11532 this.closeDropDown();
11533 dojo.stopEvent(e);
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;
11544 dojo.stopEvent(e);
11545 }
11546 },
11547
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
11553 if(d && d.focus){
11554 setTimeout(dojo.hitch(d, "focus"), 1);
11555 }
11556 }
11557 },
11558
11559 _onBlur: function(){
11560 // summary:
11561 // Called magically when focus has shifted away from this widget and it's dropdown
11562
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);
11568
11569 this.closeDropDown(focusMe);
11570
11571 this.inherited(arguments);
11572 },
11573
11574 isLoaded: function(){
11575 // summary:
11576 // Returns whether or not the dropdown is loaded. This can
11577 // be overridden in order to force a call to loadDropDown().
11578 // tags:
11579 // protected
11580
11581 return true;
11582 },
11583
11584 loadDropDown: function(/* Function */ loadCallback){
11585 // summary:
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.
11589 // tags:
11590 // protected
11591
11592 loadCallback();
11593 },
11594
11595 toggleDropDown: function(){
11596 // summary:
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
11600 // tags:
11601 // protected
11602
11603 if(this.disabled || this.readOnly){ return; }
11604 if(!this._opened){
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"));
11608 return;
11609 }else{
11610 this.openDropDown();
11611 }
11612 }else{
11613 this.closeDropDown();
11614 }
11615 },
11616
11617 openDropDown: function(){
11618 // summary:
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).
11621 // returns:
11622 // return value of dijit.popup.open()
11623 // tags:
11624 // protected
11625
11626 var dropDown = this.dropDown,
11627 ddNode = dropDown.domNode,
11628 aroundNode = this._aroundNode || this.domNode,
11629 self = this;
11630
11631 // Prepare our popup's height and honor maxHeight if it exists.
11632
11633 // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(),
11634 // ie, dependent on how much space is available (BK)
11635
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;
11641 }
11642 if(ddNode.style.height){
11643 this._explicitDDHeight = true;
11644 }
11645 }
11646
11647 // Code for resizing dropdown (height limitation, or increasing width to match my width)
11648 if(this.maxHeight || this.forceWidth || this.autoWidth){
11649 var myStyle = {
11650 display: "",
11651 visibility: "hidden"
11652 };
11653 if(!this._explicitDDWidth){
11654 myStyle.width = "";
11655 }
11656 if(!this._explicitDDHeight){
11657 myStyle.height = "";
11658 }
11659 dojo.style(ddNode, myStyle);
11660
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)));
11669 }
11670
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();
11675 }
11676
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"
11684 });
11685 if(overHeight){
11686 mb.h = maxHeight;
11687 if("w" in mb){
11688 mb.w += 16; // room for vertical scrollbar
11689 }
11690 }else{
11691 delete mb.h;
11692 }
11693
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);
11699 }else{
11700 delete mb.w;
11701 }
11702
11703 // And finally, resize the dropdown to calculated height and width
11704 if(dojo.isFunction(dropDown.resize)){
11705 dropDown.resize(mb);
11706 }else{
11707 dojo.marginBox(ddNode, mb);
11708 }
11709 }
11710
11711 var retVal = dijit.popup.open({
11712 parent: this,
11713 popup: dropDown,
11714 around: aroundNode,
11715 orient: dijit.getPopupAroundAlignment((this.dropDownPosition && this.dropDownPosition.length) ? this.dropDownPosition : ["below"],this.isLeftToRight()),
11716 onExecute: function(){
11717 self.closeDropDown(true);
11718 },
11719 onCancel: function(){
11720 self.closeDropDown(true);
11721 },
11722 onClose: function(){
11723 dojo.attr(self._popupStateNode, "popupActive", false);
11724 dojo.removeClass(self._popupStateNode, "dijitHasDropDownOpen");
11725 self._opened = false;
11726 }
11727 });
11728 dojo.attr(this._popupStateNode, "popupActive", "true");
11729 dojo.addClass(self._popupStateNode, "dijitHasDropDownOpen");
11730 this._opened=true;
11731
11732 // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown
11733 return retVal;
11734 },
11735
11736 closeDropDown: function(/*Boolean*/ focus){
11737 // summary:
11738 // Closes the drop down on this widget
11739 // focus:
11740 // If true, refocuses the button widget
11741 // tags:
11742 // protected
11743
11744 if(this._opened){
11745 if(focus){ this.focus(); }
11746 dijit.popup.close(this.dropDown);
11747 this._opened = false;
11748 }
11749 }
11750
11751 }
11752 );
11753
11754 }
11755
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");
11759
11760
11761
11762
11763
11764 dojo.declare("dijit.form.Button",
11765 dijit.form._FormWidget,
11766 {
11767 // summary:
11768 // Basically the same thing as a normal HTML button, but with special styling.
11769 // description:
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.
11773 // example:
11774 // | <button dojoType="dijit.form.Button" onClick="...">Hello world</button>
11775 //
11776 // example:
11777 // | var button1 = new dijit.form.Button({label: "hello world", onClick: foo});
11778 // | dojo.body().appendChild(button1.domNode);
11779
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.
11784 label: "",
11785
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.
11791 //
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.
11794 showLabel: true,
11795
11796 // iconClass: String
11797 // Class to apply to DOMNode in button to make it display an icon
11798 iconClass: "",
11799
11800 // type: String
11801 // Defines the type of button. "button", "submit", or "reset".
11802 type: "button",
11803
11804 baseClass: "dijitButton",
11805
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\">&#x25CF;</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdojoAttachPoint=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),
11807
11808 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
11809 value: "valueNode"
11810 }),
11811
11812 _onClick: function(/*Event*/ e){
11813 // summary:
11814 // Internal function to handle click actions
11815 if(this.disabled){
11816 return false;
11817 }
11818 this._clicked(); // widget click actions
11819 return this.onClick(e); // user click actions
11820 },
11821
11822 _onButtonClick: function(/*Event*/ e){
11823 // summary:
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);
11832 break;
11833 }
11834 }
11835 }else if(this.valueNode){
11836 this.valueNode.click();
11837 e.preventDefault(); // cancel BUTTON click and continue with hidden INPUT click
11838 }
11839 },
11840
11841 buildRendering: function(){
11842 this.inherited(arguments);
11843 dojo.setSelectable(this.focusNode, false);
11844 },
11845
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);
11853 }
11854 },
11855
11856 _setShowLabelAttr: function(val){
11857 if(this.containerNode){
11858 dojo.toggleClass(this.containerNode, "dijitDisplayNone", !val);
11859 }
11860 this._set("showLabel", val);
11861 },
11862
11863 onClick: function(/*Event*/ e){
11864 // summary:
11865 // Callback for when button is clicked.
11866 // If type="submit", return true to perform submit, or false to cancel it.
11867 // type:
11868 // callback
11869 return true; // Boolean
11870 },
11871
11872 _clicked: function(/*Event*/ e){
11873 // summary:
11874 // Internal overridable function for when the button is clicked
11875 },
11876
11877 setLabel: function(/*String*/ content){
11878 // summary:
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);
11882 },
11883
11884 _setLabelAttr: function(/*String*/ content){
11885 // summary:
11886 // Hook for set('label', ...) to work.
11887 // description:
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 || '');
11893 }
11894 },
11895
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)
11899
11900 var oldVal = this.iconClass || "dijitNoIcon",
11901 newVal = val || "dijitNoIcon";
11902 dojo.replaceClass(this.iconNode, newVal, oldVal);
11903 this._set("iconClass", val);
11904 }
11905 });
11906
11907
11908 dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container, dijit._HasDropDown], {
11909 // summary:
11910 // A button with a drop down
11911 //
11912 // example:
11913 // | <button dojoType="dijit.form.DropDownButton" label="Hello world">
11914 // | <div dojotype="dijit.Menu">...</div>
11915 // | </button>
11916 //
11917 // example:
11918 // | var button1 = new dijit.form.DropDownButton({ label: "hi", dropDown: new dijit.Menu(...) });
11919 // | dojo.body().appendChild(button1);
11920 //
11921
11922 baseClass : "dijitDropDownButton",
11923
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\">&#9660;</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),
11925
11926 _fillContent: function(){
11927 // Overrides Button._fillContent().
11928 //
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.
11932
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]);
11938
11939 // save pointer to srcNode so we can grab the drop down widget after it's instantiated
11940 this.dropDownContainer = this.srcNodeRef;
11941 }
11942 },
11943
11944 startup: function(){
11945 if(this._started){ return; }
11946
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;
11953 }
11954 if(this.dropDown){
11955 dijit.popup.hide(this.dropDown);
11956 }
11957
11958 this.inherited(arguments);
11959 },
11960
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));
11966 },
11967
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();
11976 });
11977 dropDown.refresh();
11978 }else{
11979 this.openDropDown();
11980 }
11981 },
11982
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;
11987 }
11988 });
11989
11990 dojo.declare("dijit.form.ComboButton", dijit.form.DropDownButton, {
11991 // summary:
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.
11995 //
11996 // example:
11997 // | <button dojoType="dijit.form.ComboButton" onClick="...">
11998 // | <span>Hello world</span>
11999 // | <div dojoType="dijit.Menu">...</div>
12000 // | </button>
12001 //
12002 // example:
12003 // | var button1 = new dijit.form.ComboButton({label: "hello world", onClick: foo, dropDown: "myMenu"});
12004 // | dojo.body().appendChild(button1.domNode);
12005 //
12006
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\">&#9660;</div\n\t\t></td\n\t\t><td style=\"display:none !important;\"\n\t\t\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" dojoAttachPoint=\"valueNode\"\n\t\t/></td></tr></tbody\n></table>\n"),
12008
12009 attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), {
12010 id: "",
12011 tabIndex: ["focusNode", "titleNode"],
12012 title: "titleNode"
12013 }),
12014
12015 // optionsTitle: String
12016 // Text that describes the options menu (accessibility)
12017 optionsTitle: "",
12018
12019 baseClass: "dijitComboButton",
12020
12021 // Set classes like dijitButtonContentsHover or dijitArrowButtonActive depending on
12022 // mouse action over specified node
12023 cssStateNodes: {
12024 "buttonNode": "dijitButtonNode",
12025 "titleNode": "dijitButtonContents",
12026 "_popupStateNode": "dijitDownArrowButton"
12027 },
12028
12029 _focusedNode: null,
12030
12031 _onButtonKeyPress: function(/*Event*/ evt){
12032 // summary:
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);
12037 }
12038 },
12039
12040 _onArrowKeyPress: function(/*Event*/ evt){
12041 // summary:
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);
12046 }
12047 },
12048
12049 focus: function(/*String*/ position){
12050 // summary:
12051 // Focuses this widget to according to position, if specified,
12052 // otherwise on arrow node
12053 // position:
12054 // "start" or "end"
12055 if(!this.disabled){
12056 dijit.focus(position == "start" ? this.titleNode : this._popupStateNode);
12057 }
12058 }
12059 });
12060
12061 dojo.declare("dijit.form.ToggleButton", dijit.form.Button, {
12062 // summary:
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
12065
12066 baseClass: "dijitToggleButton",
12067
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.
12073 checked: false,
12074
12075 attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), {
12076 checked:"focusNode"
12077 }),
12078
12079 _clicked: function(/*Event*/ evt){
12080 this.set('checked', !this.checked);
12081 },
12082
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);
12088 },
12089
12090 setChecked: function(/*Boolean*/ checked){
12091 // summary:
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);
12095 },
12096
12097 reset: function(){
12098 // summary:
12099 // Reset the widget's value to what it was at initialization time
12100
12101 this._hasBeenBlurred = false;
12102
12103 // set checked state to original setting
12104 this.set('checked', this.params.checked || false);
12105 }
12106 });
12107
12108 }
12109
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");
12113
12114
12115
12116
12117 }
12118
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");
12122
12123
12124
12125 dojo.declare(
12126 "dijit.form.CheckBox",
12127 dijit.form.ToggleButton,
12128 {
12129 // summary:
12130 // Same as an HTML checkbox, but with fancy styling.
12131 //
12132 // description:
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.
12137 //
12138 // There are two modes:
12139 // 1. High contrast mode
12140 // 2. Normal mode
12141 //
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.
12145
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"),
12147
12148 baseClass: "dijitCheckBox",
12149
12150 // type: [private] String
12151 // type attribute on <input> node.
12152 // Overrides `dijit.form.Button.type`. Users should not change this value.
12153 type: "checkbox",
12154
12155 // value: String
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).
12158 //
12159 // However, get('value') will return either the string or false depending on
12160 // whether or not the checkbox is checked.
12161 //
12162 // set('value', string) will check the checkbox and change the value to the
12163 // specified string
12164 //
12165 // set('value', boolean) will change the checked state.
12166 value: "on",
12167
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.
12172 readOnly: false,
12173
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"
12178 }),
12179
12180 _setReadOnlyAttr: function(/*Boolean*/ value){
12181 this._set("readOnly", value);
12182 dojo.attr(this.focusNode, 'readOnly', value);
12183 dijit.setWaiState(this.focusNode, "readonly", value);
12184 },
12185
12186 _setValueAttr: function(/*String|Boolean*/ newValue, /*Boolean*/ priorityChange){
12187 // summary:
12188 // Handler for value= attribute to constructor, and also calls to
12189 // set('value', val).
12190 // description:
12191 // During initialization, just saves as attribute to the <input type=checkbox>.
12192 //
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);
12201 newValue = true;
12202 }
12203 if(this._created){
12204 this.set('checked', newValue, priorityChange);
12205 }
12206 },
12207 _getValueAttr: function(){
12208 // summary:
12209 // Hook so get('value') works.
12210 // description:
12211 // If the CheckBox is checked, returns the value attribute.
12212 // Otherwise returns false.
12213 return (this.checked ? this.value : false);
12214 },
12215
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,
12219
12220 postMixInProperties: function(){
12221 if(this.value == ""){
12222 this.value = "on";
12223 }
12224
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" : "";
12229
12230 this.inherited(arguments);
12231 },
12232
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
12236 },
12237
12238 reset: function(){
12239 // Override ToggleButton.reset()
12240
12241 this._hasBeenBlurred = false;
12242
12243 this.set('checked', this.params.checked || false);
12244
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);
12248 },
12249
12250 _onFocus: function(){
12251 if(this.id){
12252 dojo.query("label[for='"+this.id+"']").addClass("dijitFocusedLabel");
12253 }
12254 this.inherited(arguments);
12255 },
12256
12257 _onBlur: function(){
12258 if(this.id){
12259 dojo.query("label[for='"+this.id+"']").removeClass("dijitFocusedLabel");
12260 }
12261 this.inherited(arguments);
12262 },
12263
12264 _onClick: function(/*Event*/ e){
12265 // summary:
12266 // Internal function to handle click actions - need to check
12267 // readOnly, since button no longer does that check.
12268 if(this.readOnly){
12269 dojo.stopEvent(e);
12270 return false;
12271 }
12272 return this.inherited(arguments);
12273 }
12274 }
12275 );
12276
12277 dojo.declare(
12278 "dijit.form.RadioButton",
12279 dijit.form.CheckBox,
12280 {
12281 // summary:
12282 // Same as an HTML radio, but with fancy styling.
12283
12284 type: "radio",
12285 baseClass: "dijitRadio",
12286
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; }
12291 if(value){
12292 var _this = this;
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);
12300 }
12301 }
12302 }
12303 );
12304 }
12305 },
12306
12307 _clicked: function(/*Event*/ e){
12308 if(!this.checked){
12309 this.set('checked', true);
12310 }
12311 }
12312 }
12313 );
12314
12315 }
12316
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");
12320
12321
12322
12323
12324 }
12325
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");
12329
12330 dojo.getObject("regexp", true, dojo);
12331
12332 /*=====
12333 dojo.regexp = {
12334 // summary: Regular expressions and Builder resources
12335 };
12336 =====*/
12337
12338 dojo.regexp.escapeString = function(/*String*/str, /*String?*/except){
12339 // summary:
12340 // Adds escape sequences for special characters in regular expressions
12341 // except:
12342 // a String with special characters to be left unescaped
12343
12344 return str.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(ch){
12345 if(except && except.indexOf(ch) != -1){
12346 return ch;
12347 }
12348 return "\\" + ch;
12349 }); // String
12350 };
12351
12352 dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boolean?*/nonCapture){
12353 // summary:
12354 // Builds a regular expression that groups subexpressions
12355 // description:
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.
12361 // arr:
12362 // A single value or an array of values.
12363 // re:
12364 // A function. Takes one parameter and converts it to a regular
12365 // expression.
12366 // nonCapture:
12367 // If true, uses non-capturing match, otherwise matches are retained
12368 // by regular expression. Defaults to false
12369
12370 // case 1: a is a single value.
12371 if(!(arr instanceof Array)){
12372 return re(arr); // String
12373 }
12374
12375 // case 2: a is an array
12376 var b = [];
12377 for(var i = 0; i < arr.length; i++){
12378 // convert each elem to a RE
12379 b.push(re(arr[i]));
12380 }
12381
12382 // join the REs as alternatives in a RE group.
12383 return dojo.regexp.group(b.join("|"), nonCapture); // String
12384 };
12385
12386 dojo.regexp.group = function(/*String*/expression, /*Boolean?*/nonCapture){
12387 // summary:
12388 // adds group match to expression
12389 // nonCapture:
12390 // If true, uses non-capturing match, otherwise matches are retained
12391 // by regular expression.
12392 return "(" + (nonCapture ? "?:":"") + expression + ")"; // String
12393 };
12394
12395 }
12396
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");
12400
12401 dojo.getObject("data.util.sorter", true, dojo);
12402
12403 dojo.data.util.sorter.basicComparator = function( /*anything*/ a,
12404 /*anything*/ b){
12405 // summary:
12406 // Basic comparision function that compares if an item is greater or less than another item
12407 // description:
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.
12411
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.
12416 var r = -1;
12417 if(a === null){
12418 a = undefined;
12419 }
12420 if(b === null){
12421 b = undefined;
12422 }
12423 if(a == b){
12424 r = 0;
12425 }else if(a > b || a == null){
12426 r = 1;
12427 }
12428 return r; //int {-1,0,1}
12429 };
12430
12431 dojo.data.util.sorter.createSortFunction = function( /* attributes array */sortSpec,
12432 /*dojo.data.core.Read*/ store){
12433 // summary:
12434 // Helper function to generate the sorting function based off the list of sort attributes.
12435 // description:
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.
12440 //
12441 // sortSpec: array
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:
12444 // {
12445 // attribute: "attributeName-string" || attribute,
12446 // descending: true|false; // Default is false.
12447 // }
12448 // store: object
12449 // The datastore object to look up item values from.
12450 //
12451 var sortFunctions=[];
12452
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
12460 };
12461 }
12462 var sortAttribute;
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;
12468 if(attr){
12469 var dir = (sortAttribute.descending) ? -1 : 1;
12470 var comp = bc;
12471 if(map){
12472 if(typeof attr !== "string" && ("toString" in attr)){
12473 attr = attr.toString();
12474 }
12475 comp = map[attr] || bc;
12476 }
12477 sortFunctions.push(createSortFunction(attr,
12478 dir, comp, store));
12479 }
12480 }
12481 return function(rowA, rowB){
12482 var i=0;
12483 while(i < sortFunctions.length){
12484 var ret = sortFunctions[i++](rowA, rowB);
12485 if(ret !== 0){
12486 return ret;//int
12487 }
12488 }
12489 return 0; //int
12490 }; // Function
12491 };
12492
12493 }
12494
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");
12498
12499
12500 dojo.getObject("data.util.simpleFetch", true, dojo);
12501
12502 dojo.data.util.simpleFetch.fetch = function(/* Object? */ request){
12503 // summary:
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.
12527 //
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;
12532 }
12533 var self = this;
12534
12535 var _errorHandler = function(errorData, requestObject){
12536 if(requestObject.onError){
12537 var scope = requestObject.scope || dojo.global;
12538 requestObject.onError.call(scope, errorData, requestObject);
12539 }
12540 };
12541
12542 var _fetchHandler = function(items, requestObject){
12543 var oldAbortFunction = requestObject.abort || null;
12544 var aborted = false;
12545
12546 var startIndex = requestObject.start?requestObject.start:0;
12547 var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length;
12548
12549 requestObject.abort = function(){
12550 aborted = true;
12551 if(oldAbortFunction){
12552 oldAbortFunction.call(requestObject);
12553 }
12554 };
12555
12556 var scope = requestObject.scope || dojo.global;
12557 if(!requestObject.store){
12558 requestObject.store = self;
12559 }
12560 if(requestObject.onBegin){
12561 requestObject.onBegin.call(scope, items.length, requestObject);
12562 }
12563 if(requestObject.sort){
12564 items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self));
12565 }
12566 if(requestObject.onItem){
12567 for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){
12568 var item = items[i];
12569 if(!aborted){
12570 requestObject.onItem.call(scope, item, requestObject);
12571 }
12572 }
12573 }
12574 if(requestObject.onComplete && !aborted){
12575 var subset = null;
12576 if(!requestObject.onItem){
12577 subset = items.slice(startIndex, endIndex);
12578 }
12579 requestObject.onComplete.call(scope, subset, requestObject);
12580 }
12581 };
12582 this._fetchItems(request, _fetchHandler, _errorHandler);
12583 return request; // Object
12584 };
12585
12586 }
12587
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");
12591
12592 dojo.getObject("data.util.filter", true, dojo);
12593
12594 dojo.data.util.filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/ ignoreCase){
12595 // summary:
12596 // Helper function to convert a simple pattern to a regular expression for matching.
12597 // description:
12598 // Returns a regular expression object that conforms to the defined conversion rules.
12599 // For example:
12600 // ca* -> /^ca.*$/
12601 // *ca* -> /^.*ca.*$/
12602 // *c\*a* -> /^.*c\*a.*$/
12603 // *c\*a?* -> /^.*c\*a..*$/
12604 // and so on.
12605 //
12606 // pattern: string
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.
12613 //
12614 // ignoreCase:
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.
12617
12618 var rxp = "^";
12619 var c = null;
12620 for(var i = 0; i < pattern.length; i++){
12621 c = pattern.charAt(i);
12622 switch(c){
12623 case '\\':
12624 rxp += c;
12625 i++;
12626 rxp += pattern.charAt(i);
12627 break;
12628 case '*':
12629 rxp += ".*"; break;
12630 case '?':
12631 rxp += "."; break;
12632 case '$':
12633 case '^':
12634 case '/':
12635 case '+':
12636 case '.':
12637 case '|':
12638 case '(':
12639 case ')':
12640 case '{':
12641 case '}':
12642 case '[':
12643 case ']':
12644 rxp += "\\"; //fallthrough
12645 default:
12646 rxp += c;
12647 }
12648 }
12649 rxp += "$";
12650 if(ignoreCase){
12651 return new RegExp(rxp,"mi"); //RegExp
12652 }else{
12653 return new RegExp(rxp,"m"); //RegExp
12654 }
12655
12656 };
12657
12658 }
12659
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");
12663
12664
12665
12666 dojo.declare(
12667 "dijit.form.TextBox",
12668 dijit.form._FormValueWidget,
12669 {
12670 // summary:
12671 // A base class for textbox form inputs
12672
12673 // trim: Boolean
12674 // Removes leading and trailing whitespace if true. Default is false.
12675 trim: false,
12676
12677 // uppercase: Boolean
12678 // Converts all characters to uppercase if true. Default is false.
12679 uppercase: false,
12680
12681 // lowercase: Boolean
12682 // Converts all characters to lowercase if true. Default is false.
12683 lowercase: false,
12684
12685 // propercase: Boolean
12686 // Converts the first character of each word to uppercase if true.
12687 propercase: false,
12688
12689 // maxLength: String
12690 // HTML INPUT tag maxLength declaration.
12691 maxLength: "",
12692
12693 // selectOnClick: [const] Boolean
12694 // If true, all text will be selected when focused with mouse
12695 selectOnClick: false,
12696
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).
12700 placeHolder: "",
12701
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} />',
12704
12705 _buttonInputDisabled: dojo.isIE ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events
12706
12707 baseClass: "dijitTextBox",
12708
12709 attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, {
12710 maxLength: "focusNode"
12711 }),
12712
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;
12717 }
12718 this.inherited(arguments);
12719 },
12720
12721 _setPlaceHolderAttr: function(v){
12722 this._set("placeHolder", v);
12723 if(!this._phspan){
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.
12728 */
12729 this._phspan = dojo.create('span',{className:'dijitPlaceHolder dijitInputField'},this.textbox,'after');
12730 }
12731 this._phspan.innerHTML="";
12732 this._phspan.appendChild(document.createTextNode(v));
12733
12734 this._updatePlaceHolder();
12735 },
12736
12737 _updatePlaceHolder: function(){
12738 if(this._phspan){
12739 this._phspan.style.display=(this.placeHolder&&!this._focused&&!this.textbox.value)?"":"none";
12740 }
12741 },
12742
12743 _getValueAttr: function(){
12744 // summary:
12745 // Hook so get('value') works as we like.
12746 // description:
12747 // For `dijit.form.TextBox` this basically returns the value of the <input>.
12748 //
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);
12754 },
12755
12756 _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
12757 // summary:
12758 // Hook so set('value', ...) works.
12759 //
12760 // description:
12761 // Sets the value of the widget to "value" which can be of
12762 // any type as determined by the widget.
12763 //
12764 // value:
12765 // The visual element value is also set to a corresponding,
12766 // but not necessarily the same, value.
12767 //
12768 // formattedValue:
12769 // If specified, used to set the visual element value,
12770 // otherwise a computed visual value is used.
12771 //
12772 // priorityChange:
12773 // If true, an onChange event is fired immediately instead of
12774 // waiting for the next blur event.
12775
12776 var filteredValue;
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 = ''; }
12785 }
12786 }
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"));
12790 }
12791
12792 this._updatePlaceHolder();
12793
12794 this.inherited(arguments, [filteredValue, priorityChange]);
12795 },
12796
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.
12801 //
12802 // Setting 'displayedValue' through set('displayedValue', ...)
12803 // updates 'value', and vice-versa. Otherwise 'value' is updated
12804 // from 'displayedValue' periodically, like onBlur etc.
12805 //
12806 // TODO: move declaration to MappedTextBox?
12807 // Problem is that ComboBox references displayedValue,
12808 // for benefit of FilteringSelect.
12809 displayedValue: "",
12810
12811 getDisplayedValue: function(){
12812 // summary:
12813 // Deprecated. Use get('displayedValue') instead.
12814 // tags:
12815 // deprecated
12816 dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use set('displayedValue') instead.", "", "2.0");
12817 return this.get('displayedValue');
12818 },
12819
12820 _getDisplayedValueAttr: function(){
12821 // summary:
12822 // Hook so get('displayedValue') works.
12823 // description:
12824 // Returns the displayed value (what the user sees on the screen),
12825 // after filtering (ie, trimming spaces etc.).
12826 //
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)
12830
12831 // TODO: maybe we should update this.displayedValue on every keystroke so that we don't need
12832 // this method
12833 // TODO: this isn't really the displayed value when the user is typing
12834 return this.filter(this.textbox.value);
12835 },
12836
12837 setDisplayedValue: function(/*String*/ value){
12838 // summary:
12839 // Deprecated. Use set('displayedValue', ...) instead.
12840 // tags:
12841 // deprecated
12842 dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use set('displayedValue', ...) instead.", "", "2.0");
12843 this.set('displayedValue', value);
12844 },
12845
12846 _setDisplayedValueAttr: function(/*String*/ value){
12847 // summary:
12848 // Hook so set('displayedValue', ...) works.
12849 // description:
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.
12853
12854 if(value === null || value === undefined){ value = '' }
12855 else if(typeof value != "string"){ value = String(value) }
12856
12857 this.textbox.value = value;
12858
12859 // sets the serialized value to something corresponding to specified displayedValue
12860 // (if possible), and also updates the textbox.value, for example converting "123"
12861 // to "123.00"
12862 this._setValueAttr(this.get('value'), undefined);
12863
12864 this._set("displayedValue", this.get('displayedValue'));
12865 },
12866
12867 format: function(/*String*/ value, /*Object*/ constraints){
12868 // summary:
12869 // Replacable function to convert a value to a properly formatted string.
12870 // tags:
12871 // protected extension
12872 return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
12873 },
12874
12875 parse: function(/*String*/ value, /*Object*/ constraints){
12876 // summary:
12877 // Replacable function to convert a formatted string to a value
12878 // tags:
12879 // protected extension
12880
12881 return value; // String
12882 },
12883
12884 _refreshState: function(){
12885 // summary:
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.
12889 // tags:
12890 // protected
12891 },
12892
12893 _onInput: function(e){
12894 if(e && e.type && /key/i.test(e.type) && e.keyCode){
12895 switch(e.keyCode){
12896 case dojo.keys.SHIFT:
12897 case dojo.keys.ALT:
12898 case dojo.keys.CTRL:
12899 case dojo.keys.TAB:
12900 return;
12901 }
12902 }
12903 if(this.intermediateChanges){
12904 var _this = this;
12905 // the setTimeout allows the key to post to the widget input box
12906 setTimeout(function(){ _this._handleOnChange(_this.get('value'), false); }, 0);
12907 }
12908 this._refreshState();
12909
12910 // In case someone is watch()'ing for changes to displayedValue
12911 this._set("displayedValue", this.get("displayedValue"));
12912 },
12913
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);
12919 if(s){
12920 var ff = s.fontFamily;
12921 if(ff){
12922 var inputs = this.domNode.getElementsByTagName("INPUT");
12923 if(inputs){
12924 for(var i=0; i < inputs.length; i++){
12925 inputs[i].style.fontFamily = ff;
12926 }
12927 }
12928 }
12929 }
12930 }), 0);
12931 }
12932
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
12936
12937 this.inherited(arguments);
12938
12939 if(dojo.isMoz || dojo.isOpera){
12940 this.connect(this.textbox, "oninput", "_onInput");
12941 }else{
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");
12946 }
12947 },
12948
12949 _blankValue: '', // if the textbox is blank, what value should be reported
12950 filter: function(val){
12951 // summary:
12952 // Auto-corrections (such as trimming) that are applied to textbox
12953 // value on blur or form submit.
12954 // description:
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.
12959 //
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().
12963 //
12964 // TODO: break this into two methods in 2.0
12965 //
12966 // tags:
12967 // protected extension
12968 if(val === null){ return this._blankValue; }
12969 if(typeof val != "string"){ return val; }
12970 if(this.trim){
12971 val = dojo.trim(val);
12972 }
12973 if(this.uppercase){
12974 val = val.toUpperCase();
12975 }
12976 if(this.lowercase){
12977 val = val.toLowerCase();
12978 }
12979 if(this.propercase){
12980 val = val.replace(/[^\s]+/g, function(word){
12981 return word.substring(0,1).toUpperCase() + word.substring(1);
12982 });
12983 }
12984 return val;
12985 },
12986
12987 _setBlurValue: function(){
12988 this._setValueAttr(this.get('value'), true);
12989 },
12990
12991 _onBlur: function(e){
12992 if(this.disabled){ return; }
12993 this._setBlurValue();
12994 this.inherited(arguments);
12995
12996 if(this._selectOnClickHandle){
12997 this.disconnect(this._selectOnClickHandle);
12998 }
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
13001 }
13002
13003 this._updatePlaceHolder();
13004 },
13005
13006 _onFocus: function(/*String*/ by){
13007 if(this.disabled || this.readOnly){ return; }
13008
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
13015 // the selection.
13016 this.disconnect(this._selectOnClickHandle);
13017
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;
13021 if(dojo.isIE){
13022 var range = dojo.doc.selection.createRange();
13023 var parent = range.parentElement();
13024 textIsNotSelected = parent == this.textbox && range.text.length == 0;
13025 }else{
13026 textIsNotSelected = this.textbox.selectionStart == this.textbox.selectionEnd;
13027 }
13028 if(textIsNotSelected){
13029 dijit.selectInputText(this.textbox);
13030 }
13031 });
13032 }
13033
13034 this._updatePlaceHolder();
13035
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);
13039
13040 this._refreshState();
13041 },
13042
13043 reset: function(){
13044 // Overrides dijit._FormWidget.reset().
13045 // Additionally resets the displayed textbox value to ''
13046 this.textbox.value = '';
13047 this.inherited(arguments);
13048 }
13049 }
13050 );
13051
13052 dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
13053 // summary:
13054 // Select text in the input element argument, from start (default 0), to stop (default end).
13055
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();
13066 r.collapse(true);
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);
13070 r.select();
13071 }
13072 }else if(_window["getSelection"]){
13073 if(element.setSelectionRange){
13074 element.setSelectionRange(start, stop);
13075 }
13076 }
13077 };
13078
13079 }
13080
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");
13084
13085
13086
13087
13088 dojo.declare(
13089 "dijit._MasterTooltip",
13090 [dijit._Widget, dijit._Templated],
13091 {
13092 // summary:
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
13096 // the markup
13097 // tags:
13098 // protected
13099
13100 // duration: Integer
13101 // Milliseconds to fade in/fade out
13102 duration: dijit.defaultDuration,
13103
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"),
13105
13106 postCreate: function(){
13107 dojo.body().appendChild(this.domNode);
13108
13109 this.bgIframe = new dijit.BackgroundIframe(this.domNode);
13110
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") });
13114 },
13115
13116 show: function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
13117 // summary:
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)
13120
13121 if(this.aroundNode && this.aroundNode === aroundNode){
13122 return;
13123 }
13124
13125 // reset width; it may have been set by orient() on a previous tooltip show()
13126 this.domNode.width = "auto";
13127
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;
13131 return;
13132 }
13133 this.containerNode.innerHTML=innerHTML;
13134
13135 var pos = dijit.placeOnScreenAroundElement(this.domNode, aroundNode, dijit.getPopupAroundAlignment((position && position.length) ? position : dijit.Tooltip.defaultPosition, !rtl), dojo.hitch(this, "orient"));
13136
13137 // show it
13138 dojo.style(this.domNode, "opacity", 0);
13139 this.fadeIn.play();
13140 this.isShowingNow = true;
13141 this.aroundNode = aroundNode;
13142 },
13143
13144 orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ tooltipCorner, /*Object*/ spaceAvailable, /*Object*/ aroundNodeCoords){
13145 // summary:
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
13149 // tags:
13150 // protected
13151 this.connectorNode.style.top = ""; //reset to default
13152
13153 //Adjust the spaceAvailable width, without changing the spaceAvailable object
13154 var tooltipSpaceAvaliableWidth = spaceAvailable.w - this.connectorNode.offsetWidth;
13155
13156 node.className = "dijitTooltip " +
13157 {
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];
13165
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);
13169
13170 var width = Math.min((Math.max(tooltipSpaceAvaliableWidth,1)), size.w);
13171 var widthWasReduced = width < size.w;
13172
13173 this.domNode.style.width = width+"px";
13174
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";
13183 }
13184 }
13185
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 = "";
13195 }else{
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 = "";
13203 }
13204 }else{
13205 // reset the tooltip back to the defaults
13206 this.connectorNode.style.top = "";
13207 this.connectorNode.style.bottom = "";
13208 }
13209
13210 return Math.max(0, size.w - tooltipSpaceAvaliableWidth);
13211 },
13212
13213 _onShow: function(){
13214 // summary:
13215 // Called at end of fade-in operation
13216 // tags:
13217 // protected
13218 if(dojo.isIE){
13219 // the arrow won't show up on a node w/an opacity filter
13220 this.domNode.style.filter="";
13221 }
13222 },
13223
13224 hide: function(aroundNode){
13225 // summary:
13226 // Hide the tooltip
13227
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()
13231 this._onDeck=null;
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();
13238 }else{
13239 // just ignore the call, it's for a tooltip that has already been erased
13240 }
13241 },
13242
13243 _onHide: function(){
13244 // summary:
13245 // Called at end of fade-out operation
13246 // tags:
13247 // protected
13248
13249 this.domNode.style.cssText=""; // to position offscreen again
13250 this.containerNode.innerHTML="";
13251 if(this._onDeck){
13252 // a show request has been queued up; do it now
13253 this.show.apply(this, this._onDeck);
13254 this._onDeck=null;
13255 }
13256 }
13257
13258 }
13259 );
13260
13261 dijit.showTooltip = function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
13262 // summary:
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);
13268 };
13269
13270 dijit.hideTooltip = function(aroundNode){
13271 // summary:
13272 // Hide the tooltip
13273 if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); }
13274 return dijit._masterTT.hide(aroundNode);
13275 };
13276
13277 dojo.declare(
13278 "dijit.Tooltip",
13279 dijit._Widget,
13280 {
13281 // summary:
13282 // Pops up a tooltip (a help message) when you hover over a node.
13283
13284 // label: String
13285 // Text to display in the tooltip.
13286 // Specified as innerHTML when creating the widget from markup.
13287 label: "",
13288
13289 // showDelay: Integer
13290 // Number of milliseconds to wait after hovering over/focusing on the object, before
13291 // the tooltip is displayed.
13292 showDelay: 400,
13293
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.
13297 connectId: [],
13298
13299 // position: String[]
13300 // See description of `dijit.Tooltip.defaultPosition` for details on position parameter.
13301 position: [],
13302
13303 _setConnectIdAttr: function(/*String*/ newId){
13304 // summary:
13305 // Connect to node(s) (specified by id)
13306
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"));
13310 }, this);
13311
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);
13316 return node ? [
13317 this.connect(node, "onmouseenter", "_onTargetMouseEnter"),
13318 this.connect(node, "onmouseleave", "_onTargetMouseLeave"),
13319 this.connect(node, "onfocus", "_onTargetFocus"),
13320 this.connect(node, "onblur", "_onTargetBlur")
13321 ] : [];
13322 }, this);
13323
13324 this._set("connectId", newId);
13325
13326 this._connectIds = ary; // save as array
13327 },
13328
13329 addTarget: function(/*DOMNODE || String*/ node){
13330 // summary:
13331 // Attach tooltip to specified node if it's not already connected
13332
13333 // TODO: remove in 2.0 and just use set("connectId", ...) interface
13334
13335 var id = node.id || node;
13336 if(dojo.indexOf(this._connectIds, id) == -1){
13337 this.set("connectId", this._connectIds.concat(id));
13338 }
13339 },
13340
13341 removeTarget: function(/*DOMNODE || String*/ node){
13342 // summary:
13343 // Detach tooltip from specified node
13344
13345 // TODO: remove in 2.0 and just use set("connectId", ...) interface
13346
13347 var id = node.id || node, // map from DOMNode back to plain id string
13348 idx = dojo.indexOf(this._connectIds, id);
13349 if(idx >= 0){
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);
13353 }
13354 },
13355
13356 buildRendering: function(){
13357 this.inherited(arguments);
13358 dojo.addClass(this.domNode,"dijitTooltipData");
13359 },
13360
13361 startup: function(){
13362 this.inherited(arguments);
13363
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);
13368 },
13369
13370 _onTargetMouseEnter: function(/*Event*/ e){
13371 // summary:
13372 // Handler for mouseenter event on the target node
13373 // tags:
13374 // private
13375 this._onHover(e);
13376 },
13377
13378 _onTargetMouseLeave: function(/*Event*/ e){
13379 // summary:
13380 // Handler for mouseleave event on the target node
13381 // tags:
13382 // private
13383 this._onUnHover(e);
13384 },
13385
13386 _onTargetFocus: function(/*Event*/ e){
13387 // summary:
13388 // Handler for focus event on the target node
13389 // tags:
13390 // private
13391
13392 this._focus = true;
13393 this._onHover(e);
13394 },
13395
13396 _onTargetBlur: function(/*Event*/ e){
13397 // summary:
13398 // Handler for blur event on the target node
13399 // tags:
13400 // private
13401
13402 this._focus = false;
13403 this._onUnHover(e);
13404 },
13405
13406 _onHover: function(/*Event*/ e){
13407 // summary:
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.
13410 // tags:
13411 // private
13412 if(!this._showTimer){
13413 var target = e.target;
13414 this._showTimer = setTimeout(dojo.hitch(this, function(){this.open(target)}), this.showDelay);
13415 }
13416 },
13417
13418 _onUnHover: function(/*Event*/ e){
13419 // summary:
13420 // Despite the name of this method, it actually handles both mouseleave and blur
13421 // events on the target node, hiding the tooltip.
13422 // tags:
13423 // private
13424
13425 // keep a tooltip open if the associated element still has focus (even though the
13426 // mouse moved away)
13427 if(this._focus){ return; }
13428
13429 if(this._showTimer){
13430 clearTimeout(this._showTimer);
13431 delete this._showTimer;
13432 }
13433 this.close();
13434 },
13435
13436 open: function(/*DomNode*/ target){
13437 // summary:
13438 // Display the tooltip; usually not called directly.
13439 // tags:
13440 // private
13441
13442 if(this._showTimer){
13443 clearTimeout(this._showTimer);
13444 delete this._showTimer;
13445 }
13446 dijit.showTooltip(this.label || this.domNode.innerHTML, target, this.position, !this.isLeftToRight());
13447
13448 this._connectNode = target;
13449 this.onShow(target, this.position);
13450 },
13451
13452 close: function(){
13453 // summary:
13454 // Hide the tooltip or cancel timer for show of tooltip
13455 // tags:
13456 // private
13457
13458 if(this._connectNode){
13459 // if tooltip is currently shown
13460 dijit.hideTooltip(this._connectNode);
13461 delete this._connectNode;
13462 this.onHide();
13463 }
13464 if(this._showTimer){
13465 // if tooltip is scheduled to be shown (after a brief delay)
13466 clearTimeout(this._showTimer);
13467 delete this._showTimer;
13468 }
13469 },
13470
13471 onShow: function(target, position){
13472 // summary:
13473 // Called when the tooltip is shown
13474 // tags:
13475 // callback
13476 },
13477
13478 onHide: function(){
13479 // summary:
13480 // Called when the tooltip is hidden
13481 // tags:
13482 // callback
13483 },
13484
13485 uninitialize: function(){
13486 this.close();
13487 this.inherited(arguments);
13488 }
13489 }
13490 );
13491
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:
13495 //
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
13502 //
13503 // The list is positions is tried, in order, until a position is found where the tooltip fits
13504 // within the viewport.
13505 //
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"];
13512
13513 }
13514
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");
13518
13519
13520
13521
13522
13523
13524 /*=====
13525 dijit.form.ValidationTextBox.__Constraints = function(){
13526 // locale: String
13527 // locale used for validation, picks up value from this widget's lang attribute
13528 // _flags_: anything
13529 // various flags passed to regExpGen function
13530 this.locale = "";
13531 this._flags_ = "";
13532 }
13533 =====*/
13534
13535 dojo.declare(
13536 "dijit.form.ValidationTextBox",
13537 dijit.form.TextBox,
13538 {
13539 // summary:
13540 // Base class for textbox widgets with the ability to validate content of various types and provide user feedback.
13541 // tags:
13542 // protected
13543
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=\"&#935; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
13545 baseClass: "dijitTextBox dijitValidationTextBox",
13546
13547 // required: Boolean
13548 // User is required to enter data into this field.
13549 required: false,
13550
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.
13556 //
13557 // Message disappears when user starts typing.
13558 promptMessage: "",
13559
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_$",
13565
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_$",
13571
13572 // message: String
13573 // Currently error/prompt message.
13574 // When using the default tooltip implementation, this will only be
13575 // displayed when the field is focused.
13576 message: "",
13577
13578 // constraints: dijit.form.ValidationTextBox.__Constraints
13579 // user-defined object needed to pass parameters to the validator functions
13580 constraints: {},
13581
13582 // regExp: [extension protected] String
13583 // regular expression string used to validate the input
13584 // Do not specify both regExp and regExpGen
13585 regExp: ".*",
13586
13587 regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/ constraints){
13588 // summary:
13589 // Overridable function used to generate regExp when dependent on constraints.
13590 // Do not specify both regExp and regExpGen.
13591 // tags:
13592 // extension protected
13593 return this.regExp; // String
13594 },
13595
13596 // state: [readonly] String
13597 // Shows current state (ie, validation result) of input (""=Normal, Incomplete, or Error)
13598 state: "",
13599
13600 // tooltipPosition: String[]
13601 // See description of `dijit.Tooltip.defaultPosition` for details on this parameter.
13602 tooltipPosition: [],
13603
13604 _setValueAttr: function(){
13605 // summary:
13606 // Hook so set('value', ...) works.
13607 this.inherited(arguments);
13608 this.validate(this._focused);
13609 },
13610
13611 validator: function(/*anything*/ value, /*dijit.form.ValidationTextBox.__Constraints*/ constraints){
13612 // summary:
13613 // Overridable function used to validate the text input against the regular expression.
13614 // tags:
13615 // protected
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
13619 },
13620
13621 _isValidSubset: function(){
13622 // summary:
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;
13626 },
13627
13628 isValid: function(/*Boolean*/ isFocused){
13629 // summary:
13630 // Tests if value is valid.
13631 // Can override with your own routine in a subclass.
13632 // tags:
13633 // protected
13634 return this.validator(this.textbox.value, this.constraints);
13635 },
13636
13637 _isEmpty: function(value){
13638 // summary:
13639 // Checks for whitespace
13640 return (this.trim ? /^\s*$/ : /^$/).test(value); // Boolean
13641 },
13642
13643 getErrorMessage: function(/*Boolean*/ isFocused){
13644 // summary:
13645 // Return an error message to show if appropriate
13646 // tags:
13647 // protected
13648 return (this.required && this._isEmpty(this.textbox.value)) ? this.missingMessage : this.invalidMessage; // String
13649 },
13650
13651 getPromptMessage: function(/*Boolean*/ isFocused){
13652 // summary:
13653 // Return a hint message to show when widget is first focused
13654 // tags:
13655 // protected
13656 return this.promptMessage; // String
13657 },
13658
13659 _maskValidSubsetError: true,
13660 validate: function(/*Boolean*/ isFocused){
13661 // summary:
13662 // Called by oninit, onblur, and onkeypress.
13663 // description:
13664 // Show missing or invalid messages if appropriate, and highlight textbox field.
13665 // tags:
13666 // protected
13667 var message = "";
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");
13674
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
13681 }else if(isEmpty){
13682 message = this.getPromptMessage(isFocused); // show the prompt whenever there's no error and no text
13683 }
13684 this.set("message", message);
13685
13686 return isValid;
13687 },
13688
13689 displayMessage: function(/*String*/ message){
13690 // summary:
13691 // Overridable method to display validation errors/hints.
13692 // By default uses a tooltip.
13693 // tags:
13694 // extension
13695 dijit.hideTooltip(this.domNode);
13696 if(message && this._focused){
13697 dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
13698 }
13699 },
13700
13701 _refreshState: function(){
13702 // Overrides TextBox._refreshState()
13703 this.validate(this._focused);
13704 this.inherited(arguments);
13705 },
13706
13707 //////////// INITIALIZATION METHODS ///////////////////////////////////////
13708
13709 constructor: function(){
13710 this.constraints = {};
13711 },
13712
13713 _setConstraintsAttr: function(/*Object*/ constraints){
13714 if(!constraints.locale && this.lang){
13715 constraints.locale = this.lang;
13716 }
13717 this._set("constraints", constraints);
13718 this._computePartialRE();
13719 },
13720
13721 _computePartialRE: function(){
13722 var p = this.regExpGen(this.constraints);
13723 this.regExp = p;
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,
13728 function (re){
13729 switch(re.charAt(0)){
13730 case '{':
13731 case '+':
13732 case '?':
13733 case '*':
13734 case '^':
13735 case '$':
13736 case '|':
13737 case '(':
13738 partialre += re;
13739 break;
13740 case ")":
13741 partialre += "|$)";
13742 break;
13743 default:
13744 partialre += "(?:"+re+"|$)";
13745 break;
13746 }
13747 }
13748 );}
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 + ")$";
13756 },
13757
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
13766 },
13767
13768 _setDisabledAttr: function(/*Boolean*/ value){
13769 this.inherited(arguments); // call FormValueWidget._setDisabledAttr()
13770 this._refreshState();
13771 },
13772
13773 _setRequiredAttr: function(/*Boolean*/ value){
13774 this._set("required", value);
13775 dijit.setWaiState(this.focusNode, "required", value);
13776 this._refreshState();
13777 },
13778
13779 _setMessageAttr: function(/*String*/ message){
13780 this._set("message", message);
13781 this.displayMessage(message);
13782 },
13783
13784 reset:function(){
13785 // Overrides dijit.form.TextBox.reset() by also
13786 // hiding errors about partial matches
13787 this._maskValidSubsetError = true;
13788 this.inherited(arguments);
13789 },
13790
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('');
13795
13796 this.inherited(arguments);
13797 }
13798 }
13799 );
13800
13801 dojo.declare(
13802 "dijit.form.MappedTextBox",
13803 dijit.form.ValidationTextBox,
13804 {
13805 // summary:
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.
13809 // description:
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
13814 // locale-neutral.
13815 // tags:
13816 // protected
13817
13818 postMixInProperties: function(){
13819 this.inherited(arguments);
13820
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 = "";
13824 },
13825
13826 serialize: function(/*anything*/ val, /*Object?*/ options){
13827 // summary:
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.
13831 // tags:
13832 // protected extension
13833 return val.toString ? val.toString() : ""; // String
13834 },
13835
13836 toString: function(){
13837 // summary:
13838 // Returns widget as a printable string using the widget's value
13839 // tags:
13840 // protected
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
13843 },
13844
13845 validate: function(){
13846 // Overrides `dijit.form.TextBox.validate`
13847 this.valueNode.value = this.toString();
13848 return this.inherited(arguments);
13849 },
13850
13851 buildRendering: function(){
13852 // Overrides `dijit._Templated.buildRendering`
13853
13854 this.inherited(arguments);
13855
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, "&quot;") + "'" : "") + "/>", this.textbox, "after");
13861 },
13862
13863 reset: function(){
13864 // Overrides `dijit.form.ValidationTextBox.reset` to
13865 // reset the hidden textbox value to ''
13866 this.valueNode.value = '';
13867 this.inherited(arguments);
13868 }
13869 }
13870 );
13871
13872 /*=====
13873 dijit.form.RangeBoundTextBox.__Constraints = function(){
13874 // min: Number
13875 // Minimum signed value. Default is -Infinity
13876 // max: Number
13877 // Maximum signed value. Default is +Infinity
13878 this.min = min;
13879 this.max = max;
13880 }
13881 =====*/
13882
13883 dojo.declare(
13884 "dijit.form.RangeBoundTextBox",
13885 dijit.form.MappedTextBox,
13886 {
13887 // summary:
13888 // Base class for textbox form widgets which defines a range of valid values.
13889
13890 // rangeMessage: String
13891 // The message to display if value is out-of-range
13892 rangeMessage: "",
13893
13894 /*=====
13895 // constraints: dijit.form.RangeBoundTextBox.__Constraints
13896 constraints: {},
13897 ======*/
13898
13899 rangeCheck: function(/*Number*/ primitive, /*dijit.form.RangeBoundTextBox.__Constraints*/ constraints){
13900 // summary:
13901 // Overridable function used to validate the range of the numeric input value.
13902 // tags:
13903 // protected
13904 return ("min" in constraints? (this.compare(primitive,constraints.min) >= 0) : true) &&
13905 ("max" in constraints? (this.compare(primitive,constraints.max) <= 0) : true); // Boolean
13906 },
13907
13908 isInRange: function(/*Boolean*/ isFocused){
13909 // summary:
13910 // Tests if the value is in the min/max range specified in constraints
13911 // tags:
13912 // protected
13913 return this.rangeCheck(this.get('value'), this.constraints);
13914 },
13915
13916 _isDefinitelyOutOfRange: function(){
13917 // summary:
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;
13927 }
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;
13932 }
13933 return isTooLittle || isTooMuch;
13934 },
13935
13936 _isValidSubset: function(){
13937 // summary:
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();
13942 },
13943
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
13948 },
13949
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
13955 }
13956 return this.inherited(arguments);
13957 },
13958
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;
13964 }
13965 },
13966
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);
13972 }else{
13973 dijit.removeWaiState(this.focusNode, "valuemin");
13974 }
13975 if(this.constraints.max !== undefined){
13976 dijit.setWaiState(this.focusNode, "valuemax", this.constraints.max);
13977 }else{
13978 dijit.removeWaiState(this.focusNode, "valuemax");
13979 }
13980 }
13981 },
13982
13983 _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){
13984 // summary:
13985 // Hook so set('value', ...) works.
13986
13987 dijit.setWaiState(this.focusNode, "valuenow", value);
13988 this.inherited(arguments);
13989 }
13990 }
13991 );
13992
13993 }
13994
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");
13998
13999
14000
14001
14002
14003
14004
14005
14006
14007
14008
14009 dojo.declare(
14010 "dijit.form.ComboBoxMixin",
14011 dijit._HasDropDown,
14012 {
14013 // summary:
14014 // Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect`
14015 // description:
14016 // All widgets that mix in dijit.form.ComboBoxMixin must extend `dijit.form._FormValueWidget`.
14017 // tags:
14018 // protected
14019
14020 // item: Object
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.
14023 item: null,
14024
14025 // pageSize: Integer
14026 // Argument to data provider.
14027 // Specifies number of search results per page (before hitting "next" button)
14028 pageSize: Infinity,
14029
14030 // store: [const] Object
14031 // Reference to data provider object used by this ComboBox
14032 store: null,
14033
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:{},
14041
14042 // query: Object
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.
14046 query: {},
14047
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,
14053
14054 // highlightMatch: String
14055 // One of: "first", "all" or "none".
14056 //
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}*'
14060 //
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",
14064
14065 // searchDelay: Integer
14066 // Delay in milliseconds between when user types something and we start
14067 // searching based on that value
14068 searchDelay: 100,
14069
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",
14074
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.
14079 labelAttr: "",
14080
14081 // labelType: String
14082 // Specifies how to interpret the labelAttr in the data store items.
14083 // Can be "html" or "text".
14084 labelType: "text",
14085
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}*",
14096
14097 // ignoreCase: Boolean
14098 // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items
14099 ignoreCase: true,
14100
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,
14105
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=\"&#9660; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&#935; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n"),
14107
14108 baseClass: "dijitTextBox dijitComboBox",
14109
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",
14114
14115 // Set classes like dijitDownArrowButtonHover depending on
14116 // mouse action over button node
14117 cssStateNodes: {
14118 "_buttonNode": "dijitDownArrowButton"
14119 },
14120
14121 // Flags to _HasDropDown to limit height of drop down to make it fit in viewport
14122 maxHeight: -1,
14123
14124 // For backwards compatibility let onClick events propagate, even clicks on the down arrow button
14125 _stopClickEvents: false,
14126
14127 _getCaretPos: function(/*DomNode*/ element){
14128 // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
14129 var pos = 0;
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);
14142 try{
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;
14148 }catch(e){
14149 // If focus has shifted, 0 is fine for caret pos.
14150 }
14151 }
14152 return pos;
14153 },
14154
14155 _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
14156 location = parseInt(location);
14157 dijit.selectInputText(element, location, location);
14158 },
14159
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);
14165 },
14166
14167 _abortQuery: function(){
14168 // stop in-progress query
14169 if(this.searchTimer){
14170 clearTimeout(this.searchTimer);
14171 this.searchTimer = null;
14172 }
14173 if(this._fetchHandle){
14174 if(this._fetchHandle.abort){ this._fetchHandle.abort(); }
14175 this._fetchHandle = null;
14176 }
14177 },
14178
14179 _onInput: function(/*Event*/ evt){
14180 // summary:
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
14186 }
14187 this.inherited(arguments);
14188 },
14189
14190 _onKey: function(/*Event*/ evt){
14191 // summary:
14192 // Handles keyboard events
14193
14194 var key = evt.charOrCode;
14195
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
14199 }
14200
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();
14207
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);
14215
14216 if(this._opened){
14217 highlighted = pw.getHighlightedOption();
14218 }
14219 switch(key){
14220 case dk.PAGE_DOWN:
14221 case dk.DOWN_ARROW:
14222 case dk.PAGE_UP:
14223 case dk.UP_ARROW:
14224 // Keystroke caused ComboBox_menu to move to a different item.
14225 // Copy new item to <input> box.
14226 if(this._opened){
14227 this._announceOption(highlighted);
14228 }
14229 dojo.stopEvent(evt);
14230 break;
14231
14232 case dk.ENTER:
14233 // prevent submitting form if user presses enter. Also
14234 // prevent accepting the value if either Next or Previous
14235 // are selected
14236 if(highlighted){
14237 // only stop event on prev/next
14238 if(highlighted == pw.nextButton){
14239 this._nextSearch(1);
14240 dojo.stopEvent(evt);
14241 break;
14242 }else if(highlighted == pw.previousButton){
14243 this._nextSearch(-1);
14244 dojo.stopEvent(evt);
14245 break;
14246 }
14247 }else{
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
14251 }
14252 // default case:
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();
14258 }
14259 // fall through
14260
14261 case dk.TAB:
14262 var newvalue = this.get('displayedValue');
14263 // if the user had More Choices selected fall into the
14264 // _onBlur handler
14265 if(pw && (
14266 newvalue == pw._messages["previousMessage"] ||
14267 newvalue == pw._messages["nextMessage"])
14268 ){
14269 break;
14270 }
14271 if(highlighted){
14272 this._selectOption();
14273 }
14274 if(this._opened){
14275 this._lastQuery = null; // in case results come back later
14276 this.closeDropDown();
14277 }
14278 break;
14279
14280 case ' ':
14281 if(highlighted){
14282 // user is effectively clicking a choice in the drop down menu
14283 dojo.stopEvent(evt);
14284 this._selectOption();
14285 this.closeDropDown();
14286 }else{
14287 // user typed a space into the input box, treat as normal character
14288 doSearch = true;
14289 }
14290 break;
14291
14292 case dk.DELETE:
14293 case dk.BACKSPACE:
14294 this._prev_key_backspace = true;
14295 doSearch = true;
14296 break;
14297
14298 default:
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;
14303 }
14304 if(doSearch){
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);
14309 }
14310 },
14311
14312 _autoCompleteText: function(/*String*/ text){
14313 // summary:
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.
14319
14320 var fn = this.focusNode;
14321
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);
14335 }
14336 }else{
14337 // text does not autoComplete; replace the whole value and highlight
14338 fn.value = text;
14339 dijit.selectInputText(fn);
14340 }
14341 },
14342
14343 _openResultList: function(/*Object*/ results, /*Object*/ dataObject){
14344 // summary:
14345 // Callback when a search completes.
14346 // description:
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 ||
14352 this.readOnly ||
14353 (dataObject.query[this.searchAttr] != this._lastQuery)
14354 ){
14355 return;
14356 }
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();
14361 return;
14362 }
14363
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
14368 // highlighted.
14369
14370 dataObject._maxOptions = this._maxOptions;
14371 var nodes = this.dropDown.createOptions(
14372 results,
14373 dataObject,
14374 dojo.hitch(this, "_getMenuLabelFromItem")
14375 );
14376
14377 // show our list (only if we have content, else nothing)
14378 this._showResultList();
14379
14380 // #4091:
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();
14388 }
14389 if(wasSelected){
14390 this._announceOption(this.dropDown.getHighlightedOption());
14391 }
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
14399 }
14400 },
14401
14402 _showResultList: function(){
14403 // summary:
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).
14406
14407 this.closeDropDown(true);
14408
14409 // hide the tooltip
14410 this.displayMessage("");
14411
14412 this.openDropDown();
14413
14414 dijit.setWaiState(this.domNode, "expanded", "true");
14415 },
14416
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.
14421
14422 this._startSearchAll();
14423 },
14424
14425 isLoaded: function(){
14426 // signal to _HasDropDown that it needs to call loadDropDown() to load the
14427 // drop down asynchronously before displaying it
14428 return false;
14429 },
14430
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();
14436 if(this._opened){
14437 this.inherited(arguments);
14438 dijit.setWaiState(this.domNode, "expanded", "false");
14439 dijit.removeWaiState(this.focusNode,"activedescendant");
14440 }
14441 },
14442
14443 _setBlurValue: function(){
14444 // if the user clicks away from the textbox OR tabs away, set the
14445 // value to the textbox value
14446 // #4617:
14447 // if value is now more choices or previous choices, revert
14448 // the value
14449 var newvalue = this.get('displayedValue');
14450 var pw = this.dropDown;
14451 if(pw && (
14452 newvalue == pw._messages["previousMessage"] ||
14453 newvalue == pw._messages["nextMessage"]
14454 )
14455 ){
14456 this._setValueAttr(this._lastValueReported, true);
14457 }else if(typeof this.item == "undefined"){
14458 // Update 'value' (ex: KY) according to currently displayed text
14459 this.item = null;
14460 this.set('displayedValue', newvalue);
14461 }else{
14462 if(this.value != this._lastValueReported){
14463 dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true);
14464 }
14465 this._refreshState();
14466 }
14467 },
14468
14469 _onBlur: function(){
14470 // summary:
14471 // Called magically when focus has shifted away from this widget and it's drop down
14472 this.closeDropDown();
14473 this.inherited(arguments);
14474 },
14475
14476 _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
14477 // summary:
14478 // Set the displayed valued in the input box, and the hidden value
14479 // that gets submitted, based on a dojo.data store item.
14480 // description:
14481 // Users shouldn't call this function; they should be calling
14482 // set('item', value)
14483 // tags:
14484 // private
14485 if(!displayedValue){
14486 displayedValue = this.store.getValue(item, this.searchAttr);
14487 }
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);
14491 },
14492
14493 _announceOption: function(/*Node*/ node){
14494 // summary:
14495 // a11y code that puts the highlighted option in the textbox.
14496 // This way screen readers will know what is happening in the
14497 // menu.
14498
14499 if(!node){
14500 return;
14501 }
14502 // pull the text value from the item attached to the DOM node
14503 var newValue;
14504 if(node == this.dropDown.nextButton ||
14505 node == this.dropDown.previousButton){
14506 newValue = node.innerHTML;
14507 this.item = undefined;
14508 this.value = '';
14509 }else{
14510 newValue = this.store.getValue(node.item, this.searchAttr).toString();
14511 this.set('item', node.item, false, newValue);
14512 }
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);
14519 },
14520
14521 _selectOption: function(/*Event*/ evt){
14522 // summary:
14523 // Menu callback function, called when an item in the menu is selected.
14524 if(evt){
14525 this._announceOption(evt.target);
14526 }
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
14530 },
14531
14532 _startSearchAll: function(){
14533 this._startSearch('');
14534 },
14535
14536 _startSearchFromInput: function(){
14537 this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1"));
14538 },
14539
14540 _getQueryString: function(/*String*/ text){
14541 return dojo.string.substitute(this.queryExpr, [text]);
14542 },
14543
14544 _startSearch: function(/*String*/ key){
14545 // summary:
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),
14553 id: popupId,
14554 dir: this.dir
14555 });
14556 dijit.removeWaiState(this.focusNode,"activedescendant");
14557 dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox
14558 }
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;
14569 var fetch = {
14570 queryOptions: {
14571 ignoreCase: this.ignoreCase,
14572 deep: true
14573 },
14574 query: query,
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();
14581 },
14582 start: 0,
14583 count: this.pageSize
14584 };
14585 dojo.mixin(fetch, _this.fetchProperties);
14586 this._fetchHandle = _this.store.fetch(fetch);
14587
14588 var nextSearch = function(dataObject, direction){
14589 dataObject.start += dataObject.count*direction;
14590 // #4091:
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);
14595 this.focus();
14596 };
14597 this._nextSearch = this.dropDown.onPage = dojo.hitch(this, nextSearch, this._fetchHandle);
14598 }, query, this), this.searchDelay);
14599 },
14600
14601 _setMaxOptions: function(size, request){
14602 this._maxOptions = size;
14603 },
14604
14605 _getValueField: function(){
14606 // summary:
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;
14610 },
14611
14612 //////////// INITIALIZATION METHODS ///////////////////////////////////////
14613
14614 constructor: function(){
14615 this.query={};
14616 this.fetchProperties={};
14617 },
14618
14619 postMixInProperties: function(){
14620 if(!this.store){
14621 var srcNodeRef = this.srcNodeRef;
14622
14623 // if user didn't specify store, then assume there are option tags
14624 this.store = new dijit.form._ComboBoxDataStore(srcNodeRef);
14625
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
14628 // Select
14629
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());
14636 if(item){
14637 var valueField = this._getValueField();
14638 this.value = this.store.getValue(item, valueField);
14639 }
14640 }
14641 }
14642
14643 this.inherited(arguments);
14644 },
14645
14646 postCreate: function(){
14647 // summary:
14648 // Subclasses must call this method from their postCreate() methods
14649 // tags:
14650 // protected
14651
14652 // find any associated label element and add to ComboBox node.
14653 var label=dojo.query('label[for="'+this.id+'"]');
14654 if(label.length){
14655 label[0].id = (this.id+"_label");
14656 dijit.setWaiState(this.domNode, "labelledby", label[0].id);
14657
14658 }
14659 this.inherited(arguments);
14660 },
14661
14662 _setHasDownArrowAttr: function(val){
14663 this.hasDownArrow = val;
14664 this._buttonNode.style.display = val ? "" : "none";
14665 },
14666
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";
14674 }
14675 return {html: labelType == "html", label: label};
14676 },
14677
14678 doHighlight: function(/*String*/ label, /*String*/ find){
14679 // summary:
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.
14683 // tags:
14684 // protected
14685
14686 var
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)
14696 },
14697
14698 _escapeHtml: function(/*String*/ str){
14699 // TODO Should become dojo.html.entities(), when exists use instead
14700 // summary:
14701 // Adds escape sequences for special characters in XML: &<>"'
14702 str = String(str).replace(/&/gm, "&amp;").replace(/</gm, "&lt;")
14703 .replace(/>/gm, "&gt;").replace(/"/gm, "&quot;");
14704 return str; // string
14705 },
14706
14707 reset: function(){
14708 // Overrides the _FormWidget.reset().
14709 // Additionally reset the .item (to clean up).
14710 this.item = null;
14711 this.inherited(arguments);
14712 },
14713
14714 labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){
14715 // summary:
14716 // Computes the label to display based on the dojo.data store item.
14717 // returns:
14718 // The label that the ComboBox should display
14719 // tags:
14720 // private
14721
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
14725 }
14726 }
14727 );
14728
14729 dojo.declare(
14730 "dijit.form._ComboBoxMenu",
14731 [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
14732 {
14733 // summary:
14734 // Focus-less menu for internal use in `dijit.form.ComboBox`
14735 // tags:
14736 // private
14737
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>"
14741 +"</ul>",
14742
14743 // _messages: Object
14744 // Holds "next" and "previous" text for paging buttons on drop down
14745 _messages: null,
14746
14747 baseClass: "dijitComboBoxMenu",
14748
14749 postMixInProperties: function(){
14750 this.inherited(arguments);
14751 this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang);
14752 },
14753
14754 buildRendering: function(){
14755 this.inherited(arguments);
14756
14757 // fill in template with i18n messages
14758 this.previousButton.innerHTML = this._messages["previousMessage"];
14759 this.nextButton.innerHTML = this._messages["nextMessage"];
14760 },
14761
14762 _setValueAttr: function(/*Object*/ value){
14763 this.value = value;
14764 this.onChange(value);
14765 },
14766
14767 // stubs
14768 onChange: function(/*Object*/ value){
14769 // summary:
14770 // Notifies ComboBox/FilteringSelect that user clicked an option in the drop down menu.
14771 // Probably should be called onSelect.
14772 // tags:
14773 // callback
14774 },
14775 onPage: function(/*Number*/ direction){
14776 // summary:
14777 // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page.
14778 // tags:
14779 // callback
14780 },
14781
14782 onClose: function(){
14783 // summary:
14784 // Callback from dijit.popup code to this widget, notifying it that it closed
14785 // tags:
14786 // private
14787 this._blurOptionNode();
14788 },
14789
14790 _createOption: function(/*Object*/ item, labelFunc){
14791 // summary:
14792 // Creates an option to appear on the popup menu subclassed by
14793 // `dijit.form.FilteringSelect`.
14794
14795 var menuitem = dojo.create("li", {
14796 "class": "dijitReset dijitMenuItem" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl"),
14797 role: "option"
14798 });
14799 var labelObject = labelFunc(item);
14800 if(labelObject.html){
14801 menuitem.innerHTML = labelObject.label;
14802 }else{
14803 menuitem.appendChild(
14804 dojo.doc.createTextNode(labelObject.label)
14805 );
14806 }
14807 // #3250: in blank options, assign a normal height
14808 if(menuitem.innerHTML == ""){
14809 menuitem.innerHTML = "&nbsp;";
14810 }
14811 menuitem.item=item;
14812 return menuitem;
14813 },
14814
14815 createOptions: function(results, dataObject, labelFunc){
14816 // summary:
14817 // Fills in the items in the drop down list
14818 // results:
14819 // Array of dojo.data items
14820 // dataObject:
14821 // dojo.data store
14822 // labelFunc:
14823 // Function to produce a label in the drop down list from a dojo.data item
14824
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
14832 // #2309:
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);
14838 }, this);
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;
14850 }
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;
14855 }
14856
14857 this.nextButton.style.display = displayMore ? "" : "none";
14858 dojo.attr(this.nextButton,"id", this.id + "_next");
14859 return this.domNode.childNodes;
14860 },
14861
14862 clearResultList: function(){
14863 // summary:
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]);
14867 }
14868 this._blurOptionNode();
14869 },
14870
14871 _onMouseDown: function(/*Event*/ evt){
14872 dojo.stopEvent(evt);
14873 },
14874
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.
14879 return;
14880 }else if(evt.target == this.previousButton){
14881 this._blurOptionNode();
14882 this.onPage(-1);
14883 }else if(evt.target == this.nextButton){
14884 this._blurOptionNode();
14885 this.onPage(1);
14886 }else{
14887 var tgt = evt.target;
14888 // while the clicked node is inside the div
14889 while(!tgt.item){
14890 // recurse to the top
14891 tgt = tgt.parentNode;
14892 }
14893 this._setValueAttr({ target: tgt }, true);
14894 }
14895 },
14896
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
14902 while(!tgt.item){
14903 // recurse to the top
14904 tgt = tgt.parentNode;
14905 }
14906 }
14907 this._focusOptionNode(tgt);
14908 },
14909
14910 _onMouseOut: function(/*Event*/ evt){
14911 if(evt.target === this.domNode){ return; }
14912 this._blurOptionNode();
14913 },
14914
14915 _focusOptionNode: function(/*DomNode*/ node){
14916 // summary:
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");
14922 }
14923 },
14924
14925 _blurOptionNode: function(){
14926 // summary:
14927 // Removes highlight on highlighted option.
14928 if(this._highlighted_option){
14929 dojo.removeClass(this._highlighted_option, "dijitMenuItemSelected");
14930 this._highlighted_option = null;
14931 }
14932 },
14933
14934 _highlightNextOption: function(){
14935 // summary:
14936 // Highlight the item just below the current selection.
14937 // If nothing selected, highlight first option.
14938
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);
14945 }else{
14946 var ns = this._highlighted_option.nextSibling;
14947 if(ns && ns.style.display != "none"){
14948 this._focusOptionNode(ns);
14949 }else{
14950 this.highlightFirstOption();
14951 }
14952 }
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);
14955 },
14956
14957 highlightFirstOption: function(){
14958 // summary:
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);
14964 },
14965
14966 highlightLastOption: function(){
14967 // summary:
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);
14971 },
14972
14973 _highlightPrevOption: function(){
14974 // summary:
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);
14981 }else{
14982 var ps = this._highlighted_option.previousSibling;
14983 if(ps && ps.style.display != "none"){
14984 this._focusOptionNode(ps);
14985 }else{
14986 this.highlightLastOption();
14987 }
14988 }
14989 dojo.window.scrollIntoView(this._highlighted_option);
14990 },
14991
14992 _page: function(/*Boolean*/ up){
14993 // summary:
14994 // Handles page-up and page-down keypresses
14995
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();
15002 }
15003 while(scrollamount<height){
15004 if(up){
15005 // stop at option 1
15006 if(!this.getHighlightedOption().previousSibling ||
15007 this._highlighted_option.previousSibling.style.display == "none"){
15008 break;
15009 }
15010 this._highlightPrevOption();
15011 }else{
15012 // stop at last option
15013 if(!this.getHighlightedOption().nextSibling ||
15014 this._highlighted_option.nextSibling.style.display == "none"){
15015 break;
15016 }
15017 this._highlightNextOption();
15018 }
15019 // going backwards
15020 var newscroll=this.domNode.scrollTop;
15021 scrollamount+=(newscroll-oldscroll)*(up ? -1:1);
15022 oldscroll=newscroll;
15023 }
15024 },
15025
15026 pageUp: function(){
15027 // summary:
15028 // Handles pageup keypress.
15029 // TODO: just call _page directly from handleKey().
15030 // tags:
15031 // private
15032 this._page(true);
15033 },
15034
15035 pageDown: function(){
15036 // summary:
15037 // Handles pagedown keypress.
15038 // TODO: just call _page directly from handleKey().
15039 // tags:
15040 // private
15041 this._page(false);
15042 },
15043
15044 getHighlightedOption: function(){
15045 // summary:
15046 // Returns the highlighted option.
15047 var ho = this._highlighted_option;
15048 return (ho && ho.parentNode) ? ho : null;
15049 },
15050
15051 handleKey: function(evt){
15052 // summary:
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();
15058 return false;
15059 case dojo.keys.PAGE_DOWN:
15060 this.pageDown();
15061 return false;
15062 case dojo.keys.UP_ARROW:
15063 this._highlightPrevOption();
15064 return false;
15065 case dojo.keys.PAGE_UP:
15066 this.pageUp();
15067 return false;
15068 default:
15069 return true;
15070 }
15071 }
15072 }
15073 );
15074
15075 dojo.declare(
15076 "dijit.form.ComboBox",
15077 [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin],
15078 {
15079 // summary:
15080 // Auto-completing text box, and base class for dijit.form.FilteringSelect.
15081 //
15082 // description:
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.
15091 //
15092 // Some of the options to the ComboBox are actually arguments to the data
15093 // provider.
15094
15095 _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
15096 // summary:
15097 // Hook so set('value', value) works.
15098 // description:
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);
15103 }
15104 }
15105 );
15106
15107 dojo.declare("dijit.form._ComboBoxDataStore", null, {
15108 // summary:
15109 // Inefficient but small data store specialized for inlined `dijit.form.ComboBox` data
15110 //
15111 // description:
15112 // Provides a store for inlined data like:
15113 //
15114 // | <select>
15115 // | <option value="AL">Alabama</option>
15116 // | ...
15117 //
15118 // Actually. just implements the subset of dojo.data.Read/Notification
15119 // needed for ComboBox and FilteringSelect to work.
15120 //
15121 // Note that an item is just a pointer to the <option> DomNode.
15122
15123 constructor: function( /*DomNode*/ root){
15124 this.root = 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
15128 root = root[0];
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;
15132 }
15133 this.root = root;
15134 }
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);
15140 });
15141
15142 },
15143
15144 getValue: function( /*item*/ item,
15145 /*attribute-name-string*/ attribute,
15146 /*value?*/ defaultValue){
15147 return (attribute == "value") ? item.value : (item.innerText || item.textContent || '');
15148 },
15149
15150 isItemLoaded: function(/*anything*/ something){
15151 return true;
15152 },
15153
15154 getFeatures: function(){
15155 return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true};
15156 },
15157
15158 _fetchItems: function( /*Object*/ args,
15159 /*Function*/ findCallback,
15160 /*Function*/ errorCallback){
15161 // summary:
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);
15169 } );
15170 if(args.sort){
15171 items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this));
15172 }
15173 findCallback(items, args);
15174 },
15175
15176 close: function(/*dojo.data.api.Request || args || null*/ request){
15177 return;
15178 },
15179
15180 getLabel: function(/*item*/ item){
15181 return item.innerHTML;
15182 },
15183
15184 getIdentity: function(/*item*/ item){
15185 return dojo.attr(item, "value");
15186 },
15187
15188 fetchItemByIdentity: function(/*Object*/ args){
15189 // summary:
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.
15193 //
15194 // description:
15195 // Given arguments like:
15196 //
15197 // | {identity: "CA", onItem: function(item){...}
15198 //
15199 // Call `onItem()` with the DOM node `<option value="CA">California</option>`
15200 var item = dojo.query("> option[value='" + args.identity + "']", this.root)[0];
15201 args.onItem(item);
15202 },
15203
15204 fetchSelectedItem: function(){
15205 // summary:
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
15213 }
15214 });
15215 //Mix in the simple fetch implementation to this class.
15216 dojo.extend(dijit.form._ComboBoxDataStore,dojo.data.util.simpleFetch);
15217
15218 }
15219
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");
15223
15224
15225
15226 dojo.declare(
15227 "dijit.form.FilteringSelect",
15228 [dijit.form.MappedTextBox, dijit.form.ComboBoxMixin],
15229 {
15230 // summary:
15231 // An enhanced version of the HTML SELECT tag, populated dynamically
15232 //
15233 // description:
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.
15242 //
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)
15249 //
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)
15255
15256 // required: Boolean
15257 // True (default) if user is required to enter a value into this field.
15258 required: true,
15259
15260 _lastDisplayedValue: "",
15261
15262 _isValidSubset: function(){
15263 return this._opened;
15264 },
15265
15266 isValid: function(){
15267 // Overrides ValidationTextBox.isValid()
15268 return this.item || (!this.required && this.get('displayedValue') == ""); // #5974
15269 },
15270
15271 _refreshState: function(){
15272 if(!this.searchTimer){ // state will be refreshed after results are returned
15273 this.inherited(arguments);
15274 }
15275 },
15276
15277 _callbackSetLabel: function(
15278 /*Array*/ result,
15279 /*Object*/ dataObject,
15280 /*Boolean?*/ priorityChange){
15281 // summary:
15282 // Callback from dojo.data after lookup of user entered value finishes
15283
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)){
15289 return;
15290 }
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);
15298 }else{
15299 this.set('item', result[0], priorityChange);
15300 }
15301 },
15302
15303 _openResultList: function(/*Object*/ results, /*Object*/ dataObject){
15304 // Callback when a data store query completes.
15305 // Overrides ComboBox._openResultList()
15306
15307 // #3285: tap into search callback to see if user's query resembles a match
15308 if(dataObject.query[this.searchAttr] != this._lastQuery){
15309 return;
15310 }
15311 dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments);
15312
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);
15318 }
15319 },
15320
15321 _getValueAttr: function(){
15322 // summary:
15323 // Hook for get('value') to work.
15324
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;
15328 },
15329
15330 _getValueField: function(){
15331 // Overrides ComboBox._getValueField()
15332 return "value";
15333 },
15334
15335 _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){
15336 // summary:
15337 // Hook so set('value', value) works.
15338 // description:
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;
15343
15344 if(value === null || value === ''){
15345 this._setDisplayedValueAttr('', priorityChange);
15346 return;
15347 }
15348
15349 //#3347: fetchItemByIdentity if no keyAttr specified
15350 var self = this;
15351 this.store.fetchItemByIdentity({
15352 identity: value,
15353 onItem: function(item){
15354 self._callbackSetLabel(item? [item] : [], undefined, priorityChange);
15355 }
15356 });
15357 },
15358
15359 _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
15360 // summary:
15361 // Set the displayed valued in the input box, and the hidden value
15362 // that gets submitted, based on a dojo.data store item.
15363 // description:
15364 // Users shouldn't call this function; they should be calling
15365 // set('item', value)
15366 // tags:
15367 // private
15368 this.inherited(arguments);
15369 this.valueNode.value = this.value;
15370 this._lastDisplayedValue = this.textbox.value;
15371 },
15372
15373 _getDisplayQueryString: function(/*String*/ text){
15374 return text.replace(/([\\\*\?])/g, "\\$1");
15375 },
15376
15377 _setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){
15378 // summary:
15379 // Hook so set('displayedValue', label) works.
15380 // description:
15381 // Sets textbox to display label. Also performs reverse lookup
15382 // to set the hidden value. label should corresponding to item.searchAttr.
15383
15384 if(label == null){ label = ''; }
15385
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)){
15392 return;
15393 }
15394 priorityChange = false;
15395 }
15396
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
15399 if(this.store){
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
15411 var _this = this;
15412 var fetch = {
15413 query: query,
15414 queryOptions: {
15415 ignoreCase: this.ignoreCase,
15416 deep: true
15417 },
15418 onComplete: function(result, dataObject){
15419 _this._fetchHandle = null;
15420 dojo.hitch(_this, "_callbackSetLabel")(result, dataObject, priorityChange);
15421 },
15422 onError: function(errText){
15423 _this._fetchHandle = null;
15424 console.error('dijit.form.FilteringSelect: ' + errText);
15425 dojo.hitch(_this, "_callbackSetLabel")([], undefined, false);
15426 }
15427 };
15428 dojo.mixin(fetch, this.fetchProperties);
15429 this._fetchHandle = this.store.fetch(fetch);
15430 }
15431 },
15432
15433 undo: function(){
15434 this.set('displayedValue', this._lastDisplayedValue);
15435 }
15436 }
15437 );
15438
15439 }
15440
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");
15444
15445
15446
15447
15448
15449
15450 dojo.declare(
15451 "dijit.form.Form",
15452 [dijit._Widget, dijit._Templated, dijit.form._FormMixin, dijit.layout._ContentPaneResizeMixin],
15453 {
15454 // summary:
15455 // Widget corresponding to HTML form tag, for validation and serialization
15456 //
15457 // example:
15458 // | <form dojoType="dijit.form.Form" id="myForm">
15459 // | Name: <input type="text" name="name" />
15460 // | </form>
15461 // | myObj = {name: "John Doe"};
15462 // | dijit.byId('myForm').set('value', myObj);
15463 // |
15464 // | myObj=dijit.byId('myForm').get('value');
15465
15466 // HTML <FORM> attributes
15467
15468 // name: String?
15469 // Name of form for scripting.
15470 name: "",
15471
15472 // action: String?
15473 // Server-side form handler.
15474 action: "",
15475
15476 // method: String?
15477 // HTTP method used to submit the form, either "GET" or "POST".
15478 method: "",
15479
15480 // encType: String?
15481 // Encoding type for the form, ex: application/x-www-form-urlencoded.
15482 encType: "",
15483
15484 // accept-charset: String?
15485 // List of supported charsets.
15486 "accept-charset": "",
15487
15488 // accept: String?
15489 // List of MIME types for file upload.
15490 accept: "",
15491
15492 // target: String?
15493 // Target frame for the document to be opened in.
15494 target: "",
15495
15496 templateString: "<form dojoAttachPoint='containerNode' dojoAttachEvent='onreset:_onReset,onsubmit:_onSubmit' ${!nameAttrSetting}></form>",
15497
15498 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
15499 action: "",
15500 method: "",
15501 encType: "",
15502 "accept-charset": "",
15503 accept: "",
15504 target: ""
15505 }),
15506
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);
15512 },
15513
15514 execute: function(/*Object*/ formContents){
15515 // summary:
15516 // Deprecated: use submit()
15517 // tags:
15518 // deprecated
15519 },
15520
15521 onExecute: function(){
15522 // summary:
15523 // Deprecated: use onSubmit()
15524 // tags:
15525 // deprecated
15526 },
15527
15528 _setEncTypeAttr: function(/*String*/ value){
15529 this.encType = value;
15530 dojo.attr(this.domNode, "encType", value);
15531 if(dojo.isIE){ this.domNode.encoding = value; }
15532 },
15533
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);
15541 }
15542 }
15543 this.inherited(arguments);
15544 },
15545
15546 reset: function(/*Event?*/ e){
15547 // summary:
15548 // restores all widget values back to their init values,
15549 // calls onReset() which can cancel the reset by returning false
15550
15551 // create fake event so we can know if preventDefault() is called
15552 var faux = {
15553 returnValue: true, // the IE way
15554 preventDefault: function(){ // not IE
15555 this.returnValue = false;
15556 },
15557 stopPropagation: function(){},
15558 currentTarget: e ? e.target : this.domNode,
15559 target: e ? e.target : this.domNode
15560 };
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, []);
15564 }
15565 },
15566
15567 onReset: function(/*Event?*/ e){
15568 // summary:
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
15573 // tags:
15574 // callback
15575 return true; // Boolean
15576 },
15577
15578 _onReset: function(e){
15579 this.reset(e);
15580 dojo.stopEvent(e);
15581 return false;
15582 },
15583
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");
15589 this.onExecute();
15590 this.execute(this.getValues());
15591 }
15592 if(this.onSubmit(e) === false){ // only exactly false stops submit
15593 dojo.stopEvent(e);
15594 }
15595 },
15596
15597 onSubmit: function(/*Event?*/ e){
15598 // summary:
15599 // Callback when user submits the form.
15600 // description:
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
15605 // should proceed
15606 // tags:
15607 // extension
15608
15609 return this.isValid(); // Boolean
15610 },
15611
15612 submit: function(){
15613 // summary:
15614 // programmatically submit form if and only if the `onSubmit` returns true
15615 if(!(this.onSubmit() === false)){
15616 this.containerNode.submit();
15617 }
15618 }
15619 }
15620 );
15621
15622 }
15623
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");
15627
15628
15629
15630 // TODO: for 2.0, move the RadioButton code into this file
15631
15632 }
15633
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");
15637
15638
15639
15640
15641 /*=====
15642 dijit.form.__SelectOption = function(){
15643 // value: String
15644 // The value of the option. Setting to empty (or missing) will
15645 // place a separator at that location
15646 // label: String
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;
15656 }
15657 =====*/
15658
15659 dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
15660 // summary:
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.
15665
15666 // multiple: [const] Boolean
15667 // Whether or not we are multi-valued
15668 multiple: false,
15669
15670 // options: dijit.form.__SelectOption[]
15671 // The set of options for our select item. Roughly corresponds to
15672 // the html <option> tag.
15673 options: null,
15674
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.
15679 store: null,
15680
15681 // query: object
15682 // A query to use when fetching items from our store
15683 query: null,
15684
15685 // queryOptions: object
15686 // Query options to use when fetching from the store
15687 queryOptions: null,
15688
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)
15692 onFetch: null,
15693
15694 // sortByLabel: Boolean
15695 // Flag to sort the options returned from a store by the label of
15696 // the store.
15697 sortByLabel: true,
15698
15699
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
15704 // dropdown.
15705 loadChildrenOnOpen: false,
15706
15707 getOptions: function(/*anything*/ valueOrIdx){
15708 // summary:
15709 // Returns a given option (or options).
15710 // valueOrIdx:
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).
15714 //
15715 // If passed in a number, then the option with the given index (0-based)
15716 // within this select will be returned.
15717 //
15718 // If passed in a dijit.form.__SelectOption, the same option will be
15719 // returned if and only if it exists within this select.
15720 //
15721 // If passed an array, then an array will be returned with each element
15722 // in the array being looked up.
15723 //
15724 // If not passed a value, then all options will be returned
15725 //
15726 // returns:
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
15732
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;
15738
15739 if(lookupValue === undefined){
15740 return opts; // dijit.form.__SelectOption[]
15741 }
15742 if(dojo.isArray(lookupValue)){
15743 return dojo.map(lookupValue, "return this.getOptions(item);", this); // dijit.form.__SelectOption[]
15744 }
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)){
15751 lookupValue = idx;
15752 return true;
15753 }
15754 return false;
15755 })){
15756 lookupValue = -1;
15757 }
15758 }
15759 if(typeof lookupValue == "string"){
15760 for(var i=0; i<l; i++){
15761 if(opts[i].value === lookupValue){
15762 lookupValue = i;
15763 break;
15764 }
15765 }
15766 }
15767 if(typeof lookupValue == "number" && lookupValue >= 0 && lookupValue < l){
15768 return this.options[lookupValue] // dijit.form.__SelectOption
15769 }
15770 return null; // null
15771 },
15772
15773 addOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ option){
15774 // summary:
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);
15783 }
15784 }, this);
15785 this._loadChildren();
15786 },
15787
15788 removeOption: function(/*String|dijit.form.__SelectOption|Number|Array*/ valueOrIdx){
15789 // summary:
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...
15801 if(i){
15802 this.options = dojo.filter(this.options, function(node, idx){
15803 return (node.value !== i.value || node.label !== i.label);
15804 });
15805 this._removeOptionItem(i);
15806 }
15807 }, this);
15808 this._loadChildren();
15809 },
15810
15811 updateOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ newOption){
15812 // summary:
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;
15820 if(oldOpt){
15821 for(k in i){ oldOpt[k] = i[k]; }
15822 }
15823 }, this);
15824 this._loadChildren();
15825 },
15826
15827 setStore: function(/*dojo.data.api.Identity*/ store,
15828 /*anything?*/ selectedValue,
15829 /*Object?*/ fetchArgs){
15830 // summary:
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
15840 // has been loaded
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")
15854 ];
15855 }
15856 this._set("store", store);
15857 }
15858
15859 // Turn off change notifications while we make all these changes
15860 this._onChangeActive = false;
15861
15862 // Remove existing options (if there are any)
15863 if(this.options && this.options.length){
15864 this.removeOption(this.options);
15865 }
15866
15867 // Add our new options
15868 if(store){
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]
15875 }], store));
15876 }
15877
15878 if(fetchArgs.onFetch){
15879 items = fetchArgs.onFetch.call(this, items, opts);
15880 }
15881 // TODO: Add these guys as a batch, instead of separately
15882 dojo.forEach(items, function(i){
15883 this._addOptionForItem(i);
15884 }, this);
15885
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;
15891
15892 if(!this.loadChildrenOnOpen){
15893 this._loadChildren();
15894 }else{
15895 this._pseudoLoadChildren(items);
15896 }
15897 this._fetchedWith = opts;
15898 this._lastValueReported = this.multiple ? [] : null;
15899 this._onChangeActive = true;
15900 this.onSetStore();
15901 this._handleOnChange(this.value);
15902 },
15903 scope: this
15904 }));
15905 }else{
15906 delete this._fetchedWith;
15907 }
15908 return oStore; // dojo.data.api.Identity
15909 },
15910
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)
15913
15914 _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
15915 // summary:
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
15920 // we're done
15921 this._pendingValue = newValue;
15922 return;
15923 }
15924 var opts = this.getOptions() || [];
15925 if(!dojo.isArray(newValue)){
15926 newValue = [newValue];
15927 }
15928 dojo.forEach(newValue, function(i, idx){
15929 if(!dojo.isObject(i)){
15930 i = i + "";
15931 }
15932 if(typeof i === "string"){
15933 newValue[idx] = dojo.filter(opts, function(node){
15934 return node.value === i;
15935 })[0] || {value: "", label: ""};
15936 }
15937 }, this);
15938
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];
15943 }
15944 dojo.forEach(opts, function(i){
15945 i.selected = dojo.some(newValue, function(v){ return v.value === i.value; });
15946 });
15947 var val = dojo.map(newValue, function(i){ return i.value; }),
15948 disp = dojo.map(newValue, function(i){ return i.label; });
15949
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);
15954 },
15955
15956 _getDisplayedValueAttr: function(){
15957 // summary:
15958 // returns the displayed value of the widget
15959 var val = this.get("value");
15960 if(!dojo.isArray(val)){
15961 val = [val];
15962 }
15963 var ret = dojo.map(this.getOptions(val), function(v){
15964 if(v && "label" in v){
15965 return v.label;
15966 }else if(v){
15967 return v.value;
15968 }
15969 return null;
15970 }, this);
15971 return this.multiple ? ret : ret[0];
15972 },
15973
15974 _loadChildren: function(){
15975 // summary:
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();
15981 });
15982 // Add each menu item
15983 dojo.forEach(this.options, this._addOptionItem, this);
15984
15985 // Update states
15986 this._updateSelection();
15987 },
15988
15989 _updateSelection: function(){
15990 // summary:
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)){
15995 val = [val];
15996 }
15997 if(val && val[0]){
15998 dojo.forEach(this._getChildren(), function(child){
15999 var isSelected = dojo.some(val, function(v){
16000 return child.option && (v === child.option.value);
16001 });
16002 dojo.toggleClass(child.domNode, this.baseClass + "SelectedOption", isSelected);
16003 dijit.setWaiState(child.domNode, "selected", isSelected);
16004 }, this);
16005 }
16006 },
16007
16008 _getValueFromOpts: function(){
16009 // summary:
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){
16016 return i.selected;
16017 })[0];
16018 if(opt && opt.value){
16019 return opt.value
16020 }else{
16021 opts[0].selected = true;
16022 return opts[0].value;
16023 }
16024 }else if(this.multiple){
16025 // Set value to be the sum of all selected
16026 return dojo.map(dojo.filter(opts, function(i){
16027 return i.selected;
16028 }), function(i){
16029 return i.value;
16030 }) || [];
16031 }
16032 return "";
16033 },
16034
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);
16040 }
16041 },
16042 _onDeleteItem: function(/*item*/ item){
16043 var store = this.store;
16044 this.removeOption(store.getIdentity(item));
16045 },
16046 _onSetItem: function(/*item*/ item){
16047 this.updateOption(this._getOptionObjForItem(item));
16048 },
16049
16050 _getOptionObjForItem: function(item){
16051 // summary:
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
16059 },
16060
16061 _addOptionForItem: function(/*item*/ item){
16062 // summary:
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);
16069 },
16070 scope: this});
16071 return;
16072 }
16073 var newOpt = this._getOptionObjForItem(item);
16074 this.addOption(newOpt);
16075 },
16076
16077 constructor: function(/*Object*/ keywordArgs){
16078 // summary:
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;
16082 },
16083
16084 buildRendering: function(){
16085 this.inherited(arguments);
16086 dojo.setSelectable(this.focusNode, false);
16087 },
16088
16089 _fillContent: function(){
16090 // summary:
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
16093 // function.
16094 var opts = this.options;
16095 if(!opts){
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 };
16100 }
16101 return {
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
16109 };
16110 }, this) : [];
16111 }
16112 if(!this.value){
16113 this._set("value", this._getValueFromOpts());
16114 }else if(this.multiple && typeof this.value == "string"){
16115 this_set("value", this.value.split(","));
16116 }
16117 },
16118
16119 postCreate: function(){
16120 // summary:
16121 // sets up our event handling that we need for functioning
16122 // as a select
16123 this.inherited(arguments);
16124
16125 // Make our event connections for updating state
16126 this.connect(this, "onChange", "_updateSelection");
16127 this.connect(this, "startup", "_loadChildren");
16128
16129 this._setValueAttr(this.value, null);
16130 },
16131
16132 startup: function(){
16133 // summary:
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){
16138 if(this[i]){
16139 fetchArgs[i] = this[i];
16140 }
16141 delete this[i];
16142 }, this);
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
16146 this.store = null;
16147 this.setStore(store, this._oValue, fetchArgs);
16148 }
16149 },
16150
16151 destroy: function(){
16152 // summary:
16153 // Clean up our connections
16154 dojo.forEach(this._notifyConnections || [], dojo.disconnect);
16155 this.inherited(arguments);
16156 },
16157
16158 _addOptionItem: function(/*dijit.form.__SelectOption*/ option){
16159 // summary:
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.
16164 },
16165
16166 _removeOptionItem: function(/*dijit.form.__SelectOption*/ option){
16167 // summary:
16168 // User-overridable function which, for the given option, removes
16169 // its item from the select.
16170 },
16171
16172 _setDisplay: function(/*String or String[]*/ newDisplay){
16173 // summary:
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)
16177 },
16178
16179 _getChildren: function(){
16180 // summary:
16181 // Overridable function to return the children that this widget contains.
16182 return [];
16183 },
16184
16185 _getSelectedOptionsAttr: function(){
16186 // summary:
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"));
16190 },
16191
16192 _pseudoLoadChildren: function(/*item[]*/ items){
16193 // summary:
16194 // a function that will "fake" loading children, if needed, and
16195 // if we have set to not load children until the widget opens.
16196 // items:
16197 // An array of items that will be loaded, when needed
16198 },
16199
16200 onSetStore: function(){
16201 // summary:
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
16205 }
16206 });
16207
16208 }
16209
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");
16213
16214
16215
16216 dojo.declare("dijit._KeyNavContainer",
16217 dijit._Container,
16218 {
16219
16220 // summary:
16221 // A _Container with keyboard navigation of its children.
16222 // description:
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
16226 // widgets.
16227 /*=====
16228 // focusedChild: [protected] Widget
16229 // The currently focused child widget, or null if there isn't one
16230 focusedChild: null,
16231 =====*/
16232
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.
16237 tabIndex: "0",
16238
16239 _keyNavCodes: {},
16240
16241 connectKeyNavHandlers: function(/*dojo.keys[]*/ prevKeyCodes, /*dojo.keys[]*/ nextKeyCodes){
16242 // summary:
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.
16249 // tags:
16250 // protected
16251
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");
16261 },
16262
16263 startupKeyNavChildren: function(){
16264 // summary:
16265 // Call in startup() to set child tabindexes to -1
16266 // tags:
16267 // protected
16268 dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild"));
16269 },
16270
16271 addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
16272 // summary:
16273 // Add a child to our _Container
16274 dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
16275 this._startupChild(widget);
16276 },
16277
16278 focus: function(){
16279 // summary:
16280 // Default focus() implementation: focus the first child.
16281 this.focusFirstChild();
16282 },
16283
16284 focusFirstChild: function(){
16285 // summary:
16286 // Focus the first focusable child in the container.
16287 // tags:
16288 // protected
16289 var child = this._getFirstFocusableChild();
16290 if(child){ // edge case: Menu could be empty or hidden
16291 this.focusChild(child);
16292 }
16293 },
16294
16295 focusLastChild: function(){
16296 // summary:
16297 // Focus the last focusable child in the container.
16298 // tags:
16299 // protected
16300 var child = this._getLastFocusableChild();
16301 if(child){ // edge case: Menu could be empty or hidden
16302 this.focusChild(child);
16303 }
16304 },
16305
16306 focusNext: function(){
16307 // summary:
16308 // Focus the next widget
16309 // tags:
16310 // protected
16311 var child = this._getNextFocusableChild(this.focusedChild, 1);
16312 this.focusChild(child);
16313 },
16314
16315 focusPrev: function(){
16316 // summary:
16317 // Focus the last focusable node in the previous widget
16318 // (ex: go to the ComboButton icon section rather than button section)
16319 // tags:
16320 // protected
16321 var child = this._getNextFocusableChild(this.focusedChild, -1);
16322 this.focusChild(child, true);
16323 },
16324
16325 focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){
16326 // summary:
16327 // Focus widget.
16328 // widget:
16329 // Reference to container's child widget
16330 // last:
16331 // If true and if widget has multiple focusable nodes, focus the
16332 // last one instead of the first one
16333 // tags:
16334 // protected
16335
16336 if(this.focusedChild && widget !== this.focusedChild){
16337 this._onChildBlur(this.focusedChild);
16338 }
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);
16342 },
16343
16344 _startupChild: function(/*dijit._Widget*/ widget){
16345 // summary:
16346 // Setup for each child widget
16347 // description:
16348 // Sets tabIndex=-1 on each child, so that the tab key will
16349 // leave the container rather than visiting each child.
16350 // tags:
16351 // private
16352
16353 widget.set("tabIndex", "-1");
16354
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);
16358 });
16359 this.connect(widget, "_onBlur", function(){
16360 widget.set("tabIndex", "-1");
16361 });
16362 },
16363
16364 _onContainerFocus: function(evt){
16365 // summary:
16366 // Handler for when the container gets focus
16367 // description:
16368 // Initially the container itself has a tabIndex, but when it gets
16369 // focus, switch focus to first child...
16370 // tags:
16371 // private
16372
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)
16376
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; }
16380
16381 this.focusFirstChild();
16382
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");
16388 },
16389
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)
16395 if(this.tabIndex){
16396 dojo.attr(this.domNode, "tabIndex", this.tabIndex);
16397 }
16398 this.inherited(arguments);
16399 },
16400
16401 _onContainerKeypress: function(evt){
16402 // summary:
16403 // When a key is pressed, if it's an arrow key etc. then
16404 // it's handled here.
16405 // tags:
16406 // private
16407 if(evt.ctrlKey || evt.altKey){ return; }
16408 var func = this._keyNavCodes[evt.charOrCode];
16409 if(func){
16410 func();
16411 dojo.stopEvent(evt);
16412 }
16413 },
16414
16415 _onChildBlur: function(/*dijit._Widget*/ widget){
16416 // summary:
16417 // Called when focus leaves a child widget to go
16418 // to a sibling widget.
16419 // tags:
16420 // protected
16421 },
16422
16423 _getFirstFocusableChild: function(){
16424 // summary:
16425 // Returns first child that can be focused
16426 return this._getNextFocusableChild(null, 1); // dijit._Widget
16427 },
16428
16429 _getLastFocusableChild: function(){
16430 // summary:
16431 // Returns last child that can be focused
16432 return this._getNextFocusableChild(null, -1); // dijit._Widget
16433 },
16434
16435 _getNextFocusableChild: function(child, dir){
16436 // summary:
16437 // Returns the next or previous focusable child, compared
16438 // to "child"
16439 // child: Widget
16440 // The current widget
16441 // dir: Integer
16442 // * 1 = after
16443 // * -1 = before
16444 if(child){
16445 child = this._getSiblingOfChild(child, dir);
16446 }
16447 var children = this.getChildren();
16448 for(var i=0; i < children.length; i++){
16449 if(!child){
16450 child = children[(dir>0) ? 0 : (children.length-1)];
16451 }
16452 if(child.isFocusable()){
16453 return child; // dijit._Widget
16454 }
16455 child = this._getSiblingOfChild(child, dir);
16456 }
16457 // no focusable child found
16458 return null; // dijit._Widget
16459 }
16460 }
16461 );
16462
16463 }
16464
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");
16468
16469
16470
16471
16472
16473
16474 dojo.declare("dijit.MenuItem",
16475 [dijit._Widget, dijit._Templated, dijit._Contained, dijit._CssStateMixin],
16476 {
16477 // summary:
16478 // A line item in a Menu Widget
16479
16480 // Make 3 columns
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"),
16483
16484 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
16485 label: { node: "containerNode", type: "innerHTML" },
16486 iconClass: { node: "iconNode", type: "class" }
16487 }),
16488
16489 baseClass: "dijitMenuItem",
16490
16491 // label: String
16492 // Menu text
16493 label: '',
16494
16495 // iconClass: String
16496 // Class to apply to DOMNode to make it display an icon.
16497 iconClass: "",
16498
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
16503 // accelerators.
16504 accelKey: "",
16505
16506 // disabled: Boolean
16507 // If true, the menu item is disabled.
16508 // If false, the menu item is enabled.
16509 disabled: false,
16510
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);
16516 }
16517 },
16518
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";
16526 }
16527 dijit.setWaiState(this.domNode, "labelledby", label);
16528 dojo.setSelectable(this.domNode, false);
16529 },
16530
16531 _onHover: function(){
16532 // summary:
16533 // Handler when mouse is moved onto menu item
16534 // tags:
16535 // protected
16536 this.getParent().onItemHover(this);
16537 },
16538
16539 _onUnhover: function(){
16540 // summary:
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.
16544 // tags:
16545 // protected
16546
16547 // if we are unhovering the currently selected item
16548 // then unselect it
16549 this.getParent().onItemUnhover(this);
16550
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);
16555 },
16556
16557 _onClick: function(evt){
16558 // summary:
16559 // Internal handler for click events on MenuItem.
16560 // tags:
16561 // private
16562 this.getParent().onItemClick(this, evt);
16563 dojo.stopEvent(evt);
16564 },
16565
16566 onClick: function(/*Event*/ evt){
16567 // summary:
16568 // User defined function to handle clicks
16569 // tags:
16570 // callback
16571 },
16572
16573 focus: function(){
16574 // summary:
16575 // Focus on this MenuItem
16576 try{
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();
16580 }
16581 dijit.focus(this.focusNode);
16582 }catch(e){
16583 // this throws on IE (at least) in some scenarios
16584 }
16585 },
16586
16587 _onFocus: function(){
16588 // summary:
16589 // This is called by the focus manager when focus
16590 // goes to this MenuItem or a child menu.
16591 // tags:
16592 // protected
16593 this._setSelected(true);
16594 this.getParent()._onItemFocus(this);
16595
16596 this.inherited(arguments);
16597 },
16598
16599 _setSelected: function(selected){
16600 // summary:
16601 // Indicate that this node is the currently selected one
16602 // tags:
16603 // private
16604
16605 /***
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
16612 * _onBlur()
16613 */
16614
16615 dojo.toggleClass(this.domNode, "dijitMenuItemSelected", selected);
16616 },
16617
16618 setLabel: function(/*String*/ content){
16619 // summary:
16620 // Deprecated. Use set('label', ...) instead.
16621 // tags:
16622 // deprecated
16623 dojo.deprecated("dijit.MenuItem.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0");
16624 this.set("label", content);
16625 },
16626
16627 setDisabled: function(/*Boolean*/ disabled){
16628 // summary:
16629 // Deprecated. Use set('disabled', bool) instead.
16630 // tags:
16631 // deprecated
16632 dojo.deprecated("dijit.Menu.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0");
16633 this.set('disabled', disabled);
16634 },
16635 _setDisabledAttr: function(/*Boolean*/ value){
16636 // summary:
16637 // Hook for attr('disabled', ...) to work.
16638 // Enable or disable this menu item.
16639
16640 dijit.setWaiState(this.focusNode, 'disabled', value ? 'true' : 'false');
16641 this._set("disabled", value);
16642 },
16643 _setAccelKeyAttr: function(/*String*/ value){
16644 // summary:
16645 // Hook for attr('accelKey', ...) to work.
16646 // Set accelKey on this menu item.
16647
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");
16652
16653 this._set("accelKey", value);
16654 }
16655 });
16656
16657 }
16658
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");
16662
16663
16664
16665 dojo.declare("dijit.PopupMenuItem",
16666 dijit.MenuItem,
16667 {
16668 _fillContent: function(){
16669 // summary:
16670 // When Menu is declared in markup, this code gets the menu label and
16671 // the popup widget from the srcNodeRef.
16672 // description:
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
16675 // example:
16676 // | <div dojoType="dijit.PopupMenuItem">
16677 // | <span>pick me</span>
16678 // | <popup> ... </popup>
16679 // | </div>
16680 // tags:
16681 // protected
16682
16683 if(this.srcNodeRef){
16684 var nodes = dojo.query("*", this.srcNodeRef);
16685 dijit.PopupMenuItem.superclass._fillContent.call(this, nodes[0]);
16686
16687 // save pointer to srcNode so we can grab the drop down widget after it's instantiated
16688 this.dropDownContainer = this.srcNodeRef;
16689 }
16690 },
16691
16692 startup: function(){
16693 if(this._started){ return; }
16694 this.inherited(arguments);
16695
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.
16698 if(!this.popup){
16699 var node = dojo.query("[widgetId]", this.dropDownContainer)[0];
16700 this.popup = dijit.byNode(node);
16701 }
16702 dojo.body().appendChild(this.popup.domNode);
16703 this.popup.startup();
16704
16705 this.popup.domNode.style.display="none";
16706 if(this.arrowWrapper){
16707 dojo.style(this.arrowWrapper, "visibility", "");
16708 }
16709 dijit.setWaiState(this.focusNode, "haspopup", "true");
16710 },
16711
16712 destroyDescendants: function(){
16713 if(this.popup){
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();
16718 }
16719 delete this.popup;
16720 }
16721 this.inherited(arguments);
16722 }
16723 });
16724
16725 }
16726
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");
16730
16731
16732
16733 dojo.declare("dijit.CheckedMenuItem",
16734 dijit.MenuItem,
16735 {
16736 // summary:
16737 // A checkbox-like menu item for toggling on and off
16738
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\">&#10003;</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\">&nbsp;</td>\n</tr>\n"),
16740
16741 // checked: Boolean
16742 // Our checked state
16743 checked: false,
16744 _setCheckedAttr: function(/*Boolean*/ checked){
16745 // summary:
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);
16751 },
16752
16753 onChange: function(/*Boolean*/ checked){
16754 // summary:
16755 // User defined function to handle check/uncheck events
16756 // tags:
16757 // callback
16758 },
16759
16760 _onClick: function(/*Event*/ e){
16761 // summary:
16762 // Clicking this item just toggles its state
16763 // tags:
16764 // private
16765 if(!this.disabled){
16766 this.set("checked", !this.checked);
16767 this.onChange(this.checked);
16768 }
16769 this.inherited(arguments);
16770 }
16771 });
16772
16773 }
16774
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");
16778
16779
16780
16781
16782
16783 dojo.declare("dijit.MenuSeparator",
16784 [dijit._Widget, dijit._Templated, dijit._Contained],
16785 {
16786 // summary:
16787 // A line between two menu items
16788
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"),
16790
16791 buildRendering: function(){
16792 this.inherited(arguments);
16793 dojo.setSelectable(this.domNode, false);
16794 },
16795
16796 isFocusable: function(){
16797 // summary:
16798 // Override to always return false
16799 // tags:
16800 // protected
16801
16802 return false; // Boolean
16803 }
16804 });
16805
16806 }
16807
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");
16811
16812
16813
16814
16815
16816
16817
16818
16819
16820
16821 // "dijit/MenuItem", "dijit/PopupMenuItem", "dijit/CheckedMenuItem", "dijit/MenuSeparator" for Back-compat (TODO: remove in 2.0)
16822
16823 dojo.declare("dijit._MenuBase",
16824 [dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
16825 {
16826 // summary:
16827 // Base class for Menu and MenuBar
16828
16829 // parentMenu: [readonly] Widget
16830 // pointer to menu that displayed me
16831 parentMenu: null,
16832
16833 // popupDelay: Integer
16834 // number of milliseconds before hovering (without clicking) causes the popup to automatically open.
16835 popupDelay: 500,
16836
16837 startup: function(){
16838 if(this._started){ return; }
16839
16840 dojo.forEach(this.getChildren(), function(child){ child.startup(); });
16841 this.startupKeyNavChildren();
16842
16843 this.inherited(arguments);
16844 },
16845
16846 onExecute: function(){
16847 // summary:
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.
16852 // tags:
16853 // protected
16854 },
16855
16856 onCancel: function(/*Boolean*/ closeAll){
16857 // summary:
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.
16861 // tags:
16862 // protected
16863 },
16864
16865 _moveToPopup: function(/*Event*/ evt){
16866 // summary:
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
16870 // tags:
16871 // private
16872
16873 if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
16874 this.focusedChild._onClick(evt);
16875 }else{
16876 var topMenu = this._getTopMenu();
16877 if(topMenu && topMenu._isMenuBar){
16878 topMenu.focusNext();
16879 }
16880 }
16881 },
16882
16883 _onPopupHover: function(/*Event*/ evt){
16884 // summary:
16885 // This handler is called when the mouse moves over the popup.
16886 // tags:
16887 // private
16888
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);
16897 }
16898 parentMenu.focusedChild = this.currentPopup.from_item;
16899 parentMenu.focusedChild._setSelected(true);
16900 // cancel the pending close
16901 this._stopPendingCloseTimer(this.currentPopup);
16902 }
16903 },
16904
16905 onItemHover: function(/*MenuItem*/ item){
16906 // summary:
16907 // Called when cursor is over a MenuItem.
16908 // tags:
16909 // protected
16910
16911 // Don't do anything unless user has "activated" the menu by:
16912 // 1) clicking it
16913 // 2) opening it from a parent menu (which automatically focuses it)
16914 if(this.isActive){
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);
16918 }
16919 }
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);
16926 }
16927 this._hoveredChild = item;
16928 },
16929
16930 _onChildBlur: function(item){
16931 // summary:
16932 // Called when a child MenuItem becomes inactive because focus
16933 // has been removed from the MenuItem *and* it's descendant menus.
16934 // tags:
16935 // private
16936 this._stopPopupTimer();
16937 item._setSelected(false);
16938 // Close all popups that are open and descendants of this menu
16939 var itemPopup = item.popup;
16940 if(itemPopup){
16941 this._stopPendingCloseTimer(itemPopup);
16942 itemPopup._pendingClose_timer = setTimeout(function(){
16943 itemPopup._pendingClose_timer = null;
16944 if(itemPopup.parentMenu){
16945 itemPopup.parentMenu.currentPopup = null;
16946 }
16947 dijit.popup.close(itemPopup); // this calls onClose
16948 }, this.popupDelay);
16949 }
16950 },
16951
16952 onItemUnhover: function(/*MenuItem*/ item){
16953 // summary:
16954 // Callback fires when mouse exits a MenuItem
16955 // tags:
16956 // protected
16957
16958 if(this.isActive){
16959 this._stopPopupTimer();
16960 }
16961 if(this._hoveredChild == item){ this._hoveredChild = null; }
16962 },
16963
16964 _stopPopupTimer: function(){
16965 // summary:
16966 // Cancels the popup timer because the user has stop hovering
16967 // on the MenuItem, etc.
16968 // tags:
16969 // private
16970 if(this.hover_timer){
16971 clearTimeout(this.hover_timer);
16972 this.hover_timer = null;
16973 }
16974 },
16975
16976 _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){
16977 // summary:
16978 // Cancels the pending-close timer because the close has been preempted
16979 // tags:
16980 // private
16981 if(popup._pendingClose_timer){
16982 clearTimeout(popup._pendingClose_timer);
16983 popup._pendingClose_timer = null;
16984 }
16985 },
16986
16987 _stopFocusTimer: function(){
16988 // summary:
16989 // Cancels the pending-focus timer because the menu was closed before focus occured
16990 // tags:
16991 // private
16992 if(this._focus_timer){
16993 clearTimeout(this._focus_timer);
16994 this._focus_timer = null;
16995 }
16996 },
16997
16998 _getTopMenu: function(){
16999 // summary:
17000 // Returns the top menu in this chain of Menus
17001 // tags:
17002 // private
17003 for(var top=this; top.parentMenu; top=top.parentMenu);
17004 return top;
17005 },
17006
17007 onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){
17008 // summary:
17009 // Handle clicks on an item.
17010 // tags:
17011 // private
17012
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();
17016 }
17017
17018 this.focusChild(item);
17019
17020 if(item.disabled){ return false; }
17021
17022 if(item.popup){
17023 this._openPopup();
17024 }else{
17025 // before calling user defined handler, close hierarchy of menus
17026 // and restore focus to place it was when menu was opened
17027 this.onExecute();
17028
17029 // user defined handler for click
17030 item.onClick(evt);
17031 }
17032 },
17033
17034 _openPopup: function(){
17035 // summary:
17036 // Open the popup to the side of/underneath the current menu item
17037 // tags:
17038 // protected
17039
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);
17048 }
17049 popup.parentMenu = this;
17050 popup.from_item = from_item; // helps finding the parent item that should be focused for this popup
17051 var self = this;
17052 dijit.popup.open({
17053 parent: this,
17054 popup: 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
17066 },
17067 onExecute: dojo.hitch(this, "_cleanUp")
17068 });
17069
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
17073
17074 if(popup.focus){
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;
17081 this.focus();
17082 }), 0);
17083 }
17084 },
17085
17086 _markActive: function(){
17087 // summary:
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");
17100 },
17101
17102 onOpen: function(/*Event*/ e){
17103 // summary:
17104 // Callback when this menu is opened.
17105 // This is called by the popup manager as notification that the menu
17106 // was opened.
17107 // tags:
17108 // private
17109
17110 this.isShowingNow = true;
17111 this._markActive();
17112 },
17113
17114 _markInactive: function(){
17115 // summary:
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");
17119 },
17120
17121 onClose: function(){
17122 // summary:
17123 // Callback when this menu is closed.
17124 // This is called by the popup manager as notification that the menu
17125 // was closed.
17126 // tags:
17127 // private
17128
17129 this._stopFocusTimer();
17130 this._markInactive();
17131 this.isShowingNow = false;
17132 this.parentMenu = null;
17133 },
17134
17135 _closeChild: function(){
17136 // summary:
17137 // Called when submenu is clicked or focus is lost. Close hierarchy of menus.
17138 // tags:
17139 // private
17140 this._stopPopupTimer();
17141
17142 var fromItem = this.focusedChild && this.focusedChild.from_item;
17143
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();
17149 }
17150 // Close all popups that are open and descendants of this menu
17151 dijit.popup.close(this.currentPopup);
17152 this.currentPopup = null;
17153 }
17154
17155 if(this.focusedChild){ // unhighlight the focused item
17156 this.focusedChild._setSelected(false);
17157 this.focusedChild._onUnhover();
17158 this.focusedChild = null;
17159 }
17160 },
17161
17162 _onItemFocus: function(/*MenuItem*/ item){
17163 // summary:
17164 // Called when child of this Menu gets focus from:
17165 // 1) clicking it
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
17171 }
17172 },
17173
17174 _onBlur: function(){
17175 // summary:
17176 // Called when focus is moved away from this Menu and it's submenus.
17177 // tags:
17178 // protected
17179 this._cleanUp();
17180 this.inherited(arguments);
17181 },
17182
17183 _cleanUp: function(){
17184 // summary:
17185 // Called when the user is done with this menu. Closes hierarchy of menus.
17186 // tags:
17187 // private
17188
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();
17192 }
17193 }
17194 });
17195
17196 dojo.declare("dijit.Menu",
17197 dijit._MenuBase,
17198 {
17199 // summary
17200 // A context menu you can assign to multiple elements
17201
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
17205
17206 constructor: function(){
17207 this._bindings = [];
17208 },
17209
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"),
17211
17212 baseClass: "dijitMenu",
17213
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.
17217 targetNodeIds: [],
17218
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,
17223
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,
17227
17228 // refocus: Boolean
17229 // When this menu closes, re-focus the element which had focus before it was opened.
17230 refocus: true,
17231
17232 postCreate: function(){
17233 if(this.contextMenuForWindow){
17234 this.bindDomNode(dojo.body());
17235 }else{
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);
17240 }
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]);
17245 },
17246
17247 _onKeyPress: function(/*Event*/ evt){
17248 // summary:
17249 // Handle keyboard based menu navigation.
17250 // tags:
17251 // protected
17252
17253 if(evt.ctrlKey || evt.altKey){ return; }
17254
17255 switch(evt.charOrCode){
17256 case this._openSubMenuKey:
17257 this._moveToPopup(evt);
17258 dojo.stopEvent(evt);
17259 break;
17260 case this._closeSubMenuKey:
17261 if(this.parentMenu){
17262 if(this.parentMenu._isMenuBar){
17263 this.parentMenu.focusPrev();
17264 }else{
17265 this.onCancel(false);
17266 }
17267 }else{
17268 dojo.stopEvent(evt);
17269 }
17270 break;
17271 }
17272 },
17273
17274 // thanks burstlib!
17275 _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){
17276 // summary:
17277 // Returns the window reference of the passed iframe
17278 // tags:
17279 // private
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
17285 },
17286
17287 _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){
17288 // summary:
17289 // Returns a reference to the document object inside iframe_el
17290 // tags:
17291 // protected
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)
17295 || null;
17296 return doc; // HTMLDocument
17297 },
17298
17299 bindDomNode: function(/*String|DomNode*/ node){
17300 // summary:
17301 // Attach menu to given node
17302 node = dojo.byId(node);
17303
17304 var cn; // Connect node
17305
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"){
17309 var iframe = node,
17310 win = this._iframeContentWindow(iframe);
17311 cn = dojo.withGlobal(win, dojo.body);
17312 }else{
17313
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);
17317 }
17318
17319
17320 // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode())
17321 var binding = {
17322 node: node,
17323 iframe: iframe
17324 };
17325
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));
17330
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){
17336 return [
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});
17343 }),
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
17348 }
17349 })
17350 ];
17351 });
17352 binding.connects = cn ? doConnects(cn) : [];
17353
17354 if(iframe){
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.
17359
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
17363
17364 var win = this._iframeContentWindow(iframe);
17365 cn = dojo.withGlobal(win, dojo.body);
17366 binding.connects = doConnects(cn);
17367 });
17368 if(iframe.addEventListener){
17369 iframe.addEventListener("load", binding.onloadHandler, false);
17370 }else{
17371 iframe.attachEvent("onload", binding.onloadHandler);
17372 }
17373 }
17374 },
17375
17376 unBindDomNode: function(/*String|DomNode*/ nodeName){
17377 // summary:
17378 // Detach menu from given node
17379
17380 var node;
17381 try{
17382 node = dojo.byId(nodeName);
17383 }catch(e){
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.
17387 return;
17388 }
17389
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);
17395
17396 // Remove listener for iframe onload events
17397 var iframe = b.iframe;
17398 if(iframe){
17399 if(iframe.removeEventListener){
17400 iframe.removeEventListener("load", b.onloadHandler, false);
17401 }else{
17402 iframe.detachEvent("onload", b.onloadHandler);
17403 }
17404 }
17405
17406 dojo.removeAttr(node, attrName);
17407 delete this._bindings[bid];
17408 }
17409 },
17410
17411 _scheduleOpen: function(/*DomNode?*/ target, /*DomNode?*/ iframe, /*Object?*/ coords){
17412 // summary:
17413 // Set timer to display myself. Using a timer rather than displaying immediately solves
17414 // two problems:
17415 //
17416 // 1. IE: without the delay, focus work in "open" causes the system
17417 // context menu to appear in spite of stopEvent.
17418 //
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.)
17422
17423 if(!this._openTimer){
17424 this._openTimer = setTimeout(dojo.hitch(this, function(){
17425 delete this._openTimer;
17426 this._openMyself({
17427 target: target,
17428 iframe: iframe,
17429 coords: coords
17430 });
17431 }), 1);
17432 }
17433 },
17434
17435 _openMyself: function(args){
17436 // summary:
17437 // Internal function for opening myself when the user does a right-click or something similar.
17438 // args:
17439 // This is an Object containing:
17440 // * target:
17441 // The node that is being clicked
17442 // * iframe:
17443 // If an <iframe> is being clicked, iframe points to that iframe
17444 // * coords:
17445 // Put menu at specified x/y position in viewport, or if iframe is
17446 // specified, then relative to iframe.
17447 //
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).
17451
17452 var target = args.target,
17453 iframe = args.iframe,
17454 coords = args.coords;
17455
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.
17458 if(coords){
17459 if(iframe){
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);
17465
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);
17470
17471 coords.x += ifc.x + left - scroll.x;
17472 coords.y += ifc.y + top - scroll.y;
17473 }
17474 }else{
17475 coords = dojo.position(target, true);
17476 coords.x += 10;
17477 coords.y += 10;
17478 }
17479
17480 var self=this;
17481 var savedFocus = dijit.getFocus(this);
17482 function closeAndRestoreFocus(){
17483 // user has clicked on a menu or popup
17484 if(self.refocus){
17485 dijit.focus(savedFocus);
17486 }
17487 dijit.popup.close(self);
17488 }
17489 dijit.popup.open({
17490 popup: this,
17491 x: coords.x,
17492 y: coords.y,
17493 onExecute: closeAndRestoreFocus,
17494 onCancel: closeAndRestoreFocus,
17495 orient: this.isLeftToRight() ? 'L' : 'R'
17496 });
17497 this.focus();
17498
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
17506 };
17507 },
17508
17509 uninitialize: function(){
17510 dojo.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this);
17511 this.inherited(arguments);
17512 }
17513 }
17514 );
17515
17516 }
17517
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");
17521
17522
17523
17524
17525
17526
17527
17528 dojo.declare("dijit.form._SelectMenu", dijit.Menu, {
17529 // summary:
17530 // An internally-used menu for dropdown that allows us a vertical scrollbar
17531 buildRendering: function(){
17532 // summary:
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"}}));
17538 if(o.parentNode){
17539 o.parentNode.replaceChild(n, o);
17540 }
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");
17546 n.appendChild(o);
17547 },
17548
17549 postCreate: function(){
17550 // summary:
17551 // stop mousemove from selecting text on IE to be consistent with other browsers
17552
17553 this.inherited(arguments);
17554
17555 this.connect(this.domNode, "onmousemove", dojo.stopEvent);
17556 },
17557
17558 resize: function(/*Object*/ mb){
17559 // summary:
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
17563 // marginBox.
17564 //
17565 // mb: Object
17566 // The margin box to set this dropdown to.
17567 if(mb){
17568 dojo.marginBox(this.domNode, mb);
17569 if("w" in 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%";
17574 }
17575 }
17576 }
17577 });
17578
17579 dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropDown], {
17580 // summary:
17581 // This is a "styleable" select box - it is basically a DropDownButton which
17582 // can take a <select> as its input.
17583
17584 baseClass: "dijitSelect",
17585
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\">&#9660;</div\n\t\t></td\n\t></tr></tbody\n></table>\n"),
17587
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"}),
17591
17592 // required: Boolean
17593 // Can be true or false, default is false.
17594 required: false,
17595
17596 // state: String
17597 // Shows current state (ie, validation result) of input (Normal, Warning, or Error)
17598 state: "",
17599
17600 // message: String
17601 // Currently displayed error/prompt message
17602 message: "",
17603
17604 // tooltipPosition: String[]
17605 // See description of dijit.Tooltip.defaultPosition for details on this parameter.
17606 tooltipPosition: [],
17607
17608 // emptyLabel: string
17609 // What to display in an "empty" dropdown
17610 emptyLabel: "&nbsp;",
17611
17612 // _isLoaded: Boolean
17613 // Whether or not we have been loaded
17614 _isLoaded: false,
17615
17616 // _childrenLoaded: Boolean
17617 // Whether or not our children have been loaded
17618 _childrenLoaded: false,
17619
17620 _fillContent: function(){
17621 // summary:
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;
17628 }
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");
17632 },
17633
17634 _getMenuItemForOption: function(/*dijit.form.__SelectOption*/ option){
17635 // summary:
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();
17641 }else{
17642 // Just a regular menu option
17643 var click = dojo.hitch(this, "_setValueAttr", option);
17644 var item = new dijit.MenuItem({
17645 option: option,
17646 label: option.label || this.emptyLabel,
17647 onClick: click,
17648 disabled: option.disabled || false
17649 });
17650 dijit.setWaiRole(item.focusNode, "listitem");
17651 return item;
17652 }
17653 },
17654
17655 _addOptionItem: function(/*dijit.form.__SelectOption*/ option){
17656 // summary:
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
17659 // in that place.
17660 if(this.dropDown){
17661 this.dropDown.addChild(this._getMenuItemForOption(option));
17662 }
17663 },
17664
17665 _getChildren: function(){
17666 if(!this.dropDown){
17667 return [];
17668 }
17669 return this.dropDown.getChildren();
17670 },
17671
17672 _loadChildren: function(/*Boolean*/ loadMenuItems){
17673 // summary:
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.
17679
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)
17684 if(this.dropDown){
17685 delete this.dropDown.focusedChild;
17686 }
17687 if(this.options.length){
17688 this.inherited(arguments);
17689 }else{
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: "&nbsp;"});
17694 this.dropDown.addChild(item);
17695 }
17696 }else{
17697 this._updateSelection();
17698 }
17699
17700 this._isLoaded = false;
17701 this._childrenLoaded = true;
17702
17703 if(!this._loadingStore){
17704 // Don't call this if we are loading - since we will handle it later
17705 this._setValueAttr(this.value);
17706 }
17707 },
17708
17709 _setValueAttr: function(value){
17710 this.inherited(arguments);
17711 dojo.attr(this.valueNode, "value", this.get("value"));
17712 },
17713
17714 _setDisplay: function(/*String*/ newDisplay){
17715 // summary:
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);
17720 },
17721
17722 validate: function(/*Boolean*/ isFocused){
17723 // summary:
17724 // Called by oninit, onblur, and onkeypress.
17725 // description:
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
17728 // set the value.
17729
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);
17737 if(message){
17738 dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
17739 }
17740 }
17741 return isValid;
17742 },
17743
17744 isValid: function(/*Boolean*/ isFocused){
17745 // summary:
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
17749 },
17750
17751 reset: function(){
17752 // summary:
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", "")
17758 },
17759
17760 postMixInProperties: function(){
17761 // summary:
17762 // set the missing message
17763 this.inherited(arguments);
17764 this._missingMsg = dojo.i18n.getLocalization("dijit.form", "validate",
17765 this.lang).missingMessage;
17766 },
17767
17768 postCreate: function(){
17769 // summary:
17770 // stop mousemove from selecting text on IE to be consistent with other browsers
17771
17772 this.inherited(arguments);
17773
17774 this.connect(this.domNode, "onmousemove", dojo.stopEvent);
17775 },
17776
17777 _setStyleAttr: function(/*String||Object*/ value){
17778 this.inherited(arguments);
17779 dojo.toggleClass(this.domNode, this.baseClass + "FixedWidth", !!this.tableNode.style.width);
17780 },
17781
17782 isLoaded: function(){
17783 return this._isLoaded;
17784 },
17785
17786 loadDropDown: function(/*Function*/ loadCallback){
17787 // summary:
17788 // populates the menu
17789 this._loadChildren(true);
17790 this._isLoaded = true;
17791 loadCallback();
17792 },
17793
17794 closeDropDown: function(){
17795 // overriding _HasDropDown.closeDropDown()
17796 this.inherited(arguments);
17797
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 = "";
17803 }
17804 },
17805
17806 uninitialize: function(preserveDom){
17807 if(this.dropDown && !this.dropDown._destroyed){
17808 this.dropDown.destroyRecursive(preserveDom);
17809 delete this.dropDown;
17810 }
17811 this.inherited(arguments);
17812 }
17813 });
17814
17815 }
17816
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");
17820
17821
17822
17823 dojo.declare("dijit.form.SimpleTextarea",
17824 dijit.form.TextBox,
17825 {
17826 // summary:
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.
17830 //
17831 // example:
17832 // | <textarea dojoType="dijit.form.SimpleTextarea" name="foo" value="bar" rows=30 cols=40></textarea>
17833 //
17834 // example:
17835 // | new dijit.form.SimpleTextarea({ rows:20, cols:30 }, "foo");
17836
17837 baseClass: "dijitTextBox dijitTextArea",
17838
17839 attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, {
17840 rows:"textbox", cols: "textbox"
17841 }),
17842
17843 // rows: Number
17844 // The number of rows of text.
17845 rows: "3",
17846
17847 // rows: Number
17848 // The number of characters per line.
17849 cols: "20",
17850
17851 templateString: "<textarea ${!nameAttrSetting} dojoAttachPoint='focusNode,containerNode,textbox' autocomplete='off'></textarea>",
17852
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;
17858 }
17859 this.inherited(arguments);
17860 },
17861
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");
17866 }
17867 },
17868
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
17872 if(value){
17873 value = value.replace(/\r/g,"");
17874 }
17875 return this.inherited(arguments);
17876 },
17877
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;
17885 if(overflow > 0){
17886 if(e){ dojo.stopEvent(e); }
17887 var textarea = this.textbox;
17888 if(textarea.selectionStart){
17889 var pos = textarea.selectionStart;
17890 var cr = 0;
17891 if(dojo.isOpera){
17892 cr = (this.textbox.value.substring(0,pos).match(/\r/g) || []).length;
17893 }
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
17897 textarea.focus();
17898 var range = dojo.doc.selection.createRange();
17899 // delete overflow characters
17900 range.moveStart("character", -overflow);
17901 range.text = '';
17902 // show cursor
17903 range.select();
17904 }
17905 }
17906 this._previousValue = this.textbox.value;
17907 }
17908 this.inherited(arguments);
17909 }
17910 });
17911
17912 }
17913
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");
17917
17918
17919
17920
17921
17922
17923
17924
17925 dojo.declare("dijit.InlineEditBox",
17926 dijit._Widget,
17927 {
17928 // summary:
17929 // An element with in-line edit capabilites
17930 //
17931 // description:
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')
17943 // - void focus()
17944 // - DOM-node focusNode = node containing editable text
17945
17946 // editing: [readonly] Boolean
17947 // Is the node currently in edit mode?
17948 editing: false,
17949
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)
17953 autoSave: true,
17954
17955 // buttonSave: String
17956 // Save button label
17957 buttonSave: "",
17958
17959 // buttonCancel: String
17960 // Cancel button label
17961 buttonCancel: "",
17962
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,
17967
17968 // editor: String|Function
17969 // Class name (or reference to the Class) for Editor widget
17970 editor: "dijit.form.TextBox",
17971
17972 // editorWrapper: String|Function
17973 // Class name (or reference to the Class) for widget that wraps the editor widget, displaying save/cancel
17974 // buttons.
17975 editorWrapper: "dijit._InlineEditor",
17976
17977 // editorParams: Object
17978 // Set of parameters for editor, like {required: true}
17979 editorParams: {},
17980
17981 // disabled: Boolean
17982 // If true, clicking the InlineEditBox to edit it will have no effect.
17983 disabled: false,
17984
17985 onChange: function(value){
17986 // summary:
17987 // Set this handler to be notified of changes to value.
17988 // tags:
17989 // callback
17990 },
17991
17992 onCancel: function(){
17993 // summary:
17994 // Set this handler to be notified when editing is cancelled.
17995 // tags:
17996 // callback
17997 },
17998
17999 // width: String
18000 // Width of editor. By default it's width=100% (ie, block mode).
18001 width: "100%",
18002
18003 // value: String
18004 // The display value of the widget in read-only mode
18005 value: "",
18006
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;'>&nbsp;&nbsp;&nbsp;&nbsp;&#x270d;&nbsp;&nbsp;&nbsp;&nbsp;</span>" :
18011 "<span style='text-decoration: underline;'>&nbsp;&nbsp;&nbsp;&nbsp;&#x270d;&nbsp;&nbsp;&nbsp;&nbsp;</span>",
18012
18013 constructor: function(){
18014 // summary:
18015 // Sets up private arrays etc.
18016 // tags:
18017 // private
18018 this.editorParams = {};
18019 },
18020
18021 postMixInProperties: function(){
18022 this.inherited(arguments);
18023
18024 // save pointer to original source node, since Widget nulls-out srcNodeRef
18025 this.displayNode = this.srcNodeRef;
18026
18027 // connect handlers to the display node
18028 var events = {
18029 ondijitclick: "_onClick",
18030 onmouseover: "_onMouseOver",
18031 onmouseout: "_onMouseOut",
18032 onfocus: "_onMouseOver",
18033 onblur: "_onMouseOut"
18034 };
18035 for(var name in events){
18036 this.connect(this.displayNode, name, events[name]);
18037 }
18038 dijit.setWaiRole(this.displayNode, "button");
18039 if(!this.displayNode.getAttribute("tabIndex")){
18040 this.displayNode.setAttribute("tabIndex", 0);
18041 }
18042
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||""));
18046 }
18047 if(!this.value){
18048 this.displayNode.innerHTML = this.noValueIndicator;
18049 }
18050
18051 dojo.addClass(this.displayNode, 'dijitInlineEditBoxDisplayMode');
18052 },
18053
18054 setDisabled: function(/*Boolean*/ disabled){
18055 // summary:
18056 // Deprecated. Use set('disabled', ...) instead.
18057 // tags:
18058 // deprecated
18059 dojo.deprecated("dijit.InlineEditBox.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0");
18060 this.set('disabled', disabled);
18061 },
18062
18063 _setDisabledAttr: function(/*Boolean*/ disabled){
18064 // summary:
18065 // Hook to make set("disabled", ...) work.
18066 // Set disabled state of widget.
18067 dijit.setWaiState(this.domNode, "disabled", disabled);
18068 if(disabled){
18069 this.displayNode.removeAttribute("tabIndex");
18070 }else{
18071 this.displayNode.setAttribute("tabIndex", 0);
18072 }
18073 dojo.toggleClass(this.displayNode, "dijitInlineEditBoxDisplayModeDisabled", disabled);
18074 this._set("disabled", disabled);
18075 },
18076
18077 _onMouseOver: function(){
18078 // summary:
18079 // Handler for onmouseover and onfocus event.
18080 // tags:
18081 // private
18082 if(!this.disabled){
18083 dojo.addClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover");
18084 }
18085 },
18086
18087 _onMouseOut: function(){
18088 // summary:
18089 // Handler for onmouseout and onblur event.
18090 // tags:
18091 // private
18092 dojo.removeClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover");
18093 },
18094
18095 _onClick: function(/*Event*/ e){
18096 // summary:
18097 // Handler for onclick event.
18098 // tags:
18099 // private
18100 if(this.disabled){ return; }
18101 if(e){ dojo.stopEvent(e); }
18102 this._onMouseOut();
18103
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);
18106 },
18107
18108 edit: function(){
18109 // summary:
18110 // Display the editor widget in place of the original (read only) markup.
18111 // tags:
18112 // private
18113
18114 if(this.disabled || this.editing){ return; }
18115 this.editing = true;
18116
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";
18121
18122 if(this.wrapperWidget){
18123 var ew = this.wrapperWidget.editWidget;
18124 ew.set("displayedValue" in ew ? "displayedValue" : "value", this.value);
18125 }else{
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");
18130
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({
18134 value: this.value,
18135 buttonSave: this.buttonSave,
18136 buttonCancel: this.buttonCancel,
18137 dir: this.dir,
18138 lang: this.lang,
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")
18145 }, placeholder);
18146 if(!this._started){
18147 this.startup();
18148 }
18149 }
18150 var ww = this.wrapperWidget;
18151
18152 if(dojo.isIE){
18153 dijit.focus(dijit.getFocus()); // IE (at least 8) needs help with tab order changes
18154 }
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
18160
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
18164
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();
18170 }), 0);
18171 },
18172
18173 _onBlur: function(){
18174 // summary:
18175 // Called when focus moves outside the InlineEditBox.
18176 // Performs garbage collection.
18177 // tags:
18178 // private
18179
18180 this.inherited(arguments);
18181 if(!this.editing){
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;
18187 }
18188 }), 0);
18189 */
18190 }
18191 },
18192
18193 destroy: function(){
18194 if(this.wrapperWidget && !this.wrapperWidget._destroyed){
18195 this.wrapperWidget.destroy();
18196 delete this.wrapperWidget;
18197 }
18198 this.inherited(arguments);
18199 },
18200
18201 _showText: function(/*Boolean*/ focus){
18202 // summary:
18203 // Revert to display mode, and optionally focus on display node
18204 // tags:
18205 // private
18206
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);
18211 if(focus){
18212 dijit.focus(this.displayNode);
18213 }
18214 },
18215
18216 save: function(/*Boolean*/ focus){
18217 // summary:
18218 // Save the contents of the editor and revert to display mode.
18219 // focus: Boolean
18220 // Focus on the display mode text
18221 // tags:
18222 // private
18223
18224 if(this.disabled || !this.editing){ return; }
18225 this.editing = false;
18226
18227 var ww = this.wrapperWidget;
18228 var value = ww.getValue();
18229 this.set('value', value); // display changed, formatted value
18230
18231 this._showText(focus); // set focus as needed
18232 },
18233
18234 setValue: function(/*String*/ val){
18235 // summary:
18236 // Deprecated. Use set('value', ...) instead.
18237 // tags:
18238 // deprecated
18239 dojo.deprecated("dijit.InlineEditBox.setValue() is deprecated. Use set('value', ...) instead.", "", "2.0");
18240 return this.set("value", val);
18241 },
18242
18243 _setValueAttr: function(/*String*/ val){
18244 // summary:
18245 // Hook to make set("value", ...) work.
18246 // Inserts specified HTML value into this node, or an "input needed" character if node is blank.
18247
18248 val = dojo.trim(val);
18249 var renderVal = this.renderAsHtml ? val : val.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;").replace(/\n/g, "<br>");
18250 this.displayNode.innerHTML = renderVal || this.noValueIndicator;
18251 this._set("value", val);
18252
18253 if(this._started){
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
18256 }
18257 },
18258
18259 getValue: function(){
18260 // summary:
18261 // Deprecated. Use get('value') instead.
18262 // tags:
18263 // deprecated
18264 dojo.deprecated("dijit.InlineEditBox.getValue() is deprecated. Use get('value') instead.", "", "2.0");
18265 return this.get("value");
18266 },
18267
18268 cancel: function(/*Boolean*/ focus){
18269 // summary:
18270 // Revert to display mode, discarding any changes made in the editor
18271 // tags:
18272 // private
18273
18274 if(this.disabled || !this.editing){ return; }
18275 this.editing = false;
18276
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
18279
18280 this._showText(focus);
18281 }
18282 });
18283
18284 dojo.declare(
18285 "dijit._InlineEditor",
18286 [dijit._Widget, dijit._Templated],
18287 {
18288 // summary:
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
18292 //
18293 // Has mainly the same parameters as InlineEditBox, plus these values:
18294 //
18295 // style: Object
18296 // Set of CSS attributes of display node, to replicate in editor
18297 //
18298 // value: String
18299 // Value as an HTML string or plain text string, depending on renderAsHTML flag
18300
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,
18303
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]; }
18309 }, this);
18310 },
18311
18312 buildRendering: function(){
18313 this.inherited(arguments);
18314
18315 // Create edit widget in place in the template
18316 var cls = typeof this.editor == "string" ? dojo.getObject(this.editor) : this.editor;
18317
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]+";";
18331 }
18332 }, this);
18333 dojo.forEach(["marginTop","marginBottom","marginLeft", "marginRight"], function(prop){
18334 this.domNode.style[prop] = srcStyle[prop];
18335 }, this);
18336 var width = this.inlineEditBox.width;
18337 if(width == "100%"){
18338 // block mode
18339 editStyle += "width:100%;";
18340 this.domNode.style.display = "block";
18341 }else{
18342 // inline-block mode
18343 editStyle += "width:" + (width + (Number(width) == width ? "px" : "")) + ";";
18344 }
18345 var editorParams = dojo.delegate(this.inlineEditBox.editorParams, {
18346 style: editStyle,
18347 dir: this.dir,
18348 lang: this.lang
18349 });
18350 editorParams[ "displayedValue" in cls.prototype ? "displayedValue" : "value"] = this.value;
18351 this.editWidget = new cls(editorParams, this.editorPlaceholder);
18352
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);
18357 }
18358 },
18359
18360 postCreate: function(){
18361 this.inherited(arguments);
18362
18363 var ew = this.editWidget;
18364
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");
18368
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");
18373 }else{
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);
18379 }
18380 }
18381 },
18382
18383 _onIntermediateChange: function(val){
18384 // summary:
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());
18388 },
18389
18390 destroy: function(){
18391 this.editWidget.destroy(true); // let the parent wrapper widget clean up the DOM
18392 this.inherited(arguments);
18393 },
18394
18395 getValue: function(){
18396 // summary:
18397 // Return the [display] value of the edit widget
18398 var ew = this.editWidget;
18399 return String(ew.get("displayedValue" in ew ? "displayedValue" : "value"));
18400 },
18401
18402 _onKeyPress: function(e){
18403 // summary:
18404 // Handler for keypress in the edit box in autoSave mode.
18405 // description:
18406 // For autoSave widgets, if Esc/Enter, call cancel/save.
18407 // tags:
18408 // private
18409
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){
18414 dojo.stopEvent(e);
18415 this.cancel(true); // sets editing=false which short-circuits _onBlur processing
18416 }else if(e.charOrCode == dojo.keys.ENTER && e.target.tagName == "INPUT"){
18417 dojo.stopEvent(e);
18418 this._onChange(); // fire _onBlur and then save
18419 }
18420
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.
18434 }
18435 },
18436
18437 _onBlur: function(){
18438 // summary:
18439 // Called when focus moves outside the editor
18440 // tags:
18441 // private
18442
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()){
18448 this.save(false);
18449 }
18450 }
18451 },
18452
18453 _onChange: function(){
18454 // summary:
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.
18458 // tags:
18459 // private
18460
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
18464 }
18465 },
18466
18467 enableSave: function(){
18468 // summary:
18469 // User overridable function returning a Boolean to indicate
18470 // if the Save button should be enabled or not - usually due to invalid conditions
18471 // tags:
18472 // extension
18473 return (
18474 this.editWidget.isValid
18475 ? this.editWidget.isValid()
18476 : true
18477 );
18478 },
18479
18480 focus: function(){
18481 // summary:
18482 // Focus the edit widget.
18483 // tags:
18484 // protected
18485
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);
18490 }
18491 }), 0);
18492 }
18493 });
18494
18495 }
18496
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");
18500
18501
18502
18503 /*=====
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.
18510 // path: String?
18511 // The path to use for the cookie.
18512 // domain: String?
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;
18517 this.path = path;
18518 this.domain = domain;
18519 this.secure = secure;
18520 }
18521 =====*/
18522
18523
18524 dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/props){
18525 // summary:
18526 // Get or set a cookie.
18527 // description:
18528 // If one argument is passed, returns the value of the cookie
18529 // For two or more arguments, acts as a setter.
18530 // name:
18531 // Name of the cookie
18532 // value:
18533 // Value for the cookie
18534 // props:
18535 // Properties for the cookie
18536 // example:
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 });
18540 //
18541 // example:
18542 // de-serialize a cookie back into a JavaScript object:
18543 // | var config = dojo.fromJson(dojo.cookie("configObj"));
18544 //
18545 // example:
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
18552 }else{
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;
18560 }
18561 if(exp && exp.toUTCString){ props.expires = exp.toUTCString(); }
18562
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; }
18569 }
18570 document.cookie = updatedCookie;
18571 }
18572 };
18573
18574 dojo.cookie.isSupported = function(){
18575 // summary:
18576 // Use to determine if the current browser supports cookies or not.
18577 //
18578 // Returns true if user allows cookies.
18579 // Returns false if user doesn't allow cookies.
18580
18581 if(!("cookieEnabled" in navigator)){
18582 this("__djCookieTest__", "CookiesAllowed");
18583 navigator.cookieEnabled = this("__djCookieTest__") == "CookiesAllowed";
18584 if(navigator.cookieEnabled){
18585 this("__djCookieTest__", "", {expires: -1});
18586 }
18587 }
18588 return navigator.cookieEnabled;
18589 };
18590
18591 }
18592
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");
18596
18597
18598
18599
18600
18601
18602
18603 dojo.declare(
18604 "dijit.layout.StackController",
18605 [dijit._Widget, dijit._Templated, dijit._Container],
18606 {
18607 // summary:
18608 // Set of buttons to select a page in a page list.
18609 // description:
18610 // Monitors the specified StackContainer, and whenever a page is
18611 // added, deleted, or selected, updates itself accordingly.
18612
18613 templateString: "<span role='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>",
18614
18615 // containerId: [const] String
18616 // The id of the page container that I point to
18617 containerId: "",
18618
18619 // buttonWidget: [const] String
18620 // The name of the button widget to create to correspond to each page
18621 buttonWidget: "dijit.layout._StackButton",
18622
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
18627 },
18628
18629 buildRendering: function(){
18630 this.inherited(arguments);
18631 dijit.setWaiRole(this.domNode, "tablist"); // TODO: unneeded? it's in template above.
18632 },
18633
18634 postCreate: function(){
18635 this.inherited(arguments);
18636
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");
18643 },
18644
18645 onStartup: function(/*Object*/ info){
18646 // summary:
18647 // Called after StackContainer has finished initializing
18648 // tags:
18649 // private
18650 dojo.forEach(info.children, this.onAddChild, this);
18651 if(info.selected){
18652 // Show button corresponding to selected pane (unless selected
18653 // is null because there are no panes)
18654 this.onSelectChild(info.selected);
18655 }
18656 },
18657
18658 destroy: function(){
18659 for(var pane in this.pane2button){
18660 this.onRemoveChild(dijit.byId(pane));
18661 }
18662 this.inherited(arguments);
18663 },
18664
18665 onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){
18666 // summary:
18667 // Called whenever a page is added to the container.
18668 // Create button corresponding to the page.
18669 // tags:
18670 // private
18671
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,
18676 label: page.title,
18677 dir: page.dir,
18678 lang: page.lang,
18679 showLabel: page.showTitle,
18680 iconClass: page.iconClass,
18681 closeButton: page.closable,
18682 title: page.tooltip
18683 });
18684 dijit.setWaiState(button.focusNode,"selected", "false");
18685
18686
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"];
18690
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);
18695 });
18696 });
18697
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))
18702 ];
18703
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;
18711 }
18712 // make sure all tabs have the same length
18713 if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){
18714 this._rectifyRtlTabList();
18715 }
18716 },
18717
18718 onRemoveChild: function(/*dijit._Widget*/ page){
18719 // summary:
18720 // Called whenever a page is removed from the container.
18721 // Remove the button corresponding to the page.
18722 // tags:
18723 // private
18724
18725 if(this._currentChild === page){ this._currentChild = null; }
18726
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];
18732
18733 var button = this.pane2button[page.id];
18734 if(button){
18735 this.removeChild(button);
18736 delete this.pane2button[page.id];
18737 button.destroy();
18738 }
18739 delete page.controlButton;
18740 },
18741
18742 onSelectChild: function(/*dijit._Widget*/ page){
18743 // summary:
18744 // Called when a page has been selected in the StackContainer, either by me or by another StackController
18745 // tags:
18746 // private
18747
18748 if(!page){ return; }
18749
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");
18755 }
18756
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);
18764 },
18765
18766 onButtonClick: function(/*dijit._Widget*/ page){
18767 // summary:
18768 // Called whenever one of my child buttons is pressed in an attempt to select a page
18769 // tags:
18770 // private
18771
18772 var container = dijit.byId(this.containerId);
18773 container.selectChild(page);
18774 },
18775
18776 onCloseButtonClick: function(/*dijit._Widget*/ page){
18777 // summary:
18778 // Called whenever one of my child buttons [X] is pressed in an attempt to close a page
18779 // tags:
18780 // private
18781
18782 var container = dijit.byId(this.containerId);
18783 container.closeChild(page);
18784 if(this._currentChild){
18785 var b = this.pane2button[this._currentChild.id];
18786 if(b){
18787 dijit.focus(b.focusNode || b.domNode);
18788 }
18789 }
18790 },
18791
18792 // TODO: this is a bit redundant with forward, back api in StackContainer
18793 adjacent: function(/*Boolean*/ forward){
18794 // summary:
18795 // Helper for onkeypress to find next/previous button
18796 // tags:
18797 // private
18798
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
18806 },
18807
18808 onkeypress: function(/*Event*/ e){
18809 // summary:
18810 // Handle keystrokes on the page list, for advancing to next/previous button
18811 // and closing the current page if the page is closable.
18812 // tags:
18813 // private
18814
18815 if(this.disabled || e.altKey ){ return; }
18816 var forward = null;
18817 if(e.ctrlKey || !e._djpage){
18818 var k = dojo.keys;
18819 switch(e.charOrCode){
18820 case k.LEFT_ARROW:
18821 case k.UP_ARROW:
18822 if(!e._djpage){ forward = false; }
18823 break;
18824 case k.PAGE_UP:
18825 if(e.ctrlKey){ forward = false; }
18826 break;
18827 case k.RIGHT_ARROW:
18828 case k.DOWN_ARROW:
18829 if(!e._djpage){ forward = true; }
18830 break;
18831 case k.PAGE_DOWN:
18832 if(e.ctrlKey){ forward = true; }
18833 break;
18834 case k.HOME:
18835 case k.END:
18836 var children = this.getChildren();
18837 if(children && children.length){
18838 children[e.charOrCode == k.HOME ? 0 : children.length-1].onClick();
18839 }
18840 dojo.stopEvent(e);
18841 break;
18842 case k.DELETE:
18843 if(this._currentChild.closable){
18844 this.onCloseButtonClick(this._currentChild);
18845 }
18846 dojo.stopEvent(e);
18847 break;
18848 default:
18849 if(e.ctrlKey){
18850 if(e.charOrCode === k.TAB){
18851 this.adjacent(!e.shiftKey).onClick();
18852 dojo.stopEvent(e);
18853 }else if(e.charOrCode == "w"){
18854 if(this._currentChild.closable){
18855 this.onCloseButtonClick(this._currentChild);
18856 }
18857 dojo.stopEvent(e); // avoid browser tab closing.
18858 }
18859 }
18860 }
18861 // handle next/previous page navigation (left/right arrow, etc.)
18862 if(forward !== null){
18863 this.adjacent(forward).onClick();
18864 dojo.stopEvent(e);
18865 }
18866 }
18867 },
18868
18869 onContainerKeyPress: function(/*Object*/ info){
18870 // summary:
18871 // Called when there was a keypress on the container
18872 // tags:
18873 // private
18874 info.e._djpage = info.page;
18875 this.onkeypress(info.e);
18876 }
18877 });
18878
18879
18880 dojo.declare("dijit.layout._StackButton",
18881 dijit.form.ToggleButton,
18882 {
18883 // summary:
18884 // Internal widget used by StackContainer.
18885 // description:
18886 // The button-like or tab-like object you click to select or delete a page
18887 // tags:
18888 // private
18889
18890 // Override _FormWidget.tabIndex.
18891 // StackContainer buttons are not in the tab order by default.
18892 // Probably we should be calling this.startupKeyNavChildren() instead.
18893 tabIndex: "-1",
18894
18895 buildRendering: function(/*Event*/ evt){
18896 this.inherited(arguments);
18897 dijit.setWaiRole((this.focusNode || this.domNode), "tab");
18898 },
18899
18900 onClick: function(/*Event*/ evt){
18901 // summary:
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);
18906
18907 // ... now let StackController catch the event and tell me what to do
18908 },
18909
18910 onClickCloseButton: function(/*Event*/ evt){
18911 // summary:
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();
18916 }
18917 });
18918
18919 }
18920
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");
18924
18925
18926
18927
18928
18929
18930
18931 dojo.declare(
18932 "dijit.layout.StackContainer",
18933 dijit.layout._LayoutWidget,
18934 {
18935 // summary:
18936 // A container that has multiple children, but shows only
18937 // one child at a time
18938 //
18939 // description:
18940 // A container for widgets (ContentPanes, for example) That displays
18941 // only one Widget at a time.
18942 //
18943 // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild
18944 //
18945 // Can be base class for container, Wizard, Show, etc.
18946
18947 // doLayout: Boolean
18948 // If true, change the size of my currently displayed child to match my size
18949 doLayout: true,
18950
18951 // persist: Boolean
18952 // Remembers the selected child across sessions
18953 persist: false,
18954
18955 baseClass: "dijitStackContainer",
18956
18957 /*=====
18958 // selectedChildWidget: [readonly] dijit._Widget
18959 // References the currently selected child widget, if any.
18960 // Adjust selected child with selectChild() method.
18961 selectedChildWidget: null,
18962 =====*/
18963
18964 buildRendering: function(){
18965 this.inherited(arguments);
18966 dojo.addClass(this.domNode, "dijitLayoutContainer");
18967 dijit.setWaiRole(this.containerNode, "tabpanel");
18968 },
18969
18970 postCreate: function(){
18971 this.inherited(arguments);
18972 this.connect(this.domNode, "onkeypress", this._onKeyPress);
18973 },
18974
18975 startup: function(){
18976 if(this._started){ return; }
18977
18978 var children = this.getChildren();
18979
18980 // Setup each page panel to be initially hidden
18981 dojo.forEach(children, this._setupChild, this);
18982
18983 // Figure out which child to initially display, defaulting to first one
18984 if(this.persist){
18985 this.selectedChildWidget = dijit.byId(dojo.cookie(this.id + "_selectedChild"));
18986 }else{
18987 dojo.some(children, function(child){
18988 if(child.selected){
18989 this.selectedChildWidget = child;
18990 }
18991 return child.selected;
18992 }, this);
18993 }
18994 var selected = this.selectedChildWidget;
18995 if(!selected && children[0]){
18996 selected = this.selectedChildWidget = children[0];
18997 selected.selected = true;
18998 }
18999
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}]);
19004
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);
19008 },
19009
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);
19018 }
19019 this.inherited(arguments);
19020 },
19021
19022 _setupChild: function(/*dijit._Widget*/ child){
19023 // Overrides _LayoutWidget._setupChild()
19024
19025 this.inherited(arguments);
19026
19027 dojo.replaceClass(child.domNode, "dijitHidden", "dijitVisible");
19028
19029 // remove the title attribute so it doesn't show up when i hover
19030 // over a node
19031 child.domNode.title = "";
19032 },
19033
19034 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
19035 // Overrides _Container.addChild() to do layout and publish events
19036
19037 this.inherited(arguments);
19038
19039 if(this._started){
19040 dojo.publish(this.id+"-addChild", [child, insertIndex]);
19041
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()
19047 this.layout();
19048
19049 // if this is the first child, then select it
19050 if(!this.selectedChildWidget){
19051 this.selectChild(child);
19052 }
19053 }
19054 },
19055
19056 removeChild: function(/*dijit._Widget*/ page){
19057 // Overrides _Container.removeChild() to do layout and publish events
19058
19059 this.inherited(arguments);
19060
19061 if(this._started){
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]);
19064 }
19065
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; }
19069
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;
19074 if(this._started){
19075 var children = this.getChildren();
19076 if(children.length){
19077 this.selectChild(children[0]);
19078 }
19079 }
19080 }
19081
19082 if(this._started){
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
19086 this.layout();
19087 }
19088 },
19089
19090 selectChild: function(/*dijit._Widget|String*/ page, /*Boolean*/ animate){
19091 // summary:
19092 // Show the given widget (which must be one of my children)
19093 // page:
19094 // Reference to child widget or id of child widget
19095
19096 page = dijit.byId(page);
19097
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]);
19103
19104 if(this.persist){
19105 dojo.cookie(this.id + "_selectedChild", this.selectedChildWidget.id);
19106 }
19107 }
19108
19109 return d; // If child has an href, promise that fires when the child's href finishes loading
19110 },
19111
19112 _transition: function(/*dijit._Widget*/ newWidget, /*dijit._Widget*/ oldWidget, /*Boolean*/ animate){
19113 // summary:
19114 // Hide the old widget and display the new widget.
19115 // Subclasses should override this.
19116 // tags:
19117 // protected extension
19118 if(oldWidget){
19119 this._hideChild(oldWidget);
19120 }
19121 var d = this._showChild(newWidget);
19122
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){
19127 if(this.doLayout){
19128 newWidget.resize(this._containerContentBox || this._contentBox);
19129 }else{
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();
19133 }
19134 }
19135
19136 return d; // If child has an href, promise that fires when the child's href finishes loading
19137 },
19138
19139 _adjacent: function(/*Boolean*/ forward){
19140 // summary:
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
19146 },
19147
19148 forward: function(){
19149 // summary:
19150 // Advance to next page.
19151 return this.selectChild(this._adjacent(true), true);
19152 },
19153
19154 back: function(){
19155 // summary:
19156 // Go back to previous page.
19157 return this.selectChild(this._adjacent(false), true);
19158 },
19159
19160 _onKeyPress: function(e){
19161 dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]);
19162 },
19163
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);
19168 }
19169 },
19170
19171 _showChild: function(/*dijit._Widget*/ page){
19172 // summary:
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.
19175 // returns:
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);
19181
19182 dojo.replaceClass(page.domNode, "dijitVisible", "dijitHidden");
19183
19184 return page._onShow() || true;
19185 },
19186
19187 _hideChild: function(/*dijit._Widget*/ page){
19188 // summary:
19189 // Hide the specified child by changing it's CSS, and call _onHide() so
19190 // it's notified.
19191 page._set("selected", false);
19192 dojo.replaceClass(page.domNode, "dijitHidden", "dijitVisible");
19193
19194 page.onHide();
19195 },
19196
19197 closeChild: function(/*dijit._Widget*/ page){
19198 // summary:
19199 // Callback when user clicks the [X] to remove a page.
19200 // If onClose() returns true then remove and destroy the child.
19201 // tags:
19202 // private
19203 var remove = page.onClose(this, page);
19204 if(remove){
19205 this.removeChild(page);
19206 // makes sure we can clean up executeScripts in ContentPane onUnLoad
19207 page.destroyRecursive();
19208 }
19209 },
19210
19211 destroyDescendants: function(/*Boolean*/ preserveDom){
19212 dojo.forEach(this.getChildren(), function(child){
19213 this.removeChild(child);
19214 child.destroyRecursive(preserveDom);
19215 }, this);
19216 }
19217 });
19218
19219 // For back-compat, remove for 2.0
19220
19221
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`
19230 selected: false,
19231
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.
19235 closable: false,
19236
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.
19240 iconClass: "",
19241
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
19246 showTitle: true
19247 });
19248
19249 }
19250
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");
19254
19255
19256
19257 dojo.declare("dijit.layout.AccordionPane", dijit.layout.ContentPane, {
19258 // summary:
19259 // Deprecated widget. Use `dijit.layout.ContentPane` instead.
19260 // tags:
19261 // deprecated
19262
19263 constructor: function(){
19264 dojo.deprecated("dijit.layout.AccordionPane deprecated, use ContentPane instead", "", "2.0");
19265 },
19266
19267 onSelected: function(){
19268 // summary:
19269 // called when this pane is selected
19270 }
19271 });
19272
19273 }
19274
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");
19278
19279
19280
19281
19282
19283
19284
19285
19286 //dojo.require("dijit.layout.AccordionPane "); // for back compat, remove for 2.0
19287
19288 // Design notes:
19289 //
19290 // An AccordionContainer is a StackContainer, but each child (typically ContentPane)
19291 // is wrapped in a _AccordionInnerContainer. This is hidden from the caller.
19292 //
19293 // The resulting markup will look like:
19294 //
19295 // <div class=dijitAccordionContainer>
19296 // <div class=dijitAccordionInnerContainer> (one pane)
19297 // <div class=dijitAccordionTitle> (title bar) ... </div>
19298 // <div class=dijtAccordionChildWrapper> (content pane) </div>
19299 // </div>
19300 // </div>
19301 //
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.
19305 //
19306 // During animation there are two dijtAccordionChildWrapper's shown, so we need
19307 // to compensate for that.
19308
19309 dojo.declare(
19310 "dijit.layout.AccordionContainer",
19311 dijit.layout.StackContainer,
19312 {
19313 // summary:
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.
19316 // example:
19317 // | <div dojoType="dijit.layout.AccordionContainer">
19318 // | <div dojoType="dijit.layout.ContentPane" title="pane 1">
19319 // | </div>
19320 // | <div dojoType="dijit.layout.ContentPane" title="pane 2">
19321 // | <p>This is some text</p>
19322 // | </div>
19323 // | </div>
19324
19325 // duration: Integer
19326 // Amount of time (in ms) it takes to slide panes
19327 duration: dijit.defaultDuration,
19328
19329 // buttonWidget: [const] String
19330 // The name of the widget used to display the title of each pane
19331 buttonWidget: "dijit.layout._AccordionButton",
19332
19333 /*=====
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)
19337 _verticalSpace: 0,
19338 =====*/
19339 baseClass: "dijitAccordionContainer",
19340
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
19345 },
19346
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);
19355 }
19356 },
19357
19358 layout: function(){
19359 // Implement _LayoutWidget.layout() virtual method.
19360 // Set the height of the open pane based on what room remains.
19361
19362 var openPane = this.selectedChildWidget;
19363
19364 if(!openPane){ return;}
19365
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;
19374
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;
19380 }
19381 });
19382 this._verticalSpace = mySize.h - totalCollapsedHeight - wrapperDomNodeMargin.h
19383 - wrapperDomNodePadBorder.h - wrapperContainerNodeMargin.h - wrapperContainerNodePadBorder.h
19384 - openPane._buttonWidget.getTitleHeight();
19385
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
19391 };
19392
19393 if(openPane){
19394 openPane.resize(this._containerContentBox);
19395 }
19396 },
19397
19398 _setupChild: function(child){
19399 // Overrides _LayoutWidget._setupChild().
19400 // Put wrapper widget around the child widget, showing title
19401
19402 child._wrapperWidget = new dijit.layout._AccordionInnerContainer({
19403 contentWidget: child,
19404 buttonWidget: this.buttonWidget,
19405 id: child.id + "_wrapper",
19406 dir: child.dir,
19407 lang: child.lang,
19408 parent: this
19409 });
19410
19411 this.inherited(arguments);
19412 },
19413
19414 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
19415 if(this._started){
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.
19419
19420 // First add in child as a direct child of this AccordionContainer
19421 dojo.place(child.domNode, this.containerNode, insertIndex);
19422
19423 if(!child._started){
19424 child.startup();
19425 }
19426
19427 // Then stick the wrapper widget around the child widget
19428 this._setupChild(child);
19429
19430 // Code below copied from StackContainer
19431 dojo.publish(this.id+"-addChild", [child, insertIndex]);
19432 this.layout();
19433 if(!this.selectedChildWidget){
19434 this.selectChild(child);
19435 }
19436 }else{
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);
19440 }
19441 },
19442
19443 removeChild: function(child){
19444 // Overrides _LayoutWidget.removeChild().
19445
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;
19453 }
19454
19455 dojo.removeClass(child.domNode, "dijitHidden");
19456
19457 this.inherited(arguments);
19458 },
19459
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;
19464 }, this);
19465 },
19466
19467 destroy: function(){
19468 if(this._animation){
19469 this._animation.stop();
19470 }
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();
19476 }else{
19477 child.destroyRecursive();
19478 }
19479 });
19480 this.inherited(arguments);
19481 },
19482
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);
19487 },
19488
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);
19493 },
19494
19495 _transition: function(/*dijit._Widget?*/ newWidget, /*dijit._Widget?*/ oldWidget, /*Boolean*/ animate){
19496 // Overrides StackContainer._transition() to provide sliding of title bars etc.
19497
19498 if(dojo.isIE < 8){
19499 // workaround animation bugs by not animating; not worth supporting animation for IE6 & 7
19500 animate = false;
19501 }
19502
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;
19507 }
19508
19509 var self = this;
19510
19511 if(newWidget){
19512 newWidget._wrapperWidget.set("selected", true);
19513
19514 var d = this._showChild(newWidget); // prepare widget to be slid in
19515
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);
19521 }
19522 }
19523
19524 if(oldWidget){
19525 oldWidget._wrapperWidget.set("selected", false);
19526 if(!animate){
19527 this._hideChild(oldWidget);
19528 }
19529 }
19530
19531 if(animate){
19532 var newContents = newWidget._wrapperWidget.containerNode,
19533 oldContents = oldWidget._wrapperWidget.containerNode;
19534
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;
19542
19543 oldContents.style.height = (self._verticalSpace - animationHeightOverhead) + "px";
19544
19545 this._animation = new dojo.Animation({
19546 node: newContents,
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";
19553 },
19554 onEnd: function(){
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);
19560 }
19561 });
19562 this._animation.onStop = this._animation.onEnd;
19563 this._animation.play();
19564 }
19565
19566 return d; // If child has an href, promise that fires when the widget has finished loading
19567 },
19568
19569 // note: we are treating the container as controller here
19570 _onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){
19571 // summary:
19572 // Handle keypress events
19573 // description:
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)){
19578 return;
19579 }
19580 var k = dojo.keys,
19581 c = e.charOrCode;
19582 if((fromTitle && (c == k.LEFT_ARROW || c == k.UP_ARROW)) ||
19583 (e.ctrlKey && c == k.PAGE_UP)){
19584 this._adjacent(false)._buttonWidget._onTitleClick();
19585 dojo.stopEvent(e);
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();
19589 dojo.stopEvent(e);
19590 }
19591 }
19592 }
19593 );
19594
19595 dojo.declare("dijit.layout._AccordionInnerContainer",
19596 [dijit._Widget, dijit._CssStateMixin], {
19597 // summary:
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
19600 // this widget.
19601
19602 /*=====
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,
19608 =====*/
19609
19610 /*=====
19611 // contentWidget: dijit._Widget
19612 // Pointer to the real child widget
19613 contentWidget: null,
19614 =====*/
19615
19616 baseClass: "dijitAccordionInnerContainer",
19617
19618 // tell nested layout widget that we will take care of sizing
19619 isContainer: true,
19620 isLayoutContainer: true,
19621
19622 buildRendering: function(){
19623 // Builds a template like:
19624 // <div class=dijitAccordionInnerContainer>
19625 // Button
19626 // <div class=dijitAccordionChildWrapper>
19627 // ContentPane
19628 // </div>
19629 // </div>
19630
19631 // Create wrapper div, placed where the child is now
19632 this.domNode = dojo.place("<div class='" + this.baseClass + "'>", this.contentWidget.domNode, "after");
19633
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,
19641 dir: child.dir,
19642 lang: child.lang,
19643 iconClass: child.iconClass,
19644 id: child.id + "_button",
19645 parent: this.parent
19646 })).placeAt(this.domNode);
19647
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);
19652 },
19653
19654 postCreate: function(){
19655 this.inherited(arguments);
19656
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);
19662 })),
19663 this.contentWidget.watch('tooltip', dojo.hitch(this, function(name, oldValue, newValue){
19664 button.set("title", newValue);
19665 })),
19666 this.contentWidget.watch('iconClass', dojo.hitch(this, function(name, oldValue, newValue){
19667 button.set("iconClass", newValue);
19668 }))
19669 ];
19670 },
19671
19672 _setSelectedAttr: function(/*Boolean*/ isSelected){
19673 this._set("selected", isSelected);
19674 this.button.set("selected", isSelected);
19675 if(isSelected){
19676 var cw = this.contentWidget;
19677 if(cw.onSelected){ cw.onSelected(); }
19678 }
19679 },
19680
19681 startup: function(){
19682 // Called by _Container.addChild()
19683 this.contentWidget.startup();
19684 },
19685
19686 destroy: function(){
19687 this.button.destroyRecursive();
19688
19689 dojo.forEach(this._contentWidgetWatches || [], function(w){ w.unwatch(); });
19690
19691 delete this.contentWidget._buttonWidget;
19692 delete this.contentWidget._wrapperWidget;
19693
19694 this.inherited(arguments);
19695 },
19696
19697 destroyDescendants: function(){
19698 // since getChildren isn't working for me, have to code this manually
19699 this.contentWidget.destroyRecursive();
19700 }
19701 });
19702
19703 dojo.declare("dijit.layout._AccordionButton",
19704 [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
19705 {
19706 // summary:
19707 // The title bar to click to open up an accordion pane.
19708 // Internal widget used by AccordionContainer.
19709 // tags:
19710 // private
19711
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" }
19717 }),
19718
19719 baseClass: "dijitAccordionTitle",
19720
19721 getParent: function(){
19722 // summary:
19723 // Returns the AccordionContainer parent.
19724 // tags:
19725 // private
19726 return this.parent;
19727 },
19728
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);
19735 },
19736
19737 getTitleHeight: function(){
19738 // summary:
19739 // Returns the height of the title dom node.
19740 return dojo._getMarginSize(this.domNode).h; // Integer
19741 },
19742
19743 // TODO: maybe the parent should set these methods directly rather than forcing the code
19744 // into the button widget?
19745 _onTitleClick: function(){
19746 // summary:
19747 // Callback when someone clicks my title.
19748 var parent = this.getParent();
19749 parent.selectChild(this.contentWidget, true);
19750 dijit.focus(this.focusNode);
19751 },
19752
19753 _onTitleKeyPress: function(/*Event*/ evt){
19754 return this.getParent()._onKeyPress(evt, this.contentWidget);
19755 },
19756
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");
19762 }
19763 });
19764
19765 }
19766
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");
19770
19771
19772
19773
19774
19775 dojo.declare(
19776 "dijit.layout.BorderContainer",
19777 dijit.layout._LayoutWidget,
19778 {
19779 // summary:
19780 // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides.
19781 //
19782 // description:
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.
19789 //
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.
19794 //
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.
19799 // example:
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>
19805 // | </div>
19806
19807 // design: String
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",
19813
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.
19818 gutters: true,
19819
19820 // liveSplitters: [const] Boolean
19821 // Specifies whether splitters resize as you drag (true) or only upon mouseup (false)
19822 liveSplitters: true,
19823
19824 // persist: Boolean
19825 // Save splitter positions in a cookie.
19826 persist: false,
19827
19828 baseClass: "dijitBorderContainer",
19829
19830 // _splitterClass: String
19831 // Optional hook to override the default Splitter widget used by BorderContainer
19832 _splitterClass: "dijit.layout._Splitter",
19833
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.
19837 if(!this.gutters){
19838 this.baseClass += "NoGutter";
19839 }
19840 this.inherited(arguments);
19841 },
19842
19843 startup: function(){
19844 if(this._started){ return; }
19845 dojo.forEach(this.getChildren(), this._setupChild, this);
19846 this.inherited(arguments);
19847 },
19848
19849 _setupChild: function(/*dijit._Widget*/ child){
19850 // Override _LayoutWidget._setupChild().
19851
19852 var region = child.region;
19853 if(region){
19854 this.inherited(arguments);
19855
19856 dojo.addClass(child.domNode, this.baseClass+"Pane");
19857
19858 var ltr = this.isLeftToRight();
19859 if(region == "leading"){ region = ltr ? "left" : "right"; }
19860 if(region == "trailing"){ region = ltr ? "right" : "left"; }
19861
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",
19869 container: this,
19870 child: child,
19871 region: region,
19872 live: this.liveSplitters
19873 });
19874 splitter.isSplitter = true;
19875 child._splitterWidget = splitter;
19876
19877 dojo.place(splitter.domNode, child.domNode, "after");
19878
19879 // Splitters aren't added as Contained children, so we need to call startup explicitly
19880 splitter.startup();
19881 }
19882 child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc.
19883 }
19884 },
19885
19886 layout: function(){
19887 // Implement _LayoutWidget.layout() virtual method.
19888 this._layoutChildren();
19889 },
19890
19891 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
19892 // Override _LayoutWidget.addChild().
19893 this.inherited(arguments);
19894 if(this._started){
19895 this.layout(); //OPT
19896 }
19897 },
19898
19899 removeChild: function(/*dijit._Widget*/ child){
19900 // Override _LayoutWidget.removeChild().
19901
19902 var region = child.region;
19903 var splitter = child._splitterWidget
19904 if(splitter){
19905 splitter.destroy();
19906 delete child._splitterWidget;
19907 }
19908 this.inherited(arguments);
19909
19910 if(this._started){
19911 this._layoutChildren();
19912 }
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, {
19917 top: "auto",
19918 bottom: "auto",
19919 left: "auto",
19920 right: "auto",
19921 position: "static"
19922 });
19923 dojo.style(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto");
19924 },
19925
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;
19930 });
19931 },
19932
19933 // TODO: remove in 2.0
19934 getSplitter: function(/*String*/region){
19935 // summary:
19936 // Returns the widget responsible for rendering the splitter associated with region
19937 // tags:
19938 // deprecated
19939 return dojo.filter(this.getChildren(), function(child){
19940 return child.region == region;
19941 })[0]._splitterWidget;
19942 },
19943
19944 resize: function(newSize, currentSize){
19945 // Overrides _LayoutWidget.resize().
19946
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);
19955
19956 dojo.style(node, "padding", "0px");
19957 }
19958
19959 this.inherited(arguments);
19960 },
19961
19962 _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){
19963 // summary:
19964 // This is the main routine for setting size/position of each child.
19965 // description:
19966 // With no arguments, measures the height of top/bottom panes, the width
19967 // of left/right panes, and then sizes all panes accordingly.
19968 //
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.
19972 // changedChildId:
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
19976
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.
19980 return;
19981 }
19982
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){
19986 return {
19987 pane: child,
19988 weight: [
19989 child.region == "center" ? Infinity : 0,
19990 child.layoutPriority,
19991 (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1),
19992 idx
19993 ]
19994 };
19995 }, this);
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];
20001 }
20002 }
20003 return 0;
20004 });
20005
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);
20013 }
20014 });
20015
20016 // Compute the box in which to lay out my children
20017 var dim = {
20018 l: this.pe.l,
20019 t: this.pe.t,
20020 w: this._borderBox.w - this.pe.w,
20021 h: this._borderBox.h - this.pe.h
20022 };
20023
20024 // Layout the children, possibly changing size due to a splitter drag
20025 dijit.layout.layoutChildren(this.domNode, dim, childrenAndSplitters,
20026 changedChildId, changedChildSize);
20027 },
20028
20029 destroyRecursive: function(){
20030 // Destroy splitters first, while getChildren() still works
20031 dojo.forEach(this.getChildren(), function(child){
20032 var splitter = child._splitterWidget;
20033 if(splitter){
20034 splitter.destroy();
20035 }
20036 delete child._splitterWidget;
20037 });
20038
20039 // Then destroy the real children, and myself
20040 this.inherited(arguments);
20041 }
20042 });
20043
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.
20052 region: '',
20053
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.
20058 layoutPriority: 0,
20059
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.
20064 splitter: false,
20065
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.
20069 minSize: 0,
20070
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.
20074 maxSize: Infinity
20075 });
20076
20077 dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
20078 {
20079 // summary:
20080 // A draggable spacer between two items in a `dijit.layout.BorderContainer`.
20081 // description:
20082 // This is instantiated by `dijit.layout.BorderContainer`. Users should not
20083 // create it directly.
20084 // tags:
20085 // private
20086
20087 /*=====
20088 // container: [const] dijit.layout.BorderContainer
20089 // Pointer to the parent BorderContainer
20090 container: null,
20091
20092 // child: [const] dijit.layout._LayoutWidget
20093 // Pointer to the pane associated with this splitter
20094 child: null,
20095
20096 // region: [const] String
20097 // Region of pane associated with this splitter.
20098 // "top", "bottom", "left", "right".
20099 region: null,
20100 =====*/
20101
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)
20105 live: true,
20106
20107 templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>',
20108
20109 postMixInProperties: function(){
20110 this.inherited(arguments);
20111
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;
20115 },
20116
20117 buildRendering: function(){
20118 this.inherited(arguments);
20119
20120 dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V"));
20121
20122 if(this.container.persist){
20123 // restore old size
20124 var persistSize = dojo.cookie(this._cookieName);
20125 if(persistSize){
20126 this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize;
20127 }
20128 }
20129 },
20130
20131 _computeMaxSize: function(){
20132 // summary:
20133 // Return the maximum size that my corresponding pane can be set to
20134
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
20139
20140 return Math.min(this.child.maxSize, childSize + spaceAvailable);
20141 },
20142
20143 _startDrag: function(e){
20144 if(!this.cover){
20145 this.cover = dojo.doc.createElement('div');
20146 dojo.addClass(this.cover, "dijitSplitterCover");
20147 dojo.place(this.cover, this.child.domNode, "after");
20148 }
20149 dojo.addClass(this.cover, "dijitSplitterCoverActive");
20150
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");
20158 }
20159 dojo.addClass(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
20160 if(this.fake){
20161 dojo.removeClass(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover");
20162 }
20163
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),
20179 de = dojo.doc;
20180
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);
20186
20187 if(resize || forceResize){
20188 layoutFunc(boundChildSize);
20189 }
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";
20192 }),
20193 dojo.connect(de, "ondragstart", dojo.stopEvent),
20194 dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent),
20195 dojo.connect(de, "onmouseup", this, "_stopDrag")
20196 ]);
20197 dojo.stopEvent(e);
20198 },
20199
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);
20204 },
20205
20206 _stopDrag: function(e){
20207 try{
20208 if(this.cover){
20209 dojo.removeClass(this.cover, "dijitSplitterCoverActive");
20210 }
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);
20216 }finally{
20217 this._cleanupHandlers();
20218 delete this._drag;
20219 }
20220
20221 if(this.container.persist){
20222 dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365});
20223 }
20224 },
20225
20226 _cleanupHandlers: function(){
20227 dojo.forEach(this._handlers, dojo.disconnect);
20228 delete this._handlers;
20229 },
20230
20231 _onKeyPress: function(/*Event*/ e){
20232 // should we apply typematic to this?
20233 this._resize = true;
20234 var horizontal = this.horizontal;
20235 var tick = 1;
20236 var dk = dojo.keys;
20237 switch(e.charOrCode){
20238 case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW:
20239 tick *= -1;
20240 // break;
20241 case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW:
20242 break;
20243 default:
20244 // this.inherited(arguments);
20245 return;
20246 }
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));
20249 dojo.stopEvent(e);
20250 },
20251
20252 destroy: function(){
20253 this._cleanupHandlers();
20254 delete this.child;
20255 delete this.container;
20256 delete this.cover;
20257 delete this.fake;
20258 this.inherited(arguments);
20259 }
20260 });
20261
20262 dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated],
20263 {
20264 // summary:
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.
20267 // description:
20268 // Instantiated by `dijit.layout.BorderContainer`. Users should not
20269 // create directly.
20270 // tags:
20271 // private
20272
20273 templateString: '<div class="dijitGutter" role="presentation"></div>',
20274
20275 postMixInProperties: function(){
20276 this.inherited(arguments);
20277 this.horizontal = /top|bottom/.test(this.region);
20278 },
20279
20280 buildRendering: function(){
20281 this.inherited(arguments);
20282 dojo.addClass(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V"));
20283 }
20284 });
20285
20286 }
20287
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");
20291
20292
20293
20294
20295 dojo.declare("dijit.layout._TabContainerBase",
20296 [dijit.layout.StackContainer, dijit._Templated],
20297 {
20298 // summary:
20299 // Abstract base class for TabContainer. Must define _makeController() to instantiate
20300 // and return the widget that displays the tab labels
20301 // description:
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.
20305
20306 // tabPosition: String
20307 // Defines where tabs go relative to tab content.
20308 // "top", "bottom", "left-h", "right-h"
20309 tabPosition: "top",
20310
20311 baseClass: "dijitTabContainer",
20312
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.
20316 tabStrip: false,
20317
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.
20322 nested: false,
20323
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"),
20325
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(/-.*/, "");
20329
20330 this.srcNodeRef && dojo.style(this.srcNodeRef, "visibility", "hidden");
20331
20332 this.inherited(arguments);
20333 },
20334
20335 buildRendering: function(){
20336 this.inherited(arguments);
20337
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);
20340
20341 if(!this.doLayout){ dojo.addClass(this.domNode, "dijitTabContainerNoLayout"); }
20342
20343 if(this.nested){
20344 /* workaround IE's lack of support for "a > b" selectors by
20345 * tagging each node in the template.
20346 */
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");
20351 }else{
20352 dojo.addClass(this.domNode, "tabStrip-" + (this.tabStrip ? "enabled" : "disabled"));
20353 }
20354 },
20355
20356 _setupChild: function(/*dijit._Widget*/ tab){
20357 // Overrides StackContainer._setupChild().
20358 dojo.addClass(tab.domNode, "dijitTabPane");
20359 this.inherited(arguments);
20360 },
20361
20362 startup: function(){
20363 if(this._started){ return; }
20364
20365 // wire up the tablist and its tabs
20366 this.tablist.startup();
20367
20368 this.inherited(arguments);
20369 },
20370
20371 layout: function(){
20372 // Overrides StackContainer.layout().
20373 // Configure the content pane to take up all the space except for where the tabs are
20374
20375 if(!this._contentBox || typeof(this._contentBox.l) == "undefined"){return;}
20376
20377 var sc = this.selectedChildWidget;
20378
20379 if(this.doLayout){
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
20386 }, {
20387 domNode: this.containerNode,
20388 layoutAlign: "client"
20389 }];
20390 dijit.layout.layoutChildren(this.domNode, this._contentBox, children);
20391
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]);
20395
20396 if(sc && sc.resize){
20397 sc.resize(this._containerContentBox);
20398 }
20399 }else{
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;
20404 s.width="0";
20405 var width = dojo.contentBox(this.domNode).w;
20406 s.width="";
20407 this.tablist.resize({w: width});
20408 }
20409
20410 // and call resize() on the selected pane just to tell it that it's been made visible
20411 if(sc && sc.resize){
20412 sc.resize();
20413 }
20414 }
20415 },
20416
20417 destroy: function(){
20418 if(this.tablist){
20419 this.tablist.destroy();
20420 }
20421 this.inherited(arguments);
20422 }
20423 });
20424
20425 }
20426
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");
20430
20431
20432
20433
20434
20435
20436 // Menu is used for an accessible close button, would be nice to have a lighter-weight solution
20437
20438
20439 dojo.declare("dijit.layout.TabController",
20440 dijit.layout.StackController,
20441 {
20442 // summary:
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`.
20445 // description:
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.
20449 // tags:
20450 // private
20451
20452 templateString: "<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>",
20453
20454 // tabPosition: String
20455 // Defines where tabs go relative to the content.
20456 // "top", "bottom", "left-h", "right-h"
20457 tabPosition: "top",
20458
20459 // buttonWidget: String
20460 // The name of the tab widget to create to correspond to each page
20461 buttonWidget: "dijit.layout._TabButton",
20462
20463 _rectifyRtlTabList: function(){
20464 // summary:
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
20466
20467 if(0 >= this.tabPosition.indexOf('-h')){ return; }
20468 if(!this.pane2button){ return; }
20469
20470 var maxWidth = 0;
20471 for(var pane in this.pane2button){
20472 var ow = this.pane2button[pane].innerDiv.scrollWidth;
20473 maxWidth = Math.max(maxWidth, ow);
20474 }
20475 //unify the length of all the tabs
20476 for(pane in this.pane2button){
20477 this.pane2button[pane].innerDiv.style.width = maxWidth + 'px';
20478 }
20479 }
20480 });
20481
20482 dojo.declare("dijit.layout._TabButton",
20483 dijit.layout._StackButton,
20484 {
20485 // summary:
20486 // A tab (the thing you click to select a pane).
20487 // description:
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.
20490 // tags:
20491 // private
20492
20493 // baseClass: String
20494 // The CSS class applied to the domNode.
20495 baseClass: "dijitTab",
20496
20497 // Apply dijitTabCloseButtonHover when close button is hovered
20498 cssStateNodes: {
20499 closeNode: "dijitTabCloseButton"
20500 },
20501
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"),
20503
20504 // Override _FormWidget.scrollOnFocus.
20505 // Don't scroll the whole tab container into view when the button is focused.
20506 scrollOnFocus: false,
20507
20508 buildRendering: function(){
20509 this.inherited(arguments);
20510
20511 dojo.setSelectable(this.containerNode, false);
20512 },
20513
20514 startup: function(){
20515 this.inherited(arguments);
20516 var n = this.domNode;
20517
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;
20522 }, 1);
20523 },
20524
20525 _setCloseButtonAttr: function(/*Boolean*/ disp){
20526 // summary:
20527 // Hide/show close button
20528 this._set("closeButton", disp);
20529 dojo.toggleClass(this.innerDiv, "dijitClosable", disp);
20530 this.closeNode.style.display = disp ? "" : "none";
20531 if(disp){
20532 var _nlsResources = dojo.i18n.getLocalization("dijit", "common");
20533 if(this.closeNode){
20534 dojo.attr(this.closeNode,"title", _nlsResources.itemClose);
20535 }
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",
20540 dir: this.dir,
20541 lang: this.lang,
20542 targetNodeIds: [this.domNode]
20543 });
20544
20545 this._closeMenu.addChild(new dijit.MenuItem({
20546 label: _nlsResources.itemClose,
20547 dir: this.dir,
20548 lang: this.lang,
20549 onClick: dojo.hitch(this, "onClickCloseButton")
20550 }));
20551 }else{
20552 if(this._closeMenu){
20553 this._closeMenu.destroyRecursive();
20554 delete this._closeMenu;
20555 }
20556 }
20557 },
20558 _setLabelAttr: function(/*String*/ content){
20559 // summary:
20560 // Hook for set('label', ...) to work.
20561 // description:
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 || '');
20568 }
20569 },
20570
20571 destroy: function(){
20572 if(this._closeMenu){
20573 this._closeMenu.destroyRecursive();
20574 delete this._closeMenu;
20575 }
20576 this.inherited(arguments);
20577 }
20578 });
20579
20580 }
20581
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");
20585
20586
20587
20588
20589
20590
20591 dojo.declare("dijit.layout.ScrollingTabController",
20592 dijit.layout.TabController,
20593 {
20594 // summary:
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
20598 // or right).
20599 // tags:
20600 // private
20601
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\">&#9660;</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\" iconClass=\"dijitTabStripSlideLeftIcon\"\n\t\t\tdojoAttachPoint=\"_leftBtn\" dojoAttachEvent=\"onClick: doSlideLeft\" showLabel=\"false\">&#9664;</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\" iconClass=\"dijitTabStripSlideRightIcon\"\n\t\t\tdojoAttachPoint=\"_rightBtn\" dojoAttachEvent=\"onClick: doSlideRight\" showLabel=\"false\">&#9654;</div>\n\t<div class='dijitTabListWrapper' dojoAttachPoint='tablistWrapper'>\n\t\t<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'\n\t\t\t\tdojoAttachPoint='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>\n"),
20603
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.
20607 useMenu: true,
20608
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.
20612 useSlider: true,
20613
20614 // tabStripClass: [const] String
20615 // The css class to apply to the tab strip, if it is visible.
20616 tabStripClass: "",
20617
20618 widgetsInTemplate: true,
20619
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.
20624 _minScroll: 5,
20625
20626 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
20627 "class": "containerNode"
20628 }),
20629
20630 buildRendering: function(){
20631 this.inherited(arguments);
20632 var n = this.domNode;
20633
20634 this.scrollNode = this.tablistWrapper;
20635 this._initButtons();
20636
20637 if(!this.tabStripClass){
20638 this.tabStripClass = "dijitTabContainer" +
20639 this.tabPosition.charAt(0).toUpperCase() +
20640 this.tabPosition.substr(1).replace(/-.*/, "") +
20641 "None";
20642 dojo.addClass(n, "tabStrip-disabled")
20643 }
20644
20645 dojo.addClass(this.tablistWrapper, this.tabStripClass);
20646 },
20647
20648 onStartup: function(){
20649 this.inherited(arguments);
20650
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;
20656 },
20657
20658 onAddChild: function(page, insertIndex){
20659 this.inherited(arguments);
20660
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);
20668 }
20669 }))
20670 );
20671 }, this);
20672
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");
20679 },
20680
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;
20686 }
20687
20688 this.inherited(arguments);
20689 },
20690
20691 _initButtons: function(){
20692 // summary:
20693 // Creates the buttons used to scroll to view tabs that
20694 // may not be visible if the TabContainer is too narrow.
20695
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;
20704 return true;
20705 }else{
20706 dojo.style(btn, "display", "none");
20707 return false;
20708 }
20709 }, this);
20710 },
20711
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;
20718 }else{
20719 return 0;
20720 }
20721 },
20722
20723 _enableBtn: function(width){
20724 // summary:
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;
20730 },
20731
20732 resize: function(dim){
20733 // summary:
20734 // Hides or displays the buttons used to scroll the tab list and launch the menu
20735 // that selects tabs.
20736
20737 if(this.domNode.offsetWidth == 0){
20738 return;
20739 }
20740
20741 // Save the dimensions to be used when a child is renamed.
20742 this._dim = dim;
20743
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);
20752
20753 // Show/hide the left/right/menu navigation buttons depending on whether or not they
20754 // are needed.
20755 var enable = this._enableBtn(this._contentBox.w);
20756 this._buttons.style("display", enable ? "" : "none");
20757
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"}]);
20764
20765 // set proper scroll so that selected tab is visible
20766 if(this._selectedTab){
20767 if(this._anim && this._anim.status() == "playing"){
20768 this._anim.stop();
20769 }
20770 var w = this.scrollNode,
20771 sl = this._convertToScrollLeft(this._getScrollForSelectedTab());
20772 w.scrollLeft = sl;
20773 }
20774
20775 // Enable/disabled left right buttons depending on whether or not user can scroll to left or right
20776 this._setButtonClass(this._getScroll());
20777
20778 this._postResize = true;
20779
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};
20783 },
20784
20785 _getScroll: function(){
20786 // summary:
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;
20793 return sl;
20794 },
20795
20796 _convertToScrollLeft: function(val){
20797 // summary:
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.
20802 //
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){
20805 return val;
20806 }else{
20807 var maxScroll = dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width");
20808 return (dojo.isIE == 8 ? -1 : 1) * (val - maxScroll);
20809 }
20810 },
20811
20812 onSelectChild: function(/*dijit._Widget*/ page){
20813 // summary:
20814 // Smoothly scrolls to a tab when it is selected.
20815
20816 var tab = this.pane2button[page.id];
20817 if(!tab || !page){return;}
20818
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;
20823
20824 var sl = this._getScroll();
20825
20826 if(sl > node.offsetLeft ||
20827 sl + dojo.style(this.scrollNode, "width") <
20828 node.offsetLeft + dojo.style(node, "width")){
20829 this.createSmoothScroll().play();
20830 }
20831 }
20832
20833 this.inherited(arguments);
20834 },
20835
20836 _getScrollBounds: function(){
20837 // summary:
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();
20845
20846 if(children.length && tabsWidth > scrollNodeWidth){
20847 // Scrolling should happen
20848 return {
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 :
20852 maxPossibleScroll
20853 };
20854 }else{
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;
20857 return {
20858 min: onlyScrollPosition,
20859 max: onlyScrollPosition
20860 };
20861 }
20862 },
20863
20864 _getScrollForSelectedTab: function(){
20865 // summary:
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();
20872
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);
20877
20878 // TODO:
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...)
20882 return pos;
20883 },
20884
20885 createSmoothScroll: function(x){
20886 // summary:
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.
20889 // description:
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.
20893 // x: Integer?
20894 // An optional pixel value to scroll to, indicating distance from left.
20895
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);
20901 }else{
20902 // scroll to center the current tab
20903 x = this._getScrollForSelectedTab();
20904 }
20905
20906 if(this._anim && this._anim.status() == "playing"){
20907 this._anim.stop();
20908 }
20909
20910 var self = this,
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);
20918 },
20919 onAnimate: function(val){
20920 w.scrollLeft = val;
20921 }
20922 });
20923 this._anim = anim;
20924
20925 // Disable/enable left/right buttons according to new scroll position
20926 this._setButtonClass(x);
20927
20928 return anim; // dojo._Animation
20929 },
20930
20931 _getBtnNode: function(/*Event*/ e){
20932 // summary:
20933 // Gets a button DOM node from a mouse click event.
20934 // e:
20935 // The mouse click event.
20936 var n = e.target;
20937 while(n && !dojo.hasClass(n, "tabStripButton")){
20938 n = n.parentNode;
20939 }
20940 return n;
20941 },
20942
20943 doSlideRight: function(/*Event*/ e){
20944 // summary:
20945 // Scrolls the menu to the right.
20946 // e:
20947 // The mouse click event.
20948 this.doSlide(1, this._getBtnNode(e));
20949 },
20950
20951 doSlideLeft: function(/*Event*/ e){
20952 // summary:
20953 // Scrolls the menu to the left.
20954 // e:
20955 // The mouse click event.
20956 this.doSlide(-1,this._getBtnNode(e));
20957 },
20958
20959 doSlide: function(/*Number*/ direction, /*DomNode*/ node){
20960 // summary:
20961 // Scrolls the tab list to the left or right by 75% of the widget width.
20962 // direction:
20963 // If the direction is 1, the widget scrolls to the right, if it is
20964 // -1, it scrolls to the left.
20965
20966 if(node && dojo.hasClass(node, "dijitTabDisabled")){return;}
20967
20968 var sWidth = dojo.style(this.scrollNode, "width");
20969 var d = (sWidth * 0.75) * direction;
20970
20971 var to = this._getScroll() + d;
20972
20973 this._setButtonClass(to);
20974
20975 this.createSmoothScroll(to).play();
20976 },
20977
20978 _setButtonClass: function(/*Number*/ scroll){
20979 // summary:
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.
20982 // scroll: Integer
20983 // amount of horizontal scroll
20984
20985 var scrollBounds = this._getScrollBounds();
20986 this._leftBtn.set("disabled", scroll <= scrollBounds.min);
20987 this._rightBtn.set("disabled", scroll >= scrollBounds.max);
20988 }
20989 });
20990
20991
20992 dojo.declare("dijit.layout._ScrollingTabControllerButtonMixin", null, {
20993 baseClass: "dijitTab tabStripButton",
20994
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"),
20996
20997 // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be
20998 // able to tab to the left/right/menu buttons
20999 tabIndex: "",
21000
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; }
21004 });
21005
21006 dojo.declare("dijit.layout._ScrollingTabControllerButton",
21007 [dijit.form.Button, dijit.layout._ScrollingTabControllerButtonMixin]);
21008
21009 dojo.declare(
21010 "dijit.layout._ScrollingTabControllerMenuButton",
21011 [dijit.form.Button, dijit._HasDropDown, dijit.layout._ScrollingTabControllerButtonMixin],
21012 {
21013 // id of the TabContainer itself
21014 containerId: "",
21015
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.
21018 tabIndex: "-1",
21019
21020 isLoaded: function(){
21021 // recreate menu every time, in case the TabContainer's list of children (or their icons/labels) have changed
21022 return false;
21023 },
21024
21025 loadDropDown: function(callback){
21026 this.dropDown = new dijit.Menu({
21027 id: this.containerId + "_menu",
21028 dir: this.dir,
21029 lang: this.lang
21030 });
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",
21035 label: page.title,
21036 iconClass: page.iconClass,
21037 dir: page.dir,
21038 lang: page.lang,
21039 onClick: function(){
21040 container.selectChild(page);
21041 }
21042 });
21043 this.dropDown.addChild(menuItem);
21044 }, this);
21045 callback();
21046 },
21047
21048 closeDropDown: function(/*Boolean*/ focus){
21049 this.inherited(arguments);
21050 if(this.dropDown){
21051 this.dropDown.destroyRecursive();
21052 delete this.dropDown;
21053 }
21054 }
21055 });
21056
21057 }
21058
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");
21062
21063
21064
21065
21066
21067 dojo.declare("dijit.layout.TabContainer",
21068 dijit.layout._TabContainerBase,
21069 {
21070 // summary:
21071 // A Container with tabs to select each child (only one of which is displayed at a time).
21072 // description:
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.
21076
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.
21080 useMenu: true,
21081
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.
21085 useSlider: true,
21086
21087 // controllerWidget: String
21088 // An optional parameter to override the widget used to display the tab labels
21089 controllerWidget: "",
21090
21091 _makeController: function(/*DomNode*/ srcNode){
21092 // summary:
21093 // Instantiate tablist controller widget and return reference to it.
21094 // Callback from _TabContainerBase.postCreate().
21095 // tags:
21096 // protected extension
21097
21098 var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"),
21099 TabController = dojo.getObject(this.controllerWidget);
21100
21101 return new TabController({
21102 id: this.id + "_tablist",
21103 dir: this.dir,
21104 lang: this.lang,
21105 tabPosition: this.tabPosition,
21106 doLayout: this.doLayout,
21107 containerId: this.id,
21108 "class": cls,
21109 nested: this.nested,
21110 useMenu: this.useMenu,
21111 useSlider: this.useSlider,
21112 tabStripClass: this.tabStrip ? this.baseClass + (this.tabStrip ? "":"No") + "Strip": null
21113 }, srcNode);
21114 },
21115
21116 postMixInProperties: function(){
21117 this.inherited(arguments);
21118
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";
21123 }
21124 }
21125 });
21126
21127 }
21128
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");
21132
21133
21134
21135
21136
21137 dojo.getObject("number", true, dojo);
21138
21139 /*=====
21140 dojo.number = {
21141 // summary: localized formatting and parsing routines for Number
21142 }
21143
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.
21149 // type: String?
21150 // choose a format type based on the locale from the following:
21151 // decimal, scientific (not yet supported), percent, currency. decimal by default.
21152 // places: Number?
21153 // fixed number of decimal places to show. This overrides any
21154 // information in the provided pattern.
21155 // round: Number?
21156 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
21157 // means do not round.
21158 // locale: String?
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;
21163 this.type = type;
21164 this.places = places;
21165 this.round = round;
21166 this.locale = locale;
21167 this.fractional = fractional;
21168 }
21169 =====*/
21170
21171 dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){
21172 // summary:
21173 // Format a Number as a String, using locale-specific settings
21174 // description:
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
21178 // delimiters.
21179 // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null.
21180 // value:
21181 // the number to be formatted
21182
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
21190 };
21191
21192 //dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
21193 dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough
21194
21195 dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){
21196 // summary:
21197 // Apply pattern to format value as a string using options. Gives no
21198 // consideration to local customs.
21199 // value:
21200 // the number to be formatted.
21201 // pattern:
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.
21208
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);
21216
21217 //TODO: only test against unescaped
21218 if(pattern.indexOf('%') != -1){
21219 value *= 100;
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 || "";
21228 });
21229 }else if(pattern.indexOf('E') != -1){
21230 throw new Error("exponential notation not supported");
21231 }
21232
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);
21238 }
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}));
21242 };
21243
21244 dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){
21245 // summary:
21246 // Rounds to the nearest value with the given number of decimal places, away from zero
21247 // description:
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.
21252 // value:
21253 // The number to round
21254 // places:
21255 // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding.
21256 // Must be non-negative.
21257 // increment:
21258 // Rounds next place to nearest value of increment/10. 10 by default.
21259 // example:
21260 // >>> dojo.number.round(-0.5)
21261 // -1
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)
21265 // 10.75
21266 var factor = 10 / (increment || 10);
21267 return (factor * +value).toFixed(places) / factor; // Number
21268 };
21269
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
21273 (function(){
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){
21278 d = 0;
21279 }
21280 return round(v, p, m) + (v > 0 ? d : -d);
21281 };
21282 })();
21283 }
21284
21285 /*=====
21286 dojo.number.__FormatAbsoluteOptions = function(){
21287 // decimal: String?
21288 // the decimal separator
21289 // group: String?
21290 // the group separator
21291 // places: Number?|String?
21292 // number of decimal places. the range "n,m" will format to m places.
21293 // round: Number?
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;
21300 }
21301 =====*/
21302
21303 dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){
21304 // summary:
21305 // Apply numeric pattern to absolute value using options. Gives no
21306 // consideration to local customs.
21307 // value:
21308 // the number to be formatted, ignores sign
21309 // pattern:
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
21314
21315 var patternParts = pattern.split("."),
21316 comma = typeof options.places == "string" && options.places.indexOf(","),
21317 maxPlaces = options.places;
21318 if(comma){
21319 maxPlaces = options.places.substring(comma + 1);
21320 }else if(!(maxPlaces >= 0)){
21321 maxPlaces = (patternParts[1] || []).length;
21322 }
21323 if(!(options.round < 0)){
21324 value = dojo.number.round(value, maxPlaces, options.round);
21325 }
21326
21327 var valueParts = String(Math.abs(value)).split("."),
21328 fractional = valueParts[1] || "";
21329 if(patternParts[1] || options.places){
21330 if(comma){
21331 options.places = options.places.substring(0, comma);
21332 }
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);
21337 }
21338
21339 // Truncate fractional
21340 if(maxPlaces < fractional.length){
21341 valueParts[1] = fractional.substr(0, maxPlaces);
21342 }
21343 }else{
21344 if(valueParts[1]){ valueParts.pop(); }
21345 }
21346
21347 // Pad whole with leading zeros
21348 var patternDigits = patternParts[0].replace(',', '');
21349 pad = patternDigits.indexOf("0");
21350 if(pad != -1){
21351 pad = patternDigits.length - pad;
21352 if(pad > valueParts[0].length){
21353 valueParts[0] = dojo.string.pad(valueParts[0], pad);
21354 }
21355
21356 // Truncate whole
21357 if(patternDigits.indexOf("#") == -1){
21358 valueParts[0] = valueParts[0].substr(valueParts[0].length - pad);
21359 }
21360 }
21361
21362 // Add group separators
21363 var index = patternParts[0].lastIndexOf(','),
21364 groupSize, groupSize2;
21365 if(index != -1){
21366 groupSize = patternParts[0].length - index - 1;
21367 var remainder = patternParts[0].substr(0, index);
21368 index = remainder.lastIndexOf(',');
21369 if(index != -1){
21370 groupSize2 = remainder.length - index - 1;
21371 }
21372 }
21373 var pieces = [];
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) : "";
21378 if(groupSize2){
21379 groupSize = groupSize2;
21380 delete groupSize2;
21381 }
21382 }
21383 valueParts[0] = pieces.reverse().join(options.group || ",");
21384
21385 return valueParts.join(options.decimal || ".");
21386 };
21387
21388 /*=====
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
21393 // localization.
21394 // type: String?
21395 // choose a format type based on the locale from the following:
21396 // decimal, scientific (not yet supported), percent, currency. decimal by default.
21397 // locale: String?
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;
21406 this.type = type;
21407 this.locale = locale;
21408 this.strict = strict;
21409 this.places = places;
21410 }
21411 =====*/
21412 dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){
21413 // summary:
21414 // Builds the regular needed to parse a number
21415 // description:
21416 // Returns regular expression with positive and negative match, group
21417 // and decimal separators
21418 return dojo.number._parseInfo(options).regexp; // String
21419 };
21420
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"],
21426 //TODO: memoize?
21427 group = bundle.group,
21428 decimal = bundle.decimal,
21429 factor = 1;
21430
21431 if(pattern.indexOf('%') != -1){
21432 factor /= 100;
21433 }else if(pattern.indexOf('\u2030') != -1){
21434 factor /= 1000; // per mille
21435 }else{
21436 var isCurrency = pattern.indexOf('\u00a4') != -1;
21437 if(isCurrency){
21438 group = bundle.currencyGroup || group;
21439 decimal = bundle.currencyDecimal || decimal;
21440 }
21441 }
21442
21443 //TODO: handle quoted escapes
21444 var patternList = pattern.split(';');
21445 if(patternList.length == 1){
21446 patternList.push("-" + patternList[0]);
21447 }
21448
21449 var re = dojo.regexp.buildGroupRE(patternList, function(pattern){
21450 pattern = "(?:"+dojo.regexp.escapeString(pattern, '.')+")";
21451 return pattern.replace(dojo.number._numberPatternRE, function(format){
21452 var flags = {
21453 signed: false,
21454 separator: options.strict ? group : [group,""],
21455 fractional: options.fractional,
21456 decimal: decimal,
21457 exponent: false
21458 },
21459
21460 parts = format.split('.'),
21461 places = options.places;
21462
21463 // special condition for percent (factor != 1)
21464 // allow decimal places even if not specified in pattern
21465 if(parts.length == 1 && factor != 1){
21466 parts[1] = "###";
21467 }
21468 if(parts.length == 1 || places === 0){
21469 flags.fractional = false;
21470 }else{
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;
21475 }
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;
21481 }
21482 }
21483 return "("+dojo.number._realNumberRegexp(flags)+")";
21484 });
21485 }, true);
21486
21487 if(isCurrency){
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+")?";
21498 }
21499 return before+symbol+after;
21500 });
21501 }
21502
21503 //TODO: substitute localized sign/percent/permille/etc.?
21504
21505 // normalize whitespace and return
21506 return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object
21507 };
21508
21509 /*=====
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.
21515 // type: String?
21516 // choose a format type based on the locale from the following:
21517 // decimal, scientific (not yet supported), percent, currency. decimal by default.
21518 // locale: String?
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;
21527 this.type = type;
21528 this.locale = locale;
21529 this.strict = strict;
21530 this.fractional = fractional;
21531 }
21532 =====*/
21533 dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){
21534 // summary:
21535 // Convert a properly formatted string to a primitive Number, using
21536 // locale-specific settings.
21537 // description:
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.
21543 // expression:
21544 // A string representation of a Number
21545 var info = dojo.number._parseInfo(options),
21546 results = (new RegExp("^"+info.regexp+"$")).exec(expression);
21547 if(!results){
21548 return NaN; //NaN
21549 }
21550 var absoluteMatch = results[1]; // match for the positive expression
21551 if(!results[1]){
21552 if(!results[2]){
21553 return NaN; //NaN
21554 }
21555 // matched the negative pattern
21556 absoluteMatch =results[2];
21557 info.factor *= -1;
21558 }
21559
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
21567 };
21568
21569 /*=====
21570 dojo.number.__RealNumberRegexpFlags = function(){
21571 // places: Number?
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
21574 // unlimited.
21575 // decimal: String?
21576 // A string for the character used as the decimal point. Default
21577 // is ".".
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
21589 // applied.
21590 this.places = places;
21591 this.decimal = decimal;
21592 this.fractional = fractional;
21593 this.exponent = exponent;
21594 this.eSigned = eSigned;
21595 }
21596 =====*/
21597
21598 dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){
21599 // summary:
21600 // Builds a regular expression to match a real number in exponential
21601 // notation
21602
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]; }
21611
21612 var integerRE = dojo.number._integerRegexp(flags),
21613 decimalRE = dojo.regexp.buildGroupRE(flags.fractional,
21614 function(q){
21615 var re = "";
21616 if(q && (flags.places!==0)){
21617 re = "\\" + flags.decimal;
21618 if(flags.places == Infinity){
21619 re = "(?:" + re + "\\d+)?";
21620 }else{
21621 re += "\\d{" + flags.places + "}";
21622 }
21623 }
21624 return re;
21625 },
21626 true
21627 );
21628
21629 var exponentRE = dojo.regexp.buildGroupRE(flags.exponent,
21630 function(q){
21631 if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; }
21632 return "";
21633 }
21634 );
21635
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
21640 };
21641
21642 /*=====
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
21647 // or unsigned).
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;
21660 }
21661 =====*/
21662
21663 dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){
21664 // summary:
21665 // Builds a regular expression that matches an integer
21666
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;
21674 }
21675
21676 var signRE = dojo.regexp.buildGroupRE(flags.signed,
21677 function(q){ return q ? "[-+]" : ""; },
21678 true
21679 );
21680
21681 var numberRE = dojo.regexp.buildGroupRE(flags.separator,
21682 function(sep){
21683 if(!sep){
21684 return "(?:\\d+)";
21685 }
21686
21687 sep = dojo.regexp.escapeString(sep);
21688 if(sep == " "){ sep = "\\s"; }
21689 else if(sep == "\xa0"){ sep = "\\s\\xa0"; }
21690
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
21693 if(grp2){
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;
21696 }
21697 return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)";
21698 },
21699 true
21700 );
21701
21702 return signRE + numberRE; // String
21703 };
21704
21705 }
21706
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");
21710
21711
21712
21713
21714
21715
21716 dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
21717 // summary:
21718 // A progress indication widget, showing the amount completed
21719 // (often the percentage completed) of a task.
21720 //
21721 // example:
21722 // | <div dojoType="ProgressBar"
21723 // | places="0"
21724 // | value="..." maximum="...">
21725 // | </div>
21726
21727 // progress: [const] String (Percentage or Number)
21728 // Number or percentage indicating amount of task completed.
21729 // Deprecated. Use "value" instead.
21730 progress: "0",
21731
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.
21737 value: "",
21738
21739 // maximum: [const] Float
21740 // Max sample number
21741 maximum: 100,
21742
21743 // places: [const] Number
21744 // Number of places to show in values; 0 by default
21745 places: 0,
21746
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,
21752
21753 // label: String?
21754 // Label on progress bar. Defaults to percentage for determinate progress bar and
21755 // blank for indeterminate progress bar.
21756 label:"",
21757
21758 // name: String
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)
21761 name: '',
21762
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\">&nbsp;</span\n\t></div\n\t><div dojoAttachPoint=\"labelNode\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"></div\n\t><img dojoAttachPoint=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n"),
21764
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"),
21769
21770 postMixInProperties: function(){
21771 this.inherited(arguments);
21772 if(!("value" in this.params)){
21773 this.value = this.indeterminate ? Infinity : this.progress;
21774 }
21775 },
21776
21777 buildRendering: function(){
21778 this.inherited(arguments);
21779 this.indeterminateHighContrastImage.setAttribute("src",
21780 this._indeterminateHighContrastImagePath.toString());
21781 this.update();
21782 },
21783
21784 update: function(/*Object?*/attributes){
21785 // summary:
21786 // Internal method to change attributes of ProgressBar, similar to set(hash). Users should call
21787 // set("value", ...) rather than calling this method directly.
21788 // attributes:
21789 // May provide progress and/or maximum properties on this parameter;
21790 // see attribute specs for details.
21791 // example:
21792 // | myProgressBar.update({'indeterminate': true});
21793 // | myProgressBar.update({'progress': 80});
21794 // | myProgressBar.update({'indeterminate': true, label:"Loading ..." })
21795 // tags:
21796 // private
21797
21798 // TODO: deprecate this method and use set() instead
21799
21800 dojo.mixin(this, attributes || {});
21801 var tip = this.internalProgress, ap = this.domNode;
21802 var percent = 1;
21803 if(this.indeterminate){
21804 dijit.removeWaiState(ap, "valuenow");
21805 dijit.removeWaiState(ap, "valuemin");
21806 dijit.removeWaiState(ap, "valuemax");
21807 }else{
21808 if(String(this.progress).indexOf("%") != -1){
21809 percent = Math.min(parseFloat(this.progress)/100, 1);
21810 this.progress = percent * this.maximum;
21811 }else{
21812 this.progress = Math.min(this.progress, this.maximum);
21813 percent = this.progress / this.maximum;
21814 }
21815
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);
21820 }
21821 this.labelNode.innerHTML = this.report(percent);
21822
21823 dojo.toggleClass(this.domNode, "dijitProgressBarIndeterminate", this.indeterminate);
21824 tip.style.width = (percent * 100) + "%";
21825 this.onChange();
21826 },
21827
21828 _setValueAttr: function(v){
21829 this._set("value", v);
21830 if(v == Infinity){
21831 this.update({indeterminate:true});
21832 }else{
21833 this.update({indeterminate:false, progress:v});
21834 }
21835 },
21836
21837 _setLabelAttr: function(label){
21838 this._set("label", label);
21839 this.update();
21840 },
21841
21842 _setIndeterminateAttr: function(indeterminate){
21843 // Deprecated, use set("value", ...) instead
21844 this.indeterminate = indeterminate;
21845 this.update();
21846 },
21847
21848 report: function(/*float*/percent){
21849 // summary:
21850 // Generates message to show inside progress bar (normally indicating amount of task completed).
21851 // May be overridden.
21852 // tags:
21853 // extension
21854
21855 return this.label ? this.label :
21856 (this.indeterminate ? "&nbsp;" : dojo.number.format(percent, { type: "percent", places: this.places, locale: this.lang }));
21857 },
21858
21859 onChange: function(){
21860 // summary:
21861 // Callback fired when progress updates.
21862 // tags:
21863 // extension
21864 }
21865 });
21866
21867 }
21868
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");
21872
21873
21874
21875
21876 dojo.declare("dijit.ToolbarSeparator",
21877 [ dijit._Widget, dijit._Templated ],
21878 {
21879 // summary:
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);
21885 },
21886 isFocusable: function(){
21887 // summary:
21888 // This widget isn't focusable, so pass along that fact.
21889 // tags:
21890 // protected
21891 return false;
21892 }
21893
21894 });
21895
21896 }
21897
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");
21901
21902
21903
21904
21905
21906
21907 // Note: require of ToolbarSeparator is for back-compat, remove for 2.0
21908
21909 dojo.declare("dijit.Toolbar",
21910 [dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
21911 {
21912 // summary:
21913 // A Toolbar widget, used to hold things like `dijit.Editor` buttons
21914
21915 templateString:
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>'+
21919 // '</table>' +
21920 '</div>',
21921
21922 baseClass: "dijitToolbar",
21923
21924 postCreate: function(){
21925 this.inherited(arguments);
21926
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]
21930 );
21931 },
21932
21933 startup: function(){
21934 if(this._started){ return; }
21935
21936 this.startupKeyNavChildren();
21937
21938 this.inherited(arguments);
21939 }
21940 }
21941 );
21942
21943 }
21944
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");
21948
21949
21950 dojo.DeferredList = function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/ consumeErrors, /*Function?*/ canceller){
21951 // summary:
21952 // Provides event handling for a group of Deferred objects.
21953 // description:
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
21958 //
21959 // list:
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
21967 // canceller:
21968 // A deferred canceller function, see dojo.Deferred
21969 var resultList = [];
21970 dojo.Deferred.call(this);
21971 var self = this;
21972 if(list.length === 0 && !fireOnOneCallback){
21973 this.resolve([0, []]);
21974 }
21975 var finished = 0;
21976 dojo.forEach(list, function(item, i){
21977 item.then(function(result){
21978 if(fireOnOneCallback){
21979 self.resolve([i, result]);
21980 }else{
21981 addResult(true, result);
21982 }
21983 },function(error){
21984 if(fireOnOneErrback){
21985 self.reject(error);
21986 }else{
21987 addResult(false, error);
21988 }
21989 if(consumeErrors){
21990 return null;
21991 }
21992 throw error;
21993 });
21994 function addResult(succeeded, result){
21995 resultList[i] = [succeeded, result];
21996 finished++;
21997 if(finished === list.length){
21998 self.resolve(resultList);
21999 }
22000
22001 }
22002 });
22003 };
22004 dojo.DeferredList.prototype = new dojo.Deferred();
22005
22006 dojo.DeferredList.prototype.gatherResults= function(deferredList){
22007 // summary:
22008 // Gathers the results of the deferreds for packaging
22009 // as the parameters to the Deferred Lists' callback
22010
22011 var d = new dojo.DeferredList(deferredList, false, true, false);
22012 d.addCallback(function(results){
22013 var ret = [];
22014 dojo.forEach(results, function(result){
22015 ret.push(result[1]);
22016 });
22017 return ret;
22018 });
22019 return d;
22020 };
22021
22022 }
22023
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");
22027
22028
22029 dojo.declare(
22030 "dijit.tree.TreeStoreModel",
22031 null,
22032 {
22033 // summary:
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.
22037
22038 // store: dojo.data.Store
22039 // Underlying store
22040 store: null,
22041
22042 // childrenAttrs: String[]
22043 // One or more attribute names (attributes in the dojo.data item) that specify that item's children
22044 childrenAttrs: ["children"],
22045
22046 // newItemIdAttr: String
22047 // Name of attribute in the Object passed to newItem() that specifies the id.
22048 //
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).
22052 //
22053 // Setting this to null or "" will make every drop create a new item.
22054 newItemIdAttr: "id",
22055
22056 // labelAttr: String
22057 // If specified, get label for tree node from this attribute, rather
22058 // than by calling store.getLabel()
22059 labelAttr: "",
22060
22061 // root: [readonly] dojo.data.Item
22062 // Pointer to the root item (read only, not a parameter)
22063 root: null,
22064
22065 // query: anything
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
22068 // to root item.
22069 // example:
22070 // | {id:'ROOT'}
22071 query: null,
22072
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,
22081
22082 constructor: function(/* Object */ args){
22083 // summary:
22084 // Passed the arguments listed above (store, etc)
22085 // tags:
22086 // private
22087
22088 dojo.mixin(this, args);
22089
22090 this.connects = [];
22091
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");
22095 }
22096
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")
22103 ]);
22104 }
22105 },
22106
22107 destroy: function(){
22108 dojo.forEach(this.connects, dojo.disconnect);
22109 // TODO: should cancel any in-progress processing of getRoot(), getChildren()
22110 },
22111
22112 // =======================================================================
22113 // Methods for traversing hierarchy
22114
22115 getRoot: function(onItem, onError){
22116 // summary:
22117 // Calls onItem with the root item for the tree, possibly a fabricated item.
22118 // Calls onError on error.
22119 if(this.root){
22120 onItem(this.root);
22121 }else{
22122 this.store.fetch({
22123 query: this.query,
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");
22128 }
22129 this.root = items[0];
22130 onItem(this.root);
22131 }),
22132 onError: onError
22133 });
22134 }
22135 },
22136
22137 mayHaveChildren: function(/*dojo.data.Item*/ item){
22138 // summary:
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);
22145 }, this);
22146 },
22147
22148 getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){
22149 // summary:
22150 // Calls onComplete() with array of child items of given parent item, all loaded.
22151
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
22156 // child item)
22157 var getChildren = dojo.hitch(this, arguments.callee);
22158 store.loadItem({
22159 item: parentItem,
22160 onItem: function(parentItem){
22161 getChildren(parentItem, onComplete, onError);
22162 },
22163 onError: onError
22164 });
22165 return;
22166 }
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);
22172 }
22173
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++; } });
22178 }
22179
22180 if(_waitCount == 0){
22181 // all items are already loaded (or we aren't loading them). proceed...
22182 onComplete(childItems);
22183 }else{
22184 // still waiting for some or all of the items to load
22185 dojo.forEach(childItems, function(item, idx){
22186 if(!store.isItemLoaded(item)){
22187 store.loadItem({
22188 item: 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);
22194 }
22195 },
22196 onError: onError
22197 });
22198 }
22199 });
22200 }
22201 },
22202
22203 // =======================================================================
22204 // Inspecting items
22205
22206 isItem: function(/* anything */ something){
22207 return this.store.isItem(something); // Boolean
22208 },
22209
22210 fetchItemByIdentity: function(/* object */ keywordArgs){
22211 this.store.fetchItemByIdentity(keywordArgs);
22212 },
22213
22214 getIdentity: function(/* item */ item){
22215 return this.store.getIdentity(item); // Object
22216 },
22217
22218 getLabel: function(/*dojo.data.Item*/ item){
22219 // summary:
22220 // Get the label for an item
22221 if(this.labelAttr){
22222 return this.store.getValue(item,this.labelAttr); // String
22223 }else{
22224 return this.store.getLabel(item); // String
22225 }
22226 },
22227
22228 // =======================================================================
22229 // Write interface
22230
22231 newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){
22232 // summary:
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.
22235 // description:
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.
22239
22240 var pInfo = {parent: parent, attribute: this.childrenAttrs[0]}, LnewItem;
22241
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){
22245 if(item){
22246 // There's already a matching item in store, use it
22247 this.pasteItem(item, null, parent, true, insertIndex);
22248 }else{
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);
22254 }
22255 }
22256 }});
22257 }else{
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);
22263 }
22264 }
22265 },
22266
22267 pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){
22268 // summary:
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
22273
22274 // remove child from source item, and record the attribute that child occurred in
22275 if(oldParentItem){
22276 dojo.forEach(this.childrenAttrs, function(attr){
22277 if(store.containsValue(oldParentItem, attr, childItem)){
22278 if(!bCopy){
22279 var values = dojo.filter(store.getValues(oldParentItem, attr), function(x){
22280 return x != childItem;
22281 });
22282 store.setValues(oldParentItem, attr, values);
22283 }
22284 parentAttr = attr;
22285 }
22286 });
22287 }
22288
22289 // modify target item's children attribute to include this item
22290 if(newParentItem){
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);
22296 }else{
22297 store.setValues(newParentItem, parentAttr,
22298 store.getValues(newParentItem, parentAttr).concat(childItem));
22299 }
22300 }
22301 },
22302
22303 // =======================================================================
22304 // Callbacks
22305
22306 onChange: function(/*dojo.data.Item*/ item){
22307 // summary:
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.
22312 // tags:
22313 // callback
22314 },
22315
22316 onChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
22317 // summary:
22318 // Callback to do notifications about new, updated, or deleted items.
22319 // tags:
22320 // callback
22321 },
22322
22323 onDelete: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
22324 // summary:
22325 // Callback when an item has been deleted.
22326 // description:
22327 // Note that there will also be an onChildrenChange() callback for the parent
22328 // of this item.
22329 // tags:
22330 // callback
22331 },
22332
22333 // =======================================================================
22334 // Events from data store
22335
22336 onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){
22337 // summary:
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).
22340 // description:
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.
22344 //
22345 // tags:
22346 // extension
22347
22348 // We only care about the new item if it has a parent that corresponds to a TreeNode
22349 // we are currently displaying
22350 if(!parentInfo){
22351 return;
22352 }
22353
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);
22361 }));
22362 },
22363
22364 onDeleteItem: function(/*Object*/ item){
22365 // summary:
22366 // Handler for delete notifications from underlying store
22367 this.onDelete(item);
22368 },
22369
22370 onSetItem: function(/* item */ item,
22371 /* attribute-name-string */ attribute,
22372 /* object | array */ oldValue,
22373 /* object | array */ newValue){
22374 // summary:
22375 // Updates the tree view according to changes in the data store.
22376 // description:
22377 // Handles updates to an item's children by calling onChildrenChange(), and
22378 // other updates to an item by calling onChange().
22379 //
22380 // See `onNewItem` for more details on handling updates to an item's children.
22381 // tags:
22382 // extension
22383
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);
22389 }));
22390 }else{
22391 // item's label/icon/etc. changed.
22392 this.onChange(item);
22393 }
22394 }
22395 });
22396
22397 }
22398
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");
22402
22403
22404
22405 dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.TreeStoreModel, {
22406 // summary:
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.
22409 //
22410 // description
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.
22415 //
22416 // When using this class the developer must override a number of methods according to their app and
22417 // data, including:
22418 // - onNewRootItem
22419 // - onAddToRoot
22420 // - onLeaveRoot
22421 // - onNewItem
22422 // - onSetItem
22423
22424 // Parameters to constructor
22425
22426 // rootId: String
22427 // ID of fabricated root item
22428 rootId: "$root$",
22429
22430 // rootLabel: String
22431 // Label of fabricated root item
22432 rootLabel: "ROOT",
22433
22434 // query: String
22435 // Specifies the set of children of the root item.
22436 // example:
22437 // | {type:'continent'}
22438 query: null,
22439
22440 // End of parameters to constructor
22441
22442 constructor: function(params){
22443 // summary:
22444 // Sets up variables, etc.
22445 // tags:
22446 // private
22447
22448 // Make dummy root item
22449 this.root = {
22450 store: this,
22451 root: true,
22452 id: params.rootId,
22453 label: params.rootLabel,
22454 children: params.rootChildren // optional param
22455 };
22456 },
22457
22458 // =======================================================================
22459 // Methods for traversing hierarchy
22460
22461 mayHaveChildren: function(/*dojo.data.Item*/ item){
22462 // summary:
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)
22467 // tags:
22468 // extension
22469 return item === this.root || this.inherited(arguments);
22470 },
22471
22472 getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ callback, /*function*/ onError){
22473 // summary:
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);
22479 }else{
22480 this.store.fetch({
22481 query: this.query,
22482 onComplete: dojo.hitch(this, function(items){
22483 this.root.children = items;
22484 callback(items);
22485 }),
22486 onError: onError
22487 });
22488 }
22489 }else{
22490 this.inherited(arguments);
22491 }
22492 },
22493
22494 // =======================================================================
22495 // Inspecting items
22496
22497 isItem: function(/* anything */ something){
22498 return (something === this.root) ? true : this.inherited(arguments);
22499 },
22500
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);
22506 }
22507 }else{
22508 this.inherited(arguments);
22509 }
22510 },
22511
22512 getIdentity: function(/* item */ item){
22513 return (item === this.root) ? this.root.id : this.inherited(arguments);
22514 },
22515
22516 getLabel: function(/* item */ item){
22517 return (item === this.root) ? this.root.label : this.inherited(arguments);
22518 },
22519
22520 // =======================================================================
22521 // Write interface
22522
22523 newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){
22524 // summary:
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);
22530 }else{
22531 return this.inherited(arguments);
22532 }
22533 },
22534
22535 onNewRootItem: function(args){
22536 // summary:
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
22539 },
22540
22541 pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){
22542 // summary:
22543 // Move or copy an item from one parent item to another.
22544 // Used in drag & drop
22545 if(oldParentItem === this.root){
22546 if(!bCopy){
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);
22551 }
22552 }
22553 dijit.tree.TreeStoreModel.prototype.pasteItem.call(this, childItem,
22554 oldParentItem === this.root ? null : oldParentItem,
22555 newParentItem === this.root ? null : newParentItem,
22556 bCopy,
22557 insertIndex
22558 );
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);
22564 }
22565 },
22566
22567 // =======================================================================
22568 // Handling for top level children
22569
22570 onAddToRoot: function(/* item */ item){
22571 // summary:
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
22574 // example:
22575 // | store.setValue(item, "root", true);
22576 // tags:
22577 // extension
22578 console.log(this, ": item ", item, " added to root");
22579 },
22580
22581 onLeaveRoot: function(/* item */ item){
22582 // summary:
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
22585 // example:
22586 // | store.unsetAttribute(item, "root");
22587 // tags:
22588 // extension
22589 console.log(this, ": item ", item, " removed from root");
22590 },
22591
22592 // =======================================================================
22593 // Events from data store
22594
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 || [];
22599 this.store.fetch({
22600 query: this.query,
22601 onComplete: dojo.hitch(this, function(newChildren){
22602 this.root.children = newChildren;
22603
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);
22608 }
22609 })
22610 });
22611 },
22612
22613 onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){
22614 // summary:
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.
22617 // description:
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).
22621 //
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.
22626 // tags:
22627 // extension
22628 this._requeryTop();
22629
22630 this.inherited(arguments);
22631 },
22632
22633 onDeleteItem: function(/*Object*/ item){
22634 // summary:
22635 // Handler for delete notifications from underlying store
22636
22637 // check if this was a child of root, and if so send notification that root's children
22638 // have changed
22639 if(dojo.indexOf(this.root.children, item) != -1){
22640 this._requeryTop();
22641 }
22642
22643 this.inherited(arguments);
22644 },
22645
22646 onSetItem: function(/* item */ item,
22647 /* attribute-name-string */ attribute,
22648 /* object | array */ oldValue,
22649 /* object | array */ newValue){
22650 // summary:
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.
22653 // description:
22654 // Handles updates to an item's children by calling onChildrenChange(), and
22655 // other updates to an item by calling onChange().
22656 //
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.
22659 //
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.
22662 // tags:
22663 // extension
22664
22665 this._requeryTop();
22666 this.inherited(arguments);
22667 }
22668
22669 });
22670
22671 }
22672
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");
22676
22677
22678
22679
22680 /*
22681 Container states:
22682 "" - normal state
22683 "Over" - mouse over a container
22684 Container item states:
22685 "" - normal state
22686 "Over" - mouse over a container item
22687 */
22688
22689 /*=====
22690 dojo.declare("dojo.dnd.__ContainerArgs", [], {
22691 creator: function(){
22692 // summary:
22693 // a creator function, which takes a data item, and returns an object like that:
22694 // {node: newNode, data: usedData, type: arrayOfStrings}
22695 },
22696
22697 // skipForm: Boolean
22698 // don't start the drag operation, if clicked on form elements
22699 skipForm: false,
22700
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)
22704 dropParent: null,
22705
22706 // _skipStartup: Boolean
22707 // skip startup(), which collects children, for deferred initialization
22708 // (this is used in the markup mode)
22709 _skipStartup: false
22710 });
22711
22712 dojo.dnd.Item = function(){
22713 // summary:
22714 // Represents (one of) the source node(s) being dragged.
22715 // Contains (at least) the "type" and "data" attributes.
22716 // type: String[]
22717 // Type(s) of this item, by default this is ["text"]
22718 // data: Object
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.
22723
22724 this.type = type;
22725 this.data = data;
22726 }
22727 =====*/
22728
22729 dojo.declare("dojo.dnd.Container", null, {
22730 // summary:
22731 // a Container object, which knows when mouse hovers over it,
22732 // and over which element it hovers
22733
22734 // object attributes (for markup)
22735 skipForm: false,
22736
22737 /*=====
22738 // current: DomNode
22739 // The DOM node the mouse is currently hovered over
22740 current: null,
22741
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.
22745 map: {},
22746 =====*/
22747
22748 constructor: function(node, params){
22749 // summary:
22750 // a constructor of the Container
22751 // node: Node
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);
22760
22761 // class-specific variables
22762 this.map = {};
22763 this.current = null;
22764
22765 // states
22766 this.containerState = "";
22767 dojo.addClass(this.node, "dojoDndContainer");
22768
22769 // mark up children
22770 if(!(params && params._skipStartup)){
22771 this.startup();
22772 }
22773
22774 // set up events
22775 this.events = [
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")
22781 ];
22782 },
22783
22784 // object attributes (for markup)
22785 creator: function(){
22786 // summary:
22787 // creator function, dummy at the moment
22788 },
22789
22790 // abstract access to the map
22791 getItem: function(/*String*/ key){
22792 // summary:
22793 // returns a data item by its key (id)
22794 return this.map[key]; // dojo.dnd.Item
22795 },
22796 setItem: function(/*String*/ key, /*dojo.dnd.Item*/ data){
22797 // summary:
22798 // associates a data item with its key (id)
22799 this.map[key] = data;
22800 },
22801 delItem: function(/*String*/ key){
22802 // summary:
22803 // removes a data item from the map by its key (id)
22804 delete this.map[key];
22805 },
22806 forInItems: function(/*Function*/ f, /*Object?*/ o){
22807 // summary:
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;
22812 for(var i in m){
22813 if(i in e){ continue; }
22814 f.call(o, m[i], i, this);
22815 }
22816 return o; // Object
22817 },
22818 clearItems: function(){
22819 // summary:
22820 // removes all data items from the map
22821 this.map = {};
22822 },
22823
22824 // methods
22825 getAllNodes: function(){
22826 // summary:
22827 // returns a list (an array) of all valid child nodes
22828 return dojo.query("> .dojoDndItem", this.parent); // NodeList
22829 },
22830 sync: function(){
22831 // summary:
22832 // sync up the node list with the data map
22833 var map = {};
22834 this.getAllNodes().forEach(function(node){
22835 if(node.id){
22836 var item = this.getItem(node.id);
22837 if(item){
22838 map[node.id] = item;
22839 return;
22840 }
22841 }else{
22842 node.id = dojo.dnd.getUniqueId();
22843 }
22844 var type = node.getAttribute("dndType"),
22845 data = node.getAttribute("dndData");
22846 map[node.id] = {
22847 data: data || node.innerHTML,
22848 type: type ? type.split(/\s*,\s*/) : ["text"]
22849 };
22850 }, this);
22851 this.map = map;
22852 return this; // self
22853 },
22854 insertNodes: function(data, before, anchor){
22855 // summary:
22856 // inserts an array of new nodes before/after an anchor node
22857 // data: Array
22858 // a list of data items, which should be processed by the creator function
22859 // before: Boolean
22860 // insert before the anchor, if true, and after the anchor otherwise
22861 // anchor: Node
22862 // the anchor node to be used as a point of insertion
22863 if(!this.parent.firstChild){
22864 anchor = null;
22865 }else if(before){
22866 if(!anchor){
22867 anchor = this.parent.firstChild;
22868 }
22869 }else{
22870 if(anchor){
22871 anchor = anchor.nextSibling;
22872 }
22873 }
22874 if(anchor){
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);
22879 }
22880 }else{
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);
22885 }
22886 }
22887 return this; // self
22888 },
22889 destroy: function(){
22890 // summary:
22891 // prepares this object to be garbage-collected
22892 dojo.forEach(this.events, dojo.disconnect);
22893 this.clearItems();
22894 this.node = this.parent = this.current = null;
22895 },
22896
22897 // markup methods
22898 markupFactory: function(params, node){
22899 params._skipStartup = true;
22900 return new dojo.dnd.Container(node, params);
22901 },
22902 startup: function(){
22903 // summary:
22904 // collects valid child items and populate the map
22905
22906 // set up the real parent node
22907 if(!this.parent){
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]; }
22913 }
22914 }
22915 this.defaultCreator = dojo.dnd._defaultCreator(this.parent);
22916
22917 // process specially marked children
22918 this.sync();
22919 },
22920
22921 // mouse events
22922 onMouseOver: function(e){
22923 // summary:
22924 // event processor for onmouseover
22925 // e: Event
22926 // mouse event
22927 var n = e.relatedTarget;
22928 while(n){
22929 if(n == this.node){ break; }
22930 try{
22931 n = n.parentNode;
22932 }catch(x){
22933 n = null;
22934 }
22935 }
22936 if(!n){
22937 this._changeState("Container", "Over");
22938 this.onOverEvent();
22939 }
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"); }
22944 this.current = n;
22945 },
22946 onMouseOut: function(e){
22947 // summary:
22948 // event processor for onmouseout
22949 // e: Event
22950 // mouse event
22951 for(var n = e.relatedTarget; n;){
22952 if(n == this.node){ return; }
22953 try{
22954 n = n.parentNode;
22955 }catch(x){
22956 n = null;
22957 }
22958 }
22959 if(this.current){
22960 this._removeItemClass(this.current, "Over");
22961 this.current = null;
22962 }
22963 this._changeState("Container", "");
22964 this.onOutEvent();
22965 },
22966 onSelectStart: function(e){
22967 // summary:
22968 // event processor for onselectevent and ondragevent
22969 // e: Event
22970 // mouse event
22971 if(!this.skipForm || !dojo.dnd.isFormElement(e)){
22972 dojo.stopEvent(e);
22973 }
22974 },
22975
22976 // utilities
22977 onOverEvent: function(){
22978 // summary:
22979 // this function is called once, when mouse is over our container
22980 },
22981 onOutEvent: function(){
22982 // summary:
22983 // this function is called once, when mouse is out of our container
22984 },
22985 _changeState: function(type, newState){
22986 // summary:
22987 // changes a named state to new state value
22988 // type: String
22989 // a name of the state to change
22990 // newState: String
22991 // new state
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;
22997 },
22998 _addItemClass: function(node, type){
22999 // summary:
23000 // adds a class with prefix "dojoDndItem"
23001 // node: Node
23002 // a node
23003 // type: String
23004 // a variable suffix for a class name
23005 dojo.addClass(node, "dojoDndItem" + type);
23006 },
23007 _removeItemClass: function(node, type){
23008 // summary:
23009 // removes a class with prefix "dojoDndItem"
23010 // node: Node
23011 // a node
23012 // type: String
23013 // a variable suffix for a class name
23014 dojo.removeClass(node, "dojoDndItem" + type);
23015 },
23016 _getChildByEvent: function(e){
23017 // summary:
23018 // gets a child, which is under the mouse at the moment, or null
23019 // e: Event
23020 // a mouse event
23021 var node = e.target;
23022 if(node){
23023 for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){
23024 if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; }
23025 }
23026 }
23027 return null;
23028 },
23029 _normalizedCreator: function(/*dojo.dnd.Item*/ item, /*String*/ hint){
23030 // summary:
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");
23036 return t;
23037 }
23038 });
23039
23040 dojo.dnd._createNode = function(tag){
23041 // summary:
23042 // returns a function, which creates an element of given tag
23043 // (SPAN by default) and sets its innerHTML to given text
23044 // tag: String
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
23049 };
23050 };
23051
23052 dojo.dnd._createTrTd = function(text){
23053 // summary:
23054 // creates a TR/TD structure with given text as an innerHTML of TD
23055 // text: String
23056 // a text for TD
23057 var tr = dojo.create("tr");
23058 dojo.create("td", {innerHTML: text}, tr);
23059 return tr; // Node
23060 };
23061
23062 dojo.dnd._createSpan = function(text){
23063 // summary:
23064 // creates a SPAN element with given text as its innerHTML
23065 // text: String
23066 // a text for SPAN
23067 return dojo.create("span", {innerHTML: text}); // Node
23068 };
23069
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"};
23073
23074 dojo.dnd._defaultCreator = function(node){
23075 // summary:
23076 // takes a parent node, and returns an appropriate creator function
23077 // node: Node
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
23090 }else{
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));
23095 }
23096 if(!n.id){
23097 n.id = dojo.dnd.getUniqueId();
23098 }
23099 return {node: n, data: data, type: type};
23100 };
23101 };
23102
23103 }
23104
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");
23108
23109
23110
23111
23112 dojo.getObject("tree", true, dojo);
23113
23114 dijit.tree._compareNodes = function(n1, n2){
23115 if(n1 === n2){
23116 return 0;
23117 }
23118
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);
23127
23128 var r2 = doc.createRange();
23129 r2.setStartBefore(n2);
23130
23131 return r1.compareBoundaryPoints(r1.END_TO_END, r2);
23132 }else{
23133 throw Error("dijit.tree._compareNodes don't know how to compare two different nodes in this browser");
23134 }
23135 };
23136
23137 dojo.declare("dijit.tree._dndContainer",
23138 null,
23139 {
23140
23141 // summary:
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`.
23144 // tags:
23145 // protected
23146
23147 /*=====
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)
23151 current: null,
23152 =====*/
23153
23154 constructor: function(tree, params){
23155 // summary:
23156 // A constructor of the Container
23157 // tree: Node
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
23161 // tags:
23162 // private
23163 this.tree = tree;
23164 this.node = tree.domNode; // TODO: rename; it's not a TreeNode but the whole Tree
23165 dojo.mixin(this, params);
23166
23167 // class-specific variables
23168 this.map = {};
23169 this.current = null; // current TreeNode's DOM node
23170
23171 // states
23172 this.containerState = "";
23173 dojo.addClass(this.node, "dojoDndContainer");
23174
23175 // set up events
23176 this.events = [
23177 // container level events
23178 dojo.connect(this.node, "onmouseenter", this, "onOverEvent"),
23179 dojo.connect(this.node, "onmouseleave", this, "onOutEvent"),
23180
23181 // switching between TreeNodes
23182 dojo.connect(this.tree, "_onNodeMouseEnter", this, "onMouseOver"),
23183 dojo.connect(this.tree, "_onNodeMouseLeave", this, "onMouseOut"),
23184
23185 // cancel text selection and text dragging
23186 dojo.connect(this.node, "ondragstart", dojo, "stopEvent"),
23187 dojo.connect(this.node, "onselectstart", dojo, "stopEvent")
23188 ];
23189 },
23190
23191 getItem: function(/*String*/ key){
23192 // summary:
23193 // Returns the dojo.dnd.Item (representing a dragged node) by it's key (id).
23194 // Called by dojo.dnd.Source.checkAcceptance().
23195 // tags:
23196 // protected
23197
23198 var widget = this.selection[key],
23199 ret = {
23200 data: widget,
23201 type: ["treeNode"]
23202 };
23203
23204 return ret; // dojo.dnd.Item
23205 },
23206
23207 destroy: function(){
23208 // summary:
23209 // Prepares this object to be garbage-collected
23210
23211 dojo.forEach(this.events, dojo.disconnect);
23212 // this.clearItems();
23213 this.node = this.parent = null;
23214 },
23215
23216 // mouse events
23217 onMouseOver: function(/*TreeNode*/ widget, /*Event*/ evt){
23218 // summary:
23219 // Called when mouse is moved over a TreeNode
23220 // tags:
23221 // protected
23222 this.current = widget;
23223 },
23224
23225 onMouseOut: function(/*TreeNode*/ widget, /*Event*/ evt){
23226 // summary:
23227 // Called when mouse is moved away from a TreeNode
23228 // tags:
23229 // protected
23230 this.current = null;
23231 },
23232
23233 _changeState: function(type, newState){
23234 // summary:
23235 // Changes a named state to new state value
23236 // type: String
23237 // A name of the state to change
23238 // newState: String
23239 // new state
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;
23245 },
23246
23247 _addItemClass: function(node, type){
23248 // summary:
23249 // Adds a class with prefix "dojoDndItem"
23250 // node: Node
23251 // A node
23252 // type: String
23253 // A variable suffix for a class name
23254 dojo.addClass(node, "dojoDndItem" + type);
23255 },
23256
23257 _removeItemClass: function(node, type){
23258 // summary:
23259 // Removes a class with prefix "dojoDndItem"
23260 // node: Node
23261 // A node
23262 // type: String
23263 // A variable suffix for a class name
23264 dojo.removeClass(node, "dojoDndItem" + type);
23265 },
23266
23267 onOverEvent: function(){
23268 // summary:
23269 // This function is called once, when mouse is over our container
23270 // tags:
23271 // protected
23272 this._changeState("Container", "Over");
23273 },
23274
23275 onOutEvent: function(){
23276 // summary:
23277 // This function is called once, when mouse is out of our container
23278 // tags:
23279 // protected
23280 this._changeState("Container", "");
23281 }
23282 });
23283
23284 }
23285
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");
23289
23290
23291
23292
23293 dojo.declare("dijit.tree._dndSelector",
23294 dijit.tree._dndContainer,
23295 {
23296 // summary:
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`.
23299 // tags:
23300 // protected
23301
23302 /*=====
23303 // selection: Hash<String, DomNode>
23304 // (id, DomNode) map for every TreeNode that's currently selected.
23305 // The DOMNode is the TreeNode.rowNode.
23306 selection: {},
23307 =====*/
23308
23309 constructor: function(tree, params){
23310 // summary:
23311 // Initialization
23312 // tags:
23313 // private
23314
23315 this.selection={};
23316 this.anchor = null;
23317
23318 dijit.setWaiState(this.tree.domNode, "multiselect", !this.singular);
23319
23320 this.events.push(
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")
23324 );
23325 },
23326
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.
23330 singular: false,
23331
23332 // methods
23333 getSelectedTreeNodes: function(){
23334 // summary:
23335 // Returns a list of selected node(s).
23336 // Used by dndSource on the start of a drag.
23337 // tags:
23338 // protected
23339 var nodes=[], sel = this.selection;
23340 for(var i in sel){
23341 nodes.push(sel[i]);
23342 }
23343 return nodes;
23344 },
23345
23346 selectNone: function(){
23347 // summary:
23348 // Unselects all items
23349 // tags:
23350 // private
23351
23352 this.setSelection([]);
23353 return this; // self
23354 },
23355
23356 destroy: function(){
23357 // summary:
23358 // Prepares the object to be garbage-collected
23359 this.inherited(arguments);
23360 this.selection = this.anchor = null;
23361 },
23362 addTreeNode: function(/*dijit._TreeNode*/node, /*Boolean?*/isAnchor){
23363 // summary
23364 // add node to current selection
23365 // node: Node
23366 // node to add
23367 // isAnchor: Boolean
23368 // Whether the node should become anchor.
23369
23370 this.setSelection(this.getSelectedTreeNodes().concat( [node] ));
23371 if(isAnchor){ this.anchor = node; }
23372 return node;
23373 },
23374 removeTreeNode: function(/*dijit._TreeNode*/node){
23375 // summary
23376 // remove node from current selection
23377 // node: Node
23378 // node to remove
23379 this.setSelection(this._setDifference(this.getSelectedTreeNodes(), [node]))
23380 return node;
23381 },
23382 isTreeNodeSelected: function(/*dijit._TreeNode*/node){
23383 // summary
23384 // return true if node is currently selected
23385 // node: Node
23386 // the node to check whether it's in the current selection
23387
23388 return node.id && !!this.selection[node.id];
23389 },
23390 setSelection: function(/*dijit._treeNode[]*/ newSelection){
23391 // summary
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;
23403 }
23404 delete this.selection[node.id];
23405 }));
23406 dojo.forEach(this._setDifference(newSelection, oldSelection), dojo.hitch(this, function(node){
23407 node.setSelected(true);
23408 this.selection[node.id] = node;
23409 }));
23410 this._updateSelectionProperties();
23411 },
23412 _setDifference: function(xs,ys){
23413 // summary
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.
23418
23419 dojo.forEach(ys, function(y){ y.__exclude__ = true; });
23420 var ret = dojo.filter(xs, function(x){ return !x.__exclude__; });
23421
23422 // clean up after ourselves.
23423 dojo.forEach(ys, function(y){ delete y['__exclude__'] });
23424 return ret;
23425 },
23426 _updateSelectionProperties: function() {
23427 // summary
23428 // Update the following tree properties from the current selection:
23429 // path[s], selectedItem[s], selectedNode[s]
23430
23431 var selected = this.getSelectedTreeNodes();
23432 var paths = [], nodes = [];
23433 dojo.forEach(selected, function(node) {
23434 nodes.push(node);
23435 paths.push(node.getTreePath());
23436 });
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);
23444 },
23445 // mouse events
23446 onMouseDown: function(e){
23447 // summary:
23448 // Event processor for onmousedown
23449 // e: Event
23450 // mouse event
23451 // tags:
23452 // protected
23453
23454 // ignore click on expando node
23455 if(!this.current || this.tree.isExpandoNode( e.target, this.current)){ return; }
23456
23457 if(e.button == dojo.mouseButtons.RIGHT){ return; } // ignore right-click
23458
23459 dojo.stopEvent(e);
23460
23461 var treeNode = this.current,
23462 copy = dojo.isCopyKey(e), id = treeNode.id;
23463
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;
23469 return;
23470 }else{
23471 this._doDeselect = false;
23472 }
23473 this.userSelect(treeNode, copy, e.shiftKey);
23474 },
23475
23476 onMouseUp: function(e){
23477 // summary:
23478 // Event processor for onmouseup
23479 // e: Event
23480 // mouse event
23481 // tags:
23482 // protected
23483
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);
23492 },
23493 onMouseMove: function(e){
23494 // summary
23495 // event processor for onmousemove
23496 // e: Event
23497 // mouse event
23498 this._doDeselect = false;
23499 },
23500
23501 userSelect: function(node, multi, range){
23502 // summary:
23503 // Add or remove the given node from selection, responding
23504 // to a user action such as a click or keypress.
23505 // multi: Boolean
23506 // Indicates whether this is meant to be a multi-select action (e.g. ctrl-click)
23507 // range: Boolean
23508 // Indicates whether this is meant to be a ranged action (e.g. shift-click)
23509 // tags:
23510 // protected
23511
23512 if(this.singular){
23513 if(this.anchor == node && multi){
23514 this.selectNone();
23515 }else{
23516 this.setSelection([node]);
23517 this.anchor = node;
23518 }
23519 }else{
23520 if(range && this.anchor){
23521 var cr = dijit.tree._compareNodes(this.anchor.rowNode, node.rowNode),
23522 begin, end, anchor = this.anchor;
23523
23524 if(cr < 0){ //current is after anchor
23525 begin = anchor;
23526 end = node;
23527 }else{ //current is before anchor
23528 begin = node;
23529 end = anchor;
23530 }
23531 nodes = [];
23532 //add everything betweeen begin and end inclusively
23533 while(begin != end) {
23534 nodes.push(begin)
23535 begin = this.tree._getNextNode(begin);
23536 }
23537 nodes.push(end)
23538
23539 this.setSelection(nodes);
23540 }else{
23541 if( this.selection[ node.id ] && multi ) {
23542 this.removeTreeNode( node );
23543 } else if(multi) {
23544 this.addTreeNode(node, true);
23545 } else {
23546 this.setSelection([node]);
23547 this.anchor = node;
23548 }
23549 }
23550 }
23551 },
23552
23553 forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){
23554 // summary:
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);
23561 }
23562 }
23563 });
23564
23565 }
23566
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");
23570
23571
23572
23573
23574
23575
23576
23577
23578
23579
23580
23581
23582
23583 dojo.declare(
23584 "dijit._TreeNode",
23585 [dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained, dijit._CssStateMixin],
23586 {
23587 // summary:
23588 // Single node within a tree. This class is used internally
23589 // by Tree and should not be accessed directly.
23590 // tags:
23591 // private
23592
23593 // item: [const] dojo.data.Item
23594 // the dojo.data entry this tree represents
23595 item: null,
23596
23597 // isTreeNode: [protected] Boolean
23598 // Indicates that this is a TreeNode. Used by `dijit.Tree` only,
23599 // should not be accessed directly.
23600 isTreeNode: true,
23601
23602 // label: String
23603 // Text of this tree node
23604 label: "",
23605
23606 // isExpandable: [private] Boolean
23607 // This node has children, so show the expando node (+ sign)
23608 isExpandable: null,
23609
23610 // isExpanded: [readonly] Boolean
23611 // This node is currently expanded (ie, opened)
23612 isExpanded: false,
23613
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",
23619
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"),
23621
23622 baseClass: "dijitTreeNode",
23623
23624 // For hover effect for tree node, and focus effect for label
23625 cssStateNodes: {
23626 rowNode: "dijitTreeRow",
23627 labelNode: "dijitTreeLabel"
23628 },
23629
23630 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
23631 label: {node: "labelNode", type: "innerText"},
23632 tooltip: {node: "rowNode", type: "attribute", attribute: "title"}
23633 }),
23634
23635 buildRendering: function(){
23636 this.inherited(arguments);
23637
23638 // set expand icon for leaf
23639 this._setExpando();
23640
23641 // set icon and label class based on item
23642 this._updateItemClasses(this.item);
23643
23644 if(this.isExpandable){
23645 dijit.setWaiState(this.labelNode, "expanded", this.isExpanded);
23646 }
23647
23648 //aria-selected should be false on all selectable elements.
23649 this.setSelected(false);
23650 },
23651
23652 _setIndentAttr: function(indent){
23653 // summary:
23654 // Tell this node how many levels it should be indented
23655 // description:
23656 // 0 for top level nodes, 1 for their children, 2 for their
23657 // grandchildren, etc.
23658
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";
23661
23662 dojo.style(this.domNode, "backgroundPosition", pixels + " 0px");
23663 dojo.style(this.rowNode, this.isLeftToRight() ? "paddingLeft" : "paddingRight", pixels);
23664
23665 dojo.forEach(this.getChildren(), function(child){
23666 child.set("indent", indent+1);
23667 });
23668
23669 this._set("indent", indent);
23670 },
23671
23672 markProcessing: function(){
23673 // summary:
23674 // Visually denote that tree is loading data, etc.
23675 // tags:
23676 // private
23677 this.state = "LOADING";
23678 this._setExpando(true);
23679 },
23680
23681 unmarkProcessing: function(){
23682 // summary:
23683 // Clear markup from markProcessing() call
23684 // tags:
23685 // private
23686 this._setExpando(false);
23687 },
23688
23689 _updateItemClasses: function(item){
23690 // summary:
23691 // Set appropriate CSS classes for icon and label dom node
23692 // (used to allow for item updates to change respective CSS)
23693 // tags:
23694 // private
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)
23698 item = null;
23699 }
23700 this._applyClassAndStyle(item, "icon", "Icon");
23701 this._applyClassAndStyle(item, "label", "Label");
23702 this._applyClassAndStyle(item, "row", "Row");
23703 },
23704
23705 _applyClassAndStyle: function(item, lower, upper){
23706 // summary:
23707 // Set the appropriate CSS classes and styles for labels, icons and rows.
23708 //
23709 // item:
23710 // The data item.
23711 //
23712 // lower:
23713 // The lower case attribute to use, e.g. 'icon', 'label' or 'row'.
23714 //
23715 // upper:
23716 // The upper case attribute to use, e.g. 'Icon', 'Label' or 'Row'.
23717 //
23718 // tags:
23719 // private
23720
23721 var clsName = "_" + lower + "Class";
23722 var nodeName = lower + "Node";
23723 var oldCls = this[clsName];
23724
23725 this[clsName] = this.tree["get" + upper + "Class"](item, this.isExpanded);
23726 dojo.replaceClass(this[nodeName], this[clsName] || "", oldCls || "");
23727
23728 dojo.style(this[nodeName], this.tree["get" + upper + "Style"](item, this.isExpanded) || {});
23729 },
23730
23731 _updateLayout: function(){
23732 // summary:
23733 // Set appropriate CSS classes for this.domNode
23734 // tags:
23735 // private
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");
23740 }else{
23741 dojo.toggleClass(this.domNode, "dijitTreeIsLast", !this.getNextSibling());
23742 }
23743 },
23744
23745 _setExpando: function(/*Boolean*/ processing){
23746 // summary:
23747 // Set the right image for the expando node
23748 // tags:
23749 // private
23750
23751 var styles = ["dijitTreeExpandoLoading", "dijitTreeExpandoOpened",
23752 "dijitTreeExpandoClosed", "dijitTreeExpandoLeaf"],
23753 _a11yStates = ["*","-","+","*"],
23754 idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3);
23755
23756 // apply the appropriate class to the expando node
23757 dojo.replaceClass(this.expandoNode, styles[idx], styles);
23758
23759 // provide a non-image based indicator for images-off mode
23760 this.expandoNodeText.innerHTML = _a11yStates[idx];
23761
23762 },
23763
23764 expand: function(){
23765 // summary:
23766 // Show my children
23767 // returns:
23768 // Deferred that fires when expansion is complete
23769
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
23773 }
23774
23775 // cancel in progress collapse operation
23776 this._wipeOut && this._wipeOut.stop();
23777
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");
23784 }
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");
23790 }
23791
23792 var def,
23793 wipeIn = dojo.fx.wipeIn({
23794 node: this.containerNode, duration: dijit.defaultDuration,
23795 onEnd: function(){
23796 def.callback(true);
23797 }
23798 });
23799
23800 // Deferred that fires when expand is complete
23801 def = (this._expandDeferred = new dojo.Deferred(function(){
23802 // Canceller
23803 wipeIn.stop();
23804 }));
23805
23806 wipeIn.play();
23807
23808 return def; // dojo.Deferred
23809 },
23810
23811 collapse: function(){
23812 // summary:
23813 // Collapse this node (if it's expanded)
23814
23815 if(!this.isExpanded){ return; }
23816
23817 // cancel in progress expand operation
23818 if(this._expandDeferred){
23819 this._expandDeferred.cancel();
23820 delete this._expandDeferred;
23821 }
23822
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");
23827 }
23828 dojo.removeClass(this.contentNode,'dijitTreeContentExpanded');
23829 this._setExpando();
23830 this._updateItemClasses(this.item);
23831
23832 if(!this._wipeOut){
23833 this._wipeOut = dojo.fx.wipeOut({
23834 node: this.containerNode, duration: dijit.defaultDuration
23835 });
23836 }
23837 this._wipeOut.play();
23838 },
23839
23840 // indent: Integer
23841 // Levels from this node to the root node
23842 indent: 0,
23843
23844 setChildItems: function(/* Object[] */ items){
23845 // summary:
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
23849 // opened.
23850 // returns:
23851 // Deferred object that fires after all previously opened children
23852 // have been expanded again (or fires instantly if there are no such children).
23853
23854 var tree = this.tree,
23855 model = tree.model,
23856 defs = []; // list of deferreds that need to fire before I am complete
23857
23858
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);
23864 }, this);
23865
23866 this.state = "LOADED";
23867
23868 if(items && items.length > 0){
23869 this.isExpandable = true;
23870
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
23873 // released
23874 dojo.forEach(items, function(item){
23875 var id = model.getIdentity(item),
23876 existingNodes = tree._itemNodesMap[id],
23877 node;
23878 if(existingNodes){
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);
23883 break;
23884 }
23885 }
23886 }
23887 if(!node){
23888 node = this.tree._createTreeNode({
23889 item: item,
23890 tree: tree,
23891 isExpandable: model.mayHaveChildren(item),
23892 label: tree.getLabel(item),
23893 tooltip: tree.getTooltip(item),
23894 dir: tree.dir,
23895 lang: tree.lang,
23896 indent: this.indent + 1
23897 });
23898 if(existingNodes){
23899 existingNodes.push(node);
23900 }else{
23901 tree._itemNodesMap[id] = [node];
23902 }
23903 }
23904 this.addChild(node);
23905
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));
23910 }
23911 }, this);
23912
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();
23917 });
23918 }else{
23919 this.isExpandable=false;
23920 }
23921
23922 if(this._setExpando){
23923 // change expando to/from dot or + icon, as appropriate
23924 this._setExpando(false);
23925 }
23926
23927 // Set leaf icon or folder icon, as appropriate
23928 this._updateItemClasses(this.item);
23929
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];
23934 if(fc){
23935 fc.setFocusable(true);
23936 tree.lastFocused = fc;
23937 }else{
23938 // fallback: no nodes in tree so focus on Tree <div> itself
23939 tree.domNode.setAttribute("tabIndex", "0");
23940 }
23941 }
23942
23943 return new dojo.DeferredList(defs); // dojo.Deferred
23944 },
23945
23946 getTreePath: function(){
23947 var node = this;
23948 var path = [];
23949 while(node && node !== this.tree.rootNode){
23950 path.unshift(node.item);
23951 node = node.getParent();
23952 }
23953 path.unshift(this.tree.rootNode.item);
23954
23955 return path;
23956 },
23957
23958 getIdentity: function() {
23959 return this.tree.model.getIdentity(this.item);
23960 },
23961
23962 removeChild: function(/* treeNode */ node){
23963 this.inherited(arguments);
23964
23965 var children = this.getChildren();
23966 if(children.length == 0){
23967 this.isExpandable = false;
23968 this.collapse();
23969 }
23970
23971 dojo.forEach(children, function(child){
23972 child._updateLayout();
23973 });
23974 },
23975
23976 makeExpandable: function(){
23977 // summary:
23978 // if this node wasn't already showing the expando node,
23979 // turn it into one and call _setExpando()
23980
23981 // TODO: hmm this isn't called from anywhere, maybe should remove it for 2.0
23982
23983 this.isExpandable = true;
23984 this._setExpando(false);
23985 },
23986
23987 _onLabelFocus: function(evt){
23988 // summary:
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.
23992 // tags:
23993 // private
23994 this.tree._onNodeFocus(this);
23995 },
23996
23997 setSelected: function(/*Boolean*/ selected){
23998 // summary:
23999 // A Tree has a (single) currently selected node.
24000 // Mark that this node is/isn't that currently selected node.
24001 // description:
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);
24006 },
24007
24008 setFocusable: function(/*Boolean*/ selected){
24009 // summary:
24010 // A Tree has a (single) node that's focusable.
24011 // Mark that this node is/isn't that currently focsuable node.
24012 // description:
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).
24015
24016 this.labelNode.setAttribute("tabIndex", selected ? "0" : "-1");
24017 },
24018
24019 _onClick: function(evt){
24020 // summary:
24021 // Handler for onclick event on a node
24022 // tags:
24023 // private
24024 this.tree._onClick(this, evt);
24025 },
24026 _onDblClick: function(evt){
24027 // summary:
24028 // Handler for ondblclick event on a node
24029 // tags:
24030 // private
24031 this.tree._onDblClick(this, evt);
24032 },
24033
24034 _onMouseEnter: function(evt){
24035 // summary:
24036 // Handler for onmouseenter event on a node
24037 // tags:
24038 // private
24039 this.tree._onNodeMouseEnter(this, evt);
24040 },
24041
24042 _onMouseLeave: function(evt){
24043 // summary:
24044 // Handler for onmouseenter event on a node
24045 // tags:
24046 // private
24047 this.tree._onNodeMouseLeave(this, evt);
24048 }
24049 });
24050
24051 dojo.declare(
24052 "dijit.Tree",
24053 [dijit._Widget, dijit._Templated],
24054 {
24055 // summary:
24056 // This widget displays hierarchical data from a store.
24057
24058 // store: [deprecated] String||dojo.data.Store
24059 // Deprecated. Use "model" parameter instead.
24060 // The store to get data to display in the tree.
24061 store: null,
24062
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)
24066 model: null,
24067
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.
24071 query: null,
24072
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.
24078 label: "",
24079
24080 // showRoot: [const] Boolean
24081 // Should the root node be displayed, or hidden?
24082 showRoot: true,
24083
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"],
24088
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.
24093 paths: [],
24094
24095 // path: String[] or Item[]
24096 // Backward compatible singular variant of paths.
24097 path: [],
24098
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,
24105
24106 // selectedItem: [readonly] Item
24107 // Backward compatible singular variant of selectedItems.
24108 selectedItem: null,
24109
24110 // openOnClick: Boolean
24111 // If true, clicking a folder node's label will open it, rather than calling onClick()
24112 openOnClick: false,
24113
24114 // openOnDblClick: Boolean
24115 // If true, double-clicking a folder node's label will open it, rather than calling onDblClick()
24116 openOnDblClick: false,
24117
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"),
24119
24120 // persist: Boolean
24121 // Enables/disables use of cookies for state saving.
24122 persist: true,
24123
24124 // autoExpand: Boolean
24125 // Fully expand the tree on load. Overrides `persist`.
24126 autoExpand: false,
24127
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",
24133
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"],
24136
24137 //declare the above items so they can be pulled from the tree's markup
24138
24139 // onDndDrop: [protected] Function
24140 // Parameter to dndController, see `dijit.tree.dndSource.onDndDrop`.
24141 // Generally this doesn't need to be set.
24142 onDndDrop: null,
24143
24144 /*=====
24145 itemCreator: function(nodes, target, source){
24146 // summary:
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.
24151 // description:
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
24156 // target: DomNode
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:
24162 // | [
24163 // | { id: 123, label: "apple", foo: "bar" },
24164 // | { id: 456, label: "pear", zaz: "bam" }
24165 // | ]
24166 // tags:
24167 // extension
24168 return [{}];
24169 },
24170 =====*/
24171 itemCreator: null,
24172
24173 // onDndCancel: [protected] Function
24174 // Parameter to dndController, see `dijit.tree.dndSource.onDndCancel`.
24175 // Generally this doesn't need to be set.
24176 onDndCancel: null,
24177
24178 /*=====
24179 checkAcceptance: function(source, nodes){
24180 // summary:
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.
24187 // tags:
24188 // extension
24189 return true; // Boolean
24190 },
24191 =====*/
24192 checkAcceptance: null,
24193
24194 /*=====
24195 checkItemAcceptance: function(target, source, position){
24196 // summary:
24197 // Stub function to be overridden if one wants to check for the ability to drop at the node/item level
24198 // description:
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.
24202 // target: DOMNode
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"
24209 // tags:
24210 // extension
24211 return true; // Boolean
24212 },
24213 =====*/
24214 checkItemAcceptance: null,
24215
24216 // dragThreshold: Integer
24217 // Number of pixels mouse moves before it's considered the start of a drag operation
24218 dragThreshold: 5,
24219
24220 // betweenThreshold: Integer
24221 // Set to a positive value to allow drag and drop "between" nodes.
24222 //
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.
24226 //
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,
24231
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,
24237
24238 _publish: function(/*String*/ topicName, /*Object*/ message){
24239 // summary:
24240 // Publish a message for this widget/topic
24241 dojo.publish(this.id, [dojo.mixin({tree: this, event: topicName}, message || {})]);
24242 },
24243
24244 postMixInProperties: function(){
24245 this.tree = this;
24246
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;
24251 }
24252
24253 this._itemNodesMap={};
24254
24255 if(!this.cookieName){
24256 this.cookieName = this.id + "SaveStateCookie";
24257 }
24258
24259 this._loadDeferred = new dojo.Deferred();
24260
24261 this.inherited(arguments);
24262 },
24263
24264 postCreate: function(){
24265 this._initState();
24266
24267 // Create glue between store and Tree, if not specified directly by user
24268 if(!this.model){
24269 this._store2model();
24270 }
24271
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");
24276
24277 this._load();
24278
24279 this.inherited(arguments);
24280
24281 if(this.dndController){
24282 if(dojo.isString(this.dndController)){
24283 this.dndController = dojo.getObject(this.dndController);
24284 }
24285 var params={};
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]];
24289 }
24290 }
24291 this.dndController = new this.dndController(this, params);
24292 }
24293 },
24294
24295 _store2model: function(){
24296 // summary:
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");
24300
24301 var modelParams = {
24302 id: this.id + "_ForestStoreModel",
24303 store: this.store,
24304 query: this.query,
24305 childrenAttrs: this.childrenAttr
24306 };
24307
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");
24311 }
24312
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);
24316 });
24317 }
24318 this.model = new dijit.tree.ForestStoreModel(modelParams);
24319
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);
24323 },
24324
24325 onLoad: function(){
24326 // summary:
24327 // Called when tree finishes loading and expanding.
24328 // description:
24329 // If persist == true the loading may encompass many levels of fetches
24330 // from the data store, each asynchronous. Waits for all to finish.
24331 // tags:
24332 // callback
24333 },
24334
24335 _load: function(){
24336 // summary:
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({
24342 item: item,
24343 tree: this,
24344 isExpandable: true,
24345 label: this.label || this.getLabel(item),
24346 indent: this.showRoot ? 0 : -1
24347 }));
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');
24353
24354 dijit.setWaiRole(rn.labelNode, 'presentation');
24355 dijit.setWaiRole(rn.containerNode, 'tree');
24356 }
24357 this.domNode.appendChild(rn.domNode);
24358 var identity = this.model.getIdentity(item);
24359 if(this._itemNodesMap[identity]){
24360 this._itemNodesMap[identity].push(rn);
24361 }else{
24362 this._itemNodesMap[identity] = [rn];
24363 }
24364
24365 rn._updateLayout(); // sets "dijitTreeIsRoot" CSS classname
24366
24367 // load top level children and then fire onLoad() event
24368 this._expandNode(rn).addCallback(dojo.hitch(this, function(){
24369 this._loadDeferred.callback(true);
24370 this.onLoad();
24371 }));
24372 }),
24373 function(err){
24374 console.error(this, ": error loading root: ", err);
24375 }
24376 );
24377 },
24378
24379 getNodesByItem: function(/*dojo.data.Item or id*/ item){
24380 // summary:
24381 // Returns all tree nodes that refer to an item
24382 // returns:
24383 // Array of tree nodes that refer to passed item
24384
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]);
24389 },
24390
24391 _setSelectedItemAttr: function(/*dojo.data.Item or id*/ item){
24392 this.set('selectedItems', [item]);
24393 },
24394
24395 _setSelectedItemsAttr: function(/*dojo.data.Items or ids*/ items){
24396 // summary:
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.
24400 var tree = this;
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);
24404 });
24405 var nodes = [];
24406 dojo.forEach(identities, function(id){
24407 nodes = nodes.concat(tree._itemNodesMap[id] || []);
24408 });
24409 this.set('selectedNodes', nodes);
24410 }));
24411 },
24412
24413 _setPathAttr: function(/*Item[] || String[]*/ path){
24414 // summary:
24415 // Singular variant of _setPathsAttr
24416 if(path.length) {
24417 return this.set("paths", [path]);
24418 } else {
24419 //Empty list is interpreted as "select nothing"
24420 return this.set("paths", []);
24421 }
24422 },
24423
24424 _setPathsAttr: function(/*Item[][] || String[][]*/ paths){
24425 // summary:
24426 // Select the tree nodes identified by passed paths.
24427 // paths:
24428 // Array of arrays of items or item id's
24429 // returns:
24430 // Deferred to indicate when the set is complete
24431 var tree = this;
24432
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();
24438
24439 // normalize path to use identity
24440 path = dojo.map(path, function(item){
24441 return dojo.isString(item) ? item : tree.model.getIdentity(item);
24442 });
24443
24444 if(path.length){
24445 // Wait for the tree to load, if it hasn't already.
24446 tree._loadDeferred.addCallback(function(){ selectPath(path, [tree.rootNode], d); });
24447 }else{
24448 d.errback("Empty path");
24449 }
24450 return d;
24451 })).addCallback(setNodes);
24452
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;
24458 })[0];
24459 if(!!nextNode){
24460 if(path.length){
24461 tree._expandNode(nextNode).addCallback(function(){ selectPath(path, nextNode.getChildren(), def); });
24462 }else{
24463 //Successfully reached the end of this path
24464 def.callback(nextNode);
24465 }
24466 } else {
24467 def.errback("Could not expand path at " + nextPath);
24468 }
24469 }
24470
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];}));
24477 }
24478 },
24479
24480 _setSelectedNodeAttr: function(node){
24481 this.set('selectedNodes', [node]);
24482 },
24483 _setSelectedNodesAttr: function(nodes){
24484 this._loadDeferred.addCallback( dojo.hitch(this, function(){
24485 this.dndController.setSelection(nodes);
24486 }));
24487 },
24488
24489
24490 ////////////// Data store related functions //////////////////////
24491 // These just get passed to the model; they are here for back-compat
24492
24493 mayHaveChildren: function(/*dojo.data.Item*/ item){
24494 // summary:
24495 // Deprecated. This should be specified on the model itself.
24496 //
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)
24501 // tags:
24502 // deprecated
24503 },
24504
24505 getItemChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete){
24506 // summary:
24507 // Deprecated. This should be specified on the model itself.
24508 //
24509 // Overridable function that return array of child items of given parent item,
24510 // or if parentItem==null then return top items in tree
24511 // tags:
24512 // deprecated
24513 },
24514
24515 ///////////////////////////////////////////////////////
24516 // Functions for converting an item to a TreeNode
24517 getLabel: function(/*dojo.data.Item*/ item){
24518 // summary:
24519 // Overridable function to get the label for a tree node (given the item)
24520 // tags:
24521 // extension
24522 return this.model.getLabel(item); // String
24523 },
24524
24525 getIconClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
24526 // summary:
24527 // Overridable function to return CSS class name to display icon
24528 // tags:
24529 // extension
24530 return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf"
24531 },
24532
24533 getLabelClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
24534 // summary:
24535 // Overridable function to return CSS class name to display label
24536 // tags:
24537 // extension
24538 },
24539
24540 getRowClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
24541 // summary:
24542 // Overridable function to return CSS class name to display row
24543 // tags:
24544 // extension
24545 },
24546
24547 getIconStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
24548 // summary:
24549 // Overridable function to return CSS styles to display icon
24550 // returns:
24551 // Object suitable for input to dojo.style() like {backgroundImage: "url(...)"}
24552 // tags:
24553 // extension
24554 },
24555
24556 getLabelStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
24557 // summary:
24558 // Overridable function to return CSS styles to display label
24559 // returns:
24560 // Object suitable for input to dojo.style() like {color: "red", background: "green"}
24561 // tags:
24562 // extension
24563 },
24564
24565 getRowStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
24566 // summary:
24567 // Overridable function to return CSS styles to display row
24568 // returns:
24569 // Object suitable for input to dojo.style() like {background-color: "#bbb"}
24570 // tags:
24571 // extension
24572 },
24573
24574 getTooltip: function(/*dojo.data.Item*/ item){
24575 // summary:
24576 // Overridable function to get the tooltip for a tree node (given the item)
24577 // tags:
24578 // extension
24579 return ""; // String
24580 },
24581
24582 /////////// Keyboard and Mouse handlers ////////////////////
24583
24584 _onKeyPress: function(/*Event*/ e){
24585 // summary:
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; }
24591
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() } );
24597 dojo.stopEvent(e);
24598 }
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;
24605 }
24606
24607 var map = this._keyHandlerMap;
24608 if(!map){
24609 // setup table mapping keys to events
24610 map = {};
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;
24624 }
24625 if(this._keyHandlerMap[key]){
24626 this[this._keyHandlerMap[key]]( { node: treeNode, item: treeNode.item, evt: e } );
24627 dojo.stopEvent(e);
24628 }
24629 }
24630 },
24631
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);
24636 },
24637
24638 _onDownArrow: function(/*Object*/ message){
24639 // summary:
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);
24644 }
24645 },
24646
24647 _onUpArrow: function(/*Object*/ message){
24648 // summary:
24649 // Up arrow pressed; move to previous visible node
24650
24651 var node = message.node;
24652
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];
24662 }
24663 }else{
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)){
24668 node = parent;
24669 }
24670 }
24671
24672 if(node && node.isTreeNode){
24673 this.focusNode(node);
24674 }
24675 },
24676
24677 _onRightArrow: function(/*Object*/ message){
24678 // summary:
24679 // Right arrow pressed; go to child node
24680 var node = message.node;
24681
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);
24689 }
24690 }
24691 },
24692
24693 _onLeftArrow: function(/*Object*/ message){
24694 // summary:
24695 // Left arrow pressed.
24696 // If not collapsed, collapse, else move to parent.
24697
24698 var node = message.node;
24699
24700 if(node.isExpandable && node.isExpanded){
24701 this._collapseNode(node);
24702 }else{
24703 var parent = node.getParent();
24704 if(parent && parent.isTreeNode && !(!this.showRoot && parent === this.rootNode)){
24705 this.focusNode(parent);
24706 }
24707 }
24708 },
24709
24710 _onHomeKey: function(){
24711 // summary:
24712 // Home key pressed; get first visible node, and set focus there
24713 var node = this._getRootOrFirstNode();
24714 if(node){
24715 this.focusNode(node);
24716 }
24717 },
24718
24719 _onEndKey: function(/*Object*/ message){
24720 // summary:
24721 // End key pressed; go to last visible node.
24722
24723 var node = this.rootNode;
24724 while(node.isExpanded){
24725 var c = node.getChildren();
24726 node = c[c.length - 1];
24727 }
24728
24729 if(node && node.isTreeNode){
24730 this.focusNode(node);
24731 }
24732 },
24733
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.
24738 //
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,
24742
24743 _onLetterKeyNav: function(message){
24744 // summary:
24745 // Called when user presses a prinatable key; search for node starting with recently typed letters.
24746 // message: Object
24747 // Like { node: TreeNode, key: 'a' } where key is the key the user pressed.
24748
24749 // Branch depending on whether this key starts a new search, or modifies an existing search
24750 var cs = this._curSearch;
24751 if(cs){
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);
24756 }else{
24757 // We are starting a new search
24758 cs = this._curSearch = {
24759 pattern: message.key,
24760 startNode: message.node
24761 };
24762 }
24763
24764 // set/reset timer to forget recent keystrokes
24765 var self = this;
24766 cs.timer = setTimeout(function(){
24767 delete self._curSearch;
24768 }, this.multiCharSearchDuration);
24769
24770 // Navigate to TreeNode matching keystrokes [entered so far].
24771 var node = cs.startNode;
24772 do{
24773 node = this._getNextNode(node);
24774 //check for last node, jump to first node if necessary
24775 if(!node){
24776 node = this._getRootOrFirstNode();
24777 }
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);
24783 }
24784 }
24785 },
24786
24787 isExpandoNode: function(node, widget){
24788 // summary:
24789 // check whether a dom node is the expandoNode for a particular TreeNode widget
24790 return dojo.isDescendant(node, widget.expandoNode);
24791 },
24792 _onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){
24793 // summary:
24794 // Translates click events into commands for the controller to process
24795
24796 var domElement = e.target,
24797 isExpandoClick = this.isExpandoNode(domElement, nodeWidget);
24798
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});
24803 }
24804 }else{
24805 this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } );
24806 this.onClick(nodeWidget.item, nodeWidget, e);
24807 this.focusNode(nodeWidget);
24808 }
24809 dojo.stopEvent(e);
24810 },
24811 _onDblClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){
24812 // summary:
24813 // Translates double-click events into commands for the controller to process
24814
24815 var domElement = e.target,
24816 isExpandoClick = (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText);
24817
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});
24822 }
24823 }else{
24824 this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } );
24825 this.onDblClick(nodeWidget.item, nodeWidget, e);
24826 this.focusNode(nodeWidget);
24827 }
24828 dojo.stopEvent(e);
24829 },
24830
24831 _onExpandoClick: function(/*Object*/ message){
24832 // summary:
24833 // User clicked the +/- icon; expand or collapse my children.
24834 var node = message.node;
24835
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);
24840
24841 if(node.isExpanded){
24842 this._collapseNode(node);
24843 }else{
24844 this._expandNode(node);
24845 }
24846 },
24847
24848 onClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){
24849 // summary:
24850 // Callback when a tree node is clicked
24851 // tags:
24852 // callback
24853 },
24854 onDblClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){
24855 // summary:
24856 // Callback when a tree node is double-clicked
24857 // tags:
24858 // callback
24859 },
24860 onOpen: function(/* dojo.data */ item, /*TreeNode*/ node){
24861 // summary:
24862 // Callback when a node is opened
24863 // tags:
24864 // callback
24865 },
24866 onClose: function(/* dojo.data */ item, /*TreeNode*/ node){
24867 // summary:
24868 // Callback when a node is closed
24869 // tags:
24870 // callback
24871 },
24872
24873 _getNextNode: function(node){
24874 // summary:
24875 // Get next visible node
24876
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
24880 }else{
24881 // find a parent node with a sibling
24882 while(node && node.isTreeNode){
24883 var returnNode = node.getNextSibling();
24884 if(returnNode){
24885 return returnNode; // _TreeNode
24886 }
24887 node = node.getParent();
24888 }
24889 return null;
24890 }
24891 },
24892
24893 _getRootOrFirstNode: function(){
24894 // summary:
24895 // Get first visible node
24896 return this.showRoot ? this.rootNode : this.rootNode.getChildren()[0];
24897 },
24898
24899 _collapseNode: function(/*_TreeNode*/ node){
24900 // summary:
24901 // Called when the user has requested to collapse the node
24902
24903 if(node._expandNodeDeferred){
24904 delete node._expandNodeDeferred;
24905 }
24906
24907 if(node.isExpandable){
24908 if(node.state == "LOADING"){
24909 // ignore clicks while we are in the process of loading data
24910 return;
24911 }
24912
24913 node.collapse();
24914 this.onClose(node.item, node);
24915
24916 if(node.item){
24917 this._state(node.item,false);
24918 this._saveState();
24919 }
24920 }
24921 },
24922
24923 _expandNode: function(/*_TreeNode*/ node, /*Boolean?*/ recursive){
24924 // summary:
24925 // Called when the user has requested to expand the node
24926 // recursive:
24927 // Internal flag used when _expandNode() calls itself, don't set.
24928 // returns:
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
24931
24932 if(node._expandNodeDeferred && !recursive){
24933 // there's already an expand in progress (or completed), so just return
24934 return node._expandNodeDeferred; // dojo.Deferred
24935 }
24936
24937 var model = this.model,
24938 item = node.item,
24939 _this = this;
24940
24941 switch(node.state){
24942 case "UNCHECKED":
24943 // need to load all the children, and then expand
24944 node.markProcessing();
24945
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());
24949
24950 // Get the children
24951 model.getChildren(
24952 item,
24953 function(items){
24954 node.unmarkProcessing();
24955
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);
24959
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);
24964
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(){
24969 def.callback();
24970 })
24971 });
24972 },
24973 function(err){
24974 console.error(_this, ": error loading root children: ", err);
24975 }
24976 );
24977 break;
24978
24979 default: // "LOADED"
24980 // data is already loaded; just expand node
24981 def = (node._expandNodeDeferred = node.expand());
24982
24983 this.onOpen(node.item, node);
24984
24985 if(item){
24986 this._state(item, true);
24987 this._saveState();
24988 }
24989 }
24990
24991 return def; // dojo.Deferred
24992 },
24993
24994 ////////////////// Miscellaneous functions ////////////////
24995
24996 focusNode: function(/* _tree.Node */ node){
24997 // summary:
24998 // Focus on the specified node (which must be visible)
24999 // tags:
25000 // protected
25001
25002 // set focus so that the label will be voiced using screen readers
25003 dijit.focus(node.labelNode);
25004 },
25005
25006 _onNodeFocus: function(/*dijit._Widget*/ node){
25007 // summary:
25008 // Called when a TreeNode gets focus, either by user clicking
25009 // it, or programatically by arrow key handling code.
25010 // description:
25011 // It marks that the current node is the selected one, and the previously
25012 // selected node no longer is.
25013
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);
25018 }
25019
25020 // mark that the new node is the currently selected one
25021 node.setFocusable(true);
25022 this.lastFocused = node;
25023 }
25024 },
25025
25026 _onNodeMouseEnter: function(/*dijit._Widget*/ node){
25027 // summary:
25028 // Called when mouse is over a node (onmouseenter event),
25029 // this is monitored by the DND code
25030 },
25031
25032 _onNodeMouseLeave: function(/*dijit._Widget*/ node){
25033 // summary:
25034 // Called when mouse leaves a node (onmouseleave event),
25035 // this is monitored by the DND code
25036 },
25037
25038 //////////////// Events from the model //////////////////////////
25039
25040 _onItemChange: function(/*Item*/ item){
25041 // summary:
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];
25046
25047 if(nodes){
25048 var label = this.getLabel(item),
25049 tooltip = this.getTooltip(item);
25050 dojo.forEach(nodes, function(node){
25051 node.set({
25052 item: item, // theoretically could be new JS Object representing same item
25053 label: label,
25054 tooltip: tooltip
25055 });
25056 node._updateItemClasses(item);
25057 });
25058 }
25059 },
25060
25061 _onItemChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
25062 // summary:
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];
25067
25068 if(parentNodes){
25069 dojo.forEach(parentNodes,function(parentNode){
25070 parentNode.setChildItems(newChildrenList);
25071 });
25072 }
25073 },
25074
25075 _onItemDelete: function(/*Item*/ item){
25076 // summary:
25077 // Processes notification of a deletion of an item
25078 var model = this.model,
25079 identity = model.getIdentity(item),
25080 nodes = this._itemNodesMap[identity];
25081
25082 if(nodes){
25083 dojo.forEach(nodes,function(node){
25084 // Remove node from set of selected nodes (if it's selected)
25085 this.dndController.removeTreeNode(node);
25086
25087 var parent = node.getParent();
25088 if(parent){
25089 // if node has not already been orphaned from a _onSetItem(parent, "children", ..) call...
25090 parent.removeChild(node);
25091 }
25092 node.destroyRecursive();
25093 }, this);
25094 delete this._itemNodesMap[identity];
25095 }
25096 },
25097
25098 /////////////// Miscellaneous funcs
25099
25100 _initState: function(){
25101 // summary:
25102 // Load in which nodes should be opened automatically
25103 if(this.persist){
25104 var cookie = dojo.cookie(this.cookieName);
25105 this._openedItemIds = {};
25106 if(cookie){
25107 dojo.forEach(cookie.split(','), function(item){
25108 this._openedItemIds[item] = true;
25109 }, this);
25110 }
25111 }
25112 },
25113 _state: function(item,expanded){
25114 // summary:
25115 // Query or set expanded state for an item,
25116 if(!this.persist){
25117 return false;
25118 }
25119 var id=this.model.getIdentity(item);
25120 if(arguments.length === 1){
25121 return this._openedItemIds[id];
25122 }
25123 if(expanded){
25124 this._openedItemIds[id] = true;
25125 }else{
25126 delete this._openedItemIds[id];
25127 }
25128 },
25129 _saveState: function(){
25130 // summary:
25131 // Create and save a cookie with the currently expanded nodes identifiers
25132 if(!this.persist){
25133 return;
25134 }
25135 var ary = [];
25136 for(var id in this._openedItemIds){
25137 ary.push(id);
25138 }
25139 dojo.cookie(this.cookieName, ary.join(","), {expires:365});
25140 },
25141
25142 destroy: function(){
25143 if(this._curSearch){
25144 clearTimeout(this._curSearch.timer);
25145 delete this._curSearch;
25146 }
25147 if(this.rootNode){
25148 this.rootNode.destroyRecursive();
25149 }
25150 if(this.dndController && !dojo.isString(this.dndController)){
25151 this.dndController.destroy();
25152 }
25153 this.rootNode = null;
25154 this.inherited(arguments);
25155 },
25156
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.
25160 this.destroy();
25161 },
25162
25163 resize: function(changeSize){
25164 if(changeSize){
25165 dojo.marginBox(this.domNode, changeSize);
25166 }
25167
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;
25172
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);
25176 }
25177 },
25178
25179 _createTreeNode: function(/*Object*/ args){
25180 // summary:
25181 // creates a TreeNode
25182 // description:
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);
25188 }
25189 });
25190
25191 // For back-compat. TODO: remove in 2.0
25192
25193 }
25194
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");
25198
25199
25200
25201 dojo.declare("dojo.dnd.Avatar", null, {
25202 // summary:
25203 // Object that represents transferred DnD items visually
25204 // manager: Object
25205 // a DnD manager object
25206
25207 constructor: function(manager){
25208 this.manager = manager;
25209 this.construct();
25210 },
25211
25212 // methods
25213 construct: function(){
25214 // summary:
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",
25220 style: {
25221 position: "absolute",
25222 zIndex: "1999",
25223 margin: "0px"
25224 }
25225 }),
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", {
25231 id : "a11yIcon",
25232 innerHTML : this.manager.copy ? '+' : "<"
25233 }, td) : null,
25234 span = dojo.create("span", {
25235 innerHTML: source.generateText ? this._generateText() : ""
25236 }, td),
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
25239 dojo.attr(tr, {
25240 "class": "dojoDndAvatarHeader",
25241 style: {opacity: 0.9}
25242 });
25243 for(; i < k; ++i){
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;
25247 }else{
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);
25255 node = table;
25256 }
25257 }
25258 node.id = "";
25259 tr = dojo.create("tr", null, b);
25260 td = dojo.create("td", null, tr);
25261 td.appendChild(node);
25262 dojo.attr(tr, {
25263 "class": "dojoDndAvatarItem",
25264 style: {opacity: (9 - i) / 10}
25265 });
25266 }
25267 this.node = a;
25268 },
25269 destroy: function(){
25270 // summary:
25271 // destructor for the avatar; called to remove all references so it can be garbage-collected
25272 dojo.destroy(this.node);
25273 this.node = false;
25274 },
25275 update: function(){
25276 // summary:
25277 // updates the avatar to reflect the current DnD state
25278 dojo[(this.manager.canDropFlag ? "add" : "remove") + "Class"](this.node, "dojoDndAvatarCanDrop");
25279 if (this.isA11y){
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
25288 }
25289 icon.innerHTML=text;
25290 }
25291 // replace text
25292 dojo.query(("tr.dojoDndAvatarHeader td span" +(this.isA11y ? " span" : "")), this.node).forEach(
25293 function(node){
25294 node.innerHTML = this._generateText();
25295 }, this);
25296 },
25297 _generateText: function(){
25298 // summary: generates a proper text to reflect copying or moving of items
25299 return this.manager.nodes.length.toString();
25300 }
25301 });
25302
25303 }
25304
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");
25308
25309
25310
25311
25312
25313 dojo.declare("dojo.dnd.Manager", null, {
25314 // summary:
25315 // the manager of DnD operations (usually a singleton)
25316 constructor: function(){
25317 this.avatar = null;
25318 this.source = null;
25319 this.nodes = [];
25320 this.copy = true;
25321 this.target = null;
25322 this.canDropFlag = false;
25323 this.events = [];
25324 },
25325
25326 // avatar's offset from the mouse
25327 OFFSET_X: 16,
25328 OFFSET_Y: 16,
25329
25330 // methods
25331 overSource: function(source){
25332 // summary:
25333 // called when a source detected a mouse-over condition
25334 // source: Object
25335 // the reporter
25336 if(this.avatar){
25337 this.target = (source && source.targetState != "Disabled") ? source : null;
25338 this.canDropFlag = Boolean(this.target);
25339 this.avatar.update();
25340 }
25341 dojo.publish("/dnd/source/over", [source]);
25342 },
25343 outSource: function(source){
25344 // summary:
25345 // called when a source detected a mouse-out condition
25346 // source: Object
25347 // the reporter
25348 if(this.avatar){
25349 if(this.target == source){
25350 this.target = null;
25351 this.canDropFlag = false;
25352 this.avatar.update();
25353 dojo.publish("/dnd/source/over", [null]);
25354 }
25355 }else{
25356 dojo.publish("/dnd/source/over", [null]);
25357 }
25358 },
25359 startDrag: function(source, nodes, copy){
25360 // summary:
25361 // called to initiate the DnD operation
25362 // source: Object
25363 // the source which provides items
25364 // nodes: Array
25365 // the list of transferred items
25366 // copy: Boolean
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]);
25374 this.events = [
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)
25382 ];
25383 var c = "dojoDnd" + (copy ? "Copy" : "Move");
25384 dojo.addClass(dojo.body(), c);
25385 },
25386 canDrop: function(flag){
25387 // summary:
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();
25393 }
25394 },
25395 stopDrag: function(){
25396 // summary:
25397 // stop the DnD in progress
25398 dojo.removeClass(dojo.body(), ["dojoDndCopy", "dojoDndMove"]);
25399 dojo.forEach(this.events, dojo.disconnect);
25400 this.events = [];
25401 this.avatar.destroy();
25402 this.avatar = null;
25403 this.source = this.target = null;
25404 this.nodes = [];
25405 },
25406 makeAvatar: function(){
25407 // summary:
25408 // makes the avatar; it is separate to be overwritten dynamically, if needed
25409 return new dojo.dnd.Avatar(this);
25410 },
25411 updateAvatar: function(){
25412 // summary:
25413 // updates the avatar; it is separate to be overwritten dynamically, if needed
25414 this.avatar.update();
25415 },
25416
25417 // mouse event processors
25418 onMouseMove: function(e){
25419 // summary:
25420 // event processor for onmousemove
25421 // e: Event
25422 // mouse event
25423 var a = this.avatar;
25424 if(a){
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);
25433 }
25434 }
25435 },
25436 onMouseUp: function(e){
25437 // summary:
25438 // event processor for onmouseup
25439 // e: Event
25440 // mouse event
25441 if(this.avatar){
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);
25447 }else{
25448 dojo.publish("/dnd/cancel");
25449 }
25450 this.stopDrag();
25451 }
25452 },
25453
25454 // keyboard event processors
25455 onKeyDown: function(e){
25456 // summary:
25457 // event processor for onkeydown:
25458 // watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag
25459 // e: Event
25460 // keyboard event
25461 if(this.avatar){
25462 switch(e.keyCode){
25463 case dojo.keys.CTRL:
25464 var copy = Boolean(this.source.copyState(true));
25465 if(this.copy != copy){
25466 this._setCopyStatus(copy);
25467 }
25468 break;
25469 case dojo.keys.ESCAPE:
25470 dojo.publish("/dnd/cancel");
25471 this.stopDrag();
25472 break;
25473 }
25474 }
25475 },
25476 onKeyUp: function(e){
25477 // summary:
25478 // event processor for onkeyup, watching for CTRL for copy/move status
25479 // e: Event
25480 // keyboard event
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);
25485 }
25486 }
25487 },
25488
25489 // utilities
25490 _setCopyStatus: function(copy){
25491 // summary:
25492 // changes the copy status
25493 // copy: Boolean
25494 // the copy status
25495 this.copy = copy;
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"));
25501 }
25502 });
25503
25504 // dojo.dnd._manager:
25505 // The manager singleton variable. Can be overwritten if needed.
25506 dojo.dnd._manager = null;
25507
25508 dojo.dnd.manager = function(){
25509 // summary:
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();
25513 }
25514 return dojo.dnd._manager; // Object
25515 };
25516
25517 }
25518
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");
25522
25523
25524
25525
25526 /*=====
25527 dijit.tree.__SourceArgs = function(){
25528 // summary:
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;
25547 }
25548 =====*/
25549
25550 dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
25551 // summary:
25552 // Handles drag and drop operations (as a source or a target) for `dijit.Tree`
25553
25554 // isSource: [private] Boolean
25555 // Can be used as a DnD source.
25556 isSource: true,
25557
25558 // accept: String[]
25559 // List of accepted types (text strings) for the Tree; defaults to
25560 // ["text"]
25561 accept: ["text", "treeNode"],
25562
25563 // copyOnly: [private] Boolean
25564 // Copy items, if true, use a state of Ctrl key otherwise
25565 copyOnly: false,
25566
25567 // dragThreshold: Number
25568 // The move delay in pixels before detecting a drag; 5 by default
25569 dragThreshold: 5,
25570
25571 // betweenThreshold: Integer
25572 // Distance from upper/lower edge of node to allow drop to reorder nodes
25573 betweenThreshold: 0,
25574
25575 constructor: function(/*dijit.Tree*/ tree, /*dijit.tree.__SourceArgs*/ params){
25576 // summary:
25577 // a constructor of the Tree DnD Source
25578 // tags:
25579 // private
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;
25585 if(type.length){
25586 this.accept = {};
25587 for(var i = 0; i < type.length; ++i){
25588 this.accept[type[i]] = 1;
25589 }
25590 }
25591
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
25598 this._lastX = 0;
25599 this._lastY = 0;
25600
25601 // states
25602 this.sourceState = "";
25603 if(this.isSource){
25604 dojo.addClass(this.node, "dojoDndSource");
25605 }
25606 this.targetState = "";
25607 if(this.accept){
25608 dojo.addClass(this.node, "dojoDndTarget");
25609 }
25610
25611 // set up events
25612 this.topics = [
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")
25617 ];
25618 },
25619
25620 // methods
25621 checkAcceptance: function(source, nodes){
25622 // summary:
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.
25629 // tags:
25630 // extension
25631 return true; // Boolean
25632 },
25633
25634 copyState: function(keyPressed){
25635 // summary:
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
25640 // tags:
25641 // protected
25642 return this.copyOnly || keyPressed; // Boolean
25643 },
25644 destroy: function(){
25645 // summary:
25646 // Prepares the object to be garbage-collected.
25647 this.inherited("destroy",arguments);
25648 dojo.forEach(this.topics, dojo.unsubscribe);
25649 this.targetAnchor = null;
25650 },
25651
25652 _onDragMouse: function(e){
25653 // summary:
25654 // Helper method for processing onmousemove/onmouseover events while drag is in progress.
25655 // Keeps track of current drop target.
25656
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)
25661
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);
25669 }
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";
25674 }
25675 }
25676
25677 if(newTarget != oldTarget || newDropPosition != oldDropPosition){
25678 if(oldTarget){
25679 this._removeItemClass(oldTarget.rowNode, oldDropPosition);
25680 }
25681 if(newTarget){
25682 this._addItemClass(newTarget.rowNode, newDropPosition);
25683 }
25684
25685 // Check if it's ok to drop the dragged node on/before/after the target node.
25686 if(!newTarget){
25687 m.canDrop(false);
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)
25690 m.canDrop(false);
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)
25693 m.canDrop(false);
25694 }else if(this.checkItemAcceptance(newTarget.rowNode, m.source, newDropPosition.toLowerCase())
25695 && !this._isParentChildDrop(m.source, newTarget.rowNode)){
25696 m.canDrop(true);
25697 }else{
25698 m.canDrop(false);
25699 }
25700
25701 this.targetAnchor = newTarget;
25702 this.dropPosition = newDropPosition;
25703 }
25704 },
25705
25706 onMouseMove: function(e){
25707 // summary:
25708 // Called for any onmousemove events over the Tree
25709 // e: Event
25710 // onmousemouse event
25711 // tags:
25712 // private
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);
25718 }else{
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();
25722 if(nodes.length){
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
25729 continue nextitem;
25730 }
25731 }
25732 //this node does not have any ancestors selected, add it
25733 r.push(n);
25734 }
25735 nodes = r;
25736 }
25737 nodes = dojo.map(nodes, function(n){return n.domNode});
25738 m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e)));
25739 }
25740 }
25741 }
25742 },
25743
25744 onMouseDown: function(e){
25745 // summary:
25746 // Event processor for onmousedown
25747 // e: Event
25748 // onmousedown event
25749 // tags:
25750 // private
25751 this.mouseDown = true;
25752 this.mouseButton = e.button;
25753 this._lastX = e.pageX;
25754 this._lastY = e.pageY;
25755 this.inherited(arguments);
25756 },
25757
25758 onMouseUp: function(e){
25759 // summary:
25760 // Event processor for onmouseup
25761 // e: Event
25762 // onmouseup event
25763 // tags:
25764 // private
25765 if(this.mouseDown){
25766 this.mouseDown = false;
25767 this.inherited(arguments);
25768 }
25769 },
25770
25771 onMouseOut: function(){
25772 // summary:
25773 // Event processor for when mouse is moved away from a TreeNode
25774 // tags:
25775 // private
25776 this.inherited(arguments);
25777 this._unmarkTargetAnchor();
25778 },
25779
25780 checkItemAcceptance: function(target, source, position){
25781 // summary:
25782 // Stub function to be overridden if one wants to check for the ability to drop at the node/item level
25783 // description:
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.
25787 // target: DOMNode
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"
25794 // tags:
25795 // extension
25796 return true;
25797 },
25798
25799 // topic event processors
25800 onDndSourceOver: function(source){
25801 // summary:
25802 // Topic event processor for /dnd/source/over, called when detected a current source.
25803 // source: Object
25804 // The dijit.tree.dndSource / dojo.dnd.Source which has the mouse over it
25805 // tags:
25806 // private
25807 if(this != source){
25808 this.mouseDown = false;
25809 this._unmarkTargetAnchor();
25810 }else if(this.isDragging){
25811 var m = dojo.dnd.manager();
25812 m.canDrop(false);
25813 }
25814 },
25815 onDndStart: function(source, nodes, copy){
25816 // summary:
25817 // Topic event processor for /dnd/start, called to initiate the DnD operation
25818 // source: Object
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
25822 // copy: Boolean
25823 // Copy items, if true, move items otherwise
25824 // tags:
25825 // private
25826
25827 if(this.isSource){
25828 this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
25829 }
25830 var accepted = this.checkAcceptance(source, nodes);
25831
25832 this._changeState("Target", accepted ? "" : "Disabled");
25833
25834 if(this == source){
25835 dojo.dnd.manager().overSource(this);
25836 }
25837
25838 this.isDragging = true;
25839 },
25840
25841 itemCreator: function(/*DomNode[]*/ nodes, target, /*dojo.dnd.Source*/ source){
25842 // summary:
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.
25847 // description:
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:
25852 // | [
25853 // | { id: 123, label: "apple", foo: "bar" },
25854 // | { id: 456, label: "pear", zaz: "bam" }
25855 // | ]
25856 // tags:
25857 // extension
25858
25859 // TODO: for 2.0 refactor so itemCreator() is called once per drag node, and
25860 // make signature itemCreator(sourceItem, node, target) (or similar).
25861
25862 return dojo.map(nodes, function(node){
25863 return {
25864 "id": node.id,
25865 "name": node.textContent || node.innerText || ""
25866 };
25867 }); // Object[]
25868 },
25869
25870 onDndDrop: function(source, nodes, copy){
25871 // summary:
25872 // Topic event processor for /dnd/drop, called to finish the DnD operation.
25873 // description:
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.
25876 // source: Object
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
25880 // copy: Boolean
25881 // Copy items, if true, move items otherwise
25882 // tags:
25883 // protected
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
25889
25890 this.isDragging = false;
25891
25892 // Compute the new parent item
25893 var targetWidget = target;
25894 var newParentItem;
25895 var insertIndex;
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;
25905 }
25906 }else{
25907 newParentItem = (targetWidget && targetWidget.item) || tree.item;
25908 }
25909
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;
25913
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);
25919
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;
25927 }
25928
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.
25934
25935 if(typeof insertIndex == "number"){
25936 if(newParentItem == oldParentItem && childTreeNode.getIndexInParent() < insertIndex){
25937 insertIndex -= 1;
25938 }
25939 }
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);
25945 }else{
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);
25950 }
25951
25952 // Create new item in the tree, based on the drag source.
25953 model.newItem(newItemsParams[idx], newParentItem, insertIndex);
25954 }
25955 }, this);
25956
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);
25960 }
25961 this.onDndCancel();
25962 },
25963
25964 onDndCancel: function(){
25965 // summary:
25966 // Topic event processor for /dnd/cancel, called to cancel the DnD operation
25967 // tags:
25968 // private
25969 this._unmarkTargetAnchor();
25970 this.isDragging = false;
25971 this.mouseDown = false;
25972 delete this.mouseButton;
25973 this._changeState("Source", "");
25974 this._changeState("Target", "");
25975 },
25976
25977 // When focus moves in/out of the entire Tree
25978 onOverEvent: function(){
25979 // summary:
25980 // This method is called when mouse is moved over our container (like onmouseenter)
25981 // tags:
25982 // private
25983 this.inherited(arguments);
25984 dojo.dnd.manager().overSource(this);
25985 },
25986 onOutEvent: function(){
25987 // summary:
25988 // This method is called when mouse is moved out of our container (like onmouseleave)
25989 // tags:
25990 // private
25991 this._unmarkTargetAnchor();
25992 var m = dojo.dnd.manager();
25993 if(this.isDragging){
25994 m.canDrop(false);
25995 }
25996 m.outSource(this);
25997
25998 this.inherited(arguments);
25999 },
26000
26001 _isParentChildDrop: function(source, targetRow){
26002 // summary:
26003 // Checks whether the dragged items are parent rows in the tree which are being
26004 // dragged into their own children.
26005 //
26006 // source:
26007 // The DragSource object.
26008 //
26009 // targetRow:
26010 // The tree row onto which the dragged nodes are being dropped.
26011 //
26012 // tags:
26013 // private
26014
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){
26018 return false;
26019 }
26020
26021
26022 var root = source.tree.domNode;
26023 var ids = source.selection;
26024
26025 var node = targetRow.parentNode;
26026
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;
26031 }
26032
26033 return node.id && ids[node.id];
26034 },
26035
26036 _unmarkTargetAnchor: function(){
26037 // summary:
26038 // Removes hover class of the current target anchor
26039 // tags:
26040 // private
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;
26046 },
26047
26048 _markDndStatus: function(copy){
26049 // summary:
26050 // Changes source's state based on "copy" status
26051 this._changeState("Source", copy ? "Copied" : "Moved");
26052 }
26053 });
26054
26055 }
26056
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");
26060
26061
26062
26063
26064
26065 dojo.declare("dojo.data.ItemFileReadStore", null,{
26066 // summary:
26067 // The ItemFileReadStore implements the dojo.data.api.Read API and reads
26068 // data from JSON files that have contents in this format --
26069 // { items: [
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'}
26073 // ]}
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.
26076 //
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:
26083 // {
26084 // type0: function || object,
26085 // type1: function || object,
26086 // ...
26087 // typeN: function || object
26088 // }
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:
26092 // {
26093 // type: function, //constructor.
26094 // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately.
26095 // }
26096
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;
26104 this.data = null;
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'] = {
26111 type: Date,
26112 deserialize: function(value){
26113 return dojo.date.stamp.fromISOString(value);
26114 }
26115 };
26116 }
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;
26127 }
26128 if(keywordParameters.hierarchical !== undefined){
26129 this.hierarchical = keywordParameters.hierarchical?true:false;
26130 }
26131 if(keywordParameters.clearOnClose){
26132 this.clearOnClose = true;
26133 }
26134 if("failOk" in keywordParameters){
26135 this.failOk = keywordParameters.failOk?true:false;
26136 }
26137 },
26138
26139 url: "", // use "" rather than undefined for the benefit of the parser (#3539)
26140
26141 //Internal var, crossCheckUrl. Used so that setting either url or _jsonFileUrl, can still trigger a reload
26142 //when clearOnClose and close is used.
26143 _ccUrl: "",
26144
26145 data: null, // define this so that the parser can populate it
26146
26147 typeMap: null, //Define so parser can populate.
26148
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,
26154
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,
26159
26160 //Parameter for specifying that it is OK for the xhrGet call to fail silently.
26161 failOk: false,
26162
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,
26169
26170 _assertIsItem: function(/* item */ item){
26171 // summary:
26172 // This function tests whether the item passed in is indeed an item in the store.
26173 // item:
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.");
26177 }
26178 },
26179
26180 _assertIsAttribute: function(/* attribute-name-string */ attribute){
26181 // summary:
26182 // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
26183 // attribute:
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.");
26187 }
26188 },
26189
26190 getValue: function( /* item */ item,
26191 /* attribute-name-string */ attribute,
26192 /* value? */ defaultValue){
26193 // summary:
26194 // See dojo.data.api.Read.getValue()
26195 var values = this.getValues(item, attribute);
26196 return (values.length > 0)?values[0]:defaultValue; // mixed
26197 },
26198
26199 getValues: function(/* item */ item,
26200 /* attribute-name-string */ attribute){
26201 // summary:
26202 // See dojo.data.api.Read.getValues()
26203
26204 this._assertIsItem(item);
26205 this._assertIsAttribute(attribute);
26206 // Clone it before returning. refs: #10474
26207 return (item[attribute] || []).slice(0); // Array
26208 },
26209
26210 getAttributes: function(/* item */ item){
26211 // summary:
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);
26219 }
26220 }
26221 return attributes; // Array
26222 },
26223
26224 hasAttribute: function( /* item */ item,
26225 /* attribute-name-string */ attribute){
26226 // summary:
26227 // See dojo.data.api.Read.hasAttribute()
26228 this._assertIsItem(item);
26229 this._assertIsAttribute(attribute);
26230 return (attribute in item);
26231 },
26232
26233 containsValue: function(/* item */ item,
26234 /* attribute-name-string */ attribute,
26235 /* anything */ value){
26236 // summary:
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);
26241 }
26242 return this._containsValue(item, attribute, value, regexp); //boolean.
26243 },
26244
26245 _containsValue: function( /* item */ item,
26246 /* attribute-name-string */ attribute,
26247 /* anything */ value,
26248 /* RegExp?*/ regexp){
26249 // summary:
26250 // Internal function for looking at the values contained by the item.
26251 // description:
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)
26255 //
26256 // item:
26257 // The data item to examine for attribute values.
26258 // attribute:
26259 // The attribute to inspect.
26260 // value:
26261 // The value to match.
26262 // regexp:
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
26269 }
26270 }else if(value === possibleValue){
26271 return true; // Boolean
26272 }
26273 });
26274 },
26275
26276 isItem: function(/* anything */ something){
26277 // summary:
26278 // See dojo.data.api.Read.isItem()
26279 if(something && something[this._storeRefPropName] === this){
26280 if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){
26281 return true;
26282 }
26283 }
26284 return false; // Boolean
26285 },
26286
26287 isItemLoaded: function(/* anything */ something){
26288 // summary:
26289 // See dojo.data.api.Read.isItemLoaded()
26290 return this.isItem(something); //boolean
26291 },
26292
26293 loadItem: function(/* object */ keywordArgs){
26294 // summary:
26295 // See dojo.data.api.Read.loadItem()
26296 this._assertIsItem(keywordArgs.item);
26297 },
26298
26299 getFeatures: function(){
26300 // summary:
26301 // See dojo.data.api.Read.getFeatures()
26302 return this._features; //Object
26303 },
26304
26305 getLabel: function(/* item */ item){
26306 // summary:
26307 // See dojo.data.api.Read.getLabel()
26308 if(this._labelAttr && this.isItem(item)){
26309 return this.getValue(item,this._labelAttr); //String
26310 }
26311 return undefined; //undefined
26312 },
26313
26314 getLabelAttributes: function(/* item */ item){
26315 // summary:
26316 // See dojo.data.api.Read.getLabelAttributes()
26317 if(this._labelAttr){
26318 return [this._labelAttr]; //array
26319 }
26320 return null; //null
26321 },
26322
26323 _fetchItems: function( /* Object */ keywordArgs,
26324 /* Function */ findCallback,
26325 /* Function */ errorCallback){
26326 // summary:
26327 // See dojo.data.util.simpleFetch.fetch()
26328 var self = this,
26329 filter = function(requestArgs, arrayOfItems){
26330 var items = [],
26331 i, key;
26332 if(requestArgs.query){
26333 var value,
26334 ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false;
26335
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;
26345 }
26346 }
26347 for(i = 0; i < arrayOfItems.length; ++i){
26348 var match = true;
26349 var candidateItem = arrayOfItems[i];
26350 if(candidateItem === null){
26351 match = false;
26352 }else{
26353 for(key in requestArgs.query){
26354 value = requestArgs.query[key];
26355 if(!self._containsValue(candidateItem, key, value, regexpList[key])){
26356 match = false;
26357 }
26358 }
26359 }
26360 if(match){
26361 items.push(candidateItem);
26362 }
26363 }
26364 findCallback(items, requestArgs);
26365 }else{
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];
26373 if(item !== null){
26374 items.push(item);
26375 }
26376 }
26377 findCallback(items, requestArgs);
26378 }
26379 };
26380
26381 if(this._loadFinished){
26382 filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions));
26383 }else{
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
26389 //private.
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;
26399 }
26400
26401 //See if there was any forced reset of data.
26402 if(this.data != null){
26403 this._jsonData = this.data;
26404 this.data = null;
26405 }
26406
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});
26413 }else{
26414 this._loadInProgress = true;
26415 var getArgs = {
26416 url: self._jsonFileUrl,
26417 handleAs: "json-comment-optional",
26418 preventCache: this.urlPreventCache,
26419 failOk: this.failOk
26420 };
26421 var getHandler = dojo.xhrGet(getArgs);
26422 getHandler.addCallback(function(data){
26423 try{
26424 self._getItemsFromLoadedData(data);
26425 self._loadFinished = true;
26426 self._loadInProgress = false;
26427
26428 filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions));
26429 self._handleQueuedFetches();
26430 }catch(e){
26431 self._loadFinished = true;
26432 self._loadInProgress = false;
26433 errorCallback(e, keywordArgs);
26434 }
26435 });
26436 getHandler.addErrback(function(error){
26437 self._loadInProgress = false;
26438 errorCallback(error, keywordArgs);
26439 });
26440
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;
26448 }
26449 keywordArgs.abort = function(){
26450 var df = getHandler;
26451 if(df && df.fired === -1){
26452 df.cancel();
26453 df = null;
26454 }
26455 if(oldAbort){
26456 oldAbort.call(keywordArgs);
26457 }
26458 };
26459 }
26460 }else if(this._jsonData){
26461 try{
26462 this._loadFinished = true;
26463 this._getItemsFromLoadedData(this._jsonData);
26464 this._jsonData = null;
26465 filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions));
26466 }catch(e){
26467 errorCallback(e, keywordArgs);
26468 }
26469 }else{
26470 errorCallback(new Error("dojo.data.ItemFileReadStore: No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs);
26471 }
26472 }
26473 },
26474
26475 _handleQueuedFetches: function(){
26476 // summary:
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;
26484 if(delayedFilter){
26485 delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions));
26486 }else{
26487 this.fetchItemByIdentity(delayedQuery);
26488 }
26489 }
26490 this._queuedFetches = [];
26491 }
26492 },
26493
26494 _getItemsArray: function(/*object?*/queryOptions){
26495 // summary:
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;
26500 }
26501 return this._arrayOfTopLevelItems;
26502 },
26503
26504 close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
26505 // summary:
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.
26514
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");
26522 }
26523 this._arrayOfAllItems = [];
26524 this._arrayOfTopLevelItems = [];
26525 this._loadFinished = false;
26526 this._itemsByIdentity = null;
26527 this._loadInProgress = false;
26528 this._queuedFetches = [];
26529 }
26530 },
26531
26532 _getItemsFromLoadedData: function(/* Object */ dataObject){
26533 // summary:
26534 // Function to parse the loaded data into item format and build the internal items array.
26535 // description:
26536 // Function to parse the loaded data into item format and build the internal items array.
26537 //
26538 // dataObject:
26539 // The JS data object containing the raw data to convery into item format.
26540 //
26541 // returns: array
26542 // Array of items in store item format.
26543
26544 // First, we define a couple little utility functions...
26545 var addingArrays = false,
26546 self = this;
26547
26548 function valueIsAnItem(/* anything */ aValue){
26549 // summary:
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.
26553 // example:
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});
26562 var isItem = (
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") &&
26571 self.hierarchical
26572 );
26573 return isItem;
26574 }
26575
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);
26587 }
26588 }
26589 }else{
26590 if(valueIsAnItem(valueForAttribute)){
26591 addItemAndSubItemsToArrayOfAllItems(valueForAttribute);
26592 }
26593 }
26594 }
26595 }
26596 }
26597
26598 this._labelAttr = dataObject.label;
26599
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.
26603
26604 // Step 1: Walk through the object hierarchy and build a list of all items
26605 var i,
26606 item;
26607 this._arrayOfAllItems = [];
26608 this._arrayOfTopLevelItems = dataObject.items;
26609
26610 for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){
26611 item = this._arrayOfTopLevelItems[i];
26612 if(dojo.isArray(item)){
26613 addingArrays = true;
26614 }
26615 addItemAndSubItemsToArrayOfAllItems(item);
26616 item[this._rootItemPropName]=true;
26617 }
26618
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'}
26622 // into this:
26623 // { name:['Miss Piggy'], pets:['Foo-Foo']}
26624 //
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 = {},
26628 key;
26629
26630 for(i = 0; i < this._arrayOfAllItems.length; ++i){
26631 item = this._arrayOfAllItems[i];
26632 for(key in item){
26633 if(key !== this._rootItemPropName){
26634 var value = item[key];
26635 if(value !== null){
26636 if(!dojo.isArray(value)){
26637 item[key] = [value];
26638 }
26639 }else{
26640 item[key] = [null];
26641 }
26642 }
26643 allAttributeNames[key]=key;
26644 }
26645 }
26646
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 += "_";
26651 }
26652 while(allAttributeNames[this._itemNumPropName]){
26653 this._itemNumPropName += "_";
26654 }
26655 while(allAttributeNames[this._reverseRefMap]){
26656 this._reverseRefMap += "_";
26657 }
26658
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.
26663 var arrayOfValues;
26664
26665 var identifier = dataObject.identifier;
26666 if(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;
26675 }else{
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 + "]");
26680 }
26681 }
26682 }
26683 }else{
26684 this._features['dojo.data.api.Identity'] = Number;
26685 }
26686
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;
26693 }
26694
26695 // Step 6: We walk through all the attribute values of all the items,
26696 // looking for type/value literals and item-references.
26697 //
26698 // We replace item-references with pointers to items. For example, we change:
26699 // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
26700 // into this:
26701 // { name:['Kermit'], friends:[miss_piggy] }
26702 // (where miss_piggy is the object representing the 'Miss Piggy' item).
26703 //
26704 // We replace type/value pairs with typed-literals. For example, we change:
26705 // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'1918-07-18'}] }
26706 // into this:
26707 // { name:['Kermit'], born:(new Date(1918, 6, 18)) }
26708 //
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'}}] }
26712 for(key in item){
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)}}
26720 if(!mappingObj){
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);
26726 }else{
26727 throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function");
26728 }
26729 }
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);
26736 }else{
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],
26741 found = true;
26742 for(var refKey in referenceDescription){
26743 if(candidateItem[refKey] != referenceDescription[refKey]){
26744 found = false;
26745 }
26746 }
26747 if(found){
26748 arrayOfValues[j] = candidateItem;
26749 }
26750 }
26751 }
26752 if(this.referenceIntegrity){
26753 var refItem = arrayOfValues[j];
26754 if(this.isItem(refItem)){
26755 this._addReferenceToMap(refItem, item, key);
26756 }
26757 }
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);
26764 }
26765 }
26766 }
26767 }
26768 }
26769 }
26770 },
26771
26772 _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){
26773 // summary:
26774 // Method to add an reference map entry for an item and attribute.
26775 // description:
26776 // Method to add an reference map entry for an item and attribute. //
26777 // refItem:
26778 // The item that is referenced.
26779 // parentItem:
26780 // The item that holds the new reference to refItem.
26781 // attribute:
26782 // The attribute on parentItem that contains the new reference.
26783
26784 //Stub function, does nothing. Real processing is in ItemFileWriteStore.
26785 },
26786
26787 getIdentity: function(/* item */ item){
26788 // summary:
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
26793 }else{
26794 var arrayOfValues = item[identifier];
26795 if(arrayOfValues){
26796 return arrayOfValues[0]; // Object || String
26797 }
26798 }
26799 return null; // null
26800 },
26801
26802 fetchItemByIdentity: function(/* Object */ keywordArgs){
26803 // summary:
26804 // See dojo.data.api.Identity.fetchItemByIdentity()
26805
26806 // Hasn't loaded yet, we have to trigger the load.
26807 var item,
26808 scope;
26809 if(!this._loadFinished){
26810 var self = this;
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
26816 //private.
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;
26826 }
26827
26828 //See if there was any forced reset of data.
26829 if(this.data != null && this._jsonData == null){
26830 this._jsonData = this.data;
26831 this.data = null;
26832 }
26833
26834 if(this._jsonFileUrl){
26835
26836 if(this._loadInProgress){
26837 this._queuedFetches.push({args: keywordArgs});
26838 }else{
26839 this._loadInProgress = true;
26840 var getArgs = {
26841 url: self._jsonFileUrl,
26842 handleAs: "json-comment-optional",
26843 preventCache: this.urlPreventCache,
26844 failOk: this.failOk
26845 };
26846 var getHandler = dojo.xhrGet(getArgs);
26847 getHandler.addCallback(function(data){
26848 var scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
26849 try{
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);
26856 }
26857 self._handleQueuedFetches();
26858 }catch(error){
26859 self._loadInProgress = false;
26860 if(keywordArgs.onError){
26861 keywordArgs.onError.call(scope, error);
26862 }
26863 }
26864 });
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);
26870 }
26871 });
26872 }
26873
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);
26883 }
26884 }
26885 }else{
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);
26891 }
26892 }
26893 },
26894
26895 _getItemByIdentity: function(/* Object */ identity){
26896 // summary:
26897 // Internal function to look an item up by its identity map.
26898 var item = null;
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];
26904 }
26905 if(item === undefined){
26906 item = null;
26907 }
26908 return item; // Object
26909 },
26910
26911 getIdentityAttributes: function(/* item */ item){
26912 // summary:
26913 // See dojo.data.api.Identity.getIdentityAttributes()
26914
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
26920 // of attributes
26921 return null; // null
26922 }else{
26923 return [identifier]; // Array
26924 }
26925 },
26926
26927 _forceLoad: function(){
26928 // summary:
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.
26931 var self = this;
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
26937 //private.
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;
26947 }
26948
26949 //See if there was any forced reset of data.
26950 if(this.data != null){
26951 this._jsonData = this.data;
26952 this.data = null;
26953 }
26954
26955 if(this._jsonFileUrl){
26956 var getArgs = {
26957 url: this._jsonFileUrl,
26958 handleAs: "json-comment-optional",
26959 preventCache: this.urlPreventCache,
26960 failOk: this.failOk,
26961 sync: true
26962 };
26963 var getHandler = dojo.xhrGet(getArgs);
26964 getHandler.addCallback(function(data){
26965 try{
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.");
26980 }
26981 }catch(e){
26982 console.log(e);
26983 throw e;
26984 }
26985 });
26986 getHandler.addErrback(function(error){
26987 throw error;
26988 });
26989 }else if(this._jsonData){
26990 self._getItemsFromLoadedData(self._jsonData);
26991 self._jsonData = null;
26992 self._loadFinished = true;
26993 }
26994 }
26995 });
26996 //Mix in the simple fetch implementation to this class.
26997 dojo.extend(dojo.data.ItemFileReadStore,dojo.data.util.simpleFetch);
26998
26999 }
27000
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");
27004
27005
27006
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:
27011 // {
27012 // type0: function || object,
27013 // type1: function || object,
27014 // ...
27015 // typeN: function || object
27016 // }
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:
27021 // {
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.
27025 // }
27026
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;
27030
27031 // For keeping track of changes so that we can implement isDirty and revert
27032 this._pending = {
27033 _newItems:{},
27034 _modifiedItems:{},
27035 _deletedItems:{}
27036 };
27037
27038 if(!this._datatypeMap['Date'].serialize){
27039 this._datatypeMap['Date'].serialize = function(obj){
27040 return dojo.date.stamp.toISOString(obj, {zulu:true});
27041 };
27042 }
27043 //Disable only if explicitly set to false.
27044 if(keywordParameters && (keywordParameters.referenceIntegrity === false)){
27045 this.referenceIntegrity = false;
27046 }
27047
27048 // this._saveInProgress is set to true, briefly, from when save() is first called to when it completes
27049 this._saveInProgress = false;
27050 },
27051
27052 referenceIntegrity: true, //Flag that defaultly enabled reference integrity tracking. This way it can also be disabled pogrammatially or declaratively.
27053
27054 _assert: function(/* boolean */ condition){
27055 if(!condition){
27056 throw new Error("assertion failed in ItemFileWriteStore");
27057 }
27058 },
27059
27060 _getIdentifierAttribute: function(){
27061 var identifierAttribute = this.getFeatures()['dojo.data.api.Identity'];
27062 // this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute)));
27063 return identifierAttribute;
27064 },
27065
27066
27067 /* dojo.data.api.Write */
27068
27069 newItem: function(/* Object? */ keywordArgs, /* Object? */ parentInfo){
27070 // summary: See dojo.data.api.Write.newItem()
27071
27072 this._assert(!this._saveInProgress);
27073
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.
27077 this._forceLoad();
27078 }
27079
27080 if(typeof keywordArgs != "object" && typeof keywordArgs != "undefined"){
27081 throw new Error("newItem() was passed something other than an object");
27082 }
27083 var newIdentity = null;
27084 var identifierAttribute = this._getIdentifierAttribute();
27085 if(identifierAttribute === Number){
27086 newIdentity = this._arrayOfAllItems.length;
27087 }else{
27088 newIdentity = keywordArgs[identifierAttribute];
27089 if(typeof newIdentity === "undefined"){
27090 throw new Error("newItem() was not passed an identity for the new item");
27091 }
27092 if(dojo.isArray(newIdentity)){
27093 throw new Error("newItem() was not passed an single-valued identity");
27094 }
27095 }
27096
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");
27102 }
27103 this._assert(typeof this._pending._newItems[newIdentity] === "undefined");
27104 this._assert(typeof this._pending._deletedItems[newIdentity] === "undefined");
27105
27106 var newItem = {};
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];
27114 }
27115 this._arrayOfAllItems.push(newItem);
27116
27117 //We need to construct some data for the onNew call too...
27118 var pInfo = null;
27119
27120 // Now we need to check to see where we want to assign this thingm if any.
27121 if(parentInfo && parentInfo.parent && parentInfo.attribute){
27122 pInfo = {
27123 item: parentInfo.parent,
27124 attribute: parentInfo.attribute,
27125 oldValue: undefined
27126 };
27127
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];
27136 }else{
27137 pInfo.oldValue = values.slice(0, values.length);
27138 }
27139 tempValues.push(newItem);
27140 this._setValueOrValues(parentInfo.parent, parentInfo.attribute, tempValues, false);
27141 pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute);
27142 }else{
27143 this._setValueOrValues(parentInfo.parent, parentInfo.attribute, newItem, false);
27144 pInfo.newValue = newItem;
27145 }
27146 }else{
27147 //Toplevel item, add to both top list as well as all list.
27148 newItem[this._rootItemPropName]=true;
27149 this._arrayOfTopLevelItems.push(newItem);
27150 }
27151
27152 this._pending._newItems[newIdentity] = newItem;
27153
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");
27170 }
27171 var value = keywordArgs[key];
27172 if(!dojo.isArray(value)){
27173 value = [value];
27174 }
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);
27181 }
27182 }
27183 }
27184 }
27185 this.onNew(newItem, pInfo); // dojo.data.api.Notification call
27186 return newItem; // item
27187 },
27188
27189 _removeArrayElement: function(/* Array */ array, /* anything */ element){
27190 var index = dojo.indexOf(array, element);
27191 if(index != -1){
27192 array.splice(index, 1);
27193 return true;
27194 }
27195 return false;
27196 },
27197
27198 deleteItem: function(/* item */ item){
27199 // summary: See dojo.data.api.Write.deleteItem()
27200 this._assert(!this._saveInProgress);
27201 this._assertIsItem(item);
27202
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);
27208
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.
27213
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);
27217
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]);
27221 }
27222
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] = [];
27233 }
27234 item["backupRefs_" + this._reverseRefMap].push({id: this.getIdentity(value), attr: attribute});
27235 this._removeReferenceFromMap(value, item, attribute);
27236 }
27237 }, this);
27238 }, this);
27239
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];
27242 if(references){
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];
27248 }else{
27249 containingItem = this._arrayOfAllItems[itemId];
27250 }
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);
27258 }, this);
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);
27263 }
27264 }
27265 }
27266 }
27267 }
27268 }
27269
27270 this._arrayOfAllItems[indexInArrayOfAllItems] = null;
27271
27272 item[this._storeRefPropName] = null;
27273 if(this._itemsByIdentity){
27274 delete this._itemsByIdentity[identity];
27275 }
27276 this._pending._deletedItems[identity] = item;
27277
27278 //Remove from the toplevel items, if necessary...
27279 if(item[this._rootItemPropName]){
27280 this._removeArrayElement(this._arrayOfTopLevelItems, item);
27281 }
27282 this.onDelete(item); // dojo.data.api.Notification call
27283 return true;
27284 },
27285
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
27289 },
27290
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
27294 },
27295
27296 unsetAttribute: function(/* item */ item, /* attribute-name-string */ attribute){
27297 // summary: See dojo.data.api.Write.unsetAttribute()
27298 return this._setValueOrValues(item, attribute, [], true);
27299 },
27300
27301 _setValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute, /* anything */ newValueOrValues, /*boolean?*/ callOnSet){
27302 this._assert(!this._saveInProgress);
27303
27304 // Check for valid arguments
27305 this._assertIsItem(item);
27306 this._assert(dojo.isString(attribute));
27307 this._assert(typeof newValueOrValues !== "undefined");
27308
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.");
27313 }
27314
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);
27319
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]);
27333 }else{
27334 copyOfItemState[key] = item[key].slice(0, item[key].length);
27335 }
27336 }
27337 // Now mark the item as dirty, and save the copy of the original state
27338 this._pending._modifiedItems[identity] = copyOfItemState;
27339 }
27340
27341 // Okay, now we can actually change this attribute on the item
27342 var success = false;
27343
27344 if(dojo.isArray(newValueOrValues) && newValueOrValues.length === 0){
27345
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
27351
27352 if(this.referenceIntegrity && oldValueOrValues){
27353 var oldValues = oldValueOrValues;
27354 if(!dojo.isArray(oldValues)){
27355 oldValues = [oldValues];
27356 }
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);
27361 }
27362 }
27363 }
27364 }else{
27365 var newValueArray;
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);
27375 }else{
27376 newValueArray = [newValueOrValues];
27377 }
27378
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];
27387 }
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).
27393 var map = {};
27394 dojo.forEach(oldValues, function(possibleItem){
27395 if(this.isItem(possibleItem)){
27396 var id = this.getIdentity(possibleItem);
27397 map[id.toString()] = true;
27398 }
27399 }, this);
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()];
27405 }else{
27406 this._addReferenceToMap(possibleItem, item, attribute);
27407 }
27408 }
27409 }, this);
27410 for(var rId in map){
27411 var removedItem;
27412 if(this._itemsByIdentity){
27413 removedItem = this._itemsByIdentity[rId];
27414 }else{
27415 removedItem = this._arrayOfAllItems[rId];
27416 }
27417 this._removeReferenceFromMap(removedItem, item, attribute);
27418 }
27419 }else{
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);
27426 }
27427 }
27428 }
27429 }
27430 item[attribute] = newValueArray;
27431 success = true;
27432 }
27433
27434 // Now we make the dojo.data.api.Notification call
27435 if(callOnSet){
27436 this.onSet(item, attribute, oldValueOrValues, newValueOrValues);
27437 }
27438 return success; // boolean
27439 },
27440
27441 _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){
27442 // summary:
27443 // Method to add an reference map entry for an item and attribute.
27444 // description:
27445 // Method to add an reference map entry for an item and attribute. //
27446 // refItem:
27447 // The item that is referenced.
27448 // parentItem:
27449 // The item that holds the new reference to refItem.
27450 // attribute:
27451 // The attribute on parentItem that contains the new reference.
27452
27453 var parentId = this.getIdentity(parentItem);
27454 var references = refItem[this._reverseRefMap];
27455
27456 if(!references){
27457 references = refItem[this._reverseRefMap] = {};
27458 }
27459 var itemRef = references[parentId];
27460 if(!itemRef){
27461 itemRef = references[parentId] = {};
27462 }
27463 itemRef[attribute] = true;
27464 },
27465
27466 _removeReferenceFromMap: function(/* item */ refItem, /* item */ parentItem, /*strin*/ attribute){
27467 // summary:
27468 // Method to remove an reference map entry for an item and attribute.
27469 // description:
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.
27473 //
27474 // refItem:
27475 // The item that is referenced.
27476 // parentItem:
27477 // The item holding a reference to refItem.
27478 // attribute:
27479 // The attribute on parentItem that contains the reference.
27480 var identity = this.getIdentity(parentItem);
27481 var references = refItem[this._reverseRefMap];
27482 var itemId;
27483 if(references){
27484 for(itemId in references){
27485 if(itemId == identity){
27486 delete references[itemId][attribute];
27487 if(this._isEmpty(references[itemId])){
27488 delete references[itemId];
27489 }
27490 }
27491 }
27492 if(this._isEmpty(references)){
27493 delete refItem[this._reverseRefMap];
27494 }
27495 }
27496 },
27497
27498 _dumpReferenceMap: function(){
27499 // summary:
27500 // Function to dump the reverse reference map of all items in the store for debug purposes.
27501 // description:
27502 // Function to dump the reverse reference map of all items in the store for debug purposes.
27503 var i;
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]));
27508 }
27509 }
27510 },
27511
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];
27518 }else{
27519 valueOrValues = valueArray;
27520 }
27521 }
27522 return valueOrValues;
27523 },
27524
27525 _flatten: function(/* anything */ value){
27526 if(this.isItem(value)){
27527 var item = 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
27533 // {_reference:2}
27534 var identity = this.getIdentity(item);
27535 var referenceObject = {_reference: identity};
27536 return referenceObject;
27537 }else{
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 + "]");
27545 }
27546 return {_type: type, _value: typeMap.serialize(value)};
27547 }
27548 } else if(value instanceof typeMap){
27549 //SImple mapping, therefore, return as a toString serialization.
27550 return {_type: type, _value: value.toString()};
27551 }
27552 }
27553 }
27554 return value;
27555 }
27556 },
27557
27558 _getNewFileContentString: function(){
27559 // summary:
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 = {};
27564
27565 var identifierAttribute = this._getIdentifierAttribute();
27566 if(identifierAttribute !== Number){
27567 serializableStructure.identifier = identifierAttribute;
27568 }
27569 if(this._labelAttr){
27570 serializableStructure.label = this._labelAttr;
27571 }
27572 serializableStructure.items = [];
27573 for(var i = 0; i < this._arrayOfAllItems.length; ++i){
27574 var item = this._arrayOfAllItems[i];
27575 if(item !== null){
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]);
27583 }else{
27584 var serializableArray = [];
27585 for(var j = 0; j < valueArray.length; ++j){
27586 serializableArray.push(this._flatten(valueArray[j]));
27587 serializableItem[attribute] = serializableArray;
27588 }
27589 }
27590 }
27591 }
27592 serializableStructure.items.push(serializableItem);
27593 }
27594 }
27595 var prettyPrint = true;
27596 return dojo.toJson(serializableStructure, prettyPrint);
27597 },
27598
27599 _isEmpty: function(something){
27600 // summary:
27601 // Function to determine if an array or object has no properties or values.
27602 // something:
27603 // The array or object to examine.
27604 var empty = true;
27605 if(dojo.isObject(something)){
27606 var i;
27607 for(i in something){
27608 empty = false;
27609 break;
27610 }
27611 }else if(dojo.isArray(something)){
27612 if(something.length > 0){
27613 empty = false;
27614 }
27615 }
27616 return empty; //boolean
27617 },
27618
27619 save: function(/* object */ keywordArgs){
27620 // summary: See dojo.data.api.Write.save()
27621 this._assert(!this._saveInProgress);
27622
27623 // this._saveInProgress is set to true, briefly, from when save is first called to when it completes
27624 this._saveInProgress = true;
27625
27626 var self = this;
27627 var saveCompleteCallback = function(){
27628 self._pending = {
27629 _newItems:{},
27630 _modifiedItems:{},
27631 _deletedItems:{}
27632 };
27633
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);
27638 }
27639 };
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);
27645 }
27646 };
27647
27648 if(this._saveEverything){
27649 var newFileContentString = this._getNewFileContentString();
27650 this._saveEverything(saveCompleteCallback, saveFailedCallback, newFileContentString);
27651 }
27652 if(this._saveCustom){
27653 this._saveCustom(saveCompleteCallback, saveFailedCallback);
27654 }
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();
27660 }
27661 },
27662
27663 revert: function(){
27664 // summary: See dojo.data.api.Write.revert()
27665 this._assert(!this._saveInProgress);
27666
27667 var identity;
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];
27674 }else{
27675 modifiedItem = this._arrayOfAllItems[identity];
27676 }
27677
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];
27683 }
27684 dojo.mixin(modifiedItem, copyOfItemState);
27685 }
27686 var deletedItem;
27687 for(identity in this._pending._deletedItems){
27688 deletedItem = this._pending._deletedItems[identity];
27689 deletedItem[this._storeRefPropName] = this;
27690 var index = deletedItem[this._itemNumPropName];
27691
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];
27696 }
27697 this._arrayOfAllItems[index] = deletedItem;
27698 if(this._itemsByIdentity){
27699 this._itemsByIdentity[identity] = deletedItem;
27700 }
27701 if(deletedItem[this._rootItemPropName]){
27702 this._arrayOfTopLevelItems.push(deletedItem);
27703 }
27704 }
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){
27711 var refItem;
27712 if(this._itemsByIdentity){
27713 refItem = this._itemsByIdentity[reference.id];
27714 }else{
27715 refItem = this._arrayOfAllItems[reference.id];
27716 }
27717 this._addReferenceToMap(refItem, deletedItem, reference.attr);
27718 }, this);
27719 delete deletedItem["backupRefs_" + this._reverseRefMap];
27720 }
27721 }
27722
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);
27731 }
27732 if(this._itemsByIdentity){
27733 delete this._itemsByIdentity[identity];
27734 }
27735 }
27736
27737 this._pending = {
27738 _newItems:{},
27739 _modifiedItems:{},
27740 _deletedItems:{}
27741 };
27742 return true; // boolean
27743 },
27744
27745 isDirty: function(/* item? */ item){
27746 // summary: See dojo.data.api.Write.isDirty()
27747 if(item){
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
27753 }else{
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)){
27759 return true;
27760 }
27761 return false; // boolean
27762 }
27763 },
27764
27765 /* dojo.data.api.Notification */
27766
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()
27772
27773 // No need to do anything. This method is here just so that the
27774 // client code can connect observers to it.
27775 },
27776
27777 onNew: function(/* item */ newItem, /*object?*/ parentInfo){
27778 // summary: See dojo.data.api.Notification.onNew()
27779
27780 // No need to do anything. This method is here just so that the
27781 // client code can connect observers to it.
27782 },
27783
27784 onDelete: function(/* item */ deletedItem){
27785 // summary: See dojo.data.api.Notification.onDelete()
27786
27787 // No need to do anything. This method is here just so that the
27788 // client code can connect observers to it.
27789 },
27790
27791 close: function(/* object? */ request){
27792 // summary:
27793 // Over-ride of base close function of ItemFileReadStore to add in check for store state.
27794 // description:
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.
27798
27799 //Clear if not dirty ... or throw an error
27800 if(this.clearOnClose){
27801 if(!this.isDirty()){
27802 this.inherited(arguments);
27803 }else{
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.");
27806 }
27807 }
27808 }
27809 });
27810
27811 }
27812
27813
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"]);