]> git.wh0rd.org - tt-rss.git/blob - lib/dojo/tt-rss-layer.js.uncompressed.js
build custom layer of Dojo to speed up loading of tt-rss (refs #293)
[tt-rss.git] / lib / dojo / tt-rss-layer.js.uncompressed.js
1 /*
2 Copyright (c) 2004-2010, 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 // Methods to convert dates to or from a wire (string) format using well-known conventions
20
21 dojo.date.stamp.fromISOString = function(/*String*/formattedString, /*Number?*/defaultTime){
22 // summary:
23 // Returns a Date object given a string formatted according to a subset of the ISO-8601 standard.
24 //
25 // description:
26 // Accepts a string formatted according to a profile of ISO8601 as defined by
27 // [RFC3339](http://www.ietf.org/rfc/rfc3339.txt), except that partial input is allowed.
28 // Can also process dates as specified [by the W3C](http://www.w3.org/TR/NOTE-datetime)
29 // The following combinations are valid:
30 //
31 // * dates only
32 // | * yyyy
33 // | * yyyy-MM
34 // | * yyyy-MM-dd
35 // * times only, with an optional time zone appended
36 // | * THH:mm
37 // | * THH:mm:ss
38 // | * THH:mm:ss.SSS
39 // * and "datetimes" which could be any combination of the above
40 //
41 // timezones may be specified as Z (for UTC) or +/- followed by a time expression HH:mm
42 // Assumes the local time zone if not specified. Does not validate. Improperly formatted
43 // input may return null. Arguments which are out of bounds will be handled
44 // by the Date constructor (e.g. January 32nd typically gets resolved to February 1st)
45 // Only years between 100 and 9999 are supported.
46 //
47 // formattedString:
48 // A string such as 2005-06-30T08:05:00-07:00 or 2005-06-30 or T08:05:00
49 //
50 // defaultTime:
51 // Used for defaults for fields omitted in the formattedString.
52 // Uses 1970-01-01T00:00:00.0Z by default.
53
54 if(!dojo.date.stamp._isoRegExp){
55 dojo.date.stamp._isoRegExp =
56 //TODO: could be more restrictive and check for 00-59, etc.
57 /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/;
58 }
59
60 var match = dojo.date.stamp._isoRegExp.exec(formattedString),
61 result = null;
62
63 if(match){
64 match.shift();
65 if(match[1]){match[1]--;} // Javascript Date months are 0-based
66 if(match[6]){match[6] *= 1000;} // Javascript Date expects fractional seconds as milliseconds
67
68 if(defaultTime){
69 // mix in defaultTime. Relatively expensive, so use || operators for the fast path of defaultTime === 0
70 defaultTime = new Date(defaultTime);
71 dojo.forEach(dojo.map(["FullYear", "Month", "Date", "Hours", "Minutes", "Seconds", "Milliseconds"], function(prop){
72 return defaultTime["get" + prop]();
73 }), function(value, index){
74 match[index] = match[index] || value;
75 });
76 }
77 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
78 if(match[0] < 100){
79 result.setFullYear(match[0] || 1970);
80 }
81
82 var offset = 0,
83 zoneSign = match[7] && match[7].charAt(0);
84 if(zoneSign != 'Z'){
85 offset = ((match[8] || 0) * 60) + (Number(match[9]) || 0);
86 if(zoneSign != '-'){ offset *= -1; }
87 }
88 if(zoneSign){
89 offset -= result.getTimezoneOffset();
90 }
91 if(offset){
92 result.setTime(result.getTime() + offset * 60000);
93 }
94 }
95
96 return result; // Date or null
97 }
98
99 /*=====
100 dojo.date.stamp.__Options = function(){
101 // selector: String
102 // "date" or "time" for partial formatting of the Date object.
103 // Both date and time will be formatted by default.
104 // zulu: Boolean
105 // if true, UTC/GMT is used for a timezone
106 // milliseconds: Boolean
107 // if true, output milliseconds
108 this.selector = selector;
109 this.zulu = zulu;
110 this.milliseconds = milliseconds;
111 }
112 =====*/
113
114 dojo.date.stamp.toISOString = function(/*Date*/dateObject, /*dojo.date.stamp.__Options?*/options){
115 // summary:
116 // Format a Date object as a string according a subset of the ISO-8601 standard
117 //
118 // description:
119 // When options.selector is omitted, output follows [RFC3339](http://www.ietf.org/rfc/rfc3339.txt)
120 // The local time zone is included as an offset from GMT, except when selector=='time' (time without a date)
121 // Does not check bounds. Only years between 100 and 9999 are supported.
122 //
123 // dateObject:
124 // A Date object
125
126 var _ = function(n){ return (n < 10) ? "0" + n : n; };
127 options = options || {};
128 var formattedDate = [],
129 getter = options.zulu ? "getUTC" : "get",
130 date = "";
131 if(options.selector != "time"){
132 var year = dateObject[getter+"FullYear"]();
133 date = ["0000".substr((year+"").length)+year, _(dateObject[getter+"Month"]()+1), _(dateObject[getter+"Date"]())].join('-');
134 }
135 formattedDate.push(date);
136 if(options.selector != "date"){
137 var time = [_(dateObject[getter+"Hours"]()), _(dateObject[getter+"Minutes"]()), _(dateObject[getter+"Seconds"]())].join(':');
138 var millis = dateObject[getter+"Milliseconds"]();
139 if(options.milliseconds){
140 time += "."+ (millis < 100 ? "0" : "") + _(millis);
141 }
142 if(options.zulu){
143 time += "Z";
144 }else if(options.selector != "time"){
145 var timezoneOffset = dateObject.getTimezoneOffset();
146 var absOffset = Math.abs(timezoneOffset);
147 time += (timezoneOffset > 0 ? "-" : "+") +
148 _(Math.floor(absOffset/60)) + ":" + _(absOffset%60);
149 }
150 formattedDate.push(time);
151 }
152 return formattedDate.join('T'); // String
153 }
154
155 }
156
157 if(!dojo._hasResource["dojo.parser"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
158 dojo._hasResource["dojo.parser"] = true;
159 dojo.provide("dojo.parser");
160
161
162 new Date("X"); // workaround for #11279, new Date("") == NaN
163
164 dojo.parser = new function(){
165 // summary: The Dom/Widget parsing package
166
167 var d = dojo;
168 this._attrName = d._scopeName + "Type";
169 this._query = "[" + this._attrName + "]";
170
171 function val2type(/*Object*/ value){
172 // summary:
173 // Returns name of type of given value.
174
175 if(d.isString(value)){ return "string"; }
176 if(typeof value == "number"){ return "number"; }
177 if(typeof value == "boolean"){ return "boolean"; }
178 if(d.isFunction(value)){ return "function"; }
179 if(d.isArray(value)){ return "array"; } // typeof [] == "object"
180 if(value instanceof Date) { return "date"; } // assume timestamp
181 if(value instanceof d._Url){ return "url"; }
182 return "object";
183 }
184
185 function str2obj(/*String*/ value, /*String*/ type){
186 // summary:
187 // Convert given string value to given type
188 switch(type){
189 case "string":
190 return value;
191 case "number":
192 return value.length ? Number(value) : NaN;
193 case "boolean":
194 // for checked/disabled value might be "" or "checked". interpret as true.
195 return typeof value == "boolean" ? value : !(value.toLowerCase()=="false");
196 case "function":
197 if(d.isFunction(value)){
198 // IE gives us a function, even when we say something like onClick="foo"
199 // (in which case it gives us an invalid function "function(){ foo }").
200 // Therefore, convert to string
201 value=value.toString();
202 value=d.trim(value.substring(value.indexOf('{')+1, value.length-1));
203 }
204 try{
205 if(value === "" || value.search(/[^\w\.]+/i) != -1){
206 // The user has specified some text for a function like "return x+5"
207 return new Function(value);
208 }else{
209 // The user has specified the name of a function like "myOnClick"
210 // or a single word function "return"
211 return d.getObject(value, false) || new Function(value);
212 }
213 }catch(e){ return new Function(); }
214 case "array":
215 return value ? value.split(/\s*,\s*/) : [];
216 case "date":
217 switch(value){
218 case "": return new Date(""); // the NaN of dates
219 case "now": return new Date(); // current date
220 default: return d.date.stamp.fromISOString(value);
221 }
222 case "url":
223 return d.baseUrl + value;
224 default:
225 return d.fromJson(value);
226 }
227 }
228
229 var instanceClasses = {
230 // map from fully qualified name (like "dijit.Button") to structure like
231 // { cls: dijit.Button, params: {label: "string", disabled: "boolean"} }
232 };
233
234 // Widgets like BorderContainer add properties to _Widget via dojo.extend().
235 // If BorderContainer is loaded after _Widget's parameter list has been cached,
236 // we need to refresh that parameter list (for _Widget and all widgets that extend _Widget).
237 dojo.connect(dojo, "extend", function(){
238 instanceClasses = {};
239 });
240
241 function getClassInfo(/*String*/ className){
242 // className:
243 // fully qualified name (like "dijit.form.Button")
244 // returns:
245 // structure like
246 // {
247 // cls: dijit.Button,
248 // params: { label: "string", disabled: "boolean"}
249 // }
250
251 if(!instanceClasses[className]){
252 // get pointer to widget class
253 var cls = d.getObject(className);
254 if(!cls){ return null; } // class not defined [yet]
255
256 var proto = cls.prototype;
257
258 // get table of parameter names & types
259 var params = {}, dummyClass = {};
260 for(var name in proto){
261 if(name.charAt(0)=="_"){ continue; } // skip internal properties
262 if(name in dummyClass){ continue; } // skip "constructor" and "toString"
263 var defVal = proto[name];
264 params[name]=val2type(defVal);
265 }
266
267 instanceClasses[className] = { cls: cls, params: params };
268 }
269 return instanceClasses[className];
270 }
271
272 this._functionFromScript = function(script){
273 var preamble = "";
274 var suffix = "";
275 var argsStr = script.getAttribute("args");
276 if(argsStr){
277 d.forEach(argsStr.split(/\s*,\s*/), function(part, idx){
278 preamble += "var "+part+" = arguments["+idx+"]; ";
279 });
280 }
281 var withStr = script.getAttribute("with");
282 if(withStr && withStr.length){
283 d.forEach(withStr.split(/\s*,\s*/), function(part){
284 preamble += "with("+part+"){";
285 suffix += "}";
286 });
287 }
288 return new Function(preamble+script.innerHTML+suffix);
289 }
290
291 this.instantiate = function(/* Array */nodes, /* Object? */mixin, /* Object? */args){
292 // summary:
293 // Takes array of nodes, and turns them into class instances and
294 // potentially calls a startup method to allow them to connect with
295 // any children.
296 // nodes: Array
297 // Array of nodes or objects like
298 // | {
299 // | type: "dijit.form.Button",
300 // | node: DOMNode,
301 // | scripts: [ ... ], // array of <script type="dojo/..."> children of node
302 // | inherited: { ... } // settings inherited from ancestors like dir, theme, etc.
303 // | }
304 // mixin: Object?
305 // An object that will be mixed in with each node in the array.
306 // Values in the mixin will override values in the node, if they
307 // exist.
308 // args: Object?
309 // An object used to hold kwArgs for instantiation.
310 // Supports 'noStart' and inherited.
311 var thelist = [], dp = dojo.parser;
312 mixin = mixin||{};
313 args = args||{};
314
315 d.forEach(nodes, function(obj){
316 if(!obj){ return; }
317
318 // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc.s
319 var node, type, clsInfo, clazz, scripts;
320 if(obj.node){
321 // new format of nodes[] array, object w/lots of properties pre-computed for me
322 node = obj.node;
323 type = obj.type;
324 clsInfo = obj.clsInfo || (type && getClassInfo(type));
325 clazz = clsInfo && clsInfo.cls;
326 scripts = obj.scripts;
327 }else{
328 // old (backwards compatible) format of nodes[] array, simple array of DOMNodes
329 node = obj;
330 type = dp._attrName in mixin ? mixin[dp._attrName] : node.getAttribute(dp._attrName);
331 clsInfo = type && getClassInfo(type);
332 clazz = clsInfo && clsInfo.cls;
333 scripts = (clazz && (clazz._noScript || clazz.prototype._noScript) ? [] :
334 d.query("> script[type^='dojo/']", node));
335 }
336 if(!clsInfo){
337 throw new Error("Could not load class '" + type);
338 }
339
340 // Setup hash to hold parameter settings for this widget. Start with the parameter
341 // settings inherited from ancestors ("dir" and "lang").
342 // Inherited setting may later be overridden by explicit settings on node itself.
343 var params = {},
344 attributes = node.attributes;
345 if(args.defaults){
346 // settings for the document itself (or whatever subtree is being parsed)
347 dojo.mixin(params, args.defaults);
348 }
349 if(obj.inherited){
350 // settings from dir=rtl or lang=... on a node above this node
351 dojo.mixin(params, obj.inherited);
352 }
353
354 // read parameters (ie, attributes) specified on DOMNode
355 // clsInfo.params lists expected params like {"checked": "boolean", "n": "number"}
356 for(var name in clsInfo.params){
357 var item = name in mixin?{value:mixin[name],specified:true}:attributes.getNamedItem(name);
358 if(!item || (!item.specified && (!dojo.isIE || name.toLowerCase()!="value"))){ continue; }
359 var value = item.value;
360 // Deal with IE quirks for 'class' and 'style'
361 switch(name){
362 case "class":
363 value = "className" in mixin?mixin.className:node.className;
364 break;
365 case "style":
366 value = "style" in mixin?mixin.style:(node.style && node.style.cssText); // FIXME: Opera?
367 }
368 var _type = clsInfo.params[name];
369 if(typeof value == "string"){
370 params[name] = str2obj(value, _type);
371 }else{
372 params[name] = value;
373 }
374 }
375
376 // Process <script type="dojo/*"> script tags
377 // <script type="dojo/method" event="foo"> tags are added to params, and passed to
378 // the widget on instantiation.
379 // <script type="dojo/method"> tags (with no event) are executed after instantiation
380 // <script type="dojo/connect" event="foo"> tags are dojo.connected after instantiation
381 // note: dojo/* script tags cannot exist in self closing widgets, like <input />
382 var connects = [], // functions to connect after instantiation
383 calls = []; // functions to call after instantiation
384
385 d.forEach(scripts, function(script){
386 node.removeChild(script);
387 var event = script.getAttribute("event"),
388 type = script.getAttribute("type"),
389 nf = d.parser._functionFromScript(script);
390 if(event){
391 if(type == "dojo/connect"){
392 connects.push({event: event, func: nf});
393 }else{
394 params[event] = nf;
395 }
396 }else{
397 calls.push(nf);
398 }
399 });
400
401 var markupFactory = clazz.markupFactory || clazz.prototype && clazz.prototype.markupFactory;
402 // create the instance
403 var instance = markupFactory ? markupFactory(params, node, clazz) : new clazz(params, node);
404 thelist.push(instance);
405
406 // map it to the JS namespace if that makes sense
407 var jsname = node.getAttribute("jsId");
408 if(jsname){
409 d.setObject(jsname, instance);
410 }
411
412 // process connections and startup functions
413 d.forEach(connects, function(connect){
414 d.connect(instance, connect.event, null, connect.func);
415 });
416 d.forEach(calls, function(func){
417 func.call(instance);
418 });
419 });
420
421 // Call startup on each top level instance if it makes sense (as for
422 // widgets). Parent widgets will recursively call startup on their
423 // (non-top level) children
424 if(!mixin._started){
425 // TODO: for 2.0, when old instantiate() API is desupported, store parent-child
426 // relationships in the nodes[] array so that no getParent() call is needed.
427 // Note that will require a parse() call from ContentPane setting a param that the
428 // ContentPane is the parent widget (so that the parse doesn't call startup() on the
429 // ContentPane's children)
430 d.forEach(thelist, function(instance){
431 if( !args.noStart && instance &&
432 instance.startup &&
433 !instance._started &&
434 (!instance.getParent || !instance.getParent())
435 ){
436 instance.startup();
437 }
438 });
439 }
440 return thelist;
441 };
442
443 this.parse = function(/*DomNode?*/ rootNode, /* Object? */ args){
444 // summary:
445 // Scan the DOM for class instances, and instantiate them.
446 //
447 // description:
448 // Search specified node (or root node) recursively for class instances,
449 // and instantiate them Searches for
450 // dojoType="qualified.class.name"
451 //
452 // rootNode: DomNode?
453 // A default starting root node from which to start the parsing. Can be
454 // omitted, defaulting to the entire document. If omitted, the `args`
455 // object can be passed in this place. If the `args` object has a
456 // `rootNode` member, that is used.
457 //
458 // args:
459 // a kwArgs object passed along to instantiate()
460 //
461 // * noStart: Boolean?
462 // when set will prevent the parser from calling .startup()
463 // when locating the nodes.
464 // * rootNode: DomNode?
465 // identical to the function's `rootNode` argument, though
466 // allowed to be passed in via this `args object.
467 // * inherited: Object
468 // Hash possibly containing dir and lang settings to be applied to
469 // parsed widgets, unless there's another setting on a sub-node that overrides
470 //
471 //
472 // example:
473 // Parse all widgets on a page:
474 // | dojo.parser.parse();
475 //
476 // example:
477 // Parse all classes within the node with id="foo"
478 // | dojo.parser.parse(dojo.byId(foo));
479 //
480 // example:
481 // Parse all classes in a page, but do not call .startup() on any
482 // child
483 // | dojo.parser.parse({ noStart: true })
484 //
485 // example:
486 // Parse all classes in a node, but do not call .startup()
487 // | dojo.parser.parse(someNode, { noStart:true });
488 // | // or
489 // | dojo.parser.parse({ noStart:true, rootNode: someNode });
490
491 // determine the root node based on the passed arguments.
492 var root;
493 if(!args && rootNode && rootNode.rootNode){
494 args = rootNode;
495 root = args.rootNode;
496 }else{
497 root = rootNode;
498 }
499
500 var attrName = this._attrName;
501 function scan(parent, list){
502 // summary:
503 // Parent is an Object representing a DOMNode, with or without a dojoType specified.
504 // Scan parent's children looking for nodes with dojoType specified, storing in list[].
505 // If parent has a dojoType, also collects <script type=dojo/*> children and stores in parent.scripts[].
506 // parent: Object
507 // Object representing the parent node, like
508 // | {
509 // | node: DomNode, // scan children of this node
510 // | inherited: {dir: "rtl"}, // dir/lang setting inherited from above node
511 // |
512 // | // attributes only set if node has dojoType specified
513 // | scripts: [], // empty array, put <script type=dojo/*> in here
514 // | clsInfo: { cls: dijit.form.Button, ...}
515 // | }
516 // list: DomNode[]
517 // Output array of objects (same format as parent) representing nodes to be turned into widgets
518
519 // Effective dir and lang settings on parent node, either set directly or inherited from grandparent
520 var inherited = dojo.clone(parent.inherited);
521 dojo.forEach(["dir", "lang"], function(name){
522 var val = parent.node.getAttribute(name);
523 if(val){
524 inherited[name] = val;
525 }
526 });
527
528 // if parent is a widget, then search for <script type=dojo/*> tags and put them in scripts[].
529 var scripts = parent.scripts;
530
531 // unless parent is a widget with the stopParser flag set, continue search for dojoType, recursively
532 var recurse = !parent.clsInfo || !parent.clsInfo.cls.prototype.stopParser;
533
534 // scan parent's children looking for dojoType and <script type=dojo/*>
535 for(var child = parent.node.firstChild; child; child = child.nextSibling){
536 if(child.nodeType == 1){
537 var type = recurse && child.getAttribute(attrName);
538 if(type){
539 // if dojoType specified, add to output array of nodes to instantiate
540 var params = {
541 "type": type,
542 clsInfo: getClassInfo(type), // note: won't find classes declared via dojo.Declaration
543 node: child,
544 scripts: [], // <script> nodes that are parent's children
545 inherited: inherited // dir & lang attributes inherited from parent
546 };
547 list.push(params);
548
549 // Recurse, collecting <script type="dojo/..."> children, and also looking for
550 // descendant nodes with dojoType specified (unless the widget has the stopParser flag),
551 scan(params, list);
552 }else if(scripts && child.nodeName.toLowerCase() == "script"){
553 // if <script type="dojo/...">, save in scripts[]
554 type = child.getAttribute("type");
555 if (type && /^dojo\//i.test(type)) {
556 scripts.push(child);
557 }
558 }else if(recurse){
559 // Recurse, looking for grandchild nodes with dojoType specified
560 scan({
561 node: child,
562 inherited: inherited
563 }, list);
564 }
565 }
566 }
567 }
568
569 // Make list of all nodes on page w/dojoType specified
570 var list = [];
571 scan({
572 node: root ? dojo.byId(root) : dojo.body(),
573 inherited: (args && args.inherited) || {
574 dir: dojo._isBodyLtr() ? "ltr" : "rtl"
575 }
576 }, list);
577
578 // go build the object instances
579 return this.instantiate(list, null, args); // Array
580 };
581 }();
582
583 //Register the parser callback. It should be the first callback
584 //after the a11y test.
585
586 (function(){
587 var parseRunner = function(){
588 if(dojo.config.parseOnLoad){
589 dojo.parser.parse();
590 }
591 };
592
593 // FIXME: need to clobber cross-dependency!!
594 if(dojo.exists("dijit.wai.onload") && (dijit.wai.onload === dojo._loaders[0])){
595 dojo._loaders.splice(1, 0, parseRunner);
596 }else{
597 dojo._loaders.unshift(parseRunner);
598 }
599 })();
600
601 }
602
603 if(!dojo._hasResource["dojo.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
604 dojo._hasResource["dojo.window"] = true;
605 dojo.provide("dojo.window");
606
607 dojo.window.getBox = function(){
608 // summary:
609 // Returns the dimensions and scroll position of the viewable area of a browser window
610
611 var scrollRoot = (dojo.doc.compatMode == 'BackCompat') ? dojo.body() : dojo.doc.documentElement;
612
613 // get scroll position
614 var scroll = dojo._docScroll(); // scrollRoot.scrollTop/Left should work
615 return { w: scrollRoot.clientWidth, h: scrollRoot.clientHeight, l: scroll.x, t: scroll.y };
616 };
617
618 dojo.window.get = function(doc){
619 // summary:
620 // Get window object associated with document doc
621
622 // In some IE versions (at least 6.0), document.parentWindow does not return a
623 // reference to the real window object (maybe a copy), so we must fix it as well
624 // We use IE specific execScript to attach the real window reference to
625 // document._parentWindow for later use
626 if(dojo.isIE && window !== document.parentWindow){
627 /*
628 In IE 6, only the variable "window" can be used to connect events (others
629 may be only copies).
630 */
631 doc.parentWindow.execScript("document._parentWindow = window;", "Javascript");
632 //to prevent memory leak, unset it after use
633 //another possibility is to add an onUnload handler which seems overkill to me (liucougar)
634 var win = doc._parentWindow;
635 doc._parentWindow = null;
636 return win; // Window
637 }
638
639 return doc.parentWindow || doc.defaultView; // Window
640 };
641
642 dojo.window.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
643 // summary:
644 // Scroll the passed node into view, if it is not already.
645
646 // don't rely on node.scrollIntoView working just because the function is there
647
648 try{ // catch unexpected/unrecreatable errors (#7808) since we can recover using a semi-acceptable native method
649 node = dojo.byId(node);
650 var doc = node.ownerDocument || dojo.doc,
651 body = doc.body || dojo.body(),
652 html = doc.documentElement || body.parentNode,
653 isIE = dojo.isIE, isWK = dojo.isWebKit;
654 // if an untested browser, then use the native method
655 if((!(dojo.isMoz || isIE || isWK || dojo.isOpera) || node == body || node == html) && (typeof node.scrollIntoView != "undefined")){
656 node.scrollIntoView(false); // short-circuit to native if possible
657 return;
658 }
659 var backCompat = doc.compatMode == 'BackCompat',
660 clientAreaRoot = backCompat? body : html,
661 scrollRoot = isWK ? body : clientAreaRoot,
662 rootWidth = clientAreaRoot.clientWidth,
663 rootHeight = clientAreaRoot.clientHeight,
664 rtl = !dojo._isBodyLtr(),
665 nodePos = pos || dojo.position(node),
666 el = node.parentNode,
667 isFixed = function(el){
668 return ((isIE <= 6 || (isIE && backCompat))? false : (dojo.style(el, 'position').toLowerCase() == "fixed"));
669 };
670 if(isFixed(node)){ return; } // nothing to do
671
672 while(el){
673 if(el == body){ el = scrollRoot; }
674 var elPos = dojo.position(el),
675 fixedPos = isFixed(el);
676
677 if(el == scrollRoot){
678 elPos.w = rootWidth; elPos.h = rootHeight;
679 if(scrollRoot == html && isIE && rtl){ elPos.x += scrollRoot.offsetWidth-elPos.w; } // IE workaround where scrollbar causes negative x
680 if(elPos.x < 0 || !isIE){ elPos.x = 0; } // IE can have values > 0
681 if(elPos.y < 0 || !isIE){ elPos.y = 0; }
682 }else{
683 var pb = dojo._getPadBorderExtents(el);
684 elPos.w -= pb.w; elPos.h -= pb.h; elPos.x += pb.l; elPos.y += pb.t;
685 }
686
687 if(el != scrollRoot){ // body, html sizes already have the scrollbar removed
688 var clientSize = el.clientWidth,
689 scrollBarSize = elPos.w - clientSize;
690 if(clientSize > 0 && scrollBarSize > 0){
691 elPos.w = clientSize;
692 if(isIE && rtl){ elPos.x += scrollBarSize; }
693 }
694 clientSize = el.clientHeight;
695 scrollBarSize = elPos.h - clientSize;
696 if(clientSize > 0 && scrollBarSize > 0){
697 elPos.h = clientSize;
698 }
699 }
700 if(fixedPos){ // bounded by viewport, not parents
701 if(elPos.y < 0){
702 elPos.h += elPos.y; elPos.y = 0;
703 }
704 if(elPos.x < 0){
705 elPos.w += elPos.x; elPos.x = 0;
706 }
707 if(elPos.y + elPos.h > rootHeight){
708 elPos.h = rootHeight - elPos.y;
709 }
710 if(elPos.x + elPos.w > rootWidth){
711 elPos.w = rootWidth - elPos.x;
712 }
713 }
714 // calculate overflow in all 4 directions
715 var l = nodePos.x - elPos.x, // beyond left: < 0
716 t = nodePos.y - Math.max(elPos.y, 0), // beyond top: < 0
717 r = l + nodePos.w - elPos.w, // beyond right: > 0
718 bot = t + nodePos.h - elPos.h; // beyond bottom: > 0
719 if(r * l > 0){
720 var s = Math[l < 0? "max" : "min"](l, r);
721 nodePos.x += el.scrollLeft;
722 el.scrollLeft += (isIE >= 8 && !backCompat && rtl)? -s : s;
723 nodePos.x -= el.scrollLeft;
724 }
725 if(bot * t > 0){
726 nodePos.y += el.scrollTop;
727 el.scrollTop += Math[t < 0? "max" : "min"](t, bot);
728 nodePos.y -= el.scrollTop;
729 }
730 el = (el != scrollRoot) && !fixedPos && el.parentNode;
731 }
732 }catch(error){
733 console.error('scrollIntoView: ' + error);
734 node.scrollIntoView(false);
735 }
736 };
737
738 }
739
740 if(!dojo._hasResource["dijit._base.manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
741 dojo._hasResource["dijit._base.manager"] = true;
742 dojo.provide("dijit._base.manager");
743
744 dojo.declare("dijit.WidgetSet", null, {
745 // summary:
746 // A set of widgets indexed by id. A default instance of this class is
747 // available as `dijit.registry`
748 //
749 // example:
750 // Create a small list of widgets:
751 // | var ws = new dijit.WidgetSet();
752 // | ws.add(dijit.byId("one"));
753 // | ws.add(dijit.byId("two"));
754 // | // destroy both:
755 // | ws.forEach(function(w){ w.destroy(); });
756 //
757 // example:
758 // Using dijit.registry:
759 // | dijit.registry.forEach(function(w){ /* do something */ });
760
761 constructor: function(){
762 this._hash = {};
763 this.length = 0;
764 },
765
766 add: function(/*dijit._Widget*/ widget){
767 // summary:
768 // Add a widget to this list. If a duplicate ID is detected, a error is thrown.
769 //
770 // widget: dijit._Widget
771 // Any dijit._Widget subclass.
772 if(this._hash[widget.id]){
773 throw new Error("Tried to register widget with id==" + widget.id + " but that id is already registered");
774 }
775 this._hash[widget.id] = widget;
776 this.length++;
777 },
778
779 remove: function(/*String*/ id){
780 // summary:
781 // Remove a widget from this WidgetSet. Does not destroy the widget; simply
782 // removes the reference.
783 if(this._hash[id]){
784 delete this._hash[id];
785 this.length--;
786 }
787 },
788
789 forEach: function(/*Function*/ func, /* Object? */thisObj){
790 // summary:
791 // Call specified function for each widget in this set.
792 //
793 // func:
794 // A callback function to run for each item. Is passed the widget, the index
795 // in the iteration, and the full hash, similar to `dojo.forEach`.
796 //
797 // thisObj:
798 // An optional scope parameter
799 //
800 // example:
801 // Using the default `dijit.registry` instance:
802 // | dijit.registry.forEach(function(widget){
803 // | console.log(widget.declaredClass);
804 // | });
805 //
806 // returns:
807 // Returns self, in order to allow for further chaining.
808
809 thisObj = thisObj || dojo.global;
810 var i = 0, id;
811 for(id in this._hash){
812 func.call(thisObj, this._hash[id], i++, this._hash);
813 }
814 return this; // dijit.WidgetSet
815 },
816
817 filter: function(/*Function*/ filter, /* Object? */thisObj){
818 // summary:
819 // Filter down this WidgetSet to a smaller new WidgetSet
820 // Works the same as `dojo.filter` and `dojo.NodeList.filter`
821 //
822 // filter:
823 // Callback function to test truthiness. Is passed the widget
824 // reference and the pseudo-index in the object.
825 //
826 // thisObj: Object?
827 // Option scope to use for the filter function.
828 //
829 // example:
830 // Arbitrary: select the odd widgets in this list
831 // | dijit.registry.filter(function(w, i){
832 // | return i % 2 == 0;
833 // | }).forEach(function(w){ /* odd ones */ });
834
835 thisObj = thisObj || dojo.global;
836 var res = new dijit.WidgetSet(), i = 0, id;
837 for(id in this._hash){
838 var w = this._hash[id];
839 if(filter.call(thisObj, w, i++, this._hash)){
840 res.add(w);
841 }
842 }
843 return res; // dijit.WidgetSet
844 },
845
846 byId: function(/*String*/ id){
847 // summary:
848 // Find a widget in this list by it's id.
849 // example:
850 // Test if an id is in a particular WidgetSet
851 // | var ws = new dijit.WidgetSet();
852 // | ws.add(dijit.byId("bar"));
853 // | var t = ws.byId("bar") // returns a widget
854 // | var x = ws.byId("foo"); // returns undefined
855
856 return this._hash[id]; // dijit._Widget
857 },
858
859 byClass: function(/*String*/ cls){
860 // summary:
861 // Reduce this widgetset to a new WidgetSet of a particular `declaredClass`
862 //
863 // cls: String
864 // The Class to scan for. Full dot-notated string.
865 //
866 // example:
867 // Find all `dijit.TitlePane`s in a page:
868 // | dijit.registry.byClass("dijit.TitlePane").forEach(function(tp){ tp.close(); });
869
870 var res = new dijit.WidgetSet(), id, widget;
871 for(id in this._hash){
872 widget = this._hash[id];
873 if(widget.declaredClass == cls){
874 res.add(widget);
875 }
876 }
877 return res; // dijit.WidgetSet
878 },
879
880 toArray: function(){
881 // summary:
882 // Convert this WidgetSet into a true Array
883 //
884 // example:
885 // Work with the widget .domNodes in a real Array
886 // | dojo.map(dijit.registry.toArray(), function(w){ return w.domNode; });
887
888 var ar = [];
889 for(var id in this._hash){
890 ar.push(this._hash[id]);
891 }
892 return ar; // dijit._Widget[]
893 },
894
895 map: function(/* Function */func, /* Object? */thisObj){
896 // summary:
897 // Create a new Array from this WidgetSet, following the same rules as `dojo.map`
898 // example:
899 // | var nodes = dijit.registry.map(function(w){ return w.domNode; });
900 //
901 // returns:
902 // A new array of the returned values.
903 return dojo.map(this.toArray(), func, thisObj); // Array
904 },
905
906 every: function(func, thisObj){
907 // summary:
908 // A synthetic clone of `dojo.every` acting explicitly on this WidgetSet
909 //
910 // func: Function
911 // A callback function run for every widget in this list. Exits loop
912 // when the first false return is encountered.
913 //
914 // thisObj: Object?
915 // Optional scope parameter to use for the callback
916
917 thisObj = thisObj || dojo.global;
918 var x = 0, i;
919 for(i in this._hash){
920 if(!func.call(thisObj, this._hash[i], x++, this._hash)){
921 return false; // Boolean
922 }
923 }
924 return true; // Boolean
925 },
926
927 some: function(func, thisObj){
928 // summary:
929 // A synthetic clone of `dojo.some` acting explictly on this WidgetSet
930 //
931 // func: Function
932 // A callback function run for every widget in this list. Exits loop
933 // when the first true return is encountered.
934 //
935 // thisObj: Object?
936 // Optional scope parameter to use for the callback
937
938 thisObj = thisObj || dojo.global;
939 var x = 0, i;
940 for(i in this._hash){
941 if(func.call(thisObj, this._hash[i], x++, this._hash)){
942 return true; // Boolean
943 }
944 }
945 return false; // Boolean
946 }
947
948 });
949
950 (function(){
951
952 /*=====
953 dijit.registry = {
954 // summary:
955 // A list of widgets on a page.
956 // description:
957 // Is an instance of `dijit.WidgetSet`
958 };
959 =====*/
960 dijit.registry = new dijit.WidgetSet();
961
962 var hash = dijit.registry._hash,
963 attr = dojo.attr,
964 hasAttr = dojo.hasAttr,
965 style = dojo.style;
966
967 dijit.byId = function(/*String|dijit._Widget*/ id){
968 // summary:
969 // Returns a widget by it's id, or if passed a widget, no-op (like dojo.byId())
970 return typeof id == "string" ? hash[id] : id; // dijit._Widget
971 };
972
973 var _widgetTypeCtr = {};
974 dijit.getUniqueId = function(/*String*/widgetType){
975 // summary:
976 // Generates a unique id for a given widgetType
977
978 var id;
979 do{
980 id = widgetType + "_" +
981 (widgetType in _widgetTypeCtr ?
982 ++_widgetTypeCtr[widgetType] : _widgetTypeCtr[widgetType] = 0);
983 }while(hash[id]);
984 return dijit._scopeName == "dijit" ? id : dijit._scopeName + "_" + id; // String
985 };
986
987 dijit.findWidgets = function(/*DomNode*/ root){
988 // summary:
989 // Search subtree under root returning widgets found.
990 // Doesn't search for nested widgets (ie, widgets inside other widgets).
991
992 var outAry = [];
993
994 function getChildrenHelper(root){
995 for(var node = root.firstChild; node; node = node.nextSibling){
996 if(node.nodeType == 1){
997 var widgetId = node.getAttribute("widgetId");
998 if(widgetId){
999 outAry.push(hash[widgetId]);
1000 }else{
1001 getChildrenHelper(node);
1002 }
1003 }
1004 }
1005 }
1006
1007 getChildrenHelper(root);
1008 return outAry;
1009 };
1010
1011 dijit._destroyAll = function(){
1012 // summary:
1013 // Code to destroy all widgets and do other cleanup on page unload
1014
1015 // Clean up focus manager lingering references to widgets and nodes
1016 dijit._curFocus = null;
1017 dijit._prevFocus = null;
1018 dijit._activeStack = [];
1019
1020 // Destroy all the widgets, top down
1021 dojo.forEach(dijit.findWidgets(dojo.body()), function(widget){
1022 // Avoid double destroy of widgets like Menu that are attached to <body>
1023 // even though they are logically children of other widgets.
1024 if(!widget._destroyed){
1025 if(widget.destroyRecursive){
1026 widget.destroyRecursive();
1027 }else if(widget.destroy){
1028 widget.destroy();
1029 }
1030 }
1031 });
1032 };
1033
1034 if(dojo.isIE){
1035 // Only run _destroyAll() for IE because we think it's only necessary in that case,
1036 // and because it causes problems on FF. See bug #3531 for details.
1037 dojo.addOnWindowUnload(function(){
1038 dijit._destroyAll();
1039 });
1040 }
1041
1042 dijit.byNode = function(/*DOMNode*/ node){
1043 // summary:
1044 // Returns the widget corresponding to the given DOMNode
1045 return hash[node.getAttribute("widgetId")]; // dijit._Widget
1046 };
1047
1048 dijit.getEnclosingWidget = function(/*DOMNode*/ node){
1049 // summary:
1050 // Returns the widget whose DOM tree contains the specified DOMNode, or null if
1051 // the node is not contained within the DOM tree of any widget
1052 while(node){
1053 var id = node.getAttribute && node.getAttribute("widgetId");
1054 if(id){
1055 return hash[id];
1056 }
1057 node = node.parentNode;
1058 }
1059 return null;
1060 };
1061
1062 var shown = (dijit._isElementShown = function(/*Element*/ elem){
1063 var s = style(elem);
1064 return (s.visibility != "hidden")
1065 && (s.visibility != "collapsed")
1066 && (s.display != "none")
1067 && (attr(elem, "type") != "hidden");
1068 });
1069
1070 dijit.hasDefaultTabStop = function(/*Element*/ elem){
1071 // summary:
1072 // Tests if element is tab-navigable even without an explicit tabIndex setting
1073
1074 // No explicit tabIndex setting, need to investigate node type
1075 switch(elem.nodeName.toLowerCase()){
1076 case "a":
1077 // An <a> w/out a tabindex is only navigable if it has an href
1078 return hasAttr(elem, "href");
1079 case "area":
1080 case "button":
1081 case "input":
1082 case "object":
1083 case "select":
1084 case "textarea":
1085 // These are navigable by default
1086 return true;
1087 case "iframe":
1088 // If it's an editor <iframe> then it's tab navigable.
1089 //TODO: feature detect "designMode" in elem.contentDocument?
1090 if(dojo.isMoz){
1091 try{
1092 return elem.contentDocument.designMode == "on";
1093 }catch(err){
1094 return false;
1095 }
1096 }else if(dojo.isWebKit){
1097 var doc = elem.contentDocument,
1098 body = doc && doc.body;
1099 return body && body.contentEditable == 'true';
1100 }else{
1101 // contentWindow.document isn't accessible within IE7/8
1102 // if the iframe.src points to a foreign url and this
1103 // page contains an element, that could get focus
1104 try{
1105 doc = elem.contentWindow.document;
1106 body = doc && doc.body;
1107 return body && body.firstChild && body.firstChild.contentEditable == 'true';
1108 }catch(e){
1109 return false;
1110 }
1111 }
1112 default:
1113 return elem.contentEditable == 'true';
1114 }
1115 };
1116
1117 var isTabNavigable = (dijit.isTabNavigable = function(/*Element*/ elem){
1118 // summary:
1119 // Tests if an element is tab-navigable
1120
1121 // TODO: convert (and rename method) to return effective tabIndex; will save time in _getTabNavigable()
1122 if(attr(elem, "disabled")){
1123 return false;
1124 }else if(hasAttr(elem, "tabIndex")){
1125 // Explicit tab index setting
1126 return attr(elem, "tabIndex") >= 0; // boolean
1127 }else{
1128 // No explicit tabIndex setting, so depends on node type
1129 return dijit.hasDefaultTabStop(elem);
1130 }
1131 });
1132
1133 dijit._getTabNavigable = function(/*DOMNode*/ root){
1134 // summary:
1135 // Finds descendants of the specified root node.
1136 //
1137 // description:
1138 // Finds the following descendants of the specified root node:
1139 // * the first tab-navigable element in document order
1140 // without a tabIndex or with tabIndex="0"
1141 // * the last tab-navigable element in document order
1142 // without a tabIndex or with tabIndex="0"
1143 // * the first element in document order with the lowest
1144 // positive tabIndex value
1145 // * the last element in document order with the highest
1146 // positive tabIndex value
1147 var first, last, lowest, lowestTabindex, highest, highestTabindex;
1148 var walkTree = function(/*DOMNode*/parent){
1149 dojo.query("> *", parent).forEach(function(child){
1150 // Skip hidden elements, and also non-HTML elements (those in custom namespaces) in IE,
1151 // since show() invokes getAttribute("type"), which crash on VML nodes in IE.
1152 if((dojo.isIE && child.scopeName!=="HTML") || !shown(child)){
1153 return;
1154 }
1155
1156 if(isTabNavigable(child)){
1157 var tabindex = attr(child, "tabIndex");
1158 if(!hasAttr(child, "tabIndex") || tabindex == 0){
1159 if(!first){ first = child; }
1160 last = child;
1161 }else if(tabindex > 0){
1162 if(!lowest || tabindex < lowestTabindex){
1163 lowestTabindex = tabindex;
1164 lowest = child;
1165 }
1166 if(!highest || tabindex >= highestTabindex){
1167 highestTabindex = tabindex;
1168 highest = child;
1169 }
1170 }
1171 }
1172 if(child.nodeName.toUpperCase() != 'SELECT'){
1173 walkTree(child);
1174 }
1175 });
1176 };
1177 if(shown(root)){ walkTree(root) }
1178 return { first: first, last: last, lowest: lowest, highest: highest };
1179 }
1180 dijit.getFirstInTabbingOrder = function(/*String|DOMNode*/ root){
1181 // summary:
1182 // Finds the descendant of the specified root node
1183 // that is first in the tabbing order
1184 var elems = dijit._getTabNavigable(dojo.byId(root));
1185 return elems.lowest ? elems.lowest : elems.first; // DomNode
1186 };
1187
1188 dijit.getLastInTabbingOrder = function(/*String|DOMNode*/ root){
1189 // summary:
1190 // Finds the descendant of the specified root node
1191 // that is last in the tabbing order
1192 var elems = dijit._getTabNavigable(dojo.byId(root));
1193 return elems.last ? elems.last : elems.highest; // DomNode
1194 };
1195
1196 /*=====
1197 dojo.mixin(dijit, {
1198 // defaultDuration: Integer
1199 // The default animation speed (in ms) to use for all Dijit
1200 // transitional animations, unless otherwise specified
1201 // on a per-instance basis. Defaults to 200, overrided by
1202 // `djConfig.defaultDuration`
1203 defaultDuration: 200
1204 });
1205 =====*/
1206
1207 dijit.defaultDuration = dojo.config["defaultDuration"] || 200;
1208
1209 })();
1210
1211 }
1212
1213 if(!dojo._hasResource["dijit._base.focus"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1214 dojo._hasResource["dijit._base.focus"] = true;
1215 dojo.provide("dijit._base.focus");
1216
1217
1218 // for dijit.isTabNavigable()
1219
1220 // summary:
1221 // These functions are used to query or set the focus and selection.
1222 //
1223 // Also, they trace when widgets become activated/deactivated,
1224 // so that the widget can fire _onFocus/_onBlur events.
1225 // "Active" here means something similar to "focused", but
1226 // "focus" isn't quite the right word because we keep track of
1227 // a whole stack of "active" widgets. Example: ComboButton --> Menu -->
1228 // MenuItem. The onBlur event for ComboButton doesn't fire due to focusing
1229 // on the Menu or a MenuItem, since they are considered part of the
1230 // ComboButton widget. It only happens when focus is shifted
1231 // somewhere completely different.
1232
1233 dojo.mixin(dijit, {
1234 // _curFocus: DomNode
1235 // Currently focused item on screen
1236 _curFocus: null,
1237
1238 // _prevFocus: DomNode
1239 // Previously focused item on screen
1240 _prevFocus: null,
1241
1242 isCollapsed: function(){
1243 // summary:
1244 // Returns true if there is no text selected
1245 return dijit.getBookmark().isCollapsed;
1246 },
1247
1248 getBookmark: function(){
1249 // summary:
1250 // Retrieves a bookmark that can be used with moveToBookmark to return to the same range
1251 var bm, rg, tg, sel = dojo.doc.selection, cf = dijit._curFocus;
1252
1253 if(dojo.global.getSelection){
1254 //W3C Range API for selections.
1255 sel = dojo.global.getSelection();
1256 if(sel){
1257 if(sel.isCollapsed){
1258 tg = cf? cf.tagName : "";
1259 if(tg){
1260 //Create a fake rangelike item to restore selections.
1261 tg = tg.toLowerCase();
1262 if(tg == "textarea" ||
1263 (tg == "input" && (!cf.type || cf.type.toLowerCase() == "text"))){
1264 sel = {
1265 start: cf.selectionStart,
1266 end: cf.selectionEnd,
1267 node: cf,
1268 pRange: true
1269 };
1270 return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object.
1271 }
1272 }
1273 bm = {isCollapsed:true};
1274 }else{
1275 rg = sel.getRangeAt(0);
1276 bm = {isCollapsed: false, mark: rg.cloneRange()};
1277 }
1278 }
1279 }else if(sel){
1280 // If the current focus was a input of some sort and no selection, don't bother saving
1281 // a native bookmark. This is because it causes issues with dialog/page selection restore.
1282 // So, we need to create psuedo bookmarks to work with.
1283 tg = cf ? cf.tagName : "";
1284 tg = tg.toLowerCase();
1285 if(cf && tg && (tg == "button" || tg == "textarea" || tg == "input")){
1286 if(sel.type && sel.type.toLowerCase() == "none"){
1287 return {
1288 isCollapsed: true,
1289 mark: null
1290 }
1291 }else{
1292 rg = sel.createRange();
1293 return {
1294 isCollapsed: rg.text && rg.text.length?false:true,
1295 mark: {
1296 range: rg,
1297 pRange: true
1298 }
1299 };
1300 }
1301 }
1302 bm = {};
1303
1304 //'IE' way for selections.
1305 try{
1306 // createRange() throws exception when dojo in iframe
1307 //and nothing selected, see #9632
1308 rg = sel.createRange();
1309 bm.isCollapsed = !(sel.type == 'Text' ? rg.htmlText.length : rg.length);
1310 }catch(e){
1311 bm.isCollapsed = true;
1312 return bm;
1313 }
1314 if(sel.type.toUpperCase() == 'CONTROL'){
1315 if(rg.length){
1316 bm.mark=[];
1317 var i=0,len=rg.length;
1318 while(i<len){
1319 bm.mark.push(rg.item(i++));
1320 }
1321 }else{
1322 bm.isCollapsed = true;
1323 bm.mark = null;
1324 }
1325 }else{
1326 bm.mark = rg.getBookmark();
1327 }
1328 }else{
1329 console.warn("No idea how to store the current selection for this browser!");
1330 }
1331 return bm; // Object
1332 },
1333
1334 moveToBookmark: function(/*Object*/bookmark){
1335 // summary:
1336 // Moves current selection to a bookmark
1337 // bookmark:
1338 // This should be a returned object from dijit.getBookmark()
1339
1340 var _doc = dojo.doc,
1341 mark = bookmark.mark;
1342 if(mark){
1343 if(dojo.global.getSelection){
1344 //W3C Rangi API (FF, WebKit, Opera, etc)
1345 var sel = dojo.global.getSelection();
1346 if(sel && sel.removeAllRanges){
1347 if(mark.pRange){
1348 var r = mark;
1349 var n = r.node;
1350 n.selectionStart = r.start;
1351 n.selectionEnd = r.end;
1352 }else{
1353 sel.removeAllRanges();
1354 sel.addRange(mark);
1355 }
1356 }else{
1357 console.warn("No idea how to restore selection for this browser!");
1358 }
1359 }else if(_doc.selection && mark){
1360 //'IE' way.
1361 var rg;
1362 if(mark.pRange){
1363 rg = mark.range;
1364 }else if(dojo.isArray(mark)){
1365 rg = _doc.body.createControlRange();
1366 //rg.addElement does not have call/apply method, so can not call it directly
1367 //rg is not available in "range.addElement(item)", so can't use that either
1368 dojo.forEach(mark, function(n){
1369 rg.addElement(n);
1370 });
1371 }else{
1372 rg = _doc.body.createTextRange();
1373 rg.moveToBookmark(mark);
1374 }
1375 rg.select();
1376 }
1377 }
1378 },
1379
1380 getFocus: function(/*Widget?*/ menu, /*Window?*/ openedForWindow){
1381 // summary:
1382 // Called as getFocus(), this returns an Object showing the current focus
1383 // and selected text.
1384 //
1385 // Called as getFocus(widget), where widget is a (widget representing) a button
1386 // that was just pressed, it returns where focus was before that button
1387 // was pressed. (Pressing the button may have either shifted focus to the button,
1388 // or removed focus altogether.) In this case the selected text is not returned,
1389 // since it can't be accurately determined.
1390 //
1391 // menu: dijit._Widget or {domNode: DomNode} structure
1392 // The button that was just pressed. If focus has disappeared or moved
1393 // to this button, returns the previous focus. In this case the bookmark
1394 // information is already lost, and null is returned.
1395 //
1396 // openedForWindow:
1397 // iframe in which menu was opened
1398 //
1399 // returns:
1400 // A handle to restore focus/selection, to be passed to `dijit.focus`
1401 var node = !dijit._curFocus || (menu && dojo.isDescendant(dijit._curFocus, menu.domNode)) ? dijit._prevFocus : dijit._curFocus;
1402 return {
1403 node: node,
1404 bookmark: (node == dijit._curFocus) && dojo.withGlobal(openedForWindow || dojo.global, dijit.getBookmark),
1405 openedForWindow: openedForWindow
1406 }; // Object
1407 },
1408
1409 focus: function(/*Object || DomNode */ handle){
1410 // summary:
1411 // Sets the focused node and the selection according to argument.
1412 // To set focus to an iframe's content, pass in the iframe itself.
1413 // handle:
1414 // object returned by get(), or a DomNode
1415
1416 if(!handle){ return; }
1417
1418 var node = "node" in handle ? handle.node : handle, // because handle is either DomNode or a composite object
1419 bookmark = handle.bookmark,
1420 openedForWindow = handle.openedForWindow,
1421 collapsed = bookmark ? bookmark.isCollapsed : false;
1422
1423 // Set the focus
1424 // Note that for iframe's we need to use the <iframe> to follow the parentNode chain,
1425 // but we need to set focus to iframe.contentWindow
1426 if(node){
1427 var focusNode = (node.tagName.toLowerCase() == "iframe") ? node.contentWindow : node;
1428 if(focusNode && focusNode.focus){
1429 try{
1430 // Gecko throws sometimes if setting focus is impossible,
1431 // node not displayed or something like that
1432 focusNode.focus();
1433 }catch(e){/*quiet*/}
1434 }
1435 dijit._onFocusNode(node);
1436 }
1437
1438 // set the selection
1439 // do not need to restore if current selection is not empty
1440 // (use keyboard to select a menu item) or if previous selection was collapsed
1441 // as it may cause focus shift (Esp in IE).
1442 if(bookmark && dojo.withGlobal(openedForWindow || dojo.global, dijit.isCollapsed) && !collapsed){
1443 if(openedForWindow){
1444 openedForWindow.focus();
1445 }
1446 try{
1447 dojo.withGlobal(openedForWindow || dojo.global, dijit.moveToBookmark, null, [bookmark]);
1448 }catch(e2){
1449 /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */
1450 }
1451 }
1452 },
1453
1454 // _activeStack: dijit._Widget[]
1455 // List of currently active widgets (focused widget and it's ancestors)
1456 _activeStack: [],
1457
1458 registerIframe: function(/*DomNode*/ iframe){
1459 // summary:
1460 // Registers listeners on the specified iframe so that any click
1461 // or focus event on that iframe (or anything in it) is reported
1462 // as a focus/click event on the <iframe> itself.
1463 // description:
1464 // Currently only used by editor.
1465 // returns:
1466 // Handle to pass to unregisterIframe()
1467 return dijit.registerWin(iframe.contentWindow, iframe);
1468 },
1469
1470 unregisterIframe: function(/*Object*/ handle){
1471 // summary:
1472 // Unregisters listeners on the specified iframe created by registerIframe.
1473 // After calling be sure to delete or null out the handle itself.
1474 // handle:
1475 // Handle returned by registerIframe()
1476
1477 dijit.unregisterWin(handle);
1478 },
1479
1480 registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){
1481 // summary:
1482 // Registers listeners on the specified window (either the main
1483 // window or an iframe's window) to detect when the user has clicked somewhere
1484 // or focused somewhere.
1485 // description:
1486 // Users should call registerIframe() instead of this method.
1487 // targetWindow:
1488 // If specified this is the window associated with the iframe,
1489 // i.e. iframe.contentWindow.
1490 // effectiveNode:
1491 // If specified, report any focus events inside targetWindow as
1492 // an event on effectiveNode, rather than on evt.target.
1493 // returns:
1494 // Handle to pass to unregisterWin()
1495
1496 // TODO: make this function private in 2.0; Editor/users should call registerIframe(),
1497
1498 var mousedownListener = function(evt){
1499 dijit._justMouseDowned = true;
1500 setTimeout(function(){ dijit._justMouseDowned = false; }, 0);
1501
1502 // workaround weird IE bug where the click is on an orphaned node
1503 // (first time clicking a Select/DropDownButton inside a TooltipDialog)
1504 if(dojo.isIE && evt && evt.srcElement && evt.srcElement.parentNode == null){
1505 return;
1506 }
1507
1508 dijit._onTouchNode(effectiveNode || evt.target || evt.srcElement, "mouse");
1509 };
1510 //dojo.connect(targetWindow, "onscroll", ???);
1511
1512 // Listen for blur and focus events on targetWindow's document.
1513 // IIRC, I'm using attachEvent() rather than dojo.connect() because focus/blur events don't bubble
1514 // through dojo.connect(), and also maybe to catch the focus events early, before onfocus handlers
1515 // fire.
1516 // Connect to <html> (rather than document) on IE to avoid memory leaks, but document on other browsers because
1517 // (at least for FF) the focus event doesn't fire on <html> or <body>.
1518 var doc = dojo.isIE ? targetWindow.document.documentElement : targetWindow.document;
1519 if(doc){
1520 if(dojo.isIE){
1521 doc.attachEvent('onmousedown', mousedownListener);
1522 var activateListener = function(evt){
1523 // IE reports that nodes like <body> have gotten focus, even though they have tabIndex=-1,
1524 // Should consider those more like a mouse-click than a focus....
1525 if(evt.srcElement.tagName.toLowerCase() != "#document" &&
1526 dijit.isTabNavigable(evt.srcElement)){
1527 dijit._onFocusNode(effectiveNode || evt.srcElement);
1528 }else{
1529 dijit._onTouchNode(effectiveNode || evt.srcElement);
1530 }
1531 };
1532 doc.attachEvent('onactivate', activateListener);
1533 var deactivateListener = function(evt){
1534 dijit._onBlurNode(effectiveNode || evt.srcElement);
1535 };
1536 doc.attachEvent('ondeactivate', deactivateListener);
1537
1538 return function(){
1539 doc.detachEvent('onmousedown', mousedownListener);
1540 doc.detachEvent('onactivate', activateListener);
1541 doc.detachEvent('ondeactivate', deactivateListener);
1542 doc = null; // prevent memory leak (apparent circular reference via closure)
1543 };
1544 }else{
1545 doc.addEventListener('mousedown', mousedownListener, true);
1546 var focusListener = function(evt){
1547 dijit._onFocusNode(effectiveNode || evt.target);
1548 };
1549 doc.addEventListener('focus', focusListener, true);
1550 var blurListener = function(evt){
1551 dijit._onBlurNode(effectiveNode || evt.target);
1552 };
1553 doc.addEventListener('blur', blurListener, true);
1554
1555 return function(){
1556 doc.removeEventListener('mousedown', mousedownListener, true);
1557 doc.removeEventListener('focus', focusListener, true);
1558 doc.removeEventListener('blur', blurListener, true);
1559 doc = null; // prevent memory leak (apparent circular reference via closure)
1560 };
1561 }
1562 }
1563 },
1564
1565 unregisterWin: function(/*Handle*/ handle){
1566 // summary:
1567 // Unregisters listeners on the specified window (either the main
1568 // window or an iframe's window) according to handle returned from registerWin().
1569 // After calling be sure to delete or null out the handle itself.
1570
1571 // Currently our handle is actually a function
1572 handle && handle();
1573 },
1574
1575 _onBlurNode: function(/*DomNode*/ node){
1576 // summary:
1577 // Called when focus leaves a node.
1578 // Usually ignored, _unless_ it *isn't* follwed by touching another node,
1579 // which indicates that we tabbed off the last field on the page,
1580 // in which case every widget is marked inactive
1581 dijit._prevFocus = dijit._curFocus;
1582 dijit._curFocus = null;
1583
1584 if(dijit._justMouseDowned){
1585 // the mouse down caused a new widget to be marked as active; this blur event
1586 // is coming late, so ignore it.
1587 return;
1588 }
1589
1590 // if the blur event isn't followed by a focus event then mark all widgets as inactive.
1591 if(dijit._clearActiveWidgetsTimer){
1592 clearTimeout(dijit._clearActiveWidgetsTimer);
1593 }
1594 dijit._clearActiveWidgetsTimer = setTimeout(function(){
1595 delete dijit._clearActiveWidgetsTimer;
1596 dijit._setStack([]);
1597 dijit._prevFocus = null;
1598 }, 100);
1599 },
1600
1601 _onTouchNode: function(/*DomNode*/ node, /*String*/ by){
1602 // summary:
1603 // Callback when node is focused or mouse-downed
1604 // node:
1605 // The node that was touched.
1606 // by:
1607 // "mouse" if the focus/touch was caused by a mouse down event
1608
1609 // ignore the recent blurNode event
1610 if(dijit._clearActiveWidgetsTimer){
1611 clearTimeout(dijit._clearActiveWidgetsTimer);
1612 delete dijit._clearActiveWidgetsTimer;
1613 }
1614
1615 // compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem)
1616 var newStack=[];
1617 try{
1618 while(node){
1619 var popupParent = dojo.attr(node, "dijitPopupParent");
1620 if(popupParent){
1621 node=dijit.byId(popupParent).domNode;
1622 }else if(node.tagName && node.tagName.toLowerCase() == "body"){
1623 // is this the root of the document or just the root of an iframe?
1624 if(node === dojo.body()){
1625 // node is the root of the main document
1626 break;
1627 }
1628 // otherwise, find the iframe this node refers to (can't access it via parentNode,
1629 // need to do this trick instead). window.frameElement is supported in IE/FF/Webkit
1630 node=dojo.window.get(node.ownerDocument).frameElement;
1631 }else{
1632 // if this node is the root node of a widget, then add widget id to stack,
1633 // except ignore clicks on disabled widgets (actually focusing a disabled widget still works,
1634 // to support MenuItem)
1635 var id = node.getAttribute && node.getAttribute("widgetId"),
1636 widget = id && dijit.byId(id);
1637 if(widget && !(by == "mouse" && widget.get("disabled"))){
1638 newStack.unshift(id);
1639 }
1640 node=node.parentNode;
1641 }
1642 }
1643 }catch(e){ /* squelch */ }
1644
1645 dijit._setStack(newStack, by);
1646 },
1647
1648 _onFocusNode: function(/*DomNode*/ node){
1649 // summary:
1650 // Callback when node is focused
1651
1652 if(!node){
1653 return;
1654 }
1655
1656 if(node.nodeType == 9){
1657 // Ignore focus events on the document itself. This is here so that
1658 // (for example) clicking the up/down arrows of a spinner
1659 // (which don't get focus) won't cause that widget to blur. (FF issue)
1660 return;
1661 }
1662
1663 dijit._onTouchNode(node);
1664
1665 if(node == dijit._curFocus){ return; }
1666 if(dijit._curFocus){
1667 dijit._prevFocus = dijit._curFocus;
1668 }
1669 dijit._curFocus = node;
1670 dojo.publish("focusNode", [node]);
1671 },
1672
1673 _setStack: function(/*String[]*/ newStack, /*String*/ by){
1674 // summary:
1675 // The stack of active widgets has changed. Send out appropriate events and records new stack.
1676 // newStack:
1677 // array of widget id's, starting from the top (outermost) widget
1678 // by:
1679 // "mouse" if the focus/touch was caused by a mouse down event
1680
1681 var oldStack = dijit._activeStack;
1682 dijit._activeStack = newStack;
1683
1684 // compare old stack to new stack to see how many elements they have in common
1685 for(var nCommon=0; nCommon<Math.min(oldStack.length, newStack.length); nCommon++){
1686 if(oldStack[nCommon] != newStack[nCommon]){
1687 break;
1688 }
1689 }
1690
1691 var widget;
1692 // for all elements that have gone out of focus, send blur event
1693 for(var i=oldStack.length-1; i>=nCommon; i--){
1694 widget = dijit.byId(oldStack[i]);
1695 if(widget){
1696 widget._focused = false;
1697 widget._hasBeenBlurred = true;
1698 if(widget._onBlur){
1699 widget._onBlur(by);
1700 }
1701 dojo.publish("widgetBlur", [widget, by]);
1702 }
1703 }
1704
1705 // for all element that have come into focus, send focus event
1706 for(i=nCommon; i<newStack.length; i++){
1707 widget = dijit.byId(newStack[i]);
1708 if(widget){
1709 widget._focused = true;
1710 if(widget._onFocus){
1711 widget._onFocus(by);
1712 }
1713 dojo.publish("widgetFocus", [widget, by]);
1714 }
1715 }
1716 }
1717 });
1718
1719 // register top window and all the iframes it contains
1720 dojo.addOnLoad(function(){
1721 var handle = dijit.registerWin(window);
1722 if(dojo.isIE){
1723 dojo.addOnWindowUnload(function(){
1724 dijit.unregisterWin(handle);
1725 handle = null;
1726 })
1727 }
1728 });
1729
1730 }
1731
1732 if(!dojo._hasResource["dojo.AdapterRegistry"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1733 dojo._hasResource["dojo.AdapterRegistry"] = true;
1734 dojo.provide("dojo.AdapterRegistry");
1735
1736 dojo.AdapterRegistry = function(/*Boolean?*/ returnWrappers){
1737 // summary:
1738 // A registry to make contextual calling/searching easier.
1739 // description:
1740 // Objects of this class keep list of arrays in the form [name, check,
1741 // wrap, directReturn] that are used to determine what the contextual
1742 // result of a set of checked arguments is. All check/wrap functions
1743 // in this registry should be of the same arity.
1744 // example:
1745 // | // create a new registry
1746 // | var reg = new dojo.AdapterRegistry();
1747 // | reg.register("handleString",
1748 // | dojo.isString,
1749 // | function(str){
1750 // | // do something with the string here
1751 // | }
1752 // | );
1753 // | reg.register("handleArr",
1754 // | dojo.isArray,
1755 // | function(arr){
1756 // | // do something with the array here
1757 // | }
1758 // | );
1759 // |
1760 // | // now we can pass reg.match() *either* an array or a string and
1761 // | // the value we pass will get handled by the right function
1762 // | reg.match("someValue"); // will call the first function
1763 // | reg.match(["someValue"]); // will call the second
1764
1765 this.pairs = [];
1766 this.returnWrappers = returnWrappers || false; // Boolean
1767 }
1768
1769 dojo.extend(dojo.AdapterRegistry, {
1770 register: function(/*String*/ name, /*Function*/ check, /*Function*/ wrap, /*Boolean?*/ directReturn, /*Boolean?*/ override){
1771 // summary:
1772 // register a check function to determine if the wrap function or
1773 // object gets selected
1774 // name:
1775 // a way to identify this matcher.
1776 // check:
1777 // a function that arguments are passed to from the adapter's
1778 // match() function. The check function should return true if the
1779 // given arguments are appropriate for the wrap function.
1780 // directReturn:
1781 // If directReturn is true, the value passed in for wrap will be
1782 // returned instead of being called. Alternately, the
1783 // AdapterRegistry can be set globally to "return not call" using
1784 // the returnWrappers property. Either way, this behavior allows
1785 // the registry to act as a "search" function instead of a
1786 // function interception library.
1787 // override:
1788 // If override is given and true, the check function will be given
1789 // highest priority. Otherwise, it will be the lowest priority
1790 // adapter.
1791 this.pairs[((override) ? "unshift" : "push")]([name, check, wrap, directReturn]);
1792 },
1793
1794 match: function(/* ... */){
1795 // summary:
1796 // Find an adapter for the given arguments. If no suitable adapter
1797 // is found, throws an exception. match() accepts any number of
1798 // arguments, all of which are passed to all matching functions
1799 // from the registered pairs.
1800 for(var i = 0; i < this.pairs.length; i++){
1801 var pair = this.pairs[i];
1802 if(pair[1].apply(this, arguments)){
1803 if((pair[3])||(this.returnWrappers)){
1804 return pair[2];
1805 }else{
1806 return pair[2].apply(this, arguments);
1807 }
1808 }
1809 }
1810 throw new Error("No match found");
1811 },
1812
1813 unregister: function(name){
1814 // summary: Remove a named adapter from the registry
1815
1816 // FIXME: this is kind of a dumb way to handle this. On a large
1817 // registry this will be slow-ish and we can use the name as a lookup
1818 // should we choose to trade memory for speed.
1819 for(var i = 0; i < this.pairs.length; i++){
1820 var pair = this.pairs[i];
1821 if(pair[0] == name){
1822 this.pairs.splice(i, 1);
1823 return true;
1824 }
1825 }
1826 return false;
1827 }
1828 });
1829
1830 }
1831
1832 if(!dojo._hasResource["dijit._base.place"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1833 dojo._hasResource["dijit._base.place"] = true;
1834 dojo.provide("dijit._base.place");
1835
1836
1837
1838
1839
1840 dijit.getViewport = function(){
1841 // summary:
1842 // Returns the dimensions and scroll position of the viewable area of a browser window
1843
1844 return dojo.window.getBox();
1845 };
1846
1847 /*=====
1848 dijit.__Position = function(){
1849 // x: Integer
1850 // horizontal coordinate in pixels, relative to document body
1851 // y: Integer
1852 // vertical coordinate in pixels, relative to document body
1853
1854 thix.x = x;
1855 this.y = y;
1856 }
1857 =====*/
1858
1859
1860 dijit.placeOnScreen = function(
1861 /* DomNode */ node,
1862 /* dijit.__Position */ pos,
1863 /* String[] */ corners,
1864 /* dijit.__Position? */ padding){
1865 // summary:
1866 // Positions one of the node's corners at specified position
1867 // such that node is fully visible in viewport.
1868 // description:
1869 // NOTE: node is assumed to be absolutely or relatively positioned.
1870 // pos:
1871 // Object like {x: 10, y: 20}
1872 // corners:
1873 // Array of Strings representing order to try corners in, like ["TR", "BL"].
1874 // Possible values are:
1875 // * "BL" - bottom left
1876 // * "BR" - bottom right
1877 // * "TL" - top left
1878 // * "TR" - top right
1879 // padding:
1880 // set padding to put some buffer around the element you want to position.
1881 // example:
1882 // Try to place node's top right corner at (10,20).
1883 // If that makes node go (partially) off screen, then try placing
1884 // bottom left corner at (10,20).
1885 // | placeOnScreen(node, {x: 10, y: 20}, ["TR", "BL"])
1886
1887 var choices = dojo.map(corners, function(corner){
1888 var c = { corner: corner, pos: {x:pos.x,y:pos.y} };
1889 if(padding){
1890 c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x;
1891 c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y;
1892 }
1893 return c;
1894 });
1895
1896 return dijit._place(node, choices);
1897 }
1898
1899 dijit._place = function(/*DomNode*/ node, /* Array */ choices, /* Function */ layoutNode){
1900 // summary:
1901 // Given a list of spots to put node, put it at the first spot where it fits,
1902 // of if it doesn't fit anywhere then the place with the least overflow
1903 // choices: Array
1904 // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} }
1905 // Above example says to put the top-left corner of the node at (10,20)
1906 // layoutNode: Function(node, aroundNodeCorner, nodeCorner)
1907 // for things like tooltip, they are displayed differently (and have different dimensions)
1908 // based on their orientation relative to the parent. This adjusts the popup based on orientation.
1909
1910 // get {x: 10, y: 10, w: 100, h:100} type obj representing position of
1911 // viewport over document
1912 var view = dojo.window.getBox();
1913
1914 // This won't work if the node is inside a <div style="position: relative">,
1915 // so reattach it to dojo.doc.body. (Otherwise, the positioning will be wrong
1916 // and also it might get cutoff)
1917 if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){
1918 dojo.body().appendChild(node);
1919 }
1920
1921 var best = null;
1922 dojo.some(choices, function(choice){
1923 var corner = choice.corner;
1924 var pos = choice.pos;
1925
1926 // configure node to be displayed in given position relative to button
1927 // (need to do this in order to get an accurate size for the node, because
1928 // a tooltips size changes based on position, due to triangle)
1929 if(layoutNode){
1930 layoutNode(node, choice.aroundCorner, corner);
1931 }
1932
1933 // get node's size
1934 var style = node.style;
1935 var oldDisplay = style.display;
1936 var oldVis = style.visibility;
1937 style.visibility = "hidden";
1938 style.display = "";
1939 var mb = dojo.marginBox(node);
1940 style.display = oldDisplay;
1941 style.visibility = oldVis;
1942
1943 // coordinates and size of node with specified corner placed at pos,
1944 // and clipped by viewport
1945 var startX = Math.max(view.l, corner.charAt(1) == 'L' ? pos.x : (pos.x - mb.w)),
1946 startY = Math.max(view.t, corner.charAt(0) == 'T' ? pos.y : (pos.y - mb.h)),
1947 endX = Math.min(view.l + view.w, corner.charAt(1) == 'L' ? (startX + mb.w) : pos.x),
1948 endY = Math.min(view.t + view.h, corner.charAt(0) == 'T' ? (startY + mb.h) : pos.y),
1949 width = endX - startX,
1950 height = endY - startY,
1951 overflow = (mb.w - width) + (mb.h - height);
1952
1953 if(best == null || overflow < best.overflow){
1954 best = {
1955 corner: corner,
1956 aroundCorner: choice.aroundCorner,
1957 x: startX,
1958 y: startY,
1959 w: width,
1960 h: height,
1961 overflow: overflow
1962 };
1963 }
1964 return !overflow;
1965 });
1966
1967 node.style.left = best.x + "px";
1968 node.style.top = best.y + "px";
1969 if(best.overflow && layoutNode){
1970 layoutNode(node, best.aroundCorner, best.corner);
1971 }
1972 return best;
1973 }
1974
1975 dijit.placeOnScreenAroundNode = function(
1976 /* DomNode */ node,
1977 /* DomNode */ aroundNode,
1978 /* Object */ aroundCorners,
1979 /* Function? */ layoutNode){
1980
1981 // summary:
1982 // Position node adjacent or kitty-corner to aroundNode
1983 // such that it's fully visible in viewport.
1984 //
1985 // description:
1986 // Place node such that corner of node touches a corner of
1987 // aroundNode, and that node is fully visible.
1988 //
1989 // aroundCorners:
1990 // Ordered list of pairs of corners to try matching up.
1991 // Each pair of corners is represented as a key/value in the hash,
1992 // where the key corresponds to the aroundNode's corner, and
1993 // the value corresponds to the node's corner:
1994 //
1995 // | { aroundNodeCorner1: nodeCorner1, aroundNodeCorner2: nodeCorner2, ...}
1996 //
1997 // The following strings are used to represent the four corners:
1998 // * "BL" - bottom left
1999 // * "BR" - bottom right
2000 // * "TL" - top left
2001 // * "TR" - top right
2002 //
2003 // layoutNode: Function(node, aroundNodeCorner, nodeCorner)
2004 // For things like tooltip, they are displayed differently (and have different dimensions)
2005 // based on their orientation relative to the parent. This adjusts the popup based on orientation.
2006 //
2007 // example:
2008 // | dijit.placeOnScreenAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'});
2009 // This will try to position node such that node's top-left corner is at the same position
2010 // as the bottom left corner of the aroundNode (ie, put node below
2011 // aroundNode, with left edges aligned). If that fails it will try to put
2012 // the bottom-right corner of node where the top right corner of aroundNode is
2013 // (ie, put node above aroundNode, with right edges aligned)
2014 //
2015
2016 // get coordinates of aroundNode
2017 aroundNode = dojo.byId(aroundNode);
2018 var oldDisplay = aroundNode.style.display;
2019 aroundNode.style.display="";
2020 // #3172: use the slightly tighter border box instead of marginBox
2021 var aroundNodePos = dojo.position(aroundNode, true);
2022 aroundNode.style.display=oldDisplay;
2023
2024 // place the node around the calculated rectangle
2025 return dijit._placeOnScreenAroundRect(node,
2026 aroundNodePos.x, aroundNodePos.y, aroundNodePos.w, aroundNodePos.h, // rectangle
2027 aroundCorners, layoutNode);
2028 };
2029
2030 /*=====
2031 dijit.__Rectangle = function(){
2032 // x: Integer
2033 // horizontal offset in pixels, relative to document body
2034 // y: Integer
2035 // vertical offset in pixels, relative to document body
2036 // width: Integer
2037 // width in pixels
2038 // height: Integer
2039 // height in pixels
2040
2041 this.x = x;
2042 this.y = y;
2043 this.width = width;
2044 this.height = height;
2045 }
2046 =====*/
2047
2048
2049 dijit.placeOnScreenAroundRectangle = function(
2050 /* DomNode */ node,
2051 /* dijit.__Rectangle */ aroundRect,
2052 /* Object */ aroundCorners,
2053 /* Function */ layoutNode){
2054
2055 // summary:
2056 // Like dijit.placeOnScreenAroundNode(), except that the "around"
2057 // parameter is an arbitrary rectangle on the screen (x, y, width, height)
2058 // instead of a dom node.
2059
2060 return dijit._placeOnScreenAroundRect(node,
2061 aroundRect.x, aroundRect.y, aroundRect.width, aroundRect.height, // rectangle
2062 aroundCorners, layoutNode);
2063 };
2064
2065 dijit._placeOnScreenAroundRect = function(
2066 /* DomNode */ node,
2067 /* Number */ x,
2068 /* Number */ y,
2069 /* Number */ width,
2070 /* Number */ height,
2071 /* Object */ aroundCorners,
2072 /* Function */ layoutNode){
2073
2074 // summary:
2075 // Like dijit.placeOnScreenAroundNode(), except it accepts coordinates
2076 // of a rectangle to place node adjacent to.
2077
2078 // TODO: combine with placeOnScreenAroundRectangle()
2079
2080 // Generate list of possible positions for node
2081 var choices = [];
2082 for(var nodeCorner in aroundCorners){
2083 choices.push( {
2084 aroundCorner: nodeCorner,
2085 corner: aroundCorners[nodeCorner],
2086 pos: {
2087 x: x + (nodeCorner.charAt(1) == 'L' ? 0 : width),
2088 y: y + (nodeCorner.charAt(0) == 'T' ? 0 : height)
2089 }
2090 });
2091 }
2092
2093 return dijit._place(node, choices, layoutNode);
2094 };
2095
2096 dijit.placementRegistry= new dojo.AdapterRegistry();
2097 dijit.placementRegistry.register("node",
2098 function(n, x){
2099 return typeof x == "object" &&
2100 typeof x.offsetWidth != "undefined" && typeof x.offsetHeight != "undefined";
2101 },
2102 dijit.placeOnScreenAroundNode);
2103 dijit.placementRegistry.register("rect",
2104 function(n, x){
2105 return typeof x == "object" &&
2106 "x" in x && "y" in x && "width" in x && "height" in x;
2107 },
2108 dijit.placeOnScreenAroundRectangle);
2109
2110 dijit.placeOnScreenAroundElement = function(
2111 /* DomNode */ node,
2112 /* Object */ aroundElement,
2113 /* Object */ aroundCorners,
2114 /* Function */ layoutNode){
2115
2116 // summary:
2117 // Like dijit.placeOnScreenAroundNode(), except it accepts an arbitrary object
2118 // for the "around" argument and finds a proper processor to place a node.
2119
2120 return dijit.placementRegistry.match.apply(dijit.placementRegistry, arguments);
2121 };
2122
2123 dijit.getPopupAroundAlignment = function(/*Array*/ position, /*Boolean*/ leftToRight){
2124 // summary:
2125 // Transforms the passed array of preferred positions into a format suitable for passing as the aroundCorners argument to dijit.placeOnScreenAroundElement.
2126 //
2127 // position: String[]
2128 // This variable controls the position of the drop down.
2129 // It's an array of strings with the following values:
2130 //
2131 // * before: places drop down to the left of the target node/widget, or to the right in
2132 // the case of RTL scripts like Hebrew and Arabic
2133 // * after: places drop down to the right of the target node/widget, or to the left in
2134 // the case of RTL scripts like Hebrew and Arabic
2135 // * above: drop down goes above target node
2136 // * below: drop down goes below target node
2137 //
2138 // The list is positions is tried, in order, until a position is found where the drop down fits
2139 // within the viewport.
2140 //
2141 // leftToRight: Boolean
2142 // Whether the popup will be displaying in leftToRight mode.
2143 //
2144 var align = {};
2145 dojo.forEach(position, function(pos){
2146 switch(pos){
2147 case "after":
2148 align[leftToRight ? "BR" : "BL"] = leftToRight ? "BL" : "BR";
2149 break;
2150 case "before":
2151 align[leftToRight ? "BL" : "BR"] = leftToRight ? "BR" : "BL";
2152 break;
2153 case "below":
2154 // first try to align left borders, next try to align right borders (or reverse for RTL mode)
2155 align[leftToRight ? "BL" : "BR"] = leftToRight ? "TL" : "TR";
2156 align[leftToRight ? "BR" : "BL"] = leftToRight ? "TR" : "TL";
2157 break;
2158 case "above":
2159 default:
2160 // first try to align left borders, next try to align right borders (or reverse for RTL mode)
2161 align[leftToRight ? "TL" : "TR"] = leftToRight ? "BL" : "BR";
2162 align[leftToRight ? "TR" : "TL"] = leftToRight ? "BR" : "BL";
2163 break;
2164 }
2165 });
2166 return align;
2167 };
2168
2169 }
2170
2171 if(!dojo._hasResource["dijit._base.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2172 dojo._hasResource["dijit._base.window"] = true;
2173 dojo.provide("dijit._base.window");
2174
2175
2176
2177 dijit.getDocumentWindow = function(doc){
2178 return dojo.window.get(doc);
2179 };
2180
2181 }
2182
2183 if(!dojo._hasResource["dijit._base.popup"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2184 dojo._hasResource["dijit._base.popup"] = true;
2185 dojo.provide("dijit._base.popup");
2186
2187
2188
2189
2190
2191 /*=====
2192 dijit.popup.__OpenArgs = function(){
2193 // popup: Widget
2194 // widget to display
2195 // parent: Widget
2196 // the button etc. that is displaying this popup
2197 // around: DomNode
2198 // DOM node (typically a button); place popup relative to this node. (Specify this *or* "x" and "y" parameters.)
2199 // x: Integer
2200 // Absolute horizontal position (in pixels) to place node at. (Specify this *or* "around" parameter.)
2201 // y: Integer
2202 // Absolute vertical position (in pixels) to place node at. (Specify this *or* "around" parameter.)
2203 // orient: Object|String
2204 // When the around parameter is specified, orient should be an
2205 // ordered list of tuples of the form (around-node-corner, popup-node-corner).
2206 // dijit.popup.open() tries to position the popup according to each tuple in the list, in order,
2207 // until the popup appears fully within the viewport.
2208 //
2209 // The default value is {BL:'TL', TL:'BL'}, which represents a list of two tuples:
2210 // 1. (BL, TL)
2211 // 2. (TL, BL)
2212 // where BL means "bottom left" and "TL" means "top left".
2213 // So by default, it first tries putting the popup below the around node, left-aligning them,
2214 // and then tries to put it above the around node, still left-aligning them. Note that the
2215 // default is horizontally reversed when in RTL mode.
2216 //
2217 // When an (x,y) position is specified rather than an around node, orient is either
2218 // "R" or "L". R (for right) means that it tries to put the popup to the right of the mouse,
2219 // specifically positioning the popup's top-right corner at the mouse position, and if that doesn't
2220 // fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner,
2221 // and the top-right corner.
2222 // onCancel: Function
2223 // callback when user has canceled the popup by
2224 // 1. hitting ESC or
2225 // 2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog);
2226 // i.e. whenever popupWidget.onCancel() is called, args.onCancel is called
2227 // onClose: Function
2228 // callback whenever this popup is closed
2229 // onExecute: Function
2230 // callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only)
2231 // padding: dijit.__Position
2232 // adding a buffer around the opening position. This is only useful when around is not set.
2233 this.popup = popup;
2234 this.parent = parent;
2235 this.around = around;
2236 this.x = x;
2237 this.y = y;
2238 this.orient = orient;
2239 this.onCancel = onCancel;
2240 this.onClose = onClose;
2241 this.onExecute = onExecute;
2242 this.padding = padding;
2243 }
2244 =====*/
2245
2246 dijit.popup = {
2247 // summary:
2248 // This singleton is used to show/hide widgets as popups.
2249
2250 // _stack: dijit._Widget[]
2251 // Stack of currently popped up widgets.
2252 // (someone opened _stack[0], and then it opened _stack[1], etc.)
2253 _stack: [],
2254
2255 // _beginZIndex: Number
2256 // Z-index of the first popup. (If first popup opens other
2257 // popups they get a higher z-index.)
2258 _beginZIndex: 1000,
2259
2260 _idGen: 1,
2261
2262 moveOffScreen: function(/*DomNode*/ node){
2263 // summary:
2264 // Initialization for nodes that will be used as popups
2265 //
2266 // description:
2267 // Puts node inside a wrapper <div>, and
2268 // positions wrapper div off screen, but not display:none, so that
2269 // the widget doesn't appear in the page flow and/or cause a blank
2270 // area at the bottom of the viewport (making scrollbar longer), but
2271 // initialization of contained widgets works correctly
2272
2273 var wrapper = node.parentNode;
2274
2275 // Create a wrapper widget for when this node (in the future) will be used as a popup.
2276 // This is done early because of IE bugs where creating/moving DOM nodes causes focus
2277 // to go wonky, see tests/robot/Toolbar.html to reproduce
2278 if(!wrapper || !dojo.hasClass(wrapper, "dijitPopup")){
2279 wrapper = dojo.create("div",{
2280 "class":"dijitPopup",
2281 style:{
2282 visibility:"hidden",
2283 top: "-9999px"
2284 }
2285 }, dojo.body());
2286 dijit.setWaiRole(wrapper, "presentation");
2287 wrapper.appendChild(node);
2288 }
2289
2290
2291 var s = node.style;
2292 s.display = "";
2293 s.visibility = "";
2294 s.position = "";
2295 s.top = "0px";
2296
2297 dojo.style(wrapper, {
2298 visibility: "hidden",
2299 // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
2300 top: "-9999px"
2301 });
2302 },
2303
2304 getTopPopup: function(){
2305 // summary:
2306 // Compute the closest ancestor popup that's *not* a child of another popup.
2307 // Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button.
2308 var stack = this._stack;
2309 for(var pi=stack.length-1; pi > 0 && stack[pi].parent === stack[pi-1].widget; pi--){
2310 /* do nothing, just trying to get right value for pi */
2311 }
2312 return stack[pi];
2313 },
2314
2315 open: function(/*dijit.popup.__OpenArgs*/ args){
2316 // summary:
2317 // Popup the widget at the specified position
2318 //
2319 // example:
2320 // opening at the mouse position
2321 // | dijit.popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
2322 //
2323 // example:
2324 // opening the widget as a dropdown
2325 // | dijit.popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
2326 //
2327 // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback
2328 // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed.
2329
2330 var stack = this._stack,
2331 widget = args.popup,
2332 orient = args.orient || (
2333 (args.parent ? args.parent.isLeftToRight() : dojo._isBodyLtr()) ?
2334 {'BL':'TL', 'BR':'TR', 'TL':'BL', 'TR':'BR'} :
2335 {'BR':'TR', 'BL':'TL', 'TR':'BR', 'TL':'BL'}
2336 ),
2337 around = args.around,
2338 id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++);
2339
2340
2341 // The wrapper may have already been created, but in case it wasn't, create here
2342 var wrapper = widget.domNode.parentNode;
2343 if(!wrapper || !dojo.hasClass(wrapper, "dijitPopup")){
2344 this.moveOffScreen(widget.domNode);
2345 wrapper = widget.domNode.parentNode;
2346 }
2347
2348 dojo.attr(wrapper, {
2349 id: id,
2350 style: {
2351 zIndex: this._beginZIndex + stack.length
2352 },
2353 "class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup",
2354 dijitPopupParent: args.parent ? args.parent.id : ""
2355 });
2356
2357 if(dojo.isIE || dojo.isMoz){
2358 var iframe = wrapper.childNodes[1];
2359 if(!iframe){
2360 iframe = new dijit.BackgroundIframe(wrapper);
2361 }
2362 }
2363
2364 // position the wrapper node and make it visible
2365 var best = around ?
2366 dijit.placeOnScreenAroundElement(wrapper, around, orient, widget.orient ? dojo.hitch(widget, "orient") : null) :
2367 dijit.placeOnScreen(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding);
2368
2369 wrapper.style.visibility = "visible";
2370 widget.domNode.style.visibility = "visible"; // counteract effects from _HasDropDown
2371
2372 var handlers = [];
2373
2374 // provide default escape and tab key handling
2375 // (this will work for any widget, not just menu)
2376 handlers.push(dojo.connect(wrapper, "onkeypress", this, function(evt){
2377 if(evt.charOrCode == dojo.keys.ESCAPE && args.onCancel){
2378 dojo.stopEvent(evt);
2379 args.onCancel();
2380 }else if(evt.charOrCode === dojo.keys.TAB){
2381 dojo.stopEvent(evt);
2382 var topPopup = this.getTopPopup();
2383 if(topPopup && topPopup.onCancel){
2384 topPopup.onCancel();
2385 }
2386 }
2387 }));
2388
2389 // watch for cancel/execute events on the popup and notify the caller
2390 // (for a menu, "execute" means clicking an item)
2391 if(widget.onCancel){
2392 handlers.push(dojo.connect(widget, "onCancel", args.onCancel));
2393 }
2394
2395 handlers.push(dojo.connect(widget, widget.onExecute ? "onExecute" : "onChange", this, function(){
2396 var topPopup = this.getTopPopup();
2397 if(topPopup && topPopup.onExecute){
2398 topPopup.onExecute();
2399 }
2400 }));
2401
2402 stack.push({
2403 wrapper: wrapper,
2404 iframe: iframe,
2405 widget: widget,
2406 parent: args.parent,
2407 onExecute: args.onExecute,
2408 onCancel: args.onCancel,
2409 onClose: args.onClose,
2410 handlers: handlers
2411 });
2412
2413 if(widget.onOpen){
2414 // TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here)
2415 widget.onOpen(best);
2416 }
2417
2418 return best;
2419 },
2420
2421 close: function(/*dijit._Widget*/ popup){
2422 // summary:
2423 // Close specified popup and any popups that it parented
2424
2425 var stack = this._stack;
2426
2427 // Basically work backwards from the top of the stack closing popups
2428 // until we hit the specified popup, but IIRC there was some issue where closing
2429 // a popup would cause others to close too. Thus if we are trying to close B in [A,B,C]
2430 // closing C might close B indirectly and then the while() condition will run where stack==[A]...
2431 // so the while condition is constructed defensively.
2432 while(dojo.some(stack, function(elem){return elem.widget == popup;})){
2433 var top = stack.pop(),
2434 wrapper = top.wrapper,
2435 iframe = top.iframe,
2436 widget = top.widget,
2437 onClose = top.onClose;
2438
2439 if(widget.onClose){
2440 // TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here)
2441 widget.onClose();
2442 }
2443 dojo.forEach(top.handlers, dojo.disconnect);
2444
2445 // Move the widget plus it's wrapper off screen, unless it has already been destroyed in above onClose() etc.
2446 if(widget && widget.domNode){
2447 this.moveOffScreen(widget.domNode);
2448 }else{
2449 dojo.destroy(wrapper);
2450 }
2451
2452 if(onClose){
2453 onClose();
2454 }
2455 }
2456 }
2457 };
2458
2459 dijit._frames = new function(){
2460 // summary:
2461 // cache of iframes
2462 var queue = [];
2463
2464 this.pop = function(){
2465 var iframe;
2466 if(queue.length){
2467 iframe = queue.pop();
2468 iframe.style.display="";
2469 }else{
2470 if(dojo.isIE){
2471 var burl = dojo.config["dojoBlankHtmlUrl"] || (dojo.moduleUrl("dojo", "resources/blank.html")+"") || "javascript:\"\"";
2472 var html="<iframe src='" + burl + "'"
2473 + " style='position: absolute; left: 0px; top: 0px;"
2474 + "z-index: -1; filter:Alpha(Opacity=\"0\");'>";
2475 iframe = dojo.doc.createElement(html);
2476 }else{
2477 iframe = dojo.create("iframe");
2478 iframe.src = 'javascript:""';
2479 iframe.className = "dijitBackgroundIframe";
2480 dojo.style(iframe, "opacity", 0.1);
2481 }
2482 iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didnt work.
2483 dijit.setWaiRole(iframe,"presentation");
2484 }
2485 return iframe;
2486 };
2487
2488 this.push = function(iframe){
2489 iframe.style.display="none";
2490 queue.push(iframe);
2491 }
2492 }();
2493
2494
2495 dijit.BackgroundIframe = function(/* DomNode */node){
2496 // summary:
2497 // For IE/FF z-index schenanigans. id attribute is required.
2498 //
2499 // description:
2500 // new dijit.BackgroundIframe(node)
2501 // Makes a background iframe as a child of node, that fills
2502 // area (and position) of node
2503
2504 if(!node.id){ throw new Error("no id"); }
2505 if(dojo.isIE || dojo.isMoz){
2506 var iframe = dijit._frames.pop();
2507 node.appendChild(iframe);
2508 if(dojo.isIE<7){
2509 this.resize(node);
2510 this._conn = dojo.connect(node, 'onresize', this, function(){
2511 this.resize(node);
2512 });
2513 }else{
2514 dojo.style(iframe, {
2515 width: '100%',
2516 height: '100%'
2517 });
2518 }
2519 this.iframe = iframe;
2520 }
2521 };
2522
2523 dojo.extend(dijit.BackgroundIframe, {
2524 resize: function(node){
2525 // summary:
2526 // resize the iframe so its the same size as node
2527 // description:
2528 // this function is a no-op in all browsers except
2529 // IE6, which does not support 100% width/height
2530 // of absolute positioned iframes
2531 if(this.iframe && dojo.isIE<7){
2532 dojo.style(this.iframe, {
2533 width: node.offsetWidth + 'px',
2534 height: node.offsetHeight + 'px'
2535 });
2536 }
2537 },
2538 destroy: function(){
2539 // summary:
2540 // destroy the iframe
2541 if(this._conn){
2542 dojo.disconnect(this._conn);
2543 this._conn = null;
2544 }
2545 if(this.iframe){
2546 dijit._frames.push(this.iframe);
2547 delete this.iframe;
2548 }
2549 }
2550 });
2551
2552 }
2553
2554 if(!dojo._hasResource["dijit._base.scroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2555 dojo._hasResource["dijit._base.scroll"] = true;
2556 dojo.provide("dijit._base.scroll");
2557
2558
2559
2560 dijit.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
2561 // summary:
2562 // Scroll the passed node into view, if it is not already.
2563 // Deprecated, use `dojo.window.scrollIntoView` instead.
2564
2565 dojo.window.scrollIntoView(node, pos);
2566 };
2567
2568 }
2569
2570 if(!dojo._hasResource["dojo.uacss"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2571 dojo._hasResource["dojo.uacss"] = true;
2572 dojo.provide("dojo.uacss");
2573
2574 (function(){
2575 // summary:
2576 // Applies pre-set CSS classes to the top-level HTML node, based on:
2577 // - browser (ex: dj_ie)
2578 // - browser version (ex: dj_ie6)
2579 // - box model (ex: dj_contentBox)
2580 // - text direction (ex: dijitRtl)
2581 //
2582 // In addition, browser, browser version, and box model are
2583 // combined with an RTL flag when browser text is RTL. ex: dj_ie-rtl.
2584
2585 var d = dojo,
2586 html = d.doc.documentElement,
2587 ie = d.isIE,
2588 opera = d.isOpera,
2589 maj = Math.floor,
2590 ff = d.isFF,
2591 boxModel = d.boxModel.replace(/-/,''),
2592
2593 classes = {
2594 dj_ie: ie,
2595 dj_ie6: maj(ie) == 6,
2596 dj_ie7: maj(ie) == 7,
2597 dj_ie8: maj(ie) == 8,
2598 dj_quirks: d.isQuirks,
2599 dj_iequirks: ie && d.isQuirks,
2600
2601 // NOTE: Opera not supported by dijit
2602 dj_opera: opera,
2603
2604 dj_khtml: d.isKhtml,
2605
2606 dj_webkit: d.isWebKit,
2607 dj_safari: d.isSafari,
2608 dj_chrome: d.isChrome,
2609
2610 dj_gecko: d.isMozilla,
2611 dj_ff3: maj(ff) == 3
2612 }; // no dojo unsupported browsers
2613
2614 classes["dj_" + boxModel] = true;
2615
2616 // apply browser, browser version, and box model class names
2617 var classStr = "";
2618 for(var clz in classes){
2619 if(classes[clz]){
2620 classStr += clz + " ";
2621 }
2622 }
2623 html.className = d.trim(html.className + " " + classStr);
2624
2625 // If RTL mode, then add dj_rtl flag plus repeat existing classes with -rtl extension.
2626 // We can't run the code below until the <body> tag has loaded (so we can check for dir=rtl).
2627 // Unshift() is to run sniff code before the parser.
2628 dojo._loaders.unshift(function(){
2629 if(!dojo._isBodyLtr()){
2630 var rtlClassStr = "dj_rtl dijitRtl " + classStr.replace(/ /g, "-rtl ")
2631 html.className = d.trim(html.className + " " + rtlClassStr);
2632 }
2633 });
2634 })();
2635
2636 }
2637
2638 if(!dojo._hasResource["dijit._base.sniff"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2639 dojo._hasResource["dijit._base.sniff"] = true;
2640 // summary:
2641 // Applies pre-set CSS classes to the top-level HTML node, see
2642 // `dojo.uacss` for details.
2643 //
2644 // Simply doing a require on this module will
2645 // establish this CSS. Modified version of Morris' CSS hack.
2646
2647 dojo.provide("dijit._base.sniff");
2648
2649
2650
2651 }
2652
2653 if(!dojo._hasResource["dijit._base.typematic"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2654 dojo._hasResource["dijit._base.typematic"] = true;
2655 dojo.provide("dijit._base.typematic");
2656
2657 dijit.typematic = {
2658 // summary:
2659 // These functions are used to repetitively call a user specified callback
2660 // method when a specific key or mouse click over a specific DOM node is
2661 // held down for a specific amount of time.
2662 // Only 1 such event is allowed to occur on the browser page at 1 time.
2663
2664 _fireEventAndReload: function(){
2665 this._timer = null;
2666 this._callback(++this._count, this._node, this._evt);
2667
2668 // Schedule next event, timer is at most minDelay (default 10ms) to avoid
2669 // browser overload (particularly avoiding starving DOH robot so it never gets to send a mouseup)
2670 this._currentTimeout = Math.max(
2671 this._currentTimeout < 0 ? this._initialDelay :
2672 (this._subsequentDelay > 1 ? this._subsequentDelay : Math.round(this._currentTimeout * this._subsequentDelay)),
2673 this._minDelay);
2674 this._timer = setTimeout(dojo.hitch(this, "_fireEventAndReload"), this._currentTimeout);
2675 },
2676
2677 trigger: function(/*Event*/ evt, /*Object*/ _this, /*DOMNode*/ node, /*Function*/ callback, /*Object*/ obj, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
2678 // summary:
2679 // Start a timed, repeating callback sequence.
2680 // If already started, the function call is ignored.
2681 // This method is not normally called by the user but can be
2682 // when the normal listener code is insufficient.
2683 // evt:
2684 // key or mouse event object to pass to the user callback
2685 // _this:
2686 // pointer to the user's widget space.
2687 // node:
2688 // the DOM node object to pass the the callback function
2689 // callback:
2690 // function to call until the sequence is stopped called with 3 parameters:
2691 // count:
2692 // integer representing number of repeated calls (0..n) with -1 indicating the iteration has stopped
2693 // node:
2694 // the DOM node object passed in
2695 // evt:
2696 // key or mouse event object
2697 // obj:
2698 // user space object used to uniquely identify each typematic sequence
2699 // subsequentDelay (optional):
2700 // if > 1, the number of milliseconds until the 3->n events occur
2701 // or else the fractional time multiplier for the next event's delay, default=0.9
2702 // initialDelay (optional):
2703 // the number of milliseconds until the 2nd event occurs, default=500ms
2704 // minDelay (optional):
2705 // the maximum delay in milliseconds for event to fire, default=10ms
2706 if(obj != this._obj){
2707 this.stop();
2708 this._initialDelay = initialDelay || 500;
2709 this._subsequentDelay = subsequentDelay || 0.90;
2710 this._minDelay = minDelay || 10;
2711 this._obj = obj;
2712 this._evt = evt;
2713 this._node = node;
2714 this._currentTimeout = -1;
2715 this._count = -1;
2716 this._callback = dojo.hitch(_this, callback);
2717 this._fireEventAndReload();
2718 this._evt = dojo.mixin({faux: true}, evt);
2719 }
2720 },
2721
2722 stop: function(){
2723 // summary:
2724 // Stop an ongoing timed, repeating callback sequence.
2725 if(this._timer){
2726 clearTimeout(this._timer);
2727 this._timer = null;
2728 }
2729 if(this._obj){
2730 this._callback(-1, this._node, this._evt);
2731 this._obj = null;
2732 }
2733 },
2734
2735 addKeyListener: function(/*DOMNode*/ node, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
2736 // summary:
2737 // Start listening for a specific typematic key.
2738 // See also the trigger method for other parameters.
2739 // keyObject:
2740 // an object defining the key to listen for:
2741 // charOrCode:
2742 // the printable character (string) or keyCode (number) to listen for.
2743 // keyCode:
2744 // (deprecated - use charOrCode) the keyCode (number) to listen for (implies charCode = 0).
2745 // charCode:
2746 // (deprecated - use charOrCode) the charCode (number) to listen for.
2747 // ctrlKey:
2748 // desired ctrl key state to initiate the callback sequence:
2749 // - pressed (true)
2750 // - released (false)
2751 // - either (unspecified)
2752 // altKey:
2753 // same as ctrlKey but for the alt key
2754 // shiftKey:
2755 // same as ctrlKey but for the shift key
2756 // returns:
2757 // an array of dojo.connect handles
2758 if(keyObject.keyCode){
2759 keyObject.charOrCode = keyObject.keyCode;
2760 dojo.deprecated("keyCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0");
2761 }else if(keyObject.charCode){
2762 keyObject.charOrCode = String.fromCharCode(keyObject.charCode);
2763 dojo.deprecated("charCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0");
2764 }
2765 return [
2766 dojo.connect(node, "onkeypress", this, function(evt){
2767 if(evt.charOrCode == keyObject.charOrCode &&
2768 (keyObject.ctrlKey === undefined || keyObject.ctrlKey == evt.ctrlKey) &&
2769 (keyObject.altKey === undefined || keyObject.altKey == evt.altKey) &&
2770 (keyObject.metaKey === undefined || keyObject.metaKey == (evt.metaKey || false)) && // IE doesn't even set metaKey
2771 (keyObject.shiftKey === undefined || keyObject.shiftKey == evt.shiftKey)){
2772 dojo.stopEvent(evt);
2773 dijit.typematic.trigger(evt, _this, node, callback, keyObject, subsequentDelay, initialDelay, minDelay);
2774 }else if(dijit.typematic._obj == keyObject){
2775 dijit.typematic.stop();
2776 }
2777 }),
2778 dojo.connect(node, "onkeyup", this, function(evt){
2779 if(dijit.typematic._obj == keyObject){
2780 dijit.typematic.stop();
2781 }
2782 })
2783 ];
2784 },
2785
2786 addMouseListener: function(/*DOMNode*/ node, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
2787 // summary:
2788 // Start listening for a typematic mouse click.
2789 // See the trigger method for other parameters.
2790 // returns:
2791 // an array of dojo.connect handles
2792 var dc = dojo.connect;
2793 return [
2794 dc(node, "mousedown", this, function(evt){
2795 dojo.stopEvent(evt);
2796 dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
2797 }),
2798 dc(node, "mouseup", this, function(evt){
2799 dojo.stopEvent(evt);
2800 dijit.typematic.stop();
2801 }),
2802 dc(node, "mouseout", this, function(evt){
2803 dojo.stopEvent(evt);
2804 dijit.typematic.stop();
2805 }),
2806 dc(node, "mousemove", this, function(evt){
2807 evt.preventDefault();
2808 }),
2809 dc(node, "dblclick", this, function(evt){
2810 dojo.stopEvent(evt);
2811 if(dojo.isIE){
2812 dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
2813 setTimeout(dojo.hitch(this, dijit.typematic.stop), 50);
2814 }
2815 })
2816 ];
2817 },
2818
2819 addListener: function(/*Node*/ mouseNode, /*Node*/ keyNode, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
2820 // summary:
2821 // Start listening for a specific typematic key and mouseclick.
2822 // This is a thin wrapper to addKeyListener and addMouseListener.
2823 // See the addMouseListener and addKeyListener methods for other parameters.
2824 // mouseNode:
2825 // the DOM node object to listen on for mouse events.
2826 // keyNode:
2827 // the DOM node object to listen on for key events.
2828 // returns:
2829 // an array of dojo.connect handles
2830 return this.addKeyListener(keyNode, keyObject, _this, callback, subsequentDelay, initialDelay, minDelay).concat(
2831 this.addMouseListener(mouseNode, _this, callback, subsequentDelay, initialDelay, minDelay));
2832 }
2833 };
2834
2835 }
2836
2837 if(!dojo._hasResource["dijit._base.wai"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2838 dojo._hasResource["dijit._base.wai"] = true;
2839 dojo.provide("dijit._base.wai");
2840
2841 dijit.wai = {
2842 onload: function(){
2843 // summary:
2844 // Detects if we are in high-contrast mode or not
2845
2846 // This must be a named function and not an anonymous
2847 // function, so that the widget parsing code can make sure it
2848 // registers its onload function after this function.
2849 // DO NOT USE "this" within this function.
2850
2851 // create div for testing if high contrast mode is on or images are turned off
2852 var div = dojo.create("div",{
2853 id: "a11yTestNode",
2854 style:{
2855 cssText:'border: 1px solid;'
2856 + 'border-color:red green;'
2857 + 'position: absolute;'
2858 + 'height: 5px;'
2859 + 'top: -999px;'
2860 + 'background-image: url("' + (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")) + '");'
2861 }
2862 }, dojo.body());
2863
2864 // test it
2865 var cs = dojo.getComputedStyle(div);
2866 if(cs){
2867 var bkImg = cs.backgroundImage;
2868 var needsA11y = (cs.borderTopColor == cs.borderRightColor) || (bkImg != null && (bkImg == "none" || bkImg == "url(invalid-url:)" ));
2869 dojo[needsA11y ? "addClass" : "removeClass"](dojo.body(), "dijit_a11y");
2870 if(dojo.isIE){
2871 div.outerHTML = ""; // prevent mixed-content warning, see http://support.microsoft.com/kb/925014
2872 }else{
2873 dojo.body().removeChild(div);
2874 }
2875 }
2876 }
2877 };
2878
2879 // Test if computer is in high contrast mode.
2880 // Make sure the a11y test runs first, before widgets are instantiated.
2881 if(dojo.isIE || dojo.isMoz){ // NOTE: checking in Safari messes things up
2882 dojo._loaders.unshift(dijit.wai.onload);
2883 }
2884
2885 dojo.mixin(dijit, {
2886 _XhtmlRoles: /banner|contentinfo|definition|main|navigation|search|note|secondary|seealso/,
2887
2888 hasWaiRole: function(/*Element*/ elem, /*String*/ role){
2889 // summary:
2890 // Determines if an element has a particular non-XHTML role.
2891 // returns:
2892 // True if elem has the specific non-XHTML role attribute and false if not.
2893 // For backwards compatibility if role parameter not provided,
2894 // returns true if has non XHTML role
2895 var waiRole = this.getWaiRole(elem);
2896 return role ? (waiRole.indexOf(role) > -1) : (waiRole.length > 0);
2897 },
2898
2899 getWaiRole: function(/*Element*/ elem){
2900 // summary:
2901 // Gets the non-XHTML role for an element (which should be a wai role).
2902 // returns:
2903 // The non-XHTML role of elem or an empty string if elem
2904 // does not have a role.
2905 return dojo.trim((dojo.attr(elem, "role") || "").replace(this._XhtmlRoles,"").replace("wairole:",""));
2906 },
2907
2908 setWaiRole: function(/*Element*/ elem, /*String*/ role){
2909 // summary:
2910 // Sets the role on an element.
2911 // description:
2912 // Replace existing role attribute with new role.
2913 // If elem already has an XHTML role, append this role to XHTML role
2914 // and remove other ARIA roles.
2915
2916 var curRole = dojo.attr(elem, "role") || "";
2917 if(!this._XhtmlRoles.test(curRole)){
2918 dojo.attr(elem, "role", role);
2919 }else{
2920 if((" "+ curRole +" ").indexOf(" " + role + " ") < 0){
2921 var clearXhtml = dojo.trim(curRole.replace(this._XhtmlRoles, ""));
2922 var cleanRole = dojo.trim(curRole.replace(clearXhtml, ""));
2923 dojo.attr(elem, "role", cleanRole + (cleanRole ? ' ' : '') + role);
2924 }
2925 }
2926 },
2927
2928 removeWaiRole: function(/*Element*/ elem, /*String*/ role){
2929 // summary:
2930 // Removes the specified non-XHTML role from an element.
2931 // Removes role attribute if no specific role provided (for backwards compat.)
2932
2933 var roleValue = dojo.attr(elem, "role");
2934 if(!roleValue){ return; }
2935 if(role){
2936 var t = dojo.trim((" " + roleValue + " ").replace(" " + role + " ", " "));
2937 dojo.attr(elem, "role", t);
2938 }else{
2939 elem.removeAttribute("role");
2940 }
2941 },
2942
2943 hasWaiState: function(/*Element*/ elem, /*String*/ state){
2944 // summary:
2945 // Determines if an element has a given state.
2946 // description:
2947 // Checks for an attribute called "aria-"+state.
2948 // returns:
2949 // true if elem has a value for the given state and
2950 // false if it does not.
2951
2952 return elem.hasAttribute ? elem.hasAttribute("aria-"+state) : !!elem.getAttribute("aria-"+state);
2953 },
2954
2955 getWaiState: function(/*Element*/ elem, /*String*/ state){
2956 // summary:
2957 // Gets the value of a state on an element.
2958 // description:
2959 // Checks for an attribute called "aria-"+state.
2960 // returns:
2961 // The value of the requested state on elem
2962 // or an empty string if elem has no value for state.
2963
2964 return elem.getAttribute("aria-"+state) || "";
2965 },
2966
2967 setWaiState: function(/*Element*/ elem, /*String*/ state, /*String*/ value){
2968 // summary:
2969 // Sets a state on an element.
2970 // description:
2971 // Sets an attribute called "aria-"+state.
2972
2973 elem.setAttribute("aria-"+state, value);
2974 },
2975
2976 removeWaiState: function(/*Element*/ elem, /*String*/ state){
2977 // summary:
2978 // Removes a state from an element.
2979 // description:
2980 // Sets an attribute called "aria-"+state.
2981
2982 elem.removeAttribute("aria-"+state);
2983 }
2984 });
2985
2986 }
2987
2988 if(!dojo._hasResource["dijit._base"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2989 dojo._hasResource["dijit._base"] = true;
2990 dojo.provide("dijit._base");
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002 }
3003
3004 if(!dojo._hasResource["dijit._Widget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3005 dojo._hasResource["dijit._Widget"] = true;
3006 dojo.provide("dijit._Widget");
3007
3008 dojo.require( "dijit._base" );
3009
3010
3011 // This code is to assist deferring dojo.connect() calls in widgets (connecting to events on the widgets'
3012 // DOM nodes) until someone actually needs to monitor that event.
3013 dojo.connect(dojo, "_connect",
3014 function(/*dijit._Widget*/ widget, /*String*/ event){
3015 if(widget && dojo.isFunction(widget._onConnect)){
3016 widget._onConnect(event);
3017 }
3018 });
3019
3020 dijit._connectOnUseEventHandler = function(/*Event*/ event){};
3021
3022 // Keep track of where the last keydown event was, to help avoid generating
3023 // spurious ondijitclick events when:
3024 // 1. focus is on a <button> or <a>
3025 // 2. user presses then releases the ENTER key
3026 // 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler
3027 // 4. onkeyup event fires, causing the ondijitclick handler to fire
3028 dijit._lastKeyDownNode = null;
3029 if(dojo.isIE){
3030 (function(){
3031 var keydownCallback = function(evt){
3032 dijit._lastKeyDownNode = evt.srcElement;
3033 };
3034 dojo.doc.attachEvent('onkeydown', keydownCallback);
3035 dojo.addOnWindowUnload(function(){
3036 dojo.doc.detachEvent('onkeydown', keydownCallback);
3037 });
3038 })();
3039 }else{
3040 dojo.doc.addEventListener('keydown', function(evt){
3041 dijit._lastKeyDownNode = evt.target;
3042 }, true);
3043 }
3044
3045 (function(){
3046
3047 var _attrReg = {}, // cached results from getSetterAttributes
3048 getSetterAttributes = function(widget){
3049 // summary:
3050 // Returns list of attributes with custom setters for specified widget
3051 var dc = widget.declaredClass;
3052 if(!_attrReg[dc]){
3053 var r = [],
3054 attrs,
3055 proto = widget.constructor.prototype;
3056 for(var fxName in proto){
3057 if(dojo.isFunction(proto[fxName]) && (attrs = fxName.match(/^_set([a-zA-Z]*)Attr$/)) && attrs[1]){
3058 r.push(attrs[1].charAt(0).toLowerCase() + attrs[1].substr(1));
3059 }
3060 }
3061 _attrReg[dc] = r;
3062 }
3063 return _attrReg[dc] || []; // String[]
3064 };
3065
3066 dojo.declare("dijit._Widget", null, {
3067 // summary:
3068 // Base class for all Dijit widgets.
3069
3070 // id: [const] String
3071 // A unique, opaque ID string that can be assigned by users or by the
3072 // system. If the developer passes an ID which is known not to be
3073 // unique, the specified ID is ignored and the system-generated ID is
3074 // used instead.
3075 id: "",
3076
3077 // lang: [const] String
3078 // Rarely used. Overrides the default Dojo locale used to render this widget,
3079 // as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute.
3080 // Value must be among the list of locales specified during by the Dojo bootstrap,
3081 // formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us).
3082 lang: "",
3083
3084 // dir: [const] String
3085 // Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir)
3086 // attribute. Either left-to-right "ltr" or right-to-left "rtl". If undefined, widgets renders in page's
3087 // default direction.
3088 dir: "",
3089
3090 // class: String
3091 // HTML class attribute
3092 "class": "",
3093
3094 // style: String||Object
3095 // HTML style attributes as cssText string or name/value hash
3096 style: "",
3097
3098 // title: String
3099 // HTML title attribute.
3100 //
3101 // For form widgets this specifies a tooltip to display when hovering over
3102 // the widget (just like the native HTML title attribute).
3103 //
3104 // For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer,
3105 // etc., it's used to specify the tab label, accordion pane title, etc.
3106 title: "",
3107
3108 // tooltip: String
3109 // When this widget's title attribute is used to for a tab label, accordion pane title, etc.,
3110 // this specifies the tooltip to appear when the mouse is hovered over that text.
3111 tooltip: "",
3112
3113 // baseClass: [protected] String
3114 // Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate
3115 // widget state.
3116 baseClass: "",
3117
3118 // srcNodeRef: [readonly] DomNode
3119 // pointer to original DOM node
3120 srcNodeRef: null,
3121
3122 // domNode: [readonly] DomNode
3123 // This is our visible representation of the widget! Other DOM
3124 // Nodes may by assigned to other properties, usually through the
3125 // template system's dojoAttachPoint syntax, but the domNode
3126 // property is the canonical "top level" node in widget UI.
3127 domNode: null,
3128
3129 // containerNode: [readonly] DomNode
3130 // Designates where children of the source DOM node will be placed.
3131 // "Children" in this case refers to both DOM nodes and widgets.
3132 // For example, for myWidget:
3133 //
3134 // | <div dojoType=myWidget>
3135 // | <b> here's a plain DOM node
3136 // | <span dojoType=subWidget>and a widget</span>
3137 // | <i> and another plain DOM node </i>
3138 // | </div>
3139 //
3140 // containerNode would point to:
3141 //
3142 // | <b> here's a plain DOM node
3143 // | <span dojoType=subWidget>and a widget</span>
3144 // | <i> and another plain DOM node </i>
3145 //
3146 // In templated widgets, "containerNode" is set via a
3147 // dojoAttachPoint assignment.
3148 //
3149 // containerNode must be defined for any widget that accepts innerHTML
3150 // (like ContentPane or BorderContainer or even Button), and conversely
3151 // is null for widgets that don't, like TextBox.
3152 containerNode: null,
3153
3154 /*=====
3155 // _started: Boolean
3156 // startup() has completed.
3157 _started: false,
3158 =====*/
3159
3160 // attributeMap: [protected] Object
3161 // attributeMap sets up a "binding" between attributes (aka properties)
3162 // of the widget and the widget's DOM.
3163 // Changes to widget attributes listed in attributeMap will be
3164 // reflected into the DOM.
3165 //
3166 // For example, calling attr('title', 'hello')
3167 // on a TitlePane will automatically cause the TitlePane's DOM to update
3168 // with the new title.
3169 //
3170 // attributeMap is a hash where the key is an attribute of the widget,
3171 // and the value reflects a binding to a:
3172 //
3173 // - DOM node attribute
3174 // | focus: {node: "focusNode", type: "attribute"}
3175 // Maps this.focus to this.focusNode.focus
3176 //
3177 // - DOM node innerHTML
3178 // | title: { node: "titleNode", type: "innerHTML" }
3179 // Maps this.title to this.titleNode.innerHTML
3180 //
3181 // - DOM node innerText
3182 // | title: { node: "titleNode", type: "innerText" }
3183 // Maps this.title to this.titleNode.innerText
3184 //
3185 // - DOM node CSS class
3186 // | myClass: { node: "domNode", type: "class" }
3187 // Maps this.myClass to this.domNode.className
3188 //
3189 // If the value is an array, then each element in the array matches one of the
3190 // formats of the above list.
3191 //
3192 // There are also some shorthands for backwards compatibility:
3193 // - string --> { node: string, type: "attribute" }, for example:
3194 // | "focusNode" ---> { node: "focusNode", type: "attribute" }
3195 // - "" --> { node: "domNode", type: "attribute" }
3196 attributeMap: {id:"", dir:"", lang:"", "class":"", style:"", title:""},
3197
3198 // _deferredConnects: [protected] Object
3199 // attributeMap addendum for event handlers that should be connected only on first use
3200 _deferredConnects: {
3201 onClick: "",
3202 onDblClick: "",
3203 onKeyDown: "",
3204 onKeyPress: "",
3205 onKeyUp: "",
3206 onMouseMove: "",
3207 onMouseDown: "",
3208 onMouseOut: "",
3209 onMouseOver: "",
3210 onMouseLeave: "",
3211 onMouseEnter: "",
3212 onMouseUp: ""
3213 },
3214
3215 onClick: dijit._connectOnUseEventHandler,
3216 /*=====
3217 onClick: function(event){
3218 // summary:
3219 // Connect to this function to receive notifications of mouse click events.
3220 // event:
3221 // mouse Event
3222 // tags:
3223 // callback
3224 },
3225 =====*/
3226 onDblClick: dijit._connectOnUseEventHandler,
3227 /*=====
3228 onDblClick: function(event){
3229 // summary:
3230 // Connect to this function to receive notifications of mouse double click events.
3231 // event:
3232 // mouse Event
3233 // tags:
3234 // callback
3235 },
3236 =====*/
3237 onKeyDown: dijit._connectOnUseEventHandler,
3238 /*=====
3239 onKeyDown: function(event){
3240 // summary:
3241 // Connect to this function to receive notifications of keys being pressed down.
3242 // event:
3243 // key Event
3244 // tags:
3245 // callback
3246 },
3247 =====*/
3248 onKeyPress: dijit._connectOnUseEventHandler,
3249 /*=====
3250 onKeyPress: function(event){
3251 // summary:
3252 // Connect to this function to receive notifications of printable keys being typed.
3253 // event:
3254 // key Event
3255 // tags:
3256 // callback
3257 },
3258 =====*/
3259 onKeyUp: dijit._connectOnUseEventHandler,
3260 /*=====
3261 onKeyUp: function(event){
3262 // summary:
3263 // Connect to this function to receive notifications of keys being released.
3264 // event:
3265 // key Event
3266 // tags:
3267 // callback
3268 },
3269 =====*/
3270 onMouseDown: dijit._connectOnUseEventHandler,
3271 /*=====
3272 onMouseDown: function(event){
3273 // summary:
3274 // Connect to this function to receive notifications of when the mouse button is pressed down.
3275 // event:
3276 // mouse Event
3277 // tags:
3278 // callback
3279 },
3280 =====*/
3281 onMouseMove: dijit._connectOnUseEventHandler,
3282 /*=====
3283 onMouseMove: function(event){
3284 // summary:
3285 // Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget.
3286 // event:
3287 // mouse Event
3288 // tags:
3289 // callback
3290 },
3291 =====*/
3292 onMouseOut: dijit._connectOnUseEventHandler,
3293 /*=====
3294 onMouseOut: function(event){
3295 // summary:
3296 // Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget.
3297 // event:
3298 // mouse Event
3299 // tags:
3300 // callback
3301 },
3302 =====*/
3303 onMouseOver: dijit._connectOnUseEventHandler,
3304 /*=====
3305 onMouseOver: function(event){
3306 // summary:
3307 // Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget.
3308 // event:
3309 // mouse Event
3310 // tags:
3311 // callback
3312 },
3313 =====*/
3314 onMouseLeave: dijit._connectOnUseEventHandler,
3315 /*=====
3316 onMouseLeave: function(event){
3317 // summary:
3318 // Connect to this function to receive notifications of when the mouse moves off of this widget.
3319 // event:
3320 // mouse Event
3321 // tags:
3322 // callback
3323 },
3324 =====*/
3325 onMouseEnter: dijit._connectOnUseEventHandler,
3326 /*=====
3327 onMouseEnter: function(event){
3328 // summary:
3329 // Connect to this function to receive notifications of when the mouse moves onto this widget.
3330 // event:
3331 // mouse Event
3332 // tags:
3333 // callback
3334 },
3335 =====*/
3336 onMouseUp: dijit._connectOnUseEventHandler,
3337 /*=====
3338 onMouseUp: function(event){
3339 // summary:
3340 // Connect to this function to receive notifications of when the mouse button is released.
3341 // event:
3342 // mouse Event
3343 // tags:
3344 // callback
3345 },
3346 =====*/
3347
3348 // Constants used in templates
3349
3350 // _blankGif: [protected] String
3351 // Path to a blank 1x1 image.
3352 // Used by <img> nodes in templates that really get their image via CSS background-image.
3353 _blankGif: (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")).toString(),
3354
3355 //////////// INITIALIZATION METHODS ///////////////////////////////////////
3356
3357 postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
3358 // summary:
3359 // Kicks off widget instantiation. See create() for details.
3360 // tags:
3361 // private
3362 this.create(params, srcNodeRef);
3363 },
3364
3365 create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){
3366 // summary:
3367 // Kick off the life-cycle of a widget
3368 // params:
3369 // Hash of initialization parameters for widget, including
3370 // scalar values (like title, duration etc.) and functions,
3371 // typically callbacks like onClick.
3372 // srcNodeRef:
3373 // If a srcNodeRef (DOM node) is specified:
3374 // - use srcNodeRef.innerHTML as my contents
3375 // - if this is a behavioral widget then apply behavior
3376 // to that srcNodeRef
3377 // - otherwise, replace srcNodeRef with my generated DOM
3378 // tree
3379 // description:
3380 // Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate,
3381 // etc.), some of which of you'll want to override. See http://docs.dojocampus.org/dijit/_Widget
3382 // for a discussion of the widget creation lifecycle.
3383 //
3384 // Of course, adventurous developers could override create entirely, but this should
3385 // only be done as a last resort.
3386 // tags:
3387 // private
3388
3389 // store pointer to original DOM tree
3390 this.srcNodeRef = dojo.byId(srcNodeRef);
3391
3392 // For garbage collection. An array of handles returned by Widget.connect()
3393 // Each handle returned from Widget.connect() is an array of handles from dojo.connect()
3394 this._connects = [];
3395
3396 // For garbage collection. An array of handles returned by Widget.subscribe()
3397 // The handle returned from Widget.subscribe() is the handle returned from dojo.subscribe()
3398 this._subscribes = [];
3399
3400 // To avoid double-connects, remove entries from _deferredConnects
3401 // that have been setup manually by a subclass (ex, by dojoAttachEvent).
3402 // If a subclass has redefined a callback (ex: onClick) then assume it's being
3403 // connected to manually.
3404 this._deferredConnects = dojo.clone(this._deferredConnects);
3405 for(var attr in this.attributeMap){
3406 delete this._deferredConnects[attr]; // can't be in both attributeMap and _deferredConnects
3407 }
3408 for(attr in this._deferredConnects){
3409 if(this[attr] !== dijit._connectOnUseEventHandler){
3410 delete this._deferredConnects[attr]; // redefined, probably dojoAttachEvent exists
3411 }
3412 }
3413
3414 //mixin our passed parameters
3415 if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; }
3416 if(params){
3417 this.params = params;
3418 dojo.mixin(this,params);
3419 }
3420 this.postMixInProperties();
3421
3422 // generate an id for the widget if one wasn't specified
3423 // (be sure to do this before buildRendering() because that function might
3424 // expect the id to be there.)
3425 if(!this.id){
3426 this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
3427 }
3428 dijit.registry.add(this);
3429
3430 this.buildRendering();
3431
3432 if(this.domNode){
3433 // Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
3434 this._applyAttributes();
3435
3436 var source = this.srcNodeRef;
3437 if(source && source.parentNode){
3438 source.parentNode.replaceChild(this.domNode, source);
3439 }
3440
3441 // If the developer has specified a handler as a widget parameter
3442 // (ex: new Button({onClick: ...})
3443 // then naturally need to connect from DOM node to that handler immediately,
3444 for(attr in this.params){
3445 this._onConnect(attr);
3446 }
3447 }
3448
3449 if(this.domNode){
3450 this.domNode.setAttribute("widgetId", this.id);
3451 }
3452 this.postCreate();
3453
3454 // If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC.
3455 if(this.srcNodeRef && !this.srcNodeRef.parentNode){
3456 delete this.srcNodeRef;
3457 }
3458
3459 this._created = true;
3460 },
3461
3462 _applyAttributes: function(){
3463 // summary:
3464 // Step during widget creation to copy all widget attributes to the
3465 // DOM as per attributeMap and _setXXXAttr functions.
3466 // description:
3467 // Skips over blank/false attribute values, unless they were explicitly specified
3468 // as parameters to the widget, since those are the default anyway,
3469 // and setting tabIndex="" is different than not setting tabIndex at all.
3470 //
3471 // It processes the attributes in the attribute map first, and then
3472 // it goes through and processes the attributes for the _setXXXAttr
3473 // functions that have been specified
3474 // tags:
3475 // private
3476 var condAttrApply = function(attr, scope){
3477 if((scope.params && attr in scope.params) || scope[attr]){
3478 scope.set(attr, scope[attr]);
3479 }
3480 };
3481
3482 // Do the attributes in attributeMap
3483 for(var attr in this.attributeMap){
3484 condAttrApply(attr, this);
3485 }
3486
3487 // And also any attributes with custom setters
3488 dojo.forEach(getSetterAttributes(this), function(a){
3489 if(!(a in this.attributeMap)){
3490 condAttrApply(a, this);
3491 }
3492 }, this);
3493 },
3494
3495 postMixInProperties: function(){
3496 // summary:
3497 // Called after the parameters to the widget have been read-in,
3498 // but before the widget template is instantiated. Especially
3499 // useful to set properties that are referenced in the widget
3500 // template.
3501 // tags:
3502 // protected
3503 },
3504
3505 buildRendering: function(){
3506 // summary:
3507 // Construct the UI for this widget, setting this.domNode
3508 // description:
3509 // Most widgets will mixin `dijit._Templated`, which implements this
3510 // method.
3511 // tags:
3512 // protected
3513 this.domNode = this.srcNodeRef || dojo.create('div');
3514 },
3515
3516 postCreate: function(){
3517 // summary:
3518 // Processing after the DOM fragment is created
3519 // description:
3520 // Called after the DOM fragment has been created, but not necessarily
3521 // added to the document. Do not include any operations which rely on
3522 // node dimensions or placement.
3523 // tags:
3524 // protected
3525
3526 // baseClass is a single class name or occasionally a space-separated list of names.
3527 // Add those classes to the DOMNod. If RTL mode then also add with Rtl suffix.
3528 if(this.baseClass){
3529 var classes = this.baseClass.split(" ");
3530 if(!this.isLeftToRight()){
3531 classes = classes.concat( dojo.map(classes, function(name){ return name+"Rtl"; }));
3532 }
3533 dojo.addClass(this.domNode, classes);
3534 }
3535 },
3536
3537 startup: function(){
3538 // summary:
3539 // Processing after the DOM fragment is added to the document
3540 // description:
3541 // Called after a widget and its children have been created and added to the page,
3542 // and all related widgets have finished their create() cycle, up through postCreate().
3543 // This is useful for composite widgets that need to control or layout sub-widgets.
3544 // Many layout widgets can use this as a wiring phase.
3545 this._started = true;
3546 },
3547
3548 //////////// DESTROY FUNCTIONS ////////////////////////////////
3549
3550 destroyRecursive: function(/*Boolean?*/ preserveDom){
3551 // summary:
3552 // Destroy this widget and its descendants
3553 // description:
3554 // This is the generic "destructor" function that all widget users
3555 // should call to cleanly discard with a widget. Once a widget is
3556 // destroyed, it is removed from the manager object.
3557 // preserveDom:
3558 // If true, this method will leave the original DOM structure
3559 // alone of descendant Widgets. Note: This will NOT work with
3560 // dijit._Templated widgets.
3561
3562 this._beingDestroyed = true;
3563 this.destroyDescendants(preserveDom);
3564 this.destroy(preserveDom);
3565 },
3566
3567 destroy: function(/*Boolean*/ preserveDom){
3568 // summary:
3569 // Destroy this widget, but not its descendants.
3570 // This method will, however, destroy internal widgets such as those used within a template.
3571 // preserveDom: Boolean
3572 // If true, this method will leave the original DOM structure alone.
3573 // Note: This will not yet work with _Templated widgets
3574
3575 this._beingDestroyed = true;
3576 this.uninitialize();
3577 var d = dojo,
3578 dfe = d.forEach,
3579 dun = d.unsubscribe;
3580 dfe(this._connects, function(array){
3581 dfe(array, d.disconnect);
3582 });
3583 dfe(this._subscribes, function(handle){
3584 dun(handle);
3585 });
3586
3587 // destroy widgets created as part of template, etc.
3588 dfe(this._supportingWidgets || [], function(w){
3589 if(w.destroyRecursive){
3590 w.destroyRecursive();
3591 }else if(w.destroy){
3592 w.destroy();
3593 }
3594 });
3595
3596 this.destroyRendering(preserveDom);
3597 dijit.registry.remove(this.id);
3598 this._destroyed = true;
3599 },
3600
3601 destroyRendering: function(/*Boolean?*/ preserveDom){
3602 // summary:
3603 // Destroys the DOM nodes associated with this widget
3604 // preserveDom:
3605 // If true, this method will leave the original DOM structure alone
3606 // during tear-down. Note: this will not work with _Templated
3607 // widgets yet.
3608 // tags:
3609 // protected
3610
3611 if(this.bgIframe){
3612 this.bgIframe.destroy(preserveDom);
3613 delete this.bgIframe;
3614 }
3615
3616 if(this.domNode){
3617 if(preserveDom){
3618 dojo.removeAttr(this.domNode, "widgetId");
3619 }else{
3620 dojo.destroy(this.domNode);
3621 }
3622 delete this.domNode;
3623 }
3624
3625 if(this.srcNodeRef){
3626 if(!preserveDom){
3627 dojo.destroy(this.srcNodeRef);
3628 }
3629 delete this.srcNodeRef;
3630 }
3631 },
3632
3633 destroyDescendants: function(/*Boolean?*/ preserveDom){
3634 // summary:
3635 // Recursively destroy the children of this widget and their
3636 // descendants.
3637 // preserveDom:
3638 // If true, the preserveDom attribute is passed to all descendant
3639 // widget's .destroy() method. Not for use with _Templated
3640 // widgets.
3641
3642 // get all direct descendants and destroy them recursively
3643 dojo.forEach(this.getChildren(), function(widget){
3644 if(widget.destroyRecursive){
3645 widget.destroyRecursive(preserveDom);
3646 }
3647 });
3648 },
3649
3650
3651 uninitialize: function(){
3652 // summary:
3653 // Stub function. Override to implement custom widget tear-down
3654 // behavior.
3655 // tags:
3656 // protected
3657 return false;
3658 },
3659
3660 ////////////////// MISCELLANEOUS METHODS ///////////////////
3661
3662 onFocus: function(){
3663 // summary:
3664 // Called when the widget becomes "active" because
3665 // it or a widget inside of it either has focus, or has recently
3666 // been clicked.
3667 // tags:
3668 // callback
3669 },
3670
3671 onBlur: function(){
3672 // summary:
3673 // Called when the widget stops being "active" because
3674 // focus moved to something outside of it, or the user
3675 // clicked somewhere outside of it, or the widget was
3676 // hidden.
3677 // tags:
3678 // callback
3679 },
3680
3681 _onFocus: function(e){
3682 // summary:
3683 // This is where widgets do processing for when they are active,
3684 // such as changing CSS classes. See onFocus() for more details.
3685 // tags:
3686 // protected
3687 this.onFocus();
3688 },
3689
3690 _onBlur: function(){
3691 // summary:
3692 // This is where widgets do processing for when they stop being active,
3693 // such as changing CSS classes. See onBlur() for more details.
3694 // tags:
3695 // protected
3696 this.onBlur();
3697 },
3698
3699 _onConnect: function(/*String*/ event){
3700 // summary:
3701 // Called when someone connects to one of my handlers.
3702 // "Turn on" that handler if it isn't active yet.
3703 //
3704 // This is also called for every single initialization parameter
3705 // so need to do nothing for parameters like "id".
3706 // tags:
3707 // private
3708 if(event in this._deferredConnects){
3709 var mapNode = this[this._deferredConnects[event] || 'domNode'];
3710 this.connect(mapNode, event.toLowerCase(), event);
3711 delete this._deferredConnects[event];
3712 }
3713 },
3714
3715 _setClassAttr: function(/*String*/ value){
3716 // summary:
3717 // Custom setter for the CSS "class" attribute
3718 // tags:
3719 // protected
3720 var mapNode = this[this.attributeMap["class"] || 'domNode'];
3721 dojo.removeClass(mapNode, this["class"])
3722 this["class"] = value;
3723 dojo.addClass(mapNode, value);
3724 },
3725
3726 _setStyleAttr: function(/*String||Object*/ value){
3727 // summary:
3728 // Sets the style attribut of the widget according to value,
3729 // which is either a hash like {height: "5px", width: "3px"}
3730 // or a plain string
3731 // description:
3732 // Determines which node to set the style on based on style setting
3733 // in attributeMap.
3734 // tags:
3735 // protected
3736
3737 var mapNode = this[this.attributeMap.style || 'domNode'];
3738
3739 // Note: technically we should revert any style setting made in a previous call
3740 // to his method, but that's difficult to keep track of.
3741
3742 if(dojo.isObject(value)){
3743 dojo.style(mapNode, value);
3744 }else{
3745 if(mapNode.style.cssText){
3746 mapNode.style.cssText += "; " + value;
3747 }else{
3748 mapNode.style.cssText = value;
3749 }
3750 }
3751
3752 this.style = value;
3753 },
3754
3755 setAttribute: function(/*String*/ attr, /*anything*/ value){
3756 // summary:
3757 // Deprecated. Use set() instead.
3758 // tags:
3759 // deprecated
3760 dojo.deprecated(this.declaredClass+"::setAttribute(attr, value) is deprecated. Use set() instead.", "", "2.0");
3761 this.set(attr, value);
3762 },
3763
3764 _attrToDom: function(/*String*/ attr, /*String*/ value){
3765 // summary:
3766 // Reflect a widget attribute (title, tabIndex, duration etc.) to
3767 // the widget DOM, as specified in attributeMap.
3768 //
3769 // description:
3770 // Also sets this["attr"] to the new value.
3771 // Note some attributes like "type"
3772 // cannot be processed this way as they are not mutable.
3773 //
3774 // tags:
3775 // private
3776
3777 var commands = this.attributeMap[attr];
3778 dojo.forEach(dojo.isArray(commands) ? commands : [commands], function(command){
3779
3780 // Get target node and what we are doing to that node
3781 var mapNode = this[command.node || command || "domNode"]; // DOM node
3782 var type = command.type || "attribute"; // class, innerHTML, innerText, or attribute
3783
3784 switch(type){
3785 case "attribute":
3786 if(dojo.isFunction(value)){ // functions execute in the context of the widget
3787 value = dojo.hitch(this, value);
3788 }
3789
3790 // Get the name of the DOM node attribute; usually it's the same
3791 // as the name of the attribute in the widget (attr), but can be overridden.
3792 // Also maps handler names to lowercase, like onSubmit --> onsubmit
3793 var attrName = command.attribute ? command.attribute :
3794 (/^on[A-Z][a-zA-Z]*$/.test(attr) ? attr.toLowerCase() : attr);
3795
3796 dojo.attr(mapNode, attrName, value);
3797 break;
3798 case "innerText":
3799 mapNode.innerHTML = "";
3800 mapNode.appendChild(dojo.doc.createTextNode(value));
3801 break;
3802 case "innerHTML":
3803 mapNode.innerHTML = value;
3804 break;
3805 case "class":
3806 dojo.removeClass(mapNode, this[attr]);
3807 dojo.addClass(mapNode, value);
3808 break;
3809 }
3810 }, this);
3811 this[attr] = value;
3812 },
3813
3814 attr: function(/*String|Object*/name, /*Object?*/value){
3815 // summary:
3816 // Set or get properties on a widget instance.
3817 // name:
3818 // The property to get or set. If an object is passed here and not
3819 // a string, its keys are used as names of attributes to be set
3820 // and the value of the object as values to set in the widget.
3821 // value:
3822 // Optional. If provided, attr() operates as a setter. If omitted,
3823 // the current value of the named property is returned.
3824 // description:
3825 // This method is deprecated, use get() or set() directly.
3826
3827 // Print deprecation warning but only once per calling function
3828 if(dojo.config.isDebug){
3829 var alreadyCalledHash = arguments.callee._ach || (arguments.callee._ach = {}),
3830 caller = (arguments.callee.caller || "unknown caller").toString();
3831 if(!alreadyCalledHash[caller]){
3832 dojo.deprecated(this.declaredClass + "::attr() is deprecated. Use get() or set() instead, called from " +
3833 caller, "", "2.0");
3834 alreadyCalledHash[caller] = true;
3835 }
3836 }
3837
3838 var args = arguments.length;
3839 if(args >= 2 || typeof name === "object"){ // setter
3840 return this.set.apply(this, arguments);
3841 }else{ // getter
3842 return this.get(name);
3843 }
3844 },
3845
3846 get: function(name){
3847 // summary:
3848 // Get a property from a widget.
3849 // name:
3850 // The property to get.
3851 // description:
3852 // Get a named property from a widget. The property may
3853 // potentially be retrieved via a getter method. If no getter is defined, this
3854 // just retrieves the object's property.
3855 // For example, if the widget has a properties "foo"
3856 // and "bar" and a method named "_getFooAttr", calling:
3857 // | myWidget.get("foo");
3858 // would be equivalent to writing:
3859 // | widget._getFooAttr();
3860 // and:
3861 // | myWidget.get("bar");
3862 // would be equivalent to writing:
3863 // | widget.bar;
3864 var names = this._getAttrNames(name);
3865 return this[names.g] ? this[names.g]() : this[name];
3866 },
3867
3868 set: function(name, value){
3869 // summary:
3870 // Set a property on a widget
3871 // name:
3872 // The property to set.
3873 // value:
3874 // The value to set in the property.
3875 // description:
3876 // Sets named properties on a widget which may potentially be handled by a
3877 // setter in the widget.
3878 // For example, if the widget has a properties "foo"
3879 // and "bar" and a method named "_setFooAttr", calling:
3880 // | myWidget.set("foo", "Howdy!");
3881 // would be equivalent to writing:
3882 // | widget._setFooAttr("Howdy!");
3883 // and:
3884 // | myWidget.set("bar", 3);
3885 // would be equivalent to writing:
3886 // | widget.bar = 3;
3887 //
3888 // set() may also be called with a hash of name/value pairs, ex:
3889 // | myWidget.set({
3890 // | foo: "Howdy",
3891 // | bar: 3
3892 // | })
3893 // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
3894
3895 if(typeof name === "object"){
3896 for(var x in name){
3897 this.set(x, name[x]);
3898 }
3899 return this;
3900 }
3901 var names = this._getAttrNames(name);
3902 if(this[names.s]){
3903 // use the explicit setter
3904 var result = this[names.s].apply(this, Array.prototype.slice.call(arguments, 1));
3905 }else{
3906 // if param is specified as DOM node attribute, copy it
3907 if(name in this.attributeMap){
3908 this._attrToDom(name, value);
3909 }
3910 var oldValue = this[name];
3911 // FIXME: what about function assignments? Any way to connect() here?
3912 this[name] = value;
3913 }
3914 return result || this;
3915 },
3916
3917 _attrPairNames: {}, // shared between all widgets
3918 _getAttrNames: function(name){
3919 // summary:
3920 // Helper function for get() and set().
3921 // Caches attribute name values so we don't do the string ops every time.
3922 // tags:
3923 // private
3924
3925 var apn = this._attrPairNames;
3926 if(apn[name]){ return apn[name]; }
3927 var uc = name.charAt(0).toUpperCase() + name.substr(1);
3928 return (apn[name] = {
3929 n: name+"Node",
3930 s: "_set"+uc+"Attr",
3931 g: "_get"+uc+"Attr"
3932 });
3933 },
3934
3935 toString: function(){
3936 // summary:
3937 // Returns a string that represents the widget
3938 // description:
3939 // When a widget is cast to a string, this method will be used to generate the
3940 // output. Currently, it does not implement any sort of reversible
3941 // serialization.
3942 return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String
3943 },
3944
3945 getDescendants: function(){
3946 // summary:
3947 // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
3948 // This method should generally be avoided as it returns widgets declared in templates, which are
3949 // supposed to be internal/hidden, but it's left here for back-compat reasons.
3950
3951 return this.containerNode ? dojo.query('[widgetId]', this.containerNode).map(dijit.byNode) : []; // dijit._Widget[]
3952 },
3953
3954 getChildren: function(){
3955 // summary:
3956 // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
3957 // Does not return nested widgets, nor widgets that are part of this widget's template.
3958 return this.containerNode ? dijit.findWidgets(this.containerNode) : []; // dijit._Widget[]
3959 },
3960
3961 // nodesWithKeyClick: [private] String[]
3962 // List of nodes that correctly handle click events via native browser support,
3963 // and don't need dijit's help
3964 nodesWithKeyClick: ["input", "button"],
3965
3966 connect: function(
3967 /*Object|null*/ obj,
3968 /*String|Function*/ event,
3969 /*String|Function*/ method){
3970 // summary:
3971 // Connects specified obj/event to specified method of this object
3972 // and registers for disconnect() on widget destroy.
3973 // description:
3974 // Provide widget-specific analog to dojo.connect, except with the
3975 // implicit use of this widget as the target object.
3976 // This version of connect also provides a special "ondijitclick"
3977 // event which triggers on a click or space or enter keyup
3978 // returns:
3979 // A handle that can be passed to `disconnect` in order to disconnect before
3980 // the widget is destroyed.
3981 // example:
3982 // | var btn = new dijit.form.Button();
3983 // | // when foo.bar() is called, call the listener we're going to
3984 // | // provide in the scope of btn
3985 // | btn.connect(foo, "bar", function(){
3986 // | console.debug(this.toString());
3987 // | });
3988 // tags:
3989 // protected
3990
3991 var d = dojo,
3992 dc = d._connect,
3993 handles = [];
3994 if(event == "ondijitclick"){
3995 // add key based click activation for unsupported nodes.
3996 // do all processing onkey up to prevent spurious clicks
3997 // for details see comments at top of this file where _lastKeyDownNode is defined
3998 if(dojo.indexOf(this.nodesWithKeyClick, obj.nodeName.toLowerCase()) == -1){ // is NOT input or button
3999 var m = d.hitch(this, method);
4000 handles.push(
4001 dc(obj, "onkeydown", this, function(e){
4002 //console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
4003 if((e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) &&
4004 !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){
4005 // needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
4006 dijit._lastKeyDownNode = e.target;
4007 e.preventDefault(); // stop event to prevent scrolling on space key in IE
4008 }
4009 }),
4010 dc(obj, "onkeyup", this, function(e){
4011 //console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
4012 if( (e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) &&
4013 e.target === dijit._lastKeyDownNode &&
4014 !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){
4015 //need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
4016 dijit._lastKeyDownNode = null;
4017 return m(e);
4018 }
4019 })
4020 );
4021 }
4022 event = "onclick";
4023 }
4024 handles.push(dc(obj, event, this, method));
4025
4026 this._connects.push(handles);
4027 return handles; // _Widget.Handle
4028 },
4029
4030 disconnect: function(/* _Widget.Handle */ handles){
4031 // summary:
4032 // Disconnects handle created by `connect`.
4033 // Also removes handle from this widget's list of connects.
4034 // tags:
4035 // protected
4036 for(var i=0; i<this._connects.length; i++){
4037 if(this._connects[i] == handles){
4038 dojo.forEach(handles, dojo.disconnect);
4039 this._connects.splice(i, 1);
4040 return;
4041 }
4042 }
4043 },
4044
4045 subscribe: function(
4046 /*String*/ topic,
4047 /*String|Function*/ method){
4048 // summary:
4049 // Subscribes to the specified topic and calls the specified method
4050 // of this object and registers for unsubscribe() on widget destroy.
4051 // description:
4052 // Provide widget-specific analog to dojo.subscribe, except with the
4053 // implicit use of this widget as the target object.
4054 // example:
4055 // | var btn = new dijit.form.Button();
4056 // | // when /my/topic is published, this button changes its label to
4057 // | // be the parameter of the topic.
4058 // | btn.subscribe("/my/topic", function(v){
4059 // | this.set("label", v);
4060 // | });
4061 var d = dojo,
4062 handle = d.subscribe(topic, this, method);
4063
4064 // return handles for Any widget that may need them
4065 this._subscribes.push(handle);
4066 return handle;
4067 },
4068
4069 unsubscribe: function(/*Object*/ handle){
4070 // summary:
4071 // Unsubscribes handle created by this.subscribe.
4072 // Also removes handle from this widget's list of subscriptions
4073 for(var i=0; i<this._subscribes.length; i++){
4074 if(this._subscribes[i] == handle){
4075 dojo.unsubscribe(handle);
4076 this._subscribes.splice(i, 1);
4077 return;
4078 }
4079 }
4080 },
4081
4082 isLeftToRight: function(){
4083 // summary:
4084 // Return this widget's explicit or implicit orientation (true for LTR, false for RTL)
4085 // tags:
4086 // protected
4087 return this.dir ? (this.dir == "ltr") : dojo._isBodyLtr(); //Boolean
4088 },
4089
4090 isFocusable: function(){
4091 // summary:
4092 // Return true if this widget can currently be focused
4093 // and false if not
4094 return this.focus && (dojo.style(this.domNode, "display") != "none");
4095 },
4096
4097 placeAt: function(/* String|DomNode|_Widget */reference, /* String?|Int? */position){
4098 // summary:
4099 // Place this widget's domNode reference somewhere in the DOM based
4100 // on standard dojo.place conventions, or passing a Widget reference that
4101 // contains and addChild member.
4102 //
4103 // description:
4104 // A convenience function provided in all _Widgets, providing a simple
4105 // shorthand mechanism to put an existing (or newly created) Widget
4106 // somewhere in the dom, and allow chaining.
4107 //
4108 // reference:
4109 // The String id of a domNode, a domNode reference, or a reference to a Widget posessing
4110 // an addChild method.
4111 //
4112 // position:
4113 // If passed a string or domNode reference, the position argument
4114 // accepts a string just as dojo.place does, one of: "first", "last",
4115 // "before", or "after".
4116 //
4117 // If passed a _Widget reference, and that widget reference has an ".addChild" method,
4118 // it will be called passing this widget instance into that method, supplying the optional
4119 // position index passed.
4120 //
4121 // returns:
4122 // dijit._Widget
4123 // Provides a useful return of the newly created dijit._Widget instance so you
4124 // can "chain" this function by instantiating, placing, then saving the return value
4125 // to a variable.
4126 //
4127 // example:
4128 // | // create a Button with no srcNodeRef, and place it in the body:
4129 // | var button = new dijit.form.Button({ label:"click" }).placeAt(dojo.body());
4130 // | // now, 'button' is still the widget reference to the newly created button
4131 // | dojo.connect(button, "onClick", function(e){ console.log('click'); });
4132 //
4133 // example:
4134 // | // create a button out of a node with id="src" and append it to id="wrapper":
4135 // | var button = new dijit.form.Button({},"src").placeAt("wrapper");
4136 //
4137 // example:
4138 // | // place a new button as the first element of some div
4139 // | var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first");
4140 //
4141 // example:
4142 // | // create a contentpane and add it to a TabContainer
4143 // | var tc = dijit.byId("myTabs");
4144 // | new dijit.layout.ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc)
4145
4146 if(reference.declaredClass && reference.addChild){
4147 reference.addChild(this, position);
4148 }else{
4149 dojo.place(this.domNode, reference, position);
4150 }
4151 return this;
4152 },
4153
4154 _onShow: function(){
4155 // summary:
4156 // Internal method called when this widget is made visible.
4157 // See `onShow` for details.
4158 this.onShow();
4159 },
4160
4161 onShow: function(){
4162 // summary:
4163 // Called when this widget becomes the selected pane in a
4164 // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`,
4165 // `dijit.layout.AccordionContainer`, etc.
4166 //
4167 // Also called to indicate display of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
4168 // tags:
4169 // callback
4170 },
4171
4172 onHide: function(){
4173 // summary:
4174 // Called when another widget becomes the selected pane in a
4175 // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`,
4176 // `dijit.layout.AccordionContainer`, etc.
4177 //
4178 // Also called to indicate hide of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
4179 // tags:
4180 // callback
4181 },
4182
4183 onClose: function(){
4184 // summary:
4185 // Called when this widget is being displayed as a popup (ex: a Calendar popped
4186 // up from a DateTextBox), and it is hidden.
4187 // This is called from the dijit.popup code, and should not be called directly.
4188 //
4189 // Also used as a parameter for children of `dijit.layout.StackContainer` or subclasses.
4190 // Callback if a user tries to close the child. Child will be closed if this function returns true.
4191 // tags:
4192 // extension
4193
4194 return true; // Boolean
4195 }
4196 });
4197
4198 })();
4199
4200 }
4201
4202 if(!dojo._hasResource["dojo.string"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4203 dojo._hasResource["dojo.string"] = true;
4204 dojo.provide("dojo.string");
4205
4206 /*=====
4207 dojo.string = {
4208 // summary: String utilities for Dojo
4209 };
4210 =====*/
4211
4212 dojo.string.rep = function(/*String*/str, /*Integer*/num){
4213 // summary:
4214 // Efficiently replicate a string `n` times.
4215 // str:
4216 // the string to replicate
4217 // num:
4218 // number of times to replicate the string
4219
4220 if(num <= 0 || !str){ return ""; }
4221
4222 var buf = [];
4223 for(;;){
4224 if(num & 1){
4225 buf.push(str);
4226 }
4227 if(!(num >>= 1)){ break; }
4228 str += str;
4229 }
4230 return buf.join(""); // String
4231 };
4232
4233 dojo.string.pad = function(/*String*/text, /*Integer*/size, /*String?*/ch, /*Boolean?*/end){
4234 // summary:
4235 // Pad a string to guarantee that it is at least `size` length by
4236 // filling with the character `ch` at either the start or end of the
4237 // string. Pads at the start, by default.
4238 // text:
4239 // the string to pad
4240 // size:
4241 // length to provide padding
4242 // ch:
4243 // character to pad, defaults to '0'
4244 // end:
4245 // adds padding at the end if true, otherwise pads at start
4246 // example:
4247 // | // Fill the string to length 10 with "+" characters on the right. Yields "Dojo++++++".
4248 // | dojo.string.pad("Dojo", 10, "+", true);
4249
4250 if(!ch){
4251 ch = '0';
4252 }
4253 var out = String(text),
4254 pad = dojo.string.rep(ch, Math.ceil((size - out.length) / ch.length));
4255 return end ? out + pad : pad + out; // String
4256 };
4257
4258 dojo.string.substitute = function( /*String*/ template,
4259 /*Object|Array*/map,
4260 /*Function?*/ transform,
4261 /*Object?*/ thisObject){
4262 // summary:
4263 // Performs parameterized substitutions on a string. Throws an
4264 // exception if any parameter is unmatched.
4265 // template:
4266 // a string with expressions in the form `${key}` to be replaced or
4267 // `${key:format}` which specifies a format function. keys are case-sensitive.
4268 // map:
4269 // hash to search for substitutions
4270 // transform:
4271 // a function to process all parameters before substitution takes
4272 // place, e.g. mylib.encodeXML
4273 // thisObject:
4274 // where to look for optional format function; default to the global
4275 // namespace
4276 // example:
4277 // Substitutes two expressions in a string from an Array or Object
4278 // | // returns "File 'foo.html' is not found in directory '/temp'."
4279 // | // by providing substitution data in an Array
4280 // | dojo.string.substitute(
4281 // | "File '${0}' is not found in directory '${1}'.",
4282 // | ["foo.html","/temp"]
4283 // | );
4284 // |
4285 // | // also returns "File 'foo.html' is not found in directory '/temp'."
4286 // | // but provides substitution data in an Object structure. Dotted
4287 // | // notation may be used to traverse the structure.
4288 // | dojo.string.substitute(
4289 // | "File '${name}' is not found in directory '${info.dir}'.",
4290 // | { name: "foo.html", info: { dir: "/temp" } }
4291 // | );
4292 // example:
4293 // Use a transform function to modify the values:
4294 // | // returns "file 'foo.html' is not found in directory '/temp'."
4295 // | dojo.string.substitute(
4296 // | "${0} is not found in ${1}.",
4297 // | ["foo.html","/temp"],
4298 // | function(str){
4299 // | // try to figure out the type
4300 // | var prefix = (str.charAt(0) == "/") ? "directory": "file";
4301 // | return prefix + " '" + str + "'";
4302 // | }
4303 // | );
4304 // example:
4305 // Use a formatter
4306 // | // returns "thinger -- howdy"
4307 // | dojo.string.substitute(
4308 // | "${0:postfix}", ["thinger"], null, {
4309 // | postfix: function(value, key){
4310 // | return value + " -- howdy";
4311 // | }
4312 // | }
4313 // | );
4314
4315 thisObject = thisObject || dojo.global;
4316 transform = transform ?
4317 dojo.hitch(thisObject, transform) : function(v){ return v; };
4318
4319 return template.replace(/\$\{([^\s\:\}]+)(?:\:([^\s\:\}]+))?\}/g,
4320 function(match, key, format){
4321 var value = dojo.getObject(key, false, map);
4322 if(format){
4323 value = dojo.getObject(format, false, thisObject).call(thisObject, value, key);
4324 }
4325 return transform(value, key).toString();
4326 }); // String
4327 };
4328
4329 /*=====
4330 dojo.string.trim = function(str){
4331 // summary:
4332 // Trims whitespace from both sides of the string
4333 // str: String
4334 // String to be trimmed
4335 // returns: String
4336 // Returns the trimmed string
4337 // description:
4338 // This version of trim() was taken from [Steven Levithan's blog](http://blog.stevenlevithan.com/archives/faster-trim-javascript).
4339 // The short yet performant version of this function is dojo.trim(),
4340 // which is part of Dojo base. Uses String.prototype.trim instead, if available.
4341 return ""; // String
4342 }
4343 =====*/
4344
4345 dojo.string.trim = String.prototype.trim ?
4346 dojo.trim : // aliasing to the native function
4347 function(str){
4348 str = str.replace(/^\s+/, '');
4349 for(var i = str.length - 1; i >= 0; i--){
4350 if(/\S/.test(str.charAt(i))){
4351 str = str.substring(0, i + 1);
4352 break;
4353 }
4354 }
4355 return str;
4356 };
4357
4358 }
4359
4360 if(!dojo._hasResource["dojo.cache"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4361 dojo._hasResource["dojo.cache"] = true;
4362 dojo.provide("dojo.cache");
4363
4364 /*=====
4365 dojo.cache = {
4366 // summary:
4367 // A way to cache string content that is fetchable via `dojo.moduleUrl`.
4368 };
4369 =====*/
4370
4371 (function(){
4372 var cache = {};
4373 dojo.cache = function(/*String||Object*/module, /*String*/url, /*String||Object?*/value){
4374 // summary:
4375 // A getter and setter for storing the string content associated with the
4376 // module and url arguments.
4377 // description:
4378 // module and url are used to call `dojo.moduleUrl()` to generate a module URL.
4379 // If value is specified, the cache value for the moduleUrl will be set to
4380 // that value. Otherwise, dojo.cache will fetch the moduleUrl and store it
4381 // in its internal cache and return that cached value for the URL. To clear
4382 // a cache value pass null for value. Since XMLHttpRequest (XHR) is used to fetch the
4383 // the URL contents, only modules on the same domain of the page can use this capability.
4384 // The build system can inline the cache values though, to allow for xdomain hosting.
4385 // module: String||Object
4386 // If a String, the module name to use for the base part of the URL, similar to module argument
4387 // to `dojo.moduleUrl`. If an Object, something that has a .toString() method that
4388 // generates a valid path for the cache item. For example, a dojo._Url object.
4389 // url: String
4390 // The rest of the path to append to the path derived from the module argument. If
4391 // module is an object, then this second argument should be the "value" argument instead.
4392 // value: String||Object?
4393 // If a String, the value to use in the cache for the module/url combination.
4394 // If an Object, it can have two properties: value and sanitize. The value property
4395 // should be the value to use in the cache, and sanitize can be set to true or false,
4396 // to indicate if XML declarations should be removed from the value and if the HTML
4397 // inside a body tag in the value should be extracted as the real value. The value argument
4398 // or the value property on the value argument are usually only used by the build system
4399 // as it inlines cache content.
4400 // example:
4401 // To ask dojo.cache to fetch content and store it in the cache (the dojo["cache"] style
4402 // of call is used to avoid an issue with the build system erroneously trying to intern
4403 // this example. To get the build system to intern your dojo.cache calls, use the
4404 // "dojo.cache" style of call):
4405 // | //If template.html contains "<h1>Hello</h1>" that will be
4406 // | //the value for the text variable.
4407 // | var text = dojo["cache"]("my.module", "template.html");
4408 // example:
4409 // To ask dojo.cache to fetch content and store it in the cache, and sanitize the input
4410 // (the dojo["cache"] style of call is used to avoid an issue with the build system
4411 // erroneously trying to intern this example. To get the build system to intern your
4412 // dojo.cache calls, use the "dojo.cache" style of call):
4413 // | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
4414 // | //text variable will contain just "<h1>Hello</h1>".
4415 // | var text = dojo["cache"]("my.module", "template.html", {sanitize: true});
4416 // example:
4417 // Same example as previous, but demostrates how an object can be passed in as
4418 // the first argument, then the value argument can then be the second argument.
4419 // | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
4420 // | //text variable will contain just "<h1>Hello</h1>".
4421 // | var text = dojo["cache"](new dojo._Url("my/module/template.html"), {sanitize: true});
4422
4423 //Module could be a string, or an object that has a toString() method
4424 //that will return a useful path. If it is an object, then the "url" argument
4425 //will actually be the value argument.
4426 if(typeof module == "string"){
4427 var pathObj = dojo.moduleUrl(module, url);
4428 }else{
4429 pathObj = module;
4430 value = url;
4431 }
4432 var key = pathObj.toString();
4433
4434 var val = value;
4435 if(value != undefined && !dojo.isString(value)){
4436 val = ("value" in value ? value.value : undefined);
4437 }
4438
4439 var sanitize = value && value.sanitize ? true : false;
4440
4441 if(typeof val == "string"){
4442 //We have a string, set cache value
4443 val = cache[key] = sanitize ? dojo.cache._sanitize(val) : val;
4444 }else if(val === null){
4445 //Remove cached value
4446 delete cache[key];
4447 }else{
4448 //Allow cache values to be empty strings. If key property does
4449 //not exist, fetch it.
4450 if(!(key in cache)){
4451 val = dojo._getText(key);
4452 cache[key] = sanitize ? dojo.cache._sanitize(val) : val;
4453 }
4454 val = cache[key];
4455 }
4456 return val; //String
4457 };
4458
4459 dojo.cache._sanitize = function(/*String*/val){
4460 // summary:
4461 // Strips <?xml ...?> declarations so that external SVG and XML
4462 // documents can be added to a document without worry. Also, if the string
4463 // is an HTML document, only the part inside the body tag is returned.
4464 // description:
4465 // Copied from dijit._Templated._sanitizeTemplateString.
4466 if(val){
4467 val = val.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, "");
4468 var matches = val.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
4469 if(matches){
4470 val = matches[1];
4471 }
4472 }else{
4473 val = "";
4474 }
4475 return val; //String
4476 };
4477 })();
4478
4479 }
4480
4481 if(!dojo._hasResource["dijit._Templated"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4482 dojo._hasResource["dijit._Templated"] = true;
4483 dojo.provide("dijit._Templated");
4484
4485
4486
4487
4488
4489
4490 dojo.declare("dijit._Templated",
4491 null,
4492 {
4493 // summary:
4494 // Mixin for widgets that are instantiated from a template
4495
4496 // templateString: [protected] String
4497 // A string that represents the widget template. Pre-empts the
4498 // templatePath. In builds that have their strings "interned", the
4499 // templatePath is converted to an inline templateString, thereby
4500 // preventing a synchronous network call.
4501 //
4502 // Use in conjunction with dojo.cache() to load from a file.
4503 templateString: null,
4504
4505 // templatePath: [protected deprecated] String
4506 // Path to template (HTML file) for this widget relative to dojo.baseUrl.
4507 // Deprecated: use templateString with dojo.cache() instead.
4508 templatePath: null,
4509
4510 // widgetsInTemplate: [protected] Boolean
4511 // Should we parse the template to find widgets that might be
4512 // declared in markup inside it? False by default.
4513 widgetsInTemplate: false,
4514
4515 // skipNodeCache: [protected] Boolean
4516 // If using a cached widget template node poses issues for a
4517 // particular widget class, it can set this property to ensure
4518 // that its template is always re-built from a string
4519 _skipNodeCache: false,
4520
4521 // _earlyTemplatedStartup: Boolean
4522 // A fallback to preserve the 1.0 - 1.3 behavior of children in
4523 // templates having their startup called before the parent widget
4524 // fires postCreate. Defaults to 'false', causing child widgets to
4525 // have their .startup() called immediately before a parent widget
4526 // .startup(), but always after the parent .postCreate(). Set to
4527 // 'true' to re-enable to previous, arguably broken, behavior.
4528 _earlyTemplatedStartup: false,
4529
4530 // _attachPoints: [private] String[]
4531 // List of widget attribute names associated with dojoAttachPoint=... in the
4532 // template, ex: ["containerNode", "labelNode"]
4533 /*=====
4534 _attachPoints: [],
4535 =====*/
4536
4537 constructor: function(){
4538 this._attachPoints = [];
4539 },
4540
4541 _stringRepl: function(tmpl){
4542 // summary:
4543 // Does substitution of ${foo} type properties in template string
4544 // tags:
4545 // private
4546 var className = this.declaredClass, _this = this;
4547 // Cache contains a string because we need to do property replacement
4548 // do the property replacement
4549 return dojo.string.substitute(tmpl, this, function(value, key){
4550 if(key.charAt(0) == '!'){ value = dojo.getObject(key.substr(1), false, _this); }
4551 if(typeof value == "undefined"){ throw new Error(className+" template:"+key); } // a debugging aide
4552 if(value == null){ return ""; }
4553
4554 // Substitution keys beginning with ! will skip the transform step,
4555 // in case a user wishes to insert unescaped markup, e.g. ${!foo}
4556 return key.charAt(0) == "!" ? value :
4557 // Safer substitution, see heading "Attribute values" in
4558 // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
4559 value.toString().replace(/"/g,"&quot;"); //TODO: add &amp? use encodeXML method?
4560 }, this);
4561 },
4562
4563 // method over-ride
4564 buildRendering: function(){
4565 // summary:
4566 // Construct the UI for this widget from a template, setting this.domNode.
4567 // tags:
4568 // protected
4569
4570 // Lookup cached version of template, and download to cache if it
4571 // isn't there already. Returns either a DomNode or a string, depending on
4572 // whether or not the template contains ${foo} replacement parameters.
4573 var cached = dijit._Templated.getCachedTemplate(this.templatePath, this.templateString, this._skipNodeCache);
4574
4575 var node;
4576 if(dojo.isString(cached)){
4577 node = dojo._toDom(this._stringRepl(cached));
4578 if(node.nodeType != 1){
4579 // Flag common problems such as templates with multiple top level nodes (nodeType == 11)
4580 throw new Error("Invalid template: " + cached);
4581 }
4582 }else{
4583 // if it's a node, all we have to do is clone it
4584 node = cached.cloneNode(true);
4585 }
4586
4587 this.domNode = node;
4588
4589 // recurse through the node, looking for, and attaching to, our
4590 // attachment points and events, which should be defined on the template node.
4591 this._attachTemplateNodes(node);
4592
4593 if(this.widgetsInTemplate){
4594 // Make sure dojoType is used for parsing widgets in template.
4595 // The dojo.parser.query could be changed from multiversion support.
4596 var parser = dojo.parser, qry, attr;
4597 if(parser._query != "[dojoType]"){
4598 qry = parser._query;
4599 attr = parser._attrName;
4600 parser._query = "[dojoType]";
4601 parser._attrName = "dojoType";
4602 }
4603
4604 // Store widgets that we need to start at a later point in time
4605 var cw = (this._startupWidgets = dojo.parser.parse(node, {
4606 noStart: !this._earlyTemplatedStartup,
4607 inherited: {dir: this.dir, lang: this.lang}
4608 }));
4609
4610 // Restore the query.
4611 if(qry){
4612 parser._query = qry;
4613 parser._attrName = attr;
4614 }
4615
4616 this._supportingWidgets = dijit.findWidgets(node);
4617
4618 this._attachTemplateNodes(cw, function(n,p){
4619 return n[p];
4620 });
4621 }
4622
4623 this._fillContent(this.srcNodeRef);
4624 },
4625
4626 _fillContent: function(/*DomNode*/ source){
4627 // summary:
4628 // Relocate source contents to templated container node.
4629 // this.containerNode must be able to receive children, or exceptions will be thrown.
4630 // tags:
4631 // protected
4632 var dest = this.containerNode;
4633 if(source && dest){
4634 while(source.hasChildNodes()){
4635 dest.appendChild(source.firstChild);
4636 }
4637 }
4638 },
4639
4640 _attachTemplateNodes: function(rootNode, getAttrFunc){
4641 // summary:
4642 // Iterate through the template and attach functions and nodes accordingly.
4643 // description:
4644 // Map widget properties and functions to the handlers specified in
4645 // the dom node and it's descendants. This function iterates over all
4646 // nodes and looks for these properties:
4647 // * dojoAttachPoint
4648 // * dojoAttachEvent
4649 // * waiRole
4650 // * waiState
4651 // rootNode: DomNode|Array[Widgets]
4652 // the node to search for properties. All children will be searched.
4653 // getAttrFunc: Function?
4654 // a function which will be used to obtain property for a given
4655 // DomNode/Widget
4656 // tags:
4657 // private
4658
4659 getAttrFunc = getAttrFunc || function(n,p){ return n.getAttribute(p); };
4660
4661 var nodes = dojo.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*"));
4662 var x = dojo.isArray(rootNode) ? 0 : -1;
4663 for(; x<nodes.length; x++){
4664 var baseNode = (x == -1) ? rootNode : nodes[x];
4665 if(this.widgetsInTemplate && getAttrFunc(baseNode, "dojoType")){
4666 continue;
4667 }
4668 // Process dojoAttachPoint
4669 var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint");
4670 if(attachPoint){
4671 var point, points = attachPoint.split(/\s*,\s*/);
4672 while((point = points.shift())){
4673 if(dojo.isArray(this[point])){
4674 this[point].push(baseNode);
4675 }else{
4676 this[point]=baseNode;
4677 }
4678 this._attachPoints.push(point);
4679 }
4680 }
4681
4682 // Process dojoAttachEvent
4683 var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent");
4684 if(attachEvent){
4685 // NOTE: we want to support attributes that have the form
4686 // "domEvent: nativeEvent; ..."
4687 var event, events = attachEvent.split(/\s*,\s*/);
4688 var trim = dojo.trim;
4689 while((event = events.shift())){
4690 if(event){
4691 var thisFunc = null;
4692 if(event.indexOf(":") != -1){
4693 // oh, if only JS had tuple assignment
4694 var funcNameArr = event.split(":");
4695 event = trim(funcNameArr[0]);
4696 thisFunc = trim(funcNameArr[1]);
4697 }else{
4698 event = trim(event);
4699 }
4700 if(!thisFunc){
4701 thisFunc = event;
4702 }
4703 this.connect(baseNode, event, thisFunc);
4704 }
4705 }
4706 }
4707
4708 // waiRole, waiState
4709 var role = getAttrFunc(baseNode, "waiRole");
4710 if(role){
4711 dijit.setWaiRole(baseNode, role);
4712 }
4713 var values = getAttrFunc(baseNode, "waiState");
4714 if(values){
4715 dojo.forEach(values.split(/\s*,\s*/), function(stateValue){
4716 if(stateValue.indexOf('-') != -1){
4717 var pair = stateValue.split('-');
4718 dijit.setWaiState(baseNode, pair[0], pair[1]);
4719 }
4720 });
4721 }
4722 }
4723 },
4724
4725 startup: function(){
4726 dojo.forEach(this._startupWidgets, function(w){
4727 if(w && !w._started && w.startup){
4728 w.startup();
4729 }
4730 });
4731 this.inherited(arguments);
4732 },
4733
4734 destroyRendering: function(){
4735 // Delete all attach points to prevent IE6 memory leaks.
4736 dojo.forEach(this._attachPoints, function(point){
4737 delete this[point];
4738 }, this);
4739 this._attachPoints = [];
4740
4741 this.inherited(arguments);
4742 }
4743 }
4744 );
4745
4746 // key is either templatePath or templateString; object is either string or DOM tree
4747 dijit._Templated._templateCache = {};
4748
4749 dijit._Templated.getCachedTemplate = function(templatePath, templateString, alwaysUseString){
4750 // summary:
4751 // Static method to get a template based on the templatePath or
4752 // templateString key
4753 // templatePath: String||dojo.uri.Uri
4754 // The URL to get the template from.
4755 // templateString: String?
4756 // a string to use in lieu of fetching the template from a URL. Takes precedence
4757 // over templatePath
4758 // returns: Mixed
4759 // Either string (if there are ${} variables that need to be replaced) or just
4760 // a DOM tree (if the node can be cloned directly)
4761
4762 // is it already cached?
4763 var tmplts = dijit._Templated._templateCache;
4764 var key = templateString || templatePath;
4765 var cached = tmplts[key];
4766 if(cached){
4767 try{
4768 // 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
4769 if(!cached.ownerDocument || cached.ownerDocument == dojo.doc){
4770 // string or node of the same document
4771 return cached;
4772 }
4773 }catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded
4774 dojo.destroy(cached);
4775 }
4776
4777 // If necessary, load template string from template path
4778 if(!templateString){
4779 templateString = dojo.cache(templatePath, {sanitize: true});
4780 }
4781 templateString = dojo.string.trim(templateString);
4782
4783 if(alwaysUseString || templateString.match(/\$\{([^\}]+)\}/g)){
4784 // there are variables in the template so all we can do is cache the string
4785 return (tmplts[key] = templateString); //String
4786 }else{
4787 // there are no variables in the template so we can cache the DOM tree
4788 var node = dojo._toDom(templateString);
4789 if(node.nodeType != 1){
4790 throw new Error("Invalid template: " + templateString);
4791 }
4792 return (tmplts[key] = node); //Node
4793 }
4794 };
4795
4796 if(dojo.isIE){
4797 dojo.addOnWindowUnload(function(){
4798 var cache = dijit._Templated._templateCache;
4799 for(var key in cache){
4800 var value = cache[key];
4801 if(typeof value == "object"){ // value is either a string or a DOM node template
4802 dojo.destroy(value);
4803 }
4804 delete cache[key];
4805 }
4806 });
4807 }
4808
4809 // These arguments can be specified for widgets which are used in templates.
4810 // Since any widget can be specified as sub widgets in template, mix it
4811 // into the base widget class. (This is a hack, but it's effective.)
4812 dojo.extend(dijit._Widget,{
4813 dojoAttachEvent: "",
4814 dojoAttachPoint: "",
4815 waiRole: "",
4816 waiState:""
4817 });
4818
4819 }
4820
4821 if(!dojo._hasResource["dijit._Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4822 dojo._hasResource["dijit._Container"] = true;
4823 dojo.provide("dijit._Container");
4824
4825 dojo.declare("dijit._Container",
4826 null,
4827 {
4828 // summary:
4829 // Mixin for widgets that contain a set of widget children.
4830 // description:
4831 // Use this mixin for widgets that needs to know about and
4832 // keep track of their widget children. Suitable for widgets like BorderContainer
4833 // and TabContainer which contain (only) a set of child widgets.
4834 //
4835 // It's not suitable for widgets like ContentPane
4836 // which contains mixed HTML (plain DOM nodes in addition to widgets),
4837 // and where contained widgets are not necessarily directly below
4838 // this.containerNode. In that case calls like addChild(node, position)
4839 // wouldn't make sense.
4840
4841 // isContainer: [protected] Boolean
4842 // Indicates that this widget acts as a "parent" to the descendant widgets.
4843 // When the parent is started it will call startup() on the child widgets.
4844 // See also `isLayoutContainer`.
4845 isContainer: true,
4846
4847 buildRendering: function(){
4848 this.inherited(arguments);
4849 if(!this.containerNode){
4850 // all widgets with descendants must set containerNode
4851 this.containerNode = this.domNode;
4852 }
4853 },
4854
4855 addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
4856 // summary:
4857 // Makes the given widget a child of this widget.
4858 // description:
4859 // Inserts specified child widget's dom node as a child of this widget's
4860 // container node, and possibly does other processing (such as layout).
4861
4862 var refNode = this.containerNode;
4863 if(insertIndex && typeof insertIndex == "number"){
4864 var children = this.getChildren();
4865 if(children && children.length >= insertIndex){
4866 refNode = children[insertIndex-1].domNode;
4867 insertIndex = "after";
4868 }
4869 }
4870 dojo.place(widget.domNode, refNode, insertIndex);
4871
4872 // If I've been started but the child widget hasn't been started,
4873 // start it now. Make sure to do this after widget has been
4874 // inserted into the DOM tree, so it can see that it's being controlled by me,
4875 // so it doesn't try to size itself.
4876 if(this._started && !widget._started){
4877 widget.startup();
4878 }
4879 },
4880
4881 removeChild: function(/*Widget or int*/ widget){
4882 // summary:
4883 // Removes the passed widget instance from this widget but does
4884 // not destroy it. You can also pass in an integer indicating
4885 // the index within the container to remove
4886
4887 if(typeof widget == "number" && widget > 0){
4888 widget = this.getChildren()[widget];
4889 }
4890
4891 if(widget){
4892 var node = widget.domNode;
4893 if(node && node.parentNode){
4894 node.parentNode.removeChild(node); // detach but don't destroy
4895 }
4896 }
4897 },
4898
4899 hasChildren: function(){
4900 // summary:
4901 // Returns true if widget has children, i.e. if this.containerNode contains something.
4902 return this.getChildren().length > 0; // Boolean
4903 },
4904
4905 destroyDescendants: function(/*Boolean*/ preserveDom){
4906 // summary:
4907 // Destroys all the widgets inside this.containerNode,
4908 // but not this widget itself
4909 dojo.forEach(this.getChildren(), function(child){ child.destroyRecursive(preserveDom); });
4910 },
4911
4912 _getSiblingOfChild: function(/*dijit._Widget*/ child, /*int*/ dir){
4913 // summary:
4914 // Get the next or previous widget sibling of child
4915 // dir:
4916 // if 1, get the next sibling
4917 // if -1, get the previous sibling
4918 // tags:
4919 // private
4920 var node = child.domNode,
4921 which = (dir>0 ? "nextSibling" : "previousSibling");
4922 do{
4923 node = node[which];
4924 }while(node && (node.nodeType != 1 || !dijit.byNode(node)));
4925 return node && dijit.byNode(node); // dijit._Widget
4926 },
4927
4928 getIndexOfChild: function(/*dijit._Widget*/ child){
4929 // summary:
4930 // Gets the index of the child in this container or -1 if not found
4931 return dojo.indexOf(this.getChildren(), child); // int
4932 },
4933
4934 startup: function(){
4935 // summary:
4936 // Called after all the widgets have been instantiated and their
4937 // dom nodes have been inserted somewhere under dojo.doc.body.
4938 //
4939 // Widgets should override this method to do any initialization
4940 // dependent on other widgets existing, and then call
4941 // this superclass method to finish things off.
4942 //
4943 // startup() in subclasses shouldn't do anything
4944 // size related because the size of the widget hasn't been set yet.
4945
4946 if(this._started){ return; }
4947
4948 // Startup all children of this widget
4949 dojo.forEach(this.getChildren(), function(child){ child.startup(); });
4950
4951 this.inherited(arguments);
4952 }
4953 }
4954 );
4955
4956 }
4957
4958 if(!dojo._hasResource["dijit._Contained"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4959 dojo._hasResource["dijit._Contained"] = true;
4960 dojo.provide("dijit._Contained");
4961
4962 dojo.declare("dijit._Contained",
4963 null,
4964 {
4965 // summary:
4966 // Mixin for widgets that are children of a container widget
4967 //
4968 // example:
4969 // | // make a basic custom widget that knows about it's parents
4970 // | dojo.declare("my.customClass",[dijit._Widget,dijit._Contained],{});
4971
4972 getParent: function(){
4973 // summary:
4974 // Returns the parent widget of this widget, assuming the parent
4975 // specifies isContainer
4976 var parent = dijit.getEnclosingWidget(this.domNode.parentNode);
4977 return parent && parent.isContainer ? parent : null;
4978 },
4979
4980 _getSibling: function(/*String*/ which){
4981 // summary:
4982 // Returns next or previous sibling
4983 // which:
4984 // Either "next" or "previous"
4985 // tags:
4986 // private
4987 var node = this.domNode;
4988 do{
4989 node = node[which+"Sibling"];
4990 }while(node && node.nodeType != 1);
4991 return node && dijit.byNode(node); // dijit._Widget
4992 },
4993
4994 getPreviousSibling: function(){
4995 // summary:
4996 // Returns null if this is the first child of the parent,
4997 // otherwise returns the next element sibling to the "left".
4998
4999 return this._getSibling("previous"); // dijit._Widget
5000 },
5001
5002 getNextSibling: function(){
5003 // summary:
5004 // Returns null if this is the last child of the parent,
5005 // otherwise returns the next element sibling to the "right".
5006
5007 return this._getSibling("next"); // dijit._Widget
5008 },
5009
5010 getIndexInParent: function(){
5011 // summary:
5012 // Returns the index of this widget within its container parent.
5013 // It returns -1 if the parent does not exist, or if the parent
5014 // is not a dijit._Container
5015
5016 var p = this.getParent();
5017 if(!p || !p.getIndexOfChild){
5018 return -1; // int
5019 }
5020 return p.getIndexOfChild(this); // int
5021 }
5022 }
5023 );
5024
5025
5026 }
5027
5028 if(!dojo._hasResource["dijit.layout._LayoutWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5029 dojo._hasResource["dijit.layout._LayoutWidget"] = true;
5030 dojo.provide("dijit.layout._LayoutWidget");
5031
5032
5033
5034
5035
5036 dojo.declare("dijit.layout._LayoutWidget",
5037 [dijit._Widget, dijit._Container, dijit._Contained],
5038 {
5039 // summary:
5040 // Base class for a _Container widget which is responsible for laying out its children.
5041 // Widgets which mixin this code must define layout() to manage placement and sizing of the children.
5042
5043 // baseClass: [protected extension] String
5044 // This class name is applied to the widget's domNode
5045 // and also may be used to generate names for sub nodes,
5046 // for example dijitTabContainer-content.
5047 baseClass: "dijitLayoutContainer",
5048
5049 // isLayoutContainer: [protected] Boolean
5050 // Indicates that this widget is going to call resize() on its
5051 // children widgets, setting their size, when they become visible.
5052 isLayoutContainer: true,
5053
5054 postCreate: function(){
5055 dojo.addClass(this.domNode, "dijitContainer");
5056
5057 this.inherited(arguments);
5058 },
5059
5060 startup: function(){
5061 // summary:
5062 // Called after all the widgets have been instantiated and their
5063 // dom nodes have been inserted somewhere under dojo.doc.body.
5064 //
5065 // Widgets should override this method to do any initialization
5066 // dependent on other widgets existing, and then call
5067 // this superclass method to finish things off.
5068 //
5069 // startup() in subclasses shouldn't do anything
5070 // size related because the size of the widget hasn't been set yet.
5071
5072 if(this._started){ return; }
5073
5074 // Need to call inherited first - so that child widgets get started
5075 // up correctly
5076 this.inherited(arguments);
5077
5078 // If I am a not being controlled by a parent layout widget...
5079 var parent = this.getParent && this.getParent()
5080 if(!(parent && parent.isLayoutContainer)){
5081 // Do recursive sizing and layout of all my descendants
5082 // (passing in no argument to resize means that it has to glean the size itself)
5083 this.resize();
5084
5085 // Since my parent isn't a layout container, and my style *may be* width=height=100%
5086 // or something similar (either set directly or via a CSS class),
5087 // monitor when my size changes so that I can re-layout.
5088 // For browsers where I can't directly monitor when my size changes,
5089 // monitor when the viewport changes size, which *may* indicate a size change for me.
5090 this.connect(dojo.isIE ? this.domNode : dojo.global, 'onresize', function(){
5091 // Using function(){} closure to ensure no arguments to resize.
5092 this.resize();
5093 });
5094 }
5095 },
5096
5097 resize: function(changeSize, resultSize){
5098 // summary:
5099 // Call this to resize a widget, or after its size has changed.
5100 // description:
5101 // Change size mode:
5102 // When changeSize is specified, changes the marginBox of this widget
5103 // and forces it to relayout its contents accordingly.
5104 // changeSize may specify height, width, or both.
5105 //
5106 // If resultSize is specified it indicates the size the widget will
5107 // become after changeSize has been applied.
5108 //
5109 // Notification mode:
5110 // When changeSize is null, indicates that the caller has already changed
5111 // the size of the widget, or perhaps it changed because the browser
5112 // window was resized. Tells widget to relayout its contents accordingly.
5113 //
5114 // If resultSize is also specified it indicates the size the widget has
5115 // become.
5116 //
5117 // In either mode, this method also:
5118 // 1. Sets this._borderBox and this._contentBox to the new size of
5119 // the widget. Queries the current domNode size if necessary.
5120 // 2. Calls layout() to resize contents (and maybe adjust child widgets).
5121 //
5122 // changeSize: Object?
5123 // Sets the widget to this margin-box size and position.
5124 // May include any/all of the following properties:
5125 // | {w: int, h: int, l: int, t: int}
5126 //
5127 // resultSize: Object?
5128 // The margin-box size of this widget after applying changeSize (if
5129 // changeSize is specified). If caller knows this size and
5130 // passes it in, we don't need to query the browser to get the size.
5131 // | {w: int, h: int}
5132
5133 var node = this.domNode;
5134
5135 // set margin box size, unless it wasn't specified, in which case use current size
5136 if(changeSize){
5137 dojo.marginBox(node, changeSize);
5138
5139 // set offset of the node
5140 if(changeSize.t){ node.style.top = changeSize.t + "px"; }
5141 if(changeSize.l){ node.style.left = changeSize.l + "px"; }
5142 }
5143
5144 // If either height or width wasn't specified by the user, then query node for it.
5145 // But note that setting the margin box and then immediately querying dimensions may return
5146 // inaccurate results, so try not to depend on it.
5147 var mb = resultSize || {};
5148 dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize
5149 if( !("h" in mb) || !("w" in mb) ){
5150 mb = dojo.mixin(dojo.marginBox(node), mb); // just use dojo.marginBox() to fill in missing values
5151 }
5152
5153 // Compute and save the size of my border box and content box
5154 // (w/out calling dojo.contentBox() since that may fail if size was recently set)
5155 var cs = dojo.getComputedStyle(node);
5156 var me = dojo._getMarginExtents(node, cs);
5157 var be = dojo._getBorderExtents(node, cs);
5158 var bb = (this._borderBox = {
5159 w: mb.w - (me.w + be.w),
5160 h: mb.h - (me.h + be.h)
5161 });
5162 var pe = dojo._getPadExtents(node, cs);
5163 this._contentBox = {
5164 l: dojo._toPixelValue(node, cs.paddingLeft),
5165 t: dojo._toPixelValue(node, cs.paddingTop),
5166 w: bb.w - pe.w,
5167 h: bb.h - pe.h
5168 };
5169
5170 // Callback for widget to adjust size of its children
5171 this.layout();
5172 },
5173
5174 layout: function(){
5175 // summary:
5176 // Widgets override this method to size and position their contents/children.
5177 // When this is called this._contentBox is guaranteed to be set (see resize()).
5178 //
5179 // This is called after startup(), and also when the widget's size has been
5180 // changed.
5181 // tags:
5182 // protected extension
5183 },
5184
5185 _setupChild: function(/*dijit._Widget*/child){
5186 // summary:
5187 // Common setup for initial children and children which are added after startup
5188 // tags:
5189 // protected extension
5190
5191 dojo.addClass(child.domNode, this.baseClass+"-child");
5192 if(child.baseClass){
5193 dojo.addClass(child.domNode, this.baseClass+"-"+child.baseClass);
5194 }
5195 },
5196
5197 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
5198 // Overrides _Container.addChild() to call _setupChild()
5199 this.inherited(arguments);
5200 if(this._started){
5201 this._setupChild(child);
5202 }
5203 },
5204
5205 removeChild: function(/*dijit._Widget*/ child){
5206 // Overrides _Container.removeChild() to remove class added by _setupChild()
5207 dojo.removeClass(child.domNode, this.baseClass+"-child");
5208 if(child.baseClass){
5209 dojo.removeClass(child.domNode, this.baseClass+"-"+child.baseClass);
5210 }
5211 this.inherited(arguments);
5212 }
5213 }
5214 );
5215
5216 dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
5217 // summary:
5218 // Given the margin-box size of a node, return its content box size.
5219 // Functions like dojo.contentBox() but is more reliable since it doesn't have
5220 // to wait for the browser to compute sizes.
5221 var cs = dojo.getComputedStyle(node);
5222 var me = dojo._getMarginExtents(node, cs);
5223 var pb = dojo._getPadBorderExtents(node, cs);
5224 return {
5225 l: dojo._toPixelValue(node, cs.paddingLeft),
5226 t: dojo._toPixelValue(node, cs.paddingTop),
5227 w: mb.w - (me.w + pb.w),
5228 h: mb.h - (me.h + pb.h)
5229 };
5230 };
5231
5232 (function(){
5233 var capitalize = function(word){
5234 return word.substring(0,1).toUpperCase() + word.substring(1);
5235 };
5236
5237 var size = function(widget, dim){
5238 // size the child
5239 widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim);
5240
5241 // record child's size, but favor our own numbers when we have them.
5242 // the browser lies sometimes
5243 dojo.mixin(widget, dojo.marginBox(widget.domNode));
5244 dojo.mixin(widget, dim);
5245 };
5246
5247 dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Object[]*/ children){
5248 // summary
5249 // Layout a bunch of child dom nodes within a parent dom node
5250 // container:
5251 // parent node
5252 // dim:
5253 // {l, t, w, h} object specifying dimensions of container into which to place children
5254 // children:
5255 // an array like [ {domNode: foo, layoutAlign: "bottom" }, {domNode: bar, layoutAlign: "client"} ]
5256
5257 // copy dim because we are going to modify it
5258 dim = dojo.mixin({}, dim);
5259
5260 dojo.addClass(container, "dijitLayoutContainer");
5261
5262 // Move "client" elements to the end of the array for layout. a11y dictates that the author
5263 // needs to be able to put them in the document in tab-order, but this algorithm requires that
5264 // client be last.
5265 children = dojo.filter(children, function(item){ return item.layoutAlign != "client"; })
5266 .concat(dojo.filter(children, function(item){ return item.layoutAlign == "client"; }));
5267
5268 // set positions/sizes
5269 dojo.forEach(children, function(child){
5270 var elm = child.domNode,
5271 pos = child.layoutAlign;
5272
5273 // set elem to upper left corner of unused space; may move it later
5274 var elmStyle = elm.style;
5275 elmStyle.left = dim.l+"px";
5276 elmStyle.top = dim.t+"px";
5277 elmStyle.bottom = elmStyle.right = "auto";
5278
5279 dojo.addClass(elm, "dijitAlign" + capitalize(pos));
5280
5281 // set size && adjust record of remaining space.
5282 // note that setting the width of a <div> may affect its height.
5283 if(pos == "top" || pos == "bottom"){
5284 size(child, { w: dim.w });
5285 dim.h -= child.h;
5286 if(pos == "top"){
5287 dim.t += child.h;
5288 }else{
5289 elmStyle.top = dim.t + dim.h + "px";
5290 }
5291 }else if(pos == "left" || pos == "right"){
5292 size(child, { h: dim.h });
5293 dim.w -= child.w;
5294 if(pos == "left"){
5295 dim.l += child.w;
5296 }else{
5297 elmStyle.left = dim.l + dim.w + "px";
5298 }
5299 }else if(pos == "client"){
5300 size(child, dim);
5301 }
5302 });
5303 };
5304
5305 })();
5306
5307 }
5308
5309 if(!dojo._hasResource["dijit._CssStateMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5310 dojo._hasResource["dijit._CssStateMixin"] = true;
5311 dojo.provide("dijit._CssStateMixin");
5312
5313
5314 dojo.declare("dijit._CssStateMixin", [], {
5315 // summary:
5316 // Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus
5317 // state changes, and also higher-level state changes such becoming disabled or selected.
5318 //
5319 // description:
5320 // By mixing this class into your widget, and setting the this.baseClass attribute, it will automatically
5321 // maintain CSS classes on the widget root node (this.domNode) depending on hover,
5322 // active, focus, etc. state. Ex: with a baseClass of dijitButton, it will apply the classes
5323 // dijitButtonHovered and dijitButtonActive, as the user moves the mouse over the widget and clicks it.
5324 //
5325 // It also sets CSS like dijitButtonDisabled based on widget semantic state.
5326 //
5327 // By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons
5328 // within the widget).
5329
5330 // cssStateNodes: [protected] Object
5331 // List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus
5332 //.
5333 // Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names
5334 // (like "dijitUpArrowButton"). Example:
5335 // | {
5336 // | "upArrowButton": "dijitUpArrowButton",
5337 // | "downArrowButton": "dijitDownArrowButton"
5338 // | }
5339 // The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it
5340 // is hovered, etc.
5341 cssStateNodes: {},
5342
5343 postCreate: function(){
5344 this.inherited(arguments);
5345
5346 // Automatically monitor mouse events (essentially :hover and :active) on this.domNode
5347 dojo.forEach(["onmouseenter", "onmouseleave", "onmousedown"], function(e){
5348 this.connect(this.domNode, e, "_cssMouseEvent");
5349 }, this);
5350
5351 // Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node
5352 this.connect(this, "set", function(name, value){
5353 if(arguments.length >= 2 && {disabled: true, readOnly: true, checked:true, selected:true}[name]){
5354 this._setStateClass();
5355 }
5356 });
5357
5358 // The widget coming in/out of the focus change affects it's state
5359 dojo.forEach(["_onFocus", "_onBlur"], function(ap){
5360 this.connect(this, ap, "_setStateClass");
5361 }, this);
5362
5363 // Events on sub nodes within the widget
5364 for(var ap in this.cssStateNodes){
5365 this._trackMouseState(this[ap], this.cssStateNodes[ap]);
5366 }
5367 // Set state initially; there's probably no hover/active/focus state but widget might be
5368 // disabled/readonly so we want to set CSS classes for those conditions.
5369 this._setStateClass();
5370 },
5371
5372 _cssMouseEvent: function(/*Event*/ event){
5373 // summary:
5374 // Sets _hovering and _active properties depending on mouse state,
5375 // then calls _setStateClass() to set appropriate CSS classes for this.domNode.
5376
5377 if(!this.disabled){
5378 switch(event.type){
5379 case "mouseenter":
5380 case "mouseover": // generated on non-IE browsers even though we connected to mouseenter
5381 this._hovering = true;
5382 this._active = this._mouseDown;
5383 break;
5384
5385 case "mouseleave":
5386 case "mouseout": // generated on non-IE browsers even though we connected to mouseleave
5387 this._hovering = false;
5388 this._active = false;
5389 break;
5390
5391 case "mousedown" :
5392 this._active = true;
5393 this._mouseDown = true;
5394 // Set a global event to handle mouseup, so it fires properly
5395 // even if the cursor leaves this.domNode before the mouse up event.
5396 // Alternately could set active=false on mouseout.
5397 var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){
5398 this._active = false;
5399 this._mouseDown = false;
5400 this._setStateClass();
5401 this.disconnect(mouseUpConnector);
5402 });
5403 break;
5404 }
5405 this._setStateClass();
5406 }
5407 },
5408
5409 _setStateClass: function(){
5410 // summary:
5411 // Update the visual state of the widget by setting the css classes on this.domNode
5412 // (or this.stateNode if defined) by combining this.baseClass with
5413 // various suffixes that represent the current widget state(s).
5414 //
5415 // description:
5416 // In the case where a widget has multiple
5417 // states, it sets the class based on all possible
5418 // combinations. For example, an invalid form widget that is being hovered
5419 // will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover".
5420 //
5421 // The widget may have one or more of the following states, determined
5422 // by this.state, this.checked, this.valid, and this.selected:
5423 // - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid
5424 // - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true
5425 // - Selected - ex: currently selected tab will have this.selected==true
5426 //
5427 // In addition, it may have one or more of the following states,
5428 // based on this.disabled and flags set in _onMouse (this._active, this._hovering, this._focused):
5429 // - Disabled - if the widget is disabled
5430 // - Active - if the mouse (or space/enter key?) is being pressed down
5431 // - Focused - if the widget has focus
5432 // - Hover - if the mouse is over the widget
5433
5434 // Compute new set of classes
5435 var newStateClasses = this.baseClass.split(" ");
5436
5437 function multiply(modifier){
5438 newStateClasses = newStateClasses.concat(dojo.map(newStateClasses, function(c){ return c+modifier; }), "dijit"+modifier);
5439 }
5440
5441 if(!this.isLeftToRight()){
5442 // For RTL mode we need to set an addition class like dijitTextBoxRtl.
5443 multiply("Rtl");
5444 }
5445
5446 if(this.checked){
5447 multiply("Checked");
5448 }
5449 if(this.state){
5450 multiply(this.state);
5451 }
5452 if(this.selected){
5453 multiply("Selected");
5454 }
5455
5456 if(this.disabled){
5457 multiply("Disabled");
5458 }else if(this.readOnly){
5459 multiply("ReadOnly");
5460 }else{
5461 if(this._active){
5462 multiply("Active");
5463 }else if(this._hovering){
5464 multiply("Hover");
5465 }
5466 }
5467
5468 if(this._focused){
5469 multiply("Focused");
5470 }
5471
5472 // Remove old state classes and add new ones.
5473 // For performance concerns we only write into domNode.className once.
5474 var tn = this.stateNode || this.domNode,
5475 classHash = {}; // set of all classes (state and otherwise) for node
5476
5477 dojo.forEach(tn.className.split(" "), function(c){ classHash[c] = true; });
5478
5479 if("_stateClasses" in this){
5480 dojo.forEach(this._stateClasses, function(c){ delete classHash[c]; });
5481 }
5482
5483 dojo.forEach(newStateClasses, function(c){ classHash[c] = true; });
5484
5485 var newClasses = [];
5486 for(var c in classHash){
5487 newClasses.push(c);
5488 }
5489 tn.className = newClasses.join(" ");
5490
5491 this._stateClasses = newStateClasses;
5492 },
5493
5494 _trackMouseState: function(/*DomNode*/ node, /*String*/ clazz){
5495 // summary:
5496 // Track mouse/focus events on specified node and set CSS class on that node to indicate
5497 // current state. Usually not called directly, but via cssStateNodes attribute.
5498 // description:
5499 // Given class=foo, will set the following CSS class on the node
5500 // - fooActive: if the user is currently pressing down the mouse button while over the node
5501 // - fooHover: if the user is hovering the mouse over the node, but not pressing down a button
5502 // - fooFocus: if the node is focused
5503 //
5504 // Note that it won't set any classes if the widget is disabled.
5505 // node: DomNode
5506 // Should be a sub-node of the widget, not the top node (this.domNode), since the top node
5507 // is handled specially and automatically just by mixing in this class.
5508 // clazz: String
5509 // CSS class name (ex: dijitSliderUpArrow).
5510
5511 // Current state of node (initially false)
5512 // NB: setting specifically to false because dojo.toggleClass() needs true boolean as third arg
5513 var hovering=false, active=false, focused=false;
5514
5515 var self = this,
5516 cn = dojo.hitch(this, "connect", node);
5517
5518 function setClass(){
5519 var disabled = ("disabled" in self && self.disabled) || ("readonly" in self && self.readonly);
5520 dojo.toggleClass(node, clazz+"Hover", hovering && !active && !disabled);
5521 dojo.toggleClass(node, clazz+"Active", active && !disabled);
5522 dojo.toggleClass(node, clazz+"Focused", focused && !disabled);
5523 }
5524
5525 // Mouse
5526 cn("onmouseenter", function(){
5527 hovering = true;
5528 setClass();
5529 });
5530 cn("onmouseleave", function(){
5531 hovering = false;
5532 active = false;
5533 setClass();
5534 });
5535 cn("onmousedown", function(){
5536 active = true;
5537 setClass();
5538 });
5539 cn("onmouseup", function(){
5540 active = false;
5541 setClass();
5542 });
5543
5544 // Focus
5545 cn("onfocus", function(){
5546 focused = true;
5547 setClass();
5548 });
5549 cn("onblur", function(){
5550 focused = false;
5551 setClass();
5552 });
5553
5554 // Just in case widget is enabled/disabled while it has focus/hover/active state.
5555 // Maybe this is overkill.
5556 this.connect(this, "set", function(name, value){
5557 if(name == "disabled" || name == "readOnly"){
5558 setClass();
5559 }
5560 });
5561 }
5562 });
5563
5564 }
5565
5566 if(!dojo._hasResource["dijit.form._FormWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5567 dojo._hasResource["dijit.form._FormWidget"] = true;
5568 dojo.provide("dijit.form._FormWidget");
5569
5570
5571
5572
5573
5574
5575
5576 dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
5577 {
5578 // summary:
5579 // Base class for widgets corresponding to native HTML elements such as <checkbox> or <button>,
5580 // which can be children of a <form> node or a `dijit.form.Form` widget.
5581 //
5582 // description:
5583 // Represents a single HTML element.
5584 // All these widgets should have these attributes just like native HTML input elements.
5585 // You can set them during widget construction or afterwards, via `dijit._Widget.attr`.
5586 //
5587 // They also share some common methods.
5588
5589 // name: String
5590 // Name used when submitting form; same as "name" attribute or plain HTML elements
5591 name: "",
5592
5593 // alt: String
5594 // Corresponds to the native HTML <input> element's attribute.
5595 alt: "",
5596
5597 // value: String
5598 // Corresponds to the native HTML <input> element's attribute.
5599 value: "",
5600
5601 // type: String
5602 // Corresponds to the native HTML <input> element's attribute.
5603 type: "text",
5604
5605 // tabIndex: Integer
5606 // Order fields are traversed when user hits the tab key
5607 tabIndex: "0",
5608
5609 // disabled: Boolean
5610 // Should this widget respond to user input?
5611 // In markup, this is specified as "disabled='disabled'", or just "disabled".
5612 disabled: false,
5613
5614 // intermediateChanges: Boolean
5615 // Fires onChange for each value change or only on demand
5616 intermediateChanges: false,
5617
5618 // scrollOnFocus: Boolean
5619 // On focus, should this widget scroll into view?
5620 scrollOnFocus: true,
5621
5622 // These mixins assume that the focus node is an INPUT, as many but not all _FormWidgets are.
5623 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
5624 value: "focusNode",
5625 id: "focusNode",
5626 tabIndex: "focusNode",
5627 alt: "focusNode",
5628 title: "focusNode"
5629 }),
5630
5631 postMixInProperties: function(){
5632 // Setup name=foo string to be referenced from the template (but only if a name has been specified)
5633 // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660
5634 // Regarding escaping, see heading "Attribute values" in
5635 // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
5636 this.nameAttrSetting = this.name ? ('name="' + this.name.replace(/'/g, "&quot;") + '"') : '';
5637 this.inherited(arguments);
5638 },
5639
5640 postCreate: function(){
5641 this.inherited(arguments);
5642 this.connect(this.domNode, "onmousedown", "_onMouseDown");
5643 },
5644
5645 _setDisabledAttr: function(/*Boolean*/ value){
5646 this.disabled = value;
5647 dojo.attr(this.focusNode, 'disabled', value);
5648 if(this.valueNode){
5649 dojo.attr(this.valueNode, 'disabled', value);
5650 }
5651 dijit.setWaiState(this.focusNode, "disabled", value);
5652
5653 if(value){
5654 // reset these, because after the domNode is disabled, we can no longer receive
5655 // mouse related events, see #4200
5656 this._hovering = false;
5657 this._active = false;
5658
5659 // clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes)
5660 var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex : "focusNode";
5661 dojo.forEach(dojo.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){
5662 var node = this[attachPointName];
5663 // complex code because tabIndex=-1 on a <div> doesn't work on FF
5664 if(dojo.isWebKit || dijit.hasDefaultTabStop(node)){ // see #11064 about webkit bug
5665 node.setAttribute('tabIndex', "-1");
5666 }else{
5667 node.removeAttribute('tabIndex');
5668 }
5669 }, this);
5670 }else{
5671 this.focusNode.setAttribute('tabIndex', this.tabIndex);
5672 }
5673 },
5674
5675 setDisabled: function(/*Boolean*/ disabled){
5676 // summary:
5677 // Deprecated. Use set('disabled', ...) instead.
5678 dojo.deprecated("setDisabled("+disabled+") is deprecated. Use set('disabled',"+disabled+") instead.", "", "2.0");
5679 this.set('disabled', disabled);
5680 },
5681
5682 _onFocus: function(e){
5683 if(this.scrollOnFocus){
5684 dojo.window.scrollIntoView(this.domNode);
5685 }
5686 this.inherited(arguments);
5687 },
5688
5689 isFocusable: function(){
5690 // summary:
5691 // Tells if this widget is focusable or not. Used internally by dijit.
5692 // tags:
5693 // protected
5694 return !this.disabled && !this.readOnly && this.focusNode && (dojo.style(this.domNode, "display") != "none");
5695 },
5696
5697 focus: function(){
5698 // summary:
5699 // Put focus on this widget
5700 dijit.focus(this.focusNode);
5701 },
5702
5703 compare: function(/*anything*/val1, /*anything*/val2){
5704 // summary:
5705 // Compare 2 values (as returned by attr('value') for this widget).
5706 // tags:
5707 // protected
5708 if(typeof val1 == "number" && typeof val2 == "number"){
5709 return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
5710 }else if(val1 > val2){
5711 return 1;
5712 }else if(val1 < val2){
5713 return -1;
5714 }else{
5715 return 0;
5716 }
5717 },
5718
5719 onChange: function(newValue){
5720 // summary:
5721 // Callback when this widget's value is changed.
5722 // tags:
5723 // callback
5724 },
5725
5726 // _onChangeActive: [private] Boolean
5727 // Indicates that changes to the value should call onChange() callback.
5728 // This is false during widget initialization, to avoid calling onChange()
5729 // when the initial value is set.
5730 _onChangeActive: false,
5731
5732 _handleOnChange: function(/*anything*/ newValue, /* Boolean? */ priorityChange){
5733 // summary:
5734 // Called when the value of the widget is set. Calls onChange() if appropriate
5735 // newValue:
5736 // the new value
5737 // priorityChange:
5738 // For a slider, for example, dragging the slider is priorityChange==false,
5739 // but on mouse up, it's priorityChange==true. If intermediateChanges==true,
5740 // onChange is only called form priorityChange=true events.
5741 // tags:
5742 // private
5743 this._lastValue = newValue;
5744 if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
5745 // this block executes not for a change, but during initialization,
5746 // and is used to store away the original value (or for ToggleButton, the original checked state)
5747 this._resetValue = this._lastValueReported = newValue;
5748 }
5749 if((this.intermediateChanges || priorityChange || priorityChange === undefined) &&
5750 ((typeof newValue != typeof this._lastValueReported) ||
5751 this.compare(newValue, this._lastValueReported) != 0)){
5752 this._lastValueReported = newValue;
5753 if(this._onChangeActive){
5754 if(this._onChangeHandle){
5755 clearTimeout(this._onChangeHandle);
5756 }
5757 // setTimout allows hidden value processing to run and
5758 // also the onChange handler can safely adjust focus, etc
5759 this._onChangeHandle = setTimeout(dojo.hitch(this,
5760 function(){
5761 this._onChangeHandle = null;
5762 this.onChange(newValue);
5763 }), 0); // try to collapse multiple onChange's fired faster than can be processed
5764 }
5765 }
5766 },
5767
5768 create: function(){
5769 // Overrides _Widget.create()
5770 this.inherited(arguments);
5771 this._onChangeActive = true;
5772 },
5773
5774 destroy: function(){
5775 if(this._onChangeHandle){ // destroy called before last onChange has fired
5776 clearTimeout(this._onChangeHandle);
5777 this.onChange(this._lastValueReported);
5778 }
5779 this.inherited(arguments);
5780 },
5781
5782 setValue: function(/*String*/ value){
5783 // summary:
5784 // Deprecated. Use set('value', ...) instead.
5785 dojo.deprecated("dijit.form._FormWidget:setValue("+value+") is deprecated. Use set('value',"+value+") instead.", "", "2.0");
5786 this.set('value', value);
5787 },
5788
5789 getValue: function(){
5790 // summary:
5791 // Deprecated. Use get('value') instead.
5792 dojo.deprecated(this.declaredClass+"::getValue() is deprecated. Use get('value') instead.", "", "2.0");
5793 return this.get('value');
5794 },
5795
5796 _onMouseDown: function(e){
5797 // If user clicks on the button, even if the mouse is released outside of it,
5798 // this button should get focus (to mimics native browser buttons).
5799 // This is also needed on chrome because otherwise buttons won't get focus at all,
5800 // which leads to bizarre focus restore on Dialog close etc.
5801 if(!e.ctrlKey && this.isFocusable()){ // !e.ctrlKey to ignore right-click on mac
5802 // Set a global event to handle mouseup, so it fires properly
5803 // even if the cursor leaves this.domNode before the mouse up event.
5804 var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){
5805 if (this.isFocusable()) {
5806 this.focus();
5807 }
5808 this.disconnect(mouseUpConnector);
5809 });
5810 }
5811 }
5812 });
5813
5814 dojo.declare("dijit.form._FormValueWidget", dijit.form._FormWidget,
5815 {
5816 // summary:
5817 // Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values.
5818 // description:
5819 // Each _FormValueWidget represents a single input value, and has a (possibly hidden) <input> element,
5820 // to which it serializes it's input value, so that form submission (either normal submission or via FormBind?)
5821 // works as expected.
5822
5823 // Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared
5824 // directly in the template as read by the parser in order to function. IE is known to specifically
5825 // require the 'name' attribute at element creation time. See #8484, #8660.
5826 // TODO: unclear what that {value: ""} is for; FormWidget.attributeMap copies value to focusNode,
5827 // so maybe {value: ""} is so the value *doesn't* get copied to focusNode?
5828 // Seems like we really want value removed from attributeMap altogether
5829 // (although there's no easy way to do that now)
5830
5831 // readOnly: Boolean
5832 // Should this widget respond to user input?
5833 // In markup, this is specified as "readOnly".
5834 // Similar to disabled except readOnly form values are submitted.
5835 readOnly: false,
5836
5837 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
5838 value: "",
5839 readOnly: "focusNode"
5840 }),
5841
5842 _setReadOnlyAttr: function(/*Boolean*/ value){
5843 this.readOnly = value;
5844 dojo.attr(this.focusNode, 'readOnly', value);
5845 dijit.setWaiState(this.focusNode, "readonly", value);
5846 },
5847
5848 postCreate: function(){
5849 this.inherited(arguments);
5850
5851 if(dojo.isIE){ // IE won't stop the event with keypress
5852 this.connect(this.focusNode || this.domNode, "onkeydown", this._onKeyDown);
5853 }
5854 // Update our reset value if it hasn't yet been set (because this.set()
5855 // is only called when there *is* a value)
5856 if(this._resetValue === undefined){
5857 this._resetValue = this.value;
5858 }
5859 },
5860
5861 _setValueAttr: function(/*anything*/ newValue, /*Boolean, optional*/ priorityChange){
5862 // summary:
5863 // Hook so attr('value', value) works.
5864 // description:
5865 // Sets the value of the widget.
5866 // If the value has changed, then fire onChange event, unless priorityChange
5867 // is specified as null (or false?)
5868 this.value = newValue;
5869 this._handleOnChange(newValue, priorityChange);
5870 },
5871
5872 _getValueAttr: function(){
5873 // summary:
5874 // Hook so attr('value') works.
5875 return this._lastValue;
5876 },
5877
5878 undo: function(){
5879 // summary:
5880 // Restore the value to the last value passed to onChange
5881 this._setValueAttr(this._lastValueReported, false);
5882 },
5883
5884 reset: function(){
5885 // summary:
5886 // Reset the widget's value to what it was at initialization time
5887 this._hasBeenBlurred = false;
5888 this._setValueAttr(this._resetValue, true);
5889 },
5890
5891 _onKeyDown: function(e){
5892 if(e.keyCode == dojo.keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){
5893 var te;
5894 if(dojo.isIE){
5895 e.preventDefault(); // default behavior needs to be stopped here since keypress is too late
5896 te = document.createEventObject();
5897 te.keyCode = dojo.keys.ESCAPE;
5898 te.shiftKey = e.shiftKey;
5899 e.srcElement.fireEvent('onkeypress', te);
5900 }
5901 }
5902 },
5903
5904 _layoutHackIE7: function(){
5905 // summary:
5906 // Work around table sizing bugs on IE7 by forcing redraw
5907
5908 if(dojo.isIE == 7){ // fix IE7 layout bug when the widget is scrolled out of sight
5909 var domNode = this.domNode;
5910 var parent = domNode.parentNode;
5911 var pingNode = domNode.firstChild || domNode; // target node most unlikely to have a custom filter
5912 var origFilter = pingNode.style.filter; // save custom filter, most likely nothing
5913 var _this = this;
5914 while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet
5915 (function ping(){
5916 var disconnectHandle = _this.connect(parent, "onscroll",
5917 function(e){
5918 _this.disconnect(disconnectHandle); // only call once
5919 pingNode.style.filter = (new Date()).getMilliseconds(); // set to anything that's unique
5920 setTimeout(function(){ pingNode.style.filter = origFilter }, 0); // restore custom filter, if any
5921 }
5922 );
5923 })();
5924 parent = parent.parentNode;
5925 }
5926 }
5927 }
5928 });
5929
5930 }
5931
5932 if(!dojo._hasResource["dijit.dijit"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5933 dojo._hasResource["dijit.dijit"] = true;
5934 dojo.provide("dijit.dijit");
5935
5936 /*=====
5937 dijit.dijit = {
5938 // summary:
5939 // A roll-up for common dijit methods
5940 // description:
5941 // A rollup file for the build system including the core and common
5942 // dijit files.
5943 //
5944 // example:
5945 // | <script type="text/javascript" src="js/dojo/dijit/dijit.js"></script>
5946 //
5947 };
5948 =====*/
5949
5950 // All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require)
5951
5952
5953 // And some other stuff that we tend to pull in all the time anyway
5954
5955
5956
5957
5958
5959
5960
5961 }
5962
5963 if(!dojo._hasResource["dojo.fx.Toggler"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5964 dojo._hasResource["dojo.fx.Toggler"] = true;
5965 dojo.provide("dojo.fx.Toggler");
5966
5967 dojo.declare("dojo.fx.Toggler", null, {
5968 // summary:
5969 // A simple `dojo.Animation` toggler API.
5970 //
5971 // description:
5972 // class constructor for an animation toggler. It accepts a packed
5973 // set of arguments about what type of animation to use in each
5974 // direction, duration, etc. All available members are mixed into
5975 // these animations from the constructor (for example, `node`,
5976 // `showDuration`, `hideDuration`).
5977 //
5978 // example:
5979 // | var t = new dojo.fx.Toggler({
5980 // | node: "nodeId",
5981 // | showDuration: 500,
5982 // | // hideDuration will default to "200"
5983 // | showFunc: dojo.fx.wipeIn,
5984 // | // hideFunc will default to "fadeOut"
5985 // | });
5986 // | t.show(100); // delay showing for 100ms
5987 // | // ...time passes...
5988 // | t.hide();
5989
5990 // node: DomNode
5991 // the node to target for the showing and hiding animations
5992 node: null,
5993
5994 // showFunc: Function
5995 // The function that returns the `dojo.Animation` to show the node
5996 showFunc: dojo.fadeIn,
5997
5998 // hideFunc: Function
5999 // The function that returns the `dojo.Animation` to hide the node
6000 hideFunc: dojo.fadeOut,
6001
6002 // showDuration:
6003 // Time in milliseconds to run the show Animation
6004 showDuration: 200,
6005
6006 // hideDuration:
6007 // Time in milliseconds to run the hide Animation
6008 hideDuration: 200,
6009
6010 // FIXME: need a policy for where the toggler should "be" the next
6011 // time show/hide are called if we're stopped somewhere in the
6012 // middle.
6013 // FIXME: also would be nice to specify individual showArgs/hideArgs mixed into
6014 // each animation individually.
6015 // FIXME: also would be nice to have events from the animations exposed/bridged
6016
6017 /*=====
6018 _showArgs: null,
6019 _showAnim: null,
6020
6021 _hideArgs: null,
6022 _hideAnim: null,
6023
6024 _isShowing: false,
6025 _isHiding: false,
6026 =====*/
6027
6028 constructor: function(args){
6029 var _t = this;
6030
6031 dojo.mixin(_t, args);
6032 _t.node = args.node;
6033 _t._showArgs = dojo.mixin({}, args);
6034 _t._showArgs.node = _t.node;
6035 _t._showArgs.duration = _t.showDuration;
6036 _t.showAnim = _t.showFunc(_t._showArgs);
6037
6038 _t._hideArgs = dojo.mixin({}, args);
6039 _t._hideArgs.node = _t.node;
6040 _t._hideArgs.duration = _t.hideDuration;
6041 _t.hideAnim = _t.hideFunc(_t._hideArgs);
6042
6043 dojo.connect(_t.showAnim, "beforeBegin", dojo.hitch(_t.hideAnim, "stop", true));
6044 dojo.connect(_t.hideAnim, "beforeBegin", dojo.hitch(_t.showAnim, "stop", true));
6045 },
6046
6047 show: function(delay){
6048 // summary: Toggle the node to showing
6049 // delay: Integer?
6050 // Ammount of time to stall playing the show animation
6051 return this.showAnim.play(delay || 0);
6052 },
6053
6054 hide: function(delay){
6055 // summary: Toggle the node to hidden
6056 // delay: Integer?
6057 // Ammount of time to stall playing the hide animation
6058 return this.hideAnim.play(delay || 0);
6059 }
6060 });
6061
6062 }
6063
6064 if(!dojo._hasResource["dojo.fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6065 dojo._hasResource["dojo.fx"] = true;
6066 dojo.provide("dojo.fx");
6067 // FIXME: remove this back-compat require in 2.0
6068 /*=====
6069 dojo.fx = {
6070 // summary: Effects library on top of Base animations
6071 };
6072 =====*/
6073 (function(){
6074
6075 var d = dojo,
6076 _baseObj = {
6077 _fire: function(evt, args){
6078 if(this[evt]){
6079 this[evt].apply(this, args||[]);
6080 }
6081 return this;
6082 }
6083 };
6084
6085 var _chain = function(animations){
6086 this._index = -1;
6087 this._animations = animations||[];
6088 this._current = this._onAnimateCtx = this._onEndCtx = null;
6089
6090 this.duration = 0;
6091 d.forEach(this._animations, function(a){
6092 this.duration += a.duration;
6093 if(a.delay){ this.duration += a.delay; }
6094 }, this);
6095 };
6096 d.extend(_chain, {
6097 _onAnimate: function(){
6098 this._fire("onAnimate", arguments);
6099 },
6100 _onEnd: function(){
6101 d.disconnect(this._onAnimateCtx);
6102 d.disconnect(this._onEndCtx);
6103 this._onAnimateCtx = this._onEndCtx = null;
6104 if(this._index + 1 == this._animations.length){
6105 this._fire("onEnd");
6106 }else{
6107 // switch animations
6108 this._current = this._animations[++this._index];
6109 this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate");
6110 this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd");
6111 this._current.play(0, true);
6112 }
6113 },
6114 play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
6115 if(!this._current){ this._current = this._animations[this._index = 0]; }
6116 if(!gotoStart && this._current.status() == "playing"){ return this; }
6117 var beforeBegin = d.connect(this._current, "beforeBegin", this, function(){
6118 this._fire("beforeBegin");
6119 }),
6120 onBegin = d.connect(this._current, "onBegin", this, function(arg){
6121 this._fire("onBegin", arguments);
6122 }),
6123 onPlay = d.connect(this._current, "onPlay", this, function(arg){
6124 this._fire("onPlay", arguments);
6125 d.disconnect(beforeBegin);
6126 d.disconnect(onBegin);
6127 d.disconnect(onPlay);
6128 });
6129 if(this._onAnimateCtx){
6130 d.disconnect(this._onAnimateCtx);
6131 }
6132 this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate");
6133 if(this._onEndCtx){
6134 d.disconnect(this._onEndCtx);
6135 }
6136 this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd");
6137 this._current.play.apply(this._current, arguments);
6138 return this;
6139 },
6140 pause: function(){
6141 if(this._current){
6142 var e = d.connect(this._current, "onPause", this, function(arg){
6143 this._fire("onPause", arguments);
6144 d.disconnect(e);
6145 });
6146 this._current.pause();
6147 }
6148 return this;
6149 },
6150 gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){
6151 this.pause();
6152 var offset = this.duration * percent;
6153 this._current = null;
6154 d.some(this._animations, function(a){
6155 if(a.duration <= offset){
6156 this._current = a;
6157 return true;
6158 }
6159 offset -= a.duration;
6160 return false;
6161 });
6162 if(this._current){
6163 this._current.gotoPercent(offset / this._current.duration, andPlay);
6164 }
6165 return this;
6166 },
6167 stop: function(/*boolean?*/ gotoEnd){
6168 if(this._current){
6169 if(gotoEnd){
6170 for(; this._index + 1 < this._animations.length; ++this._index){
6171 this._animations[this._index].stop(true);
6172 }
6173 this._current = this._animations[this._index];
6174 }
6175 var e = d.connect(this._current, "onStop", this, function(arg){
6176 this._fire("onStop", arguments);
6177 d.disconnect(e);
6178 });
6179 this._current.stop();
6180 }
6181 return this;
6182 },
6183 status: function(){
6184 return this._current ? this._current.status() : "stopped";
6185 },
6186 destroy: function(){
6187 if(this._onAnimateCtx){ d.disconnect(this._onAnimateCtx); }
6188 if(this._onEndCtx){ d.disconnect(this._onEndCtx); }
6189 }
6190 });
6191 d.extend(_chain, _baseObj);
6192
6193 dojo.fx.chain = function(/*dojo.Animation[]*/ animations){
6194 // summary:
6195 // Chain a list of `dojo.Animation`s to run in sequence
6196 //
6197 // description:
6198 // Return a `dojo.Animation` which will play all passed
6199 // `dojo.Animation` instances in sequence, firing its own
6200 // synthesized events simulating a single animation. (eg:
6201 // onEnd of this animation means the end of the chain,
6202 // not the individual animations within)
6203 //
6204 // example:
6205 // Once `node` is faded out, fade in `otherNode`
6206 // | dojo.fx.chain([
6207 // | dojo.fadeIn({ node:node }),
6208 // | dojo.fadeOut({ node:otherNode })
6209 // | ]).play();
6210 //
6211 return new _chain(animations) // dojo.Animation
6212 };
6213
6214 var _combine = function(animations){
6215 this._animations = animations||[];
6216 this._connects = [];
6217 this._finished = 0;
6218
6219 this.duration = 0;
6220 d.forEach(animations, function(a){
6221 var duration = a.duration;
6222 if(a.delay){ duration += a.delay; }
6223 if(this.duration < duration){ this.duration = duration; }
6224 this._connects.push(d.connect(a, "onEnd", this, "_onEnd"));
6225 }, this);
6226
6227 this._pseudoAnimation = new d.Animation({curve: [0, 1], duration: this.duration});
6228 var self = this;
6229 d.forEach(["beforeBegin", "onBegin", "onPlay", "onAnimate", "onPause", "onStop", "onEnd"],
6230 function(evt){
6231 self._connects.push(d.connect(self._pseudoAnimation, evt,
6232 function(){ self._fire(evt, arguments); }
6233 ));
6234 }
6235 );
6236 };
6237 d.extend(_combine, {
6238 _doAction: function(action, args){
6239 d.forEach(this._animations, function(a){
6240 a[action].apply(a, args);
6241 });
6242 return this;
6243 },
6244 _onEnd: function(){
6245 if(++this._finished > this._animations.length){
6246 this._fire("onEnd");
6247 }
6248 },
6249 _call: function(action, args){
6250 var t = this._pseudoAnimation;
6251 t[action].apply(t, args);
6252 },
6253 play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
6254 this._finished = 0;
6255 this._doAction("play", arguments);
6256 this._call("play", arguments);
6257 return this;
6258 },
6259 pause: function(){
6260 this._doAction("pause", arguments);
6261 this._call("pause", arguments);
6262 return this;
6263 },
6264 gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){
6265 var ms = this.duration * percent;
6266 d.forEach(this._animations, function(a){
6267 a.gotoPercent(a.duration < ms ? 1 : (ms / a.duration), andPlay);
6268 });
6269 this._call("gotoPercent", arguments);
6270 return this;
6271 },
6272 stop: function(/*boolean?*/ gotoEnd){
6273 this._doAction("stop", arguments);
6274 this._call("stop", arguments);
6275 return this;
6276 },
6277 status: function(){
6278 return this._pseudoAnimation.status();
6279 },
6280 destroy: function(){
6281 d.forEach(this._connects, dojo.disconnect);
6282 }
6283 });
6284 d.extend(_combine, _baseObj);
6285
6286 dojo.fx.combine = function(/*dojo.Animation[]*/ animations){
6287 // summary:
6288 // Combine a list of `dojo.Animation`s to run in parallel
6289 //
6290 // description:
6291 // Combine an array of `dojo.Animation`s to run in parallel,
6292 // providing a new `dojo.Animation` instance encompasing each
6293 // animation, firing standard animation events.
6294 //
6295 // example:
6296 // Fade out `node` while fading in `otherNode` simultaneously
6297 // | dojo.fx.combine([
6298 // | dojo.fadeIn({ node:node }),
6299 // | dojo.fadeOut({ node:otherNode })
6300 // | ]).play();
6301 //
6302 // example:
6303 // When the longest animation ends, execute a function:
6304 // | var anim = dojo.fx.combine([
6305 // | dojo.fadeIn({ node: n, duration:700 }),
6306 // | dojo.fadeOut({ node: otherNode, duration: 300 })
6307 // | ]);
6308 // | dojo.connect(anim, "onEnd", function(){
6309 // | // overall animation is done.
6310 // | });
6311 // | anim.play(); // play the animation
6312 //
6313 return new _combine(animations); // dojo.Animation
6314 };
6315
6316 dojo.fx.wipeIn = function(/*Object*/ args){
6317 // summary:
6318 // Expand a node to it's natural height.
6319 //
6320 // description:
6321 // Returns an animation that will expand the
6322 // node defined in 'args' object from it's current height to
6323 // it's natural height (with no scrollbar).
6324 // Node must have no margin/border/padding.
6325 //
6326 // args: Object
6327 // A hash-map of standard `dojo.Animation` constructor properties
6328 // (such as easing: node: duration: and so on)
6329 //
6330 // example:
6331 // | dojo.fx.wipeIn({
6332 // | node:"someId"
6333 // | }).play()
6334 var node = args.node = d.byId(args.node), s = node.style, o;
6335
6336 var anim = d.animateProperty(d.mixin({
6337 properties: {
6338 height: {
6339 // wrapped in functions so we wait till the last second to query (in case value has changed)
6340 start: function(){
6341 // start at current [computed] height, but use 1px rather than 0
6342 // because 0 causes IE to display the whole panel
6343 o = s.overflow;
6344 s.overflow = "hidden";
6345 if(s.visibility == "hidden" || s.display == "none"){
6346 s.height = "1px";
6347 s.display = "";
6348 s.visibility = "";
6349 return 1;
6350 }else{
6351 var height = d.style(node, "height");
6352 return Math.max(height, 1);
6353 }
6354 },
6355 end: function(){
6356 return node.scrollHeight;
6357 }
6358 }
6359 }
6360 }, args));
6361
6362 d.connect(anim, "onEnd", function(){
6363 s.height = "auto";
6364 s.overflow = o;
6365 });
6366
6367 return anim; // dojo.Animation
6368 }
6369
6370 dojo.fx.wipeOut = function(/*Object*/ args){
6371 // summary:
6372 // Shrink a node to nothing and hide it.
6373 //
6374 // description:
6375 // Returns an animation that will shrink node defined in "args"
6376 // from it's current height to 1px, and then hide it.
6377 //
6378 // args: Object
6379 // A hash-map of standard `dojo.Animation` constructor properties
6380 // (such as easing: node: duration: and so on)
6381 //
6382 // example:
6383 // | dojo.fx.wipeOut({ node:"someId" }).play()
6384
6385 var node = args.node = d.byId(args.node), s = node.style, o;
6386
6387 var anim = d.animateProperty(d.mixin({
6388 properties: {
6389 height: {
6390 end: 1 // 0 causes IE to display the whole panel
6391 }
6392 }
6393 }, args));
6394
6395 d.connect(anim, "beforeBegin", function(){
6396 o = s.overflow;
6397 s.overflow = "hidden";
6398 s.display = "";
6399 });
6400 d.connect(anim, "onEnd", function(){
6401 s.overflow = o;
6402 s.height = "auto";
6403 s.display = "none";
6404 });
6405
6406 return anim; // dojo.Animation
6407 }
6408
6409 dojo.fx.slideTo = function(/*Object*/ args){
6410 // summary:
6411 // Slide a node to a new top/left position
6412 //
6413 // description:
6414 // Returns an animation that will slide "node"
6415 // defined in args Object from its current position to
6416 // the position defined by (args.left, args.top).
6417 //
6418 // args: Object
6419 // A hash-map of standard `dojo.Animation` constructor properties
6420 // (such as easing: node: duration: and so on). Special args members
6421 // are `top` and `left`, which indicate the new position to slide to.
6422 //
6423 // example:
6424 // | dojo.fx.slideTo({ node: node, left:"40", top:"50", units:"px" }).play()
6425
6426 var node = args.node = d.byId(args.node),
6427 top = null, left = null;
6428
6429 var init = (function(n){
6430 return function(){
6431 var cs = d.getComputedStyle(n);
6432 var pos = cs.position;
6433 top = (pos == 'absolute' ? n.offsetTop : parseInt(cs.top) || 0);
6434 left = (pos == 'absolute' ? n.offsetLeft : parseInt(cs.left) || 0);
6435 if(pos != 'absolute' && pos != 'relative'){
6436 var ret = d.position(n, true);
6437 top = ret.y;
6438 left = ret.x;
6439 n.style.position="absolute";
6440 n.style.top=top+"px";
6441 n.style.left=left+"px";
6442 }
6443 };
6444 })(node);
6445 init();
6446
6447 var anim = d.animateProperty(d.mixin({
6448 properties: {
6449 top: args.top || 0,
6450 left: args.left || 0
6451 }
6452 }, args));
6453 d.connect(anim, "beforeBegin", anim, init);
6454
6455 return anim; // dojo.Animation
6456 }
6457
6458 })();
6459
6460 }
6461
6462 if(!dojo._hasResource["dojo.NodeList-fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6463 dojo._hasResource["dojo.NodeList-fx"] = true;
6464 dojo.provide("dojo.NodeList-fx");
6465
6466
6467 /*=====
6468 dojo["NodeList-fx"] = {
6469 // summary: Adds dojo.fx animation support to dojo.query()
6470 };
6471 =====*/
6472
6473 dojo.extend(dojo.NodeList, {
6474 _anim: function(obj, method, args){
6475 args = args||{};
6476 var a = dojo.fx.combine(
6477 this.map(function(item){
6478 var tmpArgs = { node: item };
6479 dojo.mixin(tmpArgs, args);
6480 return obj[method](tmpArgs);
6481 })
6482 );
6483 return args.auto ? a.play() && this : a; // dojo.Animation|dojo.NodeList
6484 },
6485
6486 wipeIn: function(args){
6487 // summary:
6488 // wipe in all elements of this NodeList via `dojo.fx.wipeIn`
6489 //
6490 // args: Object?
6491 // Additional dojo.Animation arguments to mix into this set with the addition of
6492 // an `auto` parameter.
6493 //
6494 // returns: dojo.Animation|dojo.NodeList
6495 // A special args member `auto` can be passed to automatically play the animation.
6496 // If args.auto is present, the original dojo.NodeList will be returned for further
6497 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
6498 //
6499 // example:
6500 // Fade in all tables with class "blah":
6501 // | dojo.query("table.blah").wipeIn().play();
6502 //
6503 // example:
6504 // Utilizing `auto` to get the NodeList back:
6505 // | dojo.query(".titles").wipeIn({ auto:true }).onclick(someFunction);
6506 //
6507 return this._anim(dojo.fx, "wipeIn", args); // dojo.Animation|dojo.NodeList
6508 },
6509
6510 wipeOut: function(args){
6511 // summary:
6512 // wipe out all elements of this NodeList via `dojo.fx.wipeOut`
6513 //
6514 // args: Object?
6515 // Additional dojo.Animation arguments to mix into this set with the addition of
6516 // an `auto` parameter.
6517 //
6518 // returns: dojo.Animation|dojo.NodeList
6519 // A special args member `auto` can be passed to automatically play the animation.
6520 // If args.auto is present, the original dojo.NodeList will be returned for further
6521 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
6522 //
6523 // example:
6524 // Wipe out all tables with class "blah":
6525 // | dojo.query("table.blah").wipeOut().play();
6526 return this._anim(dojo.fx, "wipeOut", args); // dojo.Animation|dojo.NodeList
6527 },
6528
6529 slideTo: function(args){
6530 // summary:
6531 // slide all elements of the node list to the specified place via `dojo.fx.slideTo`
6532 //
6533 // args: Object?
6534 // Additional dojo.Animation arguments to mix into this set with the addition of
6535 // an `auto` parameter.
6536 //
6537 // returns: dojo.Animation|dojo.NodeList
6538 // A special args member `auto` can be passed to automatically play the animation.
6539 // If args.auto is present, the original dojo.NodeList will be returned for further
6540 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
6541 //
6542 // example:
6543 // | Move all tables with class "blah" to 300/300:
6544 // | dojo.query("table.blah").slideTo({
6545 // | left: 40,
6546 // | top: 50
6547 // | }).play();
6548 return this._anim(dojo.fx, "slideTo", args); // dojo.Animation|dojo.NodeList
6549 },
6550
6551
6552 fadeIn: function(args){
6553 // summary:
6554 // fade in all elements of this NodeList via `dojo.fadeIn`
6555 //
6556 // args: Object?
6557 // Additional dojo.Animation arguments to mix into this set with the addition of
6558 // an `auto` parameter.
6559 //
6560 // returns: dojo.Animation|dojo.NodeList
6561 // A special args member `auto` can be passed to automatically play the animation.
6562 // If args.auto is present, the original dojo.NodeList will be returned for further
6563 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
6564 //
6565 // example:
6566 // Fade in all tables with class "blah":
6567 // | dojo.query("table.blah").fadeIn().play();
6568 return this._anim(dojo, "fadeIn", args); // dojo.Animation|dojo.NodeList
6569 },
6570
6571 fadeOut: function(args){
6572 // summary:
6573 // fade out all elements of this NodeList via `dojo.fadeOut`
6574 //
6575 // args: Object?
6576 // Additional dojo.Animation arguments to mix into this set with the addition of
6577 // an `auto` parameter.
6578 //
6579 // returns: dojo.Animation|dojo.NodeList
6580 // A special args member `auto` can be passed to automatically play the animation.
6581 // If args.auto is present, the original dojo.NodeList will be returned for further
6582 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
6583 //
6584 // example:
6585 // Fade out all elements with class "zork":
6586 // | dojo.query(".zork").fadeOut().play();
6587 // example:
6588 // Fade them on a delay and do something at the end:
6589 // | var fo = dojo.query(".zork").fadeOut();
6590 // | dojo.connect(fo, "onEnd", function(){ /*...*/ });
6591 // | fo.play();
6592 // example:
6593 // Using `auto`:
6594 // | dojo.query("li").fadeOut({ auto:true }).filter(filterFn).forEach(doit);
6595 //
6596 return this._anim(dojo, "fadeOut", args); // dojo.Animation|dojo.NodeList
6597 },
6598
6599 animateProperty: function(args){
6600 // summary:
6601 // Animate all elements of this NodeList across the properties specified.
6602 // syntax identical to `dojo.animateProperty`
6603 //
6604 // returns: dojo.Animation|dojo.NodeList
6605 // A special args member `auto` can be passed to automatically play the animation.
6606 // If args.auto is present, the original dojo.NodeList will be returned for further
6607 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
6608 //
6609 // example:
6610 // | dojo.query(".zork").animateProperty({
6611 // | duration: 500,
6612 // | properties: {
6613 // | color: { start: "black", end: "white" },
6614 // | left: { end: 300 }
6615 // | }
6616 // | }).play();
6617 //
6618 // example:
6619 // | dojo.query(".grue").animateProperty({
6620 // | auto:true,
6621 // | properties: {
6622 // | height:240
6623 // | }
6624 // | }).onclick(handler);
6625 return this._anim(dojo, "animateProperty", args); // dojo.Animation|dojo.NodeList
6626 },
6627
6628 anim: function( /*Object*/ properties,
6629 /*Integer?*/ duration,
6630 /*Function?*/ easing,
6631 /*Function?*/ onEnd,
6632 /*Integer?*/ delay){
6633 // summary:
6634 // Animate one or more CSS properties for all nodes in this list.
6635 // The returned animation object will already be playing when it
6636 // is returned. See the docs for `dojo.anim` for full details.
6637 // properties: Object
6638 // the properties to animate. does NOT support the `auto` parameter like other
6639 // NodeList-fx methods.
6640 // duration: Integer?
6641 // Optional. The time to run the animations for
6642 // easing: Function?
6643 // Optional. The easing function to use.
6644 // onEnd: Function?
6645 // A function to be called when the animation ends
6646 // delay:
6647 // how long to delay playing the returned animation
6648 // example:
6649 // Another way to fade out:
6650 // | dojo.query(".thinger").anim({ opacity: 0 });
6651 // example:
6652 // animate all elements with the "thigner" class to a width of 500
6653 // pixels over half a second
6654 // | dojo.query(".thinger").anim({ width: 500 }, 700);
6655 var canim = dojo.fx.combine(
6656 this.map(function(item){
6657 return dojo.animateProperty({
6658 node: item,
6659 properties: properties,
6660 duration: duration||350,
6661 easing: easing
6662 });
6663 })
6664 );
6665 if(onEnd){
6666 dojo.connect(canim, "onEnd", onEnd);
6667 }
6668 return canim.play(delay||0); // dojo.Animation
6669 }
6670 });
6671
6672 }
6673
6674 if(!dojo._hasResource["dojo.colors"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6675 dojo._hasResource["dojo.colors"] = true;
6676 dojo.provide("dojo.colors");
6677
6678 //TODO: this module appears to break naming conventions
6679
6680 /*=====
6681 dojo.colors = {
6682 // summary: Color utilities
6683 }
6684 =====*/
6685
6686 (function(){
6687 // this is a standard conversion prescribed by the CSS3 Color Module
6688 var hue2rgb = function(m1, m2, h){
6689 if(h < 0){ ++h; }
6690 if(h > 1){ --h; }
6691 var h6 = 6 * h;
6692 if(h6 < 1){ return m1 + (m2 - m1) * h6; }
6693 if(2 * h < 1){ return m2; }
6694 if(3 * h < 2){ return m1 + (m2 - m1) * (2 / 3 - h) * 6; }
6695 return m1;
6696 };
6697
6698 dojo.colorFromRgb = function(/*String*/ color, /*dojo.Color?*/ obj){
6699 // summary:
6700 // get rgb(a) array from css-style color declarations
6701 // description:
6702 // this function can handle all 4 CSS3 Color Module formats: rgb,
6703 // rgba, hsl, hsla, including rgb(a) with percentage values.
6704 var m = color.toLowerCase().match(/^(rgba?|hsla?)\(([\s\.\-,%0-9]+)\)/);
6705 if(m){
6706 var c = m[2].split(/\s*,\s*/), l = c.length, t = m[1], a;
6707 if((t == "rgb" && l == 3) || (t == "rgba" && l == 4)){
6708 var r = c[0];
6709 if(r.charAt(r.length - 1) == "%"){
6710 // 3 rgb percentage values
6711 a = dojo.map(c, function(x){
6712 return parseFloat(x) * 2.56;
6713 });
6714 if(l == 4){ a[3] = c[3]; }
6715 return dojo.colorFromArray(a, obj); // dojo.Color
6716 }
6717 return dojo.colorFromArray(c, obj); // dojo.Color
6718 }
6719 if((t == "hsl" && l == 3) || (t == "hsla" && l == 4)){
6720 // normalize hsl values
6721 var H = ((parseFloat(c[0]) % 360) + 360) % 360 / 360,
6722 S = parseFloat(c[1]) / 100,
6723 L = parseFloat(c[2]) / 100,
6724 // calculate rgb according to the algorithm
6725 // recommended by the CSS3 Color Module
6726 m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S,
6727 m1 = 2 * L - m2;
6728 a = [
6729 hue2rgb(m1, m2, H + 1 / 3) * 256,
6730 hue2rgb(m1, m2, H) * 256,
6731 hue2rgb(m1, m2, H - 1 / 3) * 256,
6732 1
6733 ];
6734 if(l == 4){ a[3] = c[3]; }
6735 return dojo.colorFromArray(a, obj); // dojo.Color
6736 }
6737 }
6738 return null; // dojo.Color
6739 };
6740
6741 var confine = function(c, low, high){
6742 // summary:
6743 // sanitize a color component by making sure it is a number,
6744 // and clamping it to valid values
6745 c = Number(c);
6746 return isNaN(c) ? high : c < low ? low : c > high ? high : c; // Number
6747 };
6748
6749 dojo.Color.prototype.sanitize = function(){
6750 // summary: makes sure that the object has correct attributes
6751 var t = this;
6752 t.r = Math.round(confine(t.r, 0, 255));
6753 t.g = Math.round(confine(t.g, 0, 255));
6754 t.b = Math.round(confine(t.b, 0, 255));
6755 t.a = confine(t.a, 0, 1);
6756 return this; // dojo.Color
6757 };
6758 })();
6759
6760
6761 dojo.colors.makeGrey = function(/*Number*/ g, /*Number?*/ a){
6762 // summary: creates a greyscale color with an optional alpha
6763 return dojo.colorFromArray([g, g, g, a]);
6764 };
6765
6766 // mixin all CSS3 named colors not already in _base, along with SVG 1.0 variant spellings
6767 dojo.mixin(dojo.Color.named, {
6768 aliceblue: [240,248,255],
6769 antiquewhite: [250,235,215],
6770 aquamarine: [127,255,212],
6771 azure: [240,255,255],
6772 beige: [245,245,220],
6773 bisque: [255,228,196],
6774 blanchedalmond: [255,235,205],
6775 blueviolet: [138,43,226],
6776 brown: [165,42,42],
6777 burlywood: [222,184,135],
6778 cadetblue: [95,158,160],
6779 chartreuse: [127,255,0],
6780 chocolate: [210,105,30],
6781 coral: [255,127,80],
6782 cornflowerblue: [100,149,237],
6783 cornsilk: [255,248,220],
6784 crimson: [220,20,60],
6785 cyan: [0,255,255],
6786 darkblue: [0,0,139],
6787 darkcyan: [0,139,139],
6788 darkgoldenrod: [184,134,11],
6789 darkgray: [169,169,169],
6790 darkgreen: [0,100,0],
6791 darkgrey: [169,169,169],
6792 darkkhaki: [189,183,107],
6793 darkmagenta: [139,0,139],
6794 darkolivegreen: [85,107,47],
6795 darkorange: [255,140,0],
6796 darkorchid: [153,50,204],
6797 darkred: [139,0,0],
6798 darksalmon: [233,150,122],
6799 darkseagreen: [143,188,143],
6800 darkslateblue: [72,61,139],
6801 darkslategray: [47,79,79],
6802 darkslategrey: [47,79,79],
6803 darkturquoise: [0,206,209],
6804 darkviolet: [148,0,211],
6805 deeppink: [255,20,147],
6806 deepskyblue: [0,191,255],
6807 dimgray: [105,105,105],
6808 dimgrey: [105,105,105],
6809 dodgerblue: [30,144,255],
6810 firebrick: [178,34,34],
6811 floralwhite: [255,250,240],
6812 forestgreen: [34,139,34],
6813 gainsboro: [220,220,220],
6814 ghostwhite: [248,248,255],
6815 gold: [255,215,0],
6816 goldenrod: [218,165,32],
6817 greenyellow: [173,255,47],
6818 grey: [128,128,128],
6819 honeydew: [240,255,240],
6820 hotpink: [255,105,180],
6821 indianred: [205,92,92],
6822 indigo: [75,0,130],
6823 ivory: [255,255,240],
6824 khaki: [240,230,140],
6825 lavender: [230,230,250],
6826 lavenderblush: [255,240,245],
6827 lawngreen: [124,252,0],
6828 lemonchiffon: [255,250,205],
6829 lightblue: [173,216,230],
6830 lightcoral: [240,128,128],
6831 lightcyan: [224,255,255],
6832 lightgoldenrodyellow: [250,250,210],
6833 lightgray: [211,211,211],
6834 lightgreen: [144,238,144],
6835 lightgrey: [211,211,211],
6836 lightpink: [255,182,193],
6837 lightsalmon: [255,160,122],
6838 lightseagreen: [32,178,170],
6839 lightskyblue: [135,206,250],
6840 lightslategray: [119,136,153],
6841 lightslategrey: [119,136,153],
6842 lightsteelblue: [176,196,222],
6843 lightyellow: [255,255,224],
6844 limegreen: [50,205,50],
6845 linen: [250,240,230],
6846 magenta: [255,0,255],
6847 mediumaquamarine: [102,205,170],
6848 mediumblue: [0,0,205],
6849 mediumorchid: [186,85,211],
6850 mediumpurple: [147,112,219],
6851 mediumseagreen: [60,179,113],
6852 mediumslateblue: [123,104,238],
6853 mediumspringgreen: [0,250,154],
6854 mediumturquoise: [72,209,204],
6855 mediumvioletred: [199,21,133],
6856 midnightblue: [25,25,112],
6857 mintcream: [245,255,250],
6858 mistyrose: [255,228,225],
6859 moccasin: [255,228,181],
6860 navajowhite: [255,222,173],
6861 oldlace: [253,245,230],
6862 olivedrab: [107,142,35],
6863 orange: [255,165,0],
6864 orangered: [255,69,0],
6865 orchid: [218,112,214],
6866 palegoldenrod: [238,232,170],
6867 palegreen: [152,251,152],
6868 paleturquoise: [175,238,238],
6869 palevioletred: [219,112,147],
6870 papayawhip: [255,239,213],
6871 peachpuff: [255,218,185],
6872 peru: [205,133,63],
6873 pink: [255,192,203],
6874 plum: [221,160,221],
6875 powderblue: [176,224,230],
6876 rosybrown: [188,143,143],
6877 royalblue: [65,105,225],
6878 saddlebrown: [139,69,19],
6879 salmon: [250,128,114],
6880 sandybrown: [244,164,96],
6881 seagreen: [46,139,87],
6882 seashell: [255,245,238],
6883 sienna: [160,82,45],
6884 skyblue: [135,206,235],
6885 slateblue: [106,90,205],
6886 slategray: [112,128,144],
6887 slategrey: [112,128,144],
6888 snow: [255,250,250],
6889 springgreen: [0,255,127],
6890 steelblue: [70,130,180],
6891 tan: [210,180,140],
6892 thistle: [216,191,216],
6893 tomato: [255,99,71],
6894 transparent: [0, 0, 0, 0],
6895 turquoise: [64,224,208],
6896 violet: [238,130,238],
6897 wheat: [245,222,179],
6898 whitesmoke: [245,245,245],
6899 yellowgreen: [154,205,50]
6900 });
6901
6902 }
6903
6904 if(!dojo._hasResource["dojo.i18n"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6905 dojo._hasResource["dojo.i18n"] = true;
6906 dojo.provide("dojo.i18n");
6907
6908 /*=====
6909 dojo.i18n = {
6910 // summary: Utility classes to enable loading of resources for internationalization (i18n)
6911 };
6912 =====*/
6913
6914 dojo.i18n.getLocalization = function(/*String*/packageName, /*String*/bundleName, /*String?*/locale){
6915 // summary:
6916 // Returns an Object containing the localization for a given resource
6917 // bundle in a package, matching the specified locale.
6918 // description:
6919 // Returns a hash containing name/value pairs in its prototypesuch
6920 // that values can be easily overridden. Throws an exception if the
6921 // bundle is not found. Bundle must have already been loaded by
6922 // `dojo.requireLocalization()` or by a build optimization step. NOTE:
6923 // try not to call this method as part of an object property
6924 // definition (`var foo = { bar: dojo.i18n.getLocalization() }`). In
6925 // some loading situations, the bundle may not be available in time
6926 // for the object definition. Instead, call this method inside a
6927 // function that is run after all modules load or the page loads (like
6928 // in `dojo.addOnLoad()`), or in a widget lifecycle method.
6929 // packageName:
6930 // package which is associated with this resource
6931 // bundleName:
6932 // the base filename of the resource bundle (without the ".js" suffix)
6933 // locale:
6934 // the variant to load (optional). By default, the locale defined by
6935 // the host environment: dojo.locale
6936
6937 locale = dojo.i18n.normalizeLocale(locale);
6938
6939 // look for nearest locale match
6940 var elements = locale.split('-');
6941 var module = [packageName,"nls",bundleName].join('.');
6942 var bundle = dojo._loadedModules[module];
6943 if(bundle){
6944 var localization;
6945 for(var i = elements.length; i > 0; i--){
6946 var loc = elements.slice(0, i).join('_');
6947 if(bundle[loc]){
6948 localization = bundle[loc];
6949 break;
6950 }
6951 }
6952 if(!localization){
6953 localization = bundle.ROOT;
6954 }
6955
6956 // make a singleton prototype so that the caller won't accidentally change the values globally
6957 if(localization){
6958 var clazz = function(){};
6959 clazz.prototype = localization;
6960 return new clazz(); // Object
6961 }
6962 }
6963
6964 throw new Error("Bundle not found: " + bundleName + " in " + packageName+" , locale=" + locale);
6965 };
6966
6967 dojo.i18n.normalizeLocale = function(/*String?*/locale){
6968 // summary:
6969 // Returns canonical form of locale, as used by Dojo.
6970 //
6971 // description:
6972 // All variants are case-insensitive and are separated by '-' as specified in [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt).
6973 // If no locale is specified, the dojo.locale is returned. dojo.locale is defined by
6974 // the user agent's locale unless overridden by djConfig.
6975
6976 var result = locale ? locale.toLowerCase() : dojo.locale;
6977 if(result == "root"){
6978 result = "ROOT";
6979 }
6980 return result; // String
6981 };
6982
6983 dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundleName, /*String?*/locale, /*String?*/availableFlatLocales){
6984 // summary:
6985 // See dojo.requireLocalization()
6986 // description:
6987 // Called by the bootstrap, but factored out so that it is only
6988 // included in the build when needed.
6989
6990 var targetLocale = dojo.i18n.normalizeLocale(locale);
6991 var bundlePackage = [moduleName, "nls", bundleName].join(".");
6992 // NOTE:
6993 // When loading these resources, the packaging does not match what is
6994 // on disk. This is an implementation detail, as this is just a
6995 // private data structure to hold the loaded resources. e.g.
6996 // `tests/hello/nls/en-us/salutations.js` is loaded as the object
6997 // `tests.hello.nls.salutations.en_us={...}` The structure on disk is
6998 // intended to be most convenient for developers and translators, but
6999 // in memory it is more logical and efficient to store in a different
7000 // order. Locales cannot use dashes, since the resulting path will
7001 // not evaluate as valid JS, so we translate them to underscores.
7002
7003 //Find the best-match locale to load if we have available flat locales.
7004 var bestLocale = "";
7005 if(availableFlatLocales){
7006 var flatLocales = availableFlatLocales.split(",");
7007 for(var i = 0; i < flatLocales.length; i++){
7008 //Locale must match from start of string.
7009 //Using ["indexOf"] so customBase builds do not see
7010 //this as a dojo._base.array dependency.
7011 if(targetLocale["indexOf"](flatLocales[i]) == 0){
7012 if(flatLocales[i].length > bestLocale.length){
7013 bestLocale = flatLocales[i];
7014 }
7015 }
7016 }
7017 if(!bestLocale){
7018 bestLocale = "ROOT";
7019 }
7020 }
7021
7022 //See if the desired locale is already loaded.
7023 var tempLocale = availableFlatLocales ? bestLocale : targetLocale;
7024 var bundle = dojo._loadedModules[bundlePackage];
7025 var localizedBundle = null;
7026 if(bundle){
7027 if(dojo.config.localizationComplete && bundle._built){return;}
7028 var jsLoc = tempLocale.replace(/-/g, '_');
7029 var translationPackage = bundlePackage+"."+jsLoc;
7030 localizedBundle = dojo._loadedModules[translationPackage];
7031 }
7032
7033 if(!localizedBundle){
7034 bundle = dojo["provide"](bundlePackage);
7035 var syms = dojo._getModuleSymbols(moduleName);
7036 var modpath = syms.concat("nls").join("/");
7037 var parent;
7038
7039 dojo.i18n._searchLocalePath(tempLocale, availableFlatLocales, function(loc){
7040 var jsLoc = loc.replace(/-/g, '_');
7041 var translationPackage = bundlePackage + "." + jsLoc;
7042 var loaded = false;
7043 if(!dojo._loadedModules[translationPackage]){
7044 // Mark loaded whether it's found or not, so that further load attempts will not be made
7045 dojo["provide"](translationPackage);
7046 var module = [modpath];
7047 if(loc != "ROOT"){module.push(loc);}
7048 module.push(bundleName);
7049 var filespec = module.join("/") + '.js';
7050 loaded = dojo._loadPath(filespec, null, function(hash){
7051 // Use singleton with prototype to point to parent bundle, then mix-in result from loadPath
7052 var clazz = function(){};
7053 clazz.prototype = parent;
7054 bundle[jsLoc] = new clazz();
7055 for(var j in hash){ bundle[jsLoc][j] = hash[j]; }
7056 });
7057 }else{
7058 loaded = true;
7059 }
7060 if(loaded && bundle[jsLoc]){
7061 parent = bundle[jsLoc];
7062 }else{
7063 bundle[jsLoc] = parent;
7064 }
7065
7066 if(availableFlatLocales){
7067 //Stop the locale path searching if we know the availableFlatLocales, since
7068 //the first call to this function will load the only bundle that is needed.
7069 return true;
7070 }
7071 });
7072 }
7073
7074 //Save the best locale bundle as the target locale bundle when we know the
7075 //the available bundles.
7076 if(availableFlatLocales && targetLocale != bestLocale){
7077 bundle[targetLocale.replace(/-/g, '_')] = bundle[bestLocale.replace(/-/g, '_')];
7078 }
7079 };
7080
7081 (function(){
7082 // If other locales are used, dojo.requireLocalization should load them as
7083 // well, by default.
7084 //
7085 // Override dojo.requireLocalization to do load the default bundle, then
7086 // iterate through the extraLocale list and load those translations as
7087 // well, unless a particular locale was requested.
7088
7089 var extra = dojo.config.extraLocale;
7090 if(extra){
7091 if(!extra instanceof Array){
7092 extra = [extra];
7093 }
7094
7095 var req = dojo.i18n._requireLocalization;
7096 dojo.i18n._requireLocalization = function(m, b, locale, availableFlatLocales){
7097 req(m,b,locale, availableFlatLocales);
7098 if(locale){return;}
7099 for(var i=0; i<extra.length; i++){
7100 req(m,b,extra[i], availableFlatLocales);
7101 }
7102 };
7103 }
7104 })();
7105
7106 dojo.i18n._searchLocalePath = function(/*String*/locale, /*Boolean*/down, /*Function*/searchFunc){
7107 // summary:
7108 // A helper method to assist in searching for locale-based resources.
7109 // Will iterate through the variants of a particular locale, either up
7110 // or down, executing a callback function. For example, "en-us" and
7111 // true will try "en-us" followed by "en" and finally "ROOT".
7112
7113 locale = dojo.i18n.normalizeLocale(locale);
7114
7115 var elements = locale.split('-');
7116 var searchlist = [];
7117 for(var i = elements.length; i > 0; i--){
7118 searchlist.push(elements.slice(0, i).join('-'));
7119 }
7120 searchlist.push(false);
7121 if(down){searchlist.reverse();}
7122
7123 for(var j = searchlist.length - 1; j >= 0; j--){
7124 var loc = searchlist[j] || "ROOT";
7125 var stop = searchFunc(loc);
7126 if(stop){ break; }
7127 }
7128 };
7129
7130 dojo.i18n._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated){
7131 // summary:
7132 // Load built, flattened resource bundles, if available for all
7133 // locales used in the page. Only called by built layer files.
7134
7135 function preload(locale){
7136 locale = dojo.i18n.normalizeLocale(locale);
7137 dojo.i18n._searchLocalePath(locale, true, function(loc){
7138 for(var i=0; i<localesGenerated.length;i++){
7139 if(localesGenerated[i] == loc){
7140 dojo["require"](bundlePrefix+"_"+loc);
7141 return true; // Boolean
7142 }
7143 }
7144 return false; // Boolean
7145 });
7146 }
7147 preload();
7148 var extra = dojo.config.extraLocale||[];
7149 for(var i=0; i<extra.length; i++){
7150 preload(extra[i]);
7151 }
7152 };
7153
7154 }
7155
7156 if(!dojo._hasResource["dijit._PaletteMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7157 dojo._hasResource["dijit._PaletteMixin"] = true;
7158 dojo.provide("dijit._PaletteMixin");
7159
7160
7161 dojo.declare("dijit._PaletteMixin",
7162 [dijit._CssStateMixin],
7163 {
7164 // summary:
7165 // A keyboard accessible palette, for picking a color/emoticon/etc.
7166 // description:
7167 // A mixin for a grid showing various entities, so the user can pick a certain entity.
7168
7169 // defaultTimeout: Number
7170 // Number of milliseconds before a held key or button becomes typematic
7171 defaultTimeout: 500,
7172
7173 // timeoutChangeRate: Number
7174 // Fraction of time used to change the typematic timer between events
7175 // 1.0 means that each typematic event fires at defaultTimeout intervals
7176 // < 1.0 means that each typematic event fires at an increasing faster rate
7177 timeoutChangeRate: 0.90,
7178
7179 // value: String
7180 // Currently selected color/emoticon/etc.
7181 value: null,
7182
7183 // _selectedCell: [private] Integer
7184 // Index of the currently selected cell. Initially, none selected
7185 _selectedCell: -1,
7186
7187 // _currentFocus: [private] DomNode
7188 // The currently focused cell (if the palette itself has focus), or otherwise
7189 // the cell to be focused when the palette itself gets focus.
7190 // Different from value, which represents the selected (i.e. clicked) cell.
7191 /*=====
7192 _currentFocus: null,
7193 =====*/
7194
7195 // _xDim: [protected] Integer
7196 // This is the number of cells horizontally across.
7197 /*=====
7198 _xDim: null,
7199 =====*/
7200
7201 // _yDim: [protected] Integer
7202 // This is the number of cells vertically down.
7203 /*=====
7204 _yDim: null,
7205 =====*/
7206
7207 // tabIndex: String
7208 // Widget tab index.
7209 tabIndex: "0",
7210
7211 // cellClass: [protected] String
7212 // CSS class applied to each cell in the palette
7213 cellClass: "dijitPaletteCell",
7214
7215 // dyeClass: [protected] String
7216 // Name of javascript class for Object created for each cell of the palette.
7217 // dyeClass should implements dijit.Dye interface
7218 dyeClass: '',
7219
7220 _preparePalette: function(choices, titles) {
7221 // summary:
7222 // Subclass must call _preparePalette() from postCreate(), passing in the tooltip
7223 // for each cell
7224 // choices: String[][]
7225 // id's for each cell of the palette, used to create Dye JS object for each cell
7226 // titles: String[]
7227 // Localized tooltip for each cell
7228
7229 this._cells = [];
7230 var url = this._blankGif;
7231
7232 var dyeClassObj = dojo.getObject(this.dyeClass);
7233
7234 for(var row=0; row < choices.length; row++){
7235 var rowNode = dojo.create("tr", {tabIndex: "-1"}, this.gridNode);
7236 for(var col=0; col < choices[row].length; col++){
7237 var value = choices[row][col];
7238 if(value){
7239 var cellObject = new dyeClassObj(value);
7240
7241 var cellNode = dojo.create("td", {
7242 "class": this.cellClass,
7243 tabIndex: "-1",
7244 title: titles[value]
7245 });
7246
7247 // prepare cell inner structure
7248 cellObject.fillCell(cellNode, url);
7249
7250 this.connect(cellNode, "ondijitclick", "_onCellClick");
7251 this._trackMouseState(cellNode, this.cellClass);
7252
7253 dojo.place(cellNode, rowNode);
7254
7255 cellNode.index = this._cells.length;
7256
7257 // save cell info into _cells
7258 this._cells.push({node:cellNode, dye:cellObject});
7259 }
7260 }
7261 }
7262 this._xDim = choices[0].length;
7263 this._yDim = choices.length;
7264
7265 // Now set all events
7266 // The palette itself is navigated to with the tab key on the keyboard
7267 // Keyboard navigation within the Palette is with the arrow keys
7268 // Spacebar selects the cell.
7269 // For the up key the index is changed by negative the x dimension.
7270
7271 var keyIncrementMap = {
7272 UP_ARROW: -this._xDim,
7273 // The down key the index is increase by the x dimension.
7274 DOWN_ARROW: this._xDim,
7275 // Right and left move the index by 1.
7276 RIGHT_ARROW: this.isLeftToRight() ? 1 : -1,
7277 LEFT_ARROW: this.isLeftToRight() ? -1 : 1
7278 };
7279 for(var key in keyIncrementMap){
7280 this._connects.push(
7281 dijit.typematic.addKeyListener(
7282 this.domNode,
7283 {charOrCode:dojo.keys[key], ctrlKey:false, altKey:false, shiftKey:false},
7284 this,
7285 function(){
7286 var increment = keyIncrementMap[key];
7287 return function(count){ this._navigateByKey(increment, count); };
7288 }(),
7289 this.timeoutChangeRate,
7290 this.defaultTimeout
7291 )
7292 );
7293 }
7294 },
7295
7296 postCreate: function(){
7297 this.inherited(arguments);
7298
7299 // Set initial navigable node.
7300 this._setCurrent(this._cells[0].node);
7301 },
7302
7303 focus: function(){
7304 // summary:
7305 // Focus this widget. Puts focus on the most recently focused cell.
7306
7307 // The cell already has tabIndex set, just need to set CSS and focus it
7308 dijit.focus(this._currentFocus);
7309 },
7310
7311 _onCellClick: function(/*Event*/ evt){
7312 // summary:
7313 // Handler for click, enter key & space key. Selects the cell.
7314 // evt:
7315 // The event.
7316 // tags:
7317 // private
7318
7319 var target = evt.currentTarget,
7320 value = this._getDye(target).getValue();
7321
7322 // First focus the clicked cell, and then send onChange() notification.
7323 // onChange() (via _setValueAttr) must be after the focus call, because
7324 // it may trigger a refocus to somewhere else (like the Editor content area), and that
7325 // second focus should win.
7326 // Use setTimeout because IE doesn't like changing focus inside of an event handler.
7327 this._setCurrent(target);
7328 setTimeout(dojo.hitch(this, function(){
7329 dijit.focus(target);
7330 this._setValueAttr(value, true);
7331 }));
7332
7333 // workaround bug where hover class is not removed on popup because the popup is
7334 // closed and then there's no onblur event on the cell
7335 dojo.removeClass(target, "dijitPaletteCellHover");
7336
7337 dojo.stopEvent(evt);
7338 },
7339
7340 _setCurrent: function(/*DomNode*/ node){
7341 // summary:
7342 // Sets which node is the focused cell.
7343 // description:
7344 // At any point in time there's exactly one
7345 // cell with tabIndex != -1. If focus is inside the palette then
7346 // focus is on that cell.
7347 //
7348 // After calling this method, arrow key handlers and mouse click handlers
7349 // should focus the cell in a setTimeout().
7350 // tags:
7351 // protected
7352 if("_currentFocus" in this){
7353 // Remove tabIndex on old cell
7354 dojo.attr(this._currentFocus, "tabIndex", "-1");
7355 }
7356
7357 // Set tabIndex of new cell
7358 this._currentFocus = node;
7359 if(node){
7360 dojo.attr(node, "tabIndex", this.tabIndex);
7361 }
7362 },
7363
7364 _setValueAttr: function(value, priorityChange){
7365 // summary:
7366 // This selects a cell. It triggers the onChange event.
7367 // value: String value of the cell to select
7368 // tags:
7369 // protected
7370 // priorityChange:
7371 // Optional parameter used to tell the select whether or not to fire
7372 // onChange event.
7373
7374 // clear old value and selected cell
7375 this.value = null;
7376 if(this._selectedCell >= 0){
7377 dojo.removeClass(this._cells[this._selectedCell].node, "dijitPaletteCellSelected");
7378 }
7379 this._selectedCell = -1;
7380
7381 // search for cell matching specified value
7382 if(value){
7383 for(var i = 0; i < this._cells.length; i++){
7384 if(value == this._cells[i].dye.getValue()){
7385 this._selectedCell = i;
7386 this.value = value;
7387
7388 dojo.addClass(this._cells[i].node, "dijitPaletteCellSelected");
7389
7390 if(priorityChange || priorityChange === undefined){
7391 this.onChange(value);
7392 }
7393
7394 break;
7395 }
7396 }
7397 }
7398 },
7399
7400 onChange: function(value){
7401 // summary:
7402 // Callback when a cell is selected.
7403 // value: String
7404 // Value corresponding to cell.
7405 },
7406
7407 _navigateByKey: function(increment, typeCount){
7408 // summary:
7409 // This is the callback for typematic.
7410 // It changes the focus and the highlighed cell.
7411 // increment:
7412 // How much the key is navigated.
7413 // typeCount:
7414 // How many times typematic has fired.
7415 // tags:
7416 // private
7417
7418 // typecount == -1 means the key is released.
7419 if(typeCount == -1){ return; }
7420
7421 var newFocusIndex = this._currentFocus.index + increment;
7422 if(newFocusIndex < this._cells.length && newFocusIndex > -1){
7423 var focusNode = this._cells[newFocusIndex].node;
7424 this._setCurrent(focusNode);
7425
7426 // Actually focus the node, for the benefit of screen readers.
7427 // Use setTimeout because IE doesn't like changing focus inside of an event handler
7428 setTimeout(dojo.hitch(dijit, "focus", focusNode), 0);
7429 }
7430 },
7431
7432 _getDye: function(/*DomNode*/ cell){
7433 // summary:
7434 // Get JS object for given cell DOMNode
7435
7436 return this._cells[cell.index].dye;
7437 }
7438 });
7439
7440 /*=====
7441 dojo.declare("dijit.Dye",
7442 null,
7443 {
7444 // summary:
7445 // Interface for the JS Object associated with a palette cell (i.e. DOMNode)
7446
7447 constructor: function(alias){
7448 // summary:
7449 // Initialize according to value or alias like "white"
7450 // alias: String
7451 },
7452
7453 getValue: function(){
7454 // summary:
7455 // Return "value" of cell; meaning of "value" varies by subclass.
7456 // description:
7457 // For example color hex value, emoticon ascii value etc, entity hex value.
7458 },
7459
7460 fillCell: function(cell, blankGif){
7461 // summary:
7462 // Add cell DOMNode inner structure
7463 // cell: DomNode
7464 // The surrounding cell
7465 // blankGif: String
7466 // URL for blank cell image
7467 }
7468 }
7469 );
7470 =====*/
7471
7472 }
7473
7474 if(!dojo._hasResource["dijit.ColorPalette"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7475 dojo._hasResource["dijit.ColorPalette"] = true;
7476 dojo.provide("dijit.ColorPalette");
7477
7478
7479
7480
7481
7482
7483
7484
7485
7486
7487 dojo.declare("dijit.ColorPalette",
7488 [dijit._Widget, dijit._Templated, dijit._PaletteMixin],
7489 {
7490 // summary:
7491 // A keyboard accessible color-picking widget
7492 // description:
7493 // Grid showing various colors, so the user can pick a certain color.
7494 // Can be used standalone, or as a popup.
7495 //
7496 // example:
7497 // | <div dojoType="dijit.ColorPalette"></div>
7498 //
7499 // example:
7500 // | var picker = new dijit.ColorPalette({ },srcNode);
7501 // | picker.startup();
7502
7503
7504 // palette: String
7505 // Size of grid, either "7x10" or "3x4".
7506 palette: "7x10",
7507
7508 // _palettes: [protected] Map
7509 // This represents the value of the colors.
7510 // The first level is a hashmap of the different palettes available.
7511 // The next two dimensions represent the columns and rows of colors.
7512 _palettes: {
7513 "7x10": [["white", "seashell", "cornsilk", "lemonchiffon","lightyellow", "palegreen", "paleturquoise", "lightcyan", "lavender", "plum"],
7514 ["lightgray", "pink", "bisque", "moccasin", "khaki", "lightgreen", "lightseagreen", "lightskyblue", "cornflowerblue", "violet"],
7515 ["silver", "lightcoral", "sandybrown", "orange", "palegoldenrod", "chartreuse", "mediumturquoise", "skyblue", "mediumslateblue","orchid"],
7516 ["gray", "red", "orangered", "darkorange", "yellow", "limegreen", "darkseagreen", "royalblue", "slateblue", "mediumorchid"],
7517 ["dimgray", "crimson", "chocolate", "coral", "gold", "forestgreen", "seagreen", "blue", "blueviolet", "darkorchid"],
7518 ["darkslategray","firebrick","saddlebrown", "sienna", "olive", "green", "darkcyan", "mediumblue","darkslateblue", "darkmagenta" ],
7519 ["black", "darkred", "maroon", "brown", "darkolivegreen", "darkgreen", "midnightblue", "navy", "indigo", "purple"]],
7520
7521 "3x4": [["white", "lime", "green", "blue"],
7522 ["silver", "yellow", "fuchsia", "navy"],
7523 ["gray", "red", "purple", "black"]]
7524 },
7525
7526 // _imagePaths: [protected] Map
7527 // This is stores the path to the palette images
7528 _imagePaths: {
7529 "7x10": dojo.moduleUrl("dijit.themes", "a11y/colors7x10.png"),
7530 "3x4": dojo.moduleUrl("dijit.themes", "a11y/colors3x4.png"),
7531 "7x10-rtl": dojo.moduleUrl("dijit.themes", "a11y/colors7x10-rtl.png"),
7532 "3x4-rtl": dojo.moduleUrl("dijit.themes", "a11y/colors3x4-rtl.png")
7533 },
7534
7535 // templateString: String
7536 // The template of this widget.
7537 templateString: dojo.cache("dijit", "templates/ColorPalette.html", "<div class=\"dijitInline dijitColorPalette\">\n\t<img class=\"dijitColorPaletteUnder\" dojoAttachPoint=\"imageNode\" waiRole=\"presentation\" alt=\"\"/>\n\t<table class=\"dijitPaletteTable\" cellSpacing=\"0\" cellPadding=\"0\">\n\t\t<tbody dojoAttachPoint=\"gridNode\"></tbody>\n\t</table>\n</div>\n"),
7538
7539 baseClass: "dijitColorPalette",
7540
7541 dyeClass: 'dijit._Color',
7542
7543 buildRendering: function(){
7544 // Instantiate the template, which makes a skeleton into which we'll insert a bunch of
7545 // <img> nodes
7546
7547 this.inherited(arguments);
7548
7549 this.imageNode.setAttribute("src", this._imagePaths[this.palette + (this.isLeftToRight() ? "" : "-rtl")].toString());
7550
7551 var i18nColorNames = dojo.i18n.getLocalization("dojo", "colors", this.lang);
7552 this._preparePalette(
7553 this._palettes[this.palette],
7554 i18nColorNames
7555 );
7556 }
7557 });
7558
7559 dojo.declare("dijit._Color", dojo.Color,
7560 // summary:
7561 // Object associated with each cell in a ColorPalette palette.
7562 // Implements dijit.Dye.
7563 {
7564 constructor: function(/*String*/alias){
7565 this._alias = alias;
7566 this.setColor(dojo.Color.named[alias]);
7567 },
7568
7569 getValue: function(){
7570 // summary:
7571 // Note that although dijit._Color is initialized with a value like "white" getValue() always
7572 // returns a hex value
7573 return this.toHex();
7574 },
7575
7576 fillCell: function(/*DOMNode*/ cell, /*String*/ blankGif){
7577 dojo.create("img", {
7578 src: blankGif,
7579 "class": "dijitPaletteImg",
7580 alt: this._alias
7581 }, cell);
7582 }
7583 }
7584 );
7585
7586 }
7587
7588 if(!dojo._hasResource["dojo.dnd.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7589 dojo._hasResource["dojo.dnd.common"] = true;
7590 dojo.provide("dojo.dnd.common");
7591
7592 dojo.dnd.getCopyKeyState = dojo.isCopyKey;
7593
7594 dojo.dnd._uniqueId = 0;
7595 dojo.dnd.getUniqueId = function(){
7596 // summary:
7597 // returns a unique string for use with any DOM element
7598 var id;
7599 do{
7600 id = dojo._scopeName + "Unique" + (++dojo.dnd._uniqueId);
7601 }while(dojo.byId(id));
7602 return id;
7603 };
7604
7605 dojo.dnd._empty = {};
7606
7607 dojo.dnd.isFormElement = function(/*Event*/ e){
7608 // summary:
7609 // returns true if user clicked on a form element
7610 var t = e.target;
7611 if(t.nodeType == 3 /*TEXT_NODE*/){
7612 t = t.parentNode;
7613 }
7614 return " button textarea input select option ".indexOf(" " + t.tagName.toLowerCase() + " ") >= 0; // Boolean
7615 };
7616
7617 }
7618
7619 if(!dojo._hasResource["dojo.dnd.autoscroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7620 dojo._hasResource["dojo.dnd.autoscroll"] = true;
7621 dojo.provide("dojo.dnd.autoscroll");
7622
7623 dojo.dnd.getViewport = function(){
7624 // summary:
7625 // Returns a viewport size (visible part of the window)
7626
7627 // TODO: remove this when getViewport() moved to dojo core, see #7028
7628
7629 // FIXME: need more docs!!
7630 var d = dojo.doc, dd = d.documentElement, w = window, b = dojo.body();
7631 if(dojo.isMozilla){
7632 return {w: dd.clientWidth, h: w.innerHeight}; // Object
7633 }else if(!dojo.isOpera && w.innerWidth){
7634 return {w: w.innerWidth, h: w.innerHeight}; // Object
7635 }else if (!dojo.isOpera && dd && dd.clientWidth){
7636 return {w: dd.clientWidth, h: dd.clientHeight}; // Object
7637 }else if (b.clientWidth){
7638 return {w: b.clientWidth, h: b.clientHeight}; // Object
7639 }
7640 return null; // Object
7641 };
7642
7643 dojo.dnd.V_TRIGGER_AUTOSCROLL = 32;
7644 dojo.dnd.H_TRIGGER_AUTOSCROLL = 32;
7645
7646 dojo.dnd.V_AUTOSCROLL_VALUE = 16;
7647 dojo.dnd.H_AUTOSCROLL_VALUE = 16;
7648
7649 dojo.dnd.autoScroll = function(e){
7650 // summary:
7651 // a handler for onmousemove event, which scrolls the window, if
7652 // necesary
7653 // e: Event
7654 // onmousemove event
7655
7656 // FIXME: needs more docs!
7657 var v = dojo.dnd.getViewport(), dx = 0, dy = 0;
7658 if(e.clientX < dojo.dnd.H_TRIGGER_AUTOSCROLL){
7659 dx = -dojo.dnd.H_AUTOSCROLL_VALUE;
7660 }else if(e.clientX > v.w - dojo.dnd.H_TRIGGER_AUTOSCROLL){
7661 dx = dojo.dnd.H_AUTOSCROLL_VALUE;
7662 }
7663 if(e.clientY < dojo.dnd.V_TRIGGER_AUTOSCROLL){
7664 dy = -dojo.dnd.V_AUTOSCROLL_VALUE;
7665 }else if(e.clientY > v.h - dojo.dnd.V_TRIGGER_AUTOSCROLL){
7666 dy = dojo.dnd.V_AUTOSCROLL_VALUE;
7667 }
7668 window.scrollBy(dx, dy);
7669 };
7670
7671 dojo.dnd._validNodes = {"div": 1, "p": 1, "td": 1};
7672 dojo.dnd._validOverflow = {"auto": 1, "scroll": 1};
7673
7674 dojo.dnd.autoScrollNodes = function(e){
7675 // summary:
7676 // a handler for onmousemove event, which scrolls the first avaialble
7677 // Dom element, it falls back to dojo.dnd.autoScroll()
7678 // e: Event
7679 // onmousemove event
7680
7681 // FIXME: needs more docs!
7682 for(var n = e.target; n;){
7683 if(n.nodeType == 1 && (n.tagName.toLowerCase() in dojo.dnd._validNodes)){
7684 var s = dojo.getComputedStyle(n);
7685 if(s.overflow.toLowerCase() in dojo.dnd._validOverflow){
7686 var b = dojo._getContentBox(n, s), t = dojo.position(n, true);
7687 //console.log(b.l, b.t, t.x, t.y, n.scrollLeft, n.scrollTop);
7688 var w = Math.min(dojo.dnd.H_TRIGGER_AUTOSCROLL, b.w / 2),
7689 h = Math.min(dojo.dnd.V_TRIGGER_AUTOSCROLL, b.h / 2),
7690 rx = e.pageX - t.x, ry = e.pageY - t.y, dx = 0, dy = 0;
7691 if(dojo.isWebKit || dojo.isOpera){
7692 // FIXME: this code should not be here, it should be taken into account
7693 // either by the event fixing code, or the dojo.position()
7694 // FIXME: this code doesn't work on Opera 9.5 Beta
7695 rx += dojo.body().scrollLeft, ry += dojo.body().scrollTop;
7696 }
7697 if(rx > 0 && rx < b.w){
7698 if(rx < w){
7699 dx = -w;
7700 }else if(rx > b.w - w){
7701 dx = w;
7702 }
7703 }
7704 //console.log("ry =", ry, "b.h =", b.h, "h =", h);
7705 if(ry > 0 && ry < b.h){
7706 if(ry < h){
7707 dy = -h;
7708 }else if(ry > b.h - h){
7709 dy = h;
7710 }
7711 }
7712 var oldLeft = n.scrollLeft, oldTop = n.scrollTop;
7713 n.scrollLeft = n.scrollLeft + dx;
7714 n.scrollTop = n.scrollTop + dy;
7715 if(oldLeft != n.scrollLeft || oldTop != n.scrollTop){ return; }
7716 }
7717 }
7718 try{
7719 n = n.parentNode;
7720 }catch(x){
7721 n = null;
7722 }
7723 }
7724 dojo.dnd.autoScroll(e);
7725 };
7726
7727 }
7728
7729 if(!dojo._hasResource["dojo.dnd.Mover"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7730 dojo._hasResource["dojo.dnd.Mover"] = true;
7731 dojo.provide("dojo.dnd.Mover");
7732
7733
7734
7735
7736 dojo.declare("dojo.dnd.Mover", null, {
7737 constructor: function(node, e, host){
7738 // summary:
7739 // an object, which makes a node follow the mouse.
7740 // Used as a default mover, and as a base class for custom movers.
7741 // node: Node
7742 // a node (or node's id) to be moved
7743 // e: Event
7744 // a mouse event, which started the move;
7745 // only pageX and pageY properties are used
7746 // host: Object?
7747 // object which implements the functionality of the move,
7748 // and defines proper events (onMoveStart and onMoveStop)
7749 this.node = dojo.byId(node);
7750 this.marginBox = {l: e.pageX, t: e.pageY};
7751 this.mouseButton = e.button;
7752 var h = this.host = host, d = node.ownerDocument,
7753 firstEvent = dojo.connect(d, "onmousemove", this, "onFirstMove");
7754 this.events = [
7755 dojo.connect(d, "onmousemove", this, "onMouseMove"),
7756 dojo.connect(d, "onmouseup", this, "onMouseUp"),
7757 // cancel text selection and text dragging
7758 dojo.connect(d, "ondragstart", dojo.stopEvent),
7759 dojo.connect(d.body, "onselectstart", dojo.stopEvent),
7760 firstEvent
7761 ];
7762 // notify that the move has started
7763 if(h && h.onMoveStart){
7764 h.onMoveStart(this);
7765 }
7766 },
7767 // mouse event processors
7768 onMouseMove: function(e){
7769 // summary:
7770 // event processor for onmousemove
7771 // e: Event
7772 // mouse event
7773 dojo.dnd.autoScroll(e);
7774 var m = this.marginBox;
7775 this.host.onMove(this, {l: m.l + e.pageX, t: m.t + e.pageY}, e);
7776 dojo.stopEvent(e);
7777 },
7778 onMouseUp: function(e){
7779 if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ?
7780 e.button == 0 : this.mouseButton == e.button){
7781 this.destroy();
7782 }
7783 dojo.stopEvent(e);
7784 },
7785 // utilities
7786 onFirstMove: function(e){
7787 // summary:
7788 // makes the node absolute; it is meant to be called only once.
7789 // relative and absolutely positioned nodes are assumed to use pixel units
7790 var s = this.node.style, l, t, h = this.host;
7791 switch(s.position){
7792 case "relative":
7793 case "absolute":
7794 // assume that left and top values are in pixels already
7795 l = Math.round(parseFloat(s.left)) || 0;
7796 t = Math.round(parseFloat(s.top)) || 0;
7797 break;
7798 default:
7799 s.position = "absolute"; // enforcing the absolute mode
7800 var m = dojo.marginBox(this.node);
7801 // event.pageX/pageY (which we used to generate the initial
7802 // margin box) includes padding and margin set on the body.
7803 // However, setting the node's position to absolute and then
7804 // doing dojo.marginBox on it *doesn't* take that additional
7805 // space into account - so we need to subtract the combined
7806 // padding and margin. We use getComputedStyle and
7807 // _getMarginBox/_getContentBox to avoid the extra lookup of
7808 // the computed style.
7809 var b = dojo.doc.body;
7810 var bs = dojo.getComputedStyle(b);
7811 var bm = dojo._getMarginBox(b, bs);
7812 var bc = dojo._getContentBox(b, bs);
7813 l = m.l - (bc.l - bm.l);
7814 t = m.t - (bc.t - bm.t);
7815 break;
7816 }
7817 this.marginBox.l = l - this.marginBox.l;
7818 this.marginBox.t = t - this.marginBox.t;
7819 if(h && h.onFirstMove){
7820 h.onFirstMove(this, e);
7821 }
7822 dojo.disconnect(this.events.pop());
7823 },
7824 destroy: function(){
7825 // summary:
7826 // stops the move, deletes all references, so the object can be garbage-collected
7827 dojo.forEach(this.events, dojo.disconnect);
7828 // undo global settings
7829 var h = this.host;
7830 if(h && h.onMoveStop){
7831 h.onMoveStop(this);
7832 }
7833 // destroy objects
7834 this.events = this.node = this.host = null;
7835 }
7836 });
7837
7838 }
7839
7840 if(!dojo._hasResource["dojo.dnd.Moveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7841 dojo._hasResource["dojo.dnd.Moveable"] = true;
7842 dojo.provide("dojo.dnd.Moveable");
7843
7844
7845
7846 /*=====
7847 dojo.declare("dojo.dnd.__MoveableArgs", [], {
7848 // handle: Node||String
7849 // A node (or node's id), which is used as a mouse handle.
7850 // If omitted, the node itself is used as a handle.
7851 handle: null,
7852
7853 // delay: Number
7854 // delay move by this number of pixels
7855 delay: 0,
7856
7857 // skip: Boolean
7858 // skip move of form elements
7859 skip: false,
7860
7861 // mover: Object
7862 // a constructor of custom Mover
7863 mover: dojo.dnd.Mover
7864 });
7865 =====*/
7866
7867 dojo.declare("dojo.dnd.Moveable", null, {
7868 // object attributes (for markup)
7869 handle: "",
7870 delay: 0,
7871 skip: false,
7872
7873 constructor: function(node, params){
7874 // summary:
7875 // an object, which makes a node moveable
7876 // node: Node
7877 // a node (or node's id) to be moved
7878 // params: dojo.dnd.__MoveableArgs?
7879 // optional parameters
7880 this.node = dojo.byId(node);
7881 if(!params){ params = {}; }
7882 this.handle = params.handle ? dojo.byId(params.handle) : null;
7883 if(!this.handle){ this.handle = this.node; }
7884 this.delay = params.delay > 0 ? params.delay : 0;
7885 this.skip = params.skip;
7886 this.mover = params.mover ? params.mover : dojo.dnd.Mover;
7887 this.events = [
7888 dojo.connect(this.handle, "onmousedown", this, "onMouseDown"),
7889 // cancel text selection and text dragging
7890 dojo.connect(this.handle, "ondragstart", this, "onSelectStart"),
7891 dojo.connect(this.handle, "onselectstart", this, "onSelectStart")
7892 ];
7893 },
7894
7895 // markup methods
7896 markupFactory: function(params, node){
7897 return new dojo.dnd.Moveable(node, params);
7898 },
7899
7900 // methods
7901 destroy: function(){
7902 // summary:
7903 // stops watching for possible move, deletes all references, so the object can be garbage-collected
7904 dojo.forEach(this.events, dojo.disconnect);
7905 this.events = this.node = this.handle = null;
7906 },
7907
7908 // mouse event processors
7909 onMouseDown: function(e){
7910 // summary:
7911 // event processor for onmousedown, creates a Mover for the node
7912 // e: Event
7913 // mouse event
7914 if(this.skip && dojo.dnd.isFormElement(e)){ return; }
7915 if(this.delay){
7916 this.events.push(
7917 dojo.connect(this.handle, "onmousemove", this, "onMouseMove"),
7918 dojo.connect(this.handle, "onmouseup", this, "onMouseUp")
7919 );
7920 this._lastX = e.pageX;
7921 this._lastY = e.pageY;
7922 }else{
7923 this.onDragDetected(e);
7924 }
7925 dojo.stopEvent(e);
7926 },
7927 onMouseMove: function(e){
7928 // summary:
7929 // event processor for onmousemove, used only for delayed drags
7930 // e: Event
7931 // mouse event
7932 if(Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay){
7933 this.onMouseUp(e);
7934 this.onDragDetected(e);
7935 }
7936 dojo.stopEvent(e);
7937 },
7938 onMouseUp: function(e){
7939 // summary:
7940 // event processor for onmouseup, used only for delayed drags
7941 // e: Event
7942 // mouse event
7943 for(var i = 0; i < 2; ++i){
7944 dojo.disconnect(this.events.pop());
7945 }
7946 dojo.stopEvent(e);
7947 },
7948 onSelectStart: function(e){
7949 // summary:
7950 // event processor for onselectevent and ondragevent
7951 // e: Event
7952 // mouse event
7953 if(!this.skip || !dojo.dnd.isFormElement(e)){
7954 dojo.stopEvent(e);
7955 }
7956 },
7957
7958 // local events
7959 onDragDetected: function(/* Event */ e){
7960 // summary:
7961 // called when the drag is detected;
7962 // responsible for creation of the mover
7963 new this.mover(this.node, e, this);
7964 },
7965 onMoveStart: function(/* dojo.dnd.Mover */ mover){
7966 // summary:
7967 // called before every move operation
7968 dojo.publish("/dnd/move/start", [mover]);
7969 dojo.addClass(dojo.body(), "dojoMove");
7970 dojo.addClass(this.node, "dojoMoveItem");
7971 },
7972 onMoveStop: function(/* dojo.dnd.Mover */ mover){
7973 // summary:
7974 // called after every move operation
7975 dojo.publish("/dnd/move/stop", [mover]);
7976 dojo.removeClass(dojo.body(), "dojoMove");
7977 dojo.removeClass(this.node, "dojoMoveItem");
7978 },
7979 onFirstMove: function(/* dojo.dnd.Mover */ mover, /* Event */ e){
7980 // summary:
7981 // called during the very first move notification;
7982 // can be used to initialize coordinates, can be overwritten.
7983
7984 // default implementation does nothing
7985 },
7986 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop, /* Event */ e){
7987 // summary:
7988 // called during every move notification;
7989 // should actually move the node; can be overwritten.
7990 this.onMoving(mover, leftTop);
7991 var s = mover.node.style;
7992 s.left = leftTop.l + "px";
7993 s.top = leftTop.t + "px";
7994 this.onMoved(mover, leftTop);
7995 },
7996 onMoving: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
7997 // summary:
7998 // called before every incremental move; can be overwritten.
7999
8000 // default implementation does nothing
8001 },
8002 onMoved: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8003 // summary:
8004 // called after every incremental move; can be overwritten.
8005
8006 // default implementation does nothing
8007 }
8008 });
8009
8010 }
8011
8012 if(!dojo._hasResource["dojo.dnd.move"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8013 dojo._hasResource["dojo.dnd.move"] = true;
8014 dojo.provide("dojo.dnd.move");
8015
8016
8017
8018
8019 /*=====
8020 dojo.declare("dojo.dnd.move.__constrainedMoveableArgs", [dojo.dnd.__MoveableArgs], {
8021 // constraints: Function
8022 // Calculates a constraint box.
8023 // It is called in a context of the moveable object.
8024 constraints: function(){},
8025
8026 // within: Boolean
8027 // restrict move within boundaries.
8028 within: false
8029 });
8030 =====*/
8031
8032 dojo.declare("dojo.dnd.move.constrainedMoveable", dojo.dnd.Moveable, {
8033 // object attributes (for markup)
8034 constraints: function(){},
8035 within: false,
8036
8037 // markup methods
8038 markupFactory: function(params, node){
8039 return new dojo.dnd.move.constrainedMoveable(node, params);
8040 },
8041
8042 constructor: function(node, params){
8043 // summary:
8044 // an object that makes a node moveable
8045 // node: Node
8046 // a node (or node's id) to be moved
8047 // params: dojo.dnd.move.__constrainedMoveableArgs?
8048 // an optional object with additional parameters;
8049 // the rest is passed to the base class
8050 if(!params){ params = {}; }
8051 this.constraints = params.constraints;
8052 this.within = params.within;
8053 },
8054 onFirstMove: function(/* dojo.dnd.Mover */ mover){
8055 // summary:
8056 // called during the very first move notification;
8057 // can be used to initialize coordinates, can be overwritten.
8058 var c = this.constraintBox = this.constraints.call(this, mover);
8059 c.r = c.l + c.w;
8060 c.b = c.t + c.h;
8061 if(this.within){
8062 var mb = dojo.marginBox(mover.node);
8063 c.r -= mb.w;
8064 c.b -= mb.h;
8065 }
8066 },
8067 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8068 // summary:
8069 // called during every move notification;
8070 // should actually move the node; can be overwritten.
8071 var c = this.constraintBox, s = mover.node.style;
8072 s.left = (leftTop.l < c.l ? c.l : c.r < leftTop.l ? c.r : leftTop.l) + "px";
8073 s.top = (leftTop.t < c.t ? c.t : c.b < leftTop.t ? c.b : leftTop.t) + "px";
8074 }
8075 });
8076
8077 /*=====
8078 dojo.declare("dojo.dnd.move.__boxConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], {
8079 // box: Object
8080 // a constraint box
8081 box: {}
8082 });
8083 =====*/
8084
8085 dojo.declare("dojo.dnd.move.boxConstrainedMoveable", dojo.dnd.move.constrainedMoveable, {
8086 // box:
8087 // object attributes (for markup)
8088 box: {},
8089
8090 // markup methods
8091 markupFactory: function(params, node){
8092 return new dojo.dnd.move.boxConstrainedMoveable(node, params);
8093 },
8094
8095 constructor: function(node, params){
8096 // summary:
8097 // an object, which makes a node moveable
8098 // node: Node
8099 // a node (or node's id) to be moved
8100 // params: dojo.dnd.move.__boxConstrainedMoveableArgs?
8101 // an optional object with parameters
8102 var box = params && params.box;
8103 this.constraints = function(){ return box; };
8104 }
8105 });
8106
8107 /*=====
8108 dojo.declare("dojo.dnd.move.__parentConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], {
8109 // area: String
8110 // A parent's area to restrict the move.
8111 // Can be "margin", "border", "padding", or "content".
8112 area: ""
8113 });
8114 =====*/
8115
8116 dojo.declare("dojo.dnd.move.parentConstrainedMoveable", dojo.dnd.move.constrainedMoveable, {
8117 // area:
8118 // object attributes (for markup)
8119 area: "content",
8120
8121 // markup methods
8122 markupFactory: function(params, node){
8123 return new dojo.dnd.move.parentConstrainedMoveable(node, params);
8124 },
8125
8126 constructor: function(node, params){
8127 // summary:
8128 // an object, which makes a node moveable
8129 // node: Node
8130 // a node (or node's id) to be moved
8131 // params: dojo.dnd.move.__parentConstrainedMoveableArgs?
8132 // an optional object with parameters
8133 var area = params && params.area;
8134 this.constraints = function(){
8135 var n = this.node.parentNode,
8136 s = dojo.getComputedStyle(n),
8137 mb = dojo._getMarginBox(n, s);
8138 if(area == "margin"){
8139 return mb; // Object
8140 }
8141 var t = dojo._getMarginExtents(n, s);
8142 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8143 if(area == "border"){
8144 return mb; // Object
8145 }
8146 t = dojo._getBorderExtents(n, s);
8147 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8148 if(area == "padding"){
8149 return mb; // Object
8150 }
8151 t = dojo._getPadExtents(n, s);
8152 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8153 return mb; // Object
8154 };
8155 }
8156 });
8157
8158 // WARNING: below are obsolete objects, instead of custom movers use custom moveables (above)
8159
8160 dojo.dnd.move.constrainedMover = function(fun, within){
8161 // summary:
8162 // returns a constrained version of dojo.dnd.Mover
8163 // description:
8164 // this function produces n object, which will put a constraint on
8165 // the margin box of dragged object in absolute coordinates
8166 // fun: Function
8167 // called on drag, and returns a constraint box
8168 // within: Boolean
8169 // if true, constraints the whole dragged object withtin the rectangle,
8170 // otherwise the constraint is applied to the left-top corner
8171
8172 dojo.deprecated("dojo.dnd.move.constrainedMover, use dojo.dnd.move.constrainedMoveable instead");
8173 var mover = function(node, e, notifier){
8174 dojo.dnd.Mover.call(this, node, e, notifier);
8175 };
8176 dojo.extend(mover, dojo.dnd.Mover.prototype);
8177 dojo.extend(mover, {
8178 onMouseMove: function(e){
8179 // summary: event processor for onmousemove
8180 // e: Event: mouse event
8181 dojo.dnd.autoScroll(e);
8182 var m = this.marginBox, c = this.constraintBox,
8183 l = m.l + e.pageX, t = m.t + e.pageY;
8184 l = l < c.l ? c.l : c.r < l ? c.r : l;
8185 t = t < c.t ? c.t : c.b < t ? c.b : t;
8186 this.host.onMove(this, {l: l, t: t});
8187 },
8188 onFirstMove: function(){
8189 // summary: called once to initialize things; it is meant to be called only once
8190 dojo.dnd.Mover.prototype.onFirstMove.call(this);
8191 var c = this.constraintBox = fun.call(this);
8192 c.r = c.l + c.w;
8193 c.b = c.t + c.h;
8194 if(within){
8195 var mb = dojo.marginBox(this.node);
8196 c.r -= mb.w;
8197 c.b -= mb.h;
8198 }
8199 }
8200 });
8201 return mover; // Object
8202 };
8203
8204 dojo.dnd.move.boxConstrainedMover = function(box, within){
8205 // summary:
8206 // a specialization of dojo.dnd.constrainedMover, which constrains to the specified box
8207 // box: Object
8208 // a constraint box (l, t, w, h)
8209 // within: Boolean
8210 // if true, constraints the whole dragged object withtin the rectangle,
8211 // otherwise the constraint is applied to the left-top corner
8212
8213 dojo.deprecated("dojo.dnd.move.boxConstrainedMover, use dojo.dnd.move.boxConstrainedMoveable instead");
8214 return dojo.dnd.move.constrainedMover(function(){ return box; }, within); // Object
8215 };
8216
8217 dojo.dnd.move.parentConstrainedMover = function(area, within){
8218 // summary:
8219 // a specialization of dojo.dnd.constrainedMover, which constrains to the parent node
8220 // area: String
8221 // "margin" to constrain within the parent's margin box, "border" for the border box,
8222 // "padding" for the padding box, and "content" for the content box; "content" is the default value.
8223 // within: Boolean
8224 // if true, constraints the whole dragged object within the rectangle,
8225 // otherwise the constraint is applied to the left-top corner
8226
8227 dojo.deprecated("dojo.dnd.move.parentConstrainedMover, use dojo.dnd.move.parentConstrainedMoveable instead");
8228 var fun = function(){
8229 var n = this.node.parentNode,
8230 s = dojo.getComputedStyle(n),
8231 mb = dojo._getMarginBox(n, s);
8232 if(area == "margin"){
8233 return mb; // Object
8234 }
8235 var t = dojo._getMarginExtents(n, s);
8236 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8237 if(area == "border"){
8238 return mb; // Object
8239 }
8240 t = dojo._getBorderExtents(n, s);
8241 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8242 if(area == "padding"){
8243 return mb; // Object
8244 }
8245 t = dojo._getPadExtents(n, s);
8246 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8247 return mb; // Object
8248 };
8249 return dojo.dnd.move.constrainedMover(fun, within); // Object
8250 };
8251
8252 // patching functions one level up for compatibility
8253
8254 dojo.dnd.constrainedMover = dojo.dnd.move.constrainedMover;
8255 dojo.dnd.boxConstrainedMover = dojo.dnd.move.boxConstrainedMover;
8256 dojo.dnd.parentConstrainedMover = dojo.dnd.move.parentConstrainedMover;
8257
8258 }
8259
8260 if(!dojo._hasResource["dojo.dnd.TimedMoveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8261 dojo._hasResource["dojo.dnd.TimedMoveable"] = true;
8262 dojo.provide("dojo.dnd.TimedMoveable");
8263
8264
8265
8266 /*=====
8267 dojo.declare("dojo.dnd.__TimedMoveableArgs", [dojo.dnd.__MoveableArgs], {
8268 // timeout: Number
8269 // delay move by this number of ms,
8270 // accumulating position changes during the timeout
8271 timeout: 0
8272 });
8273 =====*/
8274
8275 (function(){
8276 // precalculate long expressions
8277 var oldOnMove = dojo.dnd.Moveable.prototype.onMove;
8278
8279 dojo.declare("dojo.dnd.TimedMoveable", dojo.dnd.Moveable, {
8280 // summary:
8281 // A specialized version of Moveable to support an FPS throttling.
8282 // This class puts an upper restriction on FPS, which may reduce
8283 // the CPU load. The additional parameter "timeout" regulates
8284 // the delay before actually moving the moveable object.
8285
8286 // object attributes (for markup)
8287 timeout: 40, // in ms, 40ms corresponds to 25 fps
8288
8289 constructor: function(node, params){
8290 // summary:
8291 // an object that makes a node moveable with a timer
8292 // node: Node||String
8293 // a node (or node's id) to be moved
8294 // params: dojo.dnd.__TimedMoveableArgs
8295 // object with additional parameters.
8296
8297 // sanitize parameters
8298 if(!params){ params = {}; }
8299 if(params.timeout && typeof params.timeout == "number" && params.timeout >= 0){
8300 this.timeout = params.timeout;
8301 }
8302 },
8303
8304 // markup methods
8305 markupFactory: function(params, node){
8306 return new dojo.dnd.TimedMoveable(node, params);
8307 },
8308
8309 onMoveStop: function(/* dojo.dnd.Mover */ mover){
8310 if(mover._timer){
8311 // stop timer
8312 clearTimeout(mover._timer)
8313 // reflect the last received position
8314 oldOnMove.call(this, mover, mover._leftTop)
8315 }
8316 dojo.dnd.Moveable.prototype.onMoveStop.apply(this, arguments);
8317 },
8318 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8319 mover._leftTop = leftTop;
8320 if(!mover._timer){
8321 var _t = this; // to avoid using dojo.hitch()
8322 mover._timer = setTimeout(function(){
8323 // we don't have any pending requests
8324 mover._timer = null;
8325 // reflect the last received position
8326 oldOnMove.call(_t, mover, mover._leftTop);
8327 }, this.timeout);
8328 }
8329 }
8330 });
8331 })();
8332
8333 }
8334
8335 if(!dojo._hasResource["dijit.form._FormMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8336 dojo._hasResource["dijit.form._FormMixin"] = true;
8337 dojo.provide("dijit.form._FormMixin");
8338
8339
8340
8341 dojo.declare("dijit.form._FormMixin", null,
8342 {
8343 // summary:
8344 // Mixin for containers of form widgets (i.e. widgets that represent a single value
8345 // and can be children of a <form> node or dijit.form.Form widget)
8346 // description:
8347 // Can extract all the form widgets
8348 // values and combine them into a single javascript object, or alternately
8349 // take such an object and set the values for all the contained
8350 // form widgets
8351
8352 /*=====
8353 // value: Object
8354 // Name/value hash for each child widget with a name and value.
8355 // Child widgets without names are not part of the hash.
8356 //
8357 // If there are multiple child widgets w/the same name, value is an array,
8358 // unless they are radio buttons in which case value is a scalar (since only
8359 // one radio button can be checked at a time).
8360 //
8361 // If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure.
8362 //
8363 // Example:
8364 // | { name: "John Smith", interests: ["sports", "movies"] }
8365 =====*/
8366
8367 // TODO:
8368 // * Repeater
8369 // * better handling for arrays. Often form elements have names with [] like
8370 // * people[3].sex (for a list of people [{name: Bill, sex: M}, ...])
8371 //
8372 //
8373
8374 reset: function(){
8375 dojo.forEach(this.getDescendants(), function(widget){
8376 if(widget.reset){
8377 widget.reset();
8378 }
8379 });
8380 },
8381
8382 validate: function(){
8383 // summary:
8384 // returns if the form is valid - same as isValid - but
8385 // provides a few additional (ui-specific) features.
8386 // 1 - it will highlight any sub-widgets that are not
8387 // valid
8388 // 2 - it will call focus() on the first invalid
8389 // sub-widget
8390 var didFocus = false;
8391 return dojo.every(dojo.map(this.getDescendants(), function(widget){
8392 // Need to set this so that "required" widgets get their
8393 // state set.
8394 widget._hasBeenBlurred = true;
8395 var valid = widget.disabled || !widget.validate || widget.validate();
8396 if(!valid && !didFocus){
8397 // Set focus of the first non-valid widget
8398 dojo.window.scrollIntoView(widget.containerNode || widget.domNode);
8399 widget.focus();
8400 didFocus = true;
8401 }
8402 return valid;
8403 }), function(item){ return item; });
8404 },
8405
8406 setValues: function(val){
8407 dojo.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.", "", "2.0");
8408 return this.set('value', val);
8409 },
8410 _setValueAttr: function(/*object*/obj){
8411 // summary:
8412 // Fill in form values from according to an Object (in the format returned by attr('value'))
8413
8414 // generate map from name --> [list of widgets with that name]
8415 var map = { };
8416 dojo.forEach(this.getDescendants(), function(widget){
8417 if(!widget.name){ return; }
8418 var entry = map[widget.name] || (map[widget.name] = [] );
8419 entry.push(widget);
8420 });
8421
8422 for(var name in map){
8423 if(!map.hasOwnProperty(name)){
8424 continue;
8425 }
8426 var widgets = map[name], // array of widgets w/this name
8427 values = dojo.getObject(name, false, obj); // list of values for those widgets
8428
8429 if(values === undefined){
8430 continue;
8431 }
8432 if(!dojo.isArray(values)){
8433 values = [ values ];
8434 }
8435 if(typeof widgets[0].checked == 'boolean'){
8436 // for checkbox/radio, values is a list of which widgets should be checked
8437 dojo.forEach(widgets, function(w, i){
8438 w.set('value', dojo.indexOf(values, w.value) != -1);
8439 });
8440 }else if(widgets[0].multiple){
8441 // it takes an array (e.g. multi-select)
8442 widgets[0].set('value', values);
8443 }else{
8444 // otherwise, values is a list of values to be assigned sequentially to each widget
8445 dojo.forEach(widgets, function(w, i){
8446 w.set('value', values[i]);
8447 });
8448 }
8449 }
8450
8451 /***
8452 * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets)
8453
8454 dojo.forEach(this.containerNode.elements, function(element){
8455 if(element.name == ''){return}; // like "continue"
8456 var namePath = element.name.split(".");
8457 var myObj=obj;
8458 var name=namePath[namePath.length-1];
8459 for(var j=1,len2=namePath.length;j<len2;++j){
8460 var p=namePath[j - 1];
8461 // repeater support block
8462 var nameA=p.split("[");
8463 if(nameA.length > 1){
8464 if(typeof(myObj[nameA[0]]) == "undefined"){
8465 myObj[nameA[0]]=[ ];
8466 } // if
8467
8468 nameIndex=parseInt(nameA[1]);
8469 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
8470 myObj[nameA[0]][nameIndex] = { };
8471 }
8472 myObj=myObj[nameA[0]][nameIndex];
8473 continue;
8474 } // repeater support ends
8475
8476 if(typeof(myObj[p]) == "undefined"){
8477 myObj=undefined;
8478 break;
8479 };
8480 myObj=myObj[p];
8481 }
8482
8483 if(typeof(myObj) == "undefined"){
8484 return; // like "continue"
8485 }
8486 if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){
8487 return; // like "continue"
8488 }
8489
8490 // TODO: widget values (just call attr('value', ...) on the widget)
8491
8492 // TODO: maybe should call dojo.getNodeProp() instead
8493 switch(element.type){
8494 case "checkbox":
8495 element.checked = (name in myObj) &&
8496 dojo.some(myObj[name], function(val){ return val == element.value; });
8497 break;
8498 case "radio":
8499 element.checked = (name in myObj) && myObj[name] == element.value;
8500 break;
8501 case "select-multiple":
8502 element.selectedIndex=-1;
8503 dojo.forEach(element.options, function(option){
8504 option.selected = dojo.some(myObj[name], function(val){ return option.value == val; });
8505 });
8506 break;
8507 case "select-one":
8508 element.selectedIndex="0";
8509 dojo.forEach(element.options, function(option){
8510 option.selected = option.value == myObj[name];
8511 });
8512 break;
8513 case "hidden":
8514 case "text":
8515 case "textarea":
8516 case "password":
8517 element.value = myObj[name] || "";
8518 break;
8519 }
8520 });
8521 */
8522 },
8523
8524 getValues: function(){
8525 dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0");
8526 return this.get('value');
8527 },
8528 _getValueAttr: function(){
8529 // summary:
8530 // Returns Object representing form values.
8531 // description:
8532 // Returns name/value hash for each form element.
8533 // If there are multiple elements w/the same name, value is an array,
8534 // unless they are radio buttons in which case value is a scalar since only
8535 // one can be checked at a time.
8536 //
8537 // If the name is a dot separated list (like a.b.c.d), creates a nested structure.
8538 // Only works on widget form elements.
8539 // example:
8540 // | { name: "John Smith", interests: ["sports", "movies"] }
8541
8542 // get widget values
8543 var obj = { };
8544 dojo.forEach(this.getDescendants(), function(widget){
8545 var name = widget.name;
8546 if(!name || widget.disabled){ return; }
8547
8548 // Single value widget (checkbox, radio, or plain <input> type widget
8549 var value = widget.get('value');
8550
8551 // Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays
8552 if(typeof widget.checked == 'boolean'){
8553 if(/Radio/.test(widget.declaredClass)){
8554 // radio button
8555 if(value !== false){
8556 dojo.setObject(name, value, obj);
8557 }else{
8558 // give radio widgets a default of null
8559 value = dojo.getObject(name, false, obj);
8560 if(value === undefined){
8561 dojo.setObject(name, null, obj);
8562 }
8563 }
8564 }else{
8565 // checkbox/toggle button
8566 var ary=dojo.getObject(name, false, obj);
8567 if(!ary){
8568 ary=[];
8569 dojo.setObject(name, ary, obj);
8570 }
8571 if(value !== false){
8572 ary.push(value);
8573 }
8574 }
8575 }else{
8576 var prev=dojo.getObject(name, false, obj);
8577 if(typeof prev != "undefined"){
8578 if(dojo.isArray(prev)){
8579 prev.push(value);
8580 }else{
8581 dojo.setObject(name, [prev, value], obj);
8582 }
8583 }else{
8584 // unique name
8585 dojo.setObject(name, value, obj);
8586 }
8587 }
8588 });
8589
8590 /***
8591 * code for plain input boxes (see also dojo.formToObject, can we use that instead of this code?
8592 * but it doesn't understand [] notation, presumably)
8593 var obj = { };
8594 dojo.forEach(this.containerNode.elements, function(elm){
8595 if(!elm.name) {
8596 return; // like "continue"
8597 }
8598 var namePath = elm.name.split(".");
8599 var myObj=obj;
8600 var name=namePath[namePath.length-1];
8601 for(var j=1,len2=namePath.length;j<len2;++j){
8602 var nameIndex = null;
8603 var p=namePath[j - 1];
8604 var nameA=p.split("[");
8605 if(nameA.length > 1){
8606 if(typeof(myObj[nameA[0]]) == "undefined"){
8607 myObj[nameA[0]]=[ ];
8608 } // if
8609 nameIndex=parseInt(nameA[1]);
8610 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
8611 myObj[nameA[0]][nameIndex] = { };
8612 }
8613 } else if(typeof(myObj[nameA[0]]) == "undefined"){
8614 myObj[nameA[0]] = { }
8615 } // if
8616
8617 if(nameA.length == 1){
8618 myObj=myObj[nameA[0]];
8619 } else{
8620 myObj=myObj[nameA[0]][nameIndex];
8621 } // if
8622 } // for
8623
8624 if((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type == "radio" && elm.checked)){
8625 if(name == name.split("[")[0]){
8626 myObj[name]=elm.value;
8627 } else{
8628 // can not set value when there is no name
8629 }
8630 } else if(elm.type == "checkbox" && elm.checked){
8631 if(typeof(myObj[name]) == 'undefined'){
8632 myObj[name]=[ ];
8633 }
8634 myObj[name].push(elm.value);
8635 } else if(elm.type == "select-multiple"){
8636 if(typeof(myObj[name]) == 'undefined'){
8637 myObj[name]=[ ];
8638 }
8639 for(var jdx=0,len3=elm.options.length; jdx<len3; ++jdx){
8640 if(elm.options[jdx].selected){
8641 myObj[name].push(elm.options[jdx].value);
8642 }
8643 }
8644 } // if
8645 name=undefined;
8646 }); // forEach
8647 ***/
8648 return obj;
8649 },
8650
8651 // TODO: ComboBox might need time to process a recently input value. This should be async?
8652 isValid: function(){
8653 // summary:
8654 // Returns true if all of the widgets are valid
8655
8656 // This also populate this._invalidWidgets[] array with list of invalid widgets...
8657 // TODO: put that into separate function? It's confusing to have that as a side effect
8658 // of a method named isValid().
8659
8660 this._invalidWidgets = dojo.filter(this.getDescendants(), function(widget){
8661 return !widget.disabled && widget.isValid && !widget.isValid();
8662 });
8663 return !this._invalidWidgets.length;
8664 },
8665
8666
8667 onValidStateChange: function(isValid){
8668 // summary:
8669 // Stub function to connect to if you want to do something
8670 // (like disable/enable a submit button) when the valid
8671 // state changes on the form as a whole.
8672 },
8673
8674 _widgetChange: function(widget){
8675 // summary:
8676 // Connected to a widget's onChange function - update our
8677 // valid state, if needed.
8678 var isValid = this._lastValidState;
8679 if(!widget || this._lastValidState === undefined){
8680 // We have passed a null widget, or we haven't been validated
8681 // yet - let's re-check all our children
8682 // This happens when we connect (or reconnect) our children
8683 isValid = this.isValid();
8684 if(this._lastValidState === undefined){
8685 // Set this so that we don't fire an onValidStateChange
8686 // the first time
8687 this._lastValidState = isValid;
8688 }
8689 }else if(widget.isValid){
8690 this._invalidWidgets = dojo.filter(this._invalidWidgets || [], function(w){
8691 return (w != widget);
8692 }, this);
8693 if(!widget.isValid() && !widget.get("disabled")){
8694 this._invalidWidgets.push(widget);
8695 }
8696 isValid = (this._invalidWidgets.length === 0);
8697 }
8698 if(isValid !== this._lastValidState){
8699 this._lastValidState = isValid;
8700 this.onValidStateChange(isValid);
8701 }
8702 },
8703
8704 connectChildren: function(){
8705 // summary:
8706 // Connects to the onChange function of all children to
8707 // track valid state changes. You can call this function
8708 // directly, ex. in the event that you programmatically
8709 // add a widget to the form *after* the form has been
8710 // initialized.
8711 dojo.forEach(this._changeConnections, dojo.hitch(this, "disconnect"));
8712 var _this = this;
8713
8714 // we connect to validate - so that it better reflects the states
8715 // of the widgets - also, we only connect if it has a validate
8716 // function (to avoid too many unneeded connections)
8717 var conns = (this._changeConnections = []);
8718 dojo.forEach(dojo.filter(this.getDescendants(),
8719 function(item){ return item.validate; }
8720 ),
8721 function(widget){
8722 // We are interested in whenever the widget is validated - or
8723 // whenever the disabled attribute on that widget is changed
8724 conns.push(_this.connect(widget, "validate",
8725 dojo.hitch(_this, "_widgetChange", widget)));
8726 conns.push(_this.connect(widget, "_setDisabledAttr",
8727 dojo.hitch(_this, "_widgetChange", widget)));
8728 });
8729
8730 // Call the widget change function to update the valid state, in
8731 // case something is different now.
8732 this._widgetChange(null);
8733 },
8734
8735 startup: function(){
8736 this.inherited(arguments);
8737 // Initialize our valid state tracking. Needs to be done in startup
8738 // because it's not guaranteed that our children are initialized
8739 // yet.
8740 this._changeConnections = [];
8741 this.connectChildren();
8742 }
8743 });
8744
8745 }
8746
8747 if(!dojo._hasResource["dijit._DialogMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8748 dojo._hasResource["dijit._DialogMixin"] = true;
8749 dojo.provide("dijit._DialogMixin");
8750
8751
8752
8753 dojo.declare("dijit._DialogMixin", null,
8754 {
8755 // summary:
8756 // This provides functions useful to Dialog and TooltipDialog
8757
8758 attributeMap: dijit._Widget.prototype.attributeMap,
8759
8760 execute: function(/*Object*/ formContents){
8761 // summary:
8762 // Callback when the user hits the submit button.
8763 // Override this method to handle Dialog execution.
8764 // description:
8765 // After the user has pressed the submit button, the Dialog
8766 // first calls onExecute() to notify the container to hide the
8767 // dialog and restore focus to wherever it used to be.
8768 //
8769 // *Then* this method is called.
8770 // type:
8771 // callback
8772 },
8773
8774 onCancel: function(){
8775 // summary:
8776 // Called when user has pressed the Dialog's cancel button, to notify container.
8777 // description:
8778 // Developer shouldn't override or connect to this method;
8779 // it's a private communication device between the TooltipDialog
8780 // and the thing that opened it (ex: `dijit.form.DropDownButton`)
8781 // type:
8782 // protected
8783 },
8784
8785 onExecute: function(){
8786 // summary:
8787 // Called when user has pressed the dialog's OK button, to notify container.
8788 // description:
8789 // Developer shouldn't override or connect to this method;
8790 // it's a private communication device between the TooltipDialog
8791 // and the thing that opened it (ex: `dijit.form.DropDownButton`)
8792 // type:
8793 // protected
8794 },
8795
8796 _onSubmit: function(){
8797 // summary:
8798 // Callback when user hits submit button
8799 // type:
8800 // protected
8801 this.onExecute(); // notify container that we are about to execute
8802 this.execute(this.get('value'));
8803 },
8804
8805 _getFocusItems: function(/*Node*/ dialogNode){
8806 // summary:
8807 // Find focusable Items each time a dialog is opened,
8808 // setting _firstFocusItem and _lastFocusItem
8809 // tags:
8810 // protected
8811
8812 var elems = dijit._getTabNavigable(dojo.byId(dialogNode));
8813 this._firstFocusItem = elems.lowest || elems.first || dialogNode;
8814 this._lastFocusItem = elems.last || elems.highest || this._firstFocusItem;
8815 if(dojo.isMoz && this._firstFocusItem.tagName.toLowerCase() == "input" &&
8816 dojo.getNodeProp(this._firstFocusItem, "type").toLowerCase() == "file"){
8817 // FF doesn't behave well when first element is input type=file, set first focusable to dialog container
8818 dojo.attr(dialogNode, "tabIndex", "0");
8819 this._firstFocusItem = dialogNode;
8820 }
8821 }
8822 }
8823 );
8824
8825 }
8826
8827 if(!dojo._hasResource["dijit.DialogUnderlay"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8828 dojo._hasResource["dijit.DialogUnderlay"] = true;
8829 dojo.provide("dijit.DialogUnderlay");
8830
8831
8832
8833
8834
8835
8836 dojo.declare(
8837 "dijit.DialogUnderlay",
8838 [dijit._Widget, dijit._Templated],
8839 {
8840 // summary:
8841 // The component that blocks the screen behind a `dijit.Dialog`
8842 //
8843 // description:
8844 // A component used to block input behind a `dijit.Dialog`. Only a single
8845 // instance of this widget is created by `dijit.Dialog`, and saved as
8846 // a reference to be shared between all Dialogs as `dijit._underlay`
8847 //
8848 // The underlay itself can be styled based on and id:
8849 // | #myDialog_underlay { background-color:red; }
8850 //
8851 // In the case of `dijit.Dialog`, this id is based on the id of the Dialog,
8852 // suffixed with _underlay.
8853
8854 // Template has two divs; outer div is used for fade-in/fade-out, and also to hold background iframe.
8855 // Inner div has opacity specified in CSS file.
8856 templateString: "<div class='dijitDialogUnderlayWrapper'><div class='dijitDialogUnderlay' dojoAttachPoint='node'></div></div>",
8857
8858 // Parameters on creation or updatable later
8859
8860 // dialogId: String
8861 // Id of the dialog.... DialogUnderlay's id is based on this id
8862 dialogId: "",
8863
8864 // class: String
8865 // This class name is used on the DialogUnderlay node, in addition to dijitDialogUnderlay
8866 "class": "",
8867
8868 attributeMap: { id: "domNode" },
8869
8870 _setDialogIdAttr: function(id){
8871 dojo.attr(this.node, "id", id + "_underlay");
8872 },
8873
8874 _setClassAttr: function(clazz){
8875 this.node.className = "dijitDialogUnderlay " + clazz;
8876 },
8877
8878 postCreate: function(){
8879 // summary:
8880 // Append the underlay to the body
8881 dojo.body().appendChild(this.domNode);
8882 },
8883
8884 layout: function(){
8885 // summary:
8886 // Sets the background to the size of the viewport
8887 //
8888 // description:
8889 // Sets the background to the size of the viewport (rather than the size
8890 // of the document) since we need to cover the whole browser window, even
8891 // if the document is only a few lines long.
8892 // tags:
8893 // private
8894
8895 var is = this.node.style,
8896 os = this.domNode.style;
8897
8898 // hide the background temporarily, so that the background itself isn't
8899 // causing scrollbars to appear (might happen when user shrinks browser
8900 // window and then we are called to resize)
8901 os.display = "none";
8902
8903 // then resize and show
8904 var viewport = dojo.window.getBox();
8905 os.top = viewport.t + "px";
8906 os.left = viewport.l + "px";
8907 is.width = viewport.w + "px";
8908 is.height = viewport.h + "px";
8909 os.display = "block";
8910 },
8911
8912 show: function(){
8913 // summary:
8914 // Show the dialog underlay
8915 this.domNode.style.display = "block";
8916 this.layout();
8917 this.bgIframe = new dijit.BackgroundIframe(this.domNode);
8918 },
8919
8920 hide: function(){
8921 // summary:
8922 // Hides the dialog underlay
8923 this.bgIframe.destroy();
8924 this.domNode.style.display = "none";
8925 },
8926
8927 uninitialize: function(){
8928 if(this.bgIframe){
8929 this.bgIframe.destroy();
8930 }
8931 this.inherited(arguments);
8932 }
8933 }
8934 );
8935
8936 }
8937
8938 if(!dojo._hasResource["dojo.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8939 dojo._hasResource["dojo.html"] = true;
8940 dojo.provide("dojo.html");
8941
8942 // the parser might be needed..
8943
8944
8945 (function(){ // private scope, sort of a namespace
8946
8947 // idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes
8948 var idCounter = 0,
8949 d = dojo;
8950
8951 dojo.html._secureForInnerHtml = function(/*String*/ cont){
8952 // summary:
8953 // removes !DOCTYPE and title elements from the html string.
8954 //
8955 // khtml is picky about dom faults, you can't attach a style or <title> node as child of body
8956 // must go into head, so we need to cut out those tags
8957 // cont:
8958 // An html string for insertion into the dom
8959 //
8960 return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String
8961 };
8962
8963 /*====
8964 dojo.html._emptyNode = function(node){
8965 // summary:
8966 // removes all child nodes from the given node
8967 // node: DOMNode
8968 // the parent element
8969 };
8970 =====*/
8971 dojo.html._emptyNode = dojo.empty;
8972
8973 dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){
8974 // summary:
8975 // inserts the given content into the given node
8976 // node:
8977 // the parent element
8978 // content:
8979 // the content to be set on the parent element.
8980 // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
8981
8982 // always empty
8983 d.empty(node);
8984
8985 if(cont) {
8986 if(typeof cont == "string") {
8987 cont = d._toDom(cont, node.ownerDocument);
8988 }
8989 if(!cont.nodeType && d.isArrayLike(cont)) {
8990 // handle as enumerable, but it may shrink as we enumerate it
8991 for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0) {
8992 d.place( cont[i], node, "last");
8993 }
8994 } else {
8995 // pass nodes, documentFragments and unknowns through to dojo.place
8996 d.place(cont, node, "last");
8997 }
8998 }
8999
9000 // return DomNode
9001 return node;
9002 };
9003
9004 // we wrap up the content-setting operation in a object
9005 dojo.declare("dojo.html._ContentSetter", null,
9006 {
9007 // node: DomNode|String
9008 // An node which will be the parent element that we set content into
9009 node: "",
9010
9011 // content: String|DomNode|DomNode[]
9012 // The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes
9013 content: "",
9014
9015 // id: String?
9016 // Usually only used internally, and auto-generated with each instance
9017 id: "",
9018
9019 // cleanContent: Boolean
9020 // Should the content be treated as a full html document,
9021 // and the real content stripped of <html>, <body> wrapper before injection
9022 cleanContent: false,
9023
9024 // extractContent: Boolean
9025 // Should the content be treated as a full html document, and the real content stripped of <html>, <body> wrapper before injection
9026 extractContent: false,
9027
9028 // parseContent: Boolean
9029 // Should the node by passed to the parser after the new content is set
9030 parseContent: false,
9031
9032 // lifecyle methods
9033 constructor: function(/* Object */params, /* String|DomNode */node){
9034 // summary:
9035 // Provides a configurable, extensible object to wrap the setting on content on a node
9036 // call the set() method to actually set the content..
9037
9038 // the original params are mixed directly into the instance "this"
9039 dojo.mixin(this, params || {});
9040
9041 // give precedence to params.node vs. the node argument
9042 // and ensure its a node, not an id string
9043 node = this.node = dojo.byId( this.node || node );
9044
9045 if(!this.id){
9046 this.id = [
9047 "Setter",
9048 (node) ? node.id || node.tagName : "",
9049 idCounter++
9050 ].join("_");
9051 }
9052 },
9053 set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){
9054 // summary:
9055 // front-end to the set-content sequence
9056 // cont:
9057 // An html string, node or enumerable list of nodes for insertion into the dom
9058 // If not provided, the object's content property will be used
9059 if(undefined !== cont){
9060 this.content = cont;
9061 }
9062 // in the re-use scenario, set needs to be able to mixin new configuration
9063 if(params){
9064 this._mixin(params);
9065 }
9066
9067 this.onBegin();
9068 this.setContent();
9069 this.onEnd();
9070
9071 return this.node;
9072 },
9073 setContent: function(){
9074 // summary:
9075 // sets the content on the node
9076
9077 var node = this.node;
9078 if(!node) {
9079 // can't proceed
9080 throw new Error(this.declaredClass + ": setContent given no node");
9081 }
9082 try{
9083 node = dojo.html._setNodeContent(node, this.content);
9084 }catch(e){
9085 // check if a domfault occurs when we are appending this.errorMessage
9086 // like for instance if domNode is a UL and we try append a DIV
9087
9088 // FIXME: need to allow the user to provide a content error message string
9089 var errMess = this.onContentError(e);
9090 try{
9091 node.innerHTML = errMess;
9092 }catch(e){
9093 console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e);
9094 }
9095 }
9096 // always put back the node for the next method
9097 this.node = node; // DomNode
9098 },
9099
9100 empty: function() {
9101 // summary
9102 // cleanly empty out existing content
9103
9104 // destroy any widgets from a previous run
9105 // NOTE: if you dont want this you'll need to empty
9106 // the parseResults array property yourself to avoid bad things happenning
9107 if(this.parseResults && this.parseResults.length) {
9108 dojo.forEach(this.parseResults, function(w) {
9109 if(w.destroy){
9110 w.destroy();
9111 }
9112 });
9113 delete this.parseResults;
9114 }
9115 // this is fast, but if you know its already empty or safe, you could
9116 // override empty to skip this step
9117 dojo.html._emptyNode(this.node);
9118 },
9119
9120 onBegin: function(){
9121 // summary
9122 // Called after instantiation, but before set();
9123 // It allows modification of any of the object properties
9124 // - including the node and content provided - before the set operation actually takes place
9125 // This default implementation checks for cleanContent and extractContent flags to
9126 // optionally pre-process html string content
9127 var cont = this.content;
9128
9129 if(dojo.isString(cont)){
9130 if(this.cleanContent){
9131 cont = dojo.html._secureForInnerHtml(cont);
9132 }
9133
9134 if(this.extractContent){
9135 var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
9136 if(match){ cont = match[1]; }
9137 }
9138 }
9139
9140 // clean out the node and any cruft associated with it - like widgets
9141 this.empty();
9142
9143 this.content = cont;
9144 return this.node; /* DomNode */
9145 },
9146
9147 onEnd: function(){
9148 // summary
9149 // Called after set(), when the new content has been pushed into the node
9150 // It provides an opportunity for post-processing before handing back the node to the caller
9151 // This default implementation checks a parseContent flag to optionally run the dojo parser over the new content
9152 if(this.parseContent){
9153 // populates this.parseResults if you need those..
9154 this._parse();
9155 }
9156 return this.node; /* DomNode */
9157 },
9158
9159 tearDown: function(){
9160 // summary
9161 // manually reset the Setter instance if its being re-used for example for another set()
9162 // description
9163 // tearDown() is not called automatically.
9164 // In normal use, the Setter instance properties are simply allowed to fall out of scope
9165 // but the tearDown method can be called to explicitly reset this instance.
9166 delete this.parseResults;
9167 delete this.node;
9168 delete this.content;
9169 },
9170
9171 onContentError: function(err){
9172 return "Error occured setting content: " + err;
9173 },
9174
9175 _mixin: function(params){
9176 // mix properties/methods into the instance
9177 // TODO: the intention with tearDown is to put the Setter's state
9178 // back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params)
9179 // so we could do something here to move the original properties aside for later restoration
9180 var empty = {}, key;
9181 for(key in params){
9182 if(key in empty){ continue; }
9183 // TODO: here's our opportunity to mask the properties we dont consider configurable/overridable
9184 // .. but history shows we'll almost always guess wrong
9185 this[key] = params[key];
9186 }
9187 },
9188 _parse: function(){
9189 // summary:
9190 // runs the dojo parser over the node contents, storing any results in this.parseResults
9191 // Any errors resulting from parsing are passed to _onError for handling
9192
9193 var rootNode = this.node;
9194 try{
9195 // store the results (widgets, whatever) for potential retrieval
9196 this.parseResults = dojo.parser.parse({
9197 rootNode: rootNode,
9198 dir: this.dir,
9199 lang: this.lang
9200 });
9201 }catch(e){
9202 this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id);
9203 }
9204 },
9205
9206 _onError: function(type, err, consoleText){
9207 // summary:
9208 // shows user the string that is returned by on[type]Error
9209 // overide/implement on[type]Error and return your own string to customize
9210 var errText = this['on' + type + 'Error'].call(this, err);
9211 if(consoleText){
9212 console.error(consoleText, err);
9213 }else if(errText){ // a empty string won't change current content
9214 dojo.html._setNodeContent(this.node, errText, true);
9215 }
9216 }
9217 }); // end dojo.declare()
9218
9219 dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){
9220 // summary:
9221 // inserts (replaces) the given content into the given node. dojo.place(cont, node, "only")
9222 // may be a better choice for simple HTML insertion.
9223 // description:
9224 // Unless you need to use the params capabilities of this method, you should use
9225 // dojo.place(cont, node, "only"). dojo.place() has more robust support for injecting
9226 // an HTML string into the DOM, but it only handles inserting an HTML string as DOM
9227 // elements, or inserting a DOM node. dojo.place does not handle NodeList insertions
9228 // or the other capabilities as defined by the params object for this method.
9229 // node:
9230 // the parent element that will receive the content
9231 // cont:
9232 // the content to be set on the parent element.
9233 // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
9234 // params:
9235 // Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter
9236 // example:
9237 // A safe string/node/nodelist content replacement/injection with hooks for extension
9238 // Example Usage:
9239 // dojo.html.set(node, "some string");
9240 // dojo.html.set(node, contentNode, {options});
9241 // dojo.html.set(node, myNode.childNodes, {options});
9242 if(undefined == cont){
9243 console.warn("dojo.html.set: no cont argument provided, using empty string");
9244 cont = "";
9245 }
9246 if(!params){
9247 // simple and fast
9248 return dojo.html._setNodeContent(node, cont, true);
9249 }else{
9250 // more options but slower
9251 // note the arguments are reversed in order, to match the convention for instantiation via the parser
9252 var op = new dojo.html._ContentSetter(dojo.mixin(
9253 params,
9254 { content: cont, node: node }
9255 ));
9256 return op.set();
9257 }
9258 };
9259 })();
9260
9261 }
9262
9263 if(!dojo._hasResource["dijit.layout.ContentPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9264 dojo._hasResource["dijit.layout.ContentPane"] = true;
9265 dojo.provide("dijit.layout.ContentPane");
9266
9267
9268
9269 // for dijit.layout.marginBox2contentBox()
9270
9271
9272
9273
9274
9275
9276 dojo.declare(
9277 "dijit.layout.ContentPane", dijit._Widget,
9278 {
9279 // summary:
9280 // A widget that acts as a container for mixed HTML and widgets, and includes an Ajax interface
9281 // description:
9282 // A widget that can be used as a stand alone widget
9283 // or as a base class for other widgets.
9284 //
9285 // Handles replacement of document fragment using either external uri or javascript
9286 // generated markup or DOM content, instantiating widgets within that content.
9287 // Don't confuse it with an iframe, it only needs/wants document fragments.
9288 // It's useful as a child of LayoutContainer, SplitContainer, or TabContainer.
9289 // But note that those classes can contain any widget as a child.
9290 // example:
9291 // Some quick samples:
9292 // To change the innerHTML use .set('content', '<b>new content</b>')
9293 //
9294 // Or you can send it a NodeList, .set('content', dojo.query('div [class=selected]', userSelection))
9295 // please note that the nodes in NodeList will copied, not moved
9296 //
9297 // To do a ajax update use .set('href', url)
9298
9299 // href: String
9300 // The href of the content that displays now.
9301 // Set this at construction if you want to load data externally when the
9302 // pane is shown. (Set preload=true to load it immediately.)
9303 // Changing href after creation doesn't have any effect; Use set('href', ...);
9304 href: "",
9305
9306 /*=====
9307 // content: String || DomNode || NodeList || dijit._Widget
9308 // The innerHTML of the ContentPane.
9309 // Note that the initialization parameter / argument to attr("content", ...)
9310 // can be a String, DomNode, Nodelist, or _Widget.
9311 content: "",
9312 =====*/
9313
9314 // extractContent: Boolean
9315 // Extract visible content from inside of <body> .... </body>.
9316 // I.e., strip <html> and <head> (and it's contents) from the href
9317 extractContent: false,
9318
9319 // parseOnLoad: Boolean
9320 // Parse content and create the widgets, if any.
9321 parseOnLoad: true,
9322
9323 // preventCache: Boolean
9324 // Prevent caching of data from href's by appending a timestamp to the href.
9325 preventCache: false,
9326
9327 // preload: Boolean
9328 // Force load of data on initialization even if pane is hidden.
9329 preload: false,
9330
9331 // refreshOnShow: Boolean
9332 // Refresh (re-download) content when pane goes from hidden to shown
9333 refreshOnShow: false,
9334
9335 // loadingMessage: String
9336 // Message that shows while downloading
9337 loadingMessage: "<span class='dijitContentPaneLoading'>${loadingState}</span>",
9338
9339 // errorMessage: String
9340 // Message that shows if an error occurs
9341 errorMessage: "<span class='dijitContentPaneError'>${errorState}</span>",
9342
9343 // isLoaded: [readonly] Boolean
9344 // True if the ContentPane has data in it, either specified
9345 // during initialization (via href or inline content), or set
9346 // via attr('content', ...) / attr('href', ...)
9347 //
9348 // False if it doesn't have any content, or if ContentPane is
9349 // still in the process of downloading href.
9350 isLoaded: false,
9351
9352 baseClass: "dijitContentPane",
9353
9354 // doLayout: Boolean
9355 // - false - don't adjust size of children
9356 // - true - if there is a single visible child widget, set it's size to
9357 // however big the ContentPane is
9358 doLayout: true,
9359
9360 // ioArgs: Object
9361 // Parameters to pass to xhrGet() request, for example:
9362 // | <div dojoType="dijit.layout.ContentPane" href="./bar" ioArgs="{timeout: 500}">
9363 ioArgs: {},
9364
9365 // isContainer: [protected] Boolean
9366 // Indicates that this widget acts as a "parent" to the descendant widgets.
9367 // When the parent is started it will call startup() on the child widgets.
9368 // See also `isLayoutContainer`.
9369 isContainer: true,
9370
9371 // isLayoutContainer: [protected] Boolean
9372 // Indicates that this widget will call resize() on it's child widgets
9373 // when they become visible.
9374 isLayoutContainer: true,
9375
9376 // onLoadDeferred: [readonly] dojo.Deferred
9377 // This is the `dojo.Deferred` returned by attr('href', ...) and refresh().
9378 // Calling onLoadDeferred.addCallback() or addErrback() registers your
9379 // callback to be called only once, when the prior attr('href', ...) call or
9380 // the initial href parameter to the constructor finishes loading.
9381 //
9382 // This is different than an onLoad() handler which gets called any time any href is loaded.
9383 onLoadDeferred: null,
9384
9385 // Override _Widget's attributeMap because we don't want the title attribute (used to specify
9386 // tab labels) to be copied to ContentPane.domNode... otherwise a tooltip shows up over the
9387 // entire pane.
9388 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
9389 title: []
9390 }),
9391
9392 postMixInProperties: function(){
9393 this.inherited(arguments);
9394 var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang);
9395 this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages);
9396 this.errorMessage = dojo.string.substitute(this.errorMessage, messages);
9397
9398 // Detect if we were initialized with data
9399 if(!this.href && this.srcNodeRef && this.srcNodeRef.innerHTML){
9400 this.isLoaded = true;
9401 }
9402 },
9403
9404 buildRendering: function(){
9405 // Overrides Widget.buildRendering().
9406 // Since we have no template we need to set this.containerNode ourselves.
9407 // For subclasses of ContentPane do have a template, does nothing.
9408 this.inherited(arguments);
9409 if(!this.containerNode){
9410 // make getDescendants() work
9411 this.containerNode = this.domNode;
9412 }
9413 },
9414
9415 postCreate: function(){
9416 // remove the title attribute so it doesn't show up when hovering
9417 // over a node
9418 this.domNode.title = "";
9419
9420 if(!dojo.attr(this.domNode,"role")){
9421 dijit.setWaiRole(this.domNode, "group");
9422 }
9423
9424 dojo.addClass(this.domNode, this.baseClass);
9425 },
9426
9427 startup: function(){
9428 // summary:
9429 // See `dijit.layout._LayoutWidget.startup` for description.
9430 // Although ContentPane doesn't extend _LayoutWidget, it does implement
9431 // the same API.
9432 if(this._started){ return; }
9433
9434 var parent = dijit._Contained.prototype.getParent.call(this);
9435 this._childOfLayoutWidget = parent && parent.isLayoutContainer;
9436
9437 // I need to call resize() on my child/children (when I become visible), unless
9438 // I'm the child of a layout widget in which case my parent will call resize() on me and I'll do it then.
9439 this._needLayout = !this._childOfLayoutWidget;
9440
9441 if(this.isLoaded){
9442 dojo.forEach(this.getChildren(), function(child){
9443 child.startup();
9444 });
9445 }
9446
9447 if(this._isShown() || this.preload){
9448 this._onShow();
9449 }
9450
9451 this.inherited(arguments);
9452 },
9453
9454 _checkIfSingleChild: function(){
9455 // summary:
9456 // Test if we have exactly one visible widget as a child,
9457 // and if so assume that we are a container for that widget,
9458 // and should propogate startup() and resize() calls to it.
9459 // Skips over things like data stores since they aren't visible.
9460
9461 var childNodes = dojo.query("> *", this.containerNode).filter(function(node){
9462 return node.tagName !== "SCRIPT"; // or a regexp for hidden elements like script|area|map|etc..
9463 }),
9464 childWidgetNodes = childNodes.filter(function(node){
9465 return dojo.hasAttr(node, "dojoType") || dojo.hasAttr(node, "widgetId");
9466 }),
9467 candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){
9468 return widget && widget.domNode && widget.resize;
9469 });
9470
9471 if(
9472 // all child nodes are widgets
9473 childNodes.length == childWidgetNodes.length &&
9474
9475 // all but one are invisible (like dojo.data)
9476 candidateWidgets.length == 1
9477 ){
9478 this._singleChild = candidateWidgets[0];
9479 }else{
9480 delete this._singleChild;
9481 }
9482
9483 // So we can set overflow: hidden to avoid a safari bug w/scrollbars showing up (#9449)
9484 dojo.toggleClass(this.containerNode, this.baseClass + "SingleChild", !!this._singleChild);
9485 },
9486
9487 setHref: function(/*String|Uri*/ href){
9488 // summary:
9489 // Deprecated. Use set('href', ...) instead.
9490 dojo.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.", "", "2.0");
9491 return this.set("href", href);
9492 },
9493 _setHrefAttr: function(/*String|Uri*/ href){
9494 // summary:
9495 // Hook so attr("href", ...) works.
9496 // description:
9497 // Reset the (external defined) content of this pane and replace with new url
9498 // Note: It delays the download until widget is shown if preload is false.
9499 // href:
9500 // url to the page you want to get, must be within the same domain as your mainpage
9501
9502 // Cancel any in-flight requests (an attr('href') will cancel any in-flight attr('href', ...))
9503 this.cancel();
9504
9505 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
9506
9507 this.href = href;
9508
9509 // _setHrefAttr() is called during creation and by the user, after creation.
9510 // only in the second case do we actually load the URL; otherwise it's done in startup()
9511 if(this._created && (this.preload || this._isShown())){
9512 this._load();
9513 }else{
9514 // Set flag to indicate that href needs to be loaded the next time the
9515 // ContentPane is made visible
9516 this._hrefChanged = true;
9517 }
9518
9519 return this.onLoadDeferred; // dojo.Deferred
9520 },
9521
9522 setContent: function(/*String|DomNode|Nodelist*/data){
9523 // summary:
9524 // Deprecated. Use set('content', ...) instead.
9525 dojo.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use set('content', ...) instead.", "", "2.0");
9526 this.set("content", data);
9527 },
9528 _setContentAttr: function(/*String|DomNode|Nodelist*/data){
9529 // summary:
9530 // Hook to make attr("content", ...) work.
9531 // Replaces old content with data content, include style classes from old content
9532 // data:
9533 // the new Content may be String, DomNode or NodeList
9534 //
9535 // if data is a NodeList (or an array of nodes) nodes are copied
9536 // so you can import nodes from another document implicitly
9537
9538 // clear href so we can't run refresh and clear content
9539 // refresh should only work if we downloaded the content
9540 this.href = "";
9541
9542 // Cancel any in-flight requests (an attr('content') will cancel any in-flight attr('href', ...))
9543 this.cancel();
9544
9545 // Even though user is just setting content directly, still need to define an onLoadDeferred
9546 // because the _onLoadHandler() handler is still getting called from setContent()
9547 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
9548
9549 this._setContent(data || "");
9550
9551 this._isDownloaded = false; // mark that content is from a attr('content') not an attr('href')
9552
9553 return this.onLoadDeferred; // dojo.Deferred
9554 },
9555 _getContentAttr: function(){
9556 // summary:
9557 // Hook to make attr("content") work
9558 return this.containerNode.innerHTML;
9559 },
9560
9561 cancel: function(){
9562 // summary:
9563 // Cancels an in-flight download of content
9564 if(this._xhrDfd && (this._xhrDfd.fired == -1)){
9565 this._xhrDfd.cancel();
9566 }
9567 delete this._xhrDfd; // garbage collect
9568
9569 this.onLoadDeferred = null;
9570 },
9571
9572 uninitialize: function(){
9573 if(this._beingDestroyed){
9574 this.cancel();
9575 }
9576 this.inherited(arguments);
9577 },
9578
9579 destroyRecursive: function(/*Boolean*/ preserveDom){
9580 // summary:
9581 // Destroy the ContentPane and its contents
9582
9583 // if we have multiple controllers destroying us, bail after the first
9584 if(this._beingDestroyed){
9585 return;
9586 }
9587 this.inherited(arguments);
9588 },
9589
9590 resize: function(changeSize, resultSize){
9591 // summary:
9592 // See `dijit.layout._LayoutWidget.resize` for description.
9593 // Although ContentPane doesn't extend _LayoutWidget, it does implement
9594 // the same API.
9595
9596 // For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is
9597 // never called, so resize() is our trigger to do the initial href download.
9598 if(!this._wasShown){
9599 this._onShow();
9600 }
9601
9602 this._resizeCalled = true;
9603
9604 // Set margin box size, unless it wasn't specified, in which case use current size.
9605 if(changeSize){
9606 dojo.marginBox(this.domNode, changeSize);
9607 }
9608
9609 // Compute content box size of containerNode in case we [later] need to size our single child.
9610 var cn = this.containerNode;
9611 if(cn === this.domNode){
9612 // If changeSize or resultSize was passed to this method and this.containerNode ==
9613 // this.domNode then we can compute the content-box size without querying the node,
9614 // which is more reliable (similar to LayoutWidget.resize) (see for example #9449).
9615 var mb = resultSize || {};
9616 dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize
9617 if(!("h" in mb) || !("w" in mb)){
9618 mb = dojo.mixin(dojo.marginBox(cn), mb); // just use dojo.marginBox() to fill in missing values
9619 }
9620 this._contentBox = dijit.layout.marginBox2contentBox(cn, mb);
9621 }else{
9622 this._contentBox = dojo.contentBox(cn);
9623 }
9624
9625 // Make my children layout, or size my single child widget
9626 this._layoutChildren();
9627 },
9628
9629 _isShown: function(){
9630 // summary:
9631 // Returns true if the content is currently shown.
9632 // description:
9633 // If I am a child of a layout widget then it actually returns true if I've ever been visible,
9634 // not whether I'm currently visible, since that's much faster than tracing up the DOM/widget
9635 // tree every call, and at least solves the performance problem on page load by deferring loading
9636 // hidden ContentPanes until they are first shown
9637
9638 if(this._childOfLayoutWidget){
9639 // If we are TitlePane, etc - we return that only *IF* we've been resized
9640 if(this._resizeCalled && "open" in this){
9641 return this.open;
9642 }
9643 return this._resizeCalled;
9644 }else if("open" in this){
9645 return this.open; // for TitlePane, etc.
9646 }else{
9647 // TODO: with _childOfLayoutWidget check maybe this branch no longer necessary?
9648 var node = this.domNode;
9649 return (node.style.display != 'none') && (node.style.visibility != 'hidden') && !dojo.hasClass(node, "dijitHidden");
9650 }
9651 },
9652
9653 _onShow: function(){
9654 // summary:
9655 // Called when the ContentPane is made visible
9656 // description:
9657 // For a plain ContentPane, this is called on initialization, from startup().
9658 // If the ContentPane is a hidden pane of a TabContainer etc., then it's
9659 // called whenever the pane is made visible.
9660 //
9661 // Does necessary processing, including href download and layout/resize of
9662 // child widget(s)
9663
9664 if(this.href){
9665 if(!this._xhrDfd && // if there's an href that isn't already being loaded
9666 (!this.isLoaded || this._hrefChanged || this.refreshOnShow)
9667 ){
9668 this.refresh();
9669 }
9670 }else{
9671 // If we are the child of a layout widget then the layout widget will call resize() on
9672 // us, and then we will size our child/children. Otherwise, we need to do it now.
9673 if(!this._childOfLayoutWidget && this._needLayout){
9674 // If a layout has been scheduled for when we become visible, do it now
9675 this._layoutChildren();
9676 }
9677 }
9678
9679 this.inherited(arguments);
9680
9681 // Need to keep track of whether ContentPane has been shown (which is different than
9682 // whether or not it's currently visible).
9683 this._wasShown = true;
9684 },
9685
9686 refresh: function(){
9687 // summary:
9688 // [Re]download contents of href and display
9689 // description:
9690 // 1. cancels any currently in-flight requests
9691 // 2. posts "loading..." message
9692 // 3. sends XHR to download new data
9693
9694 // Cancel possible prior in-flight request
9695 this.cancel();
9696
9697 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
9698 this._load();
9699 return this.onLoadDeferred;
9700 },
9701
9702 _load: function(){
9703 // summary:
9704 // Load/reload the href specified in this.href
9705
9706 // display loading message
9707 this._setContent(this.onDownloadStart(), true);
9708
9709 var self = this;
9710 var getArgs = {
9711 preventCache: (this.preventCache || this.refreshOnShow),
9712 url: this.href,
9713 handleAs: "text"
9714 };
9715 if(dojo.isObject(this.ioArgs)){
9716 dojo.mixin(getArgs, this.ioArgs);
9717 }
9718
9719 var hand = (this._xhrDfd = (this.ioMethod || dojo.xhrGet)(getArgs));
9720
9721 hand.addCallback(function(html){
9722 try{
9723 self._isDownloaded = true;
9724 self._setContent(html, false);
9725 self.onDownloadEnd();
9726 }catch(err){
9727 self._onError('Content', err); // onContentError
9728 }
9729 delete self._xhrDfd;
9730 return html;
9731 });
9732
9733 hand.addErrback(function(err){
9734 if(!hand.canceled){
9735 // show error message in the pane
9736 self._onError('Download', err); // onDownloadError
9737 }
9738 delete self._xhrDfd;
9739 return err;
9740 });
9741
9742 // Remove flag saying that a load is needed
9743 delete this._hrefChanged;
9744 },
9745
9746 _onLoadHandler: function(data){
9747 // summary:
9748 // This is called whenever new content is being loaded
9749 this.isLoaded = true;
9750 try{
9751 this.onLoadDeferred.callback(data);
9752 this.onLoad(data);
9753 }catch(e){
9754 console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message);
9755 }
9756 },
9757
9758 _onUnloadHandler: function(){
9759 // summary:
9760 // This is called whenever the content is being unloaded
9761 this.isLoaded = false;
9762 try{
9763 this.onUnload();
9764 }catch(e){
9765 console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message);
9766 }
9767 },
9768
9769 destroyDescendants: function(){
9770 // summary:
9771 // Destroy all the widgets inside the ContentPane and empty containerNode
9772
9773 // Make sure we call onUnload (but only when the ContentPane has real content)
9774 if(this.isLoaded){
9775 this._onUnloadHandler();
9776 }
9777
9778 // Even if this.isLoaded == false there might still be a "Loading..." message
9779 // to erase, so continue...
9780
9781 // For historical reasons we need to delete all widgets under this.containerNode,
9782 // even ones that the user has created manually.
9783 var setter = this._contentSetter;
9784 dojo.forEach(this.getChildren(), function(widget){
9785 if(widget.destroyRecursive){
9786 widget.destroyRecursive();
9787 }
9788 });
9789 if(setter){
9790 // Most of the widgets in setter.parseResults have already been destroyed, but
9791 // things like Menu that have been moved to <body> haven't yet
9792 dojo.forEach(setter.parseResults, function(widget){
9793 if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == dojo.body()){
9794 widget.destroyRecursive();
9795 }
9796 });
9797 delete setter.parseResults;
9798 }
9799
9800 // And then clear away all the DOM nodes
9801 dojo.html._emptyNode(this.containerNode);
9802
9803 // Delete any state information we have about current contents
9804 delete this._singleChild;
9805 },
9806
9807 _setContent: function(cont, isFakeContent){
9808 // summary:
9809 // Insert the content into the container node
9810
9811 // first get rid of child widgets
9812 this.destroyDescendants();
9813
9814 // dojo.html.set will take care of the rest of the details
9815 // we provide an override for the error handling to ensure the widget gets the errors
9816 // configure the setter instance with only the relevant widget instance properties
9817 // NOTE: unless we hook into attr, or provide property setters for each property,
9818 // we need to re-configure the ContentSetter with each use
9819 var setter = this._contentSetter;
9820 if(! (setter && setter instanceof dojo.html._ContentSetter)){
9821 setter = this._contentSetter = new dojo.html._ContentSetter({
9822 node: this.containerNode,
9823 _onError: dojo.hitch(this, this._onError),
9824 onContentError: dojo.hitch(this, function(e){
9825 // fires if a domfault occurs when we are appending this.errorMessage
9826 // like for instance if domNode is a UL and we try append a DIV
9827 var errMess = this.onContentError(e);
9828 try{
9829 this.containerNode.innerHTML = errMess;
9830 }catch(e){
9831 console.error('Fatal '+this.id+' could not change content due to '+e.message, e);
9832 }
9833 })/*,
9834 _onError */
9835 });
9836 };
9837
9838 var setterParams = dojo.mixin({
9839 cleanContent: this.cleanContent,
9840 extractContent: this.extractContent,
9841 parseContent: this.parseOnLoad,
9842 dir: this.dir,
9843 lang: this.lang
9844 }, this._contentSetterParams || {});
9845
9846 dojo.mixin(setter, setterParams);
9847
9848 setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont );
9849
9850 // setter params must be pulled afresh from the ContentPane each time
9851 delete this._contentSetterParams;
9852
9853 if(!isFakeContent){
9854 // Startup each top level child widget (and they will start their children, recursively)
9855 dojo.forEach(this.getChildren(), function(child){
9856 // The parser has already called startup on all widgets *without* a getParent() method
9857 if(!this.parseOnLoad || child.getParent){
9858 child.startup();
9859 }
9860 }, this);
9861
9862 // Call resize() on each of my child layout widgets,
9863 // or resize() on my single child layout widget...
9864 // either now (if I'm currently visible)
9865 // or when I become visible
9866 this._scheduleLayout();
9867
9868 this._onLoadHandler(cont);
9869 }
9870 },
9871
9872 _onError: function(type, err, consoleText){
9873 this.onLoadDeferred.errback(err);
9874
9875 // shows user the string that is returned by on[type]Error
9876 // overide on[type]Error and return your own string to customize
9877 var errText = this['on' + type + 'Error'].call(this, err);
9878 if(consoleText){
9879 console.error(consoleText, err);
9880 }else if(errText){// a empty string won't change current content
9881 this._setContent(errText, true);
9882 }
9883 },
9884
9885 _scheduleLayout: function(){
9886 // summary:
9887 // Call resize() on each of my child layout widgets, either now
9888 // (if I'm currently visible) or when I become visible
9889 if(this._isShown()){
9890 this._layoutChildren();
9891 }else{
9892 this._needLayout = true;
9893 }
9894 },
9895
9896 _layoutChildren: function(){
9897 // summary:
9898 // Since I am a Container widget, each of my children expects me to
9899 // call resize() or layout() on them.
9900 // description:
9901 // Should be called on initialization and also whenever we get new content
9902 // (from an href, or from attr('content', ...))... but deferred until
9903 // the ContentPane is visible
9904
9905 if(this.doLayout){
9906 this._checkIfSingleChild();
9907 }
9908
9909 if(this._singleChild && this._singleChild.resize){
9910 var cb = this._contentBox || dojo.contentBox(this.containerNode);
9911
9912 // note: if widget has padding this._contentBox will have l and t set,
9913 // but don't pass them to resize() or it will doubly-offset the child
9914 this._singleChild.resize({w: cb.w, h: cb.h});
9915 }else{
9916 // All my child widgets are independently sized (rather than matching my size),
9917 // but I still need to call resize() on each child to make it layout.
9918 dojo.forEach(this.getChildren(), function(widget){
9919 if(widget.resize){
9920 widget.resize();
9921 }
9922 });
9923 }
9924 delete this._needLayout;
9925 },
9926
9927 // EVENT's, should be overide-able
9928 onLoad: function(data){
9929 // summary:
9930 // Event hook, is called after everything is loaded and widgetified
9931 // tags:
9932 // callback
9933 },
9934
9935 onUnload: function(){
9936 // summary:
9937 // Event hook, is called before old content is cleared
9938 // tags:
9939 // callback
9940 },
9941
9942 onDownloadStart: function(){
9943 // summary:
9944 // Called before download starts.
9945 // description:
9946 // The string returned by this function will be the html
9947 // that tells the user we are loading something.
9948 // Override with your own function if you want to change text.
9949 // tags:
9950 // extension
9951 return this.loadingMessage;
9952 },
9953
9954 onContentError: function(/*Error*/ error){
9955 // summary:
9956 // Called on DOM faults, require faults etc. in content.
9957 //
9958 // In order to display an error message in the pane, return
9959 // the error message from this method, as an HTML string.
9960 //
9961 // By default (if this method is not overriden), it returns
9962 // nothing, so the error message is just printed to the console.
9963 // tags:
9964 // extension
9965 },
9966
9967 onDownloadError: function(/*Error*/ error){
9968 // summary:
9969 // Called when download error occurs.
9970 //
9971 // In order to display an error message in the pane, return
9972 // the error message from this method, as an HTML string.
9973 //
9974 // Default behavior (if this method is not overriden) is to display
9975 // the error message inside the pane.
9976 // tags:
9977 // extension
9978 return this.errorMessage;
9979 },
9980
9981 onDownloadEnd: function(){
9982 // summary:
9983 // Called when download is finished.
9984 // tags:
9985 // callback
9986 }
9987 });
9988
9989 }
9990
9991 if(!dojo._hasResource["dijit.TooltipDialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9992 dojo._hasResource["dijit.TooltipDialog"] = true;
9993 dojo.provide("dijit.TooltipDialog");
9994
9995
9996
9997
9998
9999
10000 dojo.declare(
10001 "dijit.TooltipDialog",
10002 [dijit.layout.ContentPane, dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin],
10003 {
10004 // summary:
10005 // Pops up a dialog that appears like a Tooltip
10006
10007 // title: String
10008 // Description of tooltip dialog (required for a11y)
10009 title: "",
10010
10011 // doLayout: [protected] Boolean
10012 // Don't change this parameter from the default value.
10013 // This ContentPane parameter doesn't make sense for TooltipDialog, since TooltipDialog
10014 // is never a child of a layout container, nor can you specify the size of
10015 // TooltipDialog in order to control the size of an inner widget.
10016 doLayout: false,
10017
10018 // autofocus: Boolean
10019 // A Toggle to modify the default focus behavior of a Dialog, which
10020 // is to focus on the first dialog element after opening the dialog.
10021 // False will disable autofocusing. Default: true
10022 autofocus: true,
10023
10024 // baseClass: [protected] String
10025 // The root className to use for the various states of this widget
10026 baseClass: "dijitTooltipDialog",
10027
10028 // _firstFocusItem: [private] [readonly] DomNode
10029 // The pointer to the first focusable node in the dialog.
10030 // Set by `dijit._DialogMixin._getFocusItems`.
10031 _firstFocusItem: null,
10032
10033 // _lastFocusItem: [private] [readonly] DomNode
10034 // The pointer to which node has focus prior to our dialog.
10035 // Set by `dijit._DialogMixin._getFocusItems`.
10036 _lastFocusItem: null,
10037
10038 templateString: dojo.cache("dijit", "templates/TooltipDialog.html", "<div waiRole=\"presentation\">\n\t<div class=\"dijitTooltipContainer\" waiRole=\"presentation\">\n\t\t<div class =\"dijitTooltipContents dijitTooltipFocusNode\" dojoAttachPoint=\"containerNode\" tabindex=\"-1\" waiRole=\"dialog\"></div>\n\t</div>\n\t<div class=\"dijitTooltipConnector\" waiRole=\"presentation\"></div>\n</div>\n"),
10039
10040 postCreate: function(){
10041 this.inherited(arguments);
10042 this.connect(this.containerNode, "onkeypress", "_onKey");
10043 this.containerNode.title = this.title;
10044 },
10045
10046 orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ corner){
10047 // summary:
10048 // Configure widget to be displayed in given position relative to the button.
10049 // This is called from the dijit.popup code, and should not be called
10050 // directly.
10051 // tags:
10052 // protected
10053 var c = this._currentOrientClass;
10054 if(c){
10055 dojo.removeClass(this.domNode, c);
10056 }
10057 c = "dijitTooltipAB"+(corner.charAt(1) == 'L'?"Left":"Right")+" dijitTooltip"+(corner.charAt(0) == 'T' ? "Below" : "Above");
10058 dojo.addClass(this.domNode, c);
10059 this._currentOrientClass = c;
10060 },
10061
10062 onOpen: function(/*Object*/ pos){
10063 // summary:
10064 // Called when dialog is displayed.
10065 // This is called from the dijit.popup code, and should not be called directly.
10066 // tags:
10067 // protected
10068
10069 this.orient(this.domNode,pos.aroundCorner, pos.corner);
10070 this._onShow(); // lazy load trigger
10071
10072 if(this.autofocus){
10073 this._getFocusItems(this.containerNode);
10074 dijit.focus(this._firstFocusItem);
10075 }
10076 },
10077
10078 onClose: function(){
10079 // summary:
10080 // Called when dialog is hidden.
10081 // This is called from the dijit.popup code, and should not be called directly.
10082 // tags:
10083 // protected
10084 this.onHide();
10085 },
10086
10087 _onKey: function(/*Event*/ evt){
10088 // summary:
10089 // Handler for keyboard events
10090 // description:
10091 // Keep keyboard focus in dialog; close dialog on escape key
10092 // tags:
10093 // private
10094
10095 var node = evt.target;
10096 var dk = dojo.keys;
10097 if(evt.charOrCode === dk.TAB){
10098 this._getFocusItems(this.containerNode);
10099 }
10100 var singleFocusItem = (this._firstFocusItem == this._lastFocusItem);
10101 if(evt.charOrCode == dk.ESCAPE){
10102 // Use setTimeout to avoid crash on IE, see #10396.
10103 setTimeout(dojo.hitch(this, "onCancel"), 0);
10104 dojo.stopEvent(evt);
10105 }else if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){
10106 if(!singleFocusItem){
10107 dijit.focus(this._lastFocusItem); // send focus to last item in dialog
10108 }
10109 dojo.stopEvent(evt);
10110 }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){
10111 if(!singleFocusItem){
10112 dijit.focus(this._firstFocusItem); // send focus to first item in dialog
10113 }
10114 dojo.stopEvent(evt);
10115 }else if(evt.charOrCode === dk.TAB){
10116 // we want the browser's default tab handling to move focus
10117 // but we don't want the tab to propagate upwards
10118 evt.stopPropagation();
10119 }
10120 }
10121 }
10122 );
10123
10124 }
10125
10126 if(!dojo._hasResource["dijit.Dialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
10127 dojo._hasResource["dijit.Dialog"] = true;
10128 dojo.provide("dijit.Dialog");
10129
10130
10131
10132
10133
10134
10135
10136
10137
10138
10139
10140
10141
10142
10143
10144 /*=====
10145 dijit._underlay = function(kwArgs){
10146 // summary:
10147 // A shared instance of a `dijit.DialogUnderlay`
10148 //
10149 // description:
10150 // A shared instance of a `dijit.DialogUnderlay` created and
10151 // used by `dijit.Dialog`, though never created until some Dialog
10152 // or subclass thereof is shown.
10153 };
10154 =====*/
10155
10156 dojo.declare(
10157 "dijit._DialogBase",
10158 [dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin, dijit._CssStateMixin],
10159 {
10160 // summary:
10161 // A modal dialog Widget
10162 //
10163 // description:
10164 // Pops up a modal dialog window, blocking access to the screen
10165 // and also graying out the screen Dialog is extended from
10166 // ContentPane so it supports all the same parameters (href, etc.)
10167 //
10168 // example:
10169 // | <div dojoType="dijit.Dialog" href="test.html"></div>
10170 //
10171 // example:
10172 // | var foo = new dijit.Dialog({ title: "test dialog", content: "test content" };
10173 // | dojo.body().appendChild(foo.domNode);
10174 // | foo.startup();
10175
10176 templateString: dojo.cache("dijit", "templates/Dialog.html", "<div class=\"dijitDialog\" tabindex=\"-1\" waiRole=\"dialog\" waiState=\"labelledby-${id}_title\">\n\t<div dojoAttachPoint=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t<span dojoAttachPoint=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"></span>\n\t<span dojoAttachPoint=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" dojoAttachEvent=\"onclick: onCancel\" title=\"${buttonCancel}\">\n\t\t<span dojoAttachPoint=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t</span>\n\t</div>\n\t\t<div dojoAttachPoint=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n"),
10177
10178 baseClass: "dijitDialog",
10179
10180 cssStateNodes: {
10181 closeButtonNode: "dijitDialogCloseIcon"
10182 },
10183
10184 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
10185 title: [
10186 { node: "titleNode", type: "innerHTML" },
10187 { node: "titleBar", type: "attribute" }
10188 ],
10189 "aria-describedby":""
10190 }),
10191
10192 // open: Boolean
10193 // True if Dialog is currently displayed on screen.
10194 open: false,
10195
10196 // duration: Integer
10197 // The time in milliseconds it takes the dialog to fade in and out
10198 duration: dijit.defaultDuration,
10199
10200 // refocus: Boolean
10201 // A Toggle to modify the default focus behavior of a Dialog, which
10202 // is to re-focus the element which had focus before being opened.
10203 // False will disable refocusing. Default: true
10204 refocus: true,
10205
10206 // autofocus: Boolean
10207 // A Toggle to modify the default focus behavior of a Dialog, which
10208 // is to focus on the first dialog element after opening the dialog.
10209 // False will disable autofocusing. Default: true
10210 autofocus: true,
10211
10212 // _firstFocusItem: [private] [readonly] DomNode
10213 // The pointer to the first focusable node in the dialog.
10214 // Set by `dijit._DialogMixin._getFocusItems`.
10215 _firstFocusItem: null,
10216
10217 // _lastFocusItem: [private] [readonly] DomNode
10218 // The pointer to which node has focus prior to our dialog.
10219 // Set by `dijit._DialogMixin._getFocusItems`.
10220 _lastFocusItem: null,
10221
10222 // doLayout: [protected] Boolean
10223 // Don't change this parameter from the default value.
10224 // This ContentPane parameter doesn't make sense for Dialog, since Dialog
10225 // is never a child of a layout container, nor can you specify the size of
10226 // Dialog in order to control the size of an inner widget.
10227 doLayout: false,
10228
10229 // draggable: Boolean
10230 // Toggles the moveable aspect of the Dialog. If true, Dialog
10231 // can be dragged by it's title. If false it will remain centered
10232 // in the viewport.
10233 draggable: true,
10234
10235 //aria-describedby: String
10236 // Allows the user to add an aria-describedby attribute onto the dialog. The value should
10237 // be the id of the container element of text that describes the dialog purpose (usually
10238 // the first text in the dialog).
10239 // <div dojoType="dijit.Dialog" aria-describedby="intro" .....>
10240 // <div id="intro">Introductory text</div>
10241 // <div>rest of dialog contents</div>
10242 // </div>
10243 "aria-describedby":"",
10244
10245 postMixInProperties: function(){
10246 var _nlsResources = dojo.i18n.getLocalization("dijit", "common");
10247 dojo.mixin(this, _nlsResources);
10248 this.inherited(arguments);
10249 },
10250
10251 postCreate: function(){
10252 dojo.style(this.domNode, {
10253 display: "none",
10254 position:"absolute"
10255 });
10256 dojo.body().appendChild(this.domNode);
10257
10258 this.inherited(arguments);
10259
10260 this.connect(this, "onExecute", "hide");
10261 this.connect(this, "onCancel", "hide");
10262 this._modalconnects = [];
10263 },
10264
10265 onLoad: function(){
10266 // summary:
10267 // Called when data has been loaded from an href.
10268 // Unlike most other callbacks, this function can be connected to (via `dojo.connect`)
10269 // but should *not* be overriden.
10270 // tags:
10271 // callback
10272
10273 // when href is specified we need to reposition the dialog after the data is loaded
10274 // and find the focusable elements
10275 this._position();
10276 if(this.autofocus){
10277 this._getFocusItems(this.domNode);
10278 dijit.focus(this._firstFocusItem);
10279 }
10280 this.inherited(arguments);
10281 },
10282
10283 _endDrag: function(e){
10284 // summary:
10285 // Called after dragging the Dialog. Saves the position of the dialog in the viewport.
10286 // tags:
10287 // private
10288 if(e && e.node && e.node === this.domNode){
10289 this._relativePosition = dojo.position(e.node);
10290 }
10291 },
10292
10293 _setup: function(){
10294 // summary:
10295 // Stuff we need to do before showing the Dialog for the first
10296 // time (but we defer it until right beforehand, for
10297 // performance reasons).
10298 // tags:
10299 // private
10300
10301 var node = this.domNode;
10302
10303 if(this.titleBar && this.draggable){
10304 this._moveable = (dojo.isIE == 6) ?
10305 new dojo.dnd.TimedMoveable(node, { handle: this.titleBar }) : // prevent overload, see #5285
10306 new dojo.dnd.Moveable(node, { handle: this.titleBar, timeout: 0 });
10307 dojo.subscribe("/dnd/move/stop",this,"_endDrag");
10308 }else{
10309 dojo.addClass(node,"dijitDialogFixed");
10310 }
10311
10312 this.underlayAttrs = {
10313 dialogId: this.id,
10314 "class": dojo.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" ")
10315 };
10316
10317 this._fadeIn = dojo.fadeIn({
10318 node: node,
10319 duration: this.duration,
10320 beforeBegin: dojo.hitch(this, function(){
10321 var underlay = dijit._underlay;
10322 if(!underlay){
10323 underlay = dijit._underlay = new dijit.DialogUnderlay(this.underlayAttrs);
10324 }else{
10325 underlay.set(this.underlayAttrs);
10326 }
10327
10328 var ds = dijit._dialogStack,
10329 zIndex = 948 + ds.length*2;
10330 if(ds.length == 1){ // first dialog
10331 underlay.show();
10332 }
10333 dojo.style(dijit._underlay.domNode, 'zIndex', zIndex);
10334 dojo.style(this.domNode, 'zIndex', zIndex + 1);
10335 }),
10336 onEnd: dojo.hitch(this, function(){
10337 if(this.autofocus){
10338 // find focusable Items each time dialog is shown since if dialog contains a widget the
10339 // first focusable items can change
10340 this._getFocusItems(this.domNode);
10341 dijit.focus(this._firstFocusItem);
10342 }
10343 })
10344 });
10345
10346 this._fadeOut = dojo.fadeOut({
10347 node: node,
10348 duration: this.duration,
10349 onEnd: dojo.hitch(this, function(){
10350 node.style.display = "none";
10351
10352 // Restore the previous dialog in the stack, or if this is the only dialog
10353 // then restore to original page
10354 var ds = dijit._dialogStack;
10355 if(ds.length == 0){
10356 dijit._underlay.hide();
10357 }else{
10358 dojo.style(dijit._underlay.domNode, 'zIndex', 948 + ds.length*2);
10359 dijit._underlay.set(ds[ds.length-1].underlayAttrs);
10360 }
10361
10362 // Restore focus to wherever it was before this dialog was displayed
10363 if(this.refocus){
10364 var focus = this._savedFocus;
10365
10366 // If we are returning control to a previous dialog but for some reason
10367 // that dialog didn't have a focused field, set focus to first focusable item.
10368 // This situation could happen if two dialogs appeared at nearly the same time,
10369 // since a dialog doesn't set it's focus until the fade-in is finished.
10370 if(ds.length > 0){
10371 var pd = ds[ds.length-1];
10372 if(!dojo.isDescendant(focus.node, pd.domNode)){
10373 pd._getFocusItems(pd.domNode);
10374 focus = pd._firstFocusItem;
10375 }
10376 }
10377
10378 dijit.focus(focus);
10379 }
10380 })
10381 });
10382 },
10383
10384 uninitialize: function(){
10385 var wasPlaying = false;
10386 if(this._fadeIn && this._fadeIn.status() == "playing"){
10387 wasPlaying = true;
10388 this._fadeIn.stop();
10389 }
10390 if(this._fadeOut && this._fadeOut.status() == "playing"){
10391 wasPlaying = true;
10392 this._fadeOut.stop();
10393 }
10394
10395 // Hide the underlay, unless the underlay widget has already been destroyed
10396 // because we are being called during page unload (when all widgets are destroyed)
10397 if((this.open || wasPlaying) && !dijit._underlay._destroyed){
10398 dijit._underlay.hide();
10399 }
10400
10401 if(this._moveable){
10402 this._moveable.destroy();
10403 }
10404 this.inherited(arguments);
10405 },
10406
10407 _size: function(){
10408 // summary:
10409 // If necessary, shrink dialog contents so dialog fits in viewport
10410 // tags:
10411 // private
10412
10413 this._checkIfSingleChild();
10414
10415 // If we resized the dialog contents earlier, reset them back to original size, so
10416 // that if the user later increases the viewport size, the dialog can display w/out a scrollbar.
10417 // Need to do this before the dojo.marginBox(this.domNode) call below.
10418 if(this._singleChild){
10419 if(this._singleChildOriginalStyle){
10420 this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle;
10421 }
10422 delete this._singleChildOriginalStyle;
10423 }else{
10424 dojo.style(this.containerNode, {
10425 width:"auto",
10426 height:"auto"
10427 });
10428 }
10429
10430 var mb = dojo.marginBox(this.domNode);
10431 var viewport = dojo.window.getBox();
10432 if(mb.w >= viewport.w || mb.h >= viewport.h){
10433 // Reduce size of dialog contents so that dialog fits in viewport
10434
10435 var w = Math.min(mb.w, Math.floor(viewport.w * 0.75)),
10436 h = Math.min(mb.h, Math.floor(viewport.h * 0.75));
10437
10438 if(this._singleChild && this._singleChild.resize){
10439 this._singleChildOriginalStyle = this._singleChild.domNode.style.cssText;
10440 this._singleChild.resize({w: w, h: h});
10441 }else{
10442 dojo.style(this.containerNode, {
10443 width: w + "px",
10444 height: h + "px",
10445 overflow: "auto",
10446 position: "relative" // workaround IE bug moving scrollbar or dragging dialog
10447 });
10448 }
10449 }else{
10450 if(this._singleChild && this._singleChild.resize){
10451 this._singleChild.resize();
10452 }
10453 }
10454 },
10455
10456 _position: function(){
10457 // summary:
10458 // Position modal dialog in the viewport. If no relative offset
10459 // in the viewport has been determined (by dragging, for instance),
10460 // center the node. Otherwise, use the Dialog's stored relative offset,
10461 // and position the node to top: left: values based on the viewport.
10462 // tags:
10463 // private
10464 if(!dojo.hasClass(dojo.body(),"dojoMove")){
10465 var node = this.domNode,
10466 viewport = dojo.window.getBox(),
10467 p = this._relativePosition,
10468 bb = p ? null : dojo._getBorderBox(node),
10469 l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)),
10470 t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2))
10471 ;
10472 dojo.style(node,{
10473 left: l + "px",
10474 top: t + "px"
10475 });
10476 }
10477 },
10478
10479 _onKey: function(/*Event*/ evt){
10480 // summary:
10481 // Handles the keyboard events for accessibility reasons
10482 // tags:
10483 // private
10484
10485 var ds = dijit._dialogStack;
10486 if(ds[ds.length-1] != this){
10487 // console.debug(this.id + ': skipping because', this, 'is not the active dialog');
10488 return;
10489 }
10490
10491 if(evt.charOrCode){
10492 var dk = dojo.keys;
10493 var node = evt.target;
10494 if(evt.charOrCode === dk.TAB){
10495 this._getFocusItems(this.domNode);
10496 }
10497 var singleFocusItem = (this._firstFocusItem == this._lastFocusItem);
10498 // see if we are shift-tabbing from first focusable item on dialog
10499 if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){
10500 if(!singleFocusItem){
10501 dijit.focus(this._lastFocusItem); // send focus to last item in dialog
10502 }
10503 dojo.stopEvent(evt);
10504 }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){
10505 if(!singleFocusItem){
10506 dijit.focus(this._firstFocusItem); // send focus to first item in dialog
10507 }
10508 dojo.stopEvent(evt);
10509 }else{
10510 // see if the key is for the dialog
10511 while(node){
10512 if(node == this.domNode || dojo.hasClass(node, "dijitPopup")){
10513 if(evt.charOrCode == dk.ESCAPE){
10514 this.onCancel();
10515 }else{
10516 return; // just let it go
10517 }
10518 }
10519 node = node.parentNode;
10520 }
10521 // this key is for the disabled document window
10522 if(evt.charOrCode !== dk.TAB){ // allow tabbing into the dialog for a11y
10523 dojo.stopEvent(evt);
10524 // opera won't tab to a div
10525 }else if(!dojo.isOpera){
10526 try{
10527 this._firstFocusItem.focus();
10528 }catch(e){ /*squelch*/ }
10529 }
10530 }
10531 }
10532 },
10533
10534 show: function(){
10535 // summary:
10536 // Display the dialog
10537 if(this.open){ return; }
10538
10539 // first time we show the dialog, there's some initialization stuff to do
10540 if(!this._alreadyInitialized){
10541 this._setup();
10542 this._alreadyInitialized=true;
10543 }
10544
10545 if(this._fadeOut.status() == "playing"){
10546 this._fadeOut.stop();
10547 }
10548
10549 this._modalconnects.push(dojo.connect(window, "onscroll", this, "layout"));
10550 this._modalconnects.push(dojo.connect(window, "onresize", this, function(){
10551 // IE gives spurious resize events and can actually get stuck
10552 // in an infinite loop if we don't ignore them
10553 var viewport = dojo.window.getBox();
10554 if(!this._oldViewport ||
10555 viewport.h != this._oldViewport.h ||
10556 viewport.w != this._oldViewport.w){
10557 this.layout();
10558 this._oldViewport = viewport;
10559 }
10560 }));
10561 this._modalconnects.push(dojo.connect(dojo.doc.documentElement, "onkeypress", this, "_onKey"));
10562
10563 dojo.style(this.domNode, {
10564 opacity:0,
10565 display:""
10566 });
10567
10568 this.open = true;
10569 this._onShow(); // lazy load trigger
10570
10571 this._size();
10572 this._position();
10573 dijit._dialogStack.push(this);
10574 this._fadeIn.play();
10575
10576 this._savedFocus = dijit.getFocus(this);
10577 },
10578
10579 hide: function(){
10580 // summary:
10581 // Hide the dialog
10582
10583 // if we haven't been initialized yet then we aren't showing and we can just return
10584 // or if we aren't the active dialog, don't allow us to close yet
10585 var ds = dijit._dialogStack;
10586 if(!this._alreadyInitialized || this != ds[ds.length-1]){
10587 return;
10588 }
10589
10590 if(this._fadeIn.status() == "playing"){
10591 this._fadeIn.stop();
10592 }
10593
10594 // throw away current active dialog from stack -- making the previous dialog or the node on the original page active
10595 ds.pop();
10596
10597 this._fadeOut.play();
10598
10599 if(this._scrollConnected){
10600 this._scrollConnected = false;
10601 }
10602 dojo.forEach(this._modalconnects, dojo.disconnect);
10603 this._modalconnects = [];
10604
10605 if(this._relativePosition){
10606 delete this._relativePosition;
10607 }
10608 this.open = false;
10609
10610 this.onHide();
10611 },
10612
10613 layout: function(){
10614 // summary:
10615 // Position the Dialog and the underlay
10616 // tags:
10617 // private
10618 if(this.domNode.style.display != "none"){
10619 if(dijit._underlay){ // avoid race condition during show()
10620 dijit._underlay.layout();
10621 }
10622 this._position();
10623 }
10624 },
10625
10626 destroy: function(){
10627 dojo.forEach(this._modalconnects, dojo.disconnect);
10628 if(this.refocus && this.open){
10629 setTimeout(dojo.hitch(dijit,"focus",this._savedFocus), 25);
10630 }
10631 this.inherited(arguments);
10632 }
10633 }
10634 );
10635
10636 dojo.declare(
10637 "dijit.Dialog",
10638 [dijit.layout.ContentPane, dijit._DialogBase],
10639 {}
10640 );
10641
10642 // Stack of currenctly displayed dialogs, layered on top of each other
10643 dijit._dialogStack = [];
10644
10645 // For back-compat. TODO: remove in 2.0
10646
10647
10648 }
10649
10650 if(!dojo._hasResource["dijit._HasDropDown"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
10651 dojo._hasResource["dijit._HasDropDown"] = true;
10652 dojo.provide("dijit._HasDropDown");
10653
10654
10655
10656
10657 dojo.declare("dijit._HasDropDown",
10658 null,
10659 {
10660 // summary:
10661 // Mixin for widgets that need drop down ability.
10662
10663 // _buttonNode: [protected] DomNode
10664 // The button/icon/node to click to display the drop down.
10665 // Can be set via a dojoAttachPoint assignment.
10666 // If missing, then either focusNode or domNode (if focusNode is also missing) will be used.
10667 _buttonNode: null,
10668
10669 // _arrowWrapperNode: [protected] DomNode
10670 // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending
10671 // on where the drop down is set to be positioned.
10672 // Can be set via a dojoAttachPoint assignment.
10673 // If missing, then _buttonNode will be used.
10674 _arrowWrapperNode: null,
10675
10676 // _popupStateNode: [protected] DomNode
10677 // The node to set the popupActive class on.
10678 // Can be set via a dojoAttachPoint assignment.
10679 // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used.
10680 _popupStateNode: null,
10681
10682 // _aroundNode: [protected] DomNode
10683 // The node to display the popup around.
10684 // Can be set via a dojoAttachPoint assignment.
10685 // If missing, then domNode will be used.
10686 _aroundNode: null,
10687
10688 // dropDown: [protected] Widget
10689 // The widget to display as a popup. This widget *must* be
10690 // defined before the startup function is called.
10691 dropDown: null,
10692
10693 // autoWidth: [protected] Boolean
10694 // Set to true to make the drop down at least as wide as this
10695 // widget. Set to false if the drop down should just be its
10696 // default width
10697 autoWidth: true,
10698
10699 // forceWidth: [protected] Boolean
10700 // Set to true to make the drop down exactly as wide as this
10701 // widget. Overrides autoWidth.
10702 forceWidth: false,
10703
10704 // maxHeight: [protected] Integer
10705 // The max height for our dropdown. Set to 0 for no max height.
10706 // any dropdown taller than this will have scrollbars
10707 maxHeight: 0,
10708
10709 // dropDownPosition: [const] String[]
10710 // This variable controls the position of the drop down.
10711 // It's an array of strings with the following values:
10712 //
10713 // * before: places drop down to the left of the target node/widget, or to the right in
10714 // the case of RTL scripts like Hebrew and Arabic
10715 // * after: places drop down to the right of the target node/widget, or to the left in
10716 // the case of RTL scripts like Hebrew and Arabic
10717 // * above: drop down goes above target node
10718 // * below: drop down goes below target node
10719 //
10720 // The list is positions is tried, in order, until a position is found where the drop down fits
10721 // within the viewport.
10722 //
10723 dropDownPosition: ["below","above"],
10724
10725 // _stopClickEvents: Boolean
10726 // When set to false, the click events will not be stopped, in
10727 // case you want to use them in your subwidget
10728 _stopClickEvents: true,
10729
10730 _onDropDownMouseDown: function(/*Event*/ e){
10731 // summary:
10732 // Callback when the user mousedown's on the arrow icon
10733
10734 if(this.disabled || this.readOnly){ return; }
10735
10736 this._docHandler = this.connect(dojo.doc, "onmouseup", "_onDropDownMouseUp");
10737
10738 this.toggleDropDown();
10739 },
10740
10741 _onDropDownMouseUp: function(/*Event?*/ e){
10742 // summary:
10743 // Callback when the user lifts their mouse after mouse down on the arrow icon.
10744 // If the drop is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our
10745 // dropDown node. If the event is missing, then we are not
10746 // a mouseup event.
10747 //
10748 // This is useful for the common mouse movement pattern
10749 // with native browser <select> nodes:
10750 // 1. mouse down on the select node (probably on the arrow)
10751 // 2. move mouse to a menu item while holding down the mouse button
10752 // 3. mouse up. this selects the menu item as though the user had clicked it.
10753 if(e && this._docHandler){
10754 this.disconnect(this._docHandler);
10755 }
10756 var dropDown = this.dropDown, overMenu = false;
10757
10758 if(e && this._opened){
10759 // This code deals with the corner-case when the drop down covers the original widget,
10760 // because it's so large. In that case mouse-up shouldn't select a value from the menu.
10761 // Find out if our target is somewhere in our dropdown widget,
10762 // but not over our _buttonNode (the clickable node)
10763 var c = dojo.position(this._buttonNode, true);
10764 if(!(e.pageX >= c.x && e.pageX <= c.x + c.w) ||
10765 !(e.pageY >= c.y && e.pageY <= c.y + c.h)){
10766 var t = e.target;
10767 while(t && !overMenu){
10768 if(dojo.hasClass(t, "dijitPopup")){
10769 overMenu = true;
10770 }else{
10771 t = t.parentNode;
10772 }
10773 }
10774 if(overMenu){
10775 t = e.target;
10776 if(dropDown.onItemClick){
10777 var menuItem;
10778 while(t && !(menuItem = dijit.byNode(t))){
10779 t = t.parentNode;
10780 }
10781 if(menuItem && menuItem.onClick && menuItem.getParent){
10782 menuItem.getParent().onItemClick(menuItem, e);
10783 }
10784 }
10785 return;
10786 }
10787 }
10788 }
10789 if(this._opened && dropDown.focus){
10790 // Focus the dropdown widget - do it on a delay so that we
10791 // don't steal our own focus.
10792 window.setTimeout(dojo.hitch(dropDown, "focus"), 1);
10793 }
10794 },
10795
10796 _onDropDownClick: function(/*Event*/ e){
10797 // the drop down was already opened on mousedown/keydown; just need to call stopEvent()
10798 if(this._stopClickEvents){
10799 dojo.stopEvent(e);
10800 }
10801 },
10802
10803 _setupDropdown: function(){
10804 // summary:
10805 // set up nodes and connect our mouse and keypress events
10806 this._buttonNode = this._buttonNode || this.focusNode || this.domNode;
10807 this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode;
10808 this._aroundNode = this._aroundNode || this.domNode;
10809 this.connect(this._buttonNode, "onmousedown", "_onDropDownMouseDown");
10810 this.connect(this._buttonNode, "onclick", "_onDropDownClick");
10811 this.connect(this._buttonNode, "onkeydown", "_onDropDownKeydown");
10812 this.connect(this._buttonNode, "onkeyup", "_onKey");
10813
10814 // If we have a _setStateClass function (which happens when
10815 // we are a form widget), then we need to connect our open/close
10816 // functions to it
10817 if(this._setStateClass){
10818 this.connect(this, "openDropDown", "_setStateClass");
10819 this.connect(this, "closeDropDown", "_setStateClass");
10820 }
10821
10822 // Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow
10823 // based on where drop down will normally appear
10824 var defaultPos = {
10825 "after" : this.isLeftToRight() ? "Right" : "Left",
10826 "before" : this.isLeftToRight() ? "Left" : "Right",
10827 "above" : "Up",
10828 "below" : "Down",
10829 "left" : "Left",
10830 "right" : "Right"
10831 }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down";
10832 dojo.addClass(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton");
10833 },
10834
10835 postCreate: function(){
10836 this._setupDropdown();
10837 this.inherited(arguments);
10838 },
10839
10840 destroyDescendants: function(){
10841 if(this.dropDown){
10842 // Destroy the drop down, unless it's already been destroyed. This can happen because
10843 // the drop down is a direct child of <body> even though it's logically my child.
10844 if(!this.dropDown._destroyed){
10845 this.dropDown.destroyRecursive();
10846 }
10847 delete this.dropDown;
10848 }
10849 this.inherited(arguments);
10850 },
10851
10852 _onDropDownKeydown: function(/*Event*/ e){
10853 if(e.keyCode == dojo.keys.DOWN_ARROW || e.keyCode == dojo.keys.ENTER || e.keyCode == dojo.keys.SPACE){
10854 e.preventDefault(); // stop IE screen jump
10855 }
10856 },
10857
10858 _onKey: function(/*Event*/ e){
10859 // summary:
10860 // Callback when the user presses a key while focused on the button node
10861
10862 if(this.disabled || this.readOnly){ return; }
10863 var d = this.dropDown;
10864 if(d && this._opened && d.handleKey){
10865 if(d.handleKey(e) === false){ return; }
10866 }
10867 if(d && this._opened && e.keyCode == dojo.keys.ESCAPE){
10868 this.toggleDropDown();
10869 }else if(d && !this._opened &&
10870 (e.keyCode == dojo.keys.DOWN_ARROW || e.keyCode == dojo.keys.ENTER || e.keyCode == dojo.keys.SPACE)){
10871 this.toggleDropDown();
10872 if(d.focus){
10873 setTimeout(dojo.hitch(d, "focus"), 1);
10874 }
10875 }
10876 },
10877
10878 _onBlur: function(){
10879 // summary:
10880 // Called magically when focus has shifted away from this widget and it's dropdown
10881
10882 this.closeDropDown();
10883 // don't focus on button. the user has explicitly focused on something else.
10884 this.inherited(arguments);
10885 },
10886
10887 isLoaded: function(){
10888 // summary:
10889 // Returns whether or not the dropdown is loaded. This can
10890 // be overridden in order to force a call to loadDropDown().
10891 // tags:
10892 // protected
10893
10894 return true;
10895 },
10896
10897 loadDropDown: function(/* Function */ loadCallback){
10898 // summary:
10899 // Loads the data for the dropdown, and at some point, calls
10900 // the given callback
10901 // tags:
10902 // protected
10903
10904 loadCallback();
10905 },
10906
10907 toggleDropDown: function(){
10908 // summary:
10909 // Toggle the drop-down widget; if it is up, close it, if not, open it
10910 // tags:
10911 // protected
10912
10913 if(this.disabled || this.readOnly){ return; }
10914 this.focus();
10915 var dropDown = this.dropDown;
10916 if(!dropDown){ return; }
10917 if(!this._opened){
10918 // If we aren't loaded, load it first so there isn't a flicker
10919 if(!this.isLoaded()){
10920 this.loadDropDown(dojo.hitch(this, "openDropDown"));
10921 return;
10922 }else{
10923 this.openDropDown();
10924 }
10925 }else{
10926 this.closeDropDown();
10927 }
10928 },
10929
10930 openDropDown: function(){
10931 // summary:
10932 // Opens the dropdown for this widget - it returns the
10933 // return value of dijit.popup.open
10934 // tags:
10935 // protected
10936
10937 var dropDown = this.dropDown;
10938 var ddNode = dropDown.domNode;
10939 var self = this;
10940
10941 // Prepare our popup's height and honor maxHeight if it exists.
10942
10943 // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(),
10944 // ie, dependent on how much space is available (BK)
10945
10946 if(!this._preparedNode){
10947 dijit.popup.moveOffScreen(ddNode);
10948 this._preparedNode = true;
10949 // Check if we have explicitly set width and height on the dropdown widget dom node
10950 if(ddNode.style.width){
10951 this._explicitDDWidth = true;
10952 }
10953 if(ddNode.style.height){
10954 this._explicitDDHeight = true;
10955 }
10956 }
10957
10958 // Code for resizing dropdown (height limitation, or increasing width to match my width)
10959 if(this.maxHeight || this.forceWidth || this.autoWidth){
10960 var myStyle = {
10961 display: "",
10962 visibility: "hidden"
10963 };
10964 if(!this._explicitDDWidth){
10965 myStyle.width = "";
10966 }
10967 if(!this._explicitDDHeight){
10968 myStyle.height = "";
10969 }
10970 dojo.style(ddNode, myStyle);
10971
10972 // Get size of drop down, and determine if vertical scroll bar needed
10973 var mb = dojo.marginBox(ddNode);
10974 var overHeight = (this.maxHeight && mb.h > this.maxHeight);
10975 dojo.style(ddNode, {
10976 overflowX: "hidden",
10977 overflowY: overHeight ? "auto" : "hidden"
10978 });
10979 if(overHeight){
10980 mb.h = this.maxHeight;
10981 if("w" in mb){
10982 mb.w += 16; // room for vertical scrollbar
10983 }
10984 }else{
10985 delete mb.h;
10986 }
10987 delete mb.t;
10988 delete mb.l;
10989
10990 // Adjust dropdown width to match or be larger than my width
10991 if(this.forceWidth){
10992 mb.w = this.domNode.offsetWidth;
10993 }else if(this.autoWidth){
10994 mb.w = Math.max(mb.w, this.domNode.offsetWidth);
10995 }else{
10996 delete mb.w;
10997 }
10998
10999 // And finally, resize the dropdown to calculated height and width
11000 if(dojo.isFunction(dropDown.resize)){
11001 dropDown.resize(mb);
11002 }else{
11003 dojo.marginBox(ddNode, mb);
11004 }
11005 }
11006
11007 var retVal = dijit.popup.open({
11008 parent: this,
11009 popup: dropDown,
11010 around: this._aroundNode,
11011 orient: dijit.getPopupAroundAlignment((this.dropDownPosition && this.dropDownPosition.length) ? this.dropDownPosition : ["below"],this.isLeftToRight()),
11012 onExecute: function(){
11013 self.closeDropDown(true);
11014 },
11015 onCancel: function(){
11016 self.closeDropDown(true);
11017 },
11018 onClose: function(){
11019 dojo.attr(self._popupStateNode, "popupActive", false);
11020 dojo.removeClass(self._popupStateNode, "dijitHasDropDownOpen");
11021 self._opened = false;
11022 self.state = "";
11023 }
11024 });
11025 dojo.attr(this._popupStateNode, "popupActive", "true");
11026 dojo.addClass(self._popupStateNode, "dijitHasDropDownOpen");
11027 this._opened=true;
11028 this.state="Opened";
11029 // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown
11030 return retVal;
11031 },
11032
11033 closeDropDown: function(/*Boolean*/ focus){
11034 // summary:
11035 // Closes the drop down on this widget
11036 // tags:
11037 // protected
11038
11039 if(this._opened){
11040 if(focus){ this.focus(); }
11041 dijit.popup.close(this.dropDown);
11042 this._opened = false;
11043 this.state = "";
11044 }
11045 }
11046
11047 }
11048 );
11049
11050 }
11051
11052 if(!dojo._hasResource["dijit.form.Button"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11053 dojo._hasResource["dijit.form.Button"] = true;
11054 dojo.provide("dijit.form.Button");
11055
11056
11057
11058
11059
11060 dojo.declare("dijit.form.Button",
11061 dijit.form._FormWidget,
11062 {
11063 // summary:
11064 // Basically the same thing as a normal HTML button, but with special styling.
11065 // description:
11066 // Buttons can display a label, an icon, or both.
11067 // A label should always be specified (through innerHTML) or the label
11068 // attribute. It can be hidden via showLabel=false.
11069 // example:
11070 // | <button dojoType="dijit.form.Button" onClick="...">Hello world</button>
11071 //
11072 // example:
11073 // | var button1 = new dijit.form.Button({label: "hello world", onClick: foo});
11074 // | dojo.body().appendChild(button1.domNode);
11075
11076 // label: HTML String
11077 // Text to display in button.
11078 // If the label is hidden (showLabel=false) then and no title has
11079 // been specified, then label is also set as title attribute of icon.
11080 label: "",
11081
11082 // showLabel: Boolean
11083 // Set this to true to hide the label text and display only the icon.
11084 // (If showLabel=false then iconClass must be specified.)
11085 // Especially useful for toolbars.
11086 // If showLabel=true, the label will become the title (a.k.a. tooltip/hint) of the icon.
11087 //
11088 // The exception case is for computers in high-contrast mode, where the label
11089 // will still be displayed, since the icon doesn't appear.
11090 showLabel: true,
11091
11092 // iconClass: String
11093 // Class to apply to DOMNode in button to make it display an icon
11094 iconClass: "",
11095
11096 // type: String
11097 // Defines the type of button. "button", "submit", or "reset".
11098 type: "button",
11099
11100 baseClass: "dijitButton",
11101
11102 templateString: dojo.cache("dijit.form", "templates/Button.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode,focusNode\"\n\t\t\twaiRole=\"button\" waiState=\"labelledby-${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">&#x25CF;</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdojoAttachPoint=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),
11103
11104 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
11105 value: "valueNode",
11106 iconClass: { node: "iconNode", type: "class" }
11107 }),
11108
11109
11110 _onClick: function(/*Event*/ e){
11111 // summary:
11112 // Internal function to handle click actions
11113 if(this.disabled){
11114 return false;
11115 }
11116 this._clicked(); // widget click actions
11117 return this.onClick(e); // user click actions
11118 },
11119
11120 _onButtonClick: function(/*Event*/ e){
11121 // summary:
11122 // Handler when the user activates the button portion.
11123 if(this._onClick(e) === false){ // returning nothing is same as true
11124 e.preventDefault(); // needed for checkbox
11125 }else if(this.type == "submit" && !(this.valueNode||this.focusNode).form){ // see if a nonform widget needs to be signalled
11126 for(var node=this.domNode; node.parentNode/*#5935*/; node=node.parentNode){
11127 var widget=dijit.byNode(node);
11128 if(widget && typeof widget._onSubmit == "function"){
11129 widget._onSubmit(e);
11130 break;
11131 }
11132 }
11133 }else if(this.valueNode){
11134 this.valueNode.click();
11135 e.preventDefault(); // cancel BUTTON click and continue with hidden INPUT click
11136 }
11137 },
11138
11139 _fillContent: function(/*DomNode*/ source){
11140 // Overrides _Templated._fillContent().
11141 // If button label is specified as srcNodeRef.innerHTML rather than
11142 // this.params.label, handle it here.
11143 if(source && (!this.params || !("label" in this.params))){
11144 this.set('label', source.innerHTML);
11145 }
11146 },
11147
11148 postCreate: function(){
11149 dojo.setSelectable(this.focusNode, false);
11150 this.inherited(arguments);
11151 },
11152
11153 _setShowLabelAttr: function(val){
11154 if(this.containerNode){
11155 dojo.toggleClass(this.containerNode, "dijitDisplayNone", !val);
11156 }
11157 this.showLabel = val;
11158 },
11159
11160 onClick: function(/*Event*/ e){
11161 // summary:
11162 // Callback for when button is clicked.
11163 // If type="submit", return true to perform submit, or false to cancel it.
11164 // type:
11165 // callback
11166 return true; // Boolean
11167 },
11168
11169 _clicked: function(/*Event*/ e){
11170 // summary:
11171 // Internal overridable function for when the button is clicked
11172 },
11173
11174 setLabel: function(/*String*/ content){
11175 // summary:
11176 // Deprecated. Use set('label', ...) instead.
11177 dojo.deprecated("dijit.form.Button.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0");
11178 this.set("label", content);
11179 },
11180
11181 _setLabelAttr: function(/*String*/ content){
11182 // summary:
11183 // Hook for attr('label', ...) to work.
11184 // description:
11185 // Set the label (text) of the button; takes an HTML string.
11186 this.containerNode.innerHTML = this.label = content;
11187 if(this.showLabel == false && !this.params.title){
11188 this.titleNode.title = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || '');
11189 }
11190 }
11191 });
11192
11193
11194 dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container, dijit._HasDropDown], {
11195 // summary:
11196 // A button with a drop down
11197 //
11198 // example:
11199 // | <button dojoType="dijit.form.DropDownButton" label="Hello world">
11200 // | <div dojotype="dijit.Menu">...</div>
11201 // | </button>
11202 //
11203 // example:
11204 // | var button1 = new dijit.form.DropDownButton({ label: "hi", dropDown: new dijit.Menu(...) });
11205 // | dojo.body().appendChild(button1);
11206 //
11207
11208 baseClass : "dijitDropDownButton",
11209
11210 templateString: dojo.cache("dijit.form", "templates/DropDownButton.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\" dojoAttachPoint=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"focusNode,titleNode,_arrowWrapperNode\"\n\t\t\twaiRole=\"button\" waiState=\"haspopup-true,labelledby-${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\"\n\t\t\t\tdojoAttachPoint=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdojoAttachPoint=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">&#9660;</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),
11211
11212 _fillContent: function(){
11213 // Overrides Button._fillContent().
11214 //
11215 // My inner HTML contains both the button contents and a drop down widget, like
11216 // <DropDownButton> <span>push me</span> <Menu> ... </Menu> </DropDownButton>
11217 // The first node is assumed to be the button content. The widget is the popup.
11218
11219 if(this.srcNodeRef){ // programatically created buttons might not define srcNodeRef
11220 //FIXME: figure out how to filter out the widget and use all remaining nodes as button
11221 // content, not just nodes[0]
11222 var nodes = dojo.query("*", this.srcNodeRef);
11223 dijit.form.DropDownButton.superclass._fillContent.call(this, nodes[0]);
11224
11225 // save pointer to srcNode so we can grab the drop down widget after it's instantiated
11226 this.dropDownContainer = this.srcNodeRef;
11227 }
11228 },
11229
11230 startup: function(){
11231 if(this._started){ return; }
11232
11233 // the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM,
11234 // make it invisible, and store a reference to pass to the popup code.
11235 if(!this.dropDown){
11236 var dropDownNode = dojo.query("[widgetId]", this.dropDownContainer)[0];
11237 this.dropDown = dijit.byNode(dropDownNode);
11238 delete this.dropDownContainer;
11239 }
11240 dijit.popup.moveOffScreen(this.dropDown.domNode);
11241
11242 this.inherited(arguments);
11243 },
11244
11245 isLoaded: function(){
11246 // Returns whether or not we are loaded - if our dropdown has an href,
11247 // then we want to check that.
11248 var dropDown = this.dropDown;
11249 return (!dropDown.href || dropDown.isLoaded);
11250 },
11251
11252 loadDropDown: function(){
11253 // Loads our dropdown
11254 var dropDown = this.dropDown;
11255 if(!dropDown){ return; }
11256 if(!this.isLoaded()){
11257 var handler = dojo.connect(dropDown, "onLoad", this, function(){
11258 dojo.disconnect(handler);
11259 this.openDropDown();
11260 });
11261 dropDown.refresh();
11262 }else{
11263 this.openDropDown();
11264 }
11265 },
11266
11267 isFocusable: function(){
11268 // Overridden so that focus is handled by the _HasDropDown mixin, not by
11269 // the _FormWidget mixin.
11270 return this.inherited(arguments) && !this._mouseDown;
11271 }
11272 });
11273
11274 dojo.declare("dijit.form.ComboButton", dijit.form.DropDownButton, {
11275 // summary:
11276 // A combination button and drop-down button.
11277 // Users can click one side to "press" the button, or click an arrow
11278 // icon to display the drop down.
11279 //
11280 // example:
11281 // | <button dojoType="dijit.form.ComboButton" onClick="...">
11282 // | <span>Hello world</span>
11283 // | <div dojoType="dijit.Menu">...</div>
11284 // | </button>
11285 //
11286 // example:
11287 // | var button1 = new dijit.form.ComboButton({label: "hello world", onClick: foo, dropDown: "myMenu"});
11288 // | dojo.body().appendChild(button1.domNode);
11289 //
11290
11291 templateString: dojo.cache("dijit.form", "templates/ComboButton.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tcellspacing='0' cellpadding='0' waiRole=\"presentation\"\n\t><tbody waiRole=\"presentation\"><tr waiRole=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonNode\" dojoAttachPoint=\"buttonNode\" dojoAttachEvent=\"ondijitclick:_onButtonClick,onkeypress:_onButtonKeyPress\"\n\t\t><div id=\"${id}_button\" class=\"dijitReset dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode\"\n\t\t\twaiRole=\"button\" waiState=\"labelledby-${id}_label\"\n\t\t\t><div class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitInline dijitButtonText\" id=\"${id}_label\" dojoAttachPoint=\"containerNode\" waiRole=\"presentation\"></div\n\t\t></div\n\t\t></td\n\t\t><td id=\"${id}_arrow\" class='dijitReset dijitRight dijitButtonNode dijitArrowButton'\n\t\t\tdojoAttachPoint=\"_popupStateNode,focusNode,_buttonNode\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onArrowKeyPress\"\n\t\t\ttitle=\"${optionsTitle}\"\n\t\t\twaiRole=\"button\" waiState=\"haspopup-true\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" waiRole=\"presentation\">&#9660;</div\n\t\t></td\n\t\t><td style=\"display:none !important;\"\n\t\t\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" dojoAttachPoint=\"valueNode\"\n\t\t/></td></tr></tbody\n></table>\n"),
11292
11293 attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), {
11294 id: "",
11295 tabIndex: ["focusNode", "titleNode"],
11296 title: "titleNode"
11297 }),
11298
11299 // optionsTitle: String
11300 // Text that describes the options menu (accessibility)
11301 optionsTitle: "",
11302
11303 baseClass: "dijitComboButton",
11304
11305 // Set classes like dijitButtonContentsHover or dijitArrowButtonActive depending on
11306 // mouse action over specified node
11307 cssStateNodes: {
11308 "buttonNode": "dijitButtonNode",
11309 "titleNode": "dijitButtonContents",
11310 "_popupStateNode": "dijitDownArrowButton"
11311 },
11312
11313 _focusedNode: null,
11314
11315 _onButtonKeyPress: function(/*Event*/ evt){
11316 // summary:
11317 // Handler for right arrow key when focus is on left part of button
11318 if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "RIGHT_ARROW" : "LEFT_ARROW"]){
11319 dijit.focus(this._popupStateNode);
11320 dojo.stopEvent(evt);
11321 }
11322 },
11323
11324 _onArrowKeyPress: function(/*Event*/ evt){
11325 // summary:
11326 // Handler for left arrow key when focus is on right part of button
11327 if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "LEFT_ARROW" : "RIGHT_ARROW"]){
11328 dijit.focus(this.titleNode);
11329 dojo.stopEvent(evt);
11330 }
11331 },
11332
11333 focus: function(/*String*/ position){
11334 // summary:
11335 // Focuses this widget to according to position, if specified,
11336 // otherwise on arrow node
11337 // position:
11338 // "start" or "end"
11339
11340 dijit.focus(position == "start" ? this.titleNode : this._popupStateNode);
11341 }
11342 });
11343
11344 dojo.declare("dijit.form.ToggleButton", dijit.form.Button, {
11345 // summary:
11346 // A button that can be in two states (checked or not).
11347 // Can be base class for things like tabs or checkbox or radio buttons
11348
11349 baseClass: "dijitToggleButton",
11350
11351 // checked: Boolean
11352 // Corresponds to the native HTML <input> element's attribute.
11353 // In markup, specified as "checked='checked'" or just "checked".
11354 // True if the button is depressed, or the checkbox is checked,
11355 // or the radio button is selected, etc.
11356 checked: false,
11357
11358 attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), {
11359 checked:"focusNode"
11360 }),
11361
11362 _clicked: function(/*Event*/ evt){
11363 this.set('checked', !this.checked);
11364 },
11365
11366 _setCheckedAttr: function(/*Boolean*/ value, /* Boolean? */ priorityChange){
11367 this.checked = value;
11368 dojo.attr(this.focusNode || this.domNode, "checked", value);
11369 dijit.setWaiState(this.focusNode || this.domNode, "pressed", value);
11370 this._handleOnChange(value, priorityChange);
11371 },
11372
11373 setChecked: function(/*Boolean*/ checked){
11374 // summary:
11375 // Deprecated. Use set('checked', true/false) instead.
11376 dojo.deprecated("setChecked("+checked+") is deprecated. Use set('checked',"+checked+") instead.", "", "2.0");
11377 this.set('checked', checked);
11378 },
11379
11380 reset: function(){
11381 // summary:
11382 // Reset the widget's value to what it was at initialization time
11383
11384 this._hasBeenBlurred = false;
11385
11386 // set checked state to original setting
11387 this.set('checked', this.params.checked || false);
11388 }
11389 });
11390
11391 }
11392
11393 if(!dojo._hasResource["dijit.form.ToggleButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11394 dojo._hasResource["dijit.form.ToggleButton"] = true;
11395 dojo.provide("dijit.form.ToggleButton");
11396
11397
11398 }
11399
11400 if(!dojo._hasResource["dijit.form.CheckBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11401 dojo._hasResource["dijit.form.CheckBox"] = true;
11402 dojo.provide("dijit.form.CheckBox");
11403
11404
11405
11406 dojo.declare(
11407 "dijit.form.CheckBox",
11408 dijit.form.ToggleButton,
11409 {
11410 // summary:
11411 // Same as an HTML checkbox, but with fancy styling.
11412 //
11413 // description:
11414 // User interacts with real html inputs.
11415 // On onclick (which occurs by mouse click, space-bar, or
11416 // using the arrow keys to switch the selected radio button),
11417 // we update the state of the checkbox/radio.
11418 //
11419 // There are two modes:
11420 // 1. High contrast mode
11421 // 2. Normal mode
11422 //
11423 // In case 1, the regular html inputs are shown and used by the user.
11424 // In case 2, the regular html inputs are invisible but still used by
11425 // the user. They are turned quasi-invisible and overlay the background-image.
11426
11427 templateString: dojo.cache("dijit.form", "templates/CheckBox.html", "<div class=\"dijit dijitReset dijitInline\" waiRole=\"presentation\"\n\t><input\n\t \t${!nameAttrSetting} type=\"${type}\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdojoAttachPoint=\"focusNode\"\n\t \tdojoAttachEvent=\"onclick:_onClick\"\n/></div>\n"),
11428
11429 baseClass: "dijitCheckBox",
11430
11431 // type: [private] String
11432 // type attribute on <input> node.
11433 // Overrides `dijit.form.Button.type`. Users should not change this value.
11434 type: "checkbox",
11435
11436 // value: String
11437 // As an initialization parameter, equivalent to value field on normal checkbox
11438 // (if checked, the value is passed as the value when form is submitted).
11439 //
11440 // However, attr('value') will return either the string or false depending on
11441 // whether or not the checkbox is checked.
11442 //
11443 // attr('value', string) will check the checkbox and change the value to the
11444 // specified string
11445 //
11446 // attr('value', boolean) will change the checked state.
11447 value: "on",
11448
11449 // readOnly: Boolean
11450 // Should this widget respond to user input?
11451 // In markup, this is specified as "readOnly".
11452 // Similar to disabled except readOnly form values are submitted.
11453 readOnly: false,
11454
11455 // the attributeMap should inherit from dijit.form._FormWidget.prototype.attributeMap
11456 // instead of ToggleButton as the icon mapping has no meaning for a CheckBox
11457 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
11458 readOnly: "focusNode"
11459 }),
11460
11461 _setReadOnlyAttr: function(/*Boolean*/ value){
11462 this.readOnly = value;
11463 dojo.attr(this.focusNode, 'readOnly', value);
11464 dijit.setWaiState(this.focusNode, "readonly", value);
11465 },
11466
11467 _setValueAttr: function(/*String or Boolean*/ newValue, /*Boolean*/ priorityChange){
11468 // summary:
11469 // Handler for value= attribute to constructor, and also calls to
11470 // attr('value', val).
11471 // description:
11472 // During initialization, just saves as attribute to the <input type=checkbox>.
11473 //
11474 // After initialization,
11475 // when passed a boolean, controls whether or not the CheckBox is checked.
11476 // If passed a string, changes the value attribute of the CheckBox (the one
11477 // specified as "value" when the CheckBox was constructed (ex: <input
11478 // dojoType="dijit.CheckBox" value="chicken">)
11479 if(typeof newValue == "string"){
11480 this.value = newValue;
11481 dojo.attr(this.focusNode, 'value', newValue);
11482 newValue = true;
11483 }
11484 if(this._created){
11485 this.set('checked', newValue, priorityChange);
11486 }
11487 },
11488 _getValueAttr: function(){
11489 // summary:
11490 // Hook so attr('value') works.
11491 // description:
11492 // If the CheckBox is checked, returns the value attribute.
11493 // Otherwise returns false.
11494 return (this.checked ? this.value : false);
11495 },
11496
11497 // Override dijit.form.Button._setLabelAttr() since we don't even have a containerNode.
11498 // Normally users won't try to set label, except when CheckBox or RadioButton is the child of a dojox.layout.TabContainer
11499 _setLabelAttr: undefined,
11500
11501 postMixInProperties: function(){
11502 if(this.value == ""){
11503 this.value = "on";
11504 }
11505
11506 // Need to set initial checked state as part of template, so that form submit works.
11507 // dojo.attr(node, "checked", bool) doesn't work on IEuntil node has been attached
11508 // to <body>, see #8666
11509 this.checkedAttrSetting = this.checked ? "checked" : "";
11510
11511 this.inherited(arguments);
11512 },
11513
11514 _fillContent: function(/*DomNode*/ source){
11515 // Override Button::_fillContent() since it doesn't make sense for CheckBox,
11516 // since CheckBox doesn't even have a container
11517 },
11518
11519 reset: function(){
11520 // Override ToggleButton.reset()
11521
11522 this._hasBeenBlurred = false;
11523
11524 this.set('checked', this.params.checked || false);
11525
11526 // Handle unlikely event that the <input type=checkbox> value attribute has changed
11527 this.value = this.params.value || "on";
11528 dojo.attr(this.focusNode, 'value', this.value);
11529 },
11530
11531 _onFocus: function(){
11532 if(this.id){
11533 dojo.query("label[for='"+this.id+"']").addClass("dijitFocusedLabel");
11534 }
11535 this.inherited(arguments);
11536 },
11537
11538 _onBlur: function(){
11539 if(this.id){
11540 dojo.query("label[for='"+this.id+"']").removeClass("dijitFocusedLabel");
11541 }
11542 this.inherited(arguments);
11543 },
11544
11545 _onClick: function(/*Event*/ e){
11546 // summary:
11547 // Internal function to handle click actions - need to check
11548 // readOnly, since button no longer does that check.
11549 if(this.readOnly){
11550 return false;
11551 }
11552 return this.inherited(arguments);
11553 }
11554 }
11555 );
11556
11557 dojo.declare(
11558 "dijit.form.RadioButton",
11559 dijit.form.CheckBox,
11560 {
11561 // summary:
11562 // Same as an HTML radio, but with fancy styling.
11563
11564 type: "radio",
11565 baseClass: "dijitRadio",
11566
11567 _setCheckedAttr: function(/*Boolean*/ value){
11568 // If I am being checked then have to deselect currently checked radio button
11569 this.inherited(arguments);
11570 if(!this._created){ return; }
11571 if(value){
11572 var _this = this;
11573 // search for radio buttons with the same name that need to be unchecked
11574 dojo.query("INPUT[type=radio]", this.focusNode.form || dojo.doc).forEach( // can't use name= since dojo.query doesn't support [] in the name
11575 function(inputNode){
11576 if(inputNode.name == _this.name && inputNode != _this.focusNode && inputNode.form == _this.focusNode.form){
11577 var widget = dijit.getEnclosingWidget(inputNode);
11578 if(widget && widget.checked){
11579 widget.set('checked', false);
11580 }
11581 }
11582 }
11583 );
11584 }
11585 },
11586
11587 _clicked: function(/*Event*/ e){
11588 if(!this.checked){
11589 this.set('checked', true);
11590 }
11591 }
11592 }
11593 );
11594
11595 }
11596
11597 if(!dojo._hasResource["dijit.form.DropDownButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11598 dojo._hasResource["dijit.form.DropDownButton"] = true;
11599 dojo.provide("dijit.form.DropDownButton");
11600
11601
11602
11603 }
11604
11605 if(!dojo._hasResource["dojo.regexp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11606 dojo._hasResource["dojo.regexp"] = true;
11607 dojo.provide("dojo.regexp");
11608
11609 /*=====
11610 dojo.regexp = {
11611 // summary: Regular expressions and Builder resources
11612 };
11613 =====*/
11614
11615 dojo.regexp.escapeString = function(/*String*/str, /*String?*/except){
11616 // summary:
11617 // Adds escape sequences for special characters in regular expressions
11618 // except:
11619 // a String with special characters to be left unescaped
11620
11621 return str.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(ch){
11622 if(except && except.indexOf(ch) != -1){
11623 return ch;
11624 }
11625 return "\\" + ch;
11626 }); // String
11627 }
11628
11629 dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boolean?*/nonCapture){
11630 // summary:
11631 // Builds a regular expression that groups subexpressions
11632 // description:
11633 // A utility function used by some of the RE generators. The
11634 // subexpressions are constructed by the function, re, in the second
11635 // parameter. re builds one subexpression for each elem in the array
11636 // a, in the first parameter. Returns a string for a regular
11637 // expression that groups all the subexpressions.
11638 // arr:
11639 // A single value or an array of values.
11640 // re:
11641 // A function. Takes one parameter and converts it to a regular
11642 // expression.
11643 // nonCapture:
11644 // If true, uses non-capturing match, otherwise matches are retained
11645 // by regular expression. Defaults to false
11646
11647 // case 1: a is a single value.
11648 if(!(arr instanceof Array)){
11649 return re(arr); // String
11650 }
11651
11652 // case 2: a is an array
11653 var b = [];
11654 for(var i = 0; i < arr.length; i++){
11655 // convert each elem to a RE
11656 b.push(re(arr[i]));
11657 }
11658
11659 // join the REs as alternatives in a RE group.
11660 return dojo.regexp.group(b.join("|"), nonCapture); // String
11661 }
11662
11663 dojo.regexp.group = function(/*String*/expression, /*Boolean?*/nonCapture){
11664 // summary:
11665 // adds group match to expression
11666 // nonCapture:
11667 // If true, uses non-capturing match, otherwise matches are retained
11668 // by regular expression.
11669 return "(" + (nonCapture ? "?:":"") + expression + ")"; // String
11670 }
11671
11672 }
11673
11674 if(!dojo._hasResource["dojo.data.util.sorter"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11675 dojo._hasResource["dojo.data.util.sorter"] = true;
11676 dojo.provide("dojo.data.util.sorter");
11677
11678 dojo.data.util.sorter.basicComparator = function( /*anything*/ a,
11679 /*anything*/ b){
11680 // summary:
11681 // Basic comparision function that compares if an item is greater or less than another item
11682 // description:
11683 // returns 1 if a > b, -1 if a < b, 0 if equal.
11684 // 'null' values (null, undefined) are treated as larger values so that they're pushed to the end of the list.
11685 // And compared to each other, null is equivalent to undefined.
11686
11687 //null is a problematic compare, so if null, we set to undefined.
11688 //Makes the check logic simple, compact, and consistent
11689 //And (null == undefined) === true, so the check later against null
11690 //works for undefined and is less bytes.
11691 var r = -1;
11692 if(a === null){
11693 a = undefined;
11694 }
11695 if(b === null){
11696 b = undefined;
11697 }
11698 if(a == b){
11699 r = 0;
11700 }else if(a > b || a == null){
11701 r = 1;
11702 }
11703 return r; //int {-1,0,1}
11704 };
11705
11706 dojo.data.util.sorter.createSortFunction = function( /* attributes array */sortSpec,
11707 /*dojo.data.core.Read*/ store){
11708 // summary:
11709 // Helper function to generate the sorting function based off the list of sort attributes.
11710 // description:
11711 // The sort function creation will look for a property on the store called 'comparatorMap'. If it exists
11712 // it will look in the mapping for comparisons function for the attributes. If one is found, it will
11713 // use it instead of the basic comparator, which is typically used for strings, ints, booleans, and dates.
11714 // Returns the sorting function for this particular list of attributes and sorting directions.
11715 //
11716 // sortSpec: array
11717 // A JS object that array that defines out what attribute names to sort on and whether it should be descenting or asending.
11718 // The objects should be formatted as follows:
11719 // {
11720 // attribute: "attributeName-string" || attribute,
11721 // descending: true|false; // Default is false.
11722 // }
11723 // store: object
11724 // The datastore object to look up item values from.
11725 //
11726 var sortFunctions=[];
11727
11728 function createSortFunction(attr, dir, comp, s){
11729 //Passing in comp and s (comparator and store), makes this
11730 //function much faster.
11731 return function(itemA, itemB){
11732 var a = s.getValue(itemA, attr);
11733 var b = s.getValue(itemB, attr);
11734 return dir * comp(a,b); //int
11735 };
11736 }
11737 var sortAttribute;
11738 var map = store.comparatorMap;
11739 var bc = dojo.data.util.sorter.basicComparator;
11740 for(var i = 0; i < sortSpec.length; i++){
11741 sortAttribute = sortSpec[i];
11742 var attr = sortAttribute.attribute;
11743 if(attr){
11744 var dir = (sortAttribute.descending) ? -1 : 1;
11745 var comp = bc;
11746 if(map){
11747 if(typeof attr !== "string" && ("toString" in attr)){
11748 attr = attr.toString();
11749 }
11750 comp = map[attr] || bc;
11751 }
11752 sortFunctions.push(createSortFunction(attr,
11753 dir, comp, store));
11754 }
11755 }
11756 return function(rowA, rowB){
11757 var i=0;
11758 while(i < sortFunctions.length){
11759 var ret = sortFunctions[i++](rowA, rowB);
11760 if(ret !== 0){
11761 return ret;//int
11762 }
11763 }
11764 return 0; //int
11765 }; // Function
11766 };
11767
11768 }
11769
11770 if(!dojo._hasResource["dojo.data.util.simpleFetch"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11771 dojo._hasResource["dojo.data.util.simpleFetch"] = true;
11772 dojo.provide("dojo.data.util.simpleFetch");
11773
11774
11775 dojo.data.util.simpleFetch.fetch = function(/* Object? */ request){
11776 // summary:
11777 // The simpleFetch mixin is designed to serve as a set of function(s) that can
11778 // be mixed into other datastore implementations to accelerate their development.
11779 // The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems()
11780 // call by returning an array of all the found items that matched the query. The simpleFetch mixin
11781 // is not designed to work for datastores that respond to a fetch() call by incrementally
11782 // loading items, or sequentially loading partial batches of the result
11783 // set. For datastores that mixin simpleFetch, simpleFetch
11784 // implements a fetch method that automatically handles eight of the fetch()
11785 // arguments -- onBegin, onItem, onComplete, onError, start, count, sort and scope
11786 // The class mixing in simpleFetch should not implement fetch(),
11787 // but should instead implement a _fetchItems() method. The _fetchItems()
11788 // method takes three arguments, the keywordArgs object that was passed
11789 // to fetch(), a callback function to be called when the result array is
11790 // available, and an error callback to be called if something goes wrong.
11791 // The _fetchItems() method should ignore any keywordArgs parameters for
11792 // start, count, onBegin, onItem, onComplete, onError, sort, and scope.
11793 // The _fetchItems() method needs to correctly handle any other keywordArgs
11794 // parameters, including the query parameter and any optional parameters
11795 // (such as includeChildren). The _fetchItems() method should create an array of
11796 // result items and pass it to the fetchHandler along with the original request object
11797 // -- or, the _fetchItems() method may, if it wants to, create an new request object
11798 // with other specifics about the request that are specific to the datastore and pass
11799 // that as the request object to the handler.
11800 //
11801 // For more information on this specific function, see dojo.data.api.Read.fetch()
11802 request = request || {};
11803 if(!request.store){
11804 request.store = this;
11805 }
11806 var self = this;
11807
11808 var _errorHandler = function(errorData, requestObject){
11809 if(requestObject.onError){
11810 var scope = requestObject.scope || dojo.global;
11811 requestObject.onError.call(scope, errorData, requestObject);
11812 }
11813 };
11814
11815 var _fetchHandler = function(items, requestObject){
11816 var oldAbortFunction = requestObject.abort || null;
11817 var aborted = false;
11818
11819 var startIndex = requestObject.start?requestObject.start:0;
11820 var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length;
11821
11822 requestObject.abort = function(){
11823 aborted = true;
11824 if(oldAbortFunction){
11825 oldAbortFunction.call(requestObject);
11826 }
11827 };
11828
11829 var scope = requestObject.scope || dojo.global;
11830 if(!requestObject.store){
11831 requestObject.store = self;
11832 }
11833 if(requestObject.onBegin){
11834 requestObject.onBegin.call(scope, items.length, requestObject);
11835 }
11836 if(requestObject.sort){
11837 items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self));
11838 }
11839 if(requestObject.onItem){
11840 for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){
11841 var item = items[i];
11842 if(!aborted){
11843 requestObject.onItem.call(scope, item, requestObject);
11844 }
11845 }
11846 }
11847 if(requestObject.onComplete && !aborted){
11848 var subset = null;
11849 if(!requestObject.onItem){
11850 subset = items.slice(startIndex, endIndex);
11851 }
11852 requestObject.onComplete.call(scope, subset, requestObject);
11853 }
11854 };
11855 this._fetchItems(request, _fetchHandler, _errorHandler);
11856 return request; // Object
11857 };
11858
11859 }
11860
11861 if(!dojo._hasResource["dojo.data.util.filter"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11862 dojo._hasResource["dojo.data.util.filter"] = true;
11863 dojo.provide("dojo.data.util.filter");
11864
11865 dojo.data.util.filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/ ignoreCase){
11866 // summary:
11867 // Helper function to convert a simple pattern to a regular expression for matching.
11868 // description:
11869 // Returns a regular expression object that conforms to the defined conversion rules.
11870 // For example:
11871 // ca* -> /^ca.*$/
11872 // *ca* -> /^.*ca.*$/
11873 // *c\*a* -> /^.*c\*a.*$/
11874 // *c\*a?* -> /^.*c\*a..*$/
11875 // and so on.
11876 //
11877 // pattern: string
11878 // A simple matching pattern to convert that follows basic rules:
11879 // * Means match anything, so ca* means match anything starting with ca
11880 // ? Means match single character. So, b?b will match to bob and bab, and so on.
11881 // \ is an escape character. So for example, \* means do not treat * as a match, but literal character *.
11882 // To use a \ as a character in the string, it must be escaped. So in the pattern it should be
11883 // represented by \\ to be treated as an ordinary \ character instead of an escape.
11884 //
11885 // ignoreCase:
11886 // An optional flag to indicate if the pattern matching should be treated as case-sensitive or not when comparing
11887 // By default, it is assumed case sensitive.
11888
11889 var rxp = "^";
11890 var c = null;
11891 for(var i = 0; i < pattern.length; i++){
11892 c = pattern.charAt(i);
11893 switch(c){
11894 case '\\':
11895 rxp += c;
11896 i++;
11897 rxp += pattern.charAt(i);
11898 break;
11899 case '*':
11900 rxp += ".*"; break;
11901 case '?':
11902 rxp += "."; break;
11903 case '$':
11904 case '^':
11905 case '/':
11906 case '+':
11907 case '.':
11908 case '|':
11909 case '(':
11910 case ')':
11911 case '{':
11912 case '}':
11913 case '[':
11914 case ']':
11915 rxp += "\\"; //fallthrough
11916 default:
11917 rxp += c;
11918 }
11919 }
11920 rxp += "$";
11921 if(ignoreCase){
11922 return new RegExp(rxp,"mi"); //RegExp
11923 }else{
11924 return new RegExp(rxp,"m"); //RegExp
11925 }
11926
11927 };
11928
11929 }
11930
11931 if(!dojo._hasResource["dijit.form.TextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11932 dojo._hasResource["dijit.form.TextBox"] = true;
11933 dojo.provide("dijit.form.TextBox");
11934
11935
11936
11937 dojo.declare(
11938 "dijit.form.TextBox",
11939 dijit.form._FormValueWidget,
11940 {
11941 // summary:
11942 // A base class for textbox form inputs
11943
11944 // trim: Boolean
11945 // Removes leading and trailing whitespace if true. Default is false.
11946 trim: false,
11947
11948 // uppercase: Boolean
11949 // Converts all characters to uppercase if true. Default is false.
11950 uppercase: false,
11951
11952 // lowercase: Boolean
11953 // Converts all characters to lowercase if true. Default is false.
11954 lowercase: false,
11955
11956 // propercase: Boolean
11957 // Converts the first character of each word to uppercase if true.
11958 propercase: false,
11959
11960 // maxLength: String
11961 // HTML INPUT tag maxLength declaration.
11962 maxLength: "",
11963
11964 // selectOnClick: [const] Boolean
11965 // If true, all text will be selected when focused with mouse
11966 selectOnClick: false,
11967
11968 // placeHolder: String
11969 // Defines a hint to help users fill out the input field (as defined in HTML 5).
11970 // This should only contain plain text (no html markup).
11971 placeHolder: "",
11972
11973 templateString: dojo.cache("dijit.form", "templates/TextBox.html", "<div class=\"dijit dijitReset dijitInline dijitLeft\" id=\"widget_${id}\" waiRole=\"presentation\"\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
11974 _singleNodeTemplate: '<input class="dijit dijitReset dijitLeft dijitInputField" dojoAttachPoint="textbox,focusNode" autocomplete="off" type="${type}" ${!nameAttrSetting} />',
11975
11976 _buttonInputDisabled: dojo.isIE ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events
11977
11978 baseClass: "dijitTextBox",
11979
11980 attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, {
11981 maxLength: "focusNode"
11982 }),
11983
11984 postMixInProperties: function(){
11985 var type = this.type.toLowerCase();
11986 if(this.templateString.toLowerCase() == "input" || ((type == "hidden" || type == "file") && this.templateString == dijit.form.TextBox.prototype.templateString)){
11987 this.templateString = this._singleNodeTemplate;
11988 }
11989 this.inherited(arguments);
11990 },
11991
11992 _setPlaceHolderAttr: function(v){
11993 this.placeHolder = v;
11994 if(!this._phspan){
11995 this._attachPoints.push('_phspan');
11996 /* dijitInputField class gives placeHolder same padding as the input field
11997 * parent node already has dijitInputField class but it doesn't affect this <span>
11998 * since it's position: absolute.
11999 */
12000 this._phspan = dojo.create('span',{className:'dijitPlaceHolder dijitInputField'},this.textbox,'after');
12001 }
12002 this._phspan.innerHTML="";
12003 this._phspan.appendChild(document.createTextNode(v));
12004
12005 this._updatePlaceHolder();
12006 },
12007
12008 _updatePlaceHolder: function(){
12009 if(this._phspan){
12010 this._phspan.style.display=(this.placeHolder&&!this._focused&&!this.textbox.value)?"":"none";
12011 }
12012 },
12013
12014 _getValueAttr: function(){
12015 // summary:
12016 // Hook so attr('value') works as we like.
12017 // description:
12018 // For `dijit.form.TextBox` this basically returns the value of the <input>.
12019 //
12020 // For `dijit.form.MappedTextBox` subclasses, which have both
12021 // a "displayed value" and a separate "submit value",
12022 // This treats the "displayed value" as the master value, computing the
12023 // submit value from it via this.parse().
12024 return this.parse(this.get('displayedValue'), this.constraints);
12025 },
12026
12027 _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
12028 // summary:
12029 // Hook so attr('value', ...) works.
12030 //
12031 // description:
12032 // Sets the value of the widget to "value" which can be of
12033 // any type as determined by the widget.
12034 //
12035 // value:
12036 // The visual element value is also set to a corresponding,
12037 // but not necessarily the same, value.
12038 //
12039 // formattedValue:
12040 // If specified, used to set the visual element value,
12041 // otherwise a computed visual value is used.
12042 //
12043 // priorityChange:
12044 // If true, an onChange event is fired immediately instead of
12045 // waiting for the next blur event.
12046
12047 var filteredValue;
12048 if(value !== undefined){
12049 // TODO: this is calling filter() on both the display value and the actual value.
12050 // I added a comment to the filter() definition about this, but it should be changed.
12051 filteredValue = this.filter(value);
12052 if(typeof formattedValue != "string"){
12053 if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){
12054 formattedValue = this.filter(this.format(filteredValue, this.constraints));
12055 }else{ formattedValue = ''; }
12056 }
12057 }
12058 if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){
12059 this.textbox.value = formattedValue;
12060 }
12061
12062 this._updatePlaceHolder();
12063
12064 this.inherited(arguments, [filteredValue, priorityChange]);
12065 },
12066
12067 // displayedValue: String
12068 // For subclasses like ComboBox where the displayed value
12069 // (ex: Kentucky) and the serialized value (ex: KY) are different,
12070 // this represents the displayed value.
12071 //
12072 // Setting 'displayedValue' through attr('displayedValue', ...)
12073 // updates 'value', and vice-versa. Otherwise 'value' is updated
12074 // from 'displayedValue' periodically, like onBlur etc.
12075 //
12076 // TODO: move declaration to MappedTextBox?
12077 // Problem is that ComboBox references displayedValue,
12078 // for benefit of FilteringSelect.
12079 displayedValue: "",
12080
12081 getDisplayedValue: function(){
12082 // summary:
12083 // Deprecated. Use set('displayedValue') instead.
12084 // tags:
12085 // deprecated
12086 dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use set('displayedValue') instead.", "", "2.0");
12087 return this.get('displayedValue');
12088 },
12089
12090 _getDisplayedValueAttr: function(){
12091 // summary:
12092 // Hook so attr('displayedValue') works.
12093 // description:
12094 // Returns the displayed value (what the user sees on the screen),
12095 // after filtering (ie, trimming spaces etc.).
12096 //
12097 // For some subclasses of TextBox (like ComboBox), the displayed value
12098 // is different from the serialized value that's actually
12099 // sent to the server (see dijit.form.ValidationTextBox.serialize)
12100
12101 return this.filter(this.textbox.value);
12102 },
12103
12104 setDisplayedValue: function(/*String*/value){
12105 // summary:
12106 // Deprecated. Use set('displayedValue', ...) instead.
12107 // tags:
12108 // deprecated
12109 dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use set('displayedValue', ...) instead.", "", "2.0");
12110 this.set('displayedValue', value);
12111 },
12112
12113 _setDisplayedValueAttr: function(/*String*/value){
12114 // summary:
12115 // Hook so attr('displayedValue', ...) works.
12116 // description:
12117 // Sets the value of the visual element to the string "value".
12118 // The widget value is also set to a corresponding,
12119 // but not necessarily the same, value.
12120
12121 if(value === null || value === undefined){ value = '' }
12122 else if(typeof value != "string"){ value = String(value) }
12123 this.textbox.value = value;
12124 this._setValueAttr(this.get('value'), undefined, value);
12125 },
12126
12127 format: function(/* String */ value, /* Object */ constraints){
12128 // summary:
12129 // Replacable function to convert a value to a properly formatted string.
12130 // tags:
12131 // protected extension
12132 return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
12133 },
12134
12135 parse: function(/* String */ value, /* Object */ constraints){
12136 // summary:
12137 // Replacable function to convert a formatted string to a value
12138 // tags:
12139 // protected extension
12140
12141 return value; // String
12142 },
12143
12144 _refreshState: function(){
12145 // summary:
12146 // After the user types some characters, etc., this method is
12147 // called to check the field for validity etc. The base method
12148 // in `dijit.form.TextBox` does nothing, but subclasses override.
12149 // tags:
12150 // protected
12151 },
12152
12153 _onInput: function(e){
12154 if(e && e.type && /key/i.test(e.type) && e.keyCode){
12155 switch(e.keyCode){
12156 case dojo.keys.SHIFT:
12157 case dojo.keys.ALT:
12158 case dojo.keys.CTRL:
12159 case dojo.keys.TAB:
12160 return;
12161 }
12162 }
12163 if(this.intermediateChanges){
12164 var _this = this;
12165 // the setTimeout allows the key to post to the widget input box
12166 setTimeout(function(){ _this._handleOnChange(_this.get('value'), false); }, 0);
12167 }
12168 this._refreshState();
12169 },
12170
12171 postCreate: function(){
12172 // setting the value here is needed since value="" in the template causes "undefined"
12173 // and setting in the DOM (instead of the JS object) helps with form reset actions
12174 if(dojo.isIE){ // IE INPUT tag fontFamily has to be set directly using STYLE
12175 var s = dojo.getComputedStyle(this.domNode);
12176 if(s){
12177 var ff = s.fontFamily;
12178 if(ff){
12179 var inputs = this.domNode.getElementsByTagName("INPUT");
12180 if(inputs){
12181 for(var i=0; i < inputs.length; i++){
12182 inputs[i].style.fontFamily = ff;
12183 }
12184 }
12185 }
12186 }
12187 }
12188 this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values shuld be the same
12189 this.inherited(arguments);
12190 if(dojo.isMoz || dojo.isOpera){
12191 this.connect(this.textbox, "oninput", this._onInput);
12192 }else{
12193 this.connect(this.textbox, "onkeydown", this._onInput);
12194 this.connect(this.textbox, "onkeyup", this._onInput);
12195 this.connect(this.textbox, "onpaste", this._onInput);
12196 this.connect(this.textbox, "oncut", this._onInput);
12197 }
12198 },
12199
12200 _blankValue: '', // if the textbox is blank, what value should be reported
12201 filter: function(val){
12202 // summary:
12203 // Auto-corrections (such as trimming) that are applied to textbox
12204 // value on blur or form submit.
12205 // description:
12206 // For MappedTextBox subclasses, this is called twice
12207 // - once with the display value
12208 // - once the value as set/returned by attr('value', ...)
12209 // and attr('value'), ex: a Number for NumberTextBox.
12210 //
12211 // In the latter case it does corrections like converting null to NaN. In
12212 // the former case the NumberTextBox.filter() method calls this.inherited()
12213 // to execute standard trimming code in TextBox.filter().
12214 //
12215 // TODO: break this into two methods in 2.0
12216 //
12217 // tags:
12218 // protected extension
12219 if(val === null){ return this._blankValue; }
12220 if(typeof val != "string"){ return val; }
12221 if(this.trim){
12222 val = dojo.trim(val);
12223 }
12224 if(this.uppercase){
12225 val = val.toUpperCase();
12226 }
12227 if(this.lowercase){
12228 val = val.toLowerCase();
12229 }
12230 if(this.propercase){
12231 val = val.replace(/[^\s]+/g, function(word){
12232 return word.substring(0,1).toUpperCase() + word.substring(1);
12233 });
12234 }
12235 return val;
12236 },
12237
12238 _setBlurValue: function(){
12239 this._setValueAttr(this.get('value'), true);
12240 },
12241
12242 _onBlur: function(e){
12243 if(this.disabled){ return; }
12244 this._setBlurValue();
12245 this.inherited(arguments);
12246
12247 if(this._selectOnClickHandle){
12248 this.disconnect(this._selectOnClickHandle);
12249 }
12250 if(this.selectOnClick && dojo.isMoz){
12251 this.textbox.selectionStart = this.textbox.selectionEnd = undefined; // clear selection so that the next mouse click doesn't reselect
12252 }
12253
12254 this._updatePlaceHolder();
12255 },
12256
12257 _onFocus: function(/*String*/ by){
12258 if(this.disabled || this.readOnly){ return; }
12259
12260 // Select all text on focus via click if nothing already selected.
12261 // Since mouse-up will clear the selection need to defer selection until after mouse-up.
12262 // Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event.
12263 if(this.selectOnClick && by == "mouse"){
12264 this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){
12265 // Only select all text on first click; otherwise users would have no way to clear
12266 // the selection.
12267 this.disconnect(this._selectOnClickHandle);
12268
12269 // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up)
12270 // and if not, then select all the text
12271 var textIsNotSelected;
12272 if(dojo.isIE){
12273 var range = dojo.doc.selection.createRange();
12274 var parent = range.parentElement();
12275 textIsNotSelected = parent == this.textbox && range.text.length == 0;
12276 }else{
12277 textIsNotSelected = this.textbox.selectionStart == this.textbox.selectionEnd;
12278 }
12279 if(textIsNotSelected){
12280 dijit.selectInputText(this.textbox);
12281 }
12282 });
12283 }
12284
12285 this._updatePlaceHolder();
12286
12287 this._refreshState();
12288 this.inherited(arguments);
12289 },
12290
12291 reset: function(){
12292 // Overrides dijit._FormWidget.reset().
12293 // Additionally resets the displayed textbox value to ''
12294 this.textbox.value = '';
12295 this.inherited(arguments);
12296 }
12297 }
12298 );
12299
12300 dijit.selectInputText = function(/*DomNode*/element, /*Number?*/ start, /*Number?*/ stop){
12301 // summary:
12302 // Select text in the input element argument, from start (default 0), to stop (default end).
12303
12304 // TODO: use functions in _editor/selection.js?
12305 var _window = dojo.global;
12306 var _document = dojo.doc;
12307 element = dojo.byId(element);
12308 if(isNaN(start)){ start = 0; }
12309 if(isNaN(stop)){ stop = element.value ? element.value.length : 0; }
12310 dijit.focus(element);
12311 if(_document["selection"] && dojo.body()["createTextRange"]){ // IE
12312 if(element.createTextRange){
12313 var range = element.createTextRange();
12314 with(range){
12315 collapse(true);
12316 moveStart("character", -99999); // move to 0
12317 moveStart("character", start); // delta from 0 is the correct position
12318 moveEnd("character", stop-start);
12319 select();
12320 }
12321 }
12322 }else if(_window["getSelection"]){
12323 if(element.setSelectionRange){
12324 element.setSelectionRange(start, stop);
12325 }
12326 }
12327 };
12328
12329 }
12330
12331 if(!dojo._hasResource["dijit.Tooltip"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12332 dojo._hasResource["dijit.Tooltip"] = true;
12333 dojo.provide("dijit.Tooltip");
12334
12335
12336
12337
12338 dojo.declare(
12339 "dijit._MasterTooltip",
12340 [dijit._Widget, dijit._Templated],
12341 {
12342 // summary:
12343 // Internal widget that holds the actual tooltip markup,
12344 // which occurs once per page.
12345 // Called by Tooltip widgets which are just containers to hold
12346 // the markup
12347 // tags:
12348 // protected
12349
12350 // duration: Integer
12351 // Milliseconds to fade in/fade out
12352 duration: dijit.defaultDuration,
12353
12354 templateString: dojo.cache("dijit", "templates/Tooltip.html", "<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\">\n\t<div class=\"dijitTooltipContainer dijitTooltipContents\" dojoAttachPoint=\"containerNode\" waiRole='alert'></div>\n\t<div class=\"dijitTooltipConnector\"></div>\n</div>\n"),
12355
12356 postCreate: function(){
12357 dojo.body().appendChild(this.domNode);
12358
12359 this.bgIframe = new dijit.BackgroundIframe(this.domNode);
12360
12361 // Setup fade-in and fade-out functions.
12362 this.fadeIn = dojo.fadeIn({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onShow") });
12363 this.fadeOut = dojo.fadeOut({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onHide") });
12364
12365 },
12366
12367 show: function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
12368 // summary:
12369 // Display tooltip w/specified contents to right of specified node
12370 // (To left if there's no space on the right, or if rtl == true)
12371
12372 if(this.aroundNode && this.aroundNode === aroundNode){
12373 return;
12374 }
12375
12376 if(this.fadeOut.status() == "playing"){
12377 // previous tooltip is being hidden; wait until the hide completes then show new one
12378 this._onDeck=arguments;
12379 return;
12380 }
12381 this.containerNode.innerHTML=innerHTML;
12382
12383 var pos = dijit.placeOnScreenAroundElement(this.domNode, aroundNode, dijit.getPopupAroundAlignment((position && position.length) ? position : dijit.Tooltip.defaultPosition, !rtl), dojo.hitch(this, "orient"));
12384
12385 // show it
12386 dojo.style(this.domNode, "opacity", 0);
12387 this.fadeIn.play();
12388 this.isShowingNow = true;
12389 this.aroundNode = aroundNode;
12390 },
12391
12392 orient: function(/* DomNode */ node, /* String */ aroundCorner, /* String */ tooltipCorner){
12393 // summary:
12394 // Private function to set CSS for tooltip node based on which position it's in.
12395 // This is called by the dijit popup code.
12396 // tags:
12397 // protected
12398
12399 node.className = "dijitTooltip " +
12400 {
12401 "BL-TL": "dijitTooltipBelow dijitTooltipABLeft",
12402 "TL-BL": "dijitTooltipAbove dijitTooltipABLeft",
12403 "BR-TR": "dijitTooltipBelow dijitTooltipABRight",
12404 "TR-BR": "dijitTooltipAbove dijitTooltipABRight",
12405 "BR-BL": "dijitTooltipRight",
12406 "BL-BR": "dijitTooltipLeft"
12407 }[aroundCorner + "-" + tooltipCorner];
12408 },
12409
12410 _onShow: function(){
12411 // summary:
12412 // Called at end of fade-in operation
12413 // tags:
12414 // protected
12415 if(dojo.isIE){
12416 // the arrow won't show up on a node w/an opacity filter
12417 this.domNode.style.filter="";
12418 }
12419 },
12420
12421 hide: function(aroundNode){
12422 // summary:
12423 // Hide the tooltip
12424 if(this._onDeck && this._onDeck[1] == aroundNode){
12425 // this hide request is for a show() that hasn't even started yet;
12426 // just cancel the pending show()
12427 this._onDeck=null;
12428 }else if(this.aroundNode === aroundNode){
12429 // this hide request is for the currently displayed tooltip
12430 this.fadeIn.stop();
12431 this.isShowingNow = false;
12432 this.aroundNode = null;
12433 this.fadeOut.play();
12434 }else{
12435 // just ignore the call, it's for a tooltip that has already been erased
12436 }
12437 },
12438
12439 _onHide: function(){
12440 // summary:
12441 // Called at end of fade-out operation
12442 // tags:
12443 // protected
12444
12445 this.domNode.style.cssText=""; // to position offscreen again
12446 this.containerNode.innerHTML="";
12447 if(this._onDeck){
12448 // a show request has been queued up; do it now
12449 this.show.apply(this, this._onDeck);
12450 this._onDeck=null;
12451 }
12452 }
12453
12454 }
12455 );
12456
12457 dijit.showTooltip = function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
12458 // summary:
12459 // Display tooltip w/specified contents in specified position.
12460 // See description of dijit.Tooltip.defaultPosition for details on position parameter.
12461 // If position is not specified then dijit.Tooltip.defaultPosition is used.
12462 if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); }
12463 return dijit._masterTT.show(innerHTML, aroundNode, position, rtl);
12464 };
12465
12466 dijit.hideTooltip = function(aroundNode){
12467 // summary:
12468 // Hide the tooltip
12469 if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); }
12470 return dijit._masterTT.hide(aroundNode);
12471 };
12472
12473 dojo.declare(
12474 "dijit.Tooltip",
12475 dijit._Widget,
12476 {
12477 // summary:
12478 // Pops up a tooltip (a help message) when you hover over a node.
12479
12480 // label: String
12481 // Text to display in the tooltip.
12482 // Specified as innerHTML when creating the widget from markup.
12483 label: "",
12484
12485 // showDelay: Integer
12486 // Number of milliseconds to wait after hovering over/focusing on the object, before
12487 // the tooltip is displayed.
12488 showDelay: 400,
12489
12490 // connectId: [const] String[]
12491 // Id's of domNodes to attach the tooltip to.
12492 // When user hovers over any of the specified dom nodes, the tooltip will appear.
12493 //
12494 // Note: Currently connectId can only be specified on initialization, it cannot
12495 // be changed via attr('connectId', ...)
12496 //
12497 // Note: in 2.0 this will be renamed to connectIds for less confusion.
12498 connectId: [],
12499
12500 // position: String[]
12501 // See description of `dijit.Tooltip.defaultPosition` for details on position parameter.
12502 position: [],
12503
12504 constructor: function(){
12505 // Map id's of nodes I'm connected to to a list of the this.connect() handles
12506 this._nodeConnectionsById = {};
12507 },
12508
12509 _setConnectIdAttr: function(newIds){
12510 for(var oldId in this._nodeConnectionsById){
12511 this.removeTarget(oldId);
12512 }
12513 dojo.forEach(dojo.isArrayLike(newIds) ? newIds : [newIds], this.addTarget, this);
12514 },
12515
12516 _getConnectIdAttr: function(){
12517 var ary = [];
12518 for(var id in this._nodeConnectionsById){
12519 ary.push(id);
12520 }
12521 return ary;
12522 },
12523
12524 addTarget: function(/*DOMNODE || String*/ id){
12525 // summary:
12526 // Attach tooltip to specified node, if it's not already connected
12527 var node = dojo.byId(id);
12528 if(!node){ return; }
12529 if(node.id in this._nodeConnectionsById){ return; }//Already connected
12530
12531 this._nodeConnectionsById[node.id] = [
12532 this.connect(node, "onmouseenter", "_onTargetMouseEnter"),
12533 this.connect(node, "onmouseleave", "_onTargetMouseLeave"),
12534 this.connect(node, "onfocus", "_onTargetFocus"),
12535 this.connect(node, "onblur", "_onTargetBlur")
12536 ];
12537 },
12538
12539 removeTarget: function(/*DOMNODE || String*/ node){
12540 // summary:
12541 // Detach tooltip from specified node
12542
12543 // map from DOMNode back to plain id string
12544 var id = node.id || node;
12545
12546 if(id in this._nodeConnectionsById){
12547 dojo.forEach(this._nodeConnectionsById[id], this.disconnect, this);
12548 delete this._nodeConnectionsById[id];
12549 }
12550 },
12551
12552 postCreate: function(){
12553 dojo.addClass(this.domNode,"dijitTooltipData");
12554 },
12555
12556 startup: function(){
12557 this.inherited(arguments);
12558
12559 // If this tooltip was created in a template, or for some other reason the specified connectId[s]
12560 // didn't exist during the widget's initialization, then connect now.
12561 var ids = this.connectId;
12562 dojo.forEach(dojo.isArrayLike(ids) ? ids : [ids], this.addTarget, this);
12563 },
12564
12565 _onTargetMouseEnter: function(/*Event*/ e){
12566 // summary:
12567 // Handler for mouseenter event on the target node
12568 // tags:
12569 // private
12570 this._onHover(e);
12571 },
12572
12573 _onTargetMouseLeave: function(/*Event*/ e){
12574 // summary:
12575 // Handler for mouseleave event on the target node
12576 // tags:
12577 // private
12578 this._onUnHover(e);
12579 },
12580
12581 _onTargetFocus: function(/*Event*/ e){
12582 // summary:
12583 // Handler for focus event on the target node
12584 // tags:
12585 // private
12586
12587 this._focus = true;
12588 this._onHover(e);
12589 },
12590
12591 _onTargetBlur: function(/*Event*/ e){
12592 // summary:
12593 // Handler for blur event on the target node
12594 // tags:
12595 // private
12596
12597 this._focus = false;
12598 this._onUnHover(e);
12599 },
12600
12601 _onHover: function(/*Event*/ e){
12602 // summary:
12603 // Despite the name of this method, it actually handles both hover and focus
12604 // events on the target node, setting a timer to show the tooltip.
12605 // tags:
12606 // private
12607 if(!this._showTimer){
12608 var target = e.target;
12609 this._showTimer = setTimeout(dojo.hitch(this, function(){this.open(target)}), this.showDelay);
12610 }
12611 },
12612
12613 _onUnHover: function(/*Event*/ e){
12614 // summary:
12615 // Despite the name of this method, it actually handles both mouseleave and blur
12616 // events on the target node, hiding the tooltip.
12617 // tags:
12618 // private
12619
12620 // keep a tooltip open if the associated element still has focus (even though the
12621 // mouse moved away)
12622 if(this._focus){ return; }
12623
12624 if(this._showTimer){
12625 clearTimeout(this._showTimer);
12626 delete this._showTimer;
12627 }
12628 this.close();
12629 },
12630
12631 open: function(/*DomNode*/ target){
12632 // summary:
12633 // Display the tooltip; usually not called directly.
12634 // tags:
12635 // private
12636
12637 if(this._showTimer){
12638 clearTimeout(this._showTimer);
12639 delete this._showTimer;
12640 }
12641 dijit.showTooltip(this.label || this.domNode.innerHTML, target, this.position, !this.isLeftToRight());
12642
12643 this._connectNode = target;
12644 this.onShow(target, this.position);
12645 },
12646
12647 close: function(){
12648 // summary:
12649 // Hide the tooltip or cancel timer for show of tooltip
12650 // tags:
12651 // private
12652
12653 if(this._connectNode){
12654 // if tooltip is currently shown
12655 dijit.hideTooltip(this._connectNode);
12656 delete this._connectNode;
12657 this.onHide();
12658 }
12659 if(this._showTimer){
12660 // if tooltip is scheduled to be shown (after a brief delay)
12661 clearTimeout(this._showTimer);
12662 delete this._showTimer;
12663 }
12664 },
12665
12666 onShow: function(target, position){
12667 // summary:
12668 // Called when the tooltip is shown
12669 // tags:
12670 // callback
12671 },
12672
12673 onHide: function(){
12674 // summary:
12675 // Called when the tooltip is hidden
12676 // tags:
12677 // callback
12678 },
12679
12680 uninitialize: function(){
12681 this.close();
12682 this.inherited(arguments);
12683 }
12684 }
12685 );
12686
12687 // dijit.Tooltip.defaultPosition: String[]
12688 // This variable controls the position of tooltips, if the position is not specified to
12689 // the Tooltip widget or *TextBox widget itself. It's an array of strings with the following values:
12690 //
12691 // * before: places tooltip to the left of the target node/widget, or to the right in
12692 // the case of RTL scripts like Hebrew and Arabic
12693 // * after: places tooltip to the right of the target node/widget, or to the left in
12694 // the case of RTL scripts like Hebrew and Arabic
12695 // * above: tooltip goes above target node
12696 // * below: tooltip goes below target node
12697 //
12698 // The list is positions is tried, in order, until a position is found where the tooltip fits
12699 // within the viewport.
12700 //
12701 // Be careful setting this parameter. A value of "above" may work fine until the user scrolls
12702 // the screen so that there's no room above the target node. Nodes with drop downs, like
12703 // DropDownButton or FilteringSelect, are especially problematic, in that you need to be sure
12704 // that the drop down and tooltip don't overlap, even when the viewport is scrolled so that there
12705 // is only room below (or above) the target node, but not both.
12706 dijit.Tooltip.defaultPosition = ["after", "before"];
12707
12708 }
12709
12710 if(!dojo._hasResource["dijit.form.ValidationTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12711 dojo._hasResource["dijit.form.ValidationTextBox"] = true;
12712 dojo.provide("dijit.form.ValidationTextBox");
12713
12714
12715
12716
12717
12718
12719
12720
12721 /*=====
12722 dijit.form.ValidationTextBox.__Constraints = function(){
12723 // locale: String
12724 // locale used for validation, picks up value from this widget's lang attribute
12725 // _flags_: anything
12726 // various flags passed to regExpGen function
12727 this.locale = "";
12728 this._flags_ = "";
12729 }
12730 =====*/
12731
12732 dojo.declare(
12733 "dijit.form.ValidationTextBox",
12734 dijit.form.TextBox,
12735 {
12736 // summary:
12737 // Base class for textbox widgets with the ability to validate content of various types and provide user feedback.
12738 // tags:
12739 // protected
12740
12741 templateString: dojo.cache("dijit.form", "templates/ValidationTextBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" waiRole=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&Chi; \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
12742 baseClass: "dijitTextBox dijitValidationTextBox",
12743
12744 // required: Boolean
12745 // User is required to enter data into this field.
12746 required: false,
12747
12748 // promptMessage: String
12749 // If defined, display this hint string immediately on focus to the textbox, if empty.
12750 // Think of this like a tooltip that tells the user what to do, not an error message
12751 // that tells the user what they've done wrong.
12752 //
12753 // Message disappears when user starts typing.
12754 promptMessage: "",
12755
12756 // invalidMessage: String
12757 // The message to display if value is invalid.
12758 // The translated string value is read from the message file by default.
12759 // Set to "" to use the promptMessage instead.
12760 invalidMessage: "$_unset_$",
12761
12762 // missingMessage: String
12763 // The message to display if value is empty and the field is required.
12764 // The translated string value is read from the message file by default.
12765 // Set to "" to use the invalidMessage instead.
12766 missingMessage: "$_unset_$",
12767
12768 // constraints: dijit.form.ValidationTextBox.__Constraints
12769 // user-defined object needed to pass parameters to the validator functions
12770 constraints: {},
12771
12772 // regExp: [extension protected] String
12773 // regular expression string used to validate the input
12774 // Do not specify both regExp and regExpGen
12775 regExp: ".*",
12776
12777 regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/constraints){
12778 // summary:
12779 // Overridable function used to generate regExp when dependent on constraints.
12780 // Do not specify both regExp and regExpGen.
12781 // tags:
12782 // extension protected
12783 return this.regExp; // String
12784 },
12785
12786 // state: [readonly] String
12787 // Shows current state (ie, validation result) of input (Normal, Warning, or Error)
12788 state: "",
12789
12790 // tooltipPosition: String[]
12791 // See description of `dijit.Tooltip.defaultPosition` for details on this parameter.
12792 tooltipPosition: [],
12793
12794 _setValueAttr: function(){
12795 // summary:
12796 // Hook so attr('value', ...) works.
12797 this.inherited(arguments);
12798 this.validate(this._focused);
12799 },
12800
12801 validator: function(/*anything*/value, /*dijit.form.ValidationTextBox.__Constraints*/constraints){
12802 // summary:
12803 // Overridable function used to validate the text input against the regular expression.
12804 // tags:
12805 // protected
12806 return (new RegExp("^(?:" + this.regExpGen(constraints) + ")"+(this.required?"":"?")+"$")).test(value) &&
12807 (!this.required || !this._isEmpty(value)) &&
12808 (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean
12809 },
12810
12811 _isValidSubset: function(){
12812 // summary:
12813 // Returns true if the value is either already valid or could be made valid by appending characters.
12814 // This is used for validation while the user [may be] still typing.
12815 return this.textbox.value.search(this._partialre) == 0;
12816 },
12817
12818 isValid: function(/*Boolean*/ isFocused){
12819 // summary:
12820 // Tests if value is valid.
12821 // Can override with your own routine in a subclass.
12822 // tags:
12823 // protected
12824 return this.validator(this.textbox.value, this.constraints);
12825 },
12826
12827 _isEmpty: function(value){
12828 // summary:
12829 // Checks for whitespace
12830 return /^\s*$/.test(value); // Boolean
12831 },
12832
12833 getErrorMessage: function(/*Boolean*/ isFocused){
12834 // summary:
12835 // Return an error message to show if appropriate
12836 // tags:
12837 // protected
12838 return (this.required && this._isEmpty(this.textbox.value)) ? this.missingMessage : this.invalidMessage; // String
12839 },
12840
12841 getPromptMessage: function(/*Boolean*/ isFocused){
12842 // summary:
12843 // Return a hint message to show when widget is first focused
12844 // tags:
12845 // protected
12846 return this.promptMessage; // String
12847 },
12848
12849 _maskValidSubsetError: true,
12850 validate: function(/*Boolean*/ isFocused){
12851 // summary:
12852 // Called by oninit, onblur, and onkeypress.
12853 // description:
12854 // Show missing or invalid messages if appropriate, and highlight textbox field.
12855 // tags:
12856 // protected
12857 var message = "";
12858 var isValid = this.disabled || this.isValid(isFocused);
12859 if(isValid){ this._maskValidSubsetError = true; }
12860 var isEmpty = this._isEmpty(this.textbox.value);
12861 var isValidSubset = !isValid && !isEmpty && isFocused && this._isValidSubset();
12862 this.state = ((isValid || ((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && this._maskValidSubsetError) ? "" : "Error";
12863 if(this.state == "Error"){ this._maskValidSubsetError = isFocused; } // we want the error to show up afer a blur and refocus
12864 this._setStateClass();
12865 dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true");
12866 if(isFocused){
12867 if(this.state == "Error"){
12868 message = this.getErrorMessage(true);
12869 }else{
12870 message = this.getPromptMessage(true); // show the prompt whever there's no error
12871 }
12872 this._maskValidSubsetError = true; // since we're focused, always mask warnings
12873 }
12874 this.displayMessage(message);
12875 return isValid;
12876 },
12877
12878 // _message: String
12879 // Currently displayed message
12880 _message: "",
12881
12882 displayMessage: function(/*String*/ message){
12883 // summary:
12884 // Overridable method to display validation errors/hints.
12885 // By default uses a tooltip.
12886 // tags:
12887 // extension
12888 if(this._message == message){ return; }
12889 this._message = message;
12890 dijit.hideTooltip(this.domNode);
12891 if(message){
12892 dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
12893 }
12894 },
12895
12896 _refreshState: function(){
12897 // Overrides TextBox._refreshState()
12898 this.validate(this._focused);
12899 this.inherited(arguments);
12900 },
12901
12902 //////////// INITIALIZATION METHODS ///////////////////////////////////////
12903
12904 constructor: function(){
12905 this.constraints = {};
12906 },
12907
12908 _setConstraintsAttr: function(/* Object */ constraints){
12909 if(!constraints.locale && this.lang){
12910 constraints.locale = this.lang;
12911 }
12912 this.constraints = constraints;
12913 this._computePartialRE();
12914 },
12915
12916 _computePartialRE: function(){
12917 var p = this.regExpGen(this.constraints);
12918 this.regExp = p;
12919 var partialre = "";
12920 // parse the regexp and produce a new regexp that matches valid subsets
12921 // if the regexp is .* then there's no use in matching subsets since everything is valid
12922 if(p != ".*"){ this.regExp.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g,
12923 function (re){
12924 switch(re.charAt(0)){
12925 case '{':
12926 case '+':
12927 case '?':
12928 case '*':
12929 case '^':
12930 case '$':
12931 case '|':
12932 case '(':
12933 partialre += re;
12934 break;
12935 case ")":
12936 partialre += "|$)";
12937 break;
12938 default:
12939 partialre += "(?:"+re+"|$)";
12940 break;
12941 }
12942 }
12943 );}
12944 try{ // this is needed for now since the above regexp parsing needs more test verification
12945 "".search(partialre);
12946 }catch(e){ // should never be here unless the original RE is bad or the parsing is bad
12947 partialre = this.regExp;
12948 console.warn('RegExp error in ' + this.declaredClass + ': ' + this.regExp);
12949 } // should never be here unless the original RE is bad or the parsing is bad
12950 this._partialre = "^(?:" + partialre + ")$";
12951 },
12952
12953 postMixInProperties: function(){
12954 this.inherited(arguments);
12955 this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang);
12956 if(this.invalidMessage == "$_unset_$"){ this.invalidMessage = this.messages.invalidMessage; }
12957 if(!this.invalidMessage){ this.invalidMessage = this.promptMessage; }
12958 if(this.missingMessage == "$_unset_$"){ this.missingMessage = this.messages.missingMessage; }
12959 if(!this.missingMessage){ this.missingMessage = this.invalidMessage; }
12960 this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls attachPoints
12961 },
12962
12963 _setDisabledAttr: function(/*Boolean*/ value){
12964 this.inherited(arguments); // call FormValueWidget._setDisabledAttr()
12965 this._refreshState();
12966 },
12967
12968 _setRequiredAttr: function(/*Boolean*/ value){
12969 this.required = value;
12970 dijit.setWaiState(this.focusNode, "required", value);
12971 this._refreshState();
12972 },
12973
12974 reset:function(){
12975 // Overrides dijit.form.TextBox.reset() by also
12976 // hiding errors about partial matches
12977 this._maskValidSubsetError = true;
12978 this.inherited(arguments);
12979 },
12980
12981 _onBlur: function(){
12982 this.displayMessage('');
12983 this.inherited(arguments);
12984 }
12985 }
12986 );
12987
12988 dojo.declare(
12989 "dijit.form.MappedTextBox",
12990 dijit.form.ValidationTextBox,
12991 {
12992 // summary:
12993 // A dijit.form.ValidationTextBox subclass which provides a base class for widgets that have
12994 // a visible formatted display value, and a serializable
12995 // value in a hidden input field which is actually sent to the server.
12996 // description:
12997 // The visible display may
12998 // be locale-dependent and interactive. The value sent to the server is stored in a hidden
12999 // input field which uses the `name` attribute declared by the original widget. That value sent
13000 // to the server is defined by the dijit.form.MappedTextBox.serialize method and is typically
13001 // locale-neutral.
13002 // tags:
13003 // protected
13004
13005 postMixInProperties: function(){
13006 this.inherited(arguments);
13007
13008 // we want the name attribute to go to the hidden <input>, not the displayed <input>,
13009 // so override _FormWidget.postMixInProperties() setting of nameAttrSetting
13010 this.nameAttrSetting = "";
13011 },
13012
13013 serialize: function(/*anything*/val, /*Object?*/options){
13014 // summary:
13015 // Overridable function used to convert the attr('value') result to a canonical
13016 // (non-localized) string. For example, will print dates in ISO format, and
13017 // numbers the same way as they are represented in javascript.
13018 // tags:
13019 // protected extension
13020 return val.toString ? val.toString() : ""; // String
13021 },
13022
13023 toString: function(){
13024 // summary:
13025 // Returns widget as a printable string using the widget's value
13026 // tags:
13027 // protected
13028 var val = this.filter(this.get('value')); // call filter in case value is nonstring and filter has been customized
13029 return val != null ? (typeof val == "string" ? val : this.serialize(val, this.constraints)) : ""; // String
13030 },
13031
13032 validate: function(){
13033 // Overrides `dijit.form.TextBox.validate`
13034 this.valueNode.value = this.toString();
13035 return this.inherited(arguments);
13036 },
13037
13038 buildRendering: function(){
13039 // Overrides `dijit._Templated.buildRendering`
13040
13041 this.inherited(arguments);
13042
13043 // Create a hidden <input> node with the serialized value used for submit
13044 // (as opposed to the displayed value).
13045 // Passing in name as markup rather than calling dojo.create() with an attrs argument
13046 // to make dojo.query(input[name=...]) work on IE. (see #8660)
13047 this.valueNode = dojo.place("<input type='hidden'" + (this.name ? " name='" + this.name + "'" : "") + ">", this.textbox, "after");
13048 },
13049
13050 reset:function(){
13051 // Overrides `dijit.form.ValidationTextBox.reset` to
13052 // reset the hidden textbox value to ''
13053 this.valueNode.value = '';
13054 this.inherited(arguments);
13055 }
13056 }
13057 );
13058
13059 /*=====
13060 dijit.form.RangeBoundTextBox.__Constraints = function(){
13061 // min: Number
13062 // Minimum signed value. Default is -Infinity
13063 // max: Number
13064 // Maximum signed value. Default is +Infinity
13065 this.min = min;
13066 this.max = max;
13067 }
13068 =====*/
13069
13070 dojo.declare(
13071 "dijit.form.RangeBoundTextBox",
13072 dijit.form.MappedTextBox,
13073 {
13074 // summary:
13075 // Base class for textbox form widgets which defines a range of valid values.
13076
13077 // rangeMessage: String
13078 // The message to display if value is out-of-range
13079 rangeMessage: "",
13080
13081 /*=====
13082 // constraints: dijit.form.RangeBoundTextBox.__Constraints
13083 constraints: {},
13084 ======*/
13085
13086 rangeCheck: function(/*Number*/ primitive, /*dijit.form.RangeBoundTextBox.__Constraints*/ constraints){
13087 // summary:
13088 // Overridable function used to validate the range of the numeric input value.
13089 // tags:
13090 // protected
13091 return ("min" in constraints? (this.compare(primitive,constraints.min) >= 0) : true) &&
13092 ("max" in constraints? (this.compare(primitive,constraints.max) <= 0) : true); // Boolean
13093 },
13094
13095 isInRange: function(/*Boolean*/ isFocused){
13096 // summary:
13097 // Tests if the value is in the min/max range specified in constraints
13098 // tags:
13099 // protected
13100 return this.rangeCheck(this.get('value'), this.constraints);
13101 },
13102
13103 _isDefinitelyOutOfRange: function(){
13104 // summary:
13105 // Returns true if the value is out of range and will remain
13106 // out of range even if the user types more characters
13107 var val = this.get('value');
13108 var isTooLittle = false;
13109 var isTooMuch = false;
13110 if("min" in this.constraints){
13111 var min = this.constraints.min;
13112 min = this.compare(val, ((typeof min == "number") && min >= 0 && val !=0) ? 0 : min);
13113 isTooLittle = (typeof min == "number") && min < 0;
13114 }
13115 if("max" in this.constraints){
13116 var max = this.constraints.max;
13117 max = this.compare(val, ((typeof max != "number") || max > 0) ? max : 0);
13118 isTooMuch = (typeof max == "number") && max > 0;
13119 }
13120 return isTooLittle || isTooMuch;
13121 },
13122
13123 _isValidSubset: function(){
13124 // summary:
13125 // Overrides `dijit.form.ValidationTextBox._isValidSubset`.
13126 // Returns true if the input is syntactically valid, and either within
13127 // range or could be made in range by more typing.
13128 return this.inherited(arguments) && !this._isDefinitelyOutOfRange();
13129 },
13130
13131 isValid: function(/*Boolean*/ isFocused){
13132 // Overrides dijit.form.ValidationTextBox.isValid to check that the value is also in range.
13133 return this.inherited(arguments) &&
13134 ((this._isEmpty(this.textbox.value) && !this.required) || this.isInRange(isFocused)); // Boolean
13135 },
13136
13137 getErrorMessage: function(/*Boolean*/ isFocused){
13138 // Overrides dijit.form.ValidationTextBox.getErrorMessage to print "out of range" message if appropriate
13139 var v = this.get('value');
13140 if(v !== null && v !== '' && v !== undefined && (typeof v != "number" || !isNaN(v)) && !this.isInRange(isFocused)){ // don't check isInRange w/o a real value
13141 return this.rangeMessage; // String
13142 }
13143 return this.inherited(arguments);
13144 },
13145
13146 postMixInProperties: function(){
13147 this.inherited(arguments);
13148 if(!this.rangeMessage){
13149 this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang);
13150 this.rangeMessage = this.messages.rangeMessage;
13151 }
13152 },
13153
13154 _setConstraintsAttr: function(/* Object */ constraints){
13155 this.inherited(arguments);
13156 if(this.focusNode){ // not set when called from postMixInProperties
13157 if(this.constraints.min !== undefined){
13158 dijit.setWaiState(this.focusNode, "valuemin", this.constraints.min);
13159 }else{
13160 dijit.removeWaiState(this.focusNode, "valuemin");
13161 }
13162 if(this.constraints.max !== undefined){
13163 dijit.setWaiState(this.focusNode, "valuemax", this.constraints.max);
13164 }else{
13165 dijit.removeWaiState(this.focusNode, "valuemax");
13166 }
13167 }
13168 },
13169
13170 _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){
13171 // summary:
13172 // Hook so attr('value', ...) works.
13173
13174 dijit.setWaiState(this.focusNode, "valuenow", value);
13175 this.inherited(arguments);
13176 }
13177 }
13178 );
13179
13180 }
13181
13182 if(!dojo._hasResource["dijit.form.ComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
13183 dojo._hasResource["dijit.form.ComboBox"] = true;
13184 dojo.provide("dijit.form.ComboBox");
13185
13186
13187
13188
13189
13190
13191
13192
13193
13194
13195
13196
13197 dojo.declare(
13198 "dijit.form.ComboBoxMixin",
13199 null,
13200 {
13201 // summary:
13202 // Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect`
13203 // description:
13204 // All widgets that mix in dijit.form.ComboBoxMixin must extend `dijit.form._FormValueWidget`.
13205 // tags:
13206 // protected
13207
13208 // item: Object
13209 // This is the item returned by the dojo.data.store implementation that
13210 // provides the data for this ComboBox, it's the currently selected item.
13211 item: null,
13212
13213 // pageSize: Integer
13214 // Argument to data provider.
13215 // Specifies number of search results per page (before hitting "next" button)
13216 pageSize: Infinity,
13217
13218 // store: Object
13219 // Reference to data provider object used by this ComboBox
13220 store: null,
13221
13222 // fetchProperties: Object
13223 // Mixin to the dojo.data store's fetch.
13224 // For example, to set the sort order of the ComboBox menu, pass:
13225 // | { sort: {attribute:"name",descending: true} }
13226 // To override the default queryOptions so that deep=false, do:
13227 // | { queryOptions: {ignoreCase: true, deep: false} }
13228 fetchProperties:{},
13229
13230 // query: Object
13231 // A query that can be passed to 'store' to initially filter the items,
13232 // before doing further filtering based on `searchAttr` and the key.
13233 // Any reference to the `searchAttr` is ignored.
13234 query: {},
13235
13236 // autoComplete: Boolean
13237 // If user types in a partial string, and then tab out of the `<input>` box,
13238 // automatically copy the first entry displayed in the drop down list to
13239 // the `<input>` field
13240 autoComplete: true,
13241
13242 // highlightMatch: String
13243 // One of: "first", "all" or "none".
13244 //
13245 // If the ComboBox/FilteringSelect opens with the search results and the searched
13246 // string can be found, it will be highlighted. If set to "all"
13247 // then will probably want to change `queryExpr` parameter to '*${0}*'
13248 //
13249 // Highlighting is only performed when `labelType` is "text", so as to not
13250 // interfere with any HTML markup an HTML label might contain.
13251 highlightMatch: "first",
13252
13253 // searchDelay: Integer
13254 // Delay in milliseconds between when user types something and we start
13255 // searching based on that value
13256 searchDelay: 100,
13257
13258 // searchAttr: String
13259 // Search for items in the data store where this attribute (in the item)
13260 // matches what the user typed
13261 searchAttr: "name",
13262
13263 // labelAttr: String?
13264 // The entries in the drop down list come from this attribute in the
13265 // dojo.data items.
13266 // If not specified, the searchAttr attribute is used instead.
13267 labelAttr: "",
13268
13269 // labelType: String
13270 // Specifies how to interpret the labelAttr in the data store items.
13271 // Can be "html" or "text".
13272 labelType: "text",
13273
13274 // queryExpr: String
13275 // This specifies what query ComboBox/FilteringSelect sends to the data store,
13276 // based on what the user has typed. Changing this expression will modify
13277 // whether the drop down shows only exact matches, a "starting with" match,
13278 // etc. Use it in conjunction with highlightMatch.
13279 // dojo.data query expression pattern.
13280 // `${0}` will be substituted for the user text.
13281 // `*` is used for wildcards.
13282 // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is"
13283 queryExpr: "${0}*",
13284
13285 // ignoreCase: Boolean
13286 // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items
13287 ignoreCase: true,
13288
13289 // hasDownArrow: [const] Boolean
13290 // Set this textbox to have a down arrow button, to display the drop down list.
13291 // Defaults to true.
13292 hasDownArrow: true,
13293
13294 templateString: dojo.cache("dijit.form", "templates/ComboBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\tdojoAttachPoint=\"comboNode\" waiRole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"downArrowNode\" waiRole=\"presentation\"\n\t\tdojoAttachEvent=\"onmousedown:_onArrowMouseDown\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"&#9660; \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&Chi; \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onKeyPress,compositionend\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" waiRole=\"textbox\" waiState=\"haspopup-true,autocomplete-list\"\n\t/></div\n></div>\n"),
13295
13296 baseClass: "dijitTextBox dijitComboBox",
13297
13298 // Set classes like dijitDownArrowButtonHover depending on
13299 // mouse action over button node
13300 cssStateNodes: {
13301 "downArrowNode": "dijitDownArrowButton"
13302 },
13303
13304 _getCaretPos: function(/*DomNode*/ element){
13305 // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
13306 var pos = 0;
13307 if(typeof(element.selectionStart) == "number"){
13308 // FIXME: this is totally borked on Moz < 1.3. Any recourse?
13309 pos = element.selectionStart;
13310 }else if(dojo.isIE){
13311 // in the case of a mouse click in a popup being handled,
13312 // then the dojo.doc.selection is not the textarea, but the popup
13313 // var r = dojo.doc.selection.createRange();
13314 // hack to get IE 6 to play nice. What a POS browser.
13315 var tr = dojo.doc.selection.createRange().duplicate();
13316 var ntr = element.createTextRange();
13317 tr.move("character",0);
13318 ntr.move("character",0);
13319 try{
13320 // If control doesnt have focus, you get an exception.
13321 // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
13322 // There appears to be no workaround for this - googled for quite a while.
13323 ntr.setEndPoint("EndToEnd", tr);
13324 pos = String(ntr.text).replace(/\r/g,"").length;
13325 }catch(e){
13326 // If focus has shifted, 0 is fine for caret pos.
13327 }
13328 }
13329 return pos;
13330 },
13331
13332 _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
13333 location = parseInt(location);
13334 dijit.selectInputText(element, location, location);
13335 },
13336
13337 _setDisabledAttr: function(/*Boolean*/ value){
13338 // Additional code to set disabled state of ComboBox node.
13339 // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr().
13340 this.inherited(arguments);
13341 dijit.setWaiState(this.comboNode, "disabled", value);
13342 },
13343
13344 _abortQuery: function(){
13345 // stop in-progress query
13346 if(this.searchTimer){
13347 clearTimeout(this.searchTimer);
13348 this.searchTimer = null;
13349 }
13350 if(this._fetchHandle){
13351 if(this._fetchHandle.abort){ this._fetchHandle.abort(); }
13352 this._fetchHandle = null;
13353 }
13354 },
13355
13356 _onInput: function(/*Event*/ evt){
13357 // summary:
13358 // Handles paste events
13359 if(!this.searchTimer && (evt.type == 'paste'/*IE|WebKit*/ || evt.type == 'input'/*Firefox*/) && this._lastInput != this.textbox.value){
13360 this.searchTimer = setTimeout(dojo.hitch(this, function(){
13361 this._onKeyPress({charOrCode: 229}); // fake IME key to cause a search
13362 }), 100); // long delay that will probably be preempted by keyboard input
13363 }
13364 this.inherited(arguments);
13365 },
13366
13367 _onKeyPress: function(/*Event*/ evt){
13368 // summary:
13369 // Handles keyboard events
13370 var key = evt.charOrCode;
13371 // except for cutting/pasting case - ctrl + x/v
13372 if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == dojo.keys.SHIFT){
13373 return; // throw out weird key combinations and spurious events
13374 }
13375 var doSearch = false;
13376 var searchFunction = "_startSearchFromInput";
13377 var pw = this._popupWidget;
13378 var dk = dojo.keys;
13379 var highlighted = null;
13380 this._prev_key_backspace = false;
13381 this._abortQuery();
13382 if(this._isShowingNow){
13383 pw.handleKey(key);
13384 highlighted = pw.getHighlightedOption();
13385 }
13386 switch(key){
13387 case dk.PAGE_DOWN:
13388 case dk.DOWN_ARROW:
13389 case dk.PAGE_UP:
13390 case dk.UP_ARROW:
13391 if(!this._isShowingNow){
13392 doSearch = true;
13393 searchFunction = "_startSearchAll";
13394 }else{
13395 this._announceOption(highlighted);
13396 }
13397 dojo.stopEvent(evt);
13398 break;
13399
13400 case dk.ENTER:
13401 // prevent submitting form if user presses enter. Also
13402 // prevent accepting the value if either Next or Previous
13403 // are selected
13404 if(highlighted){
13405 // only stop event on prev/next
13406 if(highlighted == pw.nextButton){
13407 this._nextSearch(1);
13408 dojo.stopEvent(evt);
13409 break;
13410 }else if(highlighted == pw.previousButton){
13411 this._nextSearch(-1);
13412 dojo.stopEvent(evt);
13413 break;
13414 }
13415 }else{
13416 // Update 'value' (ex: KY) according to currently displayed text
13417 this._setBlurValue(); // set value if needed
13418 this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting
13419 }
13420 // default case:
13421 // prevent submit, but allow event to bubble
13422 evt.preventDefault();
13423 // fall through
13424
13425 case dk.TAB:
13426 var newvalue = this.get('displayedValue');
13427 // if the user had More Choices selected fall into the
13428 // _onBlur handler
13429 if(pw && (
13430 newvalue == pw._messages["previousMessage"] ||
13431 newvalue == pw._messages["nextMessage"])
13432 ){
13433 break;
13434 }
13435 if(highlighted){
13436 this._selectOption();
13437 }
13438 if(this._isShowingNow){
13439 this._lastQuery = null; // in case results come back later
13440 this._hideResultList();
13441 }
13442 break;
13443
13444 case ' ':
13445 if(highlighted){
13446 dojo.stopEvent(evt);
13447 this._selectOption();
13448 this._hideResultList();
13449 }else{
13450 doSearch = true;
13451 }
13452 break;
13453
13454 case dk.ESCAPE:
13455 if(this._isShowingNow){
13456 dojo.stopEvent(evt);
13457 this._hideResultList();
13458 }
13459 break;
13460
13461 case dk.DELETE:
13462 case dk.BACKSPACE:
13463 this._prev_key_backspace = true;
13464 doSearch = true;
13465 break;
13466
13467 default:
13468 // Non char keys (F1-F12 etc..) shouldn't open list.
13469 // Ascii characters and IME input (Chinese, Japanese etc.) should.
13470 // On IE and safari, IME input produces keycode == 229, and we simulate
13471 // it on firefox by attaching to compositionend event (see compositionend method)
13472 doSearch = typeof key == 'string' || key == 229;
13473 }
13474 if(doSearch){
13475 // need to wait a tad before start search so that the event
13476 // bubbles through DOM and we have value visible
13477 this.item = undefined; // undefined means item needs to be set
13478 this.searchTimer = setTimeout(dojo.hitch(this, searchFunction),1);
13479 }
13480 },
13481
13482 _autoCompleteText: function(/*String*/ text){
13483 // summary:
13484 // Fill in the textbox with the first item from the drop down
13485 // list, and highlight the characters that were
13486 // auto-completed. For example, if user typed "CA" and the
13487 // drop down list appeared, the textbox would be changed to
13488 // "California" and "ifornia" would be highlighted.
13489
13490 var fn = this.focusNode;
13491
13492 // IE7: clear selection so next highlight works all the time
13493 dijit.selectInputText(fn, fn.value.length);
13494 // does text autoComplete the value in the textbox?
13495 var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr';
13496 if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){
13497 var cpos = this._getCaretPos(fn);
13498 // only try to extend if we added the last character at the end of the input
13499 if((cpos+1) > fn.value.length){
13500 // only add to input node as we would overwrite Capitalisation of chars
13501 // actually, that is ok
13502 fn.value = text;//.substr(cpos);
13503 // visually highlight the autocompleted characters
13504 dijit.selectInputText(fn, cpos);
13505 }
13506 }else{
13507 // text does not autoComplete; replace the whole value and highlight
13508 fn.value = text;
13509 dijit.selectInputText(fn);
13510 }
13511 },
13512
13513 _openResultList: function(/*Object*/ results, /*Object*/ dataObject){
13514 this._fetchHandle = null;
13515 if( this.disabled ||
13516 this.readOnly ||
13517 (dataObject.query[this.searchAttr] != this._lastQuery)
13518 ){
13519 return;
13520 }
13521 this._popupWidget.clearResultList();
13522 if(!results.length && !this._maxOptions){ // this condition needs to match !this._isvalid set in FilteringSelect::_openResultList
13523 this._hideResultList();
13524 return;
13525 }
13526
13527
13528 // Fill in the textbox with the first item from the drop down list,
13529 // and highlight the characters that were auto-completed. For
13530 // example, if user typed "CA" and the drop down list appeared, the
13531 // textbox would be changed to "California" and "ifornia" would be
13532 // highlighted.
13533
13534 dataObject._maxOptions = this._maxOptions;
13535 var nodes = this._popupWidget.createOptions(
13536 results,
13537 dataObject,
13538 dojo.hitch(this, "_getMenuLabelFromItem")
13539 );
13540
13541 // show our list (only if we have content, else nothing)
13542 this._showResultList();
13543
13544 // #4091:
13545 // tell the screen reader that the paging callback finished by
13546 // shouting the next choice
13547 if(dataObject.direction){
13548 if(1 == dataObject.direction){
13549 this._popupWidget.highlightFirstOption();
13550 }else if(-1 == dataObject.direction){
13551 this._popupWidget.highlightLastOption();
13552 }
13553 this._announceOption(this._popupWidget.getHighlightedOption());
13554 }else if(this.autoComplete && !this._prev_key_backspace /*&& !dataObject.direction*/
13555 // when the user clicks the arrow button to show the full list,
13556 // startSearch looks for "*".
13557 // it does not make sense to autocomplete
13558 // if they are just previewing the options available.
13559 && !/^[*]+$/.test(dataObject.query[this.searchAttr])){
13560 this._announceOption(nodes[1]); // 1st real item
13561 }
13562 },
13563
13564 _showResultList: function(){
13565 this._hideResultList();
13566 // hide the tooltip
13567 this.displayMessage("");
13568
13569 // Position the list and if it's too big to fit on the screen then
13570 // size it to the maximum possible height
13571 // Our dear friend IE doesnt take max-height so we need to
13572 // calculate that on our own every time
13573
13574 // TODO: want to redo this, see
13575 // http://trac.dojotoolkit.org/ticket/3272
13576 // and
13577 // http://trac.dojotoolkit.org/ticket/4108
13578
13579
13580 // natural size of the list has changed, so erase old
13581 // width/height settings, which were hardcoded in a previous
13582 // call to this function (via dojo.marginBox() call)
13583 dojo.style(this._popupWidget.domNode, {width: "", height: ""});
13584
13585 var best = this.open();
13586 // #3212:
13587 // only set auto scroll bars if necessary prevents issues with
13588 // scroll bars appearing when they shouldn't when node is made
13589 // wider (fractional pixels cause this)
13590 var popupbox = dojo.marginBox(this._popupWidget.domNode);
13591 this._popupWidget.domNode.style.overflow =
13592 ((best.h == popupbox.h) && (best.w == popupbox.w)) ? "hidden" : "auto";
13593 // #4134:
13594 // borrow TextArea scrollbar test so content isn't covered by
13595 // scrollbar and horizontal scrollbar doesn't appear
13596 var newwidth = best.w;
13597 if(best.h < this._popupWidget.domNode.scrollHeight){
13598 newwidth += 16;
13599 }
13600 dojo.marginBox(this._popupWidget.domNode, {
13601 h: best.h,
13602 w: Math.max(newwidth, this.domNode.offsetWidth)
13603 });
13604
13605 // If we increased the width of drop down to match the width of ComboBox.domNode,
13606 // then need to reposition the drop down (wrapper) so (all of) the drop down still
13607 // appears underneath the ComboBox.domNode
13608 if(newwidth < this.domNode.offsetWidth){
13609 this._popupWidget.domNode.parentNode.style.left = dojo.position(this.domNode, true).x + "px";
13610 }
13611
13612 dijit.setWaiState(this.comboNode, "expanded", "true");
13613 },
13614
13615 _hideResultList: function(){
13616 this._abortQuery();
13617 if(this._isShowingNow){
13618 dijit.popup.close(this._popupWidget);
13619 this._isShowingNow=false;
13620 dijit.setWaiState(this.comboNode, "expanded", "false");
13621 dijit.removeWaiState(this.focusNode,"activedescendant");
13622 }
13623 },
13624
13625 _setBlurValue: function(){
13626 // if the user clicks away from the textbox OR tabs away, set the
13627 // value to the textbox value
13628 // #4617:
13629 // if value is now more choices or previous choices, revert
13630 // the value
13631 var newvalue = this.get('displayedValue');
13632 var pw = this._popupWidget;
13633 if(pw && (
13634 newvalue == pw._messages["previousMessage"] ||
13635 newvalue == pw._messages["nextMessage"]
13636 )
13637 ){
13638 this._setValueAttr(this._lastValueReported, true);
13639 }else if(typeof this.item == "undefined"){
13640 // Update 'value' (ex: KY) according to currently displayed text
13641 this.item = null;
13642 this.set('displayedValue', newvalue);
13643 }else{
13644 if(this.value != this._lastValueReported){
13645 dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true);
13646 }
13647 this._refreshState();
13648 }
13649 },
13650
13651 _onBlur: function(){
13652 // summary:
13653 // Called magically when focus has shifted away from this widget and it's drop down
13654 this._hideResultList();
13655 this.inherited(arguments);
13656 },
13657
13658 _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
13659 // summary:
13660 // Set the displayed valued in the input box, and the hidden value
13661 // that gets submitted, based on a dojo.data store item.
13662 // description:
13663 // Users shouldn't call this function; they should be calling
13664 // attr('item', value)
13665 // tags:
13666 // private
13667 if(!displayedValue){ displayedValue = this.labelFunc(item, this.store); }
13668 this.value = this._getValueField() != this.searchAttr? this.store.getIdentity(item) : displayedValue;
13669 this.item = item;
13670 dijit.form.ComboBox.superclass._setValueAttr.call(this, this.value, priorityChange, displayedValue);
13671 },
13672
13673 _announceOption: function(/*Node*/ node){
13674 // summary:
13675 // a11y code that puts the highlighted option in the textbox.
13676 // This way screen readers will know what is happening in the
13677 // menu.
13678
13679 if(!node){
13680 return;
13681 }
13682 // pull the text value from the item attached to the DOM node
13683 var newValue;
13684 if(node == this._popupWidget.nextButton ||
13685 node == this._popupWidget.previousButton){
13686 newValue = node.innerHTML;
13687 this.item = undefined;
13688 this.value = '';
13689 }else{
13690 newValue = this.labelFunc(node.item, this.store);
13691 this.set('item', node.item, false, newValue);
13692 }
13693 // get the text that the user manually entered (cut off autocompleted text)
13694 this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length);
13695 // set up ARIA activedescendant
13696 dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id"));
13697 // autocomplete the rest of the option to announce change
13698 this._autoCompleteText(newValue);
13699 },
13700
13701 _selectOption: function(/*Event*/ evt){
13702 // summary:
13703 // Menu callback function, called when an item in the menu is selected.
13704 if(evt){
13705 this._announceOption(evt.target);
13706 }
13707 this._hideResultList();
13708 this._setCaretPos(this.focusNode, this.focusNode.value.length);
13709 dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); // set this.value and fire onChange
13710 },
13711
13712 _onArrowMouseDown: function(evt){
13713 // summary:
13714 // Callback when arrow is clicked
13715 if(this.disabled || this.readOnly){
13716 return;
13717 }
13718 dojo.stopEvent(evt);
13719 this.focus();
13720 if(this._isShowingNow){
13721 this._hideResultList();
13722 }else{
13723 // forces full population of results, if they click
13724 // on the arrow it means they want to see more options
13725 this._startSearchAll();
13726 }
13727 },
13728
13729 _startSearchAll: function(){
13730 this._startSearch('');
13731 },
13732
13733 _startSearchFromInput: function(){
13734 this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1"));
13735 },
13736
13737 _getQueryString: function(/*String*/ text){
13738 return dojo.string.substitute(this.queryExpr, [text]);
13739 },
13740
13741 _startSearch: function(/*String*/ key){
13742 if(!this._popupWidget){
13743 var popupId = this.id + "_popup";
13744 this._popupWidget = new dijit.form._ComboBoxMenu({
13745 onChange: dojo.hitch(this, this._selectOption),
13746 id: popupId,
13747 dir: this.dir
13748 });
13749 dijit.removeWaiState(this.focusNode,"activedescendant");
13750 dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox
13751 }
13752 // create a new query to prevent accidentally querying for a hidden
13753 // value from FilteringSelect's keyField
13754 var query = dojo.clone(this.query); // #5970
13755 this._lastInput = key; // Store exactly what was entered by the user.
13756 this._lastQuery = query[this.searchAttr] = this._getQueryString(key);
13757 // #5970: set _lastQuery, *then* start the timeout
13758 // otherwise, if the user types and the last query returns before the timeout,
13759 // _lastQuery won't be set and their input gets rewritten
13760 this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){
13761 this.searchTimer = null;
13762 var fetch = {
13763 queryOptions: {
13764 ignoreCase: this.ignoreCase,
13765 deep: true
13766 },
13767 query: query,
13768 onBegin: dojo.hitch(this, "_setMaxOptions"),
13769 onComplete: dojo.hitch(this, "_openResultList"),
13770 onError: function(errText){
13771 _this._fetchHandle = null;
13772 console.error('dijit.form.ComboBox: ' + errText);
13773 dojo.hitch(_this, "_hideResultList")();
13774 },
13775 start: 0,
13776 count: this.pageSize
13777 };
13778 dojo.mixin(fetch, _this.fetchProperties);
13779 this._fetchHandle = _this.store.fetch(fetch);
13780
13781 var nextSearch = function(dataObject, direction){
13782 dataObject.start += dataObject.count*direction;
13783 // #4091:
13784 // tell callback the direction of the paging so the screen
13785 // reader knows which menu option to shout
13786 dataObject.direction = direction;
13787 this._fetchHandle = this.store.fetch(dataObject);
13788 };
13789 this._nextSearch = this._popupWidget.onPage = dojo.hitch(this, nextSearch, this._fetchHandle);
13790 }, query, this), this.searchDelay);
13791 },
13792
13793 _setMaxOptions: function(size, request){
13794 this._maxOptions = size;
13795 },
13796
13797 _getValueField: function(){
13798 // summmary:
13799 // Helper for postMixInProperties() to set this.value based on data inlined into the markup.
13800 // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value.
13801 return this.searchAttr;
13802 },
13803
13804 /////////////// Event handlers /////////////////////
13805
13806 // FIXME: For 2.0, rename to "_compositionEnd"
13807 compositionend: function(/*Event*/ evt){
13808 // summary:
13809 // When inputting characters using an input method, such as
13810 // Asian languages, it will generate this event instead of
13811 // onKeyDown event.
13812 // Note: this event is only triggered in FF (not in IE/safari)
13813 // tags:
13814 // private
13815
13816 // 229 is the code produced by IE and safari while pressing keys during
13817 // IME input mode
13818 this._onKeyPress({charOrCode: 229});
13819 },
13820
13821 //////////// INITIALIZATION METHODS ///////////////////////////////////////
13822
13823 constructor: function(){
13824 this.query={};
13825 this.fetchProperties={};
13826 },
13827
13828 postMixInProperties: function(){
13829 if(!this.store){
13830 var srcNodeRef = this.srcNodeRef;
13831
13832 // if user didn't specify store, then assume there are option tags
13833 this.store = new dijit.form._ComboBoxDataStore(srcNodeRef);
13834
13835 // if there is no value set and there is an option list, set
13836 // the value to the first value to be consistent with native
13837 // Select
13838
13839 // Firefox and Safari set value
13840 // IE6 and Opera set selectedIndex, which is automatically set
13841 // by the selected attribute of an option tag
13842 // IE6 does not set value, Opera sets value = selectedIndex
13843 if(!("value" in this.params)){
13844 var item = this.store.fetchSelectedItem();
13845 if(item){
13846 var valueField = this._getValueField();
13847 this.value = valueField != this.searchAttr? this.store.getValue(item, valueField) : this.labelFunc(item, this.store);
13848 }
13849 }
13850 }
13851 this.inherited(arguments);
13852 },
13853
13854 postCreate: function(){
13855 // summary:
13856 // Subclasses must call this method from their postCreate() methods
13857 // tags:
13858 // protected
13859
13860 if(!this.hasDownArrow){
13861 this.downArrowNode.style.display = "none";
13862 }
13863
13864 // find any associated label element and add to ComboBox node.
13865 var label=dojo.query('label[for="'+this.id+'"]');
13866 if(label.length){
13867 label[0].id = (this.id+"_label");
13868 var cn=this.comboNode;
13869 dijit.setWaiState(cn, "labelledby", label[0].id);
13870
13871 }
13872 this.inherited(arguments);
13873 },
13874
13875 uninitialize: function(){
13876 if(this._popupWidget && !this._popupWidget._destroyed){
13877 this._hideResultList();
13878 this._popupWidget.destroy();
13879 }
13880 this.inherited(arguments);
13881 },
13882
13883 _getMenuLabelFromItem: function(/*Item*/ item){
13884 var label = this.labelAttr? this.store.getValue(item, this.labelAttr) : this.labelFunc(item, this.store);
13885 var labelType = this.labelType;
13886 // If labelType is not "text" we don't want to screw any markup ot whatever.
13887 if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){
13888 label = this.doHighlight(label, this._escapeHtml(this._lastInput));
13889 labelType = "html";
13890 }
13891 return {html: labelType == "html", label: label};
13892 },
13893
13894 doHighlight: function(/*String*/label, /*String*/find){
13895 // summary:
13896 // Highlights the string entered by the user in the menu. By default this
13897 // highlights the first occurence found. Override this method
13898 // to implement your custom highlighing.
13899 // tags:
13900 // protected
13901
13902 // Add greedy when this.highlightMatch == "all"
13903 var modifiers = "i"+(this.highlightMatch == "all"?"g":"");
13904 var escapedLabel = this._escapeHtml(label);
13905 find = dojo.regexp.escapeString(find); // escape regexp special chars
13906 var ret = escapedLabel.replace(new RegExp("(^|\\s)("+ find +")", modifiers),
13907 '$1<span class="dijitComboBoxHighlightMatch">$2</span>');
13908 return ret;// returns String, (almost) valid HTML (entities encoded)
13909 },
13910
13911 _escapeHtml: function(/*string*/str){
13912 // TODO Should become dojo.html.entities(), when exists use instead
13913 // summary:
13914 // Adds escape sequences for special characters in XML: &<>"'
13915 str = String(str).replace(/&/gm, "&amp;").replace(/</gm, "&lt;")
13916 .replace(/>/gm, "&gt;").replace(/"/gm, "&quot;");
13917 return str; // string
13918 },
13919
13920 open: function(){
13921 // summary:
13922 // Opens the drop down menu. TODO: rename to _open.
13923 // tags:
13924 // private
13925 this._isShowingNow=true;
13926 return dijit.popup.open({
13927 popup: this._popupWidget,
13928 around: this.domNode,
13929 parent: this
13930 });
13931 },
13932
13933 reset: function(){
13934 // Overrides the _FormWidget.reset().
13935 // Additionally reset the .item (to clean up).
13936 this.item = null;
13937 this.inherited(arguments);
13938 },
13939
13940 labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){
13941 // summary:
13942 // Computes the label to display based on the dojo.data store item.
13943 // returns:
13944 // The label that the ComboBox should display
13945 // tags:
13946 // private
13947
13948 // Use toString() because XMLStore returns an XMLItem whereas this
13949 // method is expected to return a String (#9354)
13950 return store.getValue(item, this.searchAttr).toString(); // String
13951 }
13952 }
13953 );
13954
13955 dojo.declare(
13956 "dijit.form._ComboBoxMenu",
13957 [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
13958 {
13959 // summary:
13960 // Focus-less menu for internal use in `dijit.form.ComboBox`
13961 // tags:
13962 // private
13963
13964 templateString: "<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' tabIndex='-1' style='overflow: \"auto\"; overflow-x: \"hidden\";'>"
13965 +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' waiRole='option'></li>"
13966 +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' waiRole='option'></li>"
13967 +"</ul>",
13968
13969 // _messages: Object
13970 // Holds "next" and "previous" text for paging buttons on drop down
13971 _messages: null,
13972
13973 baseClass: "dijitComboBoxMenu",
13974
13975 postMixInProperties: function(){
13976 this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang);
13977 this.inherited(arguments);
13978 },
13979
13980 _setValueAttr: function(/*Object*/ value){
13981 this.value = value;
13982 this.onChange(value);
13983 },
13984
13985 // stubs
13986 onChange: function(/*Object*/ value){
13987 // summary:
13988 // Notifies ComboBox/FilteringSelect that user clicked an option in the drop down menu.
13989 // Probably should be called onSelect.
13990 // tags:
13991 // callback
13992 },
13993 onPage: function(/*Number*/ direction){
13994 // summary:
13995 // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page.
13996 // tags:
13997 // callback
13998 },
13999
14000 postCreate: function(){
14001 // fill in template with i18n messages
14002 this.previousButton.innerHTML = this._messages["previousMessage"];
14003 this.nextButton.innerHTML = this._messages["nextMessage"];
14004 this.inherited(arguments);
14005 },
14006
14007 onClose: function(){
14008 // summary:
14009 // Callback from dijit.popup code to this widget, notifying it that it closed
14010 // tags:
14011 // private
14012 this._blurOptionNode();
14013 },
14014
14015 _createOption: function(/*Object*/ item, labelFunc){
14016 // summary:
14017 // Creates an option to appear on the popup menu subclassed by
14018 // `dijit.form.FilteringSelect`.
14019
14020 var labelObject = labelFunc(item);
14021 var menuitem = dojo.doc.createElement("li");
14022 dijit.setWaiRole(menuitem, "option");
14023 if(labelObject.html){
14024 menuitem.innerHTML = labelObject.label;
14025 }else{
14026 menuitem.appendChild(
14027 dojo.doc.createTextNode(labelObject.label)
14028 );
14029 }
14030 // #3250: in blank options, assign a normal height
14031 if(menuitem.innerHTML == ""){
14032 menuitem.innerHTML = "&nbsp;";
14033 }
14034 menuitem.item=item;
14035 return menuitem;
14036 },
14037
14038 createOptions: function(results, dataObject, labelFunc){
14039 // summary:
14040 // Fills in the items in the drop down list
14041 // results:
14042 // Array of dojo.data items
14043 // dataObject:
14044 // dojo.data store
14045 // labelFunc:
14046 // Function to produce a label in the drop down list from a dojo.data item
14047
14048 //this._dataObject=dataObject;
14049 //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList);
14050 // display "Previous . . ." button
14051 this.previousButton.style.display = (dataObject.start == 0) ? "none" : "";
14052 dojo.attr(this.previousButton, "id", this.id + "_prev");
14053 // create options using _createOption function defined by parent
14054 // ComboBox (or FilteringSelect) class
14055 // #2309:
14056 // iterate over cache nondestructively
14057 dojo.forEach(results, function(item, i){
14058 var menuitem = this._createOption(item, labelFunc);
14059 menuitem.className = "dijitReset dijitMenuItem" +
14060 (this.isLeftToRight() ? "" : " dijitMenuItemRtl");
14061 dojo.attr(menuitem, "id", this.id + i);
14062 this.domNode.insertBefore(menuitem, this.nextButton);
14063 }, this);
14064 // display "Next . . ." button
14065 var displayMore = false;
14066 //Try to determine if we should show 'more'...
14067 if(dataObject._maxOptions && dataObject._maxOptions != -1){
14068 if((dataObject.start + dataObject.count) < dataObject._maxOptions){
14069 displayMore = true;
14070 }else if((dataObject.start + dataObject.count) > dataObject._maxOptions && dataObject.count == results.length){
14071 //Weird return from a datastore, where a start + count > maxOptions
14072 // implies maxOptions isn't really valid and we have to go into faking it.
14073 //And more or less assume more if count == results.length
14074 displayMore = true;
14075 }
14076 }else if(dataObject.count == results.length){
14077 //Don't know the size, so we do the best we can based off count alone.
14078 //So, if we have an exact match to count, assume more.
14079 displayMore = true;
14080 }
14081
14082 this.nextButton.style.display = displayMore ? "" : "none";
14083 dojo.attr(this.nextButton,"id", this.id + "_next");
14084 return this.domNode.childNodes;
14085 },
14086
14087 clearResultList: function(){
14088 // summary:
14089 // Clears the entries in the drop down list, but of course keeps the previous and next buttons.
14090 while(this.domNode.childNodes.length>2){
14091 this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]);
14092 }
14093 },
14094
14095 _onMouseDown: function(/*Event*/ evt){
14096 dojo.stopEvent(evt);
14097 },
14098
14099 _onMouseUp: function(/*Event*/ evt){
14100 if(evt.target === this.domNode || !this._highlighted_option){
14101 return;
14102 }else if(evt.target == this.previousButton){
14103 this.onPage(-1);
14104 }else if(evt.target == this.nextButton){
14105 this.onPage(1);
14106 }else{
14107 var tgt = evt.target;
14108 // while the clicked node is inside the div
14109 while(!tgt.item){
14110 // recurse to the top
14111 tgt = tgt.parentNode;
14112 }
14113 this._setValueAttr({ target: tgt }, true);
14114 }
14115 },
14116
14117 _onMouseOver: function(/*Event*/ evt){
14118 if(evt.target === this.domNode){ return; }
14119 var tgt = evt.target;
14120 if(!(tgt == this.previousButton || tgt == this.nextButton)){
14121 // while the clicked node is inside the div
14122 while(!tgt.item){
14123 // recurse to the top
14124 tgt = tgt.parentNode;
14125 }
14126 }
14127 this._focusOptionNode(tgt);
14128 },
14129
14130 _onMouseOut: function(/*Event*/ evt){
14131 if(evt.target === this.domNode){ return; }
14132 this._blurOptionNode();
14133 },
14134
14135 _focusOptionNode: function(/*DomNode*/ node){
14136 // summary:
14137 // Does the actual highlight.
14138 if(this._highlighted_option != node){
14139 this._blurOptionNode();
14140 this._highlighted_option = node;
14141 dojo.addClass(this._highlighted_option, "dijitMenuItemSelected");
14142 }
14143 },
14144
14145 _blurOptionNode: function(){
14146 // summary:
14147 // Removes highlight on highlighted option.
14148 if(this._highlighted_option){
14149 dojo.removeClass(this._highlighted_option, "dijitMenuItemSelected");
14150 this._highlighted_option = null;
14151 }
14152 },
14153
14154 _highlightNextOption: function(){
14155 // summary:
14156 // Highlight the item just below the current selection.
14157 // If nothing selected, highlight first option.
14158
14159 // because each press of a button clears the menu,
14160 // the highlighted option sometimes becomes detached from the menu!
14161 // test to see if the option has a parent to see if this is the case.
14162 if(!this.getHighlightedOption()){
14163 var fc = this.domNode.firstChild;
14164 this._focusOptionNode(fc.style.display == "none" ? fc.nextSibling : fc);
14165 }else{
14166 var ns = this._highlighted_option.nextSibling;
14167 if(ns && ns.style.display != "none"){
14168 this._focusOptionNode(ns);
14169 }else{
14170 this.highlightFirstOption();
14171 }
14172 }
14173 // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover
14174 dojo.window.scrollIntoView(this._highlighted_option);
14175 },
14176
14177 highlightFirstOption: function(){
14178 // summary:
14179 // Highlight the first real item in the list (not Previous Choices).
14180 var first = this.domNode.firstChild;
14181 var second = first.nextSibling;
14182 this._focusOptionNode(second.style.display == "none" ? first : second); // remotely possible that Previous Choices is the only thing in the list
14183 dojo.window.scrollIntoView(this._highlighted_option);
14184 },
14185
14186 highlightLastOption: function(){
14187 // summary:
14188 // Highlight the last real item in the list (not More Choices).
14189 this._focusOptionNode(this.domNode.lastChild.previousSibling);
14190 dojo.window.scrollIntoView(this._highlighted_option);
14191 },
14192
14193 _highlightPrevOption: function(){
14194 // summary:
14195 // Highlight the item just above the current selection.
14196 // If nothing selected, highlight last option (if
14197 // you select Previous and try to keep scrolling up the list).
14198 if(!this.getHighlightedOption()){
14199 var lc = this.domNode.lastChild;
14200 this._focusOptionNode(lc.style.display == "none" ? lc.previousSibling : lc);
14201 }else{
14202 var ps = this._highlighted_option.previousSibling;
14203 if(ps && ps.style.display != "none"){
14204 this._focusOptionNode(ps);
14205 }else{
14206 this.highlightLastOption();
14207 }
14208 }
14209 dojo.window.scrollIntoView(this._highlighted_option);
14210 },
14211
14212 _page: function(/*Boolean*/ up){
14213 // summary:
14214 // Handles page-up and page-down keypresses
14215
14216 var scrollamount = 0;
14217 var oldscroll = this.domNode.scrollTop;
14218 var height = dojo.style(this.domNode, "height");
14219 // if no item is highlighted, highlight the first option
14220 if(!this.getHighlightedOption()){
14221 this._highlightNextOption();
14222 }
14223 while(scrollamount<height){
14224 if(up){
14225 // stop at option 1
14226 if(!this.getHighlightedOption().previousSibling ||
14227 this._highlighted_option.previousSibling.style.display == "none"){
14228 break;
14229 }
14230 this._highlightPrevOption();
14231 }else{
14232 // stop at last option
14233 if(!this.getHighlightedOption().nextSibling ||
14234 this._highlighted_option.nextSibling.style.display == "none"){
14235 break;
14236 }
14237 this._highlightNextOption();
14238 }
14239 // going backwards
14240 var newscroll=this.domNode.scrollTop;
14241 scrollamount+=(newscroll-oldscroll)*(up ? -1:1);
14242 oldscroll=newscroll;
14243 }
14244 },
14245
14246 pageUp: function(){
14247 // summary:
14248 // Handles pageup keypress.
14249 // TODO: just call _page directly from handleKey().
14250 // tags:
14251 // private
14252 this._page(true);
14253 },
14254
14255 pageDown: function(){
14256 // summary:
14257 // Handles pagedown keypress.
14258 // TODO: just call _page directly from handleKey().
14259 // tags:
14260 // private
14261 this._page(false);
14262 },
14263
14264 getHighlightedOption: function(){
14265 // summary:
14266 // Returns the highlighted option.
14267 var ho = this._highlighted_option;
14268 return (ho && ho.parentNode) ? ho : null;
14269 },
14270
14271 handleKey: function(key){
14272 switch(key){
14273 case dojo.keys.DOWN_ARROW:
14274 this._highlightNextOption();
14275 break;
14276 case dojo.keys.PAGE_DOWN:
14277 this.pageDown();
14278 break;
14279 case dojo.keys.UP_ARROW:
14280 this._highlightPrevOption();
14281 break;
14282 case dojo.keys.PAGE_UP:
14283 this.pageUp();
14284 break;
14285 }
14286 }
14287 }
14288 );
14289
14290 dojo.declare(
14291 "dijit.form.ComboBox",
14292 [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin],
14293 {
14294 // summary:
14295 // Auto-completing text box, and base class for dijit.form.FilteringSelect.
14296 //
14297 // description:
14298 // The drop down box's values are populated from an class called
14299 // a data provider, which returns a list of values based on the characters
14300 // that the user has typed into the input box.
14301 // If OPTION tags are used as the data provider via markup,
14302 // then the OPTION tag's child text node is used as the widget value
14303 // when selected. The OPTION tag's value attribute is ignored.
14304 // To set the default value when using OPTION tags, specify the selected
14305 // attribute on 1 of the child OPTION tags.
14306 //
14307 // Some of the options to the ComboBox are actually arguments to the data
14308 // provider.
14309
14310 _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
14311 // summary:
14312 // Hook so attr('value', value) works.
14313 // description:
14314 // Sets the value of the select.
14315 this.item = null; // value not looked up in store
14316 if(!value){ value = ''; } // null translates to blank
14317 dijit.form.ValidationTextBox.prototype._setValueAttr.call(this, value, priorityChange, displayedValue);
14318 }
14319 }
14320 );
14321
14322 dojo.declare("dijit.form._ComboBoxDataStore", null, {
14323 // summary:
14324 // Inefficient but small data store specialized for inlined `dijit.form.ComboBox` data
14325 //
14326 // description:
14327 // Provides a store for inlined data like:
14328 //
14329 // | <select>
14330 // | <option value="AL">Alabama</option>
14331 // | ...
14332 //
14333 // Actually. just implements the subset of dojo.data.Read/Notification
14334 // needed for ComboBox and FilteringSelect to work.
14335 //
14336 // Note that an item is just a pointer to the <option> DomNode.
14337
14338 constructor: function( /*DomNode*/ root){
14339 this.root = root;
14340 if(root.tagName != "SELECT" && root.firstChild){
14341 root = dojo.query("select", root);
14342 if(root.length > 0){ // SELECT is a child of srcNodeRef
14343 root = root[0];
14344 }else{ // no select, so create 1 to parent the option tags to define selectedIndex
14345 this.root.innerHTML = "<SELECT>"+this.root.innerHTML+"</SELECT>";
14346 root = this.root.firstChild;
14347 }
14348 this.root = root;
14349 }
14350 dojo.query("> option", root).forEach(function(node){
14351 // TODO: this was added in #3858 but unclear why/if it's needed; doesn't seem to be.
14352 // If it is needed then can we just hide the select itself instead?
14353 //node.style.display="none";
14354 node.innerHTML = dojo.trim(node.innerHTML);
14355 });
14356
14357 },
14358
14359 getValue: function( /* item */ item,
14360 /* attribute-name-string */ attribute,
14361 /* value? */ defaultValue){
14362 return (attribute == "value") ? item.value : (item.innerText || item.textContent || '');
14363 },
14364
14365 isItemLoaded: function(/* anything */ something){
14366 return true;
14367 },
14368
14369 getFeatures: function(){
14370 return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true};
14371 },
14372
14373 _fetchItems: function( /* Object */ args,
14374 /* Function */ findCallback,
14375 /* Function */ errorCallback){
14376 // summary:
14377 // See dojo.data.util.simpleFetch.fetch()
14378 if(!args.query){ args.query = {}; }
14379 if(!args.query.name){ args.query.name = ""; }
14380 if(!args.queryOptions){ args.queryOptions = {}; }
14381 var matcher = dojo.data.util.filter.patternToRegExp(args.query.name, args.queryOptions.ignoreCase),
14382 items = dojo.query("> option", this.root).filter(function(option){
14383 return (option.innerText || option.textContent || '').match(matcher);
14384 } );
14385 if(args.sort){
14386 items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this));
14387 }
14388 findCallback(items, args);
14389 },
14390
14391 close: function(/*dojo.data.api.Request || args || null */ request){
14392 return;
14393 },
14394
14395 getLabel: function(/* item */ item){
14396 return item.innerHTML;
14397 },
14398
14399 getIdentity: function(/* item */ item){
14400 return dojo.attr(item, "value");
14401 },
14402
14403 fetchItemByIdentity: function(/* Object */ args){
14404 // summary:
14405 // Given the identity of an item, this method returns the item that has
14406 // that identity through the onItem callback.
14407 // Refer to dojo.data.api.Identity.fetchItemByIdentity() for more details.
14408 //
14409 // description:
14410 // Given arguments like:
14411 //
14412 // | {identity: "CA", onItem: function(item){...}
14413 //
14414 // Call `onItem()` with the DOM node `<option value="CA">California</option>`
14415 var item = dojo.query("> option[value='" + args.identity + "']", this.root)[0];
14416 args.onItem(item);
14417 },
14418
14419 fetchSelectedItem: function(){
14420 // summary:
14421 // Get the option marked as selected, like `<option selected>`.
14422 // Not part of dojo.data API.
14423 var root = this.root,
14424 si = root.selectedIndex;
14425 return typeof si == "number"
14426 ? dojo.query("> option:nth-child(" + (si != -1 ? si+1 : 1) + ")", root)[0]
14427 : null; // dojo.data.Item
14428 }
14429 });
14430 //Mix in the simple fetch implementation to this class.
14431 dojo.extend(dijit.form._ComboBoxDataStore,dojo.data.util.simpleFetch);
14432
14433 }
14434
14435 if(!dojo._hasResource["dijit.form.FilteringSelect"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
14436 dojo._hasResource["dijit.form.FilteringSelect"] = true;
14437 dojo.provide("dijit.form.FilteringSelect");
14438
14439
14440
14441 dojo.declare(
14442 "dijit.form.FilteringSelect",
14443 [dijit.form.MappedTextBox, dijit.form.ComboBoxMixin],
14444 {
14445 // summary:
14446 // An enhanced version of the HTML SELECT tag, populated dynamically
14447 //
14448 // description:
14449 // An enhanced version of the HTML SELECT tag, populated dynamically. It works
14450 // very nicely with very large data sets because it can load and page data as needed.
14451 // It also resembles ComboBox, but does not allow values outside of the provided ones.
14452 // If OPTION tags are used as the data provider via markup, then the
14453 // OPTION tag's child text node is used as the displayed value when selected
14454 // while the OPTION tag's value attribute is used as the widget value on form submit.
14455 // To set the default value when using OPTION tags, specify the selected
14456 // attribute on 1 of the child OPTION tags.
14457 //
14458 // Similar features:
14459 // - There is a drop down list of possible values.
14460 // - You can only enter a value from the drop down list. (You can't
14461 // enter an arbitrary value.)
14462 // - The value submitted with the form is the hidden value (ex: CA),
14463 // not the displayed value a.k.a. label (ex: California)
14464 //
14465 // Enhancements over plain HTML version:
14466 // - If you type in some text then it will filter down the list of
14467 // possible values in the drop down list.
14468 // - List can be specified either as a static list or via a javascript
14469 // function (that can get the list from a server)
14470
14471 _isvalid: true,
14472
14473 // required: Boolean
14474 // True (default) if user is required to enter a value into this field.
14475 required: true,
14476
14477 _lastDisplayedValue: "",
14478
14479 isValid: function(){
14480 // Overrides ValidationTextBox.isValid()
14481 return this._isvalid || (!this.required && this.get('displayedValue') == ""); // #5974
14482 },
14483
14484 _refreshState: function(){
14485 if(!this.searchTimer){ // state will be refreshed after results are returned
14486 this.inherited(arguments);
14487 }
14488 },
14489
14490 _callbackSetLabel: function( /*Array*/ result,
14491 /*Object*/ dataObject,
14492 /*Boolean?*/ priorityChange){
14493 // summary:
14494 // Callback function that dynamically sets the label of the
14495 // ComboBox
14496
14497 // setValue does a synchronous lookup,
14498 // so it calls _callbackSetLabel directly,
14499 // and so does not pass dataObject
14500 // still need to test against _lastQuery in case it came too late
14501 if((dataObject && dataObject.query[this.searchAttr] != this._lastQuery) || (!dataObject && result.length && this.store.getIdentity(result[0]) != this._lastQuery)){
14502 return;
14503 }
14504 if(!result.length){
14505 //#3268: do nothing on bad input
14506 //#3285: change CSS to indicate error
14507 this.valueNode.value = "";
14508 dijit.form.TextBox.superclass._setValueAttr.call(this, "", priorityChange || (priorityChange === undefined && !this._focused));
14509 this._isvalid = false;
14510 this.validate(this._focused);
14511 this.item = null;
14512 }else{
14513 this.set('item', result[0], priorityChange);
14514 }
14515 },
14516
14517 _openResultList: function(/*Object*/ results, /*Object*/ dataObject){
14518 // Overrides ComboBox._openResultList()
14519
14520 // #3285: tap into search callback to see if user's query resembles a match
14521 if(dataObject.query[this.searchAttr] != this._lastQuery){
14522 return;
14523 }
14524 if(this.item === undefined){ // item == undefined for keyboard search
14525 this._isvalid = results.length != 0 || this._maxOptions != 0; // result.length==0 && maxOptions != 0 implies the nextChoices item selected but then the datastore returned 0 more entries
14526 this.validate(true);
14527 }
14528 dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments);
14529 },
14530
14531 _getValueAttr: function(){
14532 // summary:
14533 // Hook for attr('value') to work.
14534
14535 // don't get the textbox value but rather the previously set hidden value.
14536 // Use this.valueNode.value which isn't always set for other MappedTextBox widgets until blur
14537 return this.valueNode.value;
14538 },
14539
14540 _getValueField: function(){
14541 // Overrides ComboBox._getValueField()
14542 return "value";
14543 },
14544
14545 _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){
14546 // summary:
14547 // Hook so attr('value', value) works.
14548 // description:
14549 // Sets the value of the select.
14550 // Also sets the label to the corresponding value by reverse lookup.
14551 if(!this._onChangeActive){ priorityChange = null; }
14552 this._lastQuery = value;
14553
14554 if(value === null || value === ''){
14555 this._setDisplayedValueAttr('', priorityChange);
14556 return;
14557 }
14558
14559 //#3347: fetchItemByIdentity if no keyAttr specified
14560 var self = this;
14561 this.store.fetchItemByIdentity({
14562 identity: value,
14563 onItem: function(item){
14564 self._callbackSetLabel(item? [item] : [], undefined, priorityChange);
14565 }
14566 });
14567 },
14568
14569 _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
14570 // summary:
14571 // Set the displayed valued in the input box, and the hidden value
14572 // that gets submitted, based on a dojo.data store item.
14573 // description:
14574 // Users shouldn't call this function; they should be calling
14575 // attr('item', value)
14576 // tags:
14577 // private
14578 this._isvalid = true;
14579 this.inherited(arguments);
14580 this.valueNode.value = this.value;
14581 this._lastDisplayedValue = this.textbox.value;
14582 },
14583
14584 _getDisplayQueryString: function(/*String*/ text){
14585 return text.replace(/([\\\*\?])/g, "\\$1");
14586 },
14587
14588 _setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){
14589 // summary:
14590 // Hook so attr('displayedValue', label) works.
14591 // description:
14592 // Sets textbox to display label. Also performs reverse lookup
14593 // to set the hidden value.
14594
14595 // When this is called during initialization it'll ping the datastore
14596 // for reverse lookup, and when that completes (after an XHR request)
14597 // will call setValueAttr()... but that shouldn't trigger an onChange()
14598 // event, even when it happens after creation has finished
14599 if(!this._created){
14600 priorityChange = false;
14601 }
14602
14603 if(this.store){
14604 this._hideResultList();
14605 var query = dojo.clone(this.query); // #6196: populate query with user-specifics
14606 // escape meta characters of dojo.data.util.filter.patternToRegExp().
14607 this._lastQuery = query[this.searchAttr] = this._getDisplayQueryString(label);
14608 // if the label is not valid, the callback will never set it,
14609 // so the last valid value will get the warning textbox set the
14610 // textbox value now so that the impending warning will make
14611 // sense to the user
14612 this.textbox.value = label;
14613 this._lastDisplayedValue = label;
14614 var _this = this;
14615 var fetch = {
14616 query: query,
14617 queryOptions: {
14618 ignoreCase: this.ignoreCase,
14619 deep: true
14620 },
14621 onComplete: function(result, dataObject){
14622 _this._fetchHandle = null;
14623 dojo.hitch(_this, "_callbackSetLabel")(result, dataObject, priorityChange);
14624 },
14625 onError: function(errText){
14626 _this._fetchHandle = null;
14627 console.error('dijit.form.FilteringSelect: ' + errText);
14628 dojo.hitch(_this, "_callbackSetLabel")([], undefined, false);
14629 }
14630 };
14631 dojo.mixin(fetch, this.fetchProperties);
14632 this._fetchHandle = this.store.fetch(fetch);
14633 }
14634 },
14635
14636 postMixInProperties: function(){
14637 this.inherited(arguments);
14638 this._isvalid = !this.required;
14639 },
14640
14641 undo: function(){
14642 this.set('displayedValue', this._lastDisplayedValue);
14643 }
14644 }
14645 );
14646
14647 }
14648
14649 if(!dojo._hasResource["dijit.form.Form"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
14650 dojo._hasResource["dijit.form.Form"] = true;
14651 dojo.provide("dijit.form.Form");
14652
14653
14654
14655
14656
14657 dojo.declare(
14658 "dijit.form.Form",
14659 [dijit._Widget, dijit._Templated, dijit.form._FormMixin],
14660 {
14661 // summary:
14662 // Widget corresponding to HTML form tag, for validation and serialization
14663 //
14664 // example:
14665 // | <form dojoType="dijit.form.Form" id="myForm">
14666 // | Name: <input type="text" name="name" />
14667 // | </form>
14668 // | myObj = {name: "John Doe"};
14669 // | dijit.byId('myForm').set('value', myObj);
14670 // |
14671 // | myObj=dijit.byId('myForm').get('value');
14672
14673 // HTML <FORM> attributes
14674
14675 // name: String?
14676 // Name of form for scripting.
14677 name: "",
14678
14679 // action: String?
14680 // Server-side form handler.
14681 action: "",
14682
14683 // method: String?
14684 // HTTP method used to submit the form, either "GET" or "POST".
14685 method: "",
14686
14687 // encType: String?
14688 // Encoding type for the form, ex: application/x-www-form-urlencoded.
14689 encType: "",
14690
14691 // accept-charset: String?
14692 // List of supported charsets.
14693 "accept-charset": "",
14694
14695 // accept: String?
14696 // List of MIME types for file upload.
14697 accept: "",
14698
14699 // target: String?
14700 // Target frame for the document to be opened in.
14701 target: "",
14702
14703 templateString: "<form dojoAttachPoint='containerNode' dojoAttachEvent='onreset:_onReset,onsubmit:_onSubmit' ${!nameAttrSetting}></form>",
14704
14705 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
14706 action: "",
14707 method: "",
14708 encType: "",
14709 "accept-charset": "",
14710 accept: "",
14711 target: ""
14712 }),
14713
14714 postMixInProperties: function(){
14715 // Setup name=foo string to be referenced from the template (but only if a name has been specified)
14716 // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660
14717 this.nameAttrSetting = this.name ? ("name='" + this.name + "'") : "";
14718 this.inherited(arguments);
14719 },
14720
14721 execute: function(/*Object*/ formContents){
14722 // summary:
14723 // Deprecated: use submit()
14724 // tags:
14725 // deprecated
14726 },
14727
14728 onExecute: function(){
14729 // summary:
14730 // Deprecated: use onSubmit()
14731 // tags:
14732 // deprecated
14733 },
14734
14735 _setEncTypeAttr: function(/*String*/ value){
14736 this.encType = value;
14737 dojo.attr(this.domNode, "encType", value);
14738 if(dojo.isIE){ this.domNode.encoding = value; }
14739 },
14740
14741 postCreate: function(){
14742 // IE tries to hide encType
14743 // TODO: this code should be in parser, not here.
14744 if(dojo.isIE && this.srcNodeRef && this.srcNodeRef.attributes){
14745 var item = this.srcNodeRef.attributes.getNamedItem('encType');
14746 if(item && !item.specified && (typeof item.value == "string")){
14747 this.set('encType', item.value);
14748 }
14749 }
14750 this.inherited(arguments);
14751 },
14752
14753 reset: function(/*Event?*/ e){
14754 // summary:
14755 // restores all widget values back to their init values,
14756 // calls onReset() which can cancel the reset by returning false
14757
14758 // create fake event so we can know if preventDefault() is called
14759 var faux = {
14760 returnValue: true, // the IE way
14761 preventDefault: function(){ // not IE
14762 this.returnValue = false;
14763 },
14764 stopPropagation: function(){},
14765 currentTarget: e ? e.target : this.domNode,
14766 target: e ? e.target : this.domNode
14767 };
14768 // if return value is not exactly false, and haven't called preventDefault(), then reset
14769 if(!(this.onReset(faux) === false) && faux.returnValue){
14770 this.inherited(arguments, []);
14771 }
14772 },
14773
14774 onReset: function(/*Event?*/ e){
14775 // summary:
14776 // Callback when user resets the form. This method is intended
14777 // to be over-ridden. When the `reset` method is called
14778 // programmatically, the return value from `onReset` is used
14779 // to compute whether or not resetting should proceed
14780 // tags:
14781 // callback
14782 return true; // Boolean
14783 },
14784
14785 _onReset: function(e){
14786 this.reset(e);
14787 dojo.stopEvent(e);
14788 return false;
14789 },
14790
14791 _onSubmit: function(e){
14792 var fp = dijit.form.Form.prototype;
14793 // TODO: remove this if statement beginning with 2.0
14794 if(this.execute != fp.execute || this.onExecute != fp.onExecute){
14795 dojo.deprecated("dijit.form.Form:execute()/onExecute() are deprecated. Use onSubmit() instead.", "", "2.0");
14796 this.onExecute();
14797 this.execute(this.getValues());
14798 }
14799 if(this.onSubmit(e) === false){ // only exactly false stops submit
14800 dojo.stopEvent(e);
14801 }
14802 },
14803
14804 onSubmit: function(/*Event?*/e){
14805 // summary:
14806 // Callback when user submits the form.
14807 // description:
14808 // This method is intended to be over-ridden, but by default it checks and
14809 // returns the validity of form elements. When the `submit`
14810 // method is called programmatically, the return value from
14811 // `onSubmit` is used to compute whether or not submission
14812 // should proceed
14813 // tags:
14814 // extension
14815
14816 return this.isValid(); // Boolean
14817 },
14818
14819 submit: function(){
14820 // summary:
14821 // programmatically submit form if and only if the `onSubmit` returns true
14822 if(!(this.onSubmit() === false)){
14823 this.containerNode.submit();
14824 }
14825 }
14826 }
14827 );
14828
14829 }
14830
14831 if(!dojo._hasResource["dijit.form.RadioButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
14832 dojo._hasResource["dijit.form.RadioButton"] = true;
14833 dojo.provide("dijit.form.RadioButton");
14834
14835
14836 // TODO: for 2.0, move the RadioButton code into this file
14837
14838 }
14839
14840 if(!dojo._hasResource["dijit.form._FormSelectWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
14841 dojo._hasResource["dijit.form._FormSelectWidget"] = true;
14842 dojo.provide("dijit.form._FormSelectWidget");
14843
14844
14845
14846
14847 /*=====
14848 dijit.form.__SelectOption = function(){
14849 // value: String
14850 // The value of the option. Setting to empty (or missing) will
14851 // place a separator at that location
14852 // label: String
14853 // The label for our option. It can contain html tags.
14854 // selected: Boolean
14855 // Whether or not we are a selected option
14856 // disabled: Boolean
14857 // Whether or not this specific option is disabled
14858 this.value = value;
14859 this.label = label;
14860 this.selected = selected;
14861 this.disabled = disabled;
14862 }
14863 =====*/
14864
14865 dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
14866 // summary:
14867 // Extends _FormValueWidget in order to provide "select-specific"
14868 // values - i.e., those values that are unique to <select> elements.
14869 // This also provides the mechanism for reading the elements from
14870 // a store, if desired.
14871
14872 // multiple: Boolean
14873 // Whether or not we are multi-valued
14874 multiple: false,
14875
14876 // options: dijit.form.__SelectOption[]
14877 // The set of options for our select item. Roughly corresponds to
14878 // the html <option> tag.
14879 options: null,
14880
14881 // store: dojo.data.api.Identity
14882 // A store which, at the very least impelements dojo.data.api.Identity
14883 // to use for getting our list of options - rather than reading them
14884 // from the <option> html tags.
14885 store: null,
14886
14887 // query: object
14888 // A query to use when fetching items from our store
14889 query: null,
14890
14891 // queryOptions: object
14892 // Query options to use when fetching from the store
14893 queryOptions: null,
14894
14895 // onFetch: Function
14896 // A callback to do with an onFetch - but before any items are actually
14897 // iterated over (i.e. to filter even futher what you want to add)
14898 onFetch: null,
14899
14900 // sortByLabel: boolean
14901 // Flag to sort the options returned from a store by the label of
14902 // the store.
14903 sortByLabel: true,
14904
14905
14906 // loadChildrenOnOpen: boolean
14907 // By default loadChildren is called when the items are fetched from the
14908 // store. This property allows delaying loadChildren (and the creation
14909 // of the options/menuitems) until the user opens the click the button.
14910 // dropdown
14911 loadChildrenOnOpen: false,
14912
14913 getOptions: function(/* anything */ valueOrIdx){
14914 // summary:
14915 // Returns a given option (or options).
14916 // valueOrIdx:
14917 // If passed in as a string, that string is used to look up the option
14918 // in the array of options - based on the value property.
14919 // (See dijit.form.__SelectOption).
14920 //
14921 // If passed in a number, then the option with the given index (0-based)
14922 // within this select will be returned.
14923 //
14924 // If passed in a dijit.form.__SelectOption, the same option will be
14925 // returned if and only if it exists within this select.
14926 //
14927 // If passed an array, then an array will be returned with each element
14928 // in the array being looked up.
14929 //
14930 // If not passed a value, then all options will be returned
14931 //
14932 // returns:
14933 // The option corresponding with the given value or index. null
14934 // is returned if any of the following are true:
14935 // - A string value is passed in which doesn't exist
14936 // - An index is passed in which is outside the bounds of the array of options
14937 // - A dijit.form.__SelectOption is passed in which is not a part of the select
14938
14939 // NOTE: the compare for passing in a dijit.form.__SelectOption checks
14940 // if the value property matches - NOT if the exact option exists
14941 // NOTE: if passing in an array, null elements will be placed in the returned
14942 // array when a value is not found.
14943 var lookupValue = valueOrIdx, opts = this.options || [], l = opts.length;
14944
14945 if(lookupValue === undefined){
14946 return opts; // dijit.form.__SelectOption[]
14947 }
14948 if(dojo.isArray(lookupValue)){
14949 return dojo.map(lookupValue, "return this.getOptions(item);", this); // dijit.form.__SelectOption[]
14950 }
14951 if(dojo.isObject(valueOrIdx)){
14952 // We were passed an option - so see if it's in our array (directly),
14953 // and if it's not, try and find it by value.
14954 if(!dojo.some(this.options, function(o, idx){
14955 if(o === lookupValue ||
14956 (o.value && o.value === lookupValue.value)){
14957 lookupValue = idx;
14958 return true;
14959 }
14960 return false;
14961 })){
14962 lookupValue = -1;
14963 }
14964 }
14965 if(typeof lookupValue == "string"){
14966 for(var i=0; i<l; i++){
14967 if(opts[i].value === lookupValue){
14968 lookupValue = i;
14969 break;
14970 }
14971 }
14972 }
14973 if(typeof lookupValue == "number" && lookupValue >= 0 && lookupValue < l){
14974 return this.options[lookupValue] // dijit.form.__SelectOption
14975 }
14976 return null; // null
14977 },
14978
14979 addOption: function(/* dijit.form.__SelectOption, dijit.form.__SelectOption[] */ option){
14980 // summary:
14981 // Adds an option or options to the end of the select. If value
14982 // of the option is empty or missing, a separator is created instead.
14983 // Passing in an array of options will yield slightly better performance
14984 // since the children are only loaded once.
14985 if(!dojo.isArray(option)){ option = [option]; }
14986 dojo.forEach(option, function(i){
14987 if(i && dojo.isObject(i)){
14988 this.options.push(i);
14989 }
14990 }, this);
14991 this._loadChildren();
14992 },
14993
14994 removeOption: function(/* string, dijit.form.__SelectOption, number, or array */ valueOrIdx){
14995 // summary:
14996 // Removes the given option or options. You can remove by string
14997 // (in which case the value is removed), number (in which case the
14998 // index in the options array is removed), or select option (in
14999 // which case, the select option with a matching value is removed).
15000 // You can also pass in an array of those values for a slightly
15001 // better performance since the children are only loaded once.
15002 if(!dojo.isArray(valueOrIdx)){ valueOrIdx = [valueOrIdx]; }
15003 var oldOpts = this.getOptions(valueOrIdx);
15004 dojo.forEach(oldOpts, function(i){
15005 // We can get null back in our array - if our option was not found. In
15006 // that case, we don't want to blow up...
15007 if(i){
15008 this.options = dojo.filter(this.options, function(node, idx){
15009 return (node.value !== i.value);
15010 });
15011 this._removeOptionItem(i);
15012 }
15013 }, this);
15014 this._loadChildren();
15015 },
15016
15017 updateOption: function(/* dijit.form.__SelectOption, dijit.form.__SelectOption[] */ newOption){
15018 // summary:
15019 // Updates the values of the given option. The option to update
15020 // is matched based on the value of the entered option. Passing
15021 // in an array of new options will yeild better performance since
15022 // the children will only be loaded once.
15023 if(!dojo.isArray(newOption)){ newOption = [newOption]; }
15024 dojo.forEach(newOption, function(i){
15025 var oldOpt = this.getOptions(i), k;
15026 if(oldOpt){
15027 for(k in i){ oldOpt[k] = i[k]; }
15028 }
15029 }, this);
15030 this._loadChildren();
15031 },
15032
15033 setStore: function(/* dojo.data.api.Identity */ store,
15034 /* anything? */ selectedValue,
15035 /* Object? */ fetchArgs){
15036 // summary:
15037 // Sets the store you would like to use with this select widget.
15038 // The selected value is the value of the new store to set. This
15039 // function returns the original store, in case you want to reuse
15040 // it or something.
15041 // store: dojo.data.api.Identity
15042 // The store you would like to use - it MUST implement Identity,
15043 // and MAY implement Notification.
15044 // selectedValue: anything?
15045 // The value that this widget should set itself to *after* the store
15046 // has been loaded
15047 // fetchArgs: Object?
15048 // The arguments that will be passed to the store's fetch() function
15049 var oStore = this.store;
15050 fetchArgs = fetchArgs || {};
15051 if(oStore !== store){
15052 // Our store has changed, so update our notifications
15053 dojo.forEach(this._notifyConnections || [], dojo.disconnect);
15054 delete this._notifyConnections;
15055 if(store && store.getFeatures()["dojo.data.api.Notification"]){
15056 this._notifyConnections = [
15057 dojo.connect(store, "onNew", this, "_onNewItem"),
15058 dojo.connect(store, "onDelete", this, "_onDeleteItem"),
15059 dojo.connect(store, "onSet", this, "_onSetItem")
15060 ];
15061 }
15062 this.store = store;
15063 }
15064
15065 // Turn off change notifications while we make all these changes
15066 this._onChangeActive = false;
15067
15068 // Remove existing options (if there are any)
15069 if(this.options && this.options.length){
15070 this.removeOption(this.options);
15071 }
15072
15073 // Add our new options
15074 if(store){
15075 var cb = function(items){
15076 if(this.sortByLabel && !fetchArgs.sort && items.length){
15077 items.sort(dojo.data.util.sorter.createSortFunction([{
15078 attribute: store.getLabelAttributes(items[0])[0]
15079 }], store));
15080 }
15081
15082 if(fetchArgs.onFetch){
15083 items = fetchArgs.onFetch(items);
15084 }
15085 // TODO: Add these guys as a batch, instead of separately
15086 dojo.forEach(items, function(i){
15087 this._addOptionForItem(i);
15088 }, this);
15089
15090 // Set our value (which might be undefined), and then tweak
15091 // it to send a change event with the real value
15092 this._loadingStore = false;
15093 this.set("value", (("_pendingValue" in this) ? this._pendingValue : selectedValue));
15094 delete this._pendingValue;
15095
15096 if(!this.loadChildrenOnOpen){
15097 this._loadChildren();
15098 }else{
15099 this._pseudoLoadChildren(items);
15100 }
15101 this._fetchedWith = opts;
15102 this._lastValueReported = this.multiple ? [] : null;
15103 this._onChangeActive = true;
15104 this.onSetStore();
15105 this._handleOnChange(this.value);
15106 };
15107 var opts = dojo.mixin({onComplete:cb, scope: this}, fetchArgs);
15108 this._loadingStore = true;
15109 store.fetch(opts);
15110 }else{
15111 delete this._fetchedWith;
15112 }
15113 return oStore; // dojo.data.api.Identity
15114 },
15115
15116 _setValueAttr: function(/*anything*/ newValue, /*Boolean, optional*/ priorityChange){
15117 // summary:
15118 // set the value of the widget.
15119 // If a string is passed, then we set our value from looking it up.
15120 if(this._loadingStore){
15121 // Our store is loading - so save our value, and we'll set it when
15122 // we're done
15123 this._pendingValue = newValue;
15124 return;
15125 }
15126 var opts = this.getOptions() || [];
15127 if(!dojo.isArray(newValue)){
15128 newValue = [newValue];
15129 }
15130 dojo.forEach(newValue, function(i, idx){
15131 if(!dojo.isObject(i)){
15132 i = i + "";
15133 }
15134 if(typeof i === "string"){
15135 newValue[idx] = dojo.filter(opts, function(node){
15136 return node.value === i;
15137 })[0] || {value: "", label: ""};
15138 }
15139 }, this);
15140
15141 // Make sure some sane default is set
15142 newValue = dojo.filter(newValue, function(i){ return i && i.value; });
15143 if(!this.multiple && (!newValue[0] || !newValue[0].value) && opts.length){
15144 newValue[0] = opts[0];
15145 }
15146 dojo.forEach(opts, function(i){
15147 i.selected = dojo.some(newValue, function(v){ return v.value === i.value; });
15148 });
15149 var val = dojo.map(newValue, function(i){ return i.value; }),
15150 disp = dojo.map(newValue, function(i){ return i.label; });
15151
15152 this.value = this.multiple ? val : val[0];
15153 this._setDisplay(this.multiple ? disp : disp[0]);
15154 this._updateSelection();
15155 this._handleOnChange(this.value, priorityChange);
15156 },
15157
15158 _getDisplayedValueAttr: function(){
15159 // summary:
15160 // returns the displayed value of the widget
15161 var val = this.get("value");
15162 if(!dojo.isArray(val)){
15163 val = [val];
15164 }
15165 var ret = dojo.map(this.getOptions(val), function(v){
15166 if(v && "label" in v){
15167 return v.label;
15168 }else if(v){
15169 return v.value;
15170 }
15171 return null;
15172 }, this);
15173 return this.multiple ? ret : ret[0];
15174 },
15175
15176 _getValueDeprecated: false, // remove when _FormWidget:getValue is removed
15177 getValue: function(){
15178 // summary:
15179 // get the value of the widget.
15180 return this._lastValue;
15181 },
15182
15183 undo: function(){
15184 // summary:
15185 // restore the value to the last value passed to onChange
15186 this._setValueAttr(this._lastValueReported, false);
15187 },
15188
15189 _loadChildren: function(){
15190 // summary:
15191 // Loads the children represented by this widget's options.
15192 // reset the menu to make it "populatable on the next click
15193 if(this._loadingStore){ return; }
15194 dojo.forEach(this._getChildren(), function(child){
15195 child.destroyRecursive();
15196 });
15197 // Add each menu item
15198 dojo.forEach(this.options, this._addOptionItem, this);
15199
15200 // Update states
15201 this._updateSelection();
15202 },
15203
15204 _updateSelection: function(){
15205 // summary:
15206 // Sets the "selected" class on the item for styling purposes
15207 this.value = this._getValueFromOpts();
15208 var val = this.value;
15209 if(!dojo.isArray(val)){
15210 val = [val];
15211 }
15212 if(val && val[0]){
15213 dojo.forEach(this._getChildren(), function(child){
15214 var isSelected = dojo.some(val, function(v){
15215 return child.option && (v === child.option.value);
15216 });
15217 dojo.toggleClass(child.domNode, this.baseClass + "SelectedOption", isSelected);
15218 dijit.setWaiState(child.domNode, "selected", isSelected);
15219 }, this);
15220 }
15221 this._handleOnChange(this.value);
15222 },
15223
15224 _getValueFromOpts: function(){
15225 // summary:
15226 // Returns the value of the widget by reading the options for
15227 // the selected flag
15228 var opts = this.getOptions() || [];
15229 if(!this.multiple && opts.length){
15230 // Mirror what a select does - choose the first one
15231 var opt = dojo.filter(opts, function(i){
15232 return i.selected;
15233 })[0];
15234 if(opt && opt.value){
15235 return opt.value
15236 }else{
15237 opts[0].selected = true;
15238 return opts[0].value;
15239 }
15240 }else if(this.multiple){
15241 // Set value to be the sum of all selected
15242 return dojo.map(dojo.filter(opts, function(i){
15243 return i.selected;
15244 }), function(i){
15245 return i.value;
15246 }) || [];
15247 }
15248 return "";
15249 },
15250
15251 // Internal functions to call when we have store notifications come in
15252 _onNewItem: function(/* item */ item, /* Object? */ parentInfo){
15253 if(!parentInfo || !parentInfo.parent){
15254 // Only add it if we are top-level
15255 this._addOptionForItem(item);
15256 }
15257 },
15258 _onDeleteItem: function(/* item */ item){
15259 var store = this.store;
15260 this.removeOption(store.getIdentity(item));
15261 },
15262 _onSetItem: function(/* item */ item){
15263 this.updateOption(this._getOptionObjForItem(item));
15264 },
15265
15266 _getOptionObjForItem: function(item){
15267 // summary:
15268 // Returns an option object based off the given item. The "value"
15269 // of the option item will be the identity of the item, the "label"
15270 // of the option will be the label of the item. If the item contains
15271 // children, the children value of the item will be set
15272 var store = this.store, label = store.getLabel(item),
15273 value = (label ? store.getIdentity(item) : null);
15274 return {value: value, label: label, item:item}; // dijit.form.__SelectOption
15275 },
15276
15277 _addOptionForItem: function(/* item */ item){
15278 // summary:
15279 // Creates (and adds) the option for the given item
15280 var store = this.store;
15281 if(!store.isItemLoaded(item)){
15282 // We are not loaded - so let's load it and add later
15283 store.loadItem({item: item, onComplete: function(i){
15284 this._addOptionForItem(item);
15285 },
15286 scope: this});
15287 return;
15288 }
15289 var newOpt = this._getOptionObjForItem(item);
15290 this.addOption(newOpt);
15291 },
15292
15293 constructor: function(/* Object */ keywordArgs){
15294 // summary:
15295 // Saves off our value, if we have an initial one set so we
15296 // can use it if we have a store as well (see startup())
15297 this._oValue = (keywordArgs || {}).value || null;
15298 },
15299
15300 _fillContent: function(){
15301 // summary:
15302 // Loads our options and sets up our dropdown correctly. We
15303 // don't want any content, so we don't call any inherit chain
15304 // function.
15305 var opts = this.options;
15306 if(!opts){
15307 opts = this.options = this.srcNodeRef ? dojo.query(">",
15308 this.srcNodeRef).map(function(node){
15309 if(node.getAttribute("type") === "separator"){
15310 return { value: "", label: "", selected: false, disabled: false };
15311 }
15312 return { value: node.getAttribute("value"),
15313 label: String(node.innerHTML),
15314 selected: node.getAttribute("selected") || false,
15315 disabled: node.getAttribute("disabled") || false };
15316 }, this) : [];
15317 }
15318 if(!this.value){
15319 this.value = this._getValueFromOpts();
15320 }else if(this.multiple && typeof this.value == "string"){
15321 this.value = this.value.split(",");
15322 }
15323 },
15324
15325 postCreate: function(){
15326 // summary:
15327 // sets up our event handling that we need for functioning
15328 // as a select
15329 dojo.setSelectable(this.focusNode, false);
15330 this.inherited(arguments);
15331
15332 // Make our event connections for updating state
15333 this.connect(this, "onChange", "_updateSelection");
15334 this.connect(this, "startup", "_loadChildren");
15335
15336 this._setValueAttr(this.value, null);
15337 },
15338
15339 startup: function(){
15340 // summary:
15341 // Connects in our store, if we have one defined
15342 this.inherited(arguments);
15343 var store = this.store, fetchArgs = {};
15344 dojo.forEach(["query", "queryOptions", "onFetch"], function(i){
15345 if(this[i]){
15346 fetchArgs[i] = this[i];
15347 }
15348 delete this[i];
15349 }, this);
15350 if(store && store.getFeatures()["dojo.data.api.Identity"]){
15351 // Temporarily set our store to null so that it will get set
15352 // and connected appropriately
15353 this.store = null;
15354 this.setStore(store, this._oValue, fetchArgs);
15355 }
15356 },
15357
15358 destroy: function(){
15359 // summary:
15360 // Clean up our connections
15361 dojo.forEach(this._notifyConnections || [], dojo.disconnect);
15362 this.inherited(arguments);
15363 },
15364
15365 _addOptionItem: function(/* dijit.form.__SelectOption */ option){
15366 // summary:
15367 // User-overridable function which, for the given option, adds an
15368 // item to the select. If the option doesn't have a value, then a
15369 // separator is added in that place. Make sure to store the option
15370 // in the created option widget.
15371 },
15372
15373 _removeOptionItem: function(/* dijit.form.__SelectOption */ option){
15374 // summary:
15375 // User-overridable function which, for the given option, removes
15376 // its item from the select.
15377 },
15378
15379 _setDisplay: function(/*String or String[]*/ newDisplay){
15380 // summary:
15381 // Overridable function which will set the display for the
15382 // widget. newDisplay is either a string (in the case of
15383 // single selects) or array of strings (in the case of multi-selects)
15384 },
15385
15386 _getChildren: function(){
15387 // summary:
15388 // Overridable function to return the children that this widget contains.
15389 return [];
15390 },
15391
15392 _getSelectedOptionsAttr: function(){
15393 // summary:
15394 // hooks into this.attr to provide a mechanism for getting the
15395 // option items for the current value of the widget.
15396 return this.getOptions(this.get("value"));
15397 },
15398
15399 _pseudoLoadChildren: function(/* item[] */ items){
15400 // summary:
15401 // a function that will "fake" loading children, if needed, and
15402 // if we have set to not load children until the widget opens.
15403 // items:
15404 // An array of items that will be loaded, when needed
15405 },
15406
15407 onSetStore: function(){
15408 // summary:
15409 // a function that can be connected to in order to receive a
15410 // notification that the store has finished loading and all options
15411 // from that store are available
15412 }
15413 });
15414
15415 }
15416
15417 if(!dojo._hasResource["dijit._KeyNavContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15418 dojo._hasResource["dijit._KeyNavContainer"] = true;
15419 dojo.provide("dijit._KeyNavContainer");
15420
15421
15422 dojo.declare("dijit._KeyNavContainer",
15423 dijit._Container,
15424 {
15425
15426 // summary:
15427 // A _Container with keyboard navigation of its children.
15428 // description:
15429 // To use this mixin, call connectKeyNavHandlers() in
15430 // postCreate() and call startupKeyNavChildren() in startup().
15431 // It provides normalized keyboard and focusing code for Container
15432 // widgets.
15433 /*=====
15434 // focusedChild: [protected] Widget
15435 // The currently focused child widget, or null if there isn't one
15436 focusedChild: null,
15437 =====*/
15438
15439 // tabIndex: Integer
15440 // Tab index of the container; same as HTML tabIndex attribute.
15441 // Note then when user tabs into the container, focus is immediately
15442 // moved to the first item in the container.
15443 tabIndex: "0",
15444
15445 _keyNavCodes: {},
15446
15447 connectKeyNavHandlers: function(/*dojo.keys[]*/ prevKeyCodes, /*dojo.keys[]*/ nextKeyCodes){
15448 // summary:
15449 // Call in postCreate() to attach the keyboard handlers
15450 // to the container.
15451 // preKeyCodes: dojo.keys[]
15452 // Key codes for navigating to the previous child.
15453 // nextKeyCodes: dojo.keys[]
15454 // Key codes for navigating to the next child.
15455 // tags:
15456 // protected
15457
15458 var keyCodes = (this._keyNavCodes = {});
15459 var prev = dojo.hitch(this, this.focusPrev);
15460 var next = dojo.hitch(this, this.focusNext);
15461 dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; });
15462 dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; });
15463 this.connect(this.domNode, "onkeypress", "_onContainerKeypress");
15464 this.connect(this.domNode, "onfocus", "_onContainerFocus");
15465 },
15466
15467 startupKeyNavChildren: function(){
15468 // summary:
15469 // Call in startup() to set child tabindexes to -1
15470 // tags:
15471 // protected
15472 dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild"));
15473 },
15474
15475 addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
15476 // summary:
15477 // Add a child to our _Container
15478 dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
15479 this._startupChild(widget);
15480 },
15481
15482 focus: function(){
15483 // summary:
15484 // Default focus() implementation: focus the first child.
15485 this.focusFirstChild();
15486 },
15487
15488 focusFirstChild: function(){
15489 // summary:
15490 // Focus the first focusable child in the container.
15491 // tags:
15492 // protected
15493 var child = this._getFirstFocusableChild();
15494 if(child){ // edge case: Menu could be empty or hidden
15495 this.focusChild(child);
15496 }
15497 },
15498
15499 focusNext: function(){
15500 // summary:
15501 // Focus the next widget
15502 // tags:
15503 // protected
15504 var child = this._getNextFocusableChild(this.focusedChild, 1);
15505 this.focusChild(child);
15506 },
15507
15508 focusPrev: function(){
15509 // summary:
15510 // Focus the last focusable node in the previous widget
15511 // (ex: go to the ComboButton icon section rather than button section)
15512 // tags:
15513 // protected
15514 var child = this._getNextFocusableChild(this.focusedChild, -1);
15515 this.focusChild(child, true);
15516 },
15517
15518 focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){
15519 // summary:
15520 // Focus widget.
15521 // widget:
15522 // Reference to container's child widget
15523 // last:
15524 // If true and if widget has multiple focusable nodes, focus the
15525 // last one instead of the first one
15526 // tags:
15527 // protected
15528
15529 if(this.focusedChild && widget !== this.focusedChild){
15530 this._onChildBlur(this.focusedChild);
15531 }
15532 widget.focus(last ? "end" : "start");
15533 this.focusedChild = widget;
15534 },
15535
15536 _startupChild: function(/*dijit._Widget*/ widget){
15537 // summary:
15538 // Setup for each child widget
15539 // description:
15540 // Sets tabIndex=-1 on each child, so that the tab key will
15541 // leave the container rather than visiting each child.
15542 // tags:
15543 // private
15544
15545 widget.set("tabIndex", "-1");
15546
15547 this.connect(widget, "_onFocus", function(){
15548 // Set valid tabIndex so tabbing away from widget goes to right place, see #10272
15549 widget.set("tabIndex", this.tabIndex);
15550 });
15551 this.connect(widget, "_onBlur", function(){
15552 widget.set("tabIndex", "-1");
15553 });
15554 },
15555
15556 _onContainerFocus: function(evt){
15557 // summary:
15558 // Handler for when the container gets focus
15559 // description:
15560 // Initially the container itself has a tabIndex, but when it gets
15561 // focus, switch focus to first child...
15562 // tags:
15563 // private
15564
15565 // Note that we can't use _onFocus() because switching focus from the
15566 // _onFocus() handler confuses the focus.js code
15567 // (because it causes _onFocusNode() to be called recursively)
15568
15569 // focus bubbles on Firefox,
15570 // so just make sure that focus has really gone to the container
15571 if(evt.target !== this.domNode){ return; }
15572
15573 this.focusFirstChild();
15574
15575 // and then set the container's tabIndex to -1,
15576 // (don't remove as that breaks Safari 4)
15577 // so that tab or shift-tab will go to the fields after/before
15578 // the container, rather than the container itself
15579 dojo.attr(this.domNode, "tabIndex", "-1");
15580 },
15581
15582 _onBlur: function(evt){
15583 // When focus is moved away the container, and its descendant (popup) widgets,
15584 // then restore the container's tabIndex so that user can tab to it again.
15585 // Note that using _onBlur() so that this doesn't happen when focus is shifted
15586 // to one of my child widgets (typically a popup)
15587 if(this.tabIndex){
15588 dojo.attr(this.domNode, "tabIndex", this.tabIndex);
15589 }
15590 this.inherited(arguments);
15591 },
15592
15593 _onContainerKeypress: function(evt){
15594 // summary:
15595 // When a key is pressed, if it's an arrow key etc. then
15596 // it's handled here.
15597 // tags:
15598 // private
15599 if(evt.ctrlKey || evt.altKey){ return; }
15600 var func = this._keyNavCodes[evt.charOrCode];
15601 if(func){
15602 func();
15603 dojo.stopEvent(evt);
15604 }
15605 },
15606
15607 _onChildBlur: function(/*dijit._Widget*/ widget){
15608 // summary:
15609 // Called when focus leaves a child widget to go
15610 // to a sibling widget.
15611 // tags:
15612 // protected
15613 },
15614
15615 _getFirstFocusableChild: function(){
15616 // summary:
15617 // Returns first child that can be focused
15618 return this._getNextFocusableChild(null, 1); // dijit._Widget
15619 },
15620
15621 _getNextFocusableChild: function(child, dir){
15622 // summary:
15623 // Returns the next or previous focusable child, compared
15624 // to "child"
15625 // child: Widget
15626 // The current widget
15627 // dir: Integer
15628 // * 1 = after
15629 // * -1 = before
15630 if(child){
15631 child = this._getSiblingOfChild(child, dir);
15632 }
15633 var children = this.getChildren();
15634 for(var i=0; i < children.length; i++){
15635 if(!child){
15636 child = children[(dir>0) ? 0 : (children.length-1)];
15637 }
15638 if(child.isFocusable()){
15639 return child; // dijit._Widget
15640 }
15641 child = this._getSiblingOfChild(child, dir);
15642 }
15643 // no focusable child found
15644 return null; // dijit._Widget
15645 }
15646 }
15647 );
15648
15649 }
15650
15651 if(!dojo._hasResource["dijit.MenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15652 dojo._hasResource["dijit.MenuItem"] = true;
15653 dojo.provide("dijit.MenuItem");
15654
15655
15656
15657
15658
15659
15660 dojo.declare("dijit.MenuItem",
15661 [dijit._Widget, dijit._Templated, dijit._Contained, dijit._CssStateMixin],
15662 {
15663 // summary:
15664 // A line item in a Menu Widget
15665
15666 // Make 3 columns
15667 // icon, label, and expand arrow (BiDi-dependent) indicating sub-menu
15668 templateString: dojo.cache("dijit", "templates/MenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitem\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" waiRole=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" waiRole=\"presentation\">\n\t\t<div dojoAttachPoint=\"arrowWrapper\" style=\"visibility: hidden\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuExpand\"/>\n\t\t\t<span class=\"dijitMenuExpandA11y\">+</span>\n\t\t</div>\n\t</td>\n</tr>\n"),
15669
15670 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
15671 label: { node: "containerNode", type: "innerHTML" },
15672 iconClass: { node: "iconNode", type: "class" }
15673 }),
15674
15675 baseClass: "dijitMenuItem",
15676
15677 // label: String
15678 // Menu text
15679 label: '',
15680
15681 // iconClass: String
15682 // Class to apply to DOMNode to make it display an icon.
15683 iconClass: "",
15684
15685 // accelKey: String
15686 // Text for the accelerator (shortcut) key combination.
15687 // Note that although Menu can display accelerator keys there
15688 // is no infrastructure to actually catch and execute these
15689 // accelerators.
15690 accelKey: "",
15691
15692 // disabled: Boolean
15693 // If true, the menu item is disabled.
15694 // If false, the menu item is enabled.
15695 disabled: false,
15696
15697 _fillContent: function(/*DomNode*/ source){
15698 // If button label is specified as srcNodeRef.innerHTML rather than
15699 // this.params.label, handle it here.
15700 if(source && !("label" in this.params)){
15701 this.set('label', source.innerHTML);
15702 }
15703 },
15704
15705 postCreate: function(){
15706 this.inherited(arguments);
15707 dojo.setSelectable(this.domNode, false);
15708 var label = this.id+"_text";
15709 dojo.attr(this.containerNode, "id", label);
15710 if(this.accelKeyNode){
15711 dojo.attr(this.accelKeyNode, "id", this.id + "_accel");
15712 label += " " + this.id + "_accel";
15713 }
15714 dijit.setWaiState(this.domNode, "labelledby", label);
15715 },
15716
15717 _onHover: function(){
15718 // summary:
15719 // Handler when mouse is moved onto menu item
15720 // tags:
15721 // protected
15722 this.getParent().onItemHover(this);
15723 },
15724
15725 _onUnhover: function(){
15726 // summary:
15727 // Handler when mouse is moved off of menu item,
15728 // possibly to a child menu, or maybe to a sibling
15729 // menuitem or somewhere else entirely.
15730 // tags:
15731 // protected
15732
15733 // if we are unhovering the currently selected item
15734 // then unselect it
15735 this.getParent().onItemUnhover(this);
15736
15737 // _onUnhover() is called when the menu is hidden (collapsed), due to clicking
15738 // a MenuItem and having it execut. When that happens, FF and IE don't generate
15739 // an onmouseout event for the MenuItem, so give _CssStateMixin some help
15740 this._hovering = false;
15741 this._setStateClass();
15742 },
15743
15744 _onClick: function(evt){
15745 // summary:
15746 // Internal handler for click events on MenuItem.
15747 // tags:
15748 // private
15749 this.getParent().onItemClick(this, evt);
15750 dojo.stopEvent(evt);
15751 },
15752
15753 onClick: function(/*Event*/ evt){
15754 // summary:
15755 // User defined function to handle clicks
15756 // tags:
15757 // callback
15758 },
15759
15760 focus: function(){
15761 // summary:
15762 // Focus on this MenuItem
15763 try{
15764 if(dojo.isIE == 8){
15765 // needed for IE8 which won't scroll TR tags into view on focus yet calling scrollIntoView creates flicker (#10275)
15766 this.containerNode.focus();
15767 }
15768 dijit.focus(this.focusNode);
15769 }catch(e){
15770 // this throws on IE (at least) in some scenarios
15771 }
15772 },
15773
15774 _onFocus: function(){
15775 // summary:
15776 // This is called by the focus manager when focus
15777 // goes to this MenuItem or a child menu.
15778 // tags:
15779 // protected
15780 this._setSelected(true);
15781 this.getParent()._onItemFocus(this);
15782
15783 this.inherited(arguments);
15784 },
15785
15786 _setSelected: function(selected){
15787 // summary:
15788 // Indicate that this node is the currently selected one
15789 // tags:
15790 // private
15791
15792 /***
15793 * TODO: remove this method and calls to it, when _onBlur() is working for MenuItem.
15794 * Currently _onBlur() gets called when focus is moved from the MenuItem to a child menu.
15795 * That's not supposed to happen, but the problem is:
15796 * In order to allow dijit.popup's getTopPopup() to work,a sub menu's popupParent
15797 * points to the parent Menu, bypassing the parent MenuItem... thus the
15798 * MenuItem is not in the chain of active widgets and gets a premature call to
15799 * _onBlur()
15800 */
15801
15802 dojo.toggleClass(this.domNode, "dijitMenuItemSelected", selected);
15803 },
15804
15805 setLabel: function(/*String*/ content){
15806 // summary:
15807 // Deprecated. Use set('label', ...) instead.
15808 // tags:
15809 // deprecated
15810 dojo.deprecated("dijit.MenuItem.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0");
15811 this.set("label", content);
15812 },
15813
15814 setDisabled: function(/*Boolean*/ disabled){
15815 // summary:
15816 // Deprecated. Use set('disabled', bool) instead.
15817 // tags:
15818 // deprecated
15819 dojo.deprecated("dijit.Menu.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0");
15820 this.set('disabled', disabled);
15821 },
15822 _setDisabledAttr: function(/*Boolean*/ value){
15823 // summary:
15824 // Hook for attr('disabled', ...) to work.
15825 // Enable or disable this menu item.
15826 this.disabled = value;
15827 dijit.setWaiState(this.focusNode, 'disabled', value ? 'true' : 'false');
15828 },
15829 _setAccelKeyAttr: function(/*String*/ value){
15830 // summary:
15831 // Hook for attr('accelKey', ...) to work.
15832 // Set accelKey on this menu item.
15833 this.accelKey=value;
15834
15835 this.accelKeyNode.style.display=value?"":"none";
15836 this.accelKeyNode.innerHTML=value;
15837 //have to use colSpan to make it work in IE
15838 dojo.attr(this.containerNode,'colSpan',value?"1":"2");
15839 }
15840 });
15841
15842 }
15843
15844 if(!dojo._hasResource["dijit.PopupMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15845 dojo._hasResource["dijit.PopupMenuItem"] = true;
15846 dojo.provide("dijit.PopupMenuItem");
15847
15848
15849
15850 dojo.declare("dijit.PopupMenuItem",
15851 dijit.MenuItem,
15852 {
15853 _fillContent: function(){
15854 // summary:
15855 // When Menu is declared in markup, this code gets the menu label and
15856 // the popup widget from the srcNodeRef.
15857 // description:
15858 // srcNodeRefinnerHTML contains both the menu item text and a popup widget
15859 // The first part holds the menu item text and the second part is the popup
15860 // example:
15861 // | <div dojoType="dijit.PopupMenuItem">
15862 // | <span>pick me</span>
15863 // | <popup> ... </popup>
15864 // | </div>
15865 // tags:
15866 // protected
15867
15868 if(this.srcNodeRef){
15869 var nodes = dojo.query("*", this.srcNodeRef);
15870 dijit.PopupMenuItem.superclass._fillContent.call(this, nodes[0]);
15871
15872 // save pointer to srcNode so we can grab the drop down widget after it's instantiated
15873 this.dropDownContainer = this.srcNodeRef;
15874 }
15875 },
15876
15877 startup: function(){
15878 if(this._started){ return; }
15879 this.inherited(arguments);
15880
15881 // we didn't copy the dropdown widget from the this.srcNodeRef, so it's in no-man's
15882 // land now. move it to dojo.doc.body.
15883 if(!this.popup){
15884 var node = dojo.query("[widgetId]", this.dropDownContainer)[0];
15885 this.popup = dijit.byNode(node);
15886 }
15887 dojo.body().appendChild(this.popup.domNode);
15888 this.popup.startup();
15889
15890 this.popup.domNode.style.display="none";
15891 if(this.arrowWrapper){
15892 dojo.style(this.arrowWrapper, "visibility", "");
15893 }
15894 dijit.setWaiState(this.focusNode, "haspopup", "true");
15895 },
15896
15897 destroyDescendants: function(){
15898 if(this.popup){
15899 // Destroy the popup, unless it's already been destroyed. This can happen because
15900 // the popup is a direct child of <body> even though it's logically my child.
15901 if(!this.popup._destroyed){
15902 this.popup.destroyRecursive();
15903 }
15904 delete this.popup;
15905 }
15906 this.inherited(arguments);
15907 }
15908 });
15909
15910
15911 }
15912
15913 if(!dojo._hasResource["dijit.CheckedMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15914 dojo._hasResource["dijit.CheckedMenuItem"] = true;
15915 dojo.provide("dijit.CheckedMenuItem");
15916
15917
15918
15919 dojo.declare("dijit.CheckedMenuItem",
15920 dijit.MenuItem,
15921 {
15922 // summary:
15923 // A checkbox-like menu item for toggling on and off
15924
15925 templateString: dojo.cache("dijit", "templates/CheckedMenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitemcheckbox\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" waiRole=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuItemIcon dijitCheckedMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t<span class=\"dijitCheckedMenuItemIconChar\">&#10003;</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" waiRole=\"presentation\">&nbsp;</td>\n</tr>\n"),
15926
15927 // checked: Boolean
15928 // Our checked state
15929 checked: false,
15930 _setCheckedAttr: function(/*Boolean*/ checked){
15931 // summary:
15932 // Hook so attr('checked', bool) works.
15933 // Sets the class and state for the check box.
15934 dojo.toggleClass(this.domNode, "dijitCheckedMenuItemChecked", checked);
15935 dijit.setWaiState(this.domNode, "checked", checked);
15936 this.checked = checked;
15937 },
15938
15939 onChange: function(/*Boolean*/ checked){
15940 // summary:
15941 // User defined function to handle check/uncheck events
15942 // tags:
15943 // callback
15944 },
15945
15946 _onClick: function(/*Event*/ e){
15947 // summary:
15948 // Clicking this item just toggles its state
15949 // tags:
15950 // private
15951 if(!this.disabled){
15952 this.set("checked", !this.checked);
15953 this.onChange(this.checked);
15954 }
15955 this.inherited(arguments);
15956 }
15957 });
15958
15959 }
15960
15961 if(!dojo._hasResource["dijit.MenuSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15962 dojo._hasResource["dijit.MenuSeparator"] = true;
15963 dojo.provide("dijit.MenuSeparator");
15964
15965
15966
15967
15968
15969 dojo.declare("dijit.MenuSeparator",
15970 [dijit._Widget, dijit._Templated, dijit._Contained],
15971 {
15972 // summary:
15973 // A line between two menu items
15974
15975 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"),
15976
15977 postCreate: function(){
15978 dojo.setSelectable(this.domNode, false);
15979 },
15980
15981 isFocusable: function(){
15982 // summary:
15983 // Override to always return false
15984 // tags:
15985 // protected
15986
15987 return false; // Boolean
15988 }
15989 });
15990
15991
15992 }
15993
15994 if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15995 dojo._hasResource["dijit.Menu"] = true;
15996 dojo.provide("dijit.Menu");
15997
15998
15999
16000
16001
16002
16003
16004 dojo.declare("dijit._MenuBase",
16005 [dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
16006 {
16007 // summary:
16008 // Base class for Menu and MenuBar
16009
16010 // parentMenu: [readonly] Widget
16011 // pointer to menu that displayed me
16012 parentMenu: null,
16013
16014 // popupDelay: Integer
16015 // number of milliseconds before hovering (without clicking) causes the popup to automatically open.
16016 popupDelay: 500,
16017
16018 startup: function(){
16019 if(this._started){ return; }
16020
16021 dojo.forEach(this.getChildren(), function(child){ child.startup(); });
16022 this.startupKeyNavChildren();
16023
16024 this.inherited(arguments);
16025 },
16026
16027 onExecute: function(){
16028 // summary:
16029 // Attach point for notification about when a menu item has been executed.
16030 // This is an internal mechanism used for Menus to signal to their parent to
16031 // close them, because they are about to execute the onClick handler. In
16032 // general developers should not attach to or override this method.
16033 // tags:
16034 // protected
16035 },
16036
16037 onCancel: function(/*Boolean*/ closeAll){
16038 // summary:
16039 // Attach point for notification about when the user cancels the current menu
16040 // This is an internal mechanism used for Menus to signal to their parent to
16041 // close them. In general developers should not attach to or override this method.
16042 // tags:
16043 // protected
16044 },
16045
16046 _moveToPopup: function(/*Event*/ evt){
16047 // summary:
16048 // This handles the right arrow key (left arrow key on RTL systems),
16049 // which will either open a submenu, or move to the next item in the
16050 // ancestor MenuBar
16051 // tags:
16052 // private
16053
16054 if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
16055 this.focusedChild._onClick(evt);
16056 }else{
16057 var topMenu = this._getTopMenu();
16058 if(topMenu && topMenu._isMenuBar){
16059 topMenu.focusNext();
16060 }
16061 }
16062 },
16063
16064 _onPopupHover: function(/*Event*/ evt){
16065 // summary:
16066 // This handler is called when the mouse moves over the popup.
16067 // tags:
16068 // private
16069
16070 // if the mouse hovers over a menu popup that is in pending-close state,
16071 // then stop the close operation.
16072 // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker)
16073 if(this.currentPopup && this.currentPopup._pendingClose_timer){
16074 var parentMenu = this.currentPopup.parentMenu;
16075 // highlight the parent menu item pointing to this popup
16076 if(parentMenu.focusedChild){
16077 parentMenu.focusedChild._setSelected(false);
16078 }
16079 parentMenu.focusedChild = this.currentPopup.from_item;
16080 parentMenu.focusedChild._setSelected(true);
16081 // cancel the pending close
16082 this._stopPendingCloseTimer(this.currentPopup);
16083 }
16084 },
16085
16086 onItemHover: function(/*MenuItem*/ item){
16087 // summary:
16088 // Called when cursor is over a MenuItem.
16089 // tags:
16090 // protected
16091
16092 // Don't do anything unless user has "activated" the menu by:
16093 // 1) clicking it
16094 // 2) opening it from a parent menu (which automatically focuses it)
16095 if(this.isActive){
16096 this.focusChild(item);
16097 if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){
16098 this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup"), this.popupDelay);
16099 }
16100 }
16101 // if the user is mixing mouse and keyboard navigation,
16102 // then the menu may not be active but a menu item has focus,
16103 // but it's not the item that the mouse just hovered over.
16104 // To avoid both keyboard and mouse selections, use the latest.
16105 if(this.focusedChild){
16106 this.focusChild(item);
16107 }
16108 this._hoveredChild = item;
16109 },
16110
16111 _onChildBlur: function(item){
16112 // summary:
16113 // Called when a child MenuItem becomes inactive because focus
16114 // has been removed from the MenuItem *and* it's descendant menus.
16115 // tags:
16116 // private
16117 this._stopPopupTimer();
16118 item._setSelected(false);
16119 // Close all popups that are open and descendants of this menu
16120 var itemPopup = item.popup;
16121 if(itemPopup){
16122 this._stopPendingCloseTimer(itemPopup);
16123 itemPopup._pendingClose_timer = setTimeout(function(){
16124 itemPopup._pendingClose_timer = null;
16125 if(itemPopup.parentMenu){
16126 itemPopup.parentMenu.currentPopup = null;
16127 }
16128 dijit.popup.close(itemPopup); // this calls onClose
16129 }, this.popupDelay);
16130 }
16131 },
16132
16133 onItemUnhover: function(/*MenuItem*/ item){
16134 // summary:
16135 // Callback fires when mouse exits a MenuItem
16136 // tags:
16137 // protected
16138
16139 if(this.isActive){
16140 this._stopPopupTimer();
16141 }
16142 if(this._hoveredChild == item){ this._hoveredChild = null; }
16143 },
16144
16145 _stopPopupTimer: function(){
16146 // summary:
16147 // Cancels the popup timer because the user has stop hovering
16148 // on the MenuItem, etc.
16149 // tags:
16150 // private
16151 if(this.hover_timer){
16152 clearTimeout(this.hover_timer);
16153 this.hover_timer = null;
16154 }
16155 },
16156
16157 _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){
16158 // summary:
16159 // Cancels the pending-close timer because the close has been preempted
16160 // tags:
16161 // private
16162 if(popup._pendingClose_timer){
16163 clearTimeout(popup._pendingClose_timer);
16164 popup._pendingClose_timer = null;
16165 }
16166 },
16167
16168 _stopFocusTimer: function(){
16169 // summary:
16170 // Cancels the pending-focus timer because the menu was closed before focus occured
16171 // tags:
16172 // private
16173 if(this._focus_timer){
16174 clearTimeout(this._focus_timer);
16175 this._focus_timer = null;
16176 }
16177 },
16178
16179 _getTopMenu: function(){
16180 // summary:
16181 // Returns the top menu in this chain of Menus
16182 // tags:
16183 // private
16184 for(var top=this; top.parentMenu; top=top.parentMenu);
16185 return top;
16186 },
16187
16188 onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){
16189 // summary:
16190 // Handle clicks on an item.
16191 // tags:
16192 // private
16193
16194 // this can't be done in _onFocus since the _onFocus events occurs asynchronously
16195 if(typeof this.isShowingNow == 'undefined'){ // non-popup menu
16196 this._markActive();
16197 }
16198
16199 this.focusChild(item);
16200
16201 if(item.disabled){ return false; }
16202
16203 if(item.popup){
16204 this._openPopup();
16205 }else{
16206 // before calling user defined handler, close hierarchy of menus
16207 // and restore focus to place it was when menu was opened
16208 this.onExecute();
16209
16210 // user defined handler for click
16211 item.onClick(evt);
16212 }
16213 },
16214
16215 _openPopup: function(){
16216 // summary:
16217 // Open the popup to the side of/underneath the current menu item
16218 // tags:
16219 // protected
16220
16221 this._stopPopupTimer();
16222 var from_item = this.focusedChild;
16223 if(!from_item){ return; } // the focused child lost focus since the timer was started
16224 var popup = from_item.popup;
16225 if(popup.isShowingNow){ return; }
16226 if(this.currentPopup){
16227 this._stopPendingCloseTimer(this.currentPopup);
16228 dijit.popup.close(this.currentPopup);
16229 }
16230 popup.parentMenu = this;
16231 popup.from_item = from_item; // helps finding the parent item that should be focused for this popup
16232 var self = this;
16233 dijit.popup.open({
16234 parent: this,
16235 popup: popup,
16236 around: from_item.domNode,
16237 orient: this._orient || (this.isLeftToRight() ?
16238 {'TR': 'TL', 'TL': 'TR', 'BR': 'BL', 'BL': 'BR'} :
16239 {'TL': 'TR', 'TR': 'TL', 'BL': 'BR', 'BR': 'BL'}),
16240 onCancel: function(){ // called when the child menu is canceled
16241 // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus
16242 // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro)
16243 self.focusChild(from_item); // put focus back on my node
16244 self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved)
16245 from_item._setSelected(true); // oops, _cleanUp() deselected the item
16246 self.focusedChild = from_item; // and unset focusedChild
16247 },
16248 onExecute: dojo.hitch(this, "_cleanUp")
16249 });
16250
16251 this.currentPopup = popup;
16252 // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items
16253 popup.connect(popup.domNode, "onmouseenter", dojo.hitch(self, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close
16254
16255 if(popup.focus){
16256 // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar),
16257 // if the cursor happens to collide with the popup, it will generate an onmouseover event
16258 // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that
16259 // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742)
16260 popup._focus_timer = setTimeout(dojo.hitch(popup, function(){
16261 this._focus_timer = null;
16262 this.focus();
16263 }), 0);
16264 }
16265 },
16266
16267 _markActive: function(){
16268 // summary:
16269 // Mark this menu's state as active.
16270 // Called when this Menu gets focus from:
16271 // 1) clicking it (mouse or via space/arrow key)
16272 // 2) being opened by a parent menu.
16273 // This is not called just from mouse hover.
16274 // Focusing a menu via TAB does NOT automatically set isActive
16275 // since TAB is a navigation operation and not a selection one.
16276 // For Windows apps, pressing the ALT key focuses the menubar
16277 // menus (similar to TAB navigation) but the menu is not active
16278 // (ie no dropdown) until an item is clicked.
16279 this.isActive = true;
16280 dojo.addClass(this.domNode, "dijitMenuActive");
16281 dojo.removeClass(this.domNode, "dijitMenuPassive");
16282 },
16283
16284 onOpen: function(/*Event*/ e){
16285 // summary:
16286 // Callback when this menu is opened.
16287 // This is called by the popup manager as notification that the menu
16288 // was opened.
16289 // tags:
16290 // private
16291
16292 this.isShowingNow = true;
16293 this._markActive();
16294 },
16295
16296 _markInactive: function(){
16297 // summary:
16298 // Mark this menu's state as inactive.
16299 this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here
16300 dojo.removeClass(this.domNode, "dijitMenuActive");
16301 dojo.addClass(this.domNode, "dijitMenuPassive");
16302 },
16303
16304 onClose: function(){
16305 // summary:
16306 // Callback when this menu is closed.
16307 // This is called by the popup manager as notification that the menu
16308 // was closed.
16309 // tags:
16310 // private
16311
16312 this._stopFocusTimer();
16313 this._markInactive();
16314 this.isShowingNow = false;
16315 this.parentMenu = null;
16316 },
16317
16318 _closeChild: function(){
16319 // summary:
16320 // Called when submenu is clicked or focus is lost. Close hierarchy of menus.
16321 // tags:
16322 // private
16323 this._stopPopupTimer();
16324 if(this.focusedChild){ // unhighlight the focused item
16325 this.focusedChild._setSelected(false);
16326 this.focusedChild._onUnhover();
16327 this.focusedChild = null;
16328 }
16329 if(this.currentPopup){
16330 // Close all popups that are open and descendants of this menu
16331 dijit.popup.close(this.currentPopup);
16332 this.currentPopup = null;
16333 }
16334 },
16335
16336 _onItemFocus: function(/*MenuItem*/ item){
16337 // summary:
16338 // Called when child of this Menu gets focus from:
16339 // 1) clicking it
16340 // 2) tabbing into it
16341 // 3) being opened by a parent menu.
16342 // This is not called just from mouse hover.
16343 if(this._hoveredChild && this._hoveredChild != item){
16344 this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection
16345 }
16346 },
16347
16348 _onBlur: function(){
16349 // summary:
16350 // Called when focus is moved away from this Menu and it's submenus.
16351 // tags:
16352 // protected
16353 this._cleanUp();
16354 this.inherited(arguments);
16355 },
16356
16357 _cleanUp: function(){
16358 // summary:
16359 // Called when the user is done with this menu. Closes hierarchy of menus.
16360 // tags:
16361 // private
16362
16363 this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close
16364 if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose
16365 this._markInactive();
16366 }
16367 }
16368 });
16369
16370 dojo.declare("dijit.Menu",
16371 dijit._MenuBase,
16372 {
16373 // summary
16374 // A context menu you can assign to multiple elements
16375
16376 // TODO: most of the code in here is just for context menu (right-click menu)
16377 // support. In retrospect that should have been a separate class (dijit.ContextMenu).
16378 // Split them for 2.0
16379
16380 constructor: function(){
16381 this._bindings = [];
16382 },
16383
16384 templateString: dojo.cache("dijit", "templates/Menu.html", "<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" waiRole=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\" cellspacing=0>\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"),
16385
16386 baseClass: "dijitMenu",
16387
16388 // targetNodeIds: [const] String[]
16389 // Array of dom node ids of nodes to attach to.
16390 // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes.
16391 targetNodeIds: [],
16392
16393 // contextMenuForWindow: [const] Boolean
16394 // If true, right clicking anywhere on the window will cause this context menu to open.
16395 // If false, must specify targetNodeIds.
16396 contextMenuForWindow: false,
16397
16398 // leftClickToOpen: [const] Boolean
16399 // If true, menu will open on left click instead of right click, similiar to a file menu.
16400 leftClickToOpen: false,
16401
16402 // refocus: Boolean
16403 // When this menu closes, re-focus the element which had focus before it was opened.
16404 refocus: true,
16405
16406 postCreate: function(){
16407 if(this.contextMenuForWindow){
16408 this.bindDomNode(dojo.body());
16409 }else{
16410 // TODO: should have _setTargetNodeIds() method to handle initialization and a possible
16411 // later attr('targetNodeIds', ...) call. There's also a problem that targetNodeIds[]
16412 // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610)
16413 dojo.forEach(this.targetNodeIds, this.bindDomNode, this);
16414 }
16415 var k = dojo.keys, l = this.isLeftToRight();
16416 this._openSubMenuKey = l ? k.RIGHT_ARROW : k.LEFT_ARROW;
16417 this._closeSubMenuKey = l ? k.LEFT_ARROW : k.RIGHT_ARROW;
16418 this.connectKeyNavHandlers([k.UP_ARROW], [k.DOWN_ARROW]);
16419 },
16420
16421 _onKeyPress: function(/*Event*/ evt){
16422 // summary:
16423 // Handle keyboard based menu navigation.
16424 // tags:
16425 // protected
16426
16427 if(evt.ctrlKey || evt.altKey){ return; }
16428
16429 switch(evt.charOrCode){
16430 case this._openSubMenuKey:
16431 this._moveToPopup(evt);
16432 dojo.stopEvent(evt);
16433 break;
16434 case this._closeSubMenuKey:
16435 if(this.parentMenu){
16436 if(this.parentMenu._isMenuBar){
16437 this.parentMenu.focusPrev();
16438 }else{
16439 this.onCancel(false);
16440 }
16441 }else{
16442 dojo.stopEvent(evt);
16443 }
16444 break;
16445 }
16446 },
16447
16448 // thanks burstlib!
16449 _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){
16450 // summary:
16451 // Returns the window reference of the passed iframe
16452 // tags:
16453 // private
16454 var win = dojo.window.get(this._iframeContentDocument(iframe_el)) ||
16455 // Moz. TODO: is this available when defaultView isn't?
16456 this._iframeContentDocument(iframe_el)['__parent__'] ||
16457 (iframe_el.name && dojo.doc.frames[iframe_el.name]) || null;
16458 return win; // Window
16459 },
16460
16461 _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){
16462 // summary:
16463 // Returns a reference to the document object inside iframe_el
16464 // tags:
16465 // protected
16466 var doc = iframe_el.contentDocument // W3
16467 || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE
16468 || (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document)
16469 || null;
16470 return doc; // HTMLDocument
16471 },
16472
16473 bindDomNode: function(/*String|DomNode*/ node){
16474 // summary:
16475 // Attach menu to given node
16476 node = dojo.byId(node);
16477
16478 var cn; // Connect node
16479
16480 // Support context menus on iframes. Rather than binding to the iframe itself we need
16481 // to bind to the <body> node inside the iframe.
16482 if(node.tagName.toLowerCase() == "iframe"){
16483 var iframe = node,
16484 win = this._iframeContentWindow(iframe);
16485 cn = dojo.withGlobal(win, dojo.body);
16486 }else{
16487
16488 // To capture these events at the top level, attach to <html>, not <body>.
16489 // Otherwise right-click context menu just doesn't work.
16490 cn = (node == dojo.body() ? dojo.doc.documentElement : node);
16491 }
16492
16493
16494 // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode())
16495 var binding = {
16496 node: node,
16497 iframe: iframe
16498 };
16499
16500 // Save info about binding in _bindings[], and make node itself record index(+1) into
16501 // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may
16502 // start with a number, which fails on FF/safari.
16503 dojo.attr(node, "_dijitMenu" + this.id, this._bindings.push(binding));
16504
16505 // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished
16506 // loading yet, in which case we need to wait for the onload event first, and then connect
16507 // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so
16508 // we need to monitor keyboard events in addition to the oncontextmenu event.
16509 var doConnects = dojo.hitch(this, function(cn){
16510 return [
16511 // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu,
16512 // rather than shift-F10?
16513 dojo.connect(cn, this.leftClickToOpen ? "onclick" : "oncontextmenu", this, function(evt){
16514 // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler
16515 dojo.stopEvent(evt);
16516 this._scheduleOpen(evt.target, iframe, {x: evt.pageX, y: evt.pageY});
16517 }),
16518 dojo.connect(cn, "onkeydown", this, function(evt){
16519 if(evt.shiftKey && evt.keyCode == dojo.keys.F10){
16520 dojo.stopEvent(evt);
16521 this._scheduleOpen(evt.target, iframe); // no coords - open near target node
16522 }
16523 })
16524 ];
16525 });
16526 binding.connects = cn ? doConnects(cn) : [];
16527
16528 if(iframe){
16529 // Setup handler to [re]bind to the iframe when the contents are initially loaded,
16530 // and every time the contents change.
16531 // Need to do this b/c we are actually binding to the iframe's <body> node.
16532 // Note: can't use dojo.connect(), see #9609.
16533
16534 binding.onloadHandler = dojo.hitch(this, function(){
16535 // want to remove old connections, but IE throws exceptions when trying to
16536 // access the <body> node because it's already gone, or at least in a state of limbo
16537
16538 var win = this._iframeContentWindow(iframe);
16539 cn = dojo.withGlobal(win, dojo.body);
16540 binding.connects = doConnects(cn);
16541 });
16542 if(iframe.addEventListener){
16543 iframe.addEventListener("load", binding.onloadHandler, false);
16544 }else{
16545 iframe.attachEvent("onload", binding.onloadHandler);
16546 }
16547 }
16548 },
16549
16550 unBindDomNode: function(/*String|DomNode*/ nodeName){
16551 // summary:
16552 // Detach menu from given node
16553
16554 var node;
16555 try{
16556 node = dojo.byId(nodeName);
16557 }catch(e){
16558 // On IE the dojo.byId() call will get an exception if the attach point was
16559 // the <body> node of an <iframe> that has since been reloaded (and thus the
16560 // <body> node is in a limbo state of destruction.
16561 return;
16562 }
16563
16564 // node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array
16565 var attrName = "_dijitMenu" + this.id;
16566 if(node && dojo.hasAttr(node, attrName)){
16567 var bid = dojo.attr(node, attrName)-1, b = this._bindings[bid];
16568 dojo.forEach(b.connects, dojo.disconnect);
16569
16570 // Remove listener for iframe onload events
16571 var iframe = b.iframe;
16572 if(iframe){
16573 if(iframe.removeEventListener){
16574 iframe.removeEventListener("load", b.onloadHandler, false);
16575 }else{
16576 iframe.detachEvent("onload", b.onloadHandler);
16577 }
16578 }
16579
16580 dojo.removeAttr(node, attrName);
16581 delete this._bindings[bid];
16582 }
16583 },
16584
16585 _scheduleOpen: function(/*DomNode?*/ target, /*DomNode?*/ iframe, /*Object?*/ coords){
16586 // summary:
16587 // Set timer to display myself. Using a timer rather than displaying immediately solves
16588 // two problems:
16589 //
16590 // 1. IE: without the delay, focus work in "open" causes the system
16591 // context menu to appear in spite of stopEvent.
16592 //
16593 // 2. Avoid double-shows on linux, where shift-F10 generates an oncontextmenu event
16594 // even after a dojo.stopEvent(e). (Shift-F10 on windows doesn't generate the
16595 // oncontextmenu event.)
16596
16597 if(!this._openTimer){
16598 this._openTimer = setTimeout(dojo.hitch(this, function(){
16599 delete this._openTimer;
16600 this._openMyself({
16601 target: target,
16602 iframe: iframe,
16603 coords: coords
16604 });
16605 }), 1);
16606 }
16607 },
16608
16609 _openMyself: function(args){
16610 // summary:
16611 // Internal function for opening myself when the user does a right-click or something similar.
16612 // args:
16613 // This is an Object containing:
16614 // * target:
16615 // The node that is being clicked
16616 // * iframe:
16617 // If an <iframe> is being clicked, iframe points to that iframe
16618 // * coords:
16619 // Put menu at specified x/y position in viewport, or if iframe is
16620 // specified, then relative to iframe.
16621 //
16622 // _openMyself() formerly took the event object, and since various code references
16623 // evt.target (after connecting to _openMyself()), using an Object for parameters
16624 // (so that old code still works).
16625
16626 var target = args.target,
16627 iframe = args.iframe,
16628 coords = args.coords;
16629
16630 // Get coordinates to open menu, either at specified (mouse) position or (if triggered via keyboard)
16631 // then near the node the menu is assigned to.
16632 if(coords){
16633 if(iframe){
16634 // Specified coordinates are on <body> node of an <iframe>, convert to match main document
16635 var od = target.ownerDocument,
16636 ifc = dojo.position(iframe, true),
16637 win = this._iframeContentWindow(iframe),
16638 scroll = dojo.withGlobal(win, "_docScroll", dojo);
16639
16640 var cs = dojo.getComputedStyle(iframe),
16641 tp = dojo._toPixelValue,
16642 left = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingLeft)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderLeftWidth) : 0),
16643 top = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingTop)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderTopWidth) : 0);
16644
16645 coords.x += ifc.x + left - scroll.x;
16646 coords.y += ifc.y + top - scroll.y;
16647 }
16648 }else{
16649 coords = dojo.position(target, true);
16650 coords.x += 10;
16651 coords.y += 10;
16652 }
16653
16654 var self=this;
16655 var savedFocus = dijit.getFocus(this);
16656 function closeAndRestoreFocus(){
16657 // user has clicked on a menu or popup
16658 if(self.refocus){
16659 dijit.focus(savedFocus);
16660 }
16661 dijit.popup.close(self);
16662 }
16663 dijit.popup.open({
16664 popup: this,
16665 x: coords.x,
16666 y: coords.y,
16667 onExecute: closeAndRestoreFocus,
16668 onCancel: closeAndRestoreFocus,
16669 orient: this.isLeftToRight() ? 'L' : 'R'
16670 });
16671 this.focus();
16672
16673 this._onBlur = function(){
16674 this.inherited('_onBlur', arguments);
16675 // Usually the parent closes the child widget but if this is a context
16676 // menu then there is no parent
16677 dijit.popup.close(this);
16678 // don't try to restore focus; user has clicked another part of the screen
16679 // and set focus there
16680 };
16681 },
16682
16683 uninitialize: function(){
16684 dojo.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this);
16685 this.inherited(arguments);
16686 }
16687 }
16688 );
16689
16690 // Back-compat (TODO: remove in 2.0)
16691
16692
16693
16694
16695
16696
16697 }
16698
16699 if(!dojo._hasResource["dijit.form.Select"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16700 dojo._hasResource["dijit.form.Select"] = true;
16701 dojo.provide("dijit.form.Select");
16702
16703
16704
16705
16706
16707
16708
16709
16710 dojo.declare("dijit.form._SelectMenu", dijit.Menu, {
16711 // summary:
16712 // An internally-used menu for dropdown that allows us a vertical scrollbar
16713 buildRendering: function(){
16714 // summary:
16715 // Stub in our own changes, so that our domNode is not a table
16716 // otherwise, we won't respond correctly to heights/overflows
16717 this.inherited(arguments);
16718 var o = (this.menuTableNode = this.domNode);
16719 var n = (this.domNode = dojo.create("div", {style: {overflowX: "hidden", overflowY: "scroll"}}));
16720 if(o.parentNode){
16721 o.parentNode.replaceChild(n, o);
16722 }
16723 dojo.removeClass(o, "dijitMenuTable");
16724 n.className = o.className + " dijitSelectMenu";
16725 o.className = "dijitReset dijitMenuTable";
16726 dijit.setWaiRole(o,"listbox");
16727 dijit.setWaiRole(n,"presentation");
16728 n.appendChild(o);
16729 },
16730 resize: function(/*Object*/ mb){
16731 // summary:
16732 // Overridden so that we are able to handle resizing our
16733 // internal widget. Note that this is not a "full" resize
16734 // implementation - it only works correctly if you pass it a
16735 // marginBox.
16736 //
16737 // mb: Object
16738 // The margin box to set this dropdown to.
16739 if(mb){
16740 dojo.marginBox(this.domNode, mb);
16741 if("w" in mb){
16742 // We've explicitly set the wrapper <div>'s width, so set <table> width to match.
16743 // 100% is safer than a pixel value because there may be a scroll bar with
16744 // browser/OS specific width.
16745 this.menuTableNode.style.width = "100%";
16746 }
16747 }
16748 }
16749 });
16750
16751 dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropDown], {
16752 // summary:
16753 // This is a "styleable" select box - it is basically a DropDownButton which
16754 // can take a <select> as its input.
16755
16756 baseClass: "dijitSelect",
16757
16758 templateString: dojo.cache("dijit.form", "templates/Select.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdojoAttachPoint=\"_buttonNode,tableNode,focusNode\" cellspacing='0' cellpadding='0'\n\twaiRole=\"combobox\" waiState=\"haspopup-true\"\n\t><tbody waiRole=\"presentation\"><tr waiRole=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents dijitButtonNode\" waiRole=\"presentation\"\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\" dojoAttachPoint=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} dojoAttachPoint=\"valueNode\" value=\"${value}\" waiState=\"hidden-true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdojoAttachPoint=\"titleNode\" waiRole=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" waiRole=\"presentation\">&#9660;</div\n\t\t></td\n\t></tr></tbody\n></table>\n"),
16759
16760 // attributeMap: Object
16761 // Add in our style to be applied to the focus node
16762 attributeMap: dojo.mixin(dojo.clone(dijit.form._FormSelectWidget.prototype.attributeMap),{style:"tableNode"}),
16763
16764 // required: Boolean
16765 // Can be true or false, default is false.
16766 required: false,
16767
16768 // state: String
16769 // Shows current state (ie, validation result) of input (Normal, Warning, or Error)
16770 state: "",
16771
16772 // tooltipPosition: String[]
16773 // See description of dijit.Tooltip.defaultPosition for details on this parameter.
16774 tooltipPosition: [],
16775
16776 // emptyLabel: string
16777 // What to display in an "empty" dropdown
16778 emptyLabel: "",
16779
16780 // _isLoaded: Boolean
16781 // Whether or not we have been loaded
16782 _isLoaded: false,
16783
16784 // _childrenLoaded: Boolean
16785 // Whether or not our children have been loaded
16786 _childrenLoaded: false,
16787
16788 _fillContent: function(){
16789 // summary:
16790 // Set the value to be the first, or the selected index
16791 this.inherited(arguments);
16792 if(this.options.length && !this.value && this.srcNodeRef){
16793 var si = this.srcNodeRef.selectedIndex;
16794 this.value = this.options[si != -1 ? si : 0].value;
16795 }
16796
16797 // Create the dropDown widget
16798 this.dropDown = new dijit.form._SelectMenu({id: this.id + "_menu"});
16799 dojo.addClass(this.dropDown.domNode, this.baseClass + "Menu");
16800 },
16801
16802 _getMenuItemForOption: function(/*dijit.form.__SelectOption*/ option){
16803 // summary:
16804 // For the given option, return the menu item that should be
16805 // used to display it. This can be overridden as needed
16806 if(!option.value){
16807 // We are a separator (no label set for it)
16808 return new dijit.MenuSeparator();
16809 }else{
16810 // Just a regular menu option
16811 var click = dojo.hitch(this, "_setValueAttr", option);
16812 var item = new dijit.MenuItem({
16813 option: option,
16814 label: option.label,
16815 onClick: click,
16816 disabled: option.disabled || false
16817 });
16818 dijit.setWaiRole(item.focusNode, "listitem");
16819 return item;
16820 }
16821 },
16822
16823 _addOptionItem: function(/*dijit.form.__SelectOption*/ option){
16824 // summary:
16825 // For the given option, add an option to our dropdown.
16826 // If the option doesn't have a value, then a separator is added
16827 // in that place.
16828 if(this.dropDown){
16829 this.dropDown.addChild(this._getMenuItemForOption(option));
16830 }
16831 },
16832
16833 _getChildren: function(){
16834 if(!this.dropDown){
16835 return [];
16836 }
16837 return this.dropDown.getChildren();
16838 },
16839
16840 _loadChildren: function(/*Boolean*/ loadMenuItems){
16841 // summary:
16842 // Resets the menu and the length attribute of the button - and
16843 // ensures that the label is appropriately set.
16844 // loadMenuItems: Boolean
16845 // actually loads the child menu items - we only do this when we are
16846 // populating for showing the dropdown.
16847
16848 if(loadMenuItems === true){
16849 // this.inherited destroys this.dropDown's child widgets (MenuItems).
16850 // Avoid this.dropDown (Menu widget) having a pointer to a destroyed widget (which will cause
16851 // issues later in _setSelected). (see #10296)
16852 if(this.dropDown){
16853 delete this.dropDown.focusedChild;
16854 }
16855 if(this.options.length){
16856 this.inherited(arguments);
16857 }else{
16858 // Drop down menu is blank but add one blank entry just so something appears on the screen
16859 // to let users know that they are no choices (mimicing native select behavior)
16860 dojo.forEach(this._getChildren(), function(child){ child.destroyRecursive(); });
16861 var item = new dijit.MenuItem({label: "&nbsp;"});
16862 this.dropDown.addChild(item);
16863 }
16864 }else{
16865 this._updateSelection();
16866 }
16867
16868 var len = this.options.length;
16869 this._isLoaded = false;
16870 this._childrenLoaded = true;
16871
16872 if(!this._loadingStore){
16873 // Don't call this if we are loading - since we will handle it later
16874 this._setValueAttr(this.value);
16875 }
16876 },
16877
16878 _setValueAttr: function(value){
16879 this.inherited(arguments);
16880 dojo.attr(this.valueNode, "value", this.get("value"));
16881 },
16882
16883 _setDisplay: function(/*String*/ newDisplay){
16884 // summary:
16885 // sets the display for the given value (or values)
16886 this.containerNode.innerHTML = '<span class="dijitReset dijitInline ' + this.baseClass + 'Label">' +
16887 (newDisplay || this.emptyLabel || "&nbsp;") +
16888 '</span>';
16889 dijit.setWaiState(this.focusNode, "valuetext", (newDisplay || this.emptyLabel || "&nbsp;") );
16890 },
16891
16892 validate: function(/*Boolean*/ isFocused){
16893 // summary:
16894 // Called by oninit, onblur, and onkeypress.
16895 // description:
16896 // Show missing or invalid messages if appropriate, and highlight textbox field.
16897 // Used when a select is initially set to no value and the user is required to
16898 // set the value.
16899
16900 var isValid = this.isValid(isFocused);
16901 this.state = isValid ? "" : "Error";
16902 this._setStateClass();
16903 dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true");
16904 var message = isValid ? "" : this._missingMsg;
16905 if(this._message !== message){
16906 this._message = message;
16907 dijit.hideTooltip(this.domNode);
16908 if(message){
16909 dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
16910 }
16911 }
16912 return isValid;
16913 },
16914
16915 isValid: function(/*Boolean*/ isFocused){
16916 // summary:
16917 // Whether or not this is a valid value. The only way a Select
16918 // can be invalid is when it's required but nothing is selected.
16919 return (!this.required || !(/^\s*$/.test(this.value)));
16920 },
16921
16922 reset: function(){
16923 // summary:
16924 // Overridden so that the state will be cleared.
16925 this.inherited(arguments);
16926 dijit.hideTooltip(this.domNode);
16927 this.state = "";
16928 this._setStateClass();
16929 delete this._message;
16930 },
16931
16932 postMixInProperties: function(){
16933 // summary:
16934 // set the missing message
16935 this.inherited(arguments);
16936 this._missingMsg = dojo.i18n.getLocalization("dijit.form", "validate",
16937 this.lang).missingMessage;
16938 },
16939
16940 postCreate: function(){
16941 this.inherited(arguments);
16942 if(this.tableNode.style.width){
16943 dojo.addClass(this.domNode, this.baseClass + "FixedWidth");
16944 }
16945 },
16946
16947 isLoaded: function(){
16948 return this._isLoaded;
16949 },
16950
16951 loadDropDown: function(/*Function*/ loadCallback){
16952 // summary:
16953 // populates the menu
16954 this._loadChildren(true);
16955 this._isLoaded = true;
16956 loadCallback();
16957 },
16958
16959 closeDropDown: function(){
16960 // overriding _HasDropDown.closeDropDown()
16961 this.inherited(arguments);
16962
16963 if(this.dropDown && this.dropDown.menuTableNode){
16964 // Erase possible width: 100% setting from _SelectMenu.resize().
16965 // Leaving it would interfere with the next openDropDown() call, which
16966 // queries the natural size of the drop down.
16967 this.dropDown.menuTableNode.style.width = "";
16968 }
16969 },
16970
16971 uninitialize: function(preserveDom){
16972 if(this.dropDown && !this.dropDown._destroyed){
16973 this.dropDown.destroyRecursive(preserveDom);
16974 delete this.dropDown;
16975 }
16976 this.inherited(arguments);
16977 }
16978 });
16979
16980 }
16981
16982 if(!dojo._hasResource["dijit.form.SimpleTextarea"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16983 dojo._hasResource["dijit.form.SimpleTextarea"] = true;
16984 dojo.provide("dijit.form.SimpleTextarea");
16985
16986
16987
16988 dojo.declare("dijit.form.SimpleTextarea",
16989 dijit.form.TextBox,
16990 {
16991 // summary:
16992 // A simple textarea that degrades, and responds to
16993 // minimal LayoutContainer usage, and works with dijit.form.Form.
16994 // Doesn't automatically size according to input, like Textarea.
16995 //
16996 // example:
16997 // | <textarea dojoType="dijit.form.SimpleTextarea" name="foo" value="bar" rows=30 cols=40></textarea>
16998 //
16999 // example:
17000 // | new dijit.form.SimpleTextarea({ rows:20, cols:30 }, "foo");
17001
17002 baseClass: "dijitTextBox dijitTextArea",
17003
17004 attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, {
17005 rows:"textbox", cols: "textbox"
17006 }),
17007
17008 // rows: Number
17009 // The number of rows of text.
17010 rows: "3",
17011
17012 // rows: Number
17013 // The number of characters per line.
17014 cols: "20",
17015
17016 templateString: "<textarea ${!nameAttrSetting} dojoAttachPoint='focusNode,containerNode,textbox' autocomplete='off'></textarea>",
17017
17018 postMixInProperties: function(){
17019 // Copy value from srcNodeRef, unless user specified a value explicitly (or there is no srcNodeRef)
17020 if(!this.value && this.srcNodeRef){
17021 this.value = this.srcNodeRef.value;
17022 }
17023 this.inherited(arguments);
17024 },
17025
17026 filter: function(/*String*/ value){
17027 // Override TextBox.filter to deal with newlines... specifically (IIRC) this is for IE which writes newlines
17028 // as \r\n instead of just \n
17029 if(value){
17030 value = value.replace(/\r/g,"");
17031 }
17032 return this.inherited(arguments);
17033 },
17034
17035 postCreate: function(){
17036 this.inherited(arguments);
17037 if(dojo.isIE && this.cols){ // attribute selectors is not supported in IE6
17038 dojo.addClass(this.textbox, "dijitTextAreaCols");
17039 }
17040 },
17041
17042 _previousValue: "",
17043 _onInput: function(/*Event?*/ e){
17044 // Override TextBox._onInput() to enforce maxLength restriction
17045 if(this.maxLength){
17046 var maxLength = parseInt(this.maxLength);
17047 var value = this.textbox.value.replace(/\r/g,'');
17048 var overflow = value.length - maxLength;
17049 if(overflow > 0){
17050 if(e){ dojo.stopEvent(e); }
17051 var textarea = this.textbox;
17052 if(textarea.selectionStart){
17053 var pos = textarea.selectionStart;
17054 var cr = 0;
17055 if(dojo.isOpera){
17056 cr = (this.textbox.value.substring(0,pos).match(/\r/g) || []).length;
17057 }
17058 this.textbox.value = value.substring(0,pos-overflow-cr)+value.substring(pos-cr);
17059 textarea.setSelectionRange(pos-overflow, pos-overflow);
17060 }else if(dojo.doc.selection){ //IE
17061 textarea.focus();
17062 var range = dojo.doc.selection.createRange();
17063 // delete overflow characters
17064 range.moveStart("character", -overflow);
17065 range.text = '';
17066 // show cursor
17067 range.select();
17068 }
17069 }
17070 this._previousValue = this.textbox.value;
17071 }
17072 this.inherited(arguments);
17073 }
17074 });
17075
17076 }
17077
17078 if(!dojo._hasResource["dijit.InlineEditBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
17079 dojo._hasResource["dijit.InlineEditBox"] = true;
17080 dojo.provide("dijit.InlineEditBox");
17081
17082
17083
17084
17085
17086
17087
17088
17089
17090
17091 dojo.declare("dijit.InlineEditBox",
17092 dijit._Widget,
17093 {
17094 // summary:
17095 // An element with in-line edit capabilites
17096 //
17097 // description:
17098 // Behavior for an existing node (`<p>`, `<div>`, `<span>`, etc.) so that
17099 // when you click it, an editor shows up in place of the original
17100 // text. Optionally, Save and Cancel button are displayed below the edit widget.
17101 // When Save is clicked, the text is pulled from the edit
17102 // widget and redisplayed and the edit widget is again hidden.
17103 // By default a plain Textarea widget is used as the editor (or for
17104 // inline values a TextBox), but you can specify an editor such as
17105 // dijit.Editor (for editing HTML) or a Slider (for adjusting a number).
17106 // An edit widget must support the following API to be used:
17107 // - displayedValue or value as initialization parameter,
17108 // and available through set('displayedValue') / set('value')
17109 // - void focus()
17110 // - DOM-node focusNode = node containing editable text
17111
17112 // editing: [readonly] Boolean
17113 // Is the node currently in edit mode?
17114 editing: false,
17115
17116 // autoSave: Boolean
17117 // Changing the value automatically saves it; don't have to push save button
17118 // (and save button isn't even displayed)
17119 autoSave: true,
17120
17121 // buttonSave: String
17122 // Save button label
17123 buttonSave: "",
17124
17125 // buttonCancel: String
17126 // Cancel button label
17127 buttonCancel: "",
17128
17129 // renderAsHtml: Boolean
17130 // Set this to true if the specified Editor's value should be interpreted as HTML
17131 // rather than plain text (ex: `dijit.Editor`)
17132 renderAsHtml: false,
17133
17134 // editor: String
17135 // Class name for Editor widget
17136 editor: "dijit.form.TextBox",
17137
17138 // editorWrapper: String
17139 // Class name for widget that wraps the editor widget, displaying save/cancel
17140 // buttons.
17141 editorWrapper: "dijit._InlineEditor",
17142
17143 // editorParams: Object
17144 // Set of parameters for editor, like {required: true}
17145 editorParams: {},
17146
17147 onChange: function(value){
17148 // summary:
17149 // Set this handler to be notified of changes to value.
17150 // tags:
17151 // callback
17152 },
17153
17154 onCancel: function(){
17155 // summary:
17156 // Set this handler to be notified when editing is cancelled.
17157 // tags:
17158 // callback
17159 },
17160
17161 // width: String
17162 // Width of editor. By default it's width=100% (ie, block mode).
17163 width: "100%",
17164
17165 // value: String
17166 // The display value of the widget in read-only mode
17167 value: "",
17168
17169 // noValueIndicator: [const] String
17170 // The text that gets displayed when there is no value (so that the user has a place to click to edit)
17171 noValueIndicator: dojo.isIE <= 6 ? // font-family needed on IE6 but it messes up IE8
17172 "<span style='font-family: wingdings; text-decoration: underline;'>&nbsp;&nbsp;&nbsp;&nbsp;&#x270d;&nbsp;&nbsp;&nbsp;&nbsp;</span>" :
17173 "<span style='text-decoration: underline;'>&nbsp;&nbsp;&nbsp;&nbsp;&#x270d;&nbsp;&nbsp;&nbsp;&nbsp;</span>",
17174
17175 constructor: function(){
17176 // summary:
17177 // Sets up private arrays etc.
17178 // tags:
17179 // private
17180 this.editorParams = {};
17181 },
17182
17183 postMixInProperties: function(){
17184 this.inherited(arguments);
17185
17186 // save pointer to original source node, since Widget nulls-out srcNodeRef
17187 this.displayNode = this.srcNodeRef;
17188
17189 // connect handlers to the display node
17190 var events = {
17191 ondijitclick: "_onClick",
17192 onmouseover: "_onMouseOver",
17193 onmouseout: "_onMouseOut",
17194 onfocus: "_onMouseOver",
17195 onblur: "_onMouseOut"
17196 };
17197 for(var name in events){
17198 this.connect(this.displayNode, name, events[name]);
17199 }
17200 dijit.setWaiRole(this.displayNode, "button");
17201 if(!this.displayNode.getAttribute("tabIndex")){
17202 this.displayNode.setAttribute("tabIndex", 0);
17203 }
17204
17205 if(!this.value && !("value" in this.params)){ // "" is a good value if specified directly so check params){
17206 this.value = dojo.trim(this.renderAsHtml ? this.displayNode.innerHTML :
17207 (this.displayNode.innerText||this.displayNode.textContent||""));
17208 }
17209 if(!this.value){
17210 this.displayNode.innerHTML = this.noValueIndicator;
17211 }
17212
17213 dojo.addClass(this.displayNode, 'dijitInlineEditBoxDisplayMode');
17214 },
17215
17216 setDisabled: function(/*Boolean*/ disabled){
17217 // summary:
17218 // Deprecated. Use set('disabled', ...) instead.
17219 // tags:
17220 // deprecated
17221 dojo.deprecated("dijit.InlineEditBox.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0");
17222 this.set('disabled', disabled);
17223 },
17224
17225 _setDisabledAttr: function(/*Boolean*/ disabled){
17226 // summary:
17227 // Hook to make set("disabled", ...) work.
17228 // Set disabled state of widget.
17229 this.disabled = disabled;
17230 dijit.setWaiState(this.domNode, "disabled", disabled);
17231 if(disabled){
17232 this.displayNode.removeAttribute("tabIndex");
17233 }else{
17234 this.displayNode.setAttribute("tabIndex", 0);
17235 }
17236 dojo.toggleClass(this.displayNode, "dijitInlineEditBoxDisplayModeDisabled", disabled);
17237 },
17238
17239 _onMouseOver: function(){
17240 // summary:
17241 // Handler for onmouseover and onfocus event.
17242 // tags:
17243 // private
17244 if(!this.disabled){
17245 dojo.addClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover");
17246 }
17247 },
17248
17249 _onMouseOut: function(){
17250 // summary:
17251 // Handler for onmouseout and onblur event.
17252 // tags:
17253 // private
17254 dojo.removeClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover");
17255 },
17256
17257 _onClick: function(/*Event*/ e){
17258 // summary:
17259 // Handler for onclick event.
17260 // tags:
17261 // private
17262 if(this.disabled){ return; }
17263 if(e){ dojo.stopEvent(e); }
17264 this._onMouseOut();
17265
17266 // Since FF gets upset if you move a node while in an event handler for that node...
17267 setTimeout(dojo.hitch(this, "edit"), 0);
17268 },
17269
17270 edit: function(){
17271 // summary:
17272 // Display the editor widget in place of the original (read only) markup.
17273 // tags:
17274 // private
17275
17276 if(this.disabled || this.editing){ return; }
17277 this.editing = true;
17278
17279 // save some display node values that can be restored later
17280 this._savedPosition = dojo.style(this.displayNode, "position") || "static";
17281 this._savedOpacity = dojo.style(this.displayNode, "opacity") || "1";
17282 this._savedTabIndex = dojo.attr(this.displayNode, "tabIndex") || "0";
17283
17284 if(this.wrapperWidget){
17285 var ew = this.wrapperWidget.editWidget;
17286 ew.set("displayedValue" in ew ? "displayedValue" : "value", this.value);
17287 }else{
17288 // Placeholder for edit widget
17289 // Put place holder (and eventually editWidget) before the display node so that it's positioned correctly
17290 // when Calendar dropdown appears, which happens automatically on focus.
17291 var placeholder = dojo.create("span", null, this.domNode, "before");
17292
17293 // Create the editor wrapper (the thing that holds the editor widget and the save/cancel buttons)
17294 var ewc = dojo.getObject(this.editorWrapper);
17295 this.wrapperWidget = new ewc({
17296 value: this.value,
17297 buttonSave: this.buttonSave,
17298 buttonCancel: this.buttonCancel,
17299 dir: this.dir,
17300 lang: this.lang,
17301 tabIndex: this._savedTabIndex,
17302 editor: this.editor,
17303 inlineEditBox: this,
17304 sourceStyle: dojo.getComputedStyle(this.displayNode),
17305 save: dojo.hitch(this, "save"),
17306 cancel: dojo.hitch(this, "cancel")
17307 }, placeholder);
17308 }
17309 var ww = this.wrapperWidget;
17310
17311 if(dojo.isIE){
17312 dijit.focus(dijit.getFocus()); // IE (at least 8) needs help with tab order changes
17313 }
17314 // to avoid screen jitter, we first create the editor with position:absolute, visibility:hidden,
17315 // and then when it's finished rendering, we switch from display mode to editor
17316 // position:absolute releases screen space allocated to the display node
17317 // opacity:0 is the same as visibility:hidden but is still focusable
17318 // visiblity:hidden removes focus outline
17319
17320 dojo.style(this.displayNode, { position: "absolute", opacity: "0", display: "none" }); // makes display node invisible, display style used for focus-ability
17321 dojo.style(ww.domNode, { position: this._savedPosition, visibility: "visible", opacity: "1" });
17322 dojo.attr(this.displayNode, "tabIndex", "-1"); // needed by WebKit for TAB from editor to skip displayNode
17323
17324 // Replace the display widget with edit widget, leaving them both displayed for a brief time so that
17325 // focus can be shifted without incident. (browser may needs some time to render the editor.)
17326 setTimeout(dojo.hitch(this, function(){
17327 ww.focus(); // both nodes are showing, so we can switch focus safely
17328 ww._resetValue = ww.getValue();
17329 }), 0);
17330 },
17331
17332 _onBlur: function(){
17333 // summary:
17334 // Called when focus moves outside the InlineEditBox.
17335 // Performs garbage collection.
17336 // tags:
17337 // private
17338
17339 this.inherited(arguments);
17340 if(!this.editing){
17341 /* causes IE focus problems, see TooltipDialog_a11y.html...
17342 setTimeout(dojo.hitch(this, function(){
17343 if(this.wrapperWidget){
17344 this.wrapperWidget.destroy();
17345 delete this.wrapperWidget;
17346 }
17347 }), 0);
17348 */
17349 }
17350 },
17351
17352 destroy: function(){
17353 if(this.wrapperWidget){
17354 this.wrapperWidget.destroy();
17355 delete this.wrapperWidget;
17356 }
17357 this.inherited(arguments);
17358 },
17359
17360 _showText: function(/*Boolean*/ focus){
17361 // summary:
17362 // Revert to display mode, and optionally focus on display node
17363 // tags:
17364 // private
17365
17366 var ww = this.wrapperWidget;
17367 dojo.style(ww.domNode, { position: "absolute", visibility: "hidden", opacity: "0" }); // hide the editor from mouse/keyboard events
17368 dojo.style(this.displayNode, { position: this._savedPosition, opacity: this._savedOpacity, display: "" }); // make the original text visible
17369 dojo.attr(this.displayNode, "tabIndex", this._savedTabIndex);
17370 if(focus){
17371 dijit.focus(this.displayNode);
17372 }
17373 },
17374
17375 save: function(/*Boolean*/ focus){
17376 // summary:
17377 // Save the contents of the editor and revert to display mode.
17378 // focus: Boolean
17379 // Focus on the display mode text
17380 // tags:
17381 // private
17382
17383 if(this.disabled || !this.editing){ return; }
17384 this.editing = false;
17385
17386 var ww = this.wrapperWidget;
17387 var value = ww.getValue();
17388 this.set('value', value); // display changed, formatted value
17389
17390 // tell the world that we have changed
17391 setTimeout(dojo.hitch(this, "onChange", value), 0); // setTimeout prevents browser freeze for long-running event handlers
17392
17393 this._showText(focus); // set focus as needed
17394 },
17395
17396 setValue: function(/*String*/ val){
17397 // summary:
17398 // Deprecated. Use set('value', ...) instead.
17399 // tags:
17400 // deprecated
17401 dojo.deprecated("dijit.InlineEditBox.setValue() is deprecated. Use set('value', ...) instead.", "", "2.0");
17402 return this.set("value", val);
17403 },
17404
17405 _setValueAttr: function(/*String*/ val){
17406 // summary:
17407 // Hook to make set("value", ...) work.
17408 // Inserts specified HTML value into this node, or an "input needed" character if node is blank.
17409
17410 this.value = val = dojo.trim(val);
17411 if(!this.renderAsHtml){
17412 val = val.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;").replace(/\n/g, "<br>");
17413 }
17414 this.displayNode.innerHTML = val || this.noValueIndicator;
17415 },
17416
17417 getValue: function(){
17418 // summary:
17419 // Deprecated. Use get('value') instead.
17420 // tags:
17421 // deprecated
17422 dojo.deprecated("dijit.InlineEditBox.getValue() is deprecated. Use get('value') instead.", "", "2.0");
17423 return this.get("value");
17424 },
17425
17426 cancel: function(/*Boolean*/ focus){
17427 // summary:
17428 // Revert to display mode, discarding any changes made in the editor
17429 // tags:
17430 // private
17431
17432 if(this.disabled || !this.editing){ return; }
17433 this.editing = false;
17434
17435 // tell the world that we have no changes
17436 setTimeout(dojo.hitch(this, "onCancel"), 0); // setTimeout prevents browser freeze for long-running event handlers
17437
17438 this._showText(focus);
17439 }
17440 });
17441
17442 dojo.declare(
17443 "dijit._InlineEditor",
17444 [dijit._Widget, dijit._Templated],
17445 {
17446 // summary:
17447 // Internal widget used by InlineEditBox, displayed when in editing mode
17448 // to display the editor and maybe save/cancel buttons. Calling code should
17449 // connect to save/cancel methods to detect when editing is finished
17450 //
17451 // Has mainly the same parameters as InlineEditBox, plus these values:
17452 //
17453 // style: Object
17454 // Set of CSS attributes of display node, to replicate in editor
17455 //
17456 // value: String
17457 // Value as an HTML string or plain text string, depending on renderAsHTML flag
17458
17459 templateString: dojo.cache("dijit", "templates/InlineEditBox.html", "<span dojoAttachPoint=\"editNode\" waiRole=\"presentation\" style=\"position: absolute; visibility:hidden\" class=\"dijitReset dijitInline\"\n\tdojoAttachEvent=\"onkeypress: _onKeyPress\"\n\t><span dojoAttachPoint=\"editorPlaceholder\"></span\n\t><span dojoAttachPoint=\"buttonContainer\"\n\t\t><button class='saveButton' dojoAttachPoint=\"saveButton\" dojoType=\"dijit.form.Button\" dojoAttachEvent=\"onClick:save\" label=\"${buttonSave}\"></button\n\t\t><button class='cancelButton' dojoAttachPoint=\"cancelButton\" dojoType=\"dijit.form.Button\" dojoAttachEvent=\"onClick:cancel\" label=\"${buttonCancel}\"></button\n\t></span\n></span>\n"),
17460 widgetsInTemplate: true,
17461
17462 postMixInProperties: function(){
17463 this.inherited(arguments);
17464 this.messages = dojo.i18n.getLocalization("dijit", "common", this.lang);
17465 dojo.forEach(["buttonSave", "buttonCancel"], function(prop){
17466 if(!this[prop]){ this[prop] = this.messages[prop]; }
17467 }, this);
17468 },
17469
17470 postCreate: function(){
17471 // Create edit widget in place in the template
17472 var cls = dojo.getObject(this.editor);
17473
17474 // Copy the style from the source
17475 // Don't copy ALL properties though, just the necessary/applicable ones.
17476 // wrapperStyle/destStyle code is to workaround IE bug where getComputedStyle().fontSize
17477 // is a relative value like 200%, rather than an absolute value like 24px, and
17478 // the 200% can refer *either* to a setting on the node or it's ancestor (see #11175)
17479 var srcStyle = this.sourceStyle,
17480 editStyle = "line-height:" + srcStyle.lineHeight + ";",
17481 destStyle = dojo.getComputedStyle(this.domNode);
17482 dojo.forEach(["Weight","Family","Size","Style"], function(prop){
17483 var textStyle = srcStyle["font"+prop],
17484 wrapperStyle = destStyle["font"+prop];
17485 if(wrapperStyle != textStyle){
17486 editStyle += "font-"+prop+":"+srcStyle["font"+prop]+";";
17487 }
17488 }, this);
17489 dojo.forEach(["marginTop","marginBottom","marginLeft", "marginRight"], function(prop){
17490 this.domNode.style[prop] = srcStyle[prop];
17491 }, this);
17492 var width = this.inlineEditBox.width;
17493 if(width == "100%"){
17494 // block mode
17495 editStyle += "width:100%;";
17496 this.domNode.style.display = "block";
17497 }else{
17498 // inline-block mode
17499 editStyle += "width:" + (width + (Number(width) == width ? "px" : "")) + ";";
17500 }
17501 var editorParams = dojo.delegate(this.inlineEditBox.editorParams, {
17502 style: editStyle,
17503 dir: this.dir,
17504 lang: this.lang
17505 });
17506 editorParams[ "displayedValue" in cls.prototype ? "displayedValue" : "value"] = this.value;
17507 var ew = (this.editWidget = new cls(editorParams, this.editorPlaceholder));
17508
17509 if(this.inlineEditBox.autoSave){
17510 // Remove the save/cancel buttons since saving is done by simply tabbing away or
17511 // selecting a value from the drop down list
17512 dojo.destroy(this.buttonContainer);
17513
17514 // Selecting a value from a drop down list causes an onChange event and then we save
17515 this.connect(ew, "onChange", "_onChange");
17516
17517 // ESC and TAB should cancel and save. Note that edit widgets do a stopEvent() on ESC key (to
17518 // prevent Dialog from closing when the user just wants to revert the value in the edit widget),
17519 // so this is the only way we can see the key press event.
17520 this.connect(ew, "onKeyPress", "_onKeyPress");
17521 }else{
17522 // If possible, enable/disable save button based on whether the user has changed the value
17523 if("intermediateChanges" in cls.prototype){
17524 ew.set("intermediateChanges", true);
17525 this.connect(ew, "onChange", "_onIntermediateChange");
17526 this.saveButton.set("disabled", true);
17527 }
17528 }
17529 },
17530
17531 _onIntermediateChange: function(val){
17532 // summary:
17533 // Called for editor widgets that support the intermediateChanges=true flag as a way
17534 // to detect when to enable/disabled the save button
17535 this.saveButton.set("disabled", (this.getValue() == this._resetValue) || !this.enableSave());
17536 },
17537
17538 destroy: function(){
17539 this.editWidget.destroy(true); // let the parent wrapper widget clean up the DOM
17540 this.inherited(arguments);
17541 },
17542
17543 getValue: function(){
17544 // summary:
17545 // Return the [display] value of the edit widget
17546 var ew = this.editWidget;
17547 return String(ew.get("displayedValue" in ew ? "displayedValue" : "value"));
17548 },
17549
17550 _onKeyPress: function(e){
17551 // summary:
17552 // Handler for keypress in the edit box in autoSave mode.
17553 // description:
17554 // For autoSave widgets, if Esc/Enter, call cancel/save.
17555 // tags:
17556 // private
17557
17558 if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){
17559 if(e.altKey || e.ctrlKey){ return; }
17560 // If Enter/Esc pressed, treat as save/cancel.
17561 if(e.charOrCode == dojo.keys.ESCAPE){
17562 dojo.stopEvent(e);
17563 this.cancel(true); // sets editing=false which short-circuits _onBlur processing
17564 }else if(e.charOrCode == dojo.keys.ENTER && e.target.tagName == "INPUT"){
17565 dojo.stopEvent(e);
17566 this._onChange(); // fire _onBlur and then save
17567 }
17568
17569 // _onBlur will handle TAB automatically by allowing
17570 // the TAB to change focus before we mess with the DOM: #6227
17571 // Expounding by request:
17572 // The current focus is on the edit widget input field.
17573 // save() will hide and destroy this widget.
17574 // We want the focus to jump from the currently hidden
17575 // displayNode, but since it's hidden, it's impossible to
17576 // unhide it, focus it, and then have the browser focus
17577 // away from it to the next focusable element since each
17578 // of these events is asynchronous and the focus-to-next-element
17579 // is already queued.
17580 // So we allow the browser time to unqueue the move-focus event
17581 // before we do all the hide/show stuff.
17582 }
17583 },
17584
17585 _onBlur: function(){
17586 // summary:
17587 // Called when focus moves outside the editor
17588 // tags:
17589 // private
17590
17591 this.inherited(arguments);
17592 if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){
17593 if(this.getValue() == this._resetValue){
17594 this.cancel(false);
17595 }else if(this.enableSave()){
17596 this.save(false);
17597 }
17598 }
17599 },
17600
17601 _onChange: function(){
17602 // summary:
17603 // Called when the underlying widget fires an onChange event,
17604 // such as when the user selects a value from the drop down list of a ComboBox,
17605 // which means that the user has finished entering the value and we should save.
17606 // tags:
17607 // private
17608
17609 if(this.inlineEditBox.autoSave && this.inlineEditBox.editing && this.enableSave()){
17610 dojo.style(this.inlineEditBox.displayNode, { display: "" });
17611 dijit.focus(this.inlineEditBox.displayNode); // fires _onBlur which will save the formatted value
17612 }
17613 },
17614
17615 enableSave: function(){
17616 // summary:
17617 // User overridable function returning a Boolean to indicate
17618 // if the Save button should be enabled or not - usually due to invalid conditions
17619 // tags:
17620 // extension
17621 return (
17622 this.editWidget.isValid
17623 ? this.editWidget.isValid()
17624 : true
17625 );
17626 },
17627
17628 focus: function(){
17629 // summary:
17630 // Focus the edit widget.
17631 // tags:
17632 // protected
17633
17634 this.editWidget.focus();
17635 setTimeout(dojo.hitch(this, function(){
17636 if(this.editWidget.focusNode && this.editWidget.focusNode.tagName == "INPUT"){
17637 dijit.selectInputText(this.editWidget.focusNode);
17638 }
17639 }), 0);
17640 }
17641 });
17642
17643 }
17644
17645 if(!dojo._hasResource["dojo.cookie"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
17646 dojo._hasResource["dojo.cookie"] = true;
17647 dojo.provide("dojo.cookie");
17648
17649
17650
17651 /*=====
17652 dojo.__cookieProps = function(){
17653 // expires: Date|String|Number?
17654 // If a number, the number of days from today at which the cookie
17655 // will expire. If a date, the date past which the cookie will expire.
17656 // If expires is in the past, the cookie will be deleted.
17657 // If expires is omitted or is 0, the cookie will expire when the browser closes. << FIXME: 0 seems to disappear right away? FF3.
17658 // path: String?
17659 // The path to use for the cookie.
17660 // domain: String?
17661 // The domain to use for the cookie.
17662 // secure: Boolean?
17663 // Whether to only send the cookie on secure connections
17664 this.expires = expires;
17665 this.path = path;
17666 this.domain = domain;
17667 this.secure = secure;
17668 }
17669 =====*/
17670
17671
17672 dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/props){
17673 // summary:
17674 // Get or set a cookie.
17675 // description:
17676 // If one argument is passed, returns the value of the cookie
17677 // For two or more arguments, acts as a setter.
17678 // name:
17679 // Name of the cookie
17680 // value:
17681 // Value for the cookie
17682 // props:
17683 // Properties for the cookie
17684 // example:
17685 // set a cookie with the JSON-serialized contents of an object which
17686 // will expire 5 days from now:
17687 // | dojo.cookie("configObj", dojo.toJson(config), { expires: 5 });
17688 //
17689 // example:
17690 // de-serialize a cookie back into a JavaScript object:
17691 // | var config = dojo.fromJson(dojo.cookie("configObj"));
17692 //
17693 // example:
17694 // delete a cookie:
17695 // | dojo.cookie("configObj", null, {expires: -1});
17696 var c = document.cookie;
17697 if(arguments.length == 1){
17698 var matches = c.match(new RegExp("(?:^|; )" + dojo.regexp.escapeString(name) + "=([^;]*)"));
17699 return matches ? decodeURIComponent(matches[1]) : undefined; // String or undefined
17700 }else{
17701 props = props || {};
17702 // FIXME: expires=0 seems to disappear right away, not on close? (FF3) Change docs?
17703 var exp = props.expires;
17704 if(typeof exp == "number"){
17705 var d = new Date();
17706 d.setTime(d.getTime() + exp*24*60*60*1000);
17707 exp = props.expires = d;
17708 }
17709 if(exp && exp.toUTCString){ props.expires = exp.toUTCString(); }
17710
17711 value = encodeURIComponent(value);
17712 var updatedCookie = name + "=" + value, propName;
17713 for(propName in props){
17714 updatedCookie += "; " + propName;
17715 var propValue = props[propName];
17716 if(propValue !== true){ updatedCookie += "=" + propValue; }
17717 }
17718 document.cookie = updatedCookie;
17719 }
17720 };
17721
17722 dojo.cookie.isSupported = function(){
17723 // summary:
17724 // Use to determine if the current browser supports cookies or not.
17725 //
17726 // Returns true if user allows cookies.
17727 // Returns false if user doesn't allow cookies.
17728
17729 if(!("cookieEnabled" in navigator)){
17730 this("__djCookieTest__", "CookiesAllowed");
17731 navigator.cookieEnabled = this("__djCookieTest__") == "CookiesAllowed";
17732 if(navigator.cookieEnabled){
17733 this("__djCookieTest__", "", {expires: -1});
17734 }
17735 }
17736 return navigator.cookieEnabled;
17737 };
17738
17739 }
17740
17741 if(!dojo._hasResource["dijit.layout.StackController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
17742 dojo._hasResource["dijit.layout.StackController"] = true;
17743 dojo.provide("dijit.layout.StackController");
17744
17745
17746
17747
17748
17749
17750
17751 dojo.declare(
17752 "dijit.layout.StackController",
17753 [dijit._Widget, dijit._Templated, dijit._Container],
17754 {
17755 // summary:
17756 // Set of buttons to select a page in a page list.
17757 // description:
17758 // Monitors the specified StackContainer, and whenever a page is
17759 // added, deleted, or selected, updates itself accordingly.
17760
17761 templateString: "<span wairole='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>",
17762
17763 // containerId: [const] String
17764 // The id of the page container that I point to
17765 containerId: "",
17766
17767 // buttonWidget: [const] String
17768 // The name of the button widget to create to correspond to each page
17769 buttonWidget: "dijit.layout._StackButton",
17770
17771 postCreate: function(){
17772 dijit.setWaiRole(this.domNode, "tablist");
17773
17774 this.pane2button = {}; // mapping from pane id to buttons
17775 this.pane2handles = {}; // mapping from pane id to this.connect() handles
17776
17777 // Listen to notifications from StackContainer
17778 this.subscribe(this.containerId+"-startup", "onStartup");
17779 this.subscribe(this.containerId+"-addChild", "onAddChild");
17780 this.subscribe(this.containerId+"-removeChild", "onRemoveChild");
17781 this.subscribe(this.containerId+"-selectChild", "onSelectChild");
17782 this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress");
17783 },
17784
17785 onStartup: function(/*Object*/ info){
17786 // summary:
17787 // Called after StackContainer has finished initializing
17788 // tags:
17789 // private
17790 dojo.forEach(info.children, this.onAddChild, this);
17791 if(info.selected){
17792 // Show button corresponding to selected pane (unless selected
17793 // is null because there are no panes)
17794 this.onSelectChild(info.selected);
17795 }
17796 },
17797
17798 destroy: function(){
17799 for(var pane in this.pane2button){
17800 this.onRemoveChild(dijit.byId(pane));
17801 }
17802 this.inherited(arguments);
17803 },
17804
17805 onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){
17806 // summary:
17807 // Called whenever a page is added to the container.
17808 // Create button corresponding to the page.
17809 // tags:
17810 // private
17811
17812 // create an instance of the button widget
17813 var cls = dojo.getObject(this.buttonWidget);
17814 var button = new cls({
17815 id: this.id + "_" + page.id,
17816 label: page.title,
17817 dir: page.dir,
17818 lang: page.lang,
17819 showLabel: page.showTitle,
17820 iconClass: page.iconClass,
17821 closeButton: page.closable,
17822 title: page.tooltip
17823 });
17824 dijit.setWaiState(button.focusNode,"selected", "false");
17825 this.pane2handles[page.id] = [
17826 this.connect(page, 'set', function(name, value){
17827 var buttonAttr = {
17828 title: 'label',
17829 showTitle: 'showLabel',
17830 iconClass: 'iconClass',
17831 closable: 'closeButton',
17832 tooltip: 'title'
17833 }[name];
17834 if(buttonAttr){
17835 button.set(buttonAttr, value);
17836 }
17837 }),
17838 this.connect(button, 'onClick', dojo.hitch(this,"onButtonClick", page)),
17839 this.connect(button, 'onClickCloseButton', dojo.hitch(this,"onCloseButtonClick", page))
17840 ];
17841 this.addChild(button, insertIndex);
17842 this.pane2button[page.id] = button;
17843 page.controlButton = button; // this value might be overwritten if two tabs point to same container
17844 if(!this._currentChild){ // put the first child into the tab order
17845 button.focusNode.setAttribute("tabIndex", "0");
17846 dijit.setWaiState(button.focusNode, "selected", "true");
17847 this._currentChild = page;
17848 }
17849 // make sure all tabs have the same length
17850 if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){
17851 this._rectifyRtlTabList();
17852 }
17853 },
17854
17855 onRemoveChild: function(/*dijit._Widget*/ page){
17856 // summary:
17857 // Called whenever a page is removed from the container.
17858 // Remove the button corresponding to the page.
17859 // tags:
17860 // private
17861
17862 if(this._currentChild === page){ this._currentChild = null; }
17863 dojo.forEach(this.pane2handles[page.id], this.disconnect, this);
17864 delete this.pane2handles[page.id];
17865 var button = this.pane2button[page.id];
17866 if(button){
17867 this.removeChild(button);
17868 delete this.pane2button[page.id];
17869 button.destroy();
17870 }
17871 delete page.controlButton;
17872 },
17873
17874 onSelectChild: function(/*dijit._Widget*/ page){
17875 // summary:
17876 // Called when a page has been selected in the StackContainer, either by me or by another StackController
17877 // tags:
17878 // private
17879
17880 if(!page){ return; }
17881
17882 if(this._currentChild){
17883 var oldButton=this.pane2button[this._currentChild.id];
17884 oldButton.set('checked', false);
17885 dijit.setWaiState(oldButton.focusNode, "selected", "false");
17886 oldButton.focusNode.setAttribute("tabIndex", "-1");
17887 }
17888
17889 var newButton=this.pane2button[page.id];
17890 newButton.set('checked', true);
17891 dijit.setWaiState(newButton.focusNode, "selected", "true");
17892 this._currentChild = page;
17893 newButton.focusNode.setAttribute("tabIndex", "0");
17894 var container = dijit.byId(this.containerId);
17895 dijit.setWaiState(container.containerNode, "labelledby", newButton.id);
17896 },
17897
17898 onButtonClick: function(/*dijit._Widget*/ page){
17899 // summary:
17900 // Called whenever one of my child buttons is pressed in an attempt to select a page
17901 // tags:
17902 // private
17903
17904 var container = dijit.byId(this.containerId);
17905 container.selectChild(page);
17906 },
17907
17908 onCloseButtonClick: function(/*dijit._Widget*/ page){
17909 // summary:
17910 // Called whenever one of my child buttons [X] is pressed in an attempt to close a page
17911 // tags:
17912 // private
17913
17914 var container = dijit.byId(this.containerId);
17915 container.closeChild(page);
17916 if(this._currentChild){
17917 var b = this.pane2button[this._currentChild.id];
17918 if(b){
17919 dijit.focus(b.focusNode || b.domNode);
17920 }
17921 }
17922 },
17923
17924 // TODO: this is a bit redundant with forward, back api in StackContainer
17925 adjacent: function(/*Boolean*/ forward){
17926 // summary:
17927 // Helper for onkeypress to find next/previous button
17928 // tags:
17929 // private
17930
17931 if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; }
17932 // find currently focused button in children array
17933 var children = this.getChildren();
17934 var current = dojo.indexOf(children, this.pane2button[this._currentChild.id]);
17935 // pick next button to focus on
17936 var offset = forward ? 1 : children.length - 1;
17937 return children[ (current + offset) % children.length ]; // dijit._Widget
17938 },
17939
17940 onkeypress: function(/*Event*/ e){
17941 // summary:
17942 // Handle keystrokes on the page list, for advancing to next/previous button
17943 // and closing the current page if the page is closable.
17944 // tags:
17945 // private
17946
17947 if(this.disabled || e.altKey ){ return; }
17948 var forward = null;
17949 if(e.ctrlKey || !e._djpage){
17950 var k = dojo.keys;
17951 switch(e.charOrCode){
17952 case k.LEFT_ARROW:
17953 case k.UP_ARROW:
17954 if(!e._djpage){ forward = false; }
17955 break;
17956 case k.PAGE_UP:
17957 if(e.ctrlKey){ forward = false; }
17958 break;
17959 case k.RIGHT_ARROW:
17960 case k.DOWN_ARROW:
17961 if(!e._djpage){ forward = true; }
17962 break;
17963 case k.PAGE_DOWN:
17964 if(e.ctrlKey){ forward = true; }
17965 break;
17966 case k.DELETE:
17967 if(this._currentChild.closable){
17968 this.onCloseButtonClick(this._currentChild);
17969 }
17970 dojo.stopEvent(e);
17971 break;
17972 default:
17973 if(e.ctrlKey){
17974 if(e.charOrCode === k.TAB){
17975 this.adjacent(!e.shiftKey).onClick();
17976 dojo.stopEvent(e);
17977 }else if(e.charOrCode == "w"){
17978 if(this._currentChild.closable){
17979 this.onCloseButtonClick(this._currentChild);
17980 }
17981 dojo.stopEvent(e); // avoid browser tab closing.
17982 }
17983 }
17984 }
17985 // handle page navigation
17986 if(forward !== null){
17987 this.adjacent(forward).onClick();
17988 dojo.stopEvent(e);
17989 }
17990 }
17991 },
17992
17993 onContainerKeyPress: function(/*Object*/ info){
17994 // summary:
17995 // Called when there was a keypress on the container
17996 // tags:
17997 // private
17998 info.e._djpage = info.page;
17999 this.onkeypress(info.e);
18000 }
18001 });
18002
18003
18004 dojo.declare("dijit.layout._StackButton",
18005 dijit.form.ToggleButton,
18006 {
18007 // summary:
18008 // Internal widget used by StackContainer.
18009 // description:
18010 // The button-like or tab-like object you click to select or delete a page
18011 // tags:
18012 // private
18013
18014 // Override _FormWidget.tabIndex.
18015 // StackContainer buttons are not in the tab order by default.
18016 // Probably we should be calling this.startupKeyNavChildren() instead.
18017 tabIndex: "-1",
18018
18019 postCreate: function(/*Event*/ evt){
18020 dijit.setWaiRole((this.focusNode || this.domNode), "tab");
18021 this.inherited(arguments);
18022 },
18023
18024 onClick: function(/*Event*/ evt){
18025 // summary:
18026 // This is for TabContainer where the tabs are <span> rather than button,
18027 // so need to set focus explicitly (on some browsers)
18028 // Note that you shouldn't override this method, but you can connect to it.
18029 dijit.focus(this.focusNode);
18030
18031 // ... now let StackController catch the event and tell me what to do
18032 },
18033
18034 onClickCloseButton: function(/*Event*/ evt){
18035 // summary:
18036 // StackContainer connects to this function; if your widget contains a close button
18037 // then clicking it should call this function.
18038 // Note that you shouldn't override this method, but you can connect to it.
18039 evt.stopPropagation();
18040 }
18041 });
18042
18043
18044 }
18045
18046 if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
18047 dojo._hasResource["dijit.layout.StackContainer"] = true;
18048 dojo.provide("dijit.layout.StackContainer");
18049
18050
18051
18052
18053
18054
18055 dojo.declare(
18056 "dijit.layout.StackContainer",
18057 dijit.layout._LayoutWidget,
18058 {
18059 // summary:
18060 // A container that has multiple children, but shows only
18061 // one child at a time
18062 //
18063 // description:
18064 // A container for widgets (ContentPanes, for example) That displays
18065 // only one Widget at a time.
18066 //
18067 // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild
18068 //
18069 // Can be base class for container, Wizard, Show, etc.
18070
18071 // doLayout: Boolean
18072 // If true, change the size of my currently displayed child to match my size
18073 doLayout: true,
18074
18075 // persist: Boolean
18076 // Remembers the selected child across sessions
18077 persist: false,
18078
18079 baseClass: "dijitStackContainer",
18080
18081 /*=====
18082 // selectedChildWidget: [readonly] dijit._Widget
18083 // References the currently selected child widget, if any.
18084 // Adjust selected child with selectChild() method.
18085 selectedChildWidget: null,
18086 =====*/
18087
18088 postCreate: function(){
18089 this.inherited(arguments);
18090 dojo.addClass(this.domNode, "dijitLayoutContainer");
18091 dijit.setWaiRole(this.containerNode, "tabpanel");
18092 this.connect(this.domNode, "onkeypress", this._onKeyPress);
18093 },
18094
18095 startup: function(){
18096 if(this._started){ return; }
18097
18098 var children = this.getChildren();
18099
18100 // Setup each page panel to be initially hidden
18101 dojo.forEach(children, this._setupChild, this);
18102
18103 // Figure out which child to initially display, defaulting to first one
18104 if(this.persist){
18105 this.selectedChildWidget = dijit.byId(dojo.cookie(this.id + "_selectedChild"));
18106 }else{
18107 dojo.some(children, function(child){
18108 if(child.selected){
18109 this.selectedChildWidget = child;
18110 }
18111 return child.selected;
18112 }, this);
18113 }
18114 var selected = this.selectedChildWidget;
18115 if(!selected && children[0]){
18116 selected = this.selectedChildWidget = children[0];
18117 selected.selected = true;
18118 }
18119
18120 // Publish information about myself so any StackControllers can initialize.
18121 // This needs to happen before this.inherited(arguments) so that for
18122 // TabContainer, this._contentBox doesn't include the space for the tab labels.
18123 dojo.publish(this.id+"-startup", [{children: children, selected: selected}]);
18124
18125 // Startup each child widget, and do initial layout like setting this._contentBox,
18126 // then calls this.resize() which does the initial sizing on the selected child.
18127 this.inherited(arguments);
18128 },
18129
18130 resize: function(){
18131 // Resize is called when we are first made visible (it's called from startup()
18132 // if we are initially visible). If this is the first time we've been made
18133 // visible then show our first child.
18134 var selected = this.selectedChildWidget;
18135 if(selected && !this._hasBeenShown){
18136 this._hasBeenShown = true;
18137 this._showChild(selected);
18138 }
18139 this.inherited(arguments);
18140 },
18141
18142 _setupChild: function(/*dijit._Widget*/ child){
18143 // Overrides _LayoutWidget._setupChild()
18144
18145 this.inherited(arguments);
18146
18147 dojo.removeClass(child.domNode, "dijitVisible");
18148 dojo.addClass(child.domNode, "dijitHidden");
18149
18150 // remove the title attribute so it doesn't show up when i hover
18151 // over a node
18152 child.domNode.title = "";
18153 },
18154
18155 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
18156 // Overrides _Container.addChild() to do layout and publish events
18157
18158 this.inherited(arguments);
18159
18160 if(this._started){
18161 dojo.publish(this.id+"-addChild", [child, insertIndex]);
18162
18163 // in case the tab titles have overflowed from one line to two lines
18164 // (or, if this if first child, from zero lines to one line)
18165 // TODO: w/ScrollingTabController this is no longer necessary, although
18166 // ScrollTabController.resize() does need to get called to show/hide
18167 // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild()
18168 this.layout();
18169
18170 // if this is the first child, then select it
18171 if(!this.selectedChildWidget){
18172 this.selectChild(child);
18173 }
18174 }
18175 },
18176
18177 removeChild: function(/*dijit._Widget*/ page){
18178 // Overrides _Container.removeChild() to do layout and publish events
18179
18180 this.inherited(arguments);
18181
18182 if(this._started){
18183 // this will notify any tablists to remove a button; do this first because it may affect sizing
18184 dojo.publish(this.id + "-removeChild", [page]);
18185 }
18186
18187 // If we are being destroyed than don't run the code below (to select another page), because we are deleting
18188 // every page one by one
18189 if(this._beingDestroyed){ return; }
18190
18191 // Select new page to display, also updating TabController to show the respective tab.
18192 // Do this before layout call because it can affect the height of the TabController.
18193 if(this.selectedChildWidget === page){
18194 this.selectedChildWidget = undefined;
18195 if(this._started){
18196 var children = this.getChildren();
18197 if(children.length){
18198 this.selectChild(children[0]);
18199 }
18200 }
18201 }
18202
18203 if(this._started){
18204 // In case the tab titles now take up one line instead of two lines
18205 // (note though that ScrollingTabController never overflows to multiple lines),
18206 // or the height has changed slightly because of addition/removal of tab which close icon
18207 this.layout();
18208 }
18209 },
18210
18211 selectChild: function(/*dijit._Widget|String*/ page, /*Boolean*/ animate){
18212 // summary:
18213 // Show the given widget (which must be one of my children)
18214 // page:
18215 // Reference to child widget or id of child widget
18216
18217 page = dijit.byId(page);
18218
18219 if(this.selectedChildWidget != page){
18220 // Deselect old page and select new one
18221 this._transition(page, this.selectedChildWidget, animate);
18222 this.selectedChildWidget = page;
18223 dojo.publish(this.id+"-selectChild", [page]);
18224
18225 if(this.persist){
18226 dojo.cookie(this.id + "_selectedChild", this.selectedChildWidget.id);
18227 }
18228 }
18229 },
18230
18231 _transition: function(/*dijit._Widget*/newWidget, /*dijit._Widget*/oldWidget){
18232 // summary:
18233 // Hide the old widget and display the new widget.
18234 // Subclasses should override this.
18235 // tags:
18236 // protected extension
18237 if(oldWidget){
18238 this._hideChild(oldWidget);
18239 }
18240 this._showChild(newWidget);
18241
18242 // Size the new widget, in case this is the first time it's being shown,
18243 // or I have been resized since the last time it was shown.
18244 // Note that page must be visible for resizing to work.
18245 if(newWidget.resize){
18246 if(this.doLayout){
18247 newWidget.resize(this._containerContentBox || this._contentBox);
18248 }else{
18249 // the child should pick it's own size but we still need to call resize()
18250 // (with no arguments) to let the widget lay itself out
18251 newWidget.resize();
18252 }
18253 }
18254 },
18255
18256 _adjacent: function(/*Boolean*/ forward){
18257 // summary:
18258 // Gets the next/previous child widget in this container from the current selection.
18259 var children = this.getChildren();
18260 var index = dojo.indexOf(children, this.selectedChildWidget);
18261 index += forward ? 1 : children.length - 1;
18262 return children[ index % children.length ]; // dijit._Widget
18263 },
18264
18265 forward: function(){
18266 // summary:
18267 // Advance to next page.
18268 this.selectChild(this._adjacent(true), true);
18269 },
18270
18271 back: function(){
18272 // summary:
18273 // Go back to previous page.
18274 this.selectChild(this._adjacent(false), true);
18275 },
18276
18277 _onKeyPress: function(e){
18278 dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]);
18279 },
18280
18281 layout: function(){
18282 // Implement _LayoutWidget.layout() virtual method.
18283 if(this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize){
18284 this.selectedChildWidget.resize(this._containerContentBox || this._contentBox);
18285 }
18286 },
18287
18288 _showChild: function(/*dijit._Widget*/ page){
18289 // summary:
18290 // Show the specified child by changing it's CSS, and call _onShow()/onShow() so
18291 // it can do any updates it needs regarding loading href's etc.
18292 var children = this.getChildren();
18293 page.isFirstChild = (page == children[0]);
18294 page.isLastChild = (page == children[children.length-1]);
18295 page.selected = true;
18296
18297 dojo.removeClass(page.domNode, "dijitHidden");
18298 dojo.addClass(page.domNode, "dijitVisible");
18299
18300 page._onShow();
18301 },
18302
18303 _hideChild: function(/*dijit._Widget*/ page){
18304 // summary:
18305 // Hide the specified child by changing it's CSS, and call _onHide() so
18306 // it's notified.
18307 page.selected=false;
18308 dojo.removeClass(page.domNode, "dijitVisible");
18309 dojo.addClass(page.domNode, "dijitHidden");
18310
18311 page.onHide();
18312 },
18313
18314 closeChild: function(/*dijit._Widget*/ page){
18315 // summary:
18316 // Callback when user clicks the [X] to remove a page.
18317 // If onClose() returns true then remove and destroy the child.
18318 // tags:
18319 // private
18320 var remove = page.onClose(this, page);
18321 if(remove){
18322 this.removeChild(page);
18323 // makes sure we can clean up executeScripts in ContentPane onUnLoad
18324 page.destroyRecursive();
18325 }
18326 },
18327
18328 destroyDescendants: function(/*Boolean*/preserveDom){
18329 dojo.forEach(this.getChildren(), function(child){
18330 this.removeChild(child);
18331 child.destroyRecursive(preserveDom);
18332 }, this);
18333 }
18334 });
18335
18336 // For back-compat, remove for 2.0
18337
18338
18339
18340 // These arguments can be specified for the children of a StackContainer.
18341 // Since any widget can be specified as a StackContainer child, mix them
18342 // into the base widget class. (This is a hack, but it's effective.)
18343 dojo.extend(dijit._Widget, {
18344 // selected: Boolean
18345 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
18346 // Specifies that this widget should be the initially displayed pane.
18347 // Note: to change the selected child use `dijit.layout.StackContainer.selectChild`
18348 selected: false,
18349
18350 // closable: Boolean
18351 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
18352 // True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
18353 closable: false,
18354
18355 // iconClass: String
18356 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
18357 // CSS Class specifying icon to use in label associated with this pane.
18358 iconClass: "",
18359
18360 // showTitle: Boolean
18361 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
18362 // When true, display title of this widget as tab label etc., rather than just using
18363 // icon specified in iconClass
18364 showTitle: true
18365 });
18366
18367 }
18368
18369 if(!dojo._hasResource["dijit.layout.AccordionPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
18370 dojo._hasResource["dijit.layout.AccordionPane"] = true;
18371 dojo.provide("dijit.layout.AccordionPane");
18372
18373
18374
18375 dojo.declare("dijit.layout.AccordionPane", dijit.layout.ContentPane, {
18376 // summary:
18377 // Deprecated widget. Use `dijit.layout.ContentPane` instead.
18378 // tags:
18379 // deprecated
18380
18381 constructor: function(){
18382 dojo.deprecated("dijit.layout.AccordionPane deprecated, use ContentPane instead", "", "2.0");
18383 },
18384
18385 onSelected: function(){
18386 // summary:
18387 // called when this pane is selected
18388 }
18389 });
18390
18391 }
18392
18393 if(!dojo._hasResource["dijit.layout.AccordionContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
18394 dojo._hasResource["dijit.layout.AccordionContainer"] = true;
18395 dojo.provide("dijit.layout.AccordionContainer");
18396
18397
18398
18399
18400
18401
18402
18403
18404
18405 // for back compat, remove for 2.0
18406
18407 dojo.declare(
18408 "dijit.layout.AccordionContainer",
18409 dijit.layout.StackContainer,
18410 {
18411 // summary:
18412 // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time,
18413 // and switching between panes is visualized by sliding the other panes up/down.
18414 // example:
18415 // | <div dojoType="dijit.layout.AccordionContainer">
18416 // | <div dojoType="dijit.layout.ContentPane" title="pane 1">
18417 // | </div>
18418 // | <div dojoType="dijit.layout.ContentPane" title="pane 2">
18419 // | <p>This is some text</p>
18420 // | </div>
18421 // | </div>
18422
18423 // duration: Integer
18424 // Amount of time (in ms) it takes to slide panes
18425 duration: dijit.defaultDuration,
18426
18427 // buttonWidget: [const] String
18428 // The name of the widget used to display the title of each pane
18429 buttonWidget: "dijit.layout._AccordionButton",
18430
18431 // _verticalSpace: Number
18432 // Pixels of space available for the open pane
18433 // (my content box size minus the cumulative size of all the title bars)
18434 _verticalSpace: 0,
18435
18436 baseClass: "dijitAccordionContainer",
18437
18438 postCreate: function(){
18439 this.domNode.style.overflow = "hidden";
18440 this.inherited(arguments);
18441 dijit.setWaiRole(this.domNode, "tablist");
18442 },
18443
18444 startup: function(){
18445 if(this._started){ return; }
18446 this.inherited(arguments);
18447 if(this.selectedChildWidget){
18448 var style = this.selectedChildWidget.containerNode.style;
18449 style.display = "";
18450 style.overflow = "auto";
18451 this.selectedChildWidget._wrapperWidget.set("selected", true);
18452 }
18453 },
18454
18455 _getTargetHeight: function(/* Node */ node){
18456 // summary:
18457 // For the given node, returns the height that should be
18458 // set to achieve our vertical space (subtract any padding
18459 // we may have).
18460 //
18461 // This is used by the animations.
18462 //
18463 // TODO: I don't think this works correctly in IE quirks when an elements
18464 // style.height including padding and borders
18465 var cs = dojo.getComputedStyle(node);
18466 return Math.max(this._verticalSpace - dojo._getPadBorderExtents(node, cs).h - dojo._getMarginExtents(node, cs).h, 0);
18467 },
18468
18469 layout: function(){
18470 // Implement _LayoutWidget.layout() virtual method.
18471 // Set the height of the open pane based on what room remains.
18472
18473 var openPane = this.selectedChildWidget;
18474
18475 if(!openPane){ return;}
18476
18477 var openPaneContainer = openPane._wrapperWidget.domNode,
18478 openPaneContainerMargin = dojo._getMarginExtents(openPaneContainer),
18479 openPaneContainerPadBorder = dojo._getPadBorderExtents(openPaneContainer),
18480 mySize = this._contentBox;
18481
18482 // get cumulative height of all the unselected title bars
18483 var totalCollapsedHeight = 0;
18484 dojo.forEach(this.getChildren(), function(child){
18485 if(child != openPane){
18486 totalCollapsedHeight += dojo.marginBox(child._wrapperWidget.domNode).h;
18487 }
18488 });
18489 this._verticalSpace = mySize.h - totalCollapsedHeight - openPaneContainerMargin.h
18490 - openPaneContainerPadBorder.h - openPane._buttonWidget.getTitleHeight();
18491
18492 // Memo size to make displayed child
18493 this._containerContentBox = {
18494 h: this._verticalSpace,
18495 w: this._contentBox.w - openPaneContainerMargin.w - openPaneContainerPadBorder.w
18496 };
18497
18498 if(openPane){
18499 openPane.resize(this._containerContentBox);
18500 }
18501 },
18502
18503 _setupChild: function(child){
18504 // Overrides _LayoutWidget._setupChild().
18505 // Put wrapper widget around the child widget, showing title
18506
18507 child._wrapperWidget = new dijit.layout._AccordionInnerContainer({
18508 contentWidget: child,
18509 buttonWidget: this.buttonWidget,
18510 id: child.id + "_wrapper",
18511 dir: child.dir,
18512 lang: child.lang,
18513 parent: this
18514 });
18515
18516 this.inherited(arguments);
18517 },
18518
18519 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
18520 if(this._started){
18521 // Adding a child to a started Accordion is complicated because children have
18522 // wrapper widgets. Default code path (calling this.inherited()) would add
18523 // the new child inside another child's wrapper.
18524
18525 // First add in child as a direct child of this AccordionContainer
18526 dojo.place(child.domNode, this.containerNode, insertIndex);
18527
18528 if(!child._started){
18529 child.startup();
18530 }
18531
18532 // Then stick the wrapper widget around the child widget
18533 this._setupChild(child);
18534
18535 // Code below copied from StackContainer
18536 dojo.publish(this.id+"-addChild", [child, insertIndex]);
18537 this.layout();
18538 if(!this.selectedChildWidget){
18539 this.selectChild(child);
18540 }
18541 }else{
18542 // We haven't been started yet so just add in the child widget directly,
18543 // and the wrapper will be created on startup()
18544 this.inherited(arguments);
18545 }
18546 },
18547
18548 removeChild: function(child){
18549 // Overrides _LayoutWidget.removeChild().
18550
18551 // destroy wrapper widget first, before StackContainer.getChildren() call
18552 child._wrapperWidget.destroy();
18553 delete child._wrapperWidget;
18554 dojo.removeClass(child.domNode, "dijitHidden");
18555
18556 this.inherited(arguments);
18557 },
18558
18559 getChildren: function(){
18560 // Overrides _Container.getChildren() to return content panes rather than internal AccordionInnerContainer panes
18561 return dojo.map(this.inherited(arguments), function(child){
18562 return child.declaredClass == "dijit.layout._AccordionInnerContainer" ? child.contentWidget : child;
18563 }, this);
18564 },
18565
18566 destroy: function(){
18567 dojo.forEach(this.getChildren(), function(child){
18568 child._wrapperWidget.destroy();
18569 });
18570 this.inherited(arguments);
18571 },
18572
18573 _transition: function(/*dijit._Widget?*/newWidget, /*dijit._Widget?*/oldWidget, /*Boolean*/ animate){
18574 // Overrides StackContainer._transition() to provide sliding of title bars etc.
18575
18576 //TODO: should be able to replace this with calls to slideIn/slideOut
18577 if(this._inTransition){ return; }
18578 var animations = [];
18579 var paneHeight = this._verticalSpace;
18580 if(newWidget){
18581 newWidget._wrapperWidget.set("selected", true);
18582
18583 this._showChild(newWidget); // prepare widget to be slid in
18584
18585 // Size the new widget, in case this is the first time it's being shown,
18586 // or I have been resized since the last time it was shown.
18587 // Note that page must be visible for resizing to work.
18588 if(this.doLayout && newWidget.resize){
18589 newWidget.resize(this._containerContentBox);
18590 }
18591
18592 var newContents = newWidget.domNode;
18593 dojo.addClass(newContents, "dijitVisible");
18594 dojo.removeClass(newContents, "dijitHidden");
18595
18596 if(animate){
18597 var newContentsOverflow = newContents.style.overflow;
18598 newContents.style.overflow = "hidden";
18599 animations.push(dojo.animateProperty({
18600 node: newContents,
18601 duration: this.duration,
18602 properties: {
18603 height: { start: 1, end: this._getTargetHeight(newContents) }
18604 },
18605 onEnd: function(){
18606 newContents.style.overflow = newContentsOverflow;
18607
18608 // Kick IE to workaround layout bug, see #11415
18609 if(dojo.isIE){
18610 setTimeout(function(){
18611 dojo.removeClass(newContents.parentNode, "dijitAccordionInnerContainerFocused");
18612 setTimeout(function(){
18613 dojo.addClass(newContents.parentNode, "dijitAccordionInnerContainerFocused");
18614 }, 0);
18615 }, 0);
18616 }
18617 }
18618 }));
18619 }
18620 }
18621 if(oldWidget){
18622 oldWidget._wrapperWidget.set("selected", false);
18623 var oldContents = oldWidget.domNode;
18624 if(animate){
18625 var oldContentsOverflow = oldContents.style.overflow;
18626 oldContents.style.overflow = "hidden";
18627 animations.push(dojo.animateProperty({
18628 node: oldContents,
18629 duration: this.duration,
18630 properties: {
18631 height: { start: this._getTargetHeight(oldContents), end: 1 }
18632 },
18633 onEnd: function(){
18634 dojo.addClass(oldContents, "dijitHidden");
18635 dojo.removeClass(oldContents, "dijitVisible");
18636 oldContents.style.overflow = oldContentsOverflow;
18637 if(oldWidget.onHide){
18638 oldWidget.onHide();
18639 }
18640 }
18641 }));
18642 }else{
18643 dojo.addClass(oldContents, "dijitHidden");
18644 dojo.removeClass(oldContents, "dijitVisible");
18645 if(oldWidget.onHide){
18646 oldWidget.onHide();
18647 }
18648 }
18649 }
18650
18651 if(animate){
18652 this._inTransition = true;
18653 var combined = dojo.fx.combine(animations);
18654 combined.onEnd = dojo.hitch(this, function(){
18655 delete this._inTransition;
18656 });
18657 combined.play();
18658 }
18659 },
18660
18661 // note: we are treating the container as controller here
18662 _onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){
18663 // summary:
18664 // Handle keypress events
18665 // description:
18666 // This is called from a handler on AccordionContainer.domNode
18667 // (setup in StackContainer), and is also called directly from
18668 // the click handler for accordion labels
18669 if(this._inTransition || this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){
18670 if(this._inTransition){
18671 dojo.stopEvent(e);
18672 }
18673 return;
18674 }
18675 var k = dojo.keys,
18676 c = e.charOrCode;
18677 if((fromTitle && (c == k.LEFT_ARROW || c == k.UP_ARROW)) ||
18678 (e.ctrlKey && c == k.PAGE_UP)){
18679 this._adjacent(false)._buttonWidget._onTitleClick();
18680 dojo.stopEvent(e);
18681 }else if((fromTitle && (c == k.RIGHT_ARROW || c == k.DOWN_ARROW)) ||
18682 (e.ctrlKey && (c == k.PAGE_DOWN || c == k.TAB))){
18683 this._adjacent(true)._buttonWidget._onTitleClick();
18684 dojo.stopEvent(e);
18685 }
18686 }
18687 }
18688 );
18689
18690 dojo.declare("dijit.layout._AccordionInnerContainer",
18691 [dijit._Widget, dijit._CssStateMixin], {
18692 // summary:
18693 // Internal widget placed as direct child of AccordionContainer.containerNode.
18694 // When other widgets are added as children to an AccordionContainer they are wrapped in
18695 // this widget.
18696
18697 // buttonWidget: String
18698 // Name of class to use to instantiate title
18699 // (Wish we didn't have a separate widget for just the title but maintaining it
18700 // for backwards compatibility, is it worth it?)
18701 /*=====
18702 buttonWidget: null,
18703 =====*/
18704 // contentWidget: dijit._Widget
18705 // Pointer to the real child widget
18706 /*=====
18707 contentWidget: null,
18708 =====*/
18709
18710 baseClass: "dijitAccordionInnerContainer",
18711
18712 // tell nested layout widget that we will take care of sizing
18713 isContainer: true,
18714 isLayoutContainer: true,
18715
18716 buildRendering: function(){
18717 // Create wrapper div, placed where the child is now
18718 this.domNode = dojo.place("<div class='" + this.baseClass + "'>", this.contentWidget.domNode, "after");
18719
18720 // wrapper div's first child is the button widget (ie, the title bar)
18721 var child = this.contentWidget,
18722 cls = dojo.getObject(this.buttonWidget);
18723 this.button = child._buttonWidget = (new cls({
18724 contentWidget: child,
18725 label: child.title,
18726 title: child.tooltip,
18727 dir: child.dir,
18728 lang: child.lang,
18729 iconClass: child.iconClass,
18730 id: child.id + "_button",
18731 parent: this.parent
18732 })).placeAt(this.domNode);
18733
18734 // and then the actual content widget (changing it from prior-sibling to last-child)
18735 dojo.place(this.contentWidget.domNode, this.domNode);
18736 },
18737
18738 postCreate: function(){
18739 this.inherited(arguments);
18740 this.connect(this.contentWidget, 'set', function(name, value){
18741 var mappedName = {title: "label", tooltip: "title", iconClass: "iconClass"}[name];
18742 if(mappedName){
18743 this.button.set(mappedName, value);
18744 }
18745 }, this);
18746 },
18747
18748 _setSelectedAttr: function(/*Boolean*/ isSelected){
18749 this.selected = isSelected;
18750 this.button.set("selected", isSelected);
18751 if(isSelected){
18752 var cw = this.contentWidget;
18753 if(cw.onSelected){ cw.onSelected(); }
18754 }
18755 },
18756
18757 startup: function(){
18758 // Called by _Container.addChild()
18759 this.contentWidget.startup();
18760 },
18761
18762 destroy: function(){
18763 this.button.destroyRecursive();
18764
18765 delete this.contentWidget._buttonWidget;
18766 delete this.contentWidget._wrapperWidget;
18767
18768 this.inherited(arguments);
18769 },
18770
18771 destroyDescendants: function(){
18772 // since getChildren isn't working for me, have to code this manually
18773 this.contentWidget.destroyRecursive();
18774 }
18775 });
18776
18777 dojo.declare("dijit.layout._AccordionButton",
18778 [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
18779 {
18780 // summary:
18781 // The title bar to click to open up an accordion pane.
18782 // Internal widget used by AccordionContainer.
18783 // tags:
18784 // private
18785
18786 templateString: dojo.cache("dijit.layout", "templates/AccordionButton.html", "<div dojoAttachEvent='onclick:_onTitleClick' class='dijitAccordionTitle'>\n\t<div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='onkeypress:_onTitleKeyPress'\n\t\t\tclass='dijitAccordionTitleFocus' wairole=\"tab\" waiState=\"expanded-false\"\n\t\t><span class='dijitInline dijitAccordionArrow' waiRole=\"presentation\"></span\n\t\t><span class='arrowTextUp' waiRole=\"presentation\">+</span\n\t\t><span class='arrowTextDown' waiRole=\"presentation\">-</span\n\t\t><img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' style=\"vertical-align: middle\" waiRole=\"presentation\"/>\n\t\t<span waiRole=\"presentation\" dojoAttachPoint='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n"),
18787 attributeMap: dojo.mixin(dojo.clone(dijit.layout.ContentPane.prototype.attributeMap), {
18788 label: {node: "titleTextNode", type: "innerHTML" },
18789 title: {node: "titleTextNode", type: "attribute", attribute: "title"},
18790 iconClass: { node: "iconNode", type: "class" }
18791 }),
18792
18793 baseClass: "dijitAccordionTitle",
18794
18795 getParent: function(){
18796 // summary:
18797 // Returns the AccordionContainer parent.
18798 // tags:
18799 // private
18800 return this.parent;
18801 },
18802
18803 postCreate: function(){
18804 this.inherited(arguments);
18805 dojo.setSelectable(this.domNode, false);
18806 var titleTextNodeId = dojo.attr(this.domNode,'id').replace(' ','_');
18807 dojo.attr(this.titleTextNode, "id", titleTextNodeId+"_title");
18808 dijit.setWaiState(this.focusNode, "labelledby", dojo.attr(this.titleTextNode, "id"));
18809 },
18810
18811 getTitleHeight: function(){
18812 // summary:
18813 // Returns the height of the title dom node.
18814 return dojo.marginBox(this.domNode).h; // Integer
18815 },
18816
18817 // TODO: maybe the parent should set these methods directly rather than forcing the code
18818 // into the button widget?
18819 _onTitleClick: function(){
18820 // summary:
18821 // Callback when someone clicks my title.
18822 var parent = this.getParent();
18823 if(!parent._inTransition){
18824 parent.selectChild(this.contentWidget, true);
18825 dijit.focus(this.focusNode);
18826 }
18827 },
18828
18829 _onTitleKeyPress: function(/*Event*/ evt){
18830 return this.getParent()._onKeyPress(evt, this.contentWidget);
18831 },
18832
18833 _setSelectedAttr: function(/*Boolean*/ isSelected){
18834 this.selected = isSelected;
18835 dijit.setWaiState(this.focusNode, "expanded", isSelected);
18836 dijit.setWaiState(this.focusNode, "selected", isSelected);
18837 this.focusNode.setAttribute("tabIndex", isSelected ? "0" : "-1");
18838 }
18839 });
18840
18841 }
18842
18843 if(!dojo._hasResource["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
18844 dojo._hasResource["dijit.layout.BorderContainer"] = true;
18845 dojo.provide("dijit.layout.BorderContainer");
18846
18847
18848
18849
18850 dojo.declare(
18851 "dijit.layout.BorderContainer",
18852 dijit.layout._LayoutWidget,
18853 {
18854 // summary:
18855 // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides.
18856 //
18857 // description:
18858 // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;",
18859 // that contains a child widget marked region="center" and optionally children widgets marked
18860 // region equal to "top", "bottom", "leading", "trailing", "left" or "right".
18861 // Children along the edges will be laid out according to width or height dimensions and may
18862 // include optional splitters (splitter="true") to make them resizable by the user. The remaining
18863 // space is designated for the center region.
18864 //
18865 // NOTE: Splitters must not be more than 50 pixels in width.
18866 //
18867 // The outer size must be specified on the BorderContainer node. Width must be specified for the sides
18868 // and height for the top and bottom, respectively. No dimensions should be specified on the center;
18869 // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like
18870 // "left" and "right" except that they will be reversed in right-to-left environments.
18871 //
18872 // example:
18873 // | <div dojoType="dijit.layout.BorderContainer" design="sidebar" gutters="false"
18874 // | style="width: 400px; height: 300px;">
18875 // | <div dojoType="ContentPane" region="top">header text</div>
18876 // | <div dojoType="ContentPane" region="right" splitter="true" style="width: 200px;">table of contents</div>
18877 // | <div dojoType="ContentPane" region="center">client area</div>
18878 // | </div>
18879
18880 // design: String
18881 // Which design is used for the layout:
18882 // - "headline" (default) where the top and bottom extend
18883 // the full width of the container
18884 // - "sidebar" where the left and right sides extend from top to bottom.
18885 design: "headline",
18886
18887 // gutters: Boolean
18888 // Give each pane a border and margin.
18889 // Margin determined by domNode.paddingLeft.
18890 // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing.
18891 gutters: true,
18892
18893 // liveSplitters: Boolean
18894 // Specifies whether splitters resize as you drag (true) or only upon mouseup (false)
18895 liveSplitters: true,
18896
18897 // persist: Boolean
18898 // Save splitter positions in a cookie.
18899 persist: false,
18900
18901 baseClass: "dijitBorderContainer",
18902
18903 // _splitterClass: String
18904 // Optional hook to override the default Splitter widget used by BorderContainer
18905 _splitterClass: "dijit.layout._Splitter",
18906
18907 postMixInProperties: function(){
18908 // change class name to indicate that BorderContainer is being used purely for
18909 // layout (like LayoutContainer) rather than for pretty formatting.
18910 if(!this.gutters){
18911 this.baseClass += "NoGutter";
18912 }
18913 this.inherited(arguments);
18914 },
18915
18916 postCreate: function(){
18917 this.inherited(arguments);
18918
18919 this._splitters = {};
18920 this._splitterThickness = {};
18921 },
18922
18923 startup: function(){
18924 if(this._started){ return; }
18925 dojo.forEach(this.getChildren(), this._setupChild, this);
18926 this.inherited(arguments);
18927 },
18928
18929 _setupChild: function(/*dijit._Widget*/ child){
18930 // Override _LayoutWidget._setupChild().
18931
18932 var region = child.region;
18933 if(region){
18934 this.inherited(arguments);
18935
18936 dojo.addClass(child.domNode, this.baseClass+"Pane");
18937
18938 var ltr = this.isLeftToRight();
18939 if(region == "leading"){ region = ltr ? "left" : "right"; }
18940 if(region == "trailing"){ region = ltr ? "right" : "left"; }
18941
18942 //FIXME: redundant?
18943 this["_"+region] = child.domNode;
18944 this["_"+region+"Widget"] = child;
18945
18946 // Create draggable splitter for resizing pane,
18947 // or alternately if splitter=false but BorderContainer.gutters=true then
18948 // insert dummy div just for spacing
18949 if((child.splitter || this.gutters) && !this._splitters[region]){
18950 var _Splitter = dojo.getObject(child.splitter ? this._splitterClass : "dijit.layout._Gutter");
18951 var splitter = new _Splitter({
18952 id: child.id + "_splitter",
18953 container: this,
18954 child: child,
18955 region: region,
18956 live: this.liveSplitters
18957 });
18958 splitter.isSplitter = true;
18959 this._splitters[region] = splitter.domNode;
18960 dojo.place(this._splitters[region], child.domNode, "after");
18961
18962 // Splitters arent added as Contained children, so we need to call startup explicitly
18963 splitter.startup();
18964 }
18965 child.region = region;
18966 }
18967 },
18968
18969 _computeSplitterThickness: function(region){
18970 this._splitterThickness[region] = this._splitterThickness[region] ||
18971 dojo.marginBox(this._splitters[region])[(/top|bottom/.test(region) ? 'h' : 'w')];
18972 },
18973
18974 layout: function(){
18975 // Implement _LayoutWidget.layout() virtual method.
18976 for(var region in this._splitters){ this._computeSplitterThickness(region); }
18977 this._layoutChildren();
18978 },
18979
18980 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
18981 // Override _LayoutWidget.addChild().
18982 this.inherited(arguments);
18983 if(this._started){
18984 this.layout(); //OPT
18985 }
18986 },
18987
18988 removeChild: function(/*dijit._Widget*/ child){
18989 // Override _LayoutWidget.removeChild().
18990 var region = child.region;
18991 var splitter = this._splitters[region];
18992 if(splitter){
18993 dijit.byNode(splitter).destroy();
18994 delete this._splitters[region];
18995 delete this._splitterThickness[region];
18996 }
18997 this.inherited(arguments);
18998 delete this["_"+region];
18999 delete this["_" +region+"Widget"];
19000 if(this._started){
19001 this._layoutChildren();
19002 }
19003 dojo.removeClass(child.domNode, this.baseClass+"Pane");
19004 },
19005
19006 getChildren: function(){
19007 // Override _LayoutWidget.getChildren() to only return real children, not the splitters.
19008 return dojo.filter(this.inherited(arguments), function(widget){
19009 return !widget.isSplitter;
19010 });
19011 },
19012
19013 getSplitter: function(/*String*/region){
19014 // summary:
19015 // Returns the widget responsible for rendering the splitter associated with region
19016 var splitter = this._splitters[region];
19017 return splitter ? dijit.byNode(splitter) : null;
19018 },
19019
19020 resize: function(newSize, currentSize){
19021 // Overrides _LayoutWidget.resize().
19022
19023 // resetting potential padding to 0px to provide support for 100% width/height + padding
19024 // TODO: this hack doesn't respect the box model and is a temporary fix
19025 if(!this.cs || !this.pe){
19026 var node = this.domNode;
19027 this.cs = dojo.getComputedStyle(node);
19028 this.pe = dojo._getPadExtents(node, this.cs);
19029 this.pe.r = dojo._toPixelValue(node, this.cs.paddingRight);
19030 this.pe.b = dojo._toPixelValue(node, this.cs.paddingBottom);
19031
19032 dojo.style(node, "padding", "0px");
19033 }
19034
19035 this.inherited(arguments);
19036 },
19037
19038 _layoutChildren: function(/*String?*/changedRegion, /*Number?*/ changedRegionSize){
19039 // summary:
19040 // This is the main routine for setting size/position of each child.
19041 // description:
19042 // With no arguments, measures the height of top/bottom panes, the width
19043 // of left/right panes, and then sizes all panes accordingly.
19044 //
19045 // With changedRegion specified (as "left", "top", "bottom", or "right"),
19046 // it changes that region's width/height to changedRegionSize and
19047 // then resizes other regions that were affected.
19048 // changedRegion:
19049 // The region should be changed because splitter was dragged.
19050 // "left", "right", "top", or "bottom".
19051 // changedRegionSize:
19052 // The new width/height (in pixels) to make changedRegion
19053
19054 if(!this._borderBox || !this._borderBox.h){
19055 // We are currently hidden, or we haven't been sized by our parent yet.
19056 // Abort. Someone will resize us later.
19057 return;
19058 }
19059
19060 var sidebarLayout = (this.design == "sidebar");
19061 var topHeight = 0, bottomHeight = 0, leftWidth = 0, rightWidth = 0;
19062 var topStyle = {}, leftStyle = {}, rightStyle = {}, bottomStyle = {},
19063 centerStyle = (this._center && this._center.style) || {};
19064
19065 var changedSide = /left|right/.test(changedRegion);
19066
19067 var layoutSides = !changedRegion || (!changedSide && !sidebarLayout);
19068 var layoutTopBottom = !changedRegion || (changedSide && sidebarLayout);
19069
19070 // Ask browser for width/height of side panes.
19071 // Would be nice to cache this but height can change according to width
19072 // (because words wrap around). I don't think width will ever change though
19073 // (except when the user drags a splitter).
19074 if(this._top){
19075 topStyle = (changedRegion == "top" || layoutTopBottom) && this._top.style;
19076 topHeight = changedRegion == "top" ? changedRegionSize : dojo.marginBox(this._top).h;
19077 }
19078 if(this._left){
19079 leftStyle = (changedRegion == "left" || layoutSides) && this._left.style;
19080 leftWidth = changedRegion == "left" ? changedRegionSize : dojo.marginBox(this._left).w;
19081 }
19082 if(this._right){
19083 rightStyle = (changedRegion == "right" || layoutSides) && this._right.style;
19084 rightWidth = changedRegion == "right" ? changedRegionSize : dojo.marginBox(this._right).w;
19085 }
19086 if(this._bottom){
19087 bottomStyle = (changedRegion == "bottom" || layoutTopBottom) && this._bottom.style;
19088 bottomHeight = changedRegion == "bottom" ? changedRegionSize : dojo.marginBox(this._bottom).h;
19089 }
19090
19091 var splitters = this._splitters;
19092 var topSplitter = splitters.top, bottomSplitter = splitters.bottom,
19093 leftSplitter = splitters.left, rightSplitter = splitters.right;
19094 var splitterThickness = this._splitterThickness;
19095 var topSplitterThickness = splitterThickness.top || 0,
19096 leftSplitterThickness = splitterThickness.left || 0,
19097 rightSplitterThickness = splitterThickness.right || 0,
19098 bottomSplitterThickness = splitterThickness.bottom || 0;
19099
19100 // Check for race condition where CSS hasn't finished loading, so
19101 // the splitter width == the viewport width (#5824)
19102 if(leftSplitterThickness > 50 || rightSplitterThickness > 50){
19103 setTimeout(dojo.hitch(this, function(){
19104 // Results are invalid. Clear them out.
19105 this._splitterThickness = {};
19106
19107 for(var region in this._splitters){
19108 this._computeSplitterThickness(region);
19109 }
19110 this._layoutChildren();
19111 }), 50);
19112 return false;
19113 }
19114
19115 var pe = this.pe;
19116
19117 var splitterBounds = {
19118 left: (sidebarLayout ? leftWidth + leftSplitterThickness: 0) + pe.l + "px",
19119 right: (sidebarLayout ? rightWidth + rightSplitterThickness: 0) + pe.r + "px"
19120 };
19121
19122 if(topSplitter){
19123 dojo.mixin(topSplitter.style, splitterBounds);
19124 topSplitter.style.top = topHeight + pe.t + "px";
19125 }
19126
19127 if(bottomSplitter){
19128 dojo.mixin(bottomSplitter.style, splitterBounds);
19129 bottomSplitter.style.bottom = bottomHeight + pe.b + "px";
19130 }
19131
19132 splitterBounds = {
19133 top: (sidebarLayout ? 0 : topHeight + topSplitterThickness) + pe.t + "px",
19134 bottom: (sidebarLayout ? 0 : bottomHeight + bottomSplitterThickness) + pe.b + "px"
19135 };
19136
19137 if(leftSplitter){
19138 dojo.mixin(leftSplitter.style, splitterBounds);
19139 leftSplitter.style.left = leftWidth + pe.l + "px";
19140 }
19141
19142 if(rightSplitter){
19143 dojo.mixin(rightSplitter.style, splitterBounds);
19144 rightSplitter.style.right = rightWidth + pe.r + "px";
19145 }
19146
19147 dojo.mixin(centerStyle, {
19148 top: pe.t + topHeight + topSplitterThickness + "px",
19149 left: pe.l + leftWidth + leftSplitterThickness + "px",
19150 right: pe.r + rightWidth + rightSplitterThickness + "px",
19151 bottom: pe.b + bottomHeight + bottomSplitterThickness + "px"
19152 });
19153
19154 var bounds = {
19155 top: sidebarLayout ? pe.t + "px" : centerStyle.top,
19156 bottom: sidebarLayout ? pe.b + "px" : centerStyle.bottom
19157 };
19158 dojo.mixin(leftStyle, bounds);
19159 dojo.mixin(rightStyle, bounds);
19160 leftStyle.left = pe.l + "px"; rightStyle.right = pe.r + "px"; topStyle.top = pe.t + "px"; bottomStyle.bottom = pe.b + "px";
19161 if(sidebarLayout){
19162 topStyle.left = bottomStyle.left = leftWidth + leftSplitterThickness + pe.l + "px";
19163 topStyle.right = bottomStyle.right = rightWidth + rightSplitterThickness + pe.r + "px";
19164 }else{
19165 topStyle.left = bottomStyle.left = pe.l + "px";
19166 topStyle.right = bottomStyle.right = pe.r + "px";
19167 }
19168
19169 // More calculations about sizes of panes
19170 var containerHeight = this._borderBox.h - pe.t - pe.b,
19171 middleHeight = containerHeight - ( topHeight + topSplitterThickness + bottomHeight + bottomSplitterThickness),
19172 sidebarHeight = sidebarLayout ? containerHeight : middleHeight;
19173
19174 var containerWidth = this._borderBox.w - pe.l - pe.r,
19175 middleWidth = containerWidth - (leftWidth + leftSplitterThickness + rightWidth + rightSplitterThickness),
19176 sidebarWidth = sidebarLayout ? middleWidth : containerWidth;
19177
19178 // New margin-box size of each pane
19179 var dim = {
19180 top: { w: sidebarWidth, h: topHeight },
19181 bottom: { w: sidebarWidth, h: bottomHeight },
19182 left: { w: leftWidth, h: sidebarHeight },
19183 right: { w: rightWidth, h: sidebarHeight },
19184 center: { h: middleHeight, w: middleWidth }
19185 };
19186
19187 if(changedRegion){
19188 // Respond to splitter drag event by changing changedRegion's width or height
19189 var child = this["_" + changedRegion + "Widget"],
19190 mb = {};
19191 mb[ /top|bottom/.test(changedRegion) ? "h" : "w"] = changedRegionSize;
19192 child.resize ? child.resize(mb, dim[child.region]) : dojo.marginBox(child.domNode, mb);
19193 }
19194
19195 // Nodes in IE<8 don't respond to t/l/b/r, and TEXTAREA doesn't respond in any browser
19196 var janky = dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.some(this.getChildren(), function(child){
19197 return child.domNode.tagName == "TEXTAREA" || child.domNode.tagName == "INPUT";
19198 });
19199 if(janky){
19200 // Set the size of the children the old fashioned way, by setting
19201 // CSS width and height
19202
19203 var resizeWidget = function(widget, changes, result){
19204 if(widget){
19205 (widget.resize ? widget.resize(changes, result) : dojo.marginBox(widget.domNode, changes));
19206 }
19207 };
19208
19209 if(leftSplitter){ leftSplitter.style.height = sidebarHeight; }
19210 if(rightSplitter){ rightSplitter.style.height = sidebarHeight; }
19211 resizeWidget(this._leftWidget, {h: sidebarHeight}, dim.left);
19212 resizeWidget(this._rightWidget, {h: sidebarHeight}, dim.right);
19213
19214 if(topSplitter){ topSplitter.style.width = sidebarWidth; }
19215 if(bottomSplitter){ bottomSplitter.style.width = sidebarWidth; }
19216 resizeWidget(this._topWidget, {w: sidebarWidth}, dim.top);
19217 resizeWidget(this._bottomWidget, {w: sidebarWidth}, dim.bottom);
19218
19219 resizeWidget(this._centerWidget, dim.center);
19220 }else{
19221 // Calculate which panes need a notification that their size has been changed
19222 // (we've already set style.top/bottom/left/right on those other panes).
19223 var notifySides = !changedRegion || (/top|bottom/.test(changedRegion) && this.design != "sidebar"),
19224 notifyTopBottom = !changedRegion || (/left|right/.test(changedRegion) && this.design == "sidebar"),
19225 notifyList = {
19226 center: true,
19227 left: notifySides,
19228 right: notifySides,
19229 top: notifyTopBottom,
19230 bottom: notifyTopBottom
19231 };
19232
19233 // Send notification to those panes that have changed size
19234 dojo.forEach(this.getChildren(), function(child){
19235 if(child.resize && notifyList[child.region]){
19236 child.resize(null, dim[child.region]);
19237 }
19238 }, this);
19239 }
19240 },
19241
19242 destroy: function(){
19243 for(var region in this._splitters){
19244 var splitter = this._splitters[region];
19245 dijit.byNode(splitter).destroy();
19246 dojo.destroy(splitter);
19247 }
19248 delete this._splitters;
19249 delete this._splitterThickness;
19250 this.inherited(arguments);
19251 }
19252 });
19253
19254 // This argument can be specified for the children of a BorderContainer.
19255 // Since any widget can be specified as a LayoutContainer child, mix it
19256 // into the base widget class. (This is a hack, but it's effective.)
19257 dojo.extend(dijit._Widget, {
19258 // region: [const] String
19259 // Parameter for children of `dijit.layout.BorderContainer`.
19260 // Values: "top", "bottom", "leading", "trailing", "left", "right", "center".
19261 // See the `dijit.layout.BorderContainer` description for details.
19262 region: '',
19263
19264 // splitter: [const] Boolean
19265 // Parameter for child of `dijit.layout.BorderContainer` where region != "center".
19266 // If true, enables user to resize the widget by putting a draggable splitter between
19267 // this widget and the region=center widget.
19268 splitter: false,
19269
19270 // minSize: [const] Number
19271 // Parameter for children of `dijit.layout.BorderContainer`.
19272 // Specifies a minimum size (in pixels) for this widget when resized by a splitter.
19273 minSize: 0,
19274
19275 // maxSize: [const] Number
19276 // Parameter for children of `dijit.layout.BorderContainer`.
19277 // Specifies a maximum size (in pixels) for this widget when resized by a splitter.
19278 maxSize: Infinity
19279 });
19280
19281
19282
19283 dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
19284 {
19285 // summary:
19286 // A draggable spacer between two items in a `dijit.layout.BorderContainer`.
19287 // description:
19288 // This is instantiated by `dijit.layout.BorderContainer`. Users should not
19289 // create it directly.
19290 // tags:
19291 // private
19292
19293 /*=====
19294 // container: [const] dijit.layout.BorderContainer
19295 // Pointer to the parent BorderContainer
19296 container: null,
19297
19298 // child: [const] dijit.layout._LayoutWidget
19299 // Pointer to the pane associated with this splitter
19300 child: null,
19301
19302 // region: String
19303 // Region of pane associated with this splitter.
19304 // "top", "bottom", "left", "right".
19305 region: null,
19306 =====*/
19307
19308 // live: [const] Boolean
19309 // If true, the child's size changes and the child widget is redrawn as you drag the splitter;
19310 // otherwise, the size doesn't change until you drop the splitter (by mouse-up)
19311 live: true,
19312
19313 templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" waiRole="separator"><div class="dijitSplitterThumb"></div></div>',
19314
19315 postCreate: function(){
19316 this.inherited(arguments);
19317 this.horizontal = /top|bottom/.test(this.region);
19318 dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V"));
19319 // dojo.addClass(this.child.domNode, "dijitSplitterPane");
19320 // dojo.setSelectable(this.domNode, false); //TODO is this necessary?
19321
19322 this._factor = /top|left/.test(this.region) ? 1 : -1;
19323
19324 this._cookieName = this.container.id + "_" + this.region;
19325 if(this.container.persist){
19326 // restore old size
19327 var persistSize = dojo.cookie(this._cookieName);
19328 if(persistSize){
19329 this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize;
19330 }
19331 }
19332 },
19333
19334 _computeMaxSize: function(){
19335 // summary:
19336 // Compute the maximum size that my corresponding pane can be set to
19337
19338 var dim = this.horizontal ? 'h' : 'w',
19339 thickness = this.container._splitterThickness[this.region];
19340
19341 // Get DOMNode of opposite pane, if an opposite pane exists.
19342 // Ex: if I am the _Splitter for the left pane, then get the right pane.
19343 var flip = {left:'right', right:'left', top:'bottom', bottom:'top', leading:'trailing', trailing:'leading'},
19344 oppNode = this.container["_" + flip[this.region]];
19345
19346 // I can expand up to the edge of the opposite pane, or if there's no opposite pane, then to
19347 // edge of BorderContainer
19348 var available = dojo.contentBox(this.container.domNode)[dim] -
19349 (oppNode ? dojo.marginBox(oppNode)[dim] : 0) -
19350 20 - thickness * 2;
19351
19352 return Math.min(this.child.maxSize, available);
19353 },
19354
19355 _startDrag: function(e){
19356 if(!this.cover){
19357 this.cover = dojo.doc.createElement('div');
19358 dojo.addClass(this.cover, "dijitSplitterCover");
19359 dojo.place(this.cover, this.child.domNode, "after");
19360 }
19361 dojo.addClass(this.cover, "dijitSplitterCoverActive");
19362
19363 // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up.
19364 if(this.fake){ dojo.destroy(this.fake); }
19365 if(!(this._resize = this.live)){ //TODO: disable live for IE6?
19366 // create fake splitter to display at old position while we drag
19367 (this.fake = this.domNode.cloneNode(true)).removeAttribute("id");
19368 dojo.addClass(this.domNode, "dijitSplitterShadow");
19369 dojo.place(this.fake, this.domNode, "after");
19370 }
19371 dojo.addClass(this.domNode, "dijitSplitterActive");
19372 dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
19373 if(this.fake){
19374 dojo.removeClass(this.fake, "dijitSplitterHover");
19375 dojo.removeClass(this.fake, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover");
19376 }
19377
19378 //Performance: load data info local vars for onmousevent function closure
19379 var factor = this._factor,
19380 max = this._computeMaxSize(),
19381 min = this.child.minSize || 20,
19382 isHorizontal = this.horizontal,
19383 axis = isHorizontal ? "pageY" : "pageX",
19384 pageStart = e[axis],
19385 splitterStyle = this.domNode.style,
19386 dim = isHorizontal ? 'h' : 'w',
19387 childStart = dojo.marginBox(this.child.domNode)[dim],
19388 region = this.region,
19389 splitterStart = parseInt(this.domNode.style[region], 10),
19390 resize = this._resize,
19391 childNode = this.child.domNode,
19392 layoutFunc = dojo.hitch(this.container, this.container._layoutChildren),
19393 de = dojo.doc;
19394
19395 this._handlers = (this._handlers || []).concat([
19396 dojo.connect(de, "onmousemove", this._drag = function(e, forceResize){
19397 var delta = e[axis] - pageStart,
19398 childSize = factor * delta + childStart,
19399 boundChildSize = Math.max(Math.min(childSize, max), min);
19400
19401 if(resize || forceResize){
19402 layoutFunc(region, boundChildSize);
19403 }
19404 splitterStyle[region] = factor * delta + splitterStart + (boundChildSize - childSize) + "px";
19405 }),
19406 dojo.connect(de, "ondragstart", dojo.stopEvent),
19407 dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent),
19408 dojo.connect(de, "onmouseup", this, "_stopDrag")
19409 ]);
19410 dojo.stopEvent(e);
19411 },
19412
19413 _onMouse: function(e){
19414 var o = (e.type == "mouseover" || e.type == "mouseenter");
19415 dojo.toggleClass(this.domNode, "dijitSplitterHover", o);
19416 dojo.toggleClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o);
19417 },
19418
19419 _stopDrag: function(e){
19420 try{
19421 if(this.cover){
19422 dojo.removeClass(this.cover, "dijitSplitterCoverActive");
19423 }
19424 if(this.fake){ dojo.destroy(this.fake); }
19425 dojo.removeClass(this.domNode, "dijitSplitterActive");
19426 dojo.removeClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
19427 dojo.removeClass(this.domNode, "dijitSplitterShadow");
19428 this._drag(e); //TODO: redundant with onmousemove?
19429 this._drag(e, true);
19430 }finally{
19431 this._cleanupHandlers();
19432 delete this._drag;
19433 }
19434
19435 if(this.container.persist){
19436 dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365});
19437 }
19438 },
19439
19440 _cleanupHandlers: function(){
19441 dojo.forEach(this._handlers, dojo.disconnect);
19442 delete this._handlers;
19443 },
19444
19445 _onKeyPress: function(/*Event*/ e){
19446 // should we apply typematic to this?
19447 this._resize = true;
19448 var horizontal = this.horizontal;
19449 var tick = 1;
19450 var dk = dojo.keys;
19451 switch(e.charOrCode){
19452 case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW:
19453 tick *= -1;
19454 // break;
19455 case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW:
19456 break;
19457 default:
19458 // this.inherited(arguments);
19459 return;
19460 }
19461 var childSize = dojo.marginBox(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick;
19462 this.container._layoutChildren(this.region, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize));
19463 dojo.stopEvent(e);
19464 },
19465
19466 destroy: function(){
19467 this._cleanupHandlers();
19468 delete this.child;
19469 delete this.container;
19470 delete this.cover;
19471 delete this.fake;
19472 this.inherited(arguments);
19473 }
19474 });
19475
19476 dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated ],
19477 {
19478 // summary:
19479 // Just a spacer div to separate side pane from center pane.
19480 // Basically a trick to lookup the gutter/splitter width from the theme.
19481 // description:
19482 // Instantiated by `dijit.layout.BorderContainer`. Users should not
19483 // create directly.
19484 // tags:
19485 // private
19486
19487 templateString: '<div class="dijitGutter" waiRole="presentation"></div>',
19488
19489 postCreate: function(){
19490 this.horizontal = /top|bottom/.test(this.region);
19491 dojo.addClass(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V"));
19492 }
19493 });
19494
19495 }
19496
19497 if(!dojo._hasResource["dijit.layout._TabContainerBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
19498 dojo._hasResource["dijit.layout._TabContainerBase"] = true;
19499 dojo.provide("dijit.layout._TabContainerBase");
19500
19501
19502
19503
19504 dojo.declare("dijit.layout._TabContainerBase",
19505 [dijit.layout.StackContainer, dijit._Templated],
19506 {
19507 // summary:
19508 // Abstract base class for TabContainer. Must define _makeController() to instantiate
19509 // and return the widget that displays the tab labels
19510 // description:
19511 // A TabContainer is a container that has multiple panes, but shows only
19512 // one pane at a time. There are a set of tabs corresponding to each pane,
19513 // where each tab has the name (aka title) of the pane, and optionally a close button.
19514
19515 // tabPosition: String
19516 // Defines where tabs go relative to tab content.
19517 // "top", "bottom", "left-h", "right-h"
19518 tabPosition: "top",
19519
19520 baseClass: "dijitTabContainer",
19521
19522 // tabStrip: Boolean
19523 // Defines whether the tablist gets an extra class for layouting, putting a border/shading
19524 // around the set of tabs.
19525 tabStrip: false,
19526
19527 // nested: Boolean
19528 // If true, use styling for a TabContainer nested inside another TabContainer.
19529 // For tundra etc., makes tabs look like links, and hides the outer
19530 // border since the outer TabContainer already has a border.
19531 nested: false,
19532
19533 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"),
19534
19535 postMixInProperties: function(){
19536 // set class name according to tab position, ex: dijitTabContainerTop
19537 this.baseClass += this.tabPosition.charAt(0).toUpperCase() + this.tabPosition.substr(1).replace(/-.*/, "");
19538
19539 this.srcNodeRef && dojo.style(this.srcNodeRef, "visibility", "hidden");
19540
19541 this.inherited(arguments);
19542 },
19543
19544 postCreate: function(){
19545 this.inherited(arguments);
19546
19547 // Create the tab list that will have a tab (a.k.a. tab button) for each tab panel
19548 this.tablist = this._makeController(this.tablistNode);
19549
19550 if(!this.doLayout){ dojo.addClass(this.domNode, "dijitTabContainerNoLayout"); }
19551
19552 if(this.nested){
19553 /* workaround IE's lack of support for "a > b" selectors by
19554 * tagging each node in the template.
19555 */
19556 dojo.addClass(this.domNode, "dijitTabContainerNested");
19557 dojo.addClass(this.tablist.containerNode, "dijitTabContainerTabListNested");
19558 dojo.addClass(this.tablistSpacer, "dijitTabContainerSpacerNested");
19559 dojo.addClass(this.containerNode, "dijitTabPaneWrapperNested");
19560 }else{
19561 dojo.addClass(this.domNode, "tabStrip-" + (this.tabStrip ? "enabled" : "disabled"));
19562 }
19563 },
19564
19565 _setupChild: function(/*dijit._Widget*/ tab){
19566 // Overrides StackContainer._setupChild().
19567 dojo.addClass(tab.domNode, "dijitTabPane");
19568 this.inherited(arguments);
19569 },
19570
19571 startup: function(){
19572 if(this._started){ return; }
19573
19574 // wire up the tablist and its tabs
19575 this.tablist.startup();
19576
19577 this.inherited(arguments);
19578 },
19579
19580 layout: function(){
19581 // Overrides StackContainer.layout().
19582 // Configure the content pane to take up all the space except for where the tabs are
19583
19584 if(!this._contentBox || typeof(this._contentBox.l) == "undefined"){return;}
19585
19586 var sc = this.selectedChildWidget;
19587
19588 if(this.doLayout){
19589 // position and size the titles and the container node
19590 var titleAlign = this.tabPosition.replace(/-h/, "");
19591 this.tablist.layoutAlign = titleAlign;
19592 var children = [this.tablist, {
19593 domNode: this.tablistSpacer,
19594 layoutAlign: titleAlign
19595 }, {
19596 domNode: this.containerNode,
19597 layoutAlign: "client"
19598 }];
19599 dijit.layout.layoutChildren(this.domNode, this._contentBox, children);
19600
19601 // Compute size to make each of my children.
19602 // children[2] is the margin-box size of this.containerNode, set by layoutChildren() call above
19603 this._containerContentBox = dijit.layout.marginBox2contentBox(this.containerNode, children[2]);
19604
19605 if(sc && sc.resize){
19606 sc.resize(this._containerContentBox);
19607 }
19608 }else{
19609 // just layout the tab controller, so it can position left/right buttons etc.
19610 if(this.tablist.resize){
19611 this.tablist.resize({w: dojo.contentBox(this.domNode).w});
19612 }
19613
19614 // and call resize() on the selected pane just to tell it that it's been made visible
19615 if(sc && sc.resize){
19616 sc.resize();
19617 }
19618 }
19619 },
19620
19621 destroy: function(){
19622 if(this.tablist){
19623 this.tablist.destroy();
19624 }
19625 this.inherited(arguments);
19626 }
19627 });
19628
19629
19630 }
19631
19632 if(!dojo._hasResource["dijit.layout.TabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
19633 dojo._hasResource["dijit.layout.TabController"] = true;
19634 dojo.provide("dijit.layout.TabController");
19635
19636
19637
19638 // Menu is used for an accessible close button, would be nice to have a lighter-weight solution
19639
19640
19641
19642
19643
19644 dojo.declare("dijit.layout.TabController",
19645 dijit.layout.StackController,
19646 {
19647 // summary:
19648 // Set of tabs (the things with titles and a close button, that you click to show a tab panel).
19649 // Used internally by `dijit.layout.TabContainer`.
19650 // description:
19651 // Lets the user select the currently shown pane in a TabContainer or StackContainer.
19652 // TabController also monitors the TabContainer, and whenever a pane is
19653 // added or deleted updates itself accordingly.
19654 // tags:
19655 // private
19656
19657 templateString: "<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>",
19658
19659 // tabPosition: String
19660 // Defines where tabs go relative to the content.
19661 // "top", "bottom", "left-h", "right-h"
19662 tabPosition: "top",
19663
19664 // buttonWidget: String
19665 // The name of the tab widget to create to correspond to each page
19666 buttonWidget: "dijit.layout._TabButton",
19667
19668 _rectifyRtlTabList: function(){
19669 // summary:
19670 // 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
19671
19672 if(0 >= this.tabPosition.indexOf('-h')){ return; }
19673 if(!this.pane2button){ return; }
19674
19675 var maxWidth = 0;
19676 for(var pane in this.pane2button){
19677 var ow = this.pane2button[pane].innerDiv.scrollWidth;
19678 maxWidth = Math.max(maxWidth, ow);
19679 }
19680 //unify the length of all the tabs
19681 for(pane in this.pane2button){
19682 this.pane2button[pane].innerDiv.style.width = maxWidth + 'px';
19683 }
19684 }
19685 });
19686
19687 dojo.declare("dijit.layout._TabButton",
19688 dijit.layout._StackButton,
19689 {
19690 // summary:
19691 // A tab (the thing you click to select a pane).
19692 // description:
19693 // Contains the title of the pane, and optionally a close-button to destroy the pane.
19694 // This is an internal widget and should not be instantiated directly.
19695 // tags:
19696 // private
19697
19698 // baseClass: String
19699 // The CSS class applied to the domNode.
19700 baseClass: "dijitTab",
19701
19702 // Apply dijitTabCloseButtonHover when close button is hovered
19703 cssStateNodes: {
19704 closeNode: "dijitTabCloseButton"
19705 },
19706
19707 templateString: dojo.cache("dijit.layout", "templates/_TabButton.html", "<div waiRole=\"presentation\" dojoAttachPoint=\"titleNode\" dojoAttachEvent='onclick:onClick'>\n <div waiRole=\"presentation\" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'>\n <div waiRole=\"presentation\" class='dijitTabContent' dojoAttachPoint='tabContent'>\n \t<div waiRole=\"presentation\" dojoAttachPoint='focusNode'>\n\t\t <img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' />\n\t\t <span dojoAttachPoint='containerNode' class='tabLabel'></span>\n\t\t <span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" dojoAttachPoint='closeNode'\n\t\t \t\tdojoAttachEvent='onclick: onClickCloseButton' waiRole=\"presentation\">\n\t\t <span dojoAttachPoint='closeText' class='dijitTabCloseText'>x</span\n\t\t ></span>\n\t\t\t</div>\n </div>\n </div>\n</div>\n"),
19708
19709 // Override _FormWidget.scrollOnFocus.
19710 // Don't scroll the whole tab container into view when the button is focused.
19711 scrollOnFocus: false,
19712
19713 postMixInProperties: function(){
19714 // Override blank iconClass from Button to do tab height adjustment on IE6,
19715 // to make sure that tabs with and w/out close icons are same height
19716 if(!this.iconClass){
19717 this.iconClass = "dijitTabButtonIcon";
19718 }
19719 },
19720
19721 postCreate: function(){
19722 this.inherited(arguments);
19723 dojo.setSelectable(this.containerNode, false);
19724
19725 // If a custom icon class has not been set for the
19726 // tab icon, set its width to one pixel. This ensures
19727 // that the height styling of the tab is maintained,
19728 // as it is based on the height of the icon.
19729 // TODO: I still think we can just set dijitTabButtonIcon to 1px in CSS <Bill>
19730 if(this.iconNode.className == "dijitTabButtonIcon"){
19731 dojo.style(this.iconNode, "width", "1px");
19732 }
19733 },
19734
19735 startup: function(){
19736 this.inherited(arguments);
19737 var n = this.domNode;
19738
19739 // Required to give IE6 a kick, as it initially hides the
19740 // tabs until they are focused on.
19741 setTimeout(function(){
19742 n.className = n.className;
19743 }, 1);
19744 },
19745
19746 _setCloseButtonAttr: function(disp){
19747 this.closeButton = disp;
19748 dojo.toggleClass(this.innerDiv, "dijitClosable", disp);
19749 this.closeNode.style.display = disp ? "" : "none";
19750 if(disp){
19751 var _nlsResources = dojo.i18n.getLocalization("dijit", "common");
19752 if(this.closeNode){
19753 dojo.attr(this.closeNode,"title", _nlsResources.itemClose);
19754 }
19755 // add context menu onto title button
19756 var _nlsResources = dojo.i18n.getLocalization("dijit", "common");
19757 this._closeMenu = new dijit.Menu({
19758 id: this.id+"_Menu",
19759 dir: this.dir,
19760 lang: this.lang,
19761 targetNodeIds: [this.domNode]
19762 });
19763
19764 this._closeMenu.addChild(new dijit.MenuItem({
19765 label: _nlsResources.itemClose,
19766 dir: this.dir,
19767 lang: this.lang,
19768 onClick: dojo.hitch(this, "onClickCloseButton")
19769 }));
19770 }else{
19771 if(this._closeMenu){
19772 this._closeMenu.destroyRecursive();
19773 delete this._closeMenu;
19774 }
19775 }
19776 },
19777 _setLabelAttr: function(/*String*/ content){
19778 // summary:
19779 // Hook for attr('label', ...) to work.
19780 // description:
19781 // takes an HTML string.
19782 // Inherited ToggleButton implementation will Set the label (text) of the button;
19783 // Need to set the alt attribute of icon on tab buttons if no label displayed
19784 this.inherited(arguments);
19785 if(this.showLabel == false && !this.params.title){
19786 this.iconNode.alt = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || '');
19787 }
19788 },
19789
19790 destroy: function(){
19791 if(this._closeMenu){
19792 this._closeMenu.destroyRecursive();
19793 delete this._closeMenu;
19794 }
19795 this.inherited(arguments);
19796 }
19797 });
19798
19799 }
19800
19801 if(!dojo._hasResource["dijit.layout.ScrollingTabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
19802 dojo._hasResource["dijit.layout.ScrollingTabController"] = true;
19803 dojo.provide("dijit.layout.ScrollingTabController");
19804
19805
19806
19807
19808 dojo.declare("dijit.layout.ScrollingTabController",
19809 dijit.layout.TabController,
19810 {
19811 // summary:
19812 // Set of tabs with left/right arrow keys and a menu to switch between tabs not
19813 // all fitting on a single row.
19814 // Works only for horizontal tabs (either above or below the content, not to the left
19815 // or right).
19816 // tags:
19817 // private
19818
19819 templateString: dojo.cache("dijit.layout", "templates/ScrollingTabController.html", "<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\" iconClass=\"dijitTabStripMenuIcon\"\n\t\t\tdojoAttachPoint=\"_menuBtn\" showLabel=false>&#9660;</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\" iconClass=\"dijitTabStripSlideLeftIcon\"\n\t\t\tdojoAttachPoint=\"_leftBtn\" dojoAttachEvent=\"onClick: doSlideLeft\" showLabel=false>&#9664;</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\" iconClass=\"dijitTabStripSlideRightIcon\"\n\t\t\tdojoAttachPoint=\"_rightBtn\" dojoAttachEvent=\"onClick: doSlideRight\" showLabel=false>&#9654;</div>\n\t<div class='dijitTabListWrapper' dojoAttachPoint='tablistWrapper'>\n\t\t<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'\n\t\t\t\tdojoAttachPoint='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>\n"),
19820
19821 // useMenu:[const] Boolean
19822 // True if a menu should be used to select tabs when they are too
19823 // wide to fit the TabContainer, false otherwise.
19824 useMenu: true,
19825
19826 // useSlider: [const] Boolean
19827 // True if a slider should be used to select tabs when they are too
19828 // wide to fit the TabContainer, false otherwise.
19829 useSlider: true,
19830
19831 // tabStripClass: String
19832 // The css class to apply to the tab strip, if it is visible.
19833 tabStripClass: "",
19834
19835 widgetsInTemplate: true,
19836
19837 // _minScroll: Number
19838 // The distance in pixels from the edge of the tab strip which,
19839 // if a scroll animation is less than, forces the scroll to
19840 // go all the way to the left/right.
19841 _minScroll: 5,
19842
19843 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
19844 "class": "containerNode"
19845 }),
19846
19847 postCreate: function(){
19848 this.inherited(arguments);
19849 var n = this.domNode;
19850
19851 this.scrollNode = this.tablistWrapper;
19852 this._initButtons();
19853
19854 if(!this.tabStripClass){
19855 this.tabStripClass = "dijitTabContainer" +
19856 this.tabPosition.charAt(0).toUpperCase() +
19857 this.tabPosition.substr(1).replace(/-.*/, "") +
19858 "None";
19859 dojo.addClass(n, "tabStrip-disabled")
19860 }
19861
19862 dojo.addClass(this.tablistWrapper, this.tabStripClass);
19863 },
19864
19865 onStartup: function(){
19866 this.inherited(arguments);
19867
19868 // Do not show the TabController until the related
19869 // StackController has added it's children. This gives
19870 // a less visually jumpy instantiation.
19871 dojo.style(this.domNode, "visibility", "visible");
19872 this._postStartup = true;
19873 },
19874
19875 onAddChild: function(page, insertIndex){
19876 this.inherited(arguments);
19877 var menuItem;
19878 if(this.useMenu){
19879 var containerId = this.containerId;
19880 menuItem = new dijit.MenuItem({
19881 id: page.id + "_stcMi",
19882 label: page.title,
19883 dir: page.dir,
19884 lang: page.lang,
19885 onClick: dojo.hitch(this, function(){
19886 var container = dijit.byId(containerId);
19887 container.selectChild(page);
19888 })
19889 });
19890 this._menuChildren[page.id] = menuItem;
19891 this._menu.addChild(menuItem, insertIndex);
19892 }
19893
19894 // update the menuItem label when the button label is updated
19895 this.pane2handles[page.id].push(
19896 this.connect(this.pane2button[page.id], "set", function(name, value){
19897 if(this._postStartup){
19898 if(name == "label"){
19899 if(menuItem){
19900 menuItem.set(name, value);
19901 }
19902
19903 // The changed label will have changed the width of the
19904 // buttons, so do a resize
19905 if(this._dim){
19906 this.resize(this._dim);
19907 }
19908 }
19909 }
19910 })
19911 );
19912
19913 // Increment the width of the wrapper when a tab is added
19914 // This makes sure that the buttons never wrap.
19915 // The value 200 is chosen as it should be bigger than most
19916 // Tab button widths.
19917 dojo.style(this.containerNode, "width",
19918 (dojo.style(this.containerNode, "width") + 200) + "px");
19919 },
19920
19921 onRemoveChild: function(page, insertIndex){
19922 // null out _selectedTab because we are about to delete that dom node
19923 var button = this.pane2button[page.id];
19924 if(this._selectedTab === button.domNode){
19925 this._selectedTab = null;
19926 }
19927
19928 // delete menu entry corresponding to pane that was removed from TabContainer
19929 if(this.useMenu && page && page.id && this._menuChildren[page.id]){
19930 this._menu.removeChild(this._menuChildren[page.id]);
19931 this._menuChildren[page.id].destroy();
19932 delete this._menuChildren[page.id];
19933 }
19934
19935 this.inherited(arguments);
19936 },
19937
19938 _initButtons: function(){
19939 // summary:
19940 // Creates the buttons used to scroll to view tabs that
19941 // may not be visible if the TabContainer is too narrow.
19942 this._menuChildren = {};
19943
19944 // Make a list of the buttons to display when the tab labels become
19945 // wider than the TabContainer, and hide the other buttons.
19946 // Also gets the total width of the displayed buttons.
19947 this._btnWidth = 0;
19948 this._buttons = dojo.query("> .tabStripButton", this.domNode).filter(function(btn){
19949 if((this.useMenu && btn == this._menuBtn.domNode) ||
19950 (this.useSlider && (btn == this._rightBtn.domNode || btn == this._leftBtn.domNode))){
19951 this._btnWidth += dojo.marginBox(btn).w;
19952 return true;
19953 }else{
19954 dojo.style(btn, "display", "none");
19955 return false;
19956 }
19957 }, this);
19958
19959 if(this.useMenu){
19960 // Create the menu that is used to select tabs.
19961 this._menu = new dijit.Menu({
19962 id: this.id + "_menu",
19963 dir: this.dir,
19964 lang: this.lang,
19965 targetNodeIds: [this._menuBtn.domNode],
19966 leftClickToOpen: true,
19967 refocus: false // selecting a menu item sets focus to a TabButton
19968 });
19969 this._supportingWidgets.push(this._menu);
19970 }
19971 },
19972
19973 _getTabsWidth: function(){
19974 var children = this.getChildren();
19975 if(children.length){
19976 var leftTab = children[this.isLeftToRight() ? 0 : children.length - 1].domNode,
19977 rightTab = children[this.isLeftToRight() ? children.length - 1 : 0].domNode;
19978 return rightTab.offsetLeft + dojo.style(rightTab, "width") - leftTab.offsetLeft;
19979 }else{
19980 return 0;
19981 }
19982 },
19983
19984 _enableBtn: function(width){
19985 // summary:
19986 // Determines if the tabs are wider than the width of the TabContainer, and
19987 // thus that we need to display left/right/menu navigation buttons.
19988 var tabsWidth = this._getTabsWidth();
19989 width = width || dojo.style(this.scrollNode, "width");
19990 return tabsWidth > 0 && width < tabsWidth;
19991 },
19992
19993 resize: function(dim){
19994 // summary:
19995 // Hides or displays the buttons used to scroll the tab list and launch the menu
19996 // that selects tabs.
19997
19998 if(this.domNode.offsetWidth == 0){
19999 return;
20000 }
20001
20002 // Save the dimensions to be used when a child is renamed.
20003 this._dim = dim;
20004
20005 // Set my height to be my natural height (tall enough for one row of tab labels),
20006 // and my content-box width based on margin-box width specified in dim parameter.
20007 // But first reset scrollNode.height in case it was set by layoutChildren() call
20008 // in a previous run of this method.
20009 this.scrollNode.style.height = "auto";
20010 this._contentBox = dijit.layout.marginBox2contentBox(this.domNode, {h: 0, w: dim.w});
20011 this._contentBox.h = this.scrollNode.offsetHeight;
20012 dojo.contentBox(this.domNode, this._contentBox);
20013
20014 // Show/hide the left/right/menu navigation buttons depending on whether or not they
20015 // are needed.
20016 var enable = this._enableBtn(this._contentBox.w);
20017 this._buttons.style("display", enable ? "" : "none");
20018
20019 // Position and size the navigation buttons and the tablist
20020 this._leftBtn.layoutAlign = "left";
20021 this._rightBtn.layoutAlign = "right";
20022 this._menuBtn.layoutAlign = this.isLeftToRight() ? "right" : "left";
20023 dijit.layout.layoutChildren(this.domNode, this._contentBox,
20024 [this._menuBtn, this._leftBtn, this._rightBtn, {domNode: this.scrollNode, layoutAlign: "client"}]);
20025
20026 // set proper scroll so that selected tab is visible
20027 if(this._selectedTab){
20028 if(this._anim && this._anim.status() == "playing"){
20029 this._anim.stop();
20030 }
20031 var w = this.scrollNode,
20032 sl = this._convertToScrollLeft(this._getScrollForSelectedTab());
20033 w.scrollLeft = sl;
20034 }
20035
20036 // Enable/disabled left right buttons depending on whether or not user can scroll to left or right
20037 this._setButtonClass(this._getScroll());
20038
20039 this._postResize = true;
20040 },
20041
20042 _getScroll: function(){
20043 // summary:
20044 // Returns the current scroll of the tabs where 0 means
20045 // "scrolled all the way to the left" and some positive number, based on #
20046 // of pixels of possible scroll (ex: 1000) means "scrolled all the way to the right"
20047 var sl = (this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit) ? this.scrollNode.scrollLeft :
20048 dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width")
20049 + (dojo.isIE == 8 ? -1 : 1) * this.scrollNode.scrollLeft;
20050 return sl;
20051 },
20052
20053 _convertToScrollLeft: function(val){
20054 // summary:
20055 // Given a scroll value where 0 means "scrolled all the way to the left"
20056 // and some positive number, based on # of pixels of possible scroll (ex: 1000)
20057 // means "scrolled all the way to the right", return value to set this.scrollNode.scrollLeft
20058 // to achieve that scroll.
20059 //
20060 // This method is to adjust for RTL funniness in various browsers and versions.
20061 if(this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit){
20062 return val;
20063 }else{
20064 var maxScroll = dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width");
20065 return (dojo.isIE == 8 ? -1 : 1) * (val - maxScroll);
20066 }
20067 },
20068
20069 onSelectChild: function(/*dijit._Widget*/ page){
20070 // summary:
20071 // Smoothly scrolls to a tab when it is selected.
20072
20073 var tab = this.pane2button[page.id];
20074 if(!tab || !page){return;}
20075
20076 // Scroll to the selected tab, except on startup, when scrolling is handled in resize()
20077 var node = tab.domNode;
20078 if(this._postResize && node != this._selectedTab){
20079 this._selectedTab = node;
20080
20081 var sl = this._getScroll();
20082
20083 if(sl > node.offsetLeft ||
20084 sl + dojo.style(this.scrollNode, "width") <
20085 node.offsetLeft + dojo.style(node, "width")){
20086 this.createSmoothScroll().play();
20087 }
20088 }
20089
20090 this.inherited(arguments);
20091 },
20092
20093 _getScrollBounds: function(){
20094 // summary:
20095 // Returns the minimum and maximum scroll setting to show the leftmost and rightmost
20096 // tabs (respectively)
20097 var children = this.getChildren(),
20098 scrollNodeWidth = dojo.style(this.scrollNode, "width"), // about 500px
20099 containerWidth = dojo.style(this.containerNode, "width"), // 50,000px
20100 maxPossibleScroll = containerWidth - scrollNodeWidth, // scrolling until right edge of containerNode visible
20101 tabsWidth = this._getTabsWidth();
20102
20103 if(children.length && tabsWidth > scrollNodeWidth){
20104 // Scrolling should happen
20105 return {
20106 min: this.isLeftToRight() ? 0 : children[children.length-1].domNode.offsetLeft,
20107 max: this.isLeftToRight() ?
20108 (children[children.length-1].domNode.offsetLeft + dojo.style(children[children.length-1].domNode, "width")) - scrollNodeWidth :
20109 maxPossibleScroll
20110 };
20111 }else{
20112 // No scrolling needed, all tabs visible, we stay either scrolled to far left or far right (depending on dir)
20113 var onlyScrollPosition = this.isLeftToRight() ? 0 : maxPossibleScroll;
20114 return {
20115 min: onlyScrollPosition,
20116 max: onlyScrollPosition
20117 };
20118 }
20119 },
20120
20121 _getScrollForSelectedTab: function(){
20122 // summary:
20123 // Returns the scroll value setting so that the selected tab
20124 // will appear in the center
20125 var w = this.scrollNode,
20126 n = this._selectedTab,
20127 scrollNodeWidth = dojo.style(this.scrollNode, "width"),
20128 scrollBounds = this._getScrollBounds();
20129
20130 // TODO: scroll minimal amount (to either right or left) so that
20131 // selected tab is fully visible, and just return if it's already visible?
20132 var pos = (n.offsetLeft + dojo.style(n, "width")/2) - scrollNodeWidth/2;
20133 pos = Math.min(Math.max(pos, scrollBounds.min), scrollBounds.max);
20134
20135 // TODO:
20136 // If scrolling close to the left side or right side, scroll
20137 // all the way to the left or right. See this._minScroll.
20138 // (But need to make sure that doesn't scroll the tab out of view...)
20139 return pos;
20140 },
20141
20142 createSmoothScroll : function(x){
20143 // summary:
20144 // Creates a dojo._Animation object that smoothly scrolls the tab list
20145 // either to a fixed horizontal pixel value, or to the selected tab.
20146 // description:
20147 // If an number argument is passed to the function, that horizontal
20148 // pixel position is scrolled to. Otherwise the currently selected
20149 // tab is scrolled to.
20150 // x: Integer?
20151 // An optional pixel value to scroll to, indicating distance from left.
20152
20153 // Calculate position to scroll to
20154 if(arguments.length > 0){
20155 // position specified by caller, just make sure it's within bounds
20156 var scrollBounds = this._getScrollBounds();
20157 x = Math.min(Math.max(x, scrollBounds.min), scrollBounds.max);
20158 }else{
20159 // scroll to center the current tab
20160 x = this._getScrollForSelectedTab();
20161 }
20162
20163 if(this._anim && this._anim.status() == "playing"){
20164 this._anim.stop();
20165 }
20166
20167 var self = this,
20168 w = this.scrollNode,
20169 anim = new dojo._Animation({
20170 beforeBegin: function(){
20171 if(this.curve){ delete this.curve; }
20172 var oldS = w.scrollLeft,
20173 newS = self._convertToScrollLeft(x);
20174 anim.curve = new dojo._Line(oldS, newS);
20175 },
20176 onAnimate: function(val){
20177 w.scrollLeft = val;
20178 }
20179 });
20180 this._anim = anim;
20181
20182 // Disable/enable left/right buttons according to new scroll position
20183 this._setButtonClass(x);
20184
20185 return anim; // dojo._Animation
20186 },
20187
20188 _getBtnNode: function(e){
20189 // summary:
20190 // Gets a button DOM node from a mouse click event.
20191 // e:
20192 // The mouse click event.
20193 var n = e.target;
20194 while(n && !dojo.hasClass(n, "tabStripButton")){
20195 n = n.parentNode;
20196 }
20197 return n;
20198 },
20199
20200 doSlideRight: function(e){
20201 // summary:
20202 // Scrolls the menu to the right.
20203 // e:
20204 // The mouse click event.
20205 this.doSlide(1, this._getBtnNode(e));
20206 },
20207
20208 doSlideLeft: function(e){
20209 // summary:
20210 // Scrolls the menu to the left.
20211 // e:
20212 // The mouse click event.
20213 this.doSlide(-1,this._getBtnNode(e));
20214 },
20215
20216 doSlide: function(direction, node){
20217 // summary:
20218 // Scrolls the tab list to the left or right by 75% of the widget width.
20219 // direction:
20220 // If the direction is 1, the widget scrolls to the right, if it is
20221 // -1, it scrolls to the left.
20222
20223 if(node && dojo.hasClass(node, "dijitTabDisabled")){return;}
20224
20225 var sWidth = dojo.style(this.scrollNode, "width");
20226 var d = (sWidth * 0.75) * direction;
20227
20228 var to = this._getScroll() + d;
20229
20230 this._setButtonClass(to);
20231
20232 this.createSmoothScroll(to).play();
20233 },
20234
20235 _setButtonClass: function(scroll){
20236 // summary:
20237 // Disables the left scroll button if the tabs are scrolled all the way to the left,
20238 // or the right scroll button in the opposite case.
20239 // scroll: Integer
20240 // amount of horizontal scroll
20241
20242 var scrollBounds = this._getScrollBounds();
20243 this._leftBtn.set("disabled", scroll <= scrollBounds.min);
20244 this._rightBtn.set("disabled", scroll >= scrollBounds.max);
20245 }
20246 });
20247
20248 dojo.declare("dijit.layout._ScrollingTabControllerButton",
20249 dijit.form.Button,
20250 {
20251 baseClass: "dijitTab tabStripButton",
20252
20253 templateString: dojo.cache("dijit.layout", "templates/_ScrollingTabControllerButton.html", "<div dojoAttachEvent=\"onclick:_onButtonClick\">\n\t<div waiRole=\"presentation\" class=\"dijitTabInnerDiv\" dojoattachpoint=\"innerDiv,focusNode\">\n\t\t<div waiRole=\"presentation\" class=\"dijitTabContent dijitButtonContents\" dojoattachpoint=\"tabContent\">\n\t\t\t<img waiRole=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t\t<span dojoAttachPoint=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>\n"),
20254
20255 // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be
20256 // able to tab to the left/right/menu buttons
20257 tabIndex: "-1"
20258 }
20259 );
20260
20261 }
20262
20263 if(!dojo._hasResource["dijit.layout.TabContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
20264 dojo._hasResource["dijit.layout.TabContainer"] = true;
20265 dojo.provide("dijit.layout.TabContainer");
20266
20267
20268
20269
20270
20271 dojo.declare("dijit.layout.TabContainer",
20272 dijit.layout._TabContainerBase,
20273 {
20274 // summary:
20275 // A Container with tabs to select each child (only one of which is displayed at a time).
20276 // description:
20277 // A TabContainer is a container that has multiple panes, but shows only
20278 // one pane at a time. There are a set of tabs corresponding to each pane,
20279 // where each tab has the name (aka title) of the pane, and optionally a close button.
20280
20281 // useMenu: [const] Boolean
20282 // True if a menu should be used to select tabs when they are too
20283 // wide to fit the TabContainer, false otherwise.
20284 useMenu: true,
20285
20286 // useSlider: [const] Boolean
20287 // True if a slider should be used to select tabs when they are too
20288 // wide to fit the TabContainer, false otherwise.
20289 useSlider: true,
20290
20291 // controllerWidget: String
20292 // An optional parameter to override the widget used to display the tab labels
20293 controllerWidget: "",
20294
20295 _makeController: function(/*DomNode*/ srcNode){
20296 // summary:
20297 // Instantiate tablist controller widget and return reference to it.
20298 // Callback from _TabContainerBase.postCreate().
20299 // tags:
20300 // protected extension
20301
20302 var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"),
20303 TabController = dojo.getObject(this.controllerWidget);
20304
20305 return new TabController({
20306 id: this.id + "_tablist",
20307 dir: this.dir,
20308 lang: this.lang,
20309 tabPosition: this.tabPosition,
20310 doLayout: this.doLayout,
20311 containerId: this.id,
20312 "class": cls,
20313 nested: this.nested,
20314 useMenu: this.useMenu,
20315 useSlider: this.useSlider,
20316 tabStripClass: this.tabStrip ? this.baseClass + (this.tabStrip ? "":"No") + "Strip": null
20317 }, srcNode);
20318 },
20319
20320 postMixInProperties: function(){
20321 this.inherited(arguments);
20322
20323 // Scrolling controller only works for horizontal non-nested tabs
20324 if(!this.controllerWidget){
20325 this.controllerWidget = (this.tabPosition == "top" || this.tabPosition == "bottom") && !this.nested ?
20326 "dijit.layout.ScrollingTabController" : "dijit.layout.TabController";
20327 }
20328 }
20329 });
20330
20331
20332 }
20333
20334 if(!dojo._hasResource["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
20335 dojo._hasResource["dojo.number"] = true;
20336 dojo.provide("dojo.number");
20337
20338
20339
20340
20341
20342
20343
20344 /*=====
20345 dojo.number = {
20346 // summary: localized formatting and parsing routines for Number
20347 }
20348
20349 dojo.number.__FormatOptions = function(){
20350 // pattern: String?
20351 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
20352 // with this string. Default value is based on locale. Overriding this property will defeat
20353 // localization. Literal characters in patterns are not supported.
20354 // type: String?
20355 // choose a format type based on the locale from the following:
20356 // decimal, scientific (not yet supported), percent, currency. decimal by default.
20357 // places: Number?
20358 // fixed number of decimal places to show. This overrides any
20359 // information in the provided pattern.
20360 // round: Number?
20361 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
20362 // means do not round.
20363 // locale: String?
20364 // override the locale used to determine formatting rules
20365 // fractional: Boolean?
20366 // If false, show no decimal places, overriding places and pattern settings.
20367 this.pattern = pattern;
20368 this.type = type;
20369 this.places = places;
20370 this.round = round;
20371 this.locale = locale;
20372 this.fractional = fractional;
20373 }
20374 =====*/
20375
20376 dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){
20377 // summary:
20378 // Format a Number as a String, using locale-specific settings
20379 // description:
20380 // Create a string from a Number using a known localized pattern.
20381 // Formatting patterns appropriate to the locale are chosen from the
20382 // [Common Locale Data Repository](http://unicode.org/cldr) as well as the appropriate symbols and
20383 // delimiters.
20384 // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null.
20385 // value:
20386 // the number to be formatted
20387
20388 options = dojo.mixin({}, options || {});
20389 var locale = dojo.i18n.normalizeLocale(options.locale),
20390 bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale);
20391 options.customs = bundle;
20392 var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
20393 if(isNaN(value) || Math.abs(value) == Infinity){ return null; } // null
20394 return dojo.number._applyPattern(value, pattern, options); // String
20395 };
20396
20397 //dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
20398 dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough
20399
20400 dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){
20401 // summary:
20402 // Apply pattern to format value as a string using options. Gives no
20403 // consideration to local customs.
20404 // value:
20405 // the number to be formatted.
20406 // pattern:
20407 // a pattern string as described by
20408 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
20409 // options: dojo.number.__FormatOptions?
20410 // _applyPattern is usually called via `dojo.number.format()` which
20411 // populates an extra property in the options parameter, "customs".
20412 // The customs object specifies group and decimal parameters if set.
20413
20414 //TODO: support escapes
20415 options = options || {};
20416 var group = options.customs.group,
20417 decimal = options.customs.decimal,
20418 patternList = pattern.split(';'),
20419 positivePattern = patternList[0];
20420 pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern);
20421
20422 //TODO: only test against unescaped
20423 if(pattern.indexOf('%') != -1){
20424 value *= 100;
20425 }else if(pattern.indexOf('\u2030') != -1){
20426 value *= 1000; // per mille
20427 }else if(pattern.indexOf('\u00a4') != -1){
20428 group = options.customs.currencyGroup || group;//mixins instead?
20429 decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead?
20430 pattern = pattern.replace(/\u00a4{1,3}/, function(match){
20431 var prop = ["symbol", "currency", "displayName"][match.length-1];
20432 return options[prop] || options.currency || "";
20433 });
20434 }else if(pattern.indexOf('E') != -1){
20435 throw new Error("exponential notation not supported");
20436 }
20437
20438 //TODO: support @ sig figs?
20439 var numberPatternRE = dojo.number._numberPatternRE;
20440 var numberPattern = positivePattern.match(numberPatternRE);
20441 if(!numberPattern){
20442 throw new Error("unable to find a number expression in pattern: "+pattern);
20443 }
20444 if(options.fractional === false){ options.places = 0; }
20445 return pattern.replace(numberPatternRE,
20446 dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round}));
20447 }
20448
20449 dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){
20450 // summary:
20451 // Rounds to the nearest value with the given number of decimal places, away from zero
20452 // description:
20453 // Rounds to the nearest value with the given number of decimal places, away from zero if equal.
20454 // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by
20455 // fractional increments also, such as the nearest quarter.
20456 // NOTE: Subject to floating point errors. See dojox.math.round for experimental workaround.
20457 // value:
20458 // The number to round
20459 // places:
20460 // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding.
20461 // Must be non-negative.
20462 // increment:
20463 // Rounds next place to nearest value of increment/10. 10 by default.
20464 // example:
20465 // >>> dojo.number.round(-0.5)
20466 // -1
20467 // >>> dojo.number.round(162.295, 2)
20468 // 162.29 // note floating point error. Should be 162.3
20469 // >>> dojo.number.round(10.71, 0, 2.5)
20470 // 10.75
20471 var factor = 10 / (increment || 10);
20472 return (factor * +value).toFixed(places) / factor; // Number
20473 }
20474
20475 if((0.9).toFixed() == 0){
20476 // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit
20477 // is just after the rounding place and is >=5
20478 (function(){
20479 var round = dojo.number.round;
20480 dojo.number.round = function(v, p, m){
20481 var d = Math.pow(10, -p || 0), a = Math.abs(v);
20482 if(!v || a >= d || a * Math.pow(10, p + 1) < 5){
20483 d = 0;
20484 }
20485 return round(v, p, m) + (v > 0 ? d : -d);
20486 }
20487 })();
20488 }
20489
20490 /*=====
20491 dojo.number.__FormatAbsoluteOptions = function(){
20492 // decimal: String?
20493 // the decimal separator
20494 // group: String?
20495 // the group separator
20496 // places: Number?|String?
20497 // number of decimal places. the range "n,m" will format to m places.
20498 // round: Number?
20499 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
20500 // means don't round.
20501 this.decimal = decimal;
20502 this.group = group;
20503 this.places = places;
20504 this.round = round;
20505 }
20506 =====*/
20507
20508 dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){
20509 // summary:
20510 // Apply numeric pattern to absolute value using options. Gives no
20511 // consideration to local customs.
20512 // value:
20513 // the number to be formatted, ignores sign
20514 // pattern:
20515 // the number portion of a pattern (e.g. `#,##0.00`)
20516 options = options || {};
20517 if(options.places === true){options.places=0;}
20518 if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit
20519
20520 var patternParts = pattern.split("."),
20521 comma = typeof options.places == "string" && options.places.indexOf(","),
20522 maxPlaces = options.places;
20523 if(comma){
20524 maxPlaces = options.places.substring(comma + 1);
20525 }else if(!(maxPlaces >= 0)){
20526 maxPlaces = (patternParts[1] || []).length;
20527 }
20528 if(!(options.round < 0)){
20529 value = dojo.number.round(value, maxPlaces, options.round);
20530 }
20531
20532 var valueParts = String(Math.abs(value)).split("."),
20533 fractional = valueParts[1] || "";
20534 if(patternParts[1] || options.places){
20535 if(comma){
20536 options.places = options.places.substring(0, comma);
20537 }
20538 // Pad fractional with trailing zeros
20539 var pad = options.places !== undefined ? options.places : (patternParts[1] && patternParts[1].lastIndexOf("0") + 1);
20540 if(pad > fractional.length){
20541 valueParts[1] = dojo.string.pad(fractional, pad, '0', true);
20542 }
20543
20544 // Truncate fractional
20545 if(maxPlaces < fractional.length){
20546 valueParts[1] = fractional.substr(0, maxPlaces);
20547 }
20548 }else{
20549 if(valueParts[1]){ valueParts.pop(); }
20550 }
20551
20552 // Pad whole with leading zeros
20553 var patternDigits = patternParts[0].replace(',', '');
20554 pad = patternDigits.indexOf("0");
20555 if(pad != -1){
20556 pad = patternDigits.length - pad;
20557 if(pad > valueParts[0].length){
20558 valueParts[0] = dojo.string.pad(valueParts[0], pad);
20559 }
20560
20561 // Truncate whole
20562 if(patternDigits.indexOf("#") == -1){
20563 valueParts[0] = valueParts[0].substr(valueParts[0].length - pad);
20564 }
20565 }
20566
20567 // Add group separators
20568 var index = patternParts[0].lastIndexOf(','),
20569 groupSize, groupSize2;
20570 if(index != -1){
20571 groupSize = patternParts[0].length - index - 1;
20572 var remainder = patternParts[0].substr(0, index);
20573 index = remainder.lastIndexOf(',');
20574 if(index != -1){
20575 groupSize2 = remainder.length - index - 1;
20576 }
20577 }
20578 var pieces = [];
20579 for(var whole = valueParts[0]; whole;){
20580 var off = whole.length - groupSize;
20581 pieces.push((off > 0) ? whole.substr(off) : whole);
20582 whole = (off > 0) ? whole.slice(0, off) : "";
20583 if(groupSize2){
20584 groupSize = groupSize2;
20585 delete groupSize2;
20586 }
20587 }
20588 valueParts[0] = pieces.reverse().join(options.group || ",");
20589
20590 return valueParts.join(options.decimal || ".");
20591 };
20592
20593 /*=====
20594 dojo.number.__RegexpOptions = function(){
20595 // pattern: String?
20596 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
20597 // with this string. Default value is based on locale. Overriding this property will defeat
20598 // localization.
20599 // type: String?
20600 // choose a format type based on the locale from the following:
20601 // decimal, scientific (not yet supported), percent, currency. decimal by default.
20602 // locale: String?
20603 // override the locale used to determine formatting rules
20604 // strict: Boolean?
20605 // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
20606 // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
20607 // places: Number|String?
20608 // number of decimal places to accept: Infinity, a positive number, or
20609 // a range "n,m". Defined by pattern or Infinity if pattern not provided.
20610 this.pattern = pattern;
20611 this.type = type;
20612 this.locale = locale;
20613 this.strict = strict;
20614 this.places = places;
20615 }
20616 =====*/
20617 dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){
20618 // summary:
20619 // Builds the regular needed to parse a number
20620 // description:
20621 // Returns regular expression with positive and negative match, group
20622 // and decimal separators
20623 return dojo.number._parseInfo(options).regexp; // String
20624 }
20625
20626 dojo.number._parseInfo = function(/*Object?*/options){
20627 options = options || {};
20628 var locale = dojo.i18n.normalizeLocale(options.locale),
20629 bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale),
20630 pattern = options.pattern || bundle[(options.type || "decimal") + "Format"],
20631 //TODO: memoize?
20632 group = bundle.group,
20633 decimal = bundle.decimal,
20634 factor = 1;
20635
20636 if(pattern.indexOf('%') != -1){
20637 factor /= 100;
20638 }else if(pattern.indexOf('\u2030') != -1){
20639 factor /= 1000; // per mille
20640 }else{
20641 var isCurrency = pattern.indexOf('\u00a4') != -1;
20642 if(isCurrency){
20643 group = bundle.currencyGroup || group;
20644 decimal = bundle.currencyDecimal || decimal;
20645 }
20646 }
20647
20648 //TODO: handle quoted escapes
20649 var patternList = pattern.split(';');
20650 if(patternList.length == 1){
20651 patternList.push("-" + patternList[0]);
20652 }
20653
20654 var re = dojo.regexp.buildGroupRE(patternList, function(pattern){
20655 pattern = "(?:"+dojo.regexp.escapeString(pattern, '.')+")";
20656 return pattern.replace(dojo.number._numberPatternRE, function(format){
20657 var flags = {
20658 signed: false,
20659 separator: options.strict ? group : [group,""],
20660 fractional: options.fractional,
20661 decimal: decimal,
20662 exponent: false
20663 },
20664
20665 parts = format.split('.'),
20666 places = options.places;
20667
20668 // special condition for percent (factor != 1)
20669 // allow decimal places even if not specified in pattern
20670 if(parts.length == 1 && factor != 1){
20671 parts[1] = "###";
20672 }
20673 if(parts.length == 1 || places === 0){
20674 flags.fractional = false;
20675 }else{
20676 if(places === undefined){ places = options.pattern ? parts[1].lastIndexOf('0') + 1 : Infinity; }
20677 if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified
20678 if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; }
20679 flags.places = places;
20680 }
20681 var groups = parts[0].split(',');
20682 if(groups.length > 1){
20683 flags.groupSize = groups.pop().length;
20684 if(groups.length > 1){
20685 flags.groupSize2 = groups.pop().length;
20686 }
20687 }
20688 return "("+dojo.number._realNumberRegexp(flags)+")";
20689 });
20690 }, true);
20691
20692 if(isCurrency){
20693 // substitute the currency symbol for the placeholder in the pattern
20694 re = re.replace(/([\s\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match, before, target, after){
20695 var prop = ["symbol", "currency", "displayName"][target.length-1],
20696 symbol = dojo.regexp.escapeString(options[prop] || options.currency || "");
20697 before = before ? "[\\s\\xa0]" : "";
20698 after = after ? "[\\s\\xa0]" : "";
20699 if(!options.strict){
20700 if(before){before += "*";}
20701 if(after){after += "*";}
20702 return "(?:"+before+symbol+after+")?";
20703 }
20704 return before+symbol+after;
20705 });
20706 }
20707
20708 //TODO: substitute localized sign/percent/permille/etc.?
20709
20710 // normalize whitespace and return
20711 return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object
20712 }
20713
20714 /*=====
20715 dojo.number.__ParseOptions = function(){
20716 // pattern: String?
20717 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
20718 // with this string. Default value is based on locale. Overriding this property will defeat
20719 // localization. Literal characters in patterns are not supported.
20720 // type: String?
20721 // choose a format type based on the locale from the following:
20722 // decimal, scientific (not yet supported), percent, currency. decimal by default.
20723 // locale: String?
20724 // override the locale used to determine formatting rules
20725 // strict: Boolean?
20726 // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
20727 // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
20728 // fractional: Boolean?|Array?
20729 // Whether to include the fractional portion, where the number of decimal places are implied by pattern
20730 // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional.
20731 this.pattern = pattern;
20732 this.type = type;
20733 this.locale = locale;
20734 this.strict = strict;
20735 this.fractional = fractional;
20736 }
20737 =====*/
20738 dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){
20739 // summary:
20740 // Convert a properly formatted string to a primitive Number, using
20741 // locale-specific settings.
20742 // description:
20743 // Create a Number from a string using a known localized pattern.
20744 // Formatting patterns are chosen appropriate to the locale
20745 // and follow the syntax described by
20746 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
20747 // Note that literal characters in patterns are not supported.
20748 // expression:
20749 // A string representation of a Number
20750 var info = dojo.number._parseInfo(options),
20751 results = (new RegExp("^"+info.regexp+"$")).exec(expression);
20752 if(!results){
20753 return NaN; //NaN
20754 }
20755 var absoluteMatch = results[1]; // match for the positive expression
20756 if(!results[1]){
20757 if(!results[2]){
20758 return NaN; //NaN
20759 }
20760 // matched the negative pattern
20761 absoluteMatch =results[2];
20762 info.factor *= -1;
20763 }
20764
20765 // Transform it to something Javascript can parse as a number. Normalize
20766 // decimal point and strip out group separators or alternate forms of whitespace
20767 absoluteMatch = absoluteMatch.
20768 replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), "").
20769 replace(info.decimal, ".");
20770 // Adjust for negative sign, percent, etc. as necessary
20771 return absoluteMatch * info.factor; //Number
20772 };
20773
20774 /*=====
20775 dojo.number.__RealNumberRegexpFlags = function(){
20776 // places: Number?
20777 // The integer number of decimal places or a range given as "n,m". If
20778 // not given, the decimal part is optional and the number of places is
20779 // unlimited.
20780 // decimal: String?
20781 // A string for the character used as the decimal point. Default
20782 // is ".".
20783 // fractional: Boolean?|Array?
20784 // Whether decimal places are used. Can be true, false, or [true,
20785 // false]. Default is [true, false] which means optional.
20786 // exponent: Boolean?|Array?
20787 // Express in exponential notation. Can be true, false, or [true,
20788 // false]. Default is [true, false], (i.e. will match if the
20789 // exponential part is present are not).
20790 // eSigned: Boolean?|Array?
20791 // The leading plus-or-minus sign on the exponent. Can be true,
20792 // false, or [true, false]. Default is [true, false], (i.e. will
20793 // match if it is signed or unsigned). flags in regexp.integer can be
20794 // applied.
20795 this.places = places;
20796 this.decimal = decimal;
20797 this.fractional = fractional;
20798 this.exponent = exponent;
20799 this.eSigned = eSigned;
20800 }
20801 =====*/
20802
20803 dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){
20804 // summary:
20805 // Builds a regular expression to match a real number in exponential
20806 // notation
20807
20808 // assign default values to missing parameters
20809 flags = flags || {};
20810 //TODO: use mixin instead?
20811 if(!("places" in flags)){ flags.places = Infinity; }
20812 if(typeof flags.decimal != "string"){ flags.decimal = "."; }
20813 if(!("fractional" in flags) || /^0/.test(flags.places)){ flags.fractional = [true, false]; }
20814 if(!("exponent" in flags)){ flags.exponent = [true, false]; }
20815 if(!("eSigned" in flags)){ flags.eSigned = [true, false]; }
20816
20817 var integerRE = dojo.number._integerRegexp(flags),
20818 decimalRE = dojo.regexp.buildGroupRE(flags.fractional,
20819 function(q){
20820 var re = "";
20821 if(q && (flags.places!==0)){
20822 re = "\\" + flags.decimal;
20823 if(flags.places == Infinity){
20824 re = "(?:" + re + "\\d+)?";
20825 }else{
20826 re += "\\d{" + flags.places + "}";
20827 }
20828 }
20829 return re;
20830 },
20831 true
20832 );
20833
20834 var exponentRE = dojo.regexp.buildGroupRE(flags.exponent,
20835 function(q){
20836 if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; }
20837 return "";
20838 }
20839 );
20840
20841 var realRE = integerRE + decimalRE;
20842 // allow for decimals without integers, e.g. .25
20843 if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";}
20844 return realRE + exponentRE; // String
20845 };
20846
20847 /*=====
20848 dojo.number.__IntegerRegexpFlags = function(){
20849 // signed: Boolean?
20850 // The leading plus-or-minus sign. Can be true, false, or `[true,false]`.
20851 // Default is `[true, false]`, (i.e. will match if it is signed
20852 // or unsigned).
20853 // separator: String?
20854 // The character used as the thousands separator. Default is no
20855 // separator. For more than one symbol use an array, e.g. `[",", ""]`,
20856 // makes ',' optional.
20857 // groupSize: Number?
20858 // group size between separators
20859 // groupSize2: Number?
20860 // second grouping, where separators 2..n have a different interval than the first separator (for India)
20861 this.signed = signed;
20862 this.separator = separator;
20863 this.groupSize = groupSize;
20864 this.groupSize2 = groupSize2;
20865 }
20866 =====*/
20867
20868 dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){
20869 // summary:
20870 // Builds a regular expression that matches an integer
20871
20872 // assign default values to missing parameters
20873 flags = flags || {};
20874 if(!("signed" in flags)){ flags.signed = [true, false]; }
20875 if(!("separator" in flags)){
20876 flags.separator = "";
20877 }else if(!("groupSize" in flags)){
20878 flags.groupSize = 3;
20879 }
20880
20881 var signRE = dojo.regexp.buildGroupRE(flags.signed,
20882 function(q){ return q ? "[-+]" : ""; },
20883 true
20884 );
20885
20886 var numberRE = dojo.regexp.buildGroupRE(flags.separator,
20887 function(sep){
20888 if(!sep){
20889 return "(?:\\d+)";
20890 }
20891
20892 sep = dojo.regexp.escapeString(sep);
20893 if(sep == " "){ sep = "\\s"; }
20894 else if(sep == "\xa0"){ sep = "\\s\\xa0"; }
20895
20896 var grp = flags.groupSize, grp2 = flags.groupSize2;
20897 //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933
20898 if(grp2){
20899 var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})";
20900 return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE;
20901 }
20902 return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)";
20903 },
20904 true
20905 );
20906
20907 return signRE + numberRE; // String
20908 }
20909
20910 }
20911
20912 if(!dojo._hasResource["dijit.ProgressBar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
20913 dojo._hasResource["dijit.ProgressBar"] = true;
20914 dojo.provide("dijit.ProgressBar");
20915
20916
20917
20918
20919
20920
20921
20922 dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
20923 // summary:
20924 // A progress indication widget, showing the amount completed
20925 // (often the percentage completed) of a task.
20926 //
20927 // example:
20928 // | <div dojoType="ProgressBar"
20929 // | places="0"
20930 // | progress="..." maximum="...">
20931 // | </div>
20932 //
20933 // description:
20934 // Note that the progress bar is updated via (a non-standard)
20935 // update() method, rather than via attr() like other widgets.
20936
20937 // progress: [const] String (Percentage or Number)
20938 // Number or percentage indicating amount of task completed.
20939 // With "%": percentage value, 0% <= progress <= 100%, or
20940 // without "%": absolute value, 0 <= progress <= maximum
20941 // TODO: rename to value for 2.0
20942 progress: "0",
20943
20944 // maximum: [const] Float
20945 // Max sample number
20946 maximum: 100,
20947
20948 // places: [const] Number
20949 // Number of places to show in values; 0 by default
20950 places: 0,
20951
20952 // indeterminate: [const] Boolean
20953 // If false: show progress value (number or percentage).
20954 // If true: show that a process is underway but that the amount completed is unknown.
20955 indeterminate: false,
20956
20957 // name: String
20958 // this is the field name (for a form) if set. This needs to be set if you want to use
20959 // this widget in a dijit.form.Form widget (such as dijit.Dialog)
20960 name: '',
20961
20962 templateString: dojo.cache("dijit", "templates/ProgressBar.html", "<div class=\"dijitProgressBar dijitProgressBarEmpty\"\n\t><div waiRole=\"progressbar\" dojoAttachPoint=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\"></div\n\t\t><span style=\"visibility:hidden\">&nbsp;</span\n\t></div\n\t><div dojoAttachPoint=\"label\" class=\"dijitProgressBarLabel\" id=\"${id}_label\">&nbsp;</div\n\t><img dojoAttachPoint=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n"),
20963
20964 // _indeterminateHighContrastImagePath: [private] dojo._URL
20965 // URL to image to use for indeterminate progress bar when display is in high contrast mode
20966 _indeterminateHighContrastImagePath:
20967 dojo.moduleUrl("dijit", "themes/a11y/indeterminate_progress.gif"),
20968
20969 // public functions
20970 postCreate: function(){
20971 this.inherited(arguments);
20972 this.indeterminateHighContrastImage.setAttribute("src",
20973 this._indeterminateHighContrastImagePath.toString());
20974 this.update();
20975 },
20976
20977 update: function(/*Object?*/attributes){
20978 // summary:
20979 // Change attributes of ProgressBar, similar to attr(hash).
20980 //
20981 // attributes:
20982 // May provide progress and/or maximum properties on this parameter;
20983 // see attribute specs for details.
20984 //
20985 // example:
20986 // | myProgressBar.update({'indeterminate': true});
20987 // | myProgressBar.update({'progress': 80});
20988
20989 // TODO: deprecate this method and use set() instead
20990
20991 dojo.mixin(this, attributes || {});
20992 var tip = this.internalProgress;
20993 var percent = 1, classFunc;
20994 if(this.indeterminate){
20995 classFunc = "addClass";
20996 dijit.removeWaiState(tip, "valuenow");
20997 dijit.removeWaiState(tip, "valuemin");
20998 dijit.removeWaiState(tip, "valuemax");
20999 }else{
21000 classFunc = "removeClass";
21001 if(String(this.progress).indexOf("%") != -1){
21002 percent = Math.min(parseFloat(this.progress)/100, 1);
21003 this.progress = percent * this.maximum;
21004 }else{
21005 this.progress = Math.min(this.progress, this.maximum);
21006 percent = this.progress / this.maximum;
21007 }
21008 var text = this.report(percent);
21009 this.label.firstChild.nodeValue = text;
21010 dijit.setWaiState(tip, "describedby", this.label.id);
21011 dijit.setWaiState(tip, "valuenow", this.progress);
21012 dijit.setWaiState(tip, "valuemin", 0);
21013 dijit.setWaiState(tip, "valuemax", this.maximum);
21014 }
21015 dojo[classFunc](this.domNode, "dijitProgressBarIndeterminate");
21016 tip.style.width = (percent * 100) + "%";
21017 this.onChange();
21018 },
21019
21020 _setValueAttr: function(v){
21021 if(v == Infinity){
21022 this.update({indeterminate:true});
21023 }else{
21024 this.update({indeterminate:false, progress:v});
21025 }
21026 },
21027
21028 _getValueAttr: function(){
21029 return this.progress;
21030 },
21031
21032 report: function(/*float*/percent){
21033 // summary:
21034 // Generates message to show inside progress bar (normally indicating amount of task completed).
21035 // May be overridden.
21036 // tags:
21037 // extension
21038
21039 return dojo.number.format(percent, { type: "percent", places: this.places, locale: this.lang });
21040 },
21041
21042 onChange: function(){
21043 // summary:
21044 // Callback fired when progress updates.
21045 // tags:
21046 // progress
21047 }
21048 });
21049
21050 }
21051
21052 if(!dojo._hasResource["dijit.ToolbarSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21053 dojo._hasResource["dijit.ToolbarSeparator"] = true;
21054 dojo.provide("dijit.ToolbarSeparator");
21055
21056
21057
21058
21059 dojo.declare("dijit.ToolbarSeparator",
21060 [ dijit._Widget, dijit._Templated ],
21061 {
21062 // summary:
21063 // A spacer between two `dijit.Toolbar` items
21064 templateString: '<div class="dijitToolbarSeparator dijitInline" waiRole="presentation"></div>',
21065 postCreate: function(){ dojo.setSelectable(this.domNode, false); },
21066 isFocusable: function(){
21067 // summary:
21068 // This widget isn't focusable, so pass along that fact.
21069 // tags:
21070 // protected
21071 return false;
21072 }
21073
21074 });
21075
21076
21077
21078 }
21079
21080 if(!dojo._hasResource["dijit.Toolbar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21081 dojo._hasResource["dijit.Toolbar"] = true;
21082 dojo.provide("dijit.Toolbar");
21083
21084
21085
21086
21087
21088 dojo.declare("dijit.Toolbar",
21089 [dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
21090 {
21091 // summary:
21092 // A Toolbar widget, used to hold things like `dijit.Editor` buttons
21093
21094 templateString:
21095 '<div class="dijit" waiRole="toolbar" tabIndex="${tabIndex}" dojoAttachPoint="containerNode">' +
21096 // '<table style="table-layout: fixed" class="dijitReset dijitToolbarTable">' + // factor out style
21097 // '<tr class="dijitReset" dojoAttachPoint="containerNode"></tr>'+
21098 // '</table>' +
21099 '</div>',
21100
21101 baseClass: "dijitToolbar",
21102
21103 postCreate: function(){
21104 this.connectKeyNavHandlers(
21105 this.isLeftToRight() ? [dojo.keys.LEFT_ARROW] : [dojo.keys.RIGHT_ARROW],
21106 this.isLeftToRight() ? [dojo.keys.RIGHT_ARROW] : [dojo.keys.LEFT_ARROW]
21107 );
21108 this.inherited(arguments);
21109 },
21110
21111 startup: function(){
21112 if(this._started){ return; }
21113
21114 this.startupKeyNavChildren();
21115
21116 this.inherited(arguments);
21117 }
21118 }
21119 );
21120
21121 // For back-compat, remove for 2.0
21122
21123
21124 }
21125
21126 if(!dojo._hasResource["dojo.DeferredList"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21127 dojo._hasResource["dojo.DeferredList"] = true;
21128 dojo.provide("dojo.DeferredList");
21129 dojo.DeferredList = function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/ consumeErrors, /*Function?*/ canceller){
21130 // summary:
21131 // Provides event handling for a group of Deferred objects.
21132 // description:
21133 // DeferredList takes an array of existing deferreds and returns a new deferred of its own
21134 // this new deferred will typically have its callback fired when all of the deferreds in
21135 // the given list have fired their own deferreds. The parameters `fireOnOneCallback` and
21136 // fireOnOneErrback, will fire before all the deferreds as appropriate
21137 //
21138 // list:
21139 // The list of deferreds to be synchronizied with this DeferredList
21140 // fireOnOneCallback:
21141 // Will cause the DeferredLists callback to be fired as soon as any
21142 // of the deferreds in its list have been fired instead of waiting until
21143 // the entire list has finished
21144 // fireonOneErrback:
21145 // Will cause the errback to fire upon any of the deferreds errback
21146 // canceller:
21147 // A deferred canceller function, see dojo.Deferred
21148 var resultList = [];
21149 dojo.Deferred.call(this);
21150 var self = this;
21151 if(list.length === 0 && !fireOnOneCallback){
21152 this.resolve([0, []]);
21153 }
21154 var finished = 0;
21155 dojo.forEach(list, function(item, i){
21156 item.then(function(result){
21157 if(fireOnOneCallback){
21158 self.resolve([i, result]);
21159 }else{
21160 addResult(true, result);
21161 }
21162 },function(error){
21163 if(fireOnOneErrback){
21164 self.reject(error);
21165 }else{
21166 addResult(false, error);
21167 }
21168 if(consumeErrors){
21169 return null;
21170 }
21171 throw error;
21172 });
21173 function addResult(succeeded, result){
21174 resultList[i] = [succeeded, result];
21175 finished++;
21176 if(finished === list.length){
21177 self.resolve(resultList);
21178 }
21179
21180 }
21181 });
21182 };
21183 dojo.DeferredList.prototype = new dojo.Deferred();
21184
21185 dojo.DeferredList.prototype.gatherResults= function(deferredList){
21186 // summary:
21187 // Gathers the results of the deferreds for packaging
21188 // as the parameters to the Deferred Lists' callback
21189
21190 var d = new dojo.DeferredList(deferredList, false, true, false);
21191 d.addCallback(function(results){
21192 var ret = [];
21193 dojo.forEach(results, function(result){
21194 ret.push(result[1]);
21195 });
21196 return ret;
21197 });
21198 return d;
21199 };
21200
21201 }
21202
21203 if(!dojo._hasResource["dijit.tree.TreeStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21204 dojo._hasResource["dijit.tree.TreeStoreModel"] = true;
21205 dojo.provide("dijit.tree.TreeStoreModel");
21206
21207 dojo.declare(
21208 "dijit.tree.TreeStoreModel",
21209 null,
21210 {
21211 // summary:
21212 // Implements dijit.Tree.model connecting to a store with a single
21213 // root item. Any methods passed into the constructor will override
21214 // the ones defined here.
21215
21216 // store: dojo.data.Store
21217 // Underlying store
21218 store: null,
21219
21220 // childrenAttrs: String[]
21221 // One or more attribute names (attributes in the dojo.data item) that specify that item's children
21222 childrenAttrs: ["children"],
21223
21224 // newItemIdAttr: String
21225 // Name of attribute in the Object passed to newItem() that specifies the id.
21226 //
21227 // If newItemIdAttr is set then it's used when newItem() is called to see if an
21228 // item with the same id already exists, and if so just links to the old item
21229 // (so that the old item ends up with two parents).
21230 //
21231 // Setting this to null or "" will make every drop create a new item.
21232 newItemIdAttr: "id",
21233
21234 // labelAttr: String
21235 // If specified, get label for tree node from this attribute, rather
21236 // than by calling store.getLabel()
21237 labelAttr: "",
21238
21239 // root: [readonly] dojo.data.Item
21240 // Pointer to the root item (read only, not a parameter)
21241 root: null,
21242
21243 // query: anything
21244 // Specifies datastore query to return the root item for the tree.
21245 // Must only return a single item. Alternately can just pass in pointer
21246 // to root item.
21247 // example:
21248 // | {id:'ROOT'}
21249 query: null,
21250
21251 // deferItemLoadingUntilExpand: Boolean
21252 // Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes
21253 // until they are expanded. This allows for lazying loading where only one
21254 // loadItem (and generally one network call, consequently) per expansion
21255 // (rather than one for each child).
21256 // This relies on partial loading of the children items; each children item of a
21257 // fully loaded item should contain the label and info about having children.
21258 deferItemLoadingUntilExpand: false,
21259
21260 constructor: function(/* Object */ args){
21261 // summary:
21262 // Passed the arguments listed above (store, etc)
21263 // tags:
21264 // private
21265
21266 dojo.mixin(this, args);
21267
21268 this.connects = [];
21269
21270 var store = this.store;
21271 if(!store.getFeatures()['dojo.data.api.Identity']){
21272 throw new Error("dijit.Tree: store must support dojo.data.Identity");
21273 }
21274
21275 // if the store supports Notification, subscribe to the notification events
21276 if(store.getFeatures()['dojo.data.api.Notification']){
21277 this.connects = this.connects.concat([
21278 dojo.connect(store, "onNew", this, "onNewItem"),
21279 dojo.connect(store, "onDelete", this, "onDeleteItem"),
21280 dojo.connect(store, "onSet", this, "onSetItem")
21281 ]);
21282 }
21283 },
21284
21285 destroy: function(){
21286 dojo.forEach(this.connects, dojo.disconnect);
21287 // TODO: should cancel any in-progress processing of getRoot(), getChildren()
21288 },
21289
21290 // =======================================================================
21291 // Methods for traversing hierarchy
21292
21293 getRoot: function(onItem, onError){
21294 // summary:
21295 // Calls onItem with the root item for the tree, possibly a fabricated item.
21296 // Calls onError on error.
21297 if(this.root){
21298 onItem(this.root);
21299 }else{
21300 this.store.fetch({
21301 query: this.query,
21302 onComplete: dojo.hitch(this, function(items){
21303 if(items.length != 1){
21304 throw new Error(this.declaredClass + ": query " + dojo.toJson(this.query) + " returned " + items.length +
21305 " items, but must return exactly one item");
21306 }
21307 this.root = items[0];
21308 onItem(this.root);
21309 }),
21310 onError: onError
21311 });
21312 }
21313 },
21314
21315 mayHaveChildren: function(/*dojo.data.Item*/ item){
21316 // summary:
21317 // Tells if an item has or may have children. Implementing logic here
21318 // avoids showing +/- expando icon for nodes that we know don't have children.
21319 // (For efficiency reasons we may not want to check if an element actually
21320 // has children until user clicks the expando node)
21321 return dojo.some(this.childrenAttrs, function(attr){
21322 return this.store.hasAttribute(item, attr);
21323 }, this);
21324 },
21325
21326 getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){
21327 // summary:
21328 // Calls onComplete() with array of child items of given parent item, all loaded.
21329
21330 var store = this.store;
21331 if(!store.isItemLoaded(parentItem)){
21332 // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand
21333 // mode, so we will load it and just return the children (without loading each
21334 // child item)
21335 var getChildren = dojo.hitch(this, arguments.callee);
21336 store.loadItem({
21337 item: parentItem,
21338 onItem: function(parentItem){
21339 getChildren(parentItem, onComplete, onError);
21340 },
21341 onError: onError
21342 });
21343 return;
21344 }
21345 // get children of specified item
21346 var childItems = [];
21347 for(var i=0; i<this.childrenAttrs.length; i++){
21348 var vals = store.getValues(parentItem, this.childrenAttrs[i]);
21349 childItems = childItems.concat(vals);
21350 }
21351
21352 // count how many items need to be loaded
21353 var _waitCount = 0;
21354 if(!this.deferItemLoadingUntilExpand){
21355 dojo.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } });
21356 }
21357
21358 if(_waitCount == 0){
21359 // all items are already loaded (or we aren't loading them). proceed...
21360 onComplete(childItems);
21361 }else{
21362 // still waiting for some or all of the items to load
21363 dojo.forEach(childItems, function(item, idx){
21364 if(!store.isItemLoaded(item)){
21365 store.loadItem({
21366 item: item,
21367 onItem: function(item){
21368 childItems[idx] = item;
21369 if(--_waitCount == 0){
21370 // all nodes have been loaded, send them to the tree
21371 onComplete(childItems);
21372 }
21373 },
21374 onError: onError
21375 });
21376 }
21377 });
21378 }
21379 },
21380
21381 // =======================================================================
21382 // Inspecting items
21383
21384 isItem: function(/* anything */ something){
21385 return this.store.isItem(something); // Boolean
21386 },
21387
21388 fetchItemByIdentity: function(/* object */ keywordArgs){
21389 this.store.fetchItemByIdentity(keywordArgs);
21390 },
21391
21392 getIdentity: function(/* item */ item){
21393 return this.store.getIdentity(item); // Object
21394 },
21395
21396 getLabel: function(/*dojo.data.Item*/ item){
21397 // summary:
21398 // Get the label for an item
21399 if(this.labelAttr){
21400 return this.store.getValue(item,this.labelAttr); // String
21401 }else{
21402 return this.store.getLabel(item); // String
21403 }
21404 },
21405
21406 // =======================================================================
21407 // Write interface
21408
21409 newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){
21410 // summary:
21411 // Creates a new item. See `dojo.data.api.Write` for details on args.
21412 // Used in drag & drop when item from external source dropped onto tree.
21413 // description:
21414 // Developers will need to override this method if new items get added
21415 // to parents with multiple children attributes, in order to define which
21416 // children attribute points to the new item.
21417
21418 var pInfo = {parent: parent, attribute: this.childrenAttrs[0], insertIndex: insertIndex};
21419
21420 if(this.newItemIdAttr && args[this.newItemIdAttr]){
21421 // Maybe there's already a corresponding item in the store; if so, reuse it.
21422 this.fetchItemByIdentity({identity: args[this.newItemIdAttr], scope: this, onItem: function(item){
21423 if(item){
21424 // There's already a matching item in store, use it
21425 this.pasteItem(item, null, parent, true, insertIndex);
21426 }else{
21427 // Create new item in the tree, based on the drag source.
21428 this.store.newItem(args, pInfo);
21429 }
21430 }});
21431 }else{
21432 // [as far as we know] there is no id so we must assume this is a new item
21433 this.store.newItem(args, pInfo);
21434 }
21435 },
21436
21437 pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){
21438 // summary:
21439 // Move or copy an item from one parent item to another.
21440 // Used in drag & drop
21441 var store = this.store,
21442 parentAttr = this.childrenAttrs[0]; // name of "children" attr in parent item
21443
21444 // remove child from source item, and record the attribute that child occurred in
21445 if(oldParentItem){
21446 dojo.forEach(this.childrenAttrs, function(attr){
21447 if(store.containsValue(oldParentItem, attr, childItem)){
21448 if(!bCopy){
21449 var values = dojo.filter(store.getValues(oldParentItem, attr), function(x){
21450 return x != childItem;
21451 });
21452 store.setValues(oldParentItem, attr, values);
21453 }
21454 parentAttr = attr;
21455 }
21456 });
21457 }
21458
21459 // modify target item's children attribute to include this item
21460 if(newParentItem){
21461 if(typeof insertIndex == "number"){
21462 // call slice() to avoid modifying the original array, confusing the data store
21463 var childItems = store.getValues(newParentItem, parentAttr).slice();
21464 childItems.splice(insertIndex, 0, childItem);
21465 store.setValues(newParentItem, parentAttr, childItems);
21466 }else{
21467 store.setValues(newParentItem, parentAttr,
21468 store.getValues(newParentItem, parentAttr).concat(childItem));
21469 }
21470 }
21471 },
21472
21473 // =======================================================================
21474 // Callbacks
21475
21476 onChange: function(/*dojo.data.Item*/ item){
21477 // summary:
21478 // Callback whenever an item has changed, so that Tree
21479 // can update the label, icon, etc. Note that changes
21480 // to an item's children or parent(s) will trigger an
21481 // onChildrenChange() so you can ignore those changes here.
21482 // tags:
21483 // callback
21484 },
21485
21486 onChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
21487 // summary:
21488 // Callback to do notifications about new, updated, or deleted items.
21489 // tags:
21490 // callback
21491 },
21492
21493 onDelete: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
21494 // summary:
21495 // Callback when an item has been deleted.
21496 // description:
21497 // Note that there will also be an onChildrenChange() callback for the parent
21498 // of this item.
21499 // tags:
21500 // callback
21501 },
21502
21503 // =======================================================================
21504 // Events from data store
21505
21506 onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){
21507 // summary:
21508 // Handler for when new items appear in the store, either from a drop operation
21509 // or some other way. Updates the tree view (if necessary).
21510 // description:
21511 // If the new item is a child of an existing item,
21512 // calls onChildrenChange() with the new list of children
21513 // for that existing item.
21514 //
21515 // tags:
21516 // extension
21517
21518 // We only care about the new item if it has a parent that corresponds to a TreeNode
21519 // we are currently displaying
21520 if(!parentInfo){
21521 return;
21522 }
21523
21524 // Call onChildrenChange() on parent (ie, existing) item with new list of children
21525 // In the common case, the new list of children is simply parentInfo.newValue or
21526 // [ parentInfo.newValue ], although if items in the store has multiple
21527 // child attributes (see `childrenAttr`), then it's a superset of parentInfo.newValue,
21528 // so call getChildren() to be sure to get right answer.
21529 this.getChildren(parentInfo.item, dojo.hitch(this, function(children){
21530 this.onChildrenChange(parentInfo.item, children);
21531 }));
21532 },
21533
21534 onDeleteItem: function(/*Object*/ item){
21535 // summary:
21536 // Handler for delete notifications from underlying store
21537 this.onDelete(item);
21538 },
21539
21540 onSetItem: function(/* item */ item,
21541 /* attribute-name-string */ attribute,
21542 /* object | array */ oldValue,
21543 /* object | array */ newValue){
21544 // summary:
21545 // Updates the tree view according to changes in the data store.
21546 // description:
21547 // Handles updates to an item's children by calling onChildrenChange(), and
21548 // other updates to an item by calling onChange().
21549 //
21550 // See `onNewItem` for more details on handling updates to an item's children.
21551 // tags:
21552 // extension
21553
21554 if(dojo.indexOf(this.childrenAttrs, attribute) != -1){
21555 // item's children list changed
21556 this.getChildren(item, dojo.hitch(this, function(children){
21557 // See comments in onNewItem() about calling getChildren()
21558 this.onChildrenChange(item, children);
21559 }));
21560 }else{
21561 // item's label/icon/etc. changed.
21562 this.onChange(item);
21563 }
21564 }
21565 });
21566
21567
21568
21569 }
21570
21571 if(!dojo._hasResource["dijit.tree.ForestStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21572 dojo._hasResource["dijit.tree.ForestStoreModel"] = true;
21573 dojo.provide("dijit.tree.ForestStoreModel");
21574
21575
21576
21577 dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.TreeStoreModel, {
21578 // summary:
21579 // Interface between Tree and a dojo.store that doesn't have a root item,
21580 // i.e. has multiple "top level" items.
21581 //
21582 // description
21583 // Use this class to wrap a dojo.store, making all the items matching the specified query
21584 // appear as children of a fabricated "root item". If no query is specified then all the
21585 // items returned by fetch() on the underlying store become children of the root item.
21586 // It allows dijit.Tree to assume a single root item, even if the store doesn't have one.
21587
21588 // Parameters to constructor
21589
21590 // rootId: String
21591 // ID of fabricated root item
21592 rootId: "$root$",
21593
21594 // rootLabel: String
21595 // Label of fabricated root item
21596 rootLabel: "ROOT",
21597
21598 // query: String
21599 // Specifies the set of children of the root item.
21600 // example:
21601 // | {type:'continent'}
21602 query: null,
21603
21604 // End of parameters to constructor
21605
21606 constructor: function(params){
21607 // summary:
21608 // Sets up variables, etc.
21609 // tags:
21610 // private
21611
21612 // Make dummy root item
21613 this.root = {
21614 store: this,
21615 root: true,
21616 id: params.rootId,
21617 label: params.rootLabel,
21618 children: params.rootChildren // optional param
21619 };
21620 },
21621
21622 // =======================================================================
21623 // Methods for traversing hierarchy
21624
21625 mayHaveChildren: function(/*dojo.data.Item*/ item){
21626 // summary:
21627 // Tells if an item has or may have children. Implementing logic here
21628 // avoids showing +/- expando icon for nodes that we know don't have children.
21629 // (For efficiency reasons we may not want to check if an element actually
21630 // has children until user clicks the expando node)
21631 // tags:
21632 // extension
21633 return item === this.root || this.inherited(arguments);
21634 },
21635
21636 getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ callback, /*function*/ onError){
21637 // summary:
21638 // Calls onComplete() with array of child items of given parent item, all loaded.
21639 if(parentItem === this.root){
21640 if(this.root.children){
21641 // already loaded, just return
21642 callback(this.root.children);
21643 }else{
21644 this.store.fetch({
21645 query: this.query,
21646 onComplete: dojo.hitch(this, function(items){
21647 this.root.children = items;
21648 callback(items);
21649 }),
21650 onError: onError
21651 });
21652 }
21653 }else{
21654 this.inherited(arguments);
21655 }
21656 },
21657
21658 // =======================================================================
21659 // Inspecting items
21660
21661 isItem: function(/* anything */ something){
21662 return (something === this.root) ? true : this.inherited(arguments);
21663 },
21664
21665 fetchItemByIdentity: function(/* object */ keywordArgs){
21666 if(keywordArgs.identity == this.root.id){
21667 var scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
21668 if(keywordArgs.onItem){
21669 keywordArgs.onItem.call(scope, this.root);
21670 }
21671 }else{
21672 this.inherited(arguments);
21673 }
21674 },
21675
21676 getIdentity: function(/* item */ item){
21677 return (item === this.root) ? this.root.id : this.inherited(arguments);
21678 },
21679
21680 getLabel: function(/* item */ item){
21681 return (item === this.root) ? this.root.label : this.inherited(arguments);
21682 },
21683
21684 // =======================================================================
21685 // Write interface
21686
21687 newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){
21688 // summary:
21689 // Creates a new item. See dojo.data.api.Write for details on args.
21690 // Used in drag & drop when item from external source dropped onto tree.
21691 if(parent === this.root){
21692 this.onNewRootItem(args);
21693 return this.store.newItem(args);
21694 }else{
21695 return this.inherited(arguments);
21696 }
21697 },
21698
21699 onNewRootItem: function(args){
21700 // summary:
21701 // User can override this method to modify a new element that's being
21702 // added to the root of the tree, for example to add a flag like root=true
21703 },
21704
21705 pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){
21706 // summary:
21707 // Move or copy an item from one parent item to another.
21708 // Used in drag & drop
21709 if(oldParentItem === this.root){
21710 if(!bCopy){
21711 // It's onLeaveRoot()'s responsibility to modify the item so it no longer matches
21712 // this.query... thus triggering an onChildrenChange() event to notify the Tree
21713 // that this element is no longer a child of the root node
21714 this.onLeaveRoot(childItem);
21715 }
21716 }
21717 dijit.tree.TreeStoreModel.prototype.pasteItem.call(this, childItem,
21718 oldParentItem === this.root ? null : oldParentItem,
21719 newParentItem === this.root ? null : newParentItem,
21720 bCopy,
21721 insertIndex
21722 );
21723 if(newParentItem === this.root){
21724 // It's onAddToRoot()'s responsibility to modify the item so it matches
21725 // this.query... thus triggering an onChildrenChange() event to notify the Tree
21726 // that this element is now a child of the root node
21727 this.onAddToRoot(childItem);
21728 }
21729 },
21730
21731 // =======================================================================
21732 // Handling for top level children
21733
21734 onAddToRoot: function(/* item */ item){
21735 // summary:
21736 // Called when item added to root of tree; user must override this method
21737 // to modify the item so that it matches the query for top level items
21738 // example:
21739 // | store.setValue(item, "root", true);
21740 // tags:
21741 // extension
21742 console.log(this, ": item ", item, " added to root");
21743 },
21744
21745 onLeaveRoot: function(/* item */ item){
21746 // summary:
21747 // Called when item removed from root of tree; user must override this method
21748 // to modify the item so it doesn't match the query for top level items
21749 // example:
21750 // | store.unsetAttribute(item, "root");
21751 // tags:
21752 // extension
21753 console.log(this, ": item ", item, " removed from root");
21754 },
21755
21756 // =======================================================================
21757 // Events from data store
21758
21759 _requeryTop: function(){
21760 // reruns the query for the children of the root node,
21761 // sending out an onSet notification if those children have changed
21762 var oldChildren = this.root.children || [];
21763 this.store.fetch({
21764 query: this.query,
21765 onComplete: dojo.hitch(this, function(newChildren){
21766 this.root.children = newChildren;
21767
21768 // If the list of children or the order of children has changed...
21769 if(oldChildren.length != newChildren.length ||
21770 dojo.some(oldChildren, function(item, idx){ return newChildren[idx] != item;})){
21771 this.onChildrenChange(this.root, newChildren);
21772 }
21773 })
21774 });
21775 },
21776
21777 onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){
21778 // summary:
21779 // Handler for when new items appear in the store. Developers should override this
21780 // method to be more efficient based on their app/data.
21781 // description:
21782 // Note that the default implementation requeries the top level items every time
21783 // a new item is created, since any new item could be a top level item (even in
21784 // addition to being a child of another item, since items can have multiple parents).
21785 //
21786 // Developers can override this function to do something more efficient if they can
21787 // detect which items are possible top level items (based on the item and the
21788 // parentInfo parameters). Often all top level items have parentInfo==null, but
21789 // that will depend on which store you use and what your data is like.
21790 // tags:
21791 // extension
21792 this._requeryTop();
21793
21794 this.inherited(arguments);
21795 },
21796
21797 onDeleteItem: function(/*Object*/ item){
21798 // summary:
21799 // Handler for delete notifications from underlying store
21800
21801 // check if this was a child of root, and if so send notification that root's children
21802 // have changed
21803 if(dojo.indexOf(this.root.children, item) != -1){
21804 this._requeryTop();
21805 }
21806
21807 this.inherited(arguments);
21808 }
21809 });
21810
21811
21812
21813 }
21814
21815 if(!dojo._hasResource["dijit.Tree"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21816 dojo._hasResource["dijit.Tree"] = true;
21817 dojo.provide("dijit.Tree");
21818
21819
21820
21821
21822
21823
21824
21825
21826
21827
21828
21829 dojo.declare(
21830 "dijit._TreeNode",
21831 [dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained, dijit._CssStateMixin],
21832 {
21833 // summary:
21834 // Single node within a tree. This class is used internally
21835 // by Tree and should not be accessed directly.
21836 // tags:
21837 // private
21838
21839 // item: dojo.data.Item
21840 // the dojo.data entry this tree represents
21841 item: null,
21842
21843 // isTreeNode: [protected] Boolean
21844 // Indicates that this is a TreeNode. Used by `dijit.Tree` only,
21845 // should not be accessed directly.
21846 isTreeNode: true,
21847
21848 // label: String
21849 // Text of this tree node
21850 label: "",
21851
21852 // isExpandable: [private] Boolean
21853 // This node has children, so show the expando node (+ sign)
21854 isExpandable: null,
21855
21856 // isExpanded: [readonly] Boolean
21857 // This node is currently expanded (ie, opened)
21858 isExpanded: false,
21859
21860 // state: [private] String
21861 // Dynamic loading-related stuff.
21862 // When an empty folder node appears, it is "UNCHECKED" first,
21863 // then after dojo.data query it becomes "LOADING" and, finally "LOADED"
21864 state: "UNCHECKED",
21865
21866 templateString: dojo.cache("dijit", "templates/TreeNode.html", "<div class=\"dijitTreeNode\" waiRole=\"presentation\"\n\t><div dojoAttachPoint=\"rowNode\" class=\"dijitTreeRow\" waiRole=\"presentation\" dojoAttachEvent=\"onmouseenter:_onMouseEnter, onmouseleave:_onMouseLeave, onclick:_onClick, ondblclick:_onDblClick\"\n\t\t><img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" waiRole=\"presentation\"\n\t\t/><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" waiRole=\"presentation\"\n\t\t></span\n\t\t><span dojoAttachPoint=\"contentNode\"\n\t\t\tclass=\"dijitTreeContent\" waiRole=\"presentation\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"iconNode\" class=\"dijitIcon dijitTreeIcon\" waiRole=\"presentation\"\n\t\t\t/><span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" wairole=\"treeitem\" tabindex=\"-1\" waiState=\"selected-false\" dojoAttachEvent=\"onfocus:_onLabelFocus\"></span>\n\t\t</span\n\t></div>\n\t<div dojoAttachPoint=\"containerNode\" class=\"dijitTreeContainer\" waiRole=\"presentation\" style=\"display: none;\"></div>\n</div>\n"),
21867
21868 baseClass: "dijitTreeNode",
21869
21870 // For hover effect for tree node, and focus effect for label
21871 cssStateNodes: {
21872 rowNode: "dijitTreeRow",
21873 labelNode: "dijitTreeLabel"
21874 },
21875
21876 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
21877 label: {node: "labelNode", type: "innerText"},
21878 tooltip: {node: "rowNode", type: "attribute", attribute: "title"}
21879 }),
21880
21881 postCreate: function(){
21882 this.inherited(arguments);
21883
21884 // set expand icon for leaf
21885 this._setExpando();
21886
21887 // set icon and label class based on item
21888 this._updateItemClasses(this.item);
21889
21890 if(this.isExpandable){
21891 dijit.setWaiState(this.labelNode, "expanded", this.isExpanded);
21892 }
21893 },
21894
21895 _setIndentAttr: function(indent){
21896 // summary:
21897 // Tell this node how many levels it should be indented
21898 // description:
21899 // 0 for top level nodes, 1 for their children, 2 for their
21900 // grandchildren, etc.
21901 this.indent = indent;
21902
21903 // Math.max() is to prevent negative padding on hidden root node (when indent == -1)
21904 var pixels = (Math.max(indent, 0) * this.tree._nodePixelIndent) + "px";
21905
21906 dojo.style(this.domNode, "backgroundPosition", pixels + " 0px");
21907 dojo.style(this.rowNode, this.isLeftToRight() ? "paddingLeft" : "paddingRight", pixels);
21908
21909 dojo.forEach(this.getChildren(), function(child){
21910 child.set("indent", indent+1);
21911 });
21912 },
21913
21914 markProcessing: function(){
21915 // summary:
21916 // Visually denote that tree is loading data, etc.
21917 // tags:
21918 // private
21919 this.state = "LOADING";
21920 this._setExpando(true);
21921 },
21922
21923 unmarkProcessing: function(){
21924 // summary:
21925 // Clear markup from markProcessing() call
21926 // tags:
21927 // private
21928 this._setExpando(false);
21929 },
21930
21931 _updateItemClasses: function(item){
21932 // summary:
21933 // Set appropriate CSS classes for icon and label dom node
21934 // (used to allow for item updates to change respective CSS)
21935 // tags:
21936 // private
21937 var tree = this.tree, model = tree.model;
21938 if(tree._v10Compat && item === model.root){
21939 // For back-compat with 1.0, need to use null to specify root item (TODO: remove in 2.0)
21940 item = null;
21941 }
21942 this._applyClassAndStyle(item, "icon", "Icon");
21943 this._applyClassAndStyle(item, "label", "Label");
21944 this._applyClassAndStyle(item, "row", "Row");
21945 },
21946
21947 _applyClassAndStyle: function(item, lower, upper){
21948 // summary:
21949 // Set the appropriate CSS classes and styles for labels, icons and rows.
21950 //
21951 // item:
21952 // The data item.
21953 //
21954 // lower:
21955 // The lower case attribute to use, e.g. 'icon', 'label' or 'row'.
21956 //
21957 // upper:
21958 // The upper case attribute to use, e.g. 'Icon', 'Label' or 'Row'.
21959 //
21960 // tags:
21961 // private
21962
21963 var clsName = "_" + lower + "Class";
21964 var nodeName = lower + "Node";
21965
21966 if(this[clsName]){
21967 dojo.removeClass(this[nodeName], this[clsName]);
21968 }
21969 this[clsName] = this.tree["get" + upper + "Class"](item, this.isExpanded);
21970 if(this[clsName]){
21971 dojo.addClass(this[nodeName], this[clsName]);
21972 }
21973 dojo.style(this[nodeName], this.tree["get" + upper + "Style"](item, this.isExpanded) || {});
21974 },
21975
21976 _updateLayout: function(){
21977 // summary:
21978 // Set appropriate CSS classes for this.domNode
21979 // tags:
21980 // private
21981 var parent = this.getParent();
21982 if(!parent || parent.rowNode.style.display == "none"){
21983 /* if we are hiding the root node then make every first level child look like a root node */
21984 dojo.addClass(this.domNode, "dijitTreeIsRoot");
21985 }else{
21986 dojo.toggleClass(this.domNode, "dijitTreeIsLast", !this.getNextSibling());
21987 }
21988 },
21989
21990 _setExpando: function(/*Boolean*/ processing){
21991 // summary:
21992 // Set the right image for the expando node
21993 // tags:
21994 // private
21995
21996 var styles = ["dijitTreeExpandoLoading", "dijitTreeExpandoOpened",
21997 "dijitTreeExpandoClosed", "dijitTreeExpandoLeaf"],
21998 _a11yStates = ["*","-","+","*"],
21999 idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3);
22000
22001 // apply the appropriate class to the expando node
22002 dojo.removeClass(this.expandoNode, styles);
22003 dojo.addClass(this.expandoNode, styles[idx]);
22004
22005 // provide a non-image based indicator for images-off mode
22006 this.expandoNodeText.innerHTML = _a11yStates[idx];
22007
22008 },
22009
22010 expand: function(){
22011 // summary:
22012 // Show my children
22013 // returns:
22014 // Deferred that fires when expansion is complete
22015
22016 // If there's already an expand in progress or we are already expanded, just return
22017 if(this._expandDeferred){
22018 return this._expandDeferred; // dojo.Deferred
22019 }
22020
22021 // cancel in progress collapse operation
22022 this._wipeOut && this._wipeOut.stop();
22023
22024 // All the state information for when a node is expanded, maybe this should be
22025 // set when the animation completes instead
22026 this.isExpanded = true;
22027 dijit.setWaiState(this.labelNode, "expanded", "true");
22028 dijit.setWaiRole(this.containerNode, "group");
22029 dojo.addClass(this.contentNode,'dijitTreeContentExpanded');
22030 this._setExpando();
22031 this._updateItemClasses(this.item);
22032 if(this == this.tree.rootNode){
22033 dijit.setWaiState(this.tree.domNode, "expanded", "true");
22034 }
22035
22036 var def,
22037 wipeIn = dojo.fx.wipeIn({
22038 node: this.containerNode, duration: dijit.defaultDuration,
22039 onEnd: function(){
22040 def.callback(true);
22041 }
22042 });
22043
22044 // Deferred that fires when expand is complete
22045 def = (this._expandDeferred = new dojo.Deferred(function(){
22046 // Canceller
22047 wipeIn.stop();
22048 }));
22049
22050 wipeIn.play();
22051
22052 return def; // dojo.Deferred
22053 },
22054
22055 collapse: function(){
22056 // summary:
22057 // Collapse this node (if it's expanded)
22058
22059 if(!this.isExpanded){ return; }
22060
22061 // cancel in progress expand operation
22062 if(this._expandDeferred){
22063 this._expandDeferred.cancel();
22064 delete this._expandDeferred;
22065 }
22066
22067 this.isExpanded = false;
22068 dijit.setWaiState(this.labelNode, "expanded", "false");
22069 if(this == this.tree.rootNode){
22070 dijit.setWaiState(this.tree.domNode, "expanded", "false");
22071 }
22072 dojo.removeClass(this.contentNode,'dijitTreeContentExpanded');
22073 this._setExpando();
22074 this._updateItemClasses(this.item);
22075
22076 if(!this._wipeOut){
22077 this._wipeOut = dojo.fx.wipeOut({
22078 node: this.containerNode, duration: dijit.defaultDuration
22079 });
22080 }
22081 this._wipeOut.play();
22082 },
22083
22084 // indent: Integer
22085 // Levels from this node to the root node
22086 indent: 0,
22087
22088 setChildItems: function(/* Object[] */ items){
22089 // summary:
22090 // Sets the child items of this node, removing/adding nodes
22091 // from current children to match specified items[] array.
22092 // Also, if this.persist == true, expands any children that were previously
22093 // opened.
22094 // returns:
22095 // Deferred object that fires after all previously opened children
22096 // have been expanded again (or fires instantly if there are no such children).
22097
22098 var tree = this.tree,
22099 model = tree.model,
22100 defs = []; // list of deferreds that need to fire before I am complete
22101
22102
22103 // Orphan all my existing children.
22104 // If items contains some of the same items as before then we will reattach them.
22105 // Don't call this.removeChild() because that will collapse the tree etc.
22106 dojo.forEach(this.getChildren(), function(child){
22107 dijit._Container.prototype.removeChild.call(this, child);
22108 }, this);
22109
22110 this.state = "LOADED";
22111
22112 if(items && items.length > 0){
22113 this.isExpandable = true;
22114
22115 // Create _TreeNode widget for each specified tree node, unless one already
22116 // exists and isn't being used (presumably it's from a DnD move and was recently
22117 // released
22118 dojo.forEach(items, function(item){
22119 var id = model.getIdentity(item),
22120 existingNodes = tree._itemNodesMap[id],
22121 node;
22122 if(existingNodes){
22123 for(var i=0;i<existingNodes.length;i++){
22124 if(existingNodes[i] && !existingNodes[i].getParent()){
22125 node = existingNodes[i];
22126 node.set('indent', this.indent+1);
22127 break;
22128 }
22129 }
22130 }
22131 if(!node){
22132 node = this.tree._createTreeNode({
22133 item: item,
22134 tree: tree,
22135 isExpandable: model.mayHaveChildren(item),
22136 label: tree.getLabel(item),
22137 tooltip: tree.getTooltip(item),
22138 dir: tree.dir,
22139 lang: tree.lang,
22140 indent: this.indent + 1
22141 });
22142 if(existingNodes){
22143 existingNodes.push(node);
22144 }else{
22145 tree._itemNodesMap[id] = [node];
22146 }
22147 }
22148 this.addChild(node);
22149
22150 // If node was previously opened then open it again now (this may trigger
22151 // more data store accesses, recursively)
22152 if(this.tree.autoExpand || this.tree._state(item)){
22153 defs.push(tree._expandNode(node));
22154 }
22155 }, this);
22156
22157 // note that updateLayout() needs to be called on each child after
22158 // _all_ the children exist
22159 dojo.forEach(this.getChildren(), function(child, idx){
22160 child._updateLayout();
22161 });
22162 }else{
22163 this.isExpandable=false;
22164 }
22165
22166 if(this._setExpando){
22167 // change expando to/from dot or + icon, as appropriate
22168 this._setExpando(false);
22169 }
22170
22171 // Set leaf icon or folder icon, as appropriate
22172 this._updateItemClasses(this.item);
22173
22174 // On initial tree show, make the selected TreeNode as either the root node of the tree,
22175 // or the first child, if the root node is hidden
22176 if(this == tree.rootNode){
22177 var fc = this.tree.showRoot ? this : this.getChildren()[0];
22178 if(fc){
22179 fc.setFocusable(true);
22180 tree.lastFocused = fc;
22181 }else{
22182 // fallback: no nodes in tree so focus on Tree <div> itself
22183 tree.domNode.setAttribute("tabIndex", "0");
22184 }
22185 }
22186
22187 return new dojo.DeferredList(defs); // dojo.Deferred
22188 },
22189
22190 removeChild: function(/* treeNode */ node){
22191 this.inherited(arguments);
22192
22193 var children = this.getChildren();
22194 if(children.length == 0){
22195 this.isExpandable = false;
22196 this.collapse();
22197 }
22198
22199 dojo.forEach(children, function(child){
22200 child._updateLayout();
22201 });
22202 },
22203
22204 makeExpandable: function(){
22205 // summary:
22206 // if this node wasn't already showing the expando node,
22207 // turn it into one and call _setExpando()
22208
22209 // TODO: hmm this isn't called from anywhere, maybe should remove it for 2.0
22210
22211 this.isExpandable = true;
22212 this._setExpando(false);
22213 },
22214
22215 _onLabelFocus: function(evt){
22216 // summary:
22217 // Called when this row is focused (possibly programatically)
22218 // Note that we aren't using _onFocus() builtin to dijit
22219 // because it's called when focus is moved to a descendant TreeNode.
22220 // tags:
22221 // private
22222 this.tree._onNodeFocus(this);
22223 },
22224
22225 setSelected: function(/*Boolean*/ selected){
22226 // summary:
22227 // A Tree has a (single) currently selected node.
22228 // Mark that this node is/isn't that currently selected node.
22229 // description:
22230 // In particular, setting a node as selected involves setting tabIndex
22231 // so that when user tabs to the tree, focus will go to that node (only).
22232 dijit.setWaiState(this.labelNode, "selected", selected);
22233 dojo.toggleClass(this.rowNode, "dijitTreeRowSelected", selected);
22234 },
22235
22236 setFocusable: function(/*Boolean*/ selected){
22237 // summary:
22238 // A Tree has a (single) node that's focusable.
22239 // Mark that this node is/isn't that currently focsuable node.
22240 // description:
22241 // In particular, setting a node as selected involves setting tabIndex
22242 // so that when user tabs to the tree, focus will go to that node (only).
22243
22244 this.labelNode.setAttribute("tabIndex", selected ? "0" : "-1");
22245 },
22246
22247 _onClick: function(evt){
22248 // summary:
22249 // Handler for onclick event on a node
22250 // tags:
22251 // private
22252 this.tree._onClick(this, evt);
22253 },
22254 _onDblClick: function(evt){
22255 // summary:
22256 // Handler for ondblclick event on a node
22257 // tags:
22258 // private
22259 this.tree._onDblClick(this, evt);
22260 },
22261
22262 _onMouseEnter: function(evt){
22263 // summary:
22264 // Handler for onmouseenter event on a node
22265 // tags:
22266 // private
22267 this.tree._onNodeMouseEnter(this, evt);
22268 },
22269
22270 _onMouseLeave: function(evt){
22271 // summary:
22272 // Handler for onmouseenter event on a node
22273 // tags:
22274 // private
22275 this.tree._onNodeMouseLeave(this, evt);
22276 }
22277 });
22278
22279 dojo.declare(
22280 "dijit.Tree",
22281 [dijit._Widget, dijit._Templated],
22282 {
22283 // summary:
22284 // This widget displays hierarchical data from a store.
22285
22286 // store: [deprecated] String||dojo.data.Store
22287 // Deprecated. Use "model" parameter instead.
22288 // The store to get data to display in the tree.
22289 store: null,
22290
22291 // model: dijit.Tree.model
22292 // Interface to read tree data, get notifications of changes to tree data,
22293 // and for handling drop operations (i.e drag and drop onto the tree)
22294 model: null,
22295
22296 // query: [deprecated] anything
22297 // Deprecated. User should specify query to the model directly instead.
22298 // Specifies datastore query to return the root item or top items for the tree.
22299 query: null,
22300
22301 // label: [deprecated] String
22302 // Deprecated. Use dijit.tree.ForestStoreModel directly instead.
22303 // Used in conjunction with query parameter.
22304 // If a query is specified (rather than a root node id), and a label is also specified,
22305 // then a fake root node is created and displayed, with this label.
22306 label: "",
22307
22308 // showRoot: [const] Boolean
22309 // Should the root node be displayed, or hidden?
22310 showRoot: true,
22311
22312 // childrenAttr: [deprecated] String[]
22313 // Deprecated. This information should be specified in the model.
22314 // One ore more attributes that holds children of a tree node
22315 childrenAttr: ["children"],
22316
22317 // path: String[] or Item[]
22318 // Full path from rootNode to selected node expressed as array of items or array of ids.
22319 // Since setting the path may be asynchronous (because ofwaiting on dojo.data), set("path", ...)
22320 // returns a Deferred to indicate when the set is complete.
22321 path: [],
22322
22323 // selectedItem: [readonly] Item
22324 // The currently selected item in this tree.
22325 // This property can only be set (via set('selectedItem', ...)) when that item is already
22326 // visible in the tree. (I.e. the tree has already been expanded to show that node.)
22327 // Should generally use `path` attribute to set the selected item instead.
22328 selectedItem: null,
22329
22330 // openOnClick: Boolean
22331 // If true, clicking a folder node's label will open it, rather than calling onClick()
22332 openOnClick: false,
22333
22334 // openOnDblClick: Boolean
22335 // If true, double-clicking a folder node's label will open it, rather than calling onDblClick()
22336 openOnDblClick: false,
22337
22338 templateString: dojo.cache("dijit", "templates/Tree.html", "<div class=\"dijitTree dijitTreeContainer\" waiRole=\"tree\"\n\tdojoAttachEvent=\"onkeypress:_onKeyPress\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" dojoAttachPoint=\"indentDetector\"></div>\n</div>\n"),
22339
22340 // persist: Boolean
22341 // Enables/disables use of cookies for state saving.
22342 persist: true,
22343
22344 // autoExpand: Boolean
22345 // Fully expand the tree on load. Overrides `persist`
22346 autoExpand: false,
22347
22348 // dndController: [protected] String
22349 // Class name to use as as the dnd controller. Specifying this class enables DnD.
22350 // Generally you should specify this as "dijit.tree.dndSource".
22351 dndController: null,
22352
22353 // parameters to pull off of the tree and pass on to the dndController as its params
22354 dndParams: ["onDndDrop","itemCreator","onDndCancel","checkAcceptance", "checkItemAcceptance", "dragThreshold", "betweenThreshold"],
22355
22356 //declare the above items so they can be pulled from the tree's markup
22357
22358 // onDndDrop: [protected] Function
22359 // Parameter to dndController, see `dijit.tree.dndSource.onDndDrop`.
22360 // Generally this doesn't need to be set.
22361 onDndDrop: null,
22362
22363 /*=====
22364 itemCreator: function(nodes, target, source){
22365 // summary:
22366 // Returns objects passed to `Tree.model.newItem()` based on DnD nodes
22367 // dropped onto the tree. Developer must override this method to enable
22368 // dropping from external sources onto this Tree, unless the Tree.model's items
22369 // happen to look like {id: 123, name: "Apple" } with no other attributes.
22370 // description:
22371 // For each node in nodes[], which came from source, create a hash of name/value
22372 // pairs to be passed to Tree.model.newItem(). Returns array of those hashes.
22373 // nodes: DomNode[]
22374 // The DOMNodes dragged from the source container
22375 // target: DomNode
22376 // The target TreeNode.rowNode
22377 // source: dojo.dnd.Source
22378 // The source container the nodes were dragged from, perhaps another Tree or a plain dojo.dnd.Source
22379 // returns: Object[]
22380 // Array of name/value hashes for each new item to be added to the Tree, like:
22381 // | [
22382 // | { id: 123, label: "apple", foo: "bar" },
22383 // | { id: 456, label: "pear", zaz: "bam" }
22384 // | ]
22385 // tags:
22386 // extension
22387 return [{}];
22388 },
22389 =====*/
22390 itemCreator: null,
22391
22392 // onDndCancel: [protected] Function
22393 // Parameter to dndController, see `dijit.tree.dndSource.onDndCancel`.
22394 // Generally this doesn't need to be set.
22395 onDndCancel: null,
22396
22397 /*=====
22398 checkAcceptance: function(source, nodes){
22399 // summary:
22400 // Checks if the Tree itself can accept nodes from this source
22401 // source: dijit.tree._dndSource
22402 // The source which provides items
22403 // nodes: DOMNode[]
22404 // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if
22405 // source is a dijit.Tree.
22406 // tags:
22407 // extension
22408 return true; // Boolean
22409 },
22410 =====*/
22411 checkAcceptance: null,
22412
22413 /*=====
22414 checkItemAcceptance: function(target, source, position){
22415 // summary:
22416 // Stub function to be overridden if one wants to check for the ability to drop at the node/item level
22417 // description:
22418 // In the base case, this is called to check if target can become a child of source.
22419 // When betweenThreshold is set, position="before" or "after" means that we
22420 // are asking if the source node can be dropped before/after the target node.
22421 // target: DOMNode
22422 // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to
22423 // Use dijit.getEnclosingWidget(target) to get the TreeNode.
22424 // source: dijit.tree.dndSource
22425 // The (set of) nodes we are dropping
22426 // position: String
22427 // "over", "before", or "after"
22428 // tags:
22429 // extension
22430 return true; // Boolean
22431 },
22432 =====*/
22433 checkItemAcceptance: null,
22434
22435 // dragThreshold: Integer
22436 // Number of pixels mouse moves before it's considered the start of a drag operation
22437 dragThreshold: 5,
22438
22439 // betweenThreshold: Integer
22440 // Set to a positive value to allow drag and drop "between" nodes.
22441 //
22442 // If during DnD mouse is over a (target) node but less than betweenThreshold
22443 // pixels from the bottom edge, dropping the the dragged node will make it
22444 // the next sibling of the target node, rather than the child.
22445 //
22446 // Similarly, if mouse is over a target node but less that betweenThreshold
22447 // pixels from the top edge, dropping the dragged node will make it
22448 // the target node's previous sibling rather than the target node's child.
22449 betweenThreshold: 0,
22450
22451 // _nodePixelIndent: Integer
22452 // Number of pixels to indent tree nodes (relative to parent node).
22453 // Default is 19 but can be overridden by setting CSS class dijitTreeIndent
22454 // and calling resize() or startup() on tree after it's in the DOM.
22455 _nodePixelIndent: 19,
22456
22457 _publish: function(/*String*/ topicName, /*Object*/ message){
22458 // summary:
22459 // Publish a message for this widget/topic
22460 dojo.publish(this.id, [dojo.mixin({tree: this, event: topicName}, message || {})]);
22461 },
22462
22463 postMixInProperties: function(){
22464 this.tree = this;
22465
22466 if(this.autoExpand){
22467 // There's little point in saving opened/closed state of nodes for a Tree
22468 // that initially opens all it's nodes.
22469 this.persist = false;
22470 }
22471
22472 this._itemNodesMap={};
22473
22474 if(!this.cookieName){
22475 this.cookieName = this.id + "SaveStateCookie";
22476 }
22477
22478 this._loadDeferred = new dojo.Deferred();
22479
22480 this.inherited(arguments);
22481 },
22482
22483 postCreate: function(){
22484 this._initState();
22485
22486 // Create glue between store and Tree, if not specified directly by user
22487 if(!this.model){
22488 this._store2model();
22489 }
22490
22491 // monitor changes to items
22492 this.connect(this.model, "onChange", "_onItemChange");
22493 this.connect(this.model, "onChildrenChange", "_onItemChildrenChange");
22494 this.connect(this.model, "onDelete", "_onItemDelete");
22495
22496 this._load();
22497
22498 this.inherited(arguments);
22499
22500 if(this.dndController){
22501 if(dojo.isString(this.dndController)){
22502 this.dndController = dojo.getObject(this.dndController);
22503 }
22504 var params={};
22505 for(var i=0; i<this.dndParams.length;i++){
22506 if(this[this.dndParams[i]]){
22507 params[this.dndParams[i]] = this[this.dndParams[i]];
22508 }
22509 }
22510 this.dndController = new this.dndController(this, params);
22511 }
22512 },
22513
22514 _store2model: function(){
22515 // summary:
22516 // User specified a store&query rather than model, so create model from store/query
22517 this._v10Compat = true;
22518 dojo.deprecated("Tree: from version 2.0, should specify a model object rather than a store/query");
22519
22520 var modelParams = {
22521 id: this.id + "_ForestStoreModel",
22522 store: this.store,
22523 query: this.query,
22524 childrenAttrs: this.childrenAttr
22525 };
22526
22527 // Only override the model's mayHaveChildren() method if the user has specified an override
22528 if(this.params.mayHaveChildren){
22529 modelParams.mayHaveChildren = dojo.hitch(this, "mayHaveChildren");
22530 }
22531
22532 if(this.params.getItemChildren){
22533 modelParams.getChildren = dojo.hitch(this, function(item, onComplete, onError){
22534 this.getItemChildren((this._v10Compat && item === this.model.root) ? null : item, onComplete, onError);
22535 });
22536 }
22537 this.model = new dijit.tree.ForestStoreModel(modelParams);
22538
22539 // For backwards compatibility, the visibility of the root node is controlled by
22540 // whether or not the user has specified a label
22541 this.showRoot = Boolean(this.label);
22542 },
22543
22544 onLoad: function(){
22545 // summary:
22546 // Called when tree finishes loading and expanding.
22547 // description:
22548 // If persist == true the loading may encompass many levels of fetches
22549 // from the data store, each asynchronous. Waits for all to finish.
22550 // tags:
22551 // callback
22552 },
22553
22554 _load: function(){
22555 // summary:
22556 // Initial load of the tree.
22557 // Load root node (possibly hidden) and it's children.
22558 this.model.getRoot(
22559 dojo.hitch(this, function(item){
22560 var rn = (this.rootNode = this.tree._createTreeNode({
22561 item: item,
22562 tree: this,
22563 isExpandable: true,
22564 label: this.label || this.getLabel(item),
22565 indent: this.showRoot ? 0 : -1
22566 }));
22567 if(!this.showRoot){
22568 rn.rowNode.style.display="none";
22569 }
22570 this.domNode.appendChild(rn.domNode);
22571 var identity = this.model.getIdentity(item);
22572 if(this._itemNodesMap[identity]){
22573 this._itemNodesMap[identity].push(rn);
22574 }else{
22575 this._itemNodesMap[identity] = [rn];
22576 }
22577
22578 rn._updateLayout(); // sets "dijitTreeIsRoot" CSS classname
22579
22580 // load top level children and then fire onLoad() event
22581 this._expandNode(rn).addCallback(dojo.hitch(this, function(){
22582 this._loadDeferred.callback(true);
22583 this.onLoad();
22584 }));
22585 }),
22586 function(err){
22587 console.error(this, ": error loading root: ", err);
22588 }
22589 );
22590 },
22591
22592 getNodesByItem: function(/*dojo.data.Item or id*/ item){
22593 // summary:
22594 // Returns all tree nodes that refer to an item
22595 // returns:
22596 // Array of tree nodes that refer to passed item
22597
22598 if(!item){ return []; }
22599 var identity = dojo.isString(item) ? item : this.model.getIdentity(item);
22600 // return a copy so widget don't get messed up by changes to returned array
22601 return [].concat(this._itemNodesMap[identity]);
22602 },
22603
22604 _setSelectedItemAttr: function(/*dojo.data.Item or id*/ item){
22605 // summary:
22606 // Select a tree node related to passed item.
22607 // WARNING: if model use multi-parented items or desired tree node isn't already loaded
22608 // behavior is undefined. Use set('path', ...) instead.
22609
22610 var oldValue = this.get("selectedItem");
22611 var identity = (!item || dojo.isString(item)) ? item : this.model.getIdentity(item);
22612 if(identity == oldValue ? this.model.getIdentity(oldValue) : null){ return; }
22613 var nodes = this._itemNodesMap[identity];
22614 this._selectNode((nodes && nodes[0]) || null); //select the first item
22615 },
22616
22617 _getSelectedItemAttr: function(){
22618 // summary:
22619 // Return item related to selected tree node.
22620 return this.selectedNode && this.selectedNode.item;
22621 },
22622
22623 _setPathAttr: function(/*Item[] || String[]*/ path){
22624 // summary:
22625 // Select the tree node identified by passed path.
22626 // path:
22627 // Array of items or item id's
22628 // returns:
22629 // Deferred to indicate when the set is complete
22630
22631 var d = new dojo.Deferred();
22632
22633 this._selectNode(null);
22634 if(!path || !path.length){
22635 d.resolve(true);
22636 return d;
22637 }
22638
22639 // If this is called during initialization, defer running until Tree has finished loading
22640 this._loadDeferred.addCallback(dojo.hitch(this, function(){
22641 if(!this.rootNode){
22642 d.reject(new Error("!this.rootNode"));
22643 return;
22644 }
22645 if(path[0] !== this.rootNode.item && (dojo.isString(path[0]) && path[0] != this.model.getIdentity(this.rootNode.item))){
22646 d.reject(new Error(this.id + ":path[0] doesn't match this.rootNode.item. Maybe you are using the wrong tree."));
22647 return;
22648 }
22649 path.shift();
22650
22651 var node = this.rootNode;
22652
22653 function advance(){
22654 // summary:
22655 // Called when "node" has completed loading and expanding. Pop the next item from the path
22656 // (which must be a child of "node") and advance to it, and then recurse.
22657
22658 // Set item and identity to next item in path (node is pointing to the item that was popped
22659 // from the path _last_ time.
22660 var item = path.shift(),
22661 identity = dojo.isString(item) ? item : this.model.getIdentity(item);
22662
22663 // Change "node" from previous item in path to the item we just popped from path
22664 dojo.some(this._itemNodesMap[identity], function(n){
22665 if(n.getParent() == node){
22666 node = n;
22667 return true;
22668 }
22669 return false;
22670 });
22671
22672 if(path.length){
22673 // Need to do more expanding
22674 this._expandNode(node).addCallback(dojo.hitch(this, advance));
22675 }else{
22676 // Final destination node, select it
22677 this._selectNode(node);
22678
22679 // signal that path setting is finished
22680 d.resolve(true);
22681 }
22682 }
22683
22684 this._expandNode(node).addCallback(dojo.hitch(this, advance));
22685 }));
22686
22687 return d;
22688 },
22689
22690 _getPathAttr: function(){
22691 // summary:
22692 // Return an array of items that is the path to selected tree node.
22693 if(!this.selectedNode){ return; }
22694 var res = [];
22695 var treeNode = this.selectedNode;
22696 while(treeNode && treeNode !== this.rootNode){
22697 res.unshift(treeNode.item);
22698 treeNode = treeNode.getParent();
22699 }
22700 res.unshift(this.rootNode.item);
22701 return res;
22702 },
22703
22704 ////////////// Data store related functions //////////////////////
22705 // These just get passed to the model; they are here for back-compat
22706
22707 mayHaveChildren: function(/*dojo.data.Item*/ item){
22708 // summary:
22709 // Deprecated. This should be specified on the model itself.
22710 //
22711 // Overridable function to tell if an item has or may have children.
22712 // Controls whether or not +/- expando icon is shown.
22713 // (For efficiency reasons we may not want to check if an element actually
22714 // has children until user clicks the expando node)
22715 // tags:
22716 // deprecated
22717 },
22718
22719 getItemChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete){
22720 // summary:
22721 // Deprecated. This should be specified on the model itself.
22722 //
22723 // Overridable function that return array of child items of given parent item,
22724 // or if parentItem==null then return top items in tree
22725 // tags:
22726 // deprecated
22727 },
22728
22729 ///////////////////////////////////////////////////////
22730 // Functions for converting an item to a TreeNode
22731 getLabel: function(/*dojo.data.Item*/ item){
22732 // summary:
22733 // Overridable function to get the label for a tree node (given the item)
22734 // tags:
22735 // extension
22736 return this.model.getLabel(item); // String
22737 },
22738
22739 getIconClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
22740 // summary:
22741 // Overridable function to return CSS class name to display icon
22742 // tags:
22743 // extension
22744 return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf"
22745 },
22746
22747 getLabelClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
22748 // summary:
22749 // Overridable function to return CSS class name to display label
22750 // tags:
22751 // extension
22752 },
22753
22754 getRowClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
22755 // summary:
22756 // Overridable function to return CSS class name to display row
22757 // tags:
22758 // extension
22759 },
22760
22761 getIconStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
22762 // summary:
22763 // Overridable function to return CSS styles to display icon
22764 // returns:
22765 // Object suitable for input to dojo.style() like {backgroundImage: "url(...)"}
22766 // tags:
22767 // extension
22768 },
22769
22770 getLabelStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
22771 // summary:
22772 // Overridable function to return CSS styles to display label
22773 // returns:
22774 // Object suitable for input to dojo.style() like {color: "red", background: "green"}
22775 // tags:
22776 // extension
22777 },
22778
22779 getRowStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
22780 // summary:
22781 // Overridable function to return CSS styles to display row
22782 // returns:
22783 // Object suitable for input to dojo.style() like {background-color: "#bbb"}
22784 // tags:
22785 // extension
22786 },
22787
22788 getTooltip: function(/*dojo.data.Item*/ item){
22789 // summary:
22790 // Overridable function to get the tooltip for a tree node (given the item)
22791 // tags:
22792 // extension
22793 return ""; // String
22794 },
22795
22796 /////////// Keyboard and Mouse handlers ////////////////////
22797
22798 _onKeyPress: function(/*Event*/ e){
22799 // summary:
22800 // Translates keypress events into commands for the controller
22801 if(e.altKey){ return; }
22802 var dk = dojo.keys;
22803 var treeNode = dijit.getEnclosingWidget(e.target);
22804 if(!treeNode){ return; }
22805
22806 var key = e.charOrCode;
22807 if(typeof key == "string"){ // handle printables (letter navigation)
22808 // Check for key navigation.
22809 if(!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey){
22810 this._onLetterKeyNav( { node: treeNode, key: key.toLowerCase() } );
22811 dojo.stopEvent(e);
22812 }
22813 }else{ // handle non-printables (arrow keys)
22814 // clear record of recent printables (being saved for multi-char letter navigation),
22815 // because "a", down-arrow, "b" shouldn't search for "ab"
22816 if(this._curSearch){
22817 clearTimeout(this._curSearch.timer);
22818 delete this._curSearch;
22819 }
22820
22821 var map = this._keyHandlerMap;
22822 if(!map){
22823 // setup table mapping keys to events
22824 map = {};
22825 map[dk.ENTER]="_onEnterKey";
22826 map[this.isLeftToRight() ? dk.LEFT_ARROW : dk.RIGHT_ARROW]="_onLeftArrow";
22827 map[this.isLeftToRight() ? dk.RIGHT_ARROW : dk.LEFT_ARROW]="_onRightArrow";
22828 map[dk.UP_ARROW]="_onUpArrow";
22829 map[dk.DOWN_ARROW]="_onDownArrow";
22830 map[dk.HOME]="_onHomeKey";
22831 map[dk.END]="_onEndKey";
22832 this._keyHandlerMap = map;
22833 }
22834 if(this._keyHandlerMap[key]){
22835 this[this._keyHandlerMap[key]]( { node: treeNode, item: treeNode.item, evt: e } );
22836 dojo.stopEvent(e);
22837 }
22838 }
22839 },
22840
22841 _onEnterKey: function(/*Object*/ message, /*Event*/ evt){
22842 this._publish("execute", { item: message.item, node: message.node } );
22843 this._selectNode(message.node);
22844 this.onClick(message.item, message.node, evt);
22845 },
22846
22847 _onDownArrow: function(/*Object*/ message){
22848 // summary:
22849 // down arrow pressed; get next visible node, set focus there
22850 var node = this._getNextNode(message.node);
22851 if(node && node.isTreeNode){
22852 this.focusNode(node);
22853 }
22854 },
22855
22856 _onUpArrow: function(/*Object*/ message){
22857 // summary:
22858 // Up arrow pressed; move to previous visible node
22859
22860 var node = message.node;
22861
22862 // if younger siblings
22863 var previousSibling = node.getPreviousSibling();
22864 if(previousSibling){
22865 node = previousSibling;
22866 // if the previous node is expanded, dive in deep
22867 while(node.isExpandable && node.isExpanded && node.hasChildren()){
22868 // move to the last child
22869 var children = node.getChildren();
22870 node = children[children.length-1];
22871 }
22872 }else{
22873 // if this is the first child, return the parent
22874 // unless the parent is the root of a tree with a hidden root
22875 var parent = node.getParent();
22876 if(!(!this.showRoot && parent === this.rootNode)){
22877 node = parent;
22878 }
22879 }
22880
22881 if(node && node.isTreeNode){
22882 this.focusNode(node);
22883 }
22884 },
22885
22886 _onRightArrow: function(/*Object*/ message){
22887 // summary:
22888 // Right arrow pressed; go to child node
22889 var node = message.node;
22890
22891 // if not expanded, expand, else move to 1st child
22892 if(node.isExpandable && !node.isExpanded){
22893 this._expandNode(node);
22894 }else if(node.hasChildren()){
22895 node = node.getChildren()[0];
22896 if(node && node.isTreeNode){
22897 this.focusNode(node);
22898 }
22899 }
22900 },
22901
22902 _onLeftArrow: function(/*Object*/ message){
22903 // summary:
22904 // Left arrow pressed.
22905 // If not collapsed, collapse, else move to parent.
22906
22907 var node = message.node;
22908
22909 if(node.isExpandable && node.isExpanded){
22910 this._collapseNode(node);
22911 }else{
22912 var parent = node.getParent();
22913 if(parent && parent.isTreeNode && !(!this.showRoot && parent === this.rootNode)){
22914 this.focusNode(parent);
22915 }
22916 }
22917 },
22918
22919 _onHomeKey: function(){
22920 // summary:
22921 // Home key pressed; get first visible node, and set focus there
22922 var node = this._getRootOrFirstNode();
22923 if(node){
22924 this.focusNode(node);
22925 }
22926 },
22927
22928 _onEndKey: function(/*Object*/ message){
22929 // summary:
22930 // End key pressed; go to last visible node.
22931
22932 var node = this.rootNode;
22933 while(node.isExpanded){
22934 var c = node.getChildren();
22935 node = c[c.length - 1];
22936 }
22937
22938 if(node && node.isTreeNode){
22939 this.focusNode(node);
22940 }
22941 },
22942
22943 // multiCharSearchDuration: Number
22944 // If multiple characters are typed where each keystroke happens within
22945 // multiCharSearchDuration of the previous keystroke,
22946 // search for nodes matching all the keystrokes.
22947 //
22948 // For example, typing "ab" will search for entries starting with
22949 // "ab" unless the delay between "a" and "b" is greater than multiCharSearchDuration.
22950 multiCharSearchDuration: 250,
22951
22952 _onLetterKeyNav: function(message){
22953 // summary:
22954 // Called when user presses a prinatable key; search for node starting with recently typed letters.
22955 // message: Object
22956 // Like { node: TreeNode, key: 'a' } where key is the key the user pressed.
22957
22958 // Branch depending on whether this key starts a new search, or modifies an existing search
22959 var cs = this._curSearch;
22960 if(cs){
22961 // We are continuing a search. Ex: user has pressed 'a', and now has pressed
22962 // 'b', so we want to search for nodes starting w/"ab".
22963 cs.pattern = cs.pattern + message.key;
22964 clearTimeout(cs.timer);
22965 }else{
22966 // We are starting a new search
22967 cs = this._curSearch = {
22968 pattern: message.key,
22969 startNode: message.node
22970 };
22971 }
22972
22973 // set/reset timer to forget recent keystrokes
22974 var self = this;
22975 cs.timer = setTimeout(function(){
22976 delete self._curSearch;
22977 }, this.multiCharSearchDuration);
22978
22979 // Navigate to TreeNode matching keystrokes [entered so far].
22980 var node = cs.startNode;
22981 do{
22982 node = this._getNextNode(node);
22983 //check for last node, jump to first node if necessary
22984 if(!node){
22985 node = this._getRootOrFirstNode();
22986 }
22987 }while(node !== cs.startNode && (node.label.toLowerCase().substr(0, cs.pattern.length) != cs.pattern));
22988 if(node && node.isTreeNode){
22989 // no need to set focus if back where we started
22990 if(node !== cs.startNode){
22991 this.focusNode(node);
22992 }
22993 }
22994 },
22995
22996 _onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){
22997 // summary:
22998 // Translates click events into commands for the controller to process
22999
23000 var domElement = e.target,
23001 isExpandoClick = (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText);
23002
23003 if( (this.openOnClick && nodeWidget.isExpandable) || isExpandoClick ){
23004 // expando node was clicked, or label of a folder node was clicked; open it
23005 if(nodeWidget.isExpandable){
23006 this._onExpandoClick({node:nodeWidget});
23007 }
23008 }else{
23009 this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } );
23010 this.onClick(nodeWidget.item, nodeWidget, e);
23011 this.focusNode(nodeWidget);
23012 }
23013 if(!isExpandoClick){
23014 this._selectNode(nodeWidget);
23015 }
23016 dojo.stopEvent(e);
23017 },
23018 _onDblClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){
23019 // summary:
23020 // Translates double-click events into commands for the controller to process
23021
23022 var domElement = e.target,
23023 isExpandoClick = (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText);
23024
23025 if( (this.openOnDblClick && nodeWidget.isExpandable) ||isExpandoClick ){
23026 // expando node was clicked, or label of a folder node was clicked; open it
23027 if(nodeWidget.isExpandable){
23028 this._onExpandoClick({node:nodeWidget});
23029 }
23030 }else{
23031 this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } );
23032 this.onDblClick(nodeWidget.item, nodeWidget, e);
23033 this.focusNode(nodeWidget);
23034 }
23035 if(!isExpandoClick){
23036 this._selectNode(nodeWidget);
23037 }
23038 dojo.stopEvent(e);
23039 },
23040
23041 _onExpandoClick: function(/*Object*/ message){
23042 // summary:
23043 // User clicked the +/- icon; expand or collapse my children.
23044 var node = message.node;
23045
23046 // If we are collapsing, we might be hiding the currently focused node.
23047 // Also, clicking the expando node might have erased focus from the current node.
23048 // For simplicity's sake just focus on the node with the expando.
23049 this.focusNode(node);
23050
23051 if(node.isExpanded){
23052 this._collapseNode(node);
23053 }else{
23054 this._expandNode(node);
23055 }
23056 },
23057
23058 onClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){
23059 // summary:
23060 // Callback when a tree node is clicked
23061 // tags:
23062 // callback
23063 },
23064 onDblClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){
23065 // summary:
23066 // Callback when a tree node is double-clicked
23067 // tags:
23068 // callback
23069 },
23070 onOpen: function(/* dojo.data */ item, /*TreeNode*/ node){
23071 // summary:
23072 // Callback when a node is opened
23073 // tags:
23074 // callback
23075 },
23076 onClose: function(/* dojo.data */ item, /*TreeNode*/ node){
23077 // summary:
23078 // Callback when a node is closed
23079 // tags:
23080 // callback
23081 },
23082
23083 _getNextNode: function(node){
23084 // summary:
23085 // Get next visible node
23086
23087 if(node.isExpandable && node.isExpanded && node.hasChildren()){
23088 // if this is an expanded node, get the first child
23089 return node.getChildren()[0]; // _TreeNode
23090 }else{
23091 // find a parent node with a sibling
23092 while(node && node.isTreeNode){
23093 var returnNode = node.getNextSibling();
23094 if(returnNode){
23095 return returnNode; // _TreeNode
23096 }
23097 node = node.getParent();
23098 }
23099 return null;
23100 }
23101 },
23102
23103 _getRootOrFirstNode: function(){
23104 // summary:
23105 // Get first visible node
23106 return this.showRoot ? this.rootNode : this.rootNode.getChildren()[0];
23107 },
23108
23109 _collapseNode: function(/*_TreeNode*/ node){
23110 // summary:
23111 // Called when the user has requested to collapse the node
23112
23113 if(node._expandNodeDeferred){
23114 delete node._expandNodeDeferred;
23115 }
23116
23117 if(node.isExpandable){
23118 if(node.state == "LOADING"){
23119 // ignore clicks while we are in the process of loading data
23120 return;
23121 }
23122
23123 node.collapse();
23124 this.onClose(node.item, node);
23125
23126 if(node.item){
23127 this._state(node.item,false);
23128 this._saveState();
23129 }
23130 }
23131 },
23132
23133 _expandNode: function(/*_TreeNode*/ node, /*Boolean?*/ recursive){
23134 // summary:
23135 // Called when the user has requested to expand the node
23136 // recursive:
23137 // Internal flag used when _expandNode() calls itself, don't set.
23138 // returns:
23139 // Deferred that fires when the node is loaded and opened and (if persist=true) all it's descendants
23140 // that were previously opened too
23141
23142 if(node._expandNodeDeferred && !recursive){
23143 // there's already an expand in progress (or completed), so just return
23144 return node._expandNodeDeferred; // dojo.Deferred
23145 }
23146
23147 var model = this.model,
23148 item = node.item,
23149 _this = this;
23150
23151 switch(node.state){
23152 case "UNCHECKED":
23153 // need to load all the children, and then expand
23154 node.markProcessing();
23155
23156 // Setup deferred to signal when the load and expand are finished.
23157 // Save that deferred in this._expandDeferred as a flag that operation is in progress.
23158 var def = (node._expandNodeDeferred = new dojo.Deferred());
23159
23160 // Get the children
23161 model.getChildren(
23162 item,
23163 function(items){
23164 node.unmarkProcessing();
23165
23166 // Display the children and also start expanding any children that were previously expanded
23167 // (if this.persist == true). The returned Deferred will fire when those expansions finish.
23168 var scid = node.setChildItems(items);
23169
23170 // Call _expandNode() again but this time it will just to do the animation (default branch).
23171 // The returned Deferred will fire when the animation completes.
23172 // TODO: seems like I can avoid recursion and just use a deferred to sequence the events?
23173 var ed = _this._expandNode(node, true);
23174
23175 // After the above two tasks (setChildItems() and recursive _expandNode()) finish,
23176 // signal that I am done.
23177 scid.addCallback(function(){
23178 ed.addCallback(function(){
23179 def.callback();
23180 })
23181 });
23182 },
23183 function(err){
23184 console.error(_this, ": error loading root children: ", err);
23185 }
23186 );
23187 break;
23188
23189 default: // "LOADED"
23190 // data is already loaded; just expand node
23191 def = (node._expandNodeDeferred = node.expand());
23192
23193 this.onOpen(node.item, node);
23194
23195 if(item){
23196 this._state(item, true);
23197 this._saveState();
23198 }
23199 }
23200
23201 return def; // dojo.Deferred
23202 },
23203
23204 ////////////////// Miscellaneous functions ////////////////
23205
23206 focusNode: function(/* _tree.Node */ node){
23207 // summary:
23208 // Focus on the specified node (which must be visible)
23209 // tags:
23210 // protected
23211
23212 // set focus so that the label will be voiced using screen readers
23213 dijit.focus(node.labelNode);
23214 },
23215
23216 _selectNode: function(/*_tree.Node*/ node){
23217 // summary:
23218 // Mark specified node as select, and unmark currently selected node.
23219 // tags:
23220 // protected
23221
23222 if(this.selectedNode && !this.selectedNode._destroyed){
23223 this.selectedNode.setSelected(false);
23224 }
23225 if(node){
23226 node.setSelected(true);
23227 }
23228 this.selectedNode = node;
23229 },
23230
23231 _onNodeFocus: function(/*dijit._Widget*/ node){
23232 // summary:
23233 // Called when a TreeNode gets focus, either by user clicking
23234 // it, or programatically by arrow key handling code.
23235 // description:
23236 // It marks that the current node is the selected one, and the previously
23237 // selected node no longer is.
23238
23239 if(node && node != this.lastFocused){
23240 if(this.lastFocused && !this.lastFocused._destroyed){
23241 // mark that the previously focsable node is no longer focusable
23242 this.lastFocused.setFocusable(false);
23243 }
23244
23245 // mark that the new node is the currently selected one
23246 node.setFocusable(true);
23247 this.lastFocused = node;
23248 }
23249 },
23250
23251 _onNodeMouseEnter: function(/*dijit._Widget*/ node){
23252 // summary:
23253 // Called when mouse is over a node (onmouseenter event),
23254 // this is monitored by the DND code
23255 },
23256
23257 _onNodeMouseLeave: function(/*dijit._Widget*/ node){
23258 // summary:
23259 // Called when mouse leaves a node (onmouseleave event),
23260 // this is monitored by the DND code
23261 },
23262
23263 //////////////// Events from the model //////////////////////////
23264
23265 _onItemChange: function(/*Item*/ item){
23266 // summary:
23267 // Processes notification of a change to an item's scalar values like label
23268 var model = this.model,
23269 identity = model.getIdentity(item),
23270 nodes = this._itemNodesMap[identity];
23271
23272 if(nodes){
23273 var label = this.getLabel(item),
23274 tooltip = this.getTooltip(item);
23275 dojo.forEach(nodes, function(node){
23276 node.set({
23277 item: item, // theoretically could be new JS Object representing same item
23278 label: label,
23279 tooltip: tooltip
23280 });
23281 node._updateItemClasses(item);
23282 });
23283 }
23284 },
23285
23286 _onItemChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
23287 // summary:
23288 // Processes notification of a change to an item's children
23289 var model = this.model,
23290 identity = model.getIdentity(parent),
23291 parentNodes = this._itemNodesMap[identity];
23292
23293 if(parentNodes){
23294 dojo.forEach(parentNodes,function(parentNode){
23295 parentNode.setChildItems(newChildrenList);
23296 });
23297 }
23298 },
23299
23300 _onItemDelete: function(/*Item*/ item){
23301 // summary:
23302 // Processes notification of a deletion of an item
23303 var model = this.model,
23304 identity = model.getIdentity(item),
23305 nodes = this._itemNodesMap[identity];
23306
23307 if(nodes){
23308 dojo.forEach(nodes,function(node){
23309 var parent = node.getParent();
23310 if(parent){
23311 // if node has not already been orphaned from a _onSetItem(parent, "children", ..) call...
23312 parent.removeChild(node);
23313 }
23314 node.destroyRecursive();
23315 });
23316 delete this._itemNodesMap[identity];
23317 }
23318 },
23319
23320 /////////////// Miscellaneous funcs
23321
23322 _initState: function(){
23323 // summary:
23324 // Load in which nodes should be opened automatically
23325 if(this.persist){
23326 var cookie = dojo.cookie(this.cookieName);
23327 this._openedItemIds = {};
23328 if(cookie){
23329 dojo.forEach(cookie.split(','), function(item){
23330 this._openedItemIds[item] = true;
23331 }, this);
23332 }
23333 }
23334 },
23335 _state: function(item,expanded){
23336 // summary:
23337 // Query or set expanded state for an item,
23338 if(!this.persist){
23339 return false;
23340 }
23341 var id=this.model.getIdentity(item);
23342 if(arguments.length === 1){
23343 return this._openedItemIds[id];
23344 }
23345 if(expanded){
23346 this._openedItemIds[id] = true;
23347 }else{
23348 delete this._openedItemIds[id];
23349 }
23350 },
23351 _saveState: function(){
23352 // summary:
23353 // Create and save a cookie with the currently expanded nodes identifiers
23354 if(!this.persist){
23355 return;
23356 }
23357 var ary = [];
23358 for(var id in this._openedItemIds){
23359 ary.push(id);
23360 }
23361 dojo.cookie(this.cookieName, ary.join(","), {expires:365});
23362 },
23363
23364 destroy: function(){
23365 if(this._curSearch){
23366 clearTimeout(this._curSearch.timer);
23367 delete this._curSearch;
23368 }
23369 if(this.rootNode){
23370 this.rootNode.destroyRecursive();
23371 }
23372 if(this.dndController && !dojo.isString(this.dndController)){
23373 this.dndController.destroy();
23374 }
23375 this.rootNode = null;
23376 this.inherited(arguments);
23377 },
23378
23379 destroyRecursive: function(){
23380 // A tree is treated as a leaf, not as a node with children (like a grid),
23381 // but defining destroyRecursive for back-compat.
23382 this.destroy();
23383 },
23384
23385 resize: function(changeSize){
23386 if(changeSize){
23387 dojo.marginBox(this.domNode, changeSize);
23388 dojo.style(this.domNode, "overflow", "auto"); // for scrollbars
23389 }
23390
23391 // The only JS sizing involved w/tree is the indentation, which is specified
23392 // in CSS and read in through this dummy indentDetector node (tree must be
23393 // visible and attached to the DOM to read this)
23394 this._nodePixelIndent = dojo.marginBox(this.tree.indentDetector).w;
23395
23396 if(this.tree.rootNode){
23397 // If tree has already loaded, then reset indent for all the nodes
23398 this.tree.rootNode.set('indent', this.showRoot ? 0 : -1);
23399 }
23400 },
23401
23402 _createTreeNode: function(/*Object*/ args){
23403 // summary:
23404 // creates a TreeNode
23405 // description:
23406 // Developers can override this method to define their own TreeNode class;
23407 // However it will probably be removed in a future release in favor of a way
23408 // of just specifying a widget for the label, rather than one that contains
23409 // the children too.
23410 return new dijit._TreeNode(args);
23411 }
23412 });
23413
23414 // For back-compat. TODO: remove in 2.0
23415
23416
23417
23418 }
23419
23420 if(!dojo._hasResource["dojo.dnd.Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
23421 dojo._hasResource["dojo.dnd.Container"] = true;
23422 dojo.provide("dojo.dnd.Container");
23423
23424
23425
23426
23427 /*
23428 Container states:
23429 "" - normal state
23430 "Over" - mouse over a container
23431 Container item states:
23432 "" - normal state
23433 "Over" - mouse over a container item
23434 */
23435
23436 /*=====
23437 dojo.declare("dojo.dnd.__ContainerArgs", [], {
23438 creator: function(){
23439 // summary:
23440 // a creator function, which takes a data item, and returns an object like that:
23441 // {node: newNode, data: usedData, type: arrayOfStrings}
23442 },
23443
23444 // skipForm: Boolean
23445 // don't start the drag operation, if clicked on form elements
23446 skipForm: false,
23447
23448 // dropParent: Node||String
23449 // node or node's id to use as the parent node for dropped items
23450 // (must be underneath the 'node' parameter in the DOM)
23451 dropParent: null,
23452
23453 // _skipStartup: Boolean
23454 // skip startup(), which collects children, for deferred initialization
23455 // (this is used in the markup mode)
23456 _skipStartup: false
23457 });
23458
23459 dojo.dnd.Item = function(){
23460 // summary:
23461 // Represents (one of) the source node(s) being dragged.
23462 // Contains (at least) the "type" and "data" attributes.
23463 // type: String[]
23464 // Type(s) of this item, by default this is ["text"]
23465 // data: Object
23466 // Logical representation of the object being dragged.
23467 // If the drag object's type is "text" then data is a String,
23468 // if it's another type then data could be a different Object,
23469 // perhaps a name/value hash.
23470
23471 this.type = type;
23472 this.data = data;
23473 }
23474 =====*/
23475
23476 dojo.declare("dojo.dnd.Container", null, {
23477 // summary:
23478 // a Container object, which knows when mouse hovers over it,
23479 // and over which element it hovers
23480
23481 // object attributes (for markup)
23482 skipForm: false,
23483
23484 /*=====
23485 // current: DomNode
23486 // The DOM node the mouse is currently hovered over
23487 current: null,
23488
23489 // map: Hash<String, dojo.dnd.Item>
23490 // Map from an item's id (which is also the DOMNode's id) to
23491 // the dojo.dnd.Item itself.
23492 map: {},
23493 =====*/
23494
23495 constructor: function(node, params){
23496 // summary:
23497 // a constructor of the Container
23498 // node: Node
23499 // node or node's id to build the container on
23500 // params: dojo.dnd.__ContainerArgs
23501 // a dictionary of parameters
23502 this.node = dojo.byId(node);
23503 if(!params){ params = {}; }
23504 this.creator = params.creator || null;
23505 this.skipForm = params.skipForm;
23506 this.parent = params.dropParent && dojo.byId(params.dropParent);
23507
23508 // class-specific variables
23509 this.map = {};
23510 this.current = null;
23511
23512 // states
23513 this.containerState = "";
23514 dojo.addClass(this.node, "dojoDndContainer");
23515
23516 // mark up children
23517 if(!(params && params._skipStartup)){
23518 this.startup();
23519 }
23520
23521 // set up events
23522 this.events = [
23523 dojo.connect(this.node, "onmouseover", this, "onMouseOver"),
23524 dojo.connect(this.node, "onmouseout", this, "onMouseOut"),
23525 // cancel text selection and text dragging
23526 dojo.connect(this.node, "ondragstart", this, "onSelectStart"),
23527 dojo.connect(this.node, "onselectstart", this, "onSelectStart")
23528 ];
23529 },
23530
23531 // object attributes (for markup)
23532 creator: function(){
23533 // summary:
23534 // creator function, dummy at the moment
23535 },
23536
23537 // abstract access to the map
23538 getItem: function(/*String*/ key){
23539 // summary:
23540 // returns a data item by its key (id)
23541 return this.map[key]; // dojo.dnd.Item
23542 },
23543 setItem: function(/*String*/ key, /*dojo.dnd.Item*/ data){
23544 // summary:
23545 // associates a data item with its key (id)
23546 this.map[key] = data;
23547 },
23548 delItem: function(/*String*/ key){
23549 // summary:
23550 // removes a data item from the map by its key (id)
23551 delete this.map[key];
23552 },
23553 forInItems: function(/*Function*/ f, /*Object?*/ o){
23554 // summary:
23555 // iterates over a data map skipping members that
23556 // are present in the empty object (IE and/or 3rd-party libraries).
23557 o = o || dojo.global;
23558 var m = this.map, e = dojo.dnd._empty;
23559 for(var i in m){
23560 if(i in e){ continue; }
23561 f.call(o, m[i], i, this);
23562 }
23563 return o; // Object
23564 },
23565 clearItems: function(){
23566 // summary:
23567 // removes all data items from the map
23568 this.map = {};
23569 },
23570
23571 // methods
23572 getAllNodes: function(){
23573 // summary:
23574 // returns a list (an array) of all valid child nodes
23575 return dojo.query("> .dojoDndItem", this.parent); // NodeList
23576 },
23577 sync: function(){
23578 // summary:
23579 // sync up the node list with the data map
23580 var map = {};
23581 this.getAllNodes().forEach(function(node){
23582 if(node.id){
23583 var item = this.getItem(node.id);
23584 if(item){
23585 map[node.id] = item;
23586 return;
23587 }
23588 }else{
23589 node.id = dojo.dnd.getUniqueId();
23590 }
23591 var type = node.getAttribute("dndType"),
23592 data = node.getAttribute("dndData");
23593 map[node.id] = {
23594 data: data || node.innerHTML,
23595 type: type ? type.split(/\s*,\s*/) : ["text"]
23596 };
23597 }, this);
23598 this.map = map;
23599 return this; // self
23600 },
23601 insertNodes: function(data, before, anchor){
23602 // summary:
23603 // inserts an array of new nodes before/after an anchor node
23604 // data: Array
23605 // a list of data items, which should be processed by the creator function
23606 // before: Boolean
23607 // insert before the anchor, if true, and after the anchor otherwise
23608 // anchor: Node
23609 // the anchor node to be used as a point of insertion
23610 if(!this.parent.firstChild){
23611 anchor = null;
23612 }else if(before){
23613 if(!anchor){
23614 anchor = this.parent.firstChild;
23615 }
23616 }else{
23617 if(anchor){
23618 anchor = anchor.nextSibling;
23619 }
23620 }
23621 if(anchor){
23622 for(var i = 0; i < data.length; ++i){
23623 var t = this._normalizedCreator(data[i]);
23624 this.setItem(t.node.id, {data: t.data, type: t.type});
23625 this.parent.insertBefore(t.node, anchor);
23626 }
23627 }else{
23628 for(var i = 0; i < data.length; ++i){
23629 var t = this._normalizedCreator(data[i]);
23630 this.setItem(t.node.id, {data: t.data, type: t.type});
23631 this.parent.appendChild(t.node);
23632 }
23633 }
23634 return this; // self
23635 },
23636 destroy: function(){
23637 // summary:
23638 // prepares this object to be garbage-collected
23639 dojo.forEach(this.events, dojo.disconnect);
23640 this.clearItems();
23641 this.node = this.parent = this.current = null;
23642 },
23643
23644 // markup methods
23645 markupFactory: function(params, node){
23646 params._skipStartup = true;
23647 return new dojo.dnd.Container(node, params);
23648 },
23649 startup: function(){
23650 // summary:
23651 // collects valid child items and populate the map
23652
23653 // set up the real parent node
23654 if(!this.parent){
23655 // use the standard algorithm, if not assigned
23656 this.parent = this.node;
23657 if(this.parent.tagName.toLowerCase() == "table"){
23658 var c = this.parent.getElementsByTagName("tbody");
23659 if(c && c.length){ this.parent = c[0]; }
23660 }
23661 }
23662 this.defaultCreator = dojo.dnd._defaultCreator(this.parent);
23663
23664 // process specially marked children
23665 this.sync();
23666 },
23667
23668 // mouse events
23669 onMouseOver: function(e){
23670 // summary:
23671 // event processor for onmouseover
23672 // e: Event
23673 // mouse event
23674 var n = e.relatedTarget;
23675 while(n){
23676 if(n == this.node){ break; }
23677 try{
23678 n = n.parentNode;
23679 }catch(x){
23680 n = null;
23681 }
23682 }
23683 if(!n){
23684 this._changeState("Container", "Over");
23685 this.onOverEvent();
23686 }
23687 n = this._getChildByEvent(e);
23688 if(this.current == n){ return; }
23689 if(this.current){ this._removeItemClass(this.current, "Over"); }
23690 if(n){ this._addItemClass(n, "Over"); }
23691 this.current = n;
23692 },
23693 onMouseOut: function(e){
23694 // summary:
23695 // event processor for onmouseout
23696 // e: Event
23697 // mouse event
23698 for(var n = e.relatedTarget; n;){
23699 if(n == this.node){ return; }
23700 try{
23701 n = n.parentNode;
23702 }catch(x){
23703 n = null;
23704 }
23705 }
23706 if(this.current){
23707 this._removeItemClass(this.current, "Over");
23708 this.current = null;
23709 }
23710 this._changeState("Container", "");
23711 this.onOutEvent();
23712 },
23713 onSelectStart: function(e){
23714 // summary:
23715 // event processor for onselectevent and ondragevent
23716 // e: Event
23717 // mouse event
23718 if(!this.skipForm || !dojo.dnd.isFormElement(e)){
23719 dojo.stopEvent(e);
23720 }
23721 },
23722
23723 // utilities
23724 onOverEvent: function(){
23725 // summary:
23726 // this function is called once, when mouse is over our container
23727 },
23728 onOutEvent: function(){
23729 // summary:
23730 // this function is called once, when mouse is out of our container
23731 },
23732 _changeState: function(type, newState){
23733 // summary:
23734 // changes a named state to new state value
23735 // type: String
23736 // a name of the state to change
23737 // newState: String
23738 // new state
23739 var prefix = "dojoDnd" + type;
23740 var state = type.toLowerCase() + "State";
23741 //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
23742 dojo.removeClass(this.node, prefix + this[state]);
23743 dojo.addClass(this.node, prefix + newState);
23744 this[state] = newState;
23745 },
23746 _addItemClass: function(node, type){
23747 // summary:
23748 // adds a class with prefix "dojoDndItem"
23749 // node: Node
23750 // a node
23751 // type: String
23752 // a variable suffix for a class name
23753 dojo.addClass(node, "dojoDndItem" + type);
23754 },
23755 _removeItemClass: function(node, type){
23756 // summary:
23757 // removes a class with prefix "dojoDndItem"
23758 // node: Node
23759 // a node
23760 // type: String
23761 // a variable suffix for a class name
23762 dojo.removeClass(node, "dojoDndItem" + type);
23763 },
23764 _getChildByEvent: function(e){
23765 // summary:
23766 // gets a child, which is under the mouse at the moment, or null
23767 // e: Event
23768 // a mouse event
23769 var node = e.target;
23770 if(node){
23771 for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){
23772 if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; }
23773 }
23774 }
23775 return null;
23776 },
23777 _normalizedCreator: function(/*dojo.dnd.Item*/ item, /*String*/ hint){
23778 // summary:
23779 // adds all necessary data to the output of the user-supplied creator function
23780 var t = (this.creator || this.defaultCreator).call(this, item, hint);
23781 if(!dojo.isArray(t.type)){ t.type = ["text"]; }
23782 if(!t.node.id){ t.node.id = dojo.dnd.getUniqueId(); }
23783 dojo.addClass(t.node, "dojoDndItem");
23784 return t;
23785 }
23786 });
23787
23788 dojo.dnd._createNode = function(tag){
23789 // summary:
23790 // returns a function, which creates an element of given tag
23791 // (SPAN by default) and sets its innerHTML to given text
23792 // tag: String
23793 // a tag name or empty for SPAN
23794 if(!tag){ return dojo.dnd._createSpan; }
23795 return function(text){ // Function
23796 return dojo.create(tag, {innerHTML: text}); // Node
23797 };
23798 };
23799
23800 dojo.dnd._createTrTd = function(text){
23801 // summary:
23802 // creates a TR/TD structure with given text as an innerHTML of TD
23803 // text: String
23804 // a text for TD
23805 var tr = dojo.create("tr");
23806 dojo.create("td", {innerHTML: text}, tr);
23807 return tr; // Node
23808 };
23809
23810 dojo.dnd._createSpan = function(text){
23811 // summary:
23812 // creates a SPAN element with given text as its innerHTML
23813 // text: String
23814 // a text for SPAN
23815 return dojo.create("span", {innerHTML: text}); // Node
23816 };
23817
23818 // dojo.dnd._defaultCreatorNodes: Object
23819 // a dictionary that maps container tag names to child tag names
23820 dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"};
23821
23822 dojo.dnd._defaultCreator = function(node){
23823 // summary:
23824 // takes a parent node, and returns an appropriate creator function
23825 // node: Node
23826 // a container node
23827 var tag = node.tagName.toLowerCase();
23828 var c = tag == "tbody" || tag == "thead" ? dojo.dnd._createTrTd :
23829 dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]);
23830 return function(item, hint){ // Function
23831 var isObj = item && dojo.isObject(item), data, type, n;
23832 if(isObj && item.tagName && item.nodeType && item.getAttribute){
23833 // process a DOM node
23834 data = item.getAttribute("dndData") || item.innerHTML;
23835 type = item.getAttribute("dndType");
23836 type = type ? type.split(/\s*,\s*/) : ["text"];
23837 n = item; // this node is going to be moved rather than copied
23838 }else{
23839 // process a DnD item object or a string
23840 data = (isObj && item.data) ? item.data : item;
23841 type = (isObj && item.type) ? item.type : ["text"];
23842 n = (hint == "avatar" ? dojo.dnd._createSpan : c)(String(data));
23843 }
23844 if(!n.id){
23845 n.id = dojo.dnd.getUniqueId();
23846 }
23847 return {node: n, data: data, type: type};
23848 };
23849 };
23850
23851 }
23852
23853 if(!dojo._hasResource["dijit.tree._dndContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
23854 dojo._hasResource["dijit.tree._dndContainer"] = true;
23855 dojo.provide("dijit.tree._dndContainer");
23856
23857
23858
23859 dojo.declare("dijit.tree._dndContainer",
23860 null,
23861 {
23862
23863 // summary:
23864 // This is a base class for `dijit.tree._dndSelector`, and isn't meant to be used directly.
23865 // It's modeled after `dojo.dnd.Container`.
23866 // tags:
23867 // protected
23868
23869 /*=====
23870 // current: DomNode
23871 // The currently hovered TreeNode.rowNode (which is the DOM node
23872 // associated w/a given node in the tree, excluding it's descendants)
23873 current: null,
23874 =====*/
23875
23876 constructor: function(tree, params){
23877 // summary:
23878 // A constructor of the Container
23879 // tree: Node
23880 // Node or node's id to build the container on
23881 // params: dijit.tree.__SourceArgs
23882 // A dict of parameters, which gets mixed into the object
23883 // tags:
23884 // private
23885 this.tree = tree;
23886 this.node = tree.domNode; // TODO: rename; it's not a TreeNode but the whole Tree
23887 dojo.mixin(this, params);
23888
23889 // class-specific variables
23890 this.map = {};
23891 this.current = null; // current TreeNode's DOM node
23892
23893 // states
23894 this.containerState = "";
23895 dojo.addClass(this.node, "dojoDndContainer");
23896
23897 // set up events
23898 this.events = [
23899 // container level events
23900 dojo.connect(this.node, "onmouseenter", this, "onOverEvent"),
23901 dojo.connect(this.node, "onmouseleave", this, "onOutEvent"),
23902
23903 // switching between TreeNodes
23904 dojo.connect(this.tree, "_onNodeMouseEnter", this, "onMouseOver"),
23905 dojo.connect(this.tree, "_onNodeMouseLeave", this, "onMouseOut"),
23906
23907 // cancel text selection and text dragging
23908 dojo.connect(this.node, "ondragstart", dojo, "stopEvent"),
23909 dojo.connect(this.node, "onselectstart", dojo, "stopEvent")
23910 ];
23911 },
23912
23913 getItem: function(/*String*/ key){
23914 // summary:
23915 // Returns the dojo.dnd.Item (representing a dragged node) by it's key (id).
23916 // Called by dojo.dnd.Source.checkAcceptance().
23917 // tags:
23918 // protected
23919
23920 var node = this.selection[key],
23921 ret = {
23922 data: dijit.getEnclosingWidget(node),
23923 type: ["treeNode"]
23924 };
23925
23926 return ret; // dojo.dnd.Item
23927 },
23928
23929 destroy: function(){
23930 // summary:
23931 // Prepares this object to be garbage-collected
23932
23933 dojo.forEach(this.events, dojo.disconnect);
23934 // this.clearItems();
23935 this.node = this.parent = null;
23936 },
23937
23938 // mouse events
23939 onMouseOver: function(/*TreeNode*/ widget, /*Event*/ evt){
23940 // summary:
23941 // Called when mouse is moved over a TreeNode
23942 // tags:
23943 // protected
23944 this.current = widget.rowNode;
23945 this.currentWidget = widget;
23946 },
23947
23948 onMouseOut: function(/*TreeNode*/ widget, /*Event*/ evt){
23949 // summary:
23950 // Called when mouse is moved away from a TreeNode
23951 // tags:
23952 // protected
23953 this.current = null;
23954 this.currentWidget = null;
23955 },
23956
23957 _changeState: function(type, newState){
23958 // summary:
23959 // Changes a named state to new state value
23960 // type: String
23961 // A name of the state to change
23962 // newState: String
23963 // new state
23964 var prefix = "dojoDnd" + type;
23965 var state = type.toLowerCase() + "State";
23966 //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
23967 dojo.removeClass(this.node, prefix + this[state]);
23968 dojo.addClass(this.node, prefix + newState);
23969 this[state] = newState;
23970 },
23971
23972 _addItemClass: function(node, type){
23973 // summary:
23974 // Adds a class with prefix "dojoDndItem"
23975 // node: Node
23976 // A node
23977 // type: String
23978 // A variable suffix for a class name
23979 dojo.addClass(node, "dojoDndItem" + type);
23980 },
23981
23982 _removeItemClass: function(node, type){
23983 // summary:
23984 // Removes a class with prefix "dojoDndItem"
23985 // node: Node
23986 // A node
23987 // type: String
23988 // A variable suffix for a class name
23989 dojo.removeClass(node, "dojoDndItem" + type);
23990 },
23991
23992 onOverEvent: function(){
23993 // summary:
23994 // This function is called once, when mouse is over our container
23995 // tags:
23996 // protected
23997 this._changeState("Container", "Over");
23998 },
23999
24000 onOutEvent: function(){
24001 // summary:
24002 // This function is called once, when mouse is out of our container
24003 // tags:
24004 // protected
24005 this._changeState("Container", "");
24006 }
24007 });
24008
24009 }
24010
24011 if(!dojo._hasResource["dijit.tree._dndSelector"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
24012 dojo._hasResource["dijit.tree._dndSelector"] = true;
24013 dojo.provide("dijit.tree._dndSelector");
24014
24015
24016
24017 dojo.declare("dijit.tree._dndSelector",
24018 dijit.tree._dndContainer,
24019 {
24020 // summary:
24021 // This is a base class for `dijit.tree.dndSource` , and isn't meant to be used directly.
24022 // It's based on `dojo.dnd.Selector`.
24023 // tags:
24024 // protected
24025
24026 /*=====
24027 // selection: Hash<String, DomNode>
24028 // (id, DomNode) map for every TreeNode that's currently selected.
24029 // The DOMNode is the TreeNode.rowNode.
24030 selection: {},
24031 =====*/
24032
24033 constructor: function(tree, params){
24034 // summary:
24035 // Initialization
24036 // tags:
24037 // private
24038
24039 this.selection={};
24040 this.anchor = null;
24041 this.simpleSelection=false;
24042
24043 this.events.push(
24044 dojo.connect(this.tree.domNode, "onmousedown", this,"onMouseDown"),
24045 dojo.connect(this.tree.domNode, "onmouseup", this,"onMouseUp"),
24046 dojo.connect(this.tree.domNode, "onmousemove", this,"onMouseMove")
24047 );
24048 },
24049
24050 // singular: Boolean
24051 // Allows selection of only one element, if true.
24052 // Tree hasn't been tested in singular=true mode, unclear if it works.
24053 singular: false,
24054
24055 // methods
24056
24057 getSelectedNodes: function(){
24058 // summary:
24059 // Returns the set of selected nodes.
24060 // Used by dndSource on the start of a drag.
24061 // tags:
24062 // protected
24063 return this.selection;
24064 },
24065
24066 selectNone: function(){
24067 // summary:
24068 // Unselects all items
24069 // tags:
24070 // private
24071
24072 return this._removeSelection()._removeAnchor(); // self
24073 },
24074
24075 destroy: function(){
24076 // summary:
24077 // Prepares the object to be garbage-collected
24078 this.inherited(arguments);
24079 this.selection = this.anchor = null;
24080 },
24081
24082 // mouse events
24083 onMouseDown: function(e){
24084 // summary:
24085 // Event processor for onmousedown
24086 // e: Event
24087 // mouse event
24088 // tags:
24089 // protected
24090
24091 if(!this.current){ return; }
24092
24093 if(e.button == dojo.mouseButtons.RIGHT){ return; } // ignore right-click
24094
24095 var treeNode = dijit.getEnclosingWidget(this.current),
24096 id = treeNode.id + "-dnd" // so id doesn't conflict w/widget
24097
24098 if(!dojo.hasAttr(this.current, "id")){
24099 dojo.attr(this.current, "id", id);
24100 }
24101
24102 if(!this.singular && !dojo.isCopyKey(e) && !e.shiftKey && (this.current.id in this.selection)){
24103 this.simpleSelection = true;
24104 dojo.stopEvent(e);
24105 return;
24106 }
24107 if(this.singular){
24108 if(this.anchor == this.current){
24109 if(dojo.isCopyKey(e)){
24110 this.selectNone();
24111 }
24112 }else{
24113 this.selectNone();
24114 this.anchor = this.current;
24115 this._addItemClass(this.anchor, "Anchor");
24116
24117 this.selection[this.current.id] = this.current;
24118 }
24119 }else{
24120 if(!this.singular && e.shiftKey){
24121 if(dojo.isCopyKey(e)){
24122 //TODO add range to selection
24123 }else{
24124 //TODO select new range from anchor
24125 }
24126 }else{
24127 if(dojo.isCopyKey(e)){
24128 if(this.anchor == this.current){
24129 delete this.selection[this.anchor.id];
24130 this._removeAnchor();
24131 }else{
24132 if(this.current.id in this.selection){
24133 this._removeItemClass(this.current, "Selected");
24134 delete this.selection[this.current.id];
24135 }else{
24136 if(this.anchor){
24137 this._removeItemClass(this.anchor, "Anchor");
24138 this._addItemClass(this.anchor, "Selected");
24139 }
24140 this.anchor = this.current;
24141 this._addItemClass(this.current, "Anchor");
24142 this.selection[this.current.id] = this.current;
24143 }
24144 }
24145 }else{
24146 if(!(id in this.selection)){
24147 this.selectNone();
24148 this.anchor = this.current;
24149 this._addItemClass(this.current, "Anchor");
24150 this.selection[id] = this.current;
24151 }
24152 }
24153 }
24154 }
24155
24156 dojo.stopEvent(e);
24157 },
24158
24159 onMouseUp: function(e){
24160 // summary:
24161 // Event processor for onmouseup
24162 // e: Event
24163 // mouse event
24164 // tags:
24165 // protected
24166
24167 // TODO: this code is apparently for handling an edge case when the user is selecting
24168 // multiple nodes and then mousedowns on a node by accident... it lets the user keep the
24169 // current selection by moving the mouse away (or something like that). It doesn't seem
24170 // to work though and requires a lot of plumbing (including this code, the onmousemove
24171 // handler, and the this.simpleSelection attribute. Consider getting rid of all of it.
24172
24173 if(!this.simpleSelection){ return; }
24174 this.simpleSelection = false;
24175 this.selectNone();
24176 if(this.current){
24177 this.anchor = this.current;
24178 this._addItemClass(this.anchor, "Anchor");
24179 this.selection[this.current.id] = this.current;
24180 }
24181 },
24182 onMouseMove: function(e){
24183 // summary
24184 // event processor for onmousemove
24185 // e: Event
24186 // mouse event
24187 this.simpleSelection = false;
24188 },
24189
24190 _removeSelection: function(){
24191 // summary:
24192 // Unselects all items
24193 // tags:
24194 // private
24195 var e = dojo.dnd._empty;
24196 for(var i in this.selection){
24197 if(i in e){ continue; }
24198 var node = dojo.byId(i);
24199 if(node){ this._removeItemClass(node, "Selected"); }
24200 }
24201 this.selection = {};
24202 return this; // self
24203 },
24204
24205 _removeAnchor: function(){
24206 // summary:
24207 // Removes the Anchor CSS class from a node.
24208 // According to `dojo.dnd.Selector`, anchor means that
24209 // "an item is selected, and is an anchor for a 'shift' selection".
24210 // It's not relevant for Tree at this point, since we don't support multiple selection.
24211 // tags:
24212 // private
24213 if(this.anchor){
24214 this._removeItemClass(this.anchor, "Anchor");
24215 this.anchor = null;
24216 }
24217 return this; // self
24218 },
24219
24220 forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){
24221 // summary:
24222 // Iterates over selected items;
24223 // see `dojo.dnd.Container.forInItems()` for details
24224 o = o || dojo.global;
24225 for(var id in this.selection){
24226 console.log("selected item id: " + id);
24227 f.call(o, this.getItem(id), id, this);
24228 }
24229 }
24230 });
24231
24232 }
24233
24234 if(!dojo._hasResource["dojo.dnd.Avatar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
24235 dojo._hasResource["dojo.dnd.Avatar"] = true;
24236 dojo.provide("dojo.dnd.Avatar");
24237
24238
24239
24240 dojo.declare("dojo.dnd.Avatar", null, {
24241 // summary:
24242 // Object that represents transferred DnD items visually
24243 // manager: Object
24244 // a DnD manager object
24245
24246 constructor: function(manager){
24247 this.manager = manager;
24248 this.construct();
24249 },
24250
24251 // methods
24252 construct: function(){
24253 // summary:
24254 // constructor function;
24255 // it is separate so it can be (dynamically) overwritten in case of need
24256 this.isA11y = dojo.hasClass(dojo.body(),"dijit_a11y");
24257 var a = dojo.create("table", {
24258 "class": "dojoDndAvatar",
24259 style: {
24260 position: "absolute",
24261 zIndex: "1999",
24262 margin: "0px"
24263 }
24264 }),
24265 source = this.manager.source, node,
24266 b = dojo.create("tbody", null, a),
24267 tr = dojo.create("tr", null, b),
24268 td = dojo.create("td", null, tr),
24269 icon = this.isA11y ? dojo.create("span", {
24270 id : "a11yIcon",
24271 innerHTML : this.manager.copy ? '+' : "<"
24272 }, td) : null,
24273 span = dojo.create("span", {
24274 innerHTML: source.generateText ? this._generateText() : ""
24275 }, td),
24276 k = Math.min(5, this.manager.nodes.length), i = 0;
24277 // we have to set the opacity on IE only after the node is live
24278 dojo.attr(tr, {
24279 "class": "dojoDndAvatarHeader",
24280 style: {opacity: 0.9}
24281 });
24282 for(; i < k; ++i){
24283 if(source.creator){
24284 // create an avatar representation of the node
24285 node = source._normalizedCreator(source.getItem(this.manager.nodes[i].id).data, "avatar").node;
24286 }else{
24287 // or just clone the node and hope it works
24288 node = this.manager.nodes[i].cloneNode(true);
24289 if(node.tagName.toLowerCase() == "tr"){
24290 // insert extra table nodes
24291 var table = dojo.create("table"),
24292 tbody = dojo.create("tbody", null, table);
24293 tbody.appendChild(node);
24294 node = table;
24295 }
24296 }
24297 node.id = "";
24298 tr = dojo.create("tr", null, b);
24299 td = dojo.create("td", null, tr);
24300 td.appendChild(node);
24301 dojo.attr(tr, {
24302 "class": "dojoDndAvatarItem",
24303 style: {opacity: (9 - i) / 10}
24304 });
24305 }
24306 this.node = a;
24307 },
24308 destroy: function(){
24309 // summary:
24310 // destructor for the avatar; called to remove all references so it can be garbage-collected
24311 dojo.destroy(this.node);
24312 this.node = false;
24313 },
24314 update: function(){
24315 // summary:
24316 // updates the avatar to reflect the current DnD state
24317 dojo[(this.manager.canDropFlag ? "add" : "remove") + "Class"](this.node, "dojoDndAvatarCanDrop");
24318 if (this.isA11y){
24319 var icon = dojo.byId("a11yIcon");
24320 var text = '+'; // assume canDrop && copy
24321 if (this.manager.canDropFlag && !this.manager.copy) {
24322 text = '< '; // canDrop && move
24323 }else if (!this.manager.canDropFlag && !this.manager.copy) {
24324 text = "o"; //!canDrop && move
24325 }else if(!this.manager.canDropFlag){
24326 text = 'x'; // !canDrop && copy
24327 }
24328 icon.innerHTML=text;
24329 }
24330 // replace text
24331 dojo.query(("tr.dojoDndAvatarHeader td span" +(this.isA11y ? " span" : "")), this.node).forEach(
24332 function(node){
24333 node.innerHTML = this._generateText();
24334 }, this);
24335 },
24336 _generateText: function(){
24337 // summary: generates a proper text to reflect copying or moving of items
24338 return this.manager.nodes.length.toString();
24339 }
24340 });
24341
24342 }
24343
24344 if(!dojo._hasResource["dojo.dnd.Manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
24345 dojo._hasResource["dojo.dnd.Manager"] = true;
24346 dojo.provide("dojo.dnd.Manager");
24347
24348
24349
24350
24351
24352 dojo.declare("dojo.dnd.Manager", null, {
24353 // summary:
24354 // the manager of DnD operations (usually a singleton)
24355 constructor: function(){
24356 this.avatar = null;
24357 this.source = null;
24358 this.nodes = [];
24359 this.copy = true;
24360 this.target = null;
24361 this.canDropFlag = false;
24362 this.events = [];
24363 },
24364
24365 // avatar's offset from the mouse
24366 OFFSET_X: 16,
24367 OFFSET_Y: 16,
24368
24369 // methods
24370 overSource: function(source){
24371 // summary:
24372 // called when a source detected a mouse-over condition
24373 // source: Object
24374 // the reporter
24375 if(this.avatar){
24376 this.target = (source && source.targetState != "Disabled") ? source : null;
24377 this.canDropFlag = Boolean(this.target);
24378 this.avatar.update();
24379 }
24380 dojo.publish("/dnd/source/over", [source]);
24381 },
24382 outSource: function(source){
24383 // summary:
24384 // called when a source detected a mouse-out condition
24385 // source: Object
24386 // the reporter
24387 if(this.avatar){
24388 if(this.target == source){
24389 this.target = null;
24390 this.canDropFlag = false;
24391 this.avatar.update();
24392 dojo.publish("/dnd/source/over", [null]);
24393 }
24394 }else{
24395 dojo.publish("/dnd/source/over", [null]);
24396 }
24397 },
24398 startDrag: function(source, nodes, copy){
24399 // summary:
24400 // called to initiate the DnD operation
24401 // source: Object
24402 // the source which provides items
24403 // nodes: Array
24404 // the list of transferred items
24405 // copy: Boolean
24406 // copy items, if true, move items otherwise
24407 this.source = source;
24408 this.nodes = nodes;
24409 this.copy = Boolean(copy); // normalizing to true boolean
24410 this.avatar = this.makeAvatar();
24411 dojo.body().appendChild(this.avatar.node);
24412 dojo.publish("/dnd/start", [source, nodes, this.copy]);
24413 this.events = [
24414 dojo.connect(dojo.doc, "onmousemove", this, "onMouseMove"),
24415 dojo.connect(dojo.doc, "onmouseup", this, "onMouseUp"),
24416 dojo.connect(dojo.doc, "onkeydown", this, "onKeyDown"),
24417 dojo.connect(dojo.doc, "onkeyup", this, "onKeyUp"),
24418 // cancel text selection and text dragging
24419 dojo.connect(dojo.doc, "ondragstart", dojo.stopEvent),
24420 dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent)
24421 ];
24422 var c = "dojoDnd" + (copy ? "Copy" : "Move");
24423 dojo.addClass(dojo.body(), c);
24424 },
24425 canDrop: function(flag){
24426 // summary:
24427 // called to notify if the current target can accept items
24428 var canDropFlag = Boolean(this.target && flag);
24429 if(this.canDropFlag != canDropFlag){
24430 this.canDropFlag = canDropFlag;
24431 this.avatar.update();
24432 }
24433 },
24434 stopDrag: function(){
24435 // summary:
24436 // stop the DnD in progress
24437 dojo.removeClass(dojo.body(), "dojoDndCopy");
24438 dojo.removeClass(dojo.body(), "dojoDndMove");
24439 dojo.forEach(this.events, dojo.disconnect);
24440 this.events = [];
24441 this.avatar.destroy();
24442 this.avatar = null;
24443 this.source = this.target = null;
24444 this.nodes = [];
24445 },
24446 makeAvatar: function(){
24447 // summary:
24448 // makes the avatar; it is separate to be overwritten dynamically, if needed
24449 return new dojo.dnd.Avatar(this);
24450 },
24451 updateAvatar: function(){
24452 // summary:
24453 // updates the avatar; it is separate to be overwritten dynamically, if needed
24454 this.avatar.update();
24455 },
24456
24457 // mouse event processors
24458 onMouseMove: function(e){
24459 // summary:
24460 // event processor for onmousemove
24461 // e: Event
24462 // mouse event
24463 var a = this.avatar;
24464 if(a){
24465 dojo.dnd.autoScrollNodes(e);
24466 //dojo.dnd.autoScroll(e);
24467 var s = a.node.style;
24468 s.left = (e.pageX + this.OFFSET_X) + "px";
24469 s.top = (e.pageY + this.OFFSET_Y) + "px";
24470 var copy = Boolean(this.source.copyState(dojo.isCopyKey(e)));
24471 if(this.copy != copy){
24472 this._setCopyStatus(copy);
24473 }
24474 }
24475 },
24476 onMouseUp: function(e){
24477 // summary:
24478 // event processor for onmouseup
24479 // e: Event
24480 // mouse event
24481 if(this.avatar){
24482 if(this.target && this.canDropFlag){
24483 var copy = Boolean(this.source.copyState(dojo.isCopyKey(e))),
24484 params = [this.source, this.nodes, copy, this.target, e];
24485 dojo.publish("/dnd/drop/before", params);
24486 dojo.publish("/dnd/drop", params);
24487 }else{
24488 dojo.publish("/dnd/cancel");
24489 }
24490 this.stopDrag();
24491 }
24492 },
24493
24494 // keyboard event processors
24495 onKeyDown: function(e){
24496 // summary:
24497 // event processor for onkeydown:
24498 // watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag
24499 // e: Event
24500 // keyboard event
24501 if(this.avatar){
24502 switch(e.keyCode){
24503 case dojo.keys.CTRL:
24504 var copy = Boolean(this.source.copyState(true));
24505 if(this.copy != copy){
24506 this._setCopyStatus(copy);
24507 }
24508 break;
24509 case dojo.keys.ESCAPE:
24510 dojo.publish("/dnd/cancel");
24511 this.stopDrag();
24512 break;
24513 }
24514 }
24515 },
24516 onKeyUp: function(e){
24517 // summary:
24518 // event processor for onkeyup, watching for CTRL for copy/move status
24519 // e: Event
24520 // keyboard event
24521 if(this.avatar && e.keyCode == dojo.keys.CTRL){
24522 var copy = Boolean(this.source.copyState(false));
24523 if(this.copy != copy){
24524 this._setCopyStatus(copy);
24525 }
24526 }
24527 },
24528
24529 // utilities
24530 _setCopyStatus: function(copy){
24531 // summary:
24532 // changes the copy status
24533 // copy: Boolean
24534 // the copy status
24535 this.copy = copy;
24536 this.source._markDndStatus(this.copy);
24537 this.updateAvatar();
24538 dojo.removeClass(dojo.body(), "dojoDnd" + (this.copy ? "Move" : "Copy"));
24539 dojo.addClass(dojo.body(), "dojoDnd" + (this.copy ? "Copy" : "Move"));
24540 }
24541 });
24542
24543 // dojo.dnd._manager:
24544 // The manager singleton variable. Can be overwritten if needed.
24545 dojo.dnd._manager = null;
24546
24547 dojo.dnd.manager = function(){
24548 // summary:
24549 // Returns the current DnD manager. Creates one if it is not created yet.
24550 if(!dojo.dnd._manager){
24551 dojo.dnd._manager = new dojo.dnd.Manager();
24552 }
24553 return dojo.dnd._manager; // Object
24554 };
24555
24556 }
24557
24558 if(!dojo._hasResource["dijit.tree.dndSource"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
24559 dojo._hasResource["dijit.tree.dndSource"] = true;
24560 dojo.provide("dijit.tree.dndSource");
24561
24562
24563
24564
24565 /*=====
24566 dijit.tree.__SourceArgs = function(){
24567 // summary:
24568 // A dict of parameters for Tree source configuration.
24569 // isSource: Boolean?
24570 // Can be used as a DnD source. Defaults to true.
24571 // accept: String[]
24572 // List of accepted types (text strings) for a target; defaults to
24573 // ["text", "treeNode"]
24574 // copyOnly: Boolean?
24575 // Copy items, if true, use a state of Ctrl key otherwise,
24576 // dragThreshold: Number
24577 // The move delay in pixels before detecting a drag; 0 by default
24578 // betweenThreshold: Integer
24579 // Distance from upper/lower edge of node to allow drop to reorder nodes
24580 this.isSource = isSource;
24581 this.accept = accept;
24582 this.autoSync = autoSync;
24583 this.copyOnly = copyOnly;
24584 this.dragThreshold = dragThreshold;
24585 this.betweenThreshold = betweenThreshold;
24586 }
24587 =====*/
24588
24589 dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
24590 // summary:
24591 // Handles drag and drop operations (as a source or a target) for `dijit.Tree`
24592
24593 // isSource: [private] Boolean
24594 // Can be used as a DnD source.
24595 isSource: true,
24596
24597 // accept: String[]
24598 // List of accepted types (text strings) for the Tree; defaults to
24599 // ["text"]
24600 accept: ["text", "treeNode"],
24601
24602 // copyOnly: [private] Boolean
24603 // Copy items, if true, use a state of Ctrl key otherwise
24604 copyOnly: false,
24605
24606 // dragThreshold: Number
24607 // The move delay in pixels before detecting a drag; 5 by default
24608 dragThreshold: 5,
24609
24610 // betweenThreshold: Integer
24611 // Distance from upper/lower edge of node to allow drop to reorder nodes
24612 betweenThreshold: 0,
24613
24614 constructor: function(/*dijit.Tree*/ tree, /*dijit.tree.__SourceArgs*/ params){
24615 // summary:
24616 // a constructor of the Tree DnD Source
24617 // tags:
24618 // private
24619 if(!params){ params = {}; }
24620 dojo.mixin(this, params);
24621 this.isSource = typeof params.isSource == "undefined" ? true : params.isSource;
24622 var type = params.accept instanceof Array ? params.accept : ["text", "treeNode"];
24623 this.accept = null;
24624 if(type.length){
24625 this.accept = {};
24626 for(var i = 0; i < type.length; ++i){
24627 this.accept[type[i]] = 1;
24628 }
24629 }
24630
24631 // class-specific variables
24632 this.isDragging = false;
24633 this.mouseDown = false;
24634 this.targetAnchor = null; // DOMNode corresponding to the currently moused over TreeNode
24635 this.targetBox = null; // coordinates of this.targetAnchor
24636 this.dropPosition = ""; // whether mouse is over/after/before this.targetAnchor
24637 this._lastX = 0;
24638 this._lastY = 0;
24639
24640 // states
24641 this.sourceState = "";
24642 if(this.isSource){
24643 dojo.addClass(this.node, "dojoDndSource");
24644 }
24645 this.targetState = "";
24646 if(this.accept){
24647 dojo.addClass(this.node, "dojoDndTarget");
24648 }
24649
24650 // set up events
24651 this.topics = [
24652 dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"),
24653 dojo.subscribe("/dnd/start", this, "onDndStart"),
24654 dojo.subscribe("/dnd/drop", this, "onDndDrop"),
24655 dojo.subscribe("/dnd/cancel", this, "onDndCancel")
24656 ];
24657 },
24658
24659 // methods
24660 checkAcceptance: function(source, nodes){
24661 // summary:
24662 // Checks if the target can accept nodes from this source
24663 // source: dijit.tree.dndSource
24664 // The source which provides items
24665 // nodes: DOMNode[]
24666 // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if
24667 // source is a dijit.Tree.
24668 // tags:
24669 // extension
24670 return true; // Boolean
24671 },
24672
24673 copyState: function(keyPressed){
24674 // summary:
24675 // Returns true, if we need to copy items, false to move.
24676 // It is separated to be overwritten dynamically, if needed.
24677 // keyPressed: Boolean
24678 // The "copy" control key was pressed
24679 // tags:
24680 // protected
24681 return this.copyOnly || keyPressed; // Boolean
24682 },
24683 destroy: function(){
24684 // summary:
24685 // Prepares the object to be garbage-collected.
24686 this.inherited("destroy",arguments);
24687 dojo.forEach(this.topics, dojo.unsubscribe);
24688 this.targetAnchor = null;
24689 },
24690
24691 _onDragMouse: function(e){
24692 // summary:
24693 // Helper method for processing onmousemove/onmouseover events while drag is in progress.
24694 // Keeps track of current drop target.
24695
24696 var m = dojo.dnd.manager(),
24697 oldTarget = this.targetAnchor, // the DOMNode corresponding to TreeNode mouse was previously over
24698 newTarget = this.current, // DOMNode corresponding to TreeNode mouse is currently over
24699 newTargetWidget = this.currentWidget, // the TreeNode itself
24700 oldDropPosition = this.dropPosition; // the previous drop position (over/before/after)
24701
24702 // calculate if user is indicating to drop the dragged node before, after, or over
24703 // (i.e., to become a child of) the target node
24704 var newDropPosition = "Over";
24705 if(newTarget && this.betweenThreshold > 0){
24706 // If mouse is over a new TreeNode, then get new TreeNode's position and size
24707 if(!this.targetBox || oldTarget != newTarget){
24708 this.targetBox = dojo.position(newTarget, true);
24709 }
24710 if((e.pageY - this.targetBox.y) <= this.betweenThreshold){
24711 newDropPosition = "Before";
24712 }else if((e.pageY - this.targetBox.y) >= (this.targetBox.h - this.betweenThreshold)){
24713 newDropPosition = "After";
24714 }
24715 }
24716
24717 if(newTarget != oldTarget || newDropPosition != oldDropPosition){
24718 if(oldTarget){
24719 this._removeItemClass(oldTarget, oldDropPosition);
24720 }
24721 if(newTarget){
24722 this._addItemClass(newTarget, newDropPosition);
24723 }
24724
24725 // Check if it's ok to drop the dragged node on/before/after the target node.
24726 if(!newTarget){
24727 m.canDrop(false);
24728 }else if(newTargetWidget == this.tree.rootNode && newDropPosition != "Over"){
24729 // Can't drop before or after tree's root node; the dropped node would just disappear (at least visually)
24730 m.canDrop(false);
24731 }else if(m.source == this && (newTarget.id in this.selection)){
24732 // Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140)
24733 m.canDrop(false);
24734 }else if(this.checkItemAcceptance(newTarget, m.source, newDropPosition.toLowerCase())
24735 && !this._isParentChildDrop(m.source, newTarget)){
24736 m.canDrop(true);
24737 }else{
24738 m.canDrop(false);
24739 }
24740
24741 this.targetAnchor = newTarget;
24742 this.dropPosition = newDropPosition;
24743 }
24744 },
24745
24746 onMouseMove: function(e){
24747 // summary:
24748 // Called for any onmousemove events over the Tree
24749 // e: Event
24750 // onmousemouse event
24751 // tags:
24752 // private
24753 if(this.isDragging && this.targetState == "Disabled"){ return; }
24754 this.inherited(arguments);
24755 var m = dojo.dnd.manager();
24756 if(this.isDragging){
24757 this._onDragMouse(e);
24758 }else{
24759 if(this.mouseDown && this.isSource &&
24760 (Math.abs(e.pageX-this._lastX)>=this.dragThreshold || Math.abs(e.pageY-this._lastY)>=this.dragThreshold)){
24761 var n = this.getSelectedNodes();
24762 var nodes=[];
24763 for(var i in n){
24764 nodes.push(n[i]);
24765 }
24766 if(nodes.length){
24767 m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e)));
24768 }
24769 }
24770 }
24771 },
24772
24773 onMouseDown: function(e){
24774 // summary:
24775 // Event processor for onmousedown
24776 // e: Event
24777 // onmousedown event
24778 // tags:
24779 // private
24780 this.mouseDown = true;
24781 this.mouseButton = e.button;
24782 this._lastX = e.pageX;
24783 this._lastY = e.pageY;
24784 this.inherited("onMouseDown",arguments);
24785 },
24786
24787 onMouseUp: function(e){
24788 // summary:
24789 // Event processor for onmouseup
24790 // e: Event
24791 // onmouseup event
24792 // tags:
24793 // private
24794 if(this.mouseDown){
24795 this.mouseDown = false;
24796 this.inherited("onMouseUp",arguments);
24797 }
24798 },
24799
24800 onMouseOut: function(){
24801 // summary:
24802 // Event processor for when mouse is moved away from a TreeNode
24803 // tags:
24804 // private
24805 this.inherited(arguments);
24806 this._unmarkTargetAnchor();
24807 },
24808
24809 checkItemAcceptance: function(target, source, position){
24810 // summary:
24811 // Stub function to be overridden if one wants to check for the ability to drop at the node/item level
24812 // description:
24813 // In the base case, this is called to check if target can become a child of source.
24814 // When betweenThreshold is set, position="before" or "after" means that we
24815 // are asking if the source node can be dropped before/after the target node.
24816 // target: DOMNode
24817 // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to
24818 // Use dijit.getEnclosingWidget(target) to get the TreeNode.
24819 // source: dijit.tree.dndSource
24820 // The (set of) nodes we are dropping
24821 // position: String
24822 // "over", "before", or "after"
24823 // tags:
24824 // extension
24825 return true;
24826 },
24827
24828 // topic event processors
24829 onDndSourceOver: function(source){
24830 // summary:
24831 // Topic event processor for /dnd/source/over, called when detected a current source.
24832 // source: Object
24833 // The dijit.tree.dndSource / dojo.dnd.Source which has the mouse over it
24834 // tags:
24835 // private
24836 if(this != source){
24837 this.mouseDown = false;
24838 this._unmarkTargetAnchor();
24839 }else if(this.isDragging){
24840 var m = dojo.dnd.manager();
24841 m.canDrop(false);
24842 }
24843 },
24844 onDndStart: function(source, nodes, copy){
24845 // summary:
24846 // Topic event processor for /dnd/start, called to initiate the DnD operation
24847 // source: Object
24848 // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
24849 // nodes: DomNode[]
24850 // The list of transferred items, dndTreeNode nodes if dragging from a Tree
24851 // copy: Boolean
24852 // Copy items, if true, move items otherwise
24853 // tags:
24854 // private
24855
24856 if(this.isSource){
24857 this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
24858 }
24859 var accepted = this.checkAcceptance(source, nodes);
24860
24861 this._changeState("Target", accepted ? "" : "Disabled");
24862
24863 if(this == source){
24864 dojo.dnd.manager().overSource(this);
24865 }
24866
24867 this.isDragging = true;
24868 },
24869
24870 itemCreator: function(/*DomNode[]*/ nodes, target, /*dojo.dnd.Source*/ source){
24871 // summary:
24872 // Returns objects passed to `Tree.model.newItem()` based on DnD nodes
24873 // dropped onto the tree. Developer must override this method to enable
24874 // dropping from external sources onto this Tree, unless the Tree.model's items
24875 // happen to look like {id: 123, name: "Apple" } with no other attributes.
24876 // description:
24877 // For each node in nodes[], which came from source, create a hash of name/value
24878 // pairs to be passed to Tree.model.newItem(). Returns array of those hashes.
24879 // returns: Object[]
24880 // Array of name/value hashes for each new item to be added to the Tree, like:
24881 // | [
24882 // | { id: 123, label: "apple", foo: "bar" },
24883 // | { id: 456, label: "pear", zaz: "bam" }
24884 // | ]
24885 // tags:
24886 // extension
24887
24888 // TODO: for 2.0 refactor so itemCreator() is called once per drag node, and
24889 // make signature itemCreator(sourceItem, node, target) (or similar).
24890
24891 return dojo.map(nodes, function(node){
24892 return {
24893 "id": node.id,
24894 "name": node.textContent || node.innerText || ""
24895 };
24896 }); // Object[]
24897 },
24898
24899 onDndDrop: function(source, nodes, copy){
24900 // summary:
24901 // Topic event processor for /dnd/drop, called to finish the DnD operation.
24902 // description:
24903 // Updates data store items according to where node was dragged from and dropped
24904 // to. The tree will then respond to those data store updates and redraw itself.
24905 // source: Object
24906 // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
24907 // nodes: DomNode[]
24908 // The list of transferred items, dndTreeNode nodes if dragging from a Tree
24909 // copy: Boolean
24910 // Copy items, if true, move items otherwise
24911 // tags:
24912 // protected
24913 if(this.containerState == "Over"){
24914 var tree = this.tree,
24915 model = tree.model,
24916 target = this.targetAnchor,
24917 requeryRoot = false; // set to true iff top level items change
24918
24919 this.isDragging = false;
24920
24921 // Compute the new parent item
24922 var targetWidget = dijit.getEnclosingWidget(target);
24923 var newParentItem;
24924 var insertIndex;
24925 newParentItem = (targetWidget && targetWidget.item) || tree.item;
24926 if(this.dropPosition == "Before" || this.dropPosition == "After"){
24927 // TODO: if there is no parent item then disallow the drop.
24928 // Actually this should be checked during onMouseMove too, to make the drag icon red.
24929 newParentItem = (targetWidget.getParent() && targetWidget.getParent().item) || tree.item;
24930 // Compute the insert index for reordering
24931 insertIndex = targetWidget.getIndexInParent();
24932 if(this.dropPosition == "After"){
24933 insertIndex = targetWidget.getIndexInParent() + 1;
24934 }
24935 }else{
24936 newParentItem = (targetWidget && targetWidget.item) || tree.item;
24937 }
24938
24939 // If necessary, use this variable to hold array of hashes to pass to model.newItem()
24940 // (one entry in the array for each dragged node).
24941 var newItemsParams;
24942
24943 dojo.forEach(nodes, function(node, idx){
24944 // dojo.dnd.Item representing the thing being dropped.
24945 // Don't confuse the use of item here (meaning a DnD item) with the
24946 // uses below where item means dojo.data item.
24947 var sourceItem = source.getItem(node.id);
24948
24949 // Information that's available if the source is another Tree
24950 // (possibly but not necessarily this tree, possibly but not
24951 // necessarily the same model as this Tree)
24952 if(dojo.indexOf(sourceItem.type, "treeNode") != -1){
24953 var childTreeNode = sourceItem.data,
24954 childItem = childTreeNode.item,
24955 oldParentItem = childTreeNode.getParent().item;
24956 }
24957
24958 if(source == this){
24959 // This is a node from my own tree, and we are moving it, not copying.
24960 // Remove item from old parent's children attribute.
24961 // TODO: dijit.tree.dndSelector should implement deleteSelectedNodes()
24962 // and this code should go there.
24963
24964 if(typeof insertIndex == "number"){
24965 if(newParentItem == oldParentItem && childTreeNode.getIndexInParent() < insertIndex){
24966 insertIndex -= 1;
24967 }
24968 }
24969 model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex);
24970 }else if(model.isItem(childItem)){
24971 // Item from same model
24972 // (maybe we should only do this branch if the source is a tree?)
24973 model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex);
24974 }else{
24975 // Get the hash to pass to model.newItem(). A single call to
24976 // itemCreator() returns an array of hashes, one for each drag source node.
24977 if(!newItemsParams){
24978 newItemsParams = this.itemCreator(nodes, target, source);
24979 }
24980
24981 // Create new item in the tree, based on the drag source.
24982 model.newItem(newItemsParams[idx], newParentItem, insertIndex);
24983 }
24984 }, this);
24985
24986 // Expand the target node (if it's currently collapsed) so the user can see
24987 // where their node was dropped. In particular since that node is still selected.
24988 this.tree._expandNode(targetWidget);
24989 }
24990 this.onDndCancel();
24991 },
24992
24993 onDndCancel: function(){
24994 // summary:
24995 // Topic event processor for /dnd/cancel, called to cancel the DnD operation
24996 // tags:
24997 // private
24998 this._unmarkTargetAnchor();
24999 this.isDragging = false;
25000 this.mouseDown = false;
25001 delete this.mouseButton;
25002 this._changeState("Source", "");
25003 this._changeState("Target", "");
25004 },
25005
25006 // When focus moves in/out of the entire Tree
25007 onOverEvent: function(){
25008 // summary:
25009 // This method is called when mouse is moved over our container (like onmouseenter)
25010 // tags:
25011 // private
25012 this.inherited(arguments);
25013 dojo.dnd.manager().overSource(this);
25014 },
25015 onOutEvent: function(){
25016 // summary:
25017 // This method is called when mouse is moved out of our container (like onmouseleave)
25018 // tags:
25019 // private
25020 this._unmarkTargetAnchor();
25021 var m = dojo.dnd.manager();
25022 if(this.isDragging){
25023 m.canDrop(false);
25024 }
25025 m.outSource(this);
25026
25027 this.inherited(arguments);
25028 },
25029
25030 _isParentChildDrop: function(source, targetRow){
25031 // summary:
25032 // Checks whether the dragged items are parent rows in the tree which are being
25033 // dragged into their own children.
25034 //
25035 // source:
25036 // The DragSource object.
25037 //
25038 // targetRow:
25039 // The tree row onto which the dragged nodes are being dropped.
25040 //
25041 // tags:
25042 // private
25043
25044 // If the dragged object is not coming from the tree this widget belongs to,
25045 // it cannot be invalid.
25046 if(!source.tree || source.tree != this.tree){
25047 return false;
25048 }
25049
25050
25051 var root = source.tree.domNode;
25052 var ids = {};
25053 for(var x in source.selection){
25054 ids[source.selection[x].parentNode.id] = true;
25055 }
25056
25057 var node = targetRow.parentNode;
25058
25059 // Iterate up the DOM hierarchy from the target drop row,
25060 // checking of any of the dragged nodes have the same ID.
25061 while(node != root && (!node.id || !ids[node.id])){
25062 node = node.parentNode;
25063 }
25064
25065 return node.id && ids[node.id];
25066 },
25067
25068 _unmarkTargetAnchor: function(){
25069 // summary:
25070 // Removes hover class of the current target anchor
25071 // tags:
25072 // private
25073 if(!this.targetAnchor){ return; }
25074 this._removeItemClass(this.targetAnchor, this.dropPosition);
25075 this.targetAnchor = null;
25076 this.targetBox = null;
25077 this.dropPosition = null;
25078 },
25079
25080 _markDndStatus: function(copy){
25081 // summary:
25082 // Changes source's state based on "copy" status
25083 this._changeState("Source", copy ? "Copied" : "Moved");
25084 }
25085 });
25086
25087 }
25088
25089 if(!dojo._hasResource["dojo.data.ItemFileReadStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
25090 dojo._hasResource["dojo.data.ItemFileReadStore"] = true;
25091 dojo.provide("dojo.data.ItemFileReadStore");
25092
25093
25094
25095
25096
25097 dojo.declare("dojo.data.ItemFileReadStore", null,{
25098 // summary:
25099 // The ItemFileReadStore implements the dojo.data.api.Read API and reads
25100 // data from JSON files that have contents in this format --
25101 // { items: [
25102 // { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]},
25103 // { name:'Fozzie Bear', wears:['hat', 'tie']},
25104 // { name:'Miss Piggy', pets:'Foo-Foo'}
25105 // ]}
25106 // Note that it can also contain an 'identifer' property that specified which attribute on the items
25107 // in the array of items that acts as the unique identifier for that item.
25108 //
25109 constructor: function(/* Object */ keywordParameters){
25110 // summary: constructor
25111 // keywordParameters: {url: String}
25112 // keywordParameters: {data: jsonObject}
25113 // keywordParameters: {typeMap: object)
25114 // The structure of the typeMap object is as follows:
25115 // {
25116 // type0: function || object,
25117 // type1: function || object,
25118 // ...
25119 // typeN: function || object
25120 // }
25121 // Where if it is a function, it is assumed to be an object constructor that takes the
25122 // value of _value as the initialization parameters. If it is an object, then it is assumed
25123 // to be an object of general form:
25124 // {
25125 // type: function, //constructor.
25126 // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately.
25127 // }
25128
25129 this._arrayOfAllItems = [];
25130 this._arrayOfTopLevelItems = [];
25131 this._loadFinished = false;
25132 this._jsonFileUrl = keywordParameters.url;
25133 this._ccUrl = keywordParameters.url;
25134 this.url = keywordParameters.url;
25135 this._jsonData = keywordParameters.data;
25136 this.data = null;
25137 this._datatypeMap = keywordParameters.typeMap || {};
25138 if(!this._datatypeMap['Date']){
25139 //If no default mapping for dates, then set this as default.
25140 //We use the dojo.date.stamp here because the ISO format is the 'dojo way'
25141 //of generically representing dates.
25142 this._datatypeMap['Date'] = {
25143 type: Date,
25144 deserialize: function(value){
25145 return dojo.date.stamp.fromISOString(value);
25146 }
25147 };
25148 }
25149 this._features = {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true};
25150 this._itemsByIdentity = null;
25151 this._storeRefPropName = "_S"; // Default name for the store reference to attach to every item.
25152 this._itemNumPropName = "_0"; // Default Item Id for isItem to attach to every item.
25153 this._rootItemPropName = "_RI"; // Default Item Id for isItem to attach to every item.
25154 this._reverseRefMap = "_RRM"; // Default attribute for constructing a reverse reference map for use with reference integrity
25155 this._loadInProgress = false; //Got to track the initial load to prevent duelling loads of the dataset.
25156 this._queuedFetches = [];
25157 if(keywordParameters.urlPreventCache !== undefined){
25158 this.urlPreventCache = keywordParameters.urlPreventCache?true:false;
25159 }
25160 if(keywordParameters.hierarchical !== undefined){
25161 this.hierarchical = keywordParameters.hierarchical?true:false;
25162 }
25163 if(keywordParameters.clearOnClose){
25164 this.clearOnClose = true;
25165 }
25166 if("failOk" in keywordParameters){
25167 this.failOk = keywordParameters.failOk?true:false;
25168 }
25169 },
25170
25171 url: "", // use "" rather than undefined for the benefit of the parser (#3539)
25172
25173 //Internal var, crossCheckUrl. Used so that setting either url or _jsonFileUrl, can still trigger a reload
25174 //when clearOnClose and close is used.
25175 _ccUrl: "",
25176
25177 data: null, // define this so that the parser can populate it
25178
25179 typeMap: null, //Define so parser can populate.
25180
25181 //Parameter to allow users to specify if a close call should force a reload or not.
25182 //By default, it retains the old behavior of not clearing if close is called. But
25183 //if set true, the store will be reset to default state. Note that by doing this,
25184 //all item handles will become invalid and a new fetch must be issued.
25185 clearOnClose: false,
25186
25187 //Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url.
25188 //Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option.
25189 //Added for tracker: #6072
25190 urlPreventCache: false,
25191
25192 //Parameter for specifying that it is OK for the xhrGet call to fail silently.
25193 failOk: false,
25194
25195 //Parameter to indicate to process data from the url as hierarchical
25196 //(data items can contain other data items in js form). Default is true
25197 //for backwards compatibility. False means only root items are processed
25198 //as items, all child objects outside of type-mapped objects and those in
25199 //specific reference format, are left straight JS data objects.
25200 hierarchical: true,
25201
25202 _assertIsItem: function(/* item */ item){
25203 // summary:
25204 // This function tests whether the item passed in is indeed an item in the store.
25205 // item:
25206 // The item to test for being contained by the store.
25207 if(!this.isItem(item)){
25208 throw new Error("dojo.data.ItemFileReadStore: Invalid item argument.");
25209 }
25210 },
25211
25212 _assertIsAttribute: function(/* attribute-name-string */ attribute){
25213 // summary:
25214 // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
25215 // attribute:
25216 // The attribute to test for being contained by the store.
25217 if(typeof attribute !== "string"){
25218 throw new Error("dojo.data.ItemFileReadStore: Invalid attribute argument.");
25219 }
25220 },
25221
25222 getValue: function( /* item */ item,
25223 /* attribute-name-string */ attribute,
25224 /* value? */ defaultValue){
25225 // summary:
25226 // See dojo.data.api.Read.getValue()
25227 var values = this.getValues(item, attribute);
25228 return (values.length > 0)?values[0]:defaultValue; // mixed
25229 },
25230
25231 getValues: function(/* item */ item,
25232 /* attribute-name-string */ attribute){
25233 // summary:
25234 // See dojo.data.api.Read.getValues()
25235
25236 this._assertIsItem(item);
25237 this._assertIsAttribute(attribute);
25238 // Clone it before returning. refs: #10474
25239 return (item[attribute] || []).slice(0); // Array
25240 },
25241
25242 getAttributes: function(/* item */ item){
25243 // summary:
25244 // See dojo.data.api.Read.getAttributes()
25245 this._assertIsItem(item);
25246 var attributes = [];
25247 for(var key in item){
25248 // Save off only the real item attributes, not the special id marks for O(1) isItem.
25249 if((key !== this._storeRefPropName) && (key !== this._itemNumPropName) && (key !== this._rootItemPropName) && (key !== this._reverseRefMap)){
25250 attributes.push(key);
25251 }
25252 }
25253 return attributes; // Array
25254 },
25255
25256 hasAttribute: function( /* item */ item,
25257 /* attribute-name-string */ attribute){
25258 // summary:
25259 // See dojo.data.api.Read.hasAttribute()
25260 this._assertIsItem(item);
25261 this._assertIsAttribute(attribute);
25262 return (attribute in item);
25263 },
25264
25265 containsValue: function(/* item */ item,
25266 /* attribute-name-string */ attribute,
25267 /* anything */ value){
25268 // summary:
25269 // See dojo.data.api.Read.containsValue()
25270 var regexp = undefined;
25271 if(typeof value === "string"){
25272 regexp = dojo.data.util.filter.patternToRegExp(value, false);
25273 }
25274 return this._containsValue(item, attribute, value, regexp); //boolean.
25275 },
25276
25277 _containsValue: function( /* item */ item,
25278 /* attribute-name-string */ attribute,
25279 /* anything */ value,
25280 /* RegExp?*/ regexp){
25281 // summary:
25282 // Internal function for looking at the values contained by the item.
25283 // description:
25284 // Internal function for looking at the values contained by the item. This
25285 // function allows for denoting if the comparison should be case sensitive for
25286 // strings or not (for handling filtering cases where string case should not matter)
25287 //
25288 // item:
25289 // The data item to examine for attribute values.
25290 // attribute:
25291 // The attribute to inspect.
25292 // value:
25293 // The value to match.
25294 // regexp:
25295 // Optional regular expression generated off value if value was of string type to handle wildcarding.
25296 // If present and attribute values are string, then it can be used for comparison instead of 'value'
25297 return dojo.some(this.getValues(item, attribute), function(possibleValue){
25298 if(possibleValue !== null && !dojo.isObject(possibleValue) && regexp){
25299 if(possibleValue.toString().match(regexp)){
25300 return true; // Boolean
25301 }
25302 }else if(value === possibleValue){
25303 return true; // Boolean
25304 }
25305 });
25306 },
25307
25308 isItem: function(/* anything */ something){
25309 // summary:
25310 // See dojo.data.api.Read.isItem()
25311 if(something && something[this._storeRefPropName] === this){
25312 if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){
25313 return true;
25314 }
25315 }
25316 return false; // Boolean
25317 },
25318
25319 isItemLoaded: function(/* anything */ something){
25320 // summary:
25321 // See dojo.data.api.Read.isItemLoaded()
25322 return this.isItem(something); //boolean
25323 },
25324
25325 loadItem: function(/* object */ keywordArgs){
25326 // summary:
25327 // See dojo.data.api.Read.loadItem()
25328 this._assertIsItem(keywordArgs.item);
25329 },
25330
25331 getFeatures: function(){
25332 // summary:
25333 // See dojo.data.api.Read.getFeatures()
25334 return this._features; //Object
25335 },
25336
25337 getLabel: function(/* item */ item){
25338 // summary:
25339 // See dojo.data.api.Read.getLabel()
25340 if(this._labelAttr && this.isItem(item)){
25341 return this.getValue(item,this._labelAttr); //String
25342 }
25343 return undefined; //undefined
25344 },
25345
25346 getLabelAttributes: function(/* item */ item){
25347 // summary:
25348 // See dojo.data.api.Read.getLabelAttributes()
25349 if(this._labelAttr){
25350 return [this._labelAttr]; //array
25351 }
25352 return null; //null
25353 },
25354
25355 _fetchItems: function( /* Object */ keywordArgs,
25356 /* Function */ findCallback,
25357 /* Function */ errorCallback){
25358 // summary:
25359 // See dojo.data.util.simpleFetch.fetch()
25360 var self = this,
25361 filter = function(requestArgs, arrayOfItems){
25362 var items = [],
25363 i, key;
25364 if(requestArgs.query){
25365 var value,
25366 ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false;
25367
25368 //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
25369 //same value for each item examined. Much more efficient.
25370 var regexpList = {};
25371 for(key in requestArgs.query){
25372 value = requestArgs.query[key];
25373 if(typeof value === "string"){
25374 regexpList[key] = dojo.data.util.filter.patternToRegExp(value, ignoreCase);
25375 }else if(value instanceof RegExp){
25376 regexpList[key] = value;
25377 }
25378 }
25379 for(i = 0; i < arrayOfItems.length; ++i){
25380 var match = true;
25381 var candidateItem = arrayOfItems[i];
25382 if(candidateItem === null){
25383 match = false;
25384 }else{
25385 for(key in requestArgs.query){
25386 value = requestArgs.query[key];
25387 if(!self._containsValue(candidateItem, key, value, regexpList[key])){
25388 match = false;
25389 }
25390 }
25391 }
25392 if(match){
25393 items.push(candidateItem);
25394 }
25395 }
25396 findCallback(items, requestArgs);
25397 }else{
25398 // We want a copy to pass back in case the parent wishes to sort the array.
25399 // We shouldn't allow resort of the internal list, so that multiple callers
25400 // can get lists and sort without affecting each other. We also need to
25401 // filter out any null values that have been left as a result of deleteItem()
25402 // calls in ItemFileWriteStore.
25403 for(i = 0; i < arrayOfItems.length; ++i){
25404 var item = arrayOfItems[i];
25405 if(item !== null){
25406 items.push(item);
25407 }
25408 }
25409 findCallback(items, requestArgs);
25410 }
25411 };
25412
25413 if(this._loadFinished){
25414 filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions));
25415 }else{
25416 //Do a check on the JsonFileUrl and crosscheck it.
25417 //If it doesn't match the cross-check, it needs to be updated
25418 //This allows for either url or _jsonFileUrl to he changed to
25419 //reset the store load location. Done this way for backwards
25420 //compatibility. People use _jsonFileUrl (even though officially
25421 //private.
25422 if(this._jsonFileUrl !== this._ccUrl){
25423 dojo.deprecated("dojo.data.ItemFileReadStore: ",
25424 "To change the url, set the url property of the store," +
25425 " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
25426 this._ccUrl = this._jsonFileUrl;
25427 this.url = this._jsonFileUrl;
25428 }else if(this.url !== this._ccUrl){
25429 this._jsonFileUrl = this.url;
25430 this._ccUrl = this.url;
25431 }
25432
25433 //See if there was any forced reset of data.
25434 if(this.data != null && this._jsonData == null){
25435 this._jsonData = this.data;
25436 this.data = null;
25437 }
25438
25439 if(this._jsonFileUrl){
25440 //If fetches come in before the loading has finished, but while
25441 //a load is in progress, we have to defer the fetching to be
25442 //invoked in the callback.
25443 if(this._loadInProgress){
25444 this._queuedFetches.push({args: keywordArgs, filter: filter});
25445 }else{
25446 this._loadInProgress = true;
25447 var getArgs = {
25448 url: self._jsonFileUrl,
25449 handleAs: "json-comment-optional",
25450 preventCache: this.urlPreventCache,
25451 failOk: this.failOk
25452 };
25453 var getHandler = dojo.xhrGet(getArgs);
25454 getHandler.addCallback(function(data){
25455 try{
25456 self._getItemsFromLoadedData(data);
25457 self._loadFinished = true;
25458 self._loadInProgress = false;
25459
25460 filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions));
25461 self._handleQueuedFetches();
25462 }catch(e){
25463 self._loadFinished = true;
25464 self._loadInProgress = false;
25465 errorCallback(e, keywordArgs);
25466 }
25467 });
25468 getHandler.addErrback(function(error){
25469 self._loadInProgress = false;
25470 errorCallback(error, keywordArgs);
25471 });
25472
25473 //Wire up the cancel to abort of the request
25474 //This call cancel on the deferred if it hasn't been called
25475 //yet and then will chain to the simple abort of the
25476 //simpleFetch keywordArgs
25477 var oldAbort = null;
25478 if(keywordArgs.abort){
25479 oldAbort = keywordArgs.abort;
25480 }
25481 keywordArgs.abort = function(){
25482 var df = getHandler;
25483 if(df && df.fired === -1){
25484 df.cancel();
25485 df = null;
25486 }
25487 if(oldAbort){
25488 oldAbort.call(keywordArgs);
25489 }
25490 };
25491 }
25492 }else if(this._jsonData){
25493 try{
25494 this._loadFinished = true;
25495 this._getItemsFromLoadedData(this._jsonData);
25496 this._jsonData = null;
25497 filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions));
25498 }catch(e){
25499 errorCallback(e, keywordArgs);
25500 }
25501 }else{
25502 errorCallback(new Error("dojo.data.ItemFileReadStore: No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs);
25503 }
25504 }
25505 },
25506
25507 _handleQueuedFetches: function(){
25508 // summary:
25509 // Internal function to execute delayed request in the store.
25510 //Execute any deferred fetches now.
25511 if(this._queuedFetches.length > 0){
25512 for(var i = 0; i < this._queuedFetches.length; i++){
25513 var fData = this._queuedFetches[i],
25514 delayedQuery = fData.args,
25515 delayedFilter = fData.filter;
25516 if(delayedFilter){
25517 delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions));
25518 }else{
25519 this.fetchItemByIdentity(delayedQuery);
25520 }
25521 }
25522 this._queuedFetches = [];
25523 }
25524 },
25525
25526 _getItemsArray: function(/*object?*/queryOptions){
25527 // summary:
25528 // Internal function to determine which list of items to search over.
25529 // queryOptions: The query options parameter, if any.
25530 if(queryOptions && queryOptions.deep){
25531 return this._arrayOfAllItems;
25532 }
25533 return this._arrayOfTopLevelItems;
25534 },
25535
25536 close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
25537 // summary:
25538 // See dojo.data.api.Read.close()
25539 if(this.clearOnClose &&
25540 this._loadFinished &&
25541 !this._loadInProgress){
25542 //Reset all internalsback to default state. This will force a reload
25543 //on next fetch. This also checks that the data or url param was set
25544 //so that the store knows it can get data. Without one of those being set,
25545 //the next fetch will trigger an error.
25546
25547 if(((this._jsonFileUrl == "" || this._jsonFileUrl == null) &&
25548 (this.url == "" || this.url == null)
25549 ) && this.data == null){
25550 console.debug("dojo.data.ItemFileReadStore: WARNING! Data reload " +
25551 " information has not been provided." +
25552 " Please set 'url' or 'data' to the appropriate value before" +
25553 " the next fetch");
25554 }
25555 this._arrayOfAllItems = [];
25556 this._arrayOfTopLevelItems = [];
25557 this._loadFinished = false;
25558 this._itemsByIdentity = null;
25559 this._loadInProgress = false;
25560 this._queuedFetches = [];
25561 }
25562 },
25563
25564 _getItemsFromLoadedData: function(/* Object */ dataObject){
25565 // summary:
25566 // Function to parse the loaded data into item format and build the internal items array.
25567 // description:
25568 // Function to parse the loaded data into item format and build the internal items array.
25569 //
25570 // dataObject:
25571 // The JS data object containing the raw data to convery into item format.
25572 //
25573 // returns: array
25574 // Array of items in store item format.
25575
25576 // First, we define a couple little utility functions...
25577 var addingArrays = false,
25578 self = this;
25579
25580 function valueIsAnItem(/* anything */ aValue){
25581 // summary:
25582 // Given any sort of value that could be in the raw json data,
25583 // return true if we should interpret the value as being an
25584 // item itself, rather than a literal value or a reference.
25585 // example:
25586 // | false == valueIsAnItem("Kermit");
25587 // | false == valueIsAnItem(42);
25588 // | false == valueIsAnItem(new Date());
25589 // | false == valueIsAnItem({_type:'Date', _value:'May 14, 1802'});
25590 // | false == valueIsAnItem({_reference:'Kermit'});
25591 // | true == valueIsAnItem({name:'Kermit', color:'green'});
25592 // | true == valueIsAnItem({iggy:'pop'});
25593 // | true == valueIsAnItem({foo:42});
25594 var isItem = (
25595 (aValue !== null) &&
25596 (typeof aValue === "object") &&
25597 (!dojo.isArray(aValue) || addingArrays) &&
25598 (!dojo.isFunction(aValue)) &&
25599 (aValue.constructor == Object || dojo.isArray(aValue)) &&
25600 (typeof aValue._reference === "undefined") &&
25601 (typeof aValue._type === "undefined") &&
25602 (typeof aValue._value === "undefined") &&
25603 self.hierarchical
25604 );
25605 return isItem;
25606 }
25607
25608 function addItemAndSubItemsToArrayOfAllItems(/* Item */ anItem){
25609 self._arrayOfAllItems.push(anItem);
25610 for(var attribute in anItem){
25611 var valueForAttribute = anItem[attribute];
25612 if(valueForAttribute){
25613 if(dojo.isArray(valueForAttribute)){
25614 var valueArray = valueForAttribute;
25615 for(var k = 0; k < valueArray.length; ++k){
25616 var singleValue = valueArray[k];
25617 if(valueIsAnItem(singleValue)){
25618 addItemAndSubItemsToArrayOfAllItems(singleValue);
25619 }
25620 }
25621 }else{
25622 if(valueIsAnItem(valueForAttribute)){
25623 addItemAndSubItemsToArrayOfAllItems(valueForAttribute);
25624 }
25625 }
25626 }
25627 }
25628 }
25629
25630 this._labelAttr = dataObject.label;
25631
25632 // We need to do some transformations to convert the data structure
25633 // that we read from the file into a format that will be convenient
25634 // to work with in memory.
25635
25636 // Step 1: Walk through the object hierarchy and build a list of all items
25637 var i,
25638 item;
25639 this._arrayOfAllItems = [];
25640 this._arrayOfTopLevelItems = dataObject.items;
25641
25642 for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){
25643 item = this._arrayOfTopLevelItems[i];
25644 if(dojo.isArray(item)){
25645 addingArrays = true;
25646 }
25647 addItemAndSubItemsToArrayOfAllItems(item);
25648 item[this._rootItemPropName]=true;
25649 }
25650
25651 // Step 2: Walk through all the attribute values of all the items,
25652 // and replace single values with arrays. For example, we change this:
25653 // { name:'Miss Piggy', pets:'Foo-Foo'}
25654 // into this:
25655 // { name:['Miss Piggy'], pets:['Foo-Foo']}
25656 //
25657 // We also store the attribute names so we can validate our store
25658 // reference and item id special properties for the O(1) isItem
25659 var allAttributeNames = {},
25660 key;
25661
25662 for(i = 0; i < this._arrayOfAllItems.length; ++i){
25663 item = this._arrayOfAllItems[i];
25664 for(key in item){
25665 if(key !== this._rootItemPropName){
25666 var value = item[key];
25667 if(value !== null){
25668 if(!dojo.isArray(value)){
25669 item[key] = [value];
25670 }
25671 }else{
25672 item[key] = [null];
25673 }
25674 }
25675 allAttributeNames[key]=key;
25676 }
25677 }
25678
25679 // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName
25680 // This should go really fast, it will generally never even run the loop.
25681 while(allAttributeNames[this._storeRefPropName]){
25682 this._storeRefPropName += "_";
25683 }
25684 while(allAttributeNames[this._itemNumPropName]){
25685 this._itemNumPropName += "_";
25686 }
25687 while(allAttributeNames[this._reverseRefMap]){
25688 this._reverseRefMap += "_";
25689 }
25690
25691 // Step 4: Some data files specify an optional 'identifier', which is
25692 // the name of an attribute that holds the identity of each item.
25693 // If this data file specified an identifier attribute, then build a
25694 // hash table of items keyed by the identity of the items.
25695 var arrayOfValues;
25696
25697 var identifier = dataObject.identifier;
25698 if(identifier){
25699 this._itemsByIdentity = {};
25700 this._features['dojo.data.api.Identity'] = identifier;
25701 for(i = 0; i < this._arrayOfAllItems.length; ++i){
25702 item = this._arrayOfAllItems[i];
25703 arrayOfValues = item[identifier];
25704 var identity = arrayOfValues[0];
25705 if(!this._itemsByIdentity[identity]){
25706 this._itemsByIdentity[identity] = item;
25707 }else{
25708 if(this._jsonFileUrl){
25709 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 + "]");
25710 }else if(this._jsonData){
25711 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 + "]");
25712 }
25713 }
25714 }
25715 }else{
25716 this._features['dojo.data.api.Identity'] = Number;
25717 }
25718
25719 // Step 5: Walk through all the items, and set each item's properties
25720 // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true.
25721 for(i = 0; i < this._arrayOfAllItems.length; ++i){
25722 item = this._arrayOfAllItems[i];
25723 item[this._storeRefPropName] = this;
25724 item[this._itemNumPropName] = i;
25725 }
25726
25727 // Step 6: We walk through all the attribute values of all the items,
25728 // looking for type/value literals and item-references.
25729 //
25730 // We replace item-references with pointers to items. For example, we change:
25731 // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
25732 // into this:
25733 // { name:['Kermit'], friends:[miss_piggy] }
25734 // (where miss_piggy is the object representing the 'Miss Piggy' item).
25735 //
25736 // We replace type/value pairs with typed-literals. For example, we change:
25737 // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'July 18, 1918'}] }
25738 // into this:
25739 // { name:['Kermit'], born:(new Date('July 18, 1918')) }
25740 //
25741 // We also generate the associate map for all items for the O(1) isItem function.
25742 for(i = 0; i < this._arrayOfAllItems.length; ++i){
25743 item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
25744 for(key in item){
25745 arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}]
25746 for(var j = 0; j < arrayOfValues.length; ++j){
25747 value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}}
25748 if(value !== null && typeof value == "object"){
25749 if(("_type" in value) && ("_value" in value)){
25750 var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber'
25751 var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}}
25752 if(!mappingObj){
25753 throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'");
25754 }else if(dojo.isFunction(mappingObj)){
25755 arrayOfValues[j] = new mappingObj(value._value);
25756 }else if(dojo.isFunction(mappingObj.deserialize)){
25757 arrayOfValues[j] = mappingObj.deserialize(value._value);
25758 }else{
25759 throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function");
25760 }
25761 }
25762 if(value._reference){
25763 var referenceDescription = value._reference; // example: {name:'Miss Piggy'}
25764 if(!dojo.isObject(referenceDescription)){
25765 // example: 'Miss Piggy'
25766 // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]}
25767 arrayOfValues[j] = this._getItemByIdentity(referenceDescription);
25768 }else{
25769 // example: {name:'Miss Piggy'}
25770 // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
25771 for(var k = 0; k < this._arrayOfAllItems.length; ++k){
25772 var candidateItem = this._arrayOfAllItems[k],
25773 found = true;
25774 for(var refKey in referenceDescription){
25775 if(candidateItem[refKey] != referenceDescription[refKey]){
25776 found = false;
25777 }
25778 }
25779 if(found){
25780 arrayOfValues[j] = candidateItem;
25781 }
25782 }
25783 }
25784 if(this.referenceIntegrity){
25785 var refItem = arrayOfValues[j];
25786 if(this.isItem(refItem)){
25787 this._addReferenceToMap(refItem, item, key);
25788 }
25789 }
25790 }else if(this.isItem(value)){
25791 //It's a child item (not one referenced through _reference).
25792 //We need to treat this as a referenced item, so it can be cleaned up
25793 //in a write store easily.
25794 if(this.referenceIntegrity){
25795 this._addReferenceToMap(value, item, key);
25796 }
25797 }
25798 }
25799 }
25800 }
25801 }
25802 },
25803
25804 _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){
25805 // summary:
25806 // Method to add an reference map entry for an item and attribute.
25807 // description:
25808 // Method to add an reference map entry for an item and attribute. //
25809 // refItem:
25810 // The item that is referenced.
25811 // parentItem:
25812 // The item that holds the new reference to refItem.
25813 // attribute:
25814 // The attribute on parentItem that contains the new reference.
25815
25816 //Stub function, does nothing. Real processing is in ItemFileWriteStore.
25817 },
25818
25819 getIdentity: function(/* item */ item){
25820 // summary:
25821 // See dojo.data.api.Identity.getIdentity()
25822 var identifier = this._features['dojo.data.api.Identity'];
25823 if(identifier === Number){
25824 return item[this._itemNumPropName]; // Number
25825 }else{
25826 var arrayOfValues = item[identifier];
25827 if(arrayOfValues){
25828 return arrayOfValues[0]; // Object || String
25829 }
25830 }
25831 return null; // null
25832 },
25833
25834 fetchItemByIdentity: function(/* Object */ keywordArgs){
25835 // summary:
25836 // See dojo.data.api.Identity.fetchItemByIdentity()
25837
25838 // Hasn't loaded yet, we have to trigger the load.
25839 var item,
25840 scope;
25841 if(!this._loadFinished){
25842 var self = this;
25843 //Do a check on the JsonFileUrl and crosscheck it.
25844 //If it doesn't match the cross-check, it needs to be updated
25845 //This allows for either url or _jsonFileUrl to he changed to
25846 //reset the store load location. Done this way for backwards
25847 //compatibility. People use _jsonFileUrl (even though officially
25848 //private.
25849 if(this._jsonFileUrl !== this._ccUrl){
25850 dojo.deprecated("dojo.data.ItemFileReadStore: ",
25851 "To change the url, set the url property of the store," +
25852 " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
25853 this._ccUrl = this._jsonFileUrl;
25854 this.url = this._jsonFileUrl;
25855 }else if(this.url !== this._ccUrl){
25856 this._jsonFileUrl = this.url;
25857 this._ccUrl = this.url;
25858 }
25859
25860 //See if there was any forced reset of data.
25861 if(this.data != null && this._jsonData == null){
25862 this._jsonData = this.data;
25863 this.data = null;
25864 }
25865
25866 if(this._jsonFileUrl){
25867
25868 if(this._loadInProgress){
25869 this._queuedFetches.push({args: keywordArgs});
25870 }else{
25871 this._loadInProgress = true;
25872 var getArgs = {
25873 url: self._jsonFileUrl,
25874 handleAs: "json-comment-optional",
25875 preventCache: this.urlPreventCache,
25876 failOk: this.failOk
25877 };
25878 var getHandler = dojo.xhrGet(getArgs);
25879 getHandler.addCallback(function(data){
25880 var scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
25881 try{
25882 self._getItemsFromLoadedData(data);
25883 self._loadFinished = true;
25884 self._loadInProgress = false;
25885 item = self._getItemByIdentity(keywordArgs.identity);
25886 if(keywordArgs.onItem){
25887 keywordArgs.onItem.call(scope, item);
25888 }
25889 self._handleQueuedFetches();
25890 }catch(error){
25891 self._loadInProgress = false;
25892 if(keywordArgs.onError){
25893 keywordArgs.onError.call(scope, error);
25894 }
25895 }
25896 });
25897 getHandler.addErrback(function(error){
25898 self._loadInProgress = false;
25899 if(keywordArgs.onError){
25900 var scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
25901 keywordArgs.onError.call(scope, error);
25902 }
25903 });
25904 }
25905
25906 }else if(this._jsonData){
25907 // Passed in data, no need to xhr.
25908 self._getItemsFromLoadedData(self._jsonData);
25909 self._jsonData = null;
25910 self._loadFinished = true;
25911 item = self._getItemByIdentity(keywordArgs.identity);
25912 if(keywordArgs.onItem){
25913 scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
25914 keywordArgs.onItem.call(scope, item);
25915 }
25916 }
25917 }else{
25918 // Already loaded. We can just look it up and call back.
25919 item = this._getItemByIdentity(keywordArgs.identity);
25920 if(keywordArgs.onItem){
25921 scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
25922 keywordArgs.onItem.call(scope, item);
25923 }
25924 }
25925 },
25926
25927 _getItemByIdentity: function(/* Object */ identity){
25928 // summary:
25929 // Internal function to look an item up by its identity map.
25930 var item = null;
25931 if(this._itemsByIdentity){
25932 item = this._itemsByIdentity[identity];
25933 }else{
25934 item = this._arrayOfAllItems[identity];
25935 }
25936 if(item === undefined){
25937 item = null;
25938 }
25939 return item; // Object
25940 },
25941
25942 getIdentityAttributes: function(/* item */ item){
25943 // summary:
25944 // See dojo.data.api.Identity.getIdentifierAttributes()
25945
25946 var identifier = this._features['dojo.data.api.Identity'];
25947 if(identifier === Number){
25948 // If (identifier === Number) it means getIdentity() just returns
25949 // an integer item-number for each item. The dojo.data.api.Identity
25950 // spec says we need to return null if the identity is not composed
25951 // of attributes
25952 return null; // null
25953 }else{
25954 return [identifier]; // Array
25955 }
25956 },
25957
25958 _forceLoad: function(){
25959 // summary:
25960 // Internal function to force a load of the store if it hasn't occurred yet. This is required
25961 // for specific functions to work properly.
25962 var self = this;
25963 //Do a check on the JsonFileUrl and crosscheck it.
25964 //If it doesn't match the cross-check, it needs to be updated
25965 //This allows for either url or _jsonFileUrl to he changed to
25966 //reset the store load location. Done this way for backwards
25967 //compatibility. People use _jsonFileUrl (even though officially
25968 //private.
25969 if(this._jsonFileUrl !== this._ccUrl){
25970 dojo.deprecated("dojo.data.ItemFileReadStore: ",
25971 "To change the url, set the url property of the store," +
25972 " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
25973 this._ccUrl = this._jsonFileUrl;
25974 this.url = this._jsonFileUrl;
25975 }else if(this.url !== this._ccUrl){
25976 this._jsonFileUrl = this.url;
25977 this._ccUrl = this.url;
25978 }
25979
25980 //See if there was any forced reset of data.
25981 if(this.data != null && this._jsonData == null){
25982 this._jsonData = this.data;
25983 this.data = null;
25984 }
25985
25986 if(this._jsonFileUrl){
25987 var getArgs = {
25988 url: this._jsonFileUrl,
25989 handleAs: "json-comment-optional",
25990 preventCache: this.urlPreventCache,
25991 failOk: this.failOk,
25992 sync: true
25993 };
25994 var getHandler = dojo.xhrGet(getArgs);
25995 getHandler.addCallback(function(data){
25996 try{
25997 //Check to be sure there wasn't another load going on concurrently
25998 //So we don't clobber data that comes in on it. If there is a load going on
25999 //then do not save this data. It will potentially clobber current data.
26000 //We mainly wanted to sync/wait here.
26001 //TODO: Revisit the loading scheme of this store to improve multi-initial
26002 //request handling.
26003 if(self._loadInProgress !== true && !self._loadFinished){
26004 self._getItemsFromLoadedData(data);
26005 self._loadFinished = true;
26006 }else if(self._loadInProgress){
26007 //Okay, we hit an error state we can't recover from. A forced load occurred
26008 //while an async load was occurring. Since we cannot block at this point, the best
26009 //that can be managed is to throw an error.
26010 throw new Error("dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress.");
26011 }
26012 }catch(e){
26013 console.log(e);
26014 throw e;
26015 }
26016 });
26017 getHandler.addErrback(function(error){
26018 throw error;
26019 });
26020 }else if(this._jsonData){
26021 self._getItemsFromLoadedData(self._jsonData);
26022 self._jsonData = null;
26023 self._loadFinished = true;
26024 }
26025 }
26026 });
26027 //Mix in the simple fetch implementation to this class.
26028 dojo.extend(dojo.data.ItemFileReadStore,dojo.data.util.simpleFetch);
26029
26030 }
26031
26032 if(!dojo._hasResource["dojo.data.ItemFileWriteStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
26033 dojo._hasResource["dojo.data.ItemFileWriteStore"] = true;
26034 dojo.provide("dojo.data.ItemFileWriteStore");
26035
26036
26037 dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
26038 constructor: function(/* object */ keywordParameters){
26039 // keywordParameters: {typeMap: object)
26040 // The structure of the typeMap object is as follows:
26041 // {
26042 // type0: function || object,
26043 // type1: function || object,
26044 // ...
26045 // typeN: function || object
26046 // }
26047 // Where if it is a function, it is assumed to be an object constructor that takes the
26048 // value of _value as the initialization parameters. It is serialized assuming object.toString()
26049 // serialization. If it is an object, then it is assumed
26050 // to be an object of general form:
26051 // {
26052 // type: function, //constructor.
26053 // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately.
26054 // serialize: function(object) //The function that converts the object back into the proper file format form.
26055 // }
26056
26057 // ItemFileWriteStore extends ItemFileReadStore to implement these additional dojo.data APIs
26058 this._features['dojo.data.api.Write'] = true;
26059 this._features['dojo.data.api.Notification'] = true;
26060
26061 // For keeping track of changes so that we can implement isDirty and revert
26062 this._pending = {
26063 _newItems:{},
26064 _modifiedItems:{},
26065 _deletedItems:{}
26066 };
26067
26068 if(!this._datatypeMap['Date'].serialize){
26069 this._datatypeMap['Date'].serialize = function(obj){
26070 return dojo.date.stamp.toISOString(obj, {zulu:true});
26071 };
26072 }
26073 //Disable only if explicitly set to false.
26074 if(keywordParameters && (keywordParameters.referenceIntegrity === false)){
26075 this.referenceIntegrity = false;
26076 }
26077
26078 // this._saveInProgress is set to true, briefly, from when save() is first called to when it completes
26079 this._saveInProgress = false;
26080 },
26081
26082 referenceIntegrity: true, //Flag that defaultly enabled reference integrity tracking. This way it can also be disabled pogrammatially or declaratively.
26083
26084 _assert: function(/* boolean */ condition){
26085 if(!condition){
26086 throw new Error("assertion failed in ItemFileWriteStore");
26087 }
26088 },
26089
26090 _getIdentifierAttribute: function(){
26091 var identifierAttribute = this.getFeatures()['dojo.data.api.Identity'];
26092 // this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute)));
26093 return identifierAttribute;
26094 },
26095
26096
26097 /* dojo.data.api.Write */
26098
26099 newItem: function(/* Object? */ keywordArgs, /* Object? */ parentInfo){
26100 // summary: See dojo.data.api.Write.newItem()
26101
26102 this._assert(!this._saveInProgress);
26103
26104 if(!this._loadFinished){
26105 // We need to do this here so that we'll be able to find out what
26106 // identifierAttribute was specified in the data file.
26107 this._forceLoad();
26108 }
26109
26110 if(typeof keywordArgs != "object" && typeof keywordArgs != "undefined"){
26111 throw new Error("newItem() was passed something other than an object");
26112 }
26113 var newIdentity = null;
26114 var identifierAttribute = this._getIdentifierAttribute();
26115 if(identifierAttribute === Number){
26116 newIdentity = this._arrayOfAllItems.length;
26117 }else{
26118 newIdentity = keywordArgs[identifierAttribute];
26119 if(typeof newIdentity === "undefined"){
26120 throw new Error("newItem() was not passed an identity for the new item");
26121 }
26122 if(dojo.isArray(newIdentity)){
26123 throw new Error("newItem() was not passed an single-valued identity");
26124 }
26125 }
26126
26127 // make sure this identity is not already in use by another item, if identifiers were
26128 // defined in the file. Otherwise it would be the item count,
26129 // which should always be unique in this case.
26130 if(this._itemsByIdentity){
26131 this._assert(typeof this._itemsByIdentity[newIdentity] === "undefined");
26132 }
26133 this._assert(typeof this._pending._newItems[newIdentity] === "undefined");
26134 this._assert(typeof this._pending._deletedItems[newIdentity] === "undefined");
26135
26136 var newItem = {};
26137 newItem[this._storeRefPropName] = this;
26138 newItem[this._itemNumPropName] = this._arrayOfAllItems.length;
26139 if(this._itemsByIdentity){
26140 this._itemsByIdentity[newIdentity] = newItem;
26141 //We have to set the identifier now, otherwise we can't look it
26142 //up at calls to setValueorValues in parentInfo handling.
26143 newItem[identifierAttribute] = [newIdentity];
26144 }
26145 this._arrayOfAllItems.push(newItem);
26146
26147 //We need to construct some data for the onNew call too...
26148 var pInfo = null;
26149
26150 // Now we need to check to see where we want to assign this thingm if any.
26151 if(parentInfo && parentInfo.parent && parentInfo.attribute){
26152 pInfo = {
26153 item: parentInfo.parent,
26154 attribute: parentInfo.attribute,
26155 oldValue: undefined
26156 };
26157
26158 //See if it is multi-valued or not and handle appropriately
26159 //Generally, all attributes are multi-valued for this store
26160 //So, we only need to append if there are already values present.
26161 var values = this.getValues(parentInfo.parent, parentInfo.attribute);
26162 if(values && values.length > 0){
26163 var tempValues = values.slice(0, values.length);
26164 if(values.length === 1){
26165 pInfo.oldValue = values[0];
26166 }else{
26167 pInfo.oldValue = values.slice(0, values.length);
26168 }
26169 tempValues.push(newItem);
26170 this._setValueOrValues(parentInfo.parent, parentInfo.attribute, tempValues, false);
26171 pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute);
26172 }else{
26173 this._setValueOrValues(parentInfo.parent, parentInfo.attribute, newItem, false);
26174 pInfo.newValue = newItem;
26175 }
26176 }else{
26177 //Toplevel item, add to both top list as well as all list.
26178 newItem[this._rootItemPropName]=true;
26179 this._arrayOfTopLevelItems.push(newItem);
26180 }
26181
26182 this._pending._newItems[newIdentity] = newItem;
26183
26184 //Clone over the properties to the new item
26185 for(var key in keywordArgs){
26186 if(key === this._storeRefPropName || key === this._itemNumPropName){
26187 // Bummer, the user is trying to do something like
26188 // newItem({_S:"foo"}). Unfortunately, our superclass,
26189 // ItemFileReadStore, is already using _S in each of our items
26190 // to hold private info. To avoid a naming collision, we
26191 // need to move all our private info to some other property
26192 // of all the items/objects. So, we need to iterate over all
26193 // the items and do something like:
26194 // item.__S = item._S;
26195 // item._S = undefined;
26196 // But first we have to make sure the new "__S" variable is
26197 // not in use, which means we have to iterate over all the
26198 // items checking for that.
26199 throw new Error("encountered bug in ItemFileWriteStore.newItem");
26200 }
26201 var value = keywordArgs[key];
26202 if(!dojo.isArray(value)){
26203 value = [value];
26204 }
26205 newItem[key] = value;
26206 if(this.referenceIntegrity){
26207 for(var i = 0; i < value.length; i++){
26208 var val = value[i];
26209 if(this.isItem(val)){
26210 this._addReferenceToMap(val, newItem, key);
26211 }
26212 }
26213 }
26214 }
26215 this.onNew(newItem, pInfo); // dojo.data.api.Notification call
26216 return newItem; // item
26217 },
26218
26219 _removeArrayElement: function(/* Array */ array, /* anything */ element){
26220 var index = dojo.indexOf(array, element);
26221 if(index != -1){
26222 array.splice(index, 1);
26223 return true;
26224 }
26225 return false;
26226 },
26227
26228 deleteItem: function(/* item */ item){
26229 // summary: See dojo.data.api.Write.deleteItem()
26230 this._assert(!this._saveInProgress);
26231 this._assertIsItem(item);
26232
26233 // Remove this item from the _arrayOfAllItems, but leave a null value in place
26234 // of the item, so as not to change the length of the array, so that in newItem()
26235 // we can still safely do: newIdentity = this._arrayOfAllItems.length;
26236 var indexInArrayOfAllItems = item[this._itemNumPropName];
26237 var identity = this.getIdentity(item);
26238
26239 //If we have reference integrity on, we need to do reference cleanup for the deleted item
26240 if(this.referenceIntegrity){
26241 //First scan all the attributes of this items for references and clean them up in the map
26242 //As this item is going away, no need to track its references anymore.
26243
26244 //Get the attributes list before we generate the backup so it
26245 //doesn't pollute the attributes list.
26246 var attributes = this.getAttributes(item);
26247
26248 //Backup the map, we'll have to restore it potentially, in a revert.
26249 if(item[this._reverseRefMap]){
26250 item["backup_" + this._reverseRefMap] = dojo.clone(item[this._reverseRefMap]);
26251 }
26252
26253 //TODO: This causes a reversion problem. This list won't be restored on revert since it is
26254 //attached to the 'value'. item, not ours. Need to back tese up somehow too.
26255 //Maybe build a map of the backup of the entries and attach it to the deleted item to be restored
26256 //later. Or just record them and call _addReferenceToMap on them in revert.
26257 dojo.forEach(attributes, function(attribute){
26258 dojo.forEach(this.getValues(item, attribute), function(value){
26259 if(this.isItem(value)){
26260 //We have to back up all the references we had to others so they can be restored on a revert.
26261 if(!item["backupRefs_" + this._reverseRefMap]){
26262 item["backupRefs_" + this._reverseRefMap] = [];
26263 }
26264 item["backupRefs_" + this._reverseRefMap].push({id: this.getIdentity(value), attr: attribute});
26265 this._removeReferenceFromMap(value, item, attribute);
26266 }
26267 }, this);
26268 }, this);
26269
26270 //Next, see if we have references to this item, if we do, we have to clean them up too.
26271 var references = item[this._reverseRefMap];
26272 if(references){
26273 //Look through all the items noted as references to clean them up.
26274 for(var itemId in references){
26275 var containingItem = null;
26276 if(this._itemsByIdentity){
26277 containingItem = this._itemsByIdentity[itemId];
26278 }else{
26279 containingItem = this._arrayOfAllItems[itemId];
26280 }
26281 //We have a reference to a containing item, now we have to process the
26282 //attributes and clear all references to the item being deleted.
26283 if(containingItem){
26284 for(var attribute in references[itemId]){
26285 var oldValues = this.getValues(containingItem, attribute) || [];
26286 var newValues = dojo.filter(oldValues, function(possibleItem){
26287 return !(this.isItem(possibleItem) && this.getIdentity(possibleItem) == identity);
26288 }, this);
26289 //Remove the note of the reference to the item and set the values on the modified attribute.
26290 this._removeReferenceFromMap(item, containingItem, attribute);
26291 if(newValues.length < oldValues.length){
26292 this._setValueOrValues(containingItem, attribute, newValues, true);
26293 }
26294 }
26295 }
26296 }
26297 }
26298 }
26299
26300 this._arrayOfAllItems[indexInArrayOfAllItems] = null;
26301
26302 item[this._storeRefPropName] = null;
26303 if(this._itemsByIdentity){
26304 delete this._itemsByIdentity[identity];
26305 }
26306 this._pending._deletedItems[identity] = item;
26307
26308 //Remove from the toplevel items, if necessary...
26309 if(item[this._rootItemPropName]){
26310 this._removeArrayElement(this._arrayOfTopLevelItems, item);
26311 }
26312 this.onDelete(item); // dojo.data.api.Notification call
26313 return true;
26314 },
26315
26316 setValue: function(/* item */ item, /* attribute-name-string */ attribute, /* almost anything */ value){
26317 // summary: See dojo.data.api.Write.set()
26318 return this._setValueOrValues(item, attribute, value, true); // boolean
26319 },
26320
26321 setValues: function(/* item */ item, /* attribute-name-string */ attribute, /* array */ values){
26322 // summary: See dojo.data.api.Write.setValues()
26323 return this._setValueOrValues(item, attribute, values, true); // boolean
26324 },
26325
26326 unsetAttribute: function(/* item */ item, /* attribute-name-string */ attribute){
26327 // summary: See dojo.data.api.Write.unsetAttribute()
26328 return this._setValueOrValues(item, attribute, [], true);
26329 },
26330
26331 _setValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute, /* anything */ newValueOrValues, /*boolean?*/ callOnSet){
26332 this._assert(!this._saveInProgress);
26333
26334 // Check for valid arguments
26335 this._assertIsItem(item);
26336 this._assert(dojo.isString(attribute));
26337 this._assert(typeof newValueOrValues !== "undefined");
26338
26339 // Make sure the user isn't trying to change the item's identity
26340 var identifierAttribute = this._getIdentifierAttribute();
26341 if(attribute == identifierAttribute){
26342 throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier.");
26343 }
26344
26345 // To implement the Notification API, we need to make a note of what
26346 // the old attribute value was, so that we can pass that info when
26347 // we call the onSet method.
26348 var oldValueOrValues = this._getValueOrValues(item, attribute);
26349
26350 var identity = this.getIdentity(item);
26351 if(!this._pending._modifiedItems[identity]){
26352 // Before we actually change the item, we make a copy of it to
26353 // record the original state, so that we'll be able to revert if
26354 // the revert method gets called. If the item has already been
26355 // modified then there's no need to do this now, since we already
26356 // have a record of the original state.
26357 var copyOfItemState = {};
26358 for(var key in item){
26359 if((key === this._storeRefPropName) || (key === this._itemNumPropName) || (key === this._rootItemPropName)){
26360 copyOfItemState[key] = item[key];
26361 }else if(key === this._reverseRefMap){
26362 copyOfItemState[key] = dojo.clone(item[key]);
26363 }else{
26364 copyOfItemState[key] = item[key].slice(0, item[key].length);
26365 }
26366 }
26367 // Now mark the item as dirty, and save the copy of the original state
26368 this._pending._modifiedItems[identity] = copyOfItemState;
26369 }
26370
26371 // Okay, now we can actually change this attribute on the item
26372 var success = false;
26373
26374 if(dojo.isArray(newValueOrValues) && newValueOrValues.length === 0){
26375
26376 // If we were passed an empty array as the value, that counts
26377 // as "unsetting" the attribute, so we need to remove this
26378 // attribute from the item.
26379 success = delete item[attribute];
26380 newValueOrValues = undefined; // used in the onSet Notification call below
26381
26382 if(this.referenceIntegrity && oldValueOrValues){
26383 var oldValues = oldValueOrValues;
26384 if(!dojo.isArray(oldValues)){
26385 oldValues = [oldValues];
26386 }
26387 for(var i = 0; i < oldValues.length; i++){
26388 var value = oldValues[i];
26389 if(this.isItem(value)){
26390 this._removeReferenceFromMap(value, item, attribute);
26391 }
26392 }
26393 }
26394 }else{
26395 var newValueArray;
26396 if(dojo.isArray(newValueOrValues)){
26397 var newValues = newValueOrValues;
26398 // Unfortunately, it's not safe to just do this:
26399 // newValueArray = newValues;
26400 // Instead, we need to copy the array, which slice() does very nicely.
26401 // This is so that our internal data structure won't
26402 // get corrupted if the user mucks with the values array *after*
26403 // calling setValues().
26404 newValueArray = newValueOrValues.slice(0, newValueOrValues.length);
26405 }else{
26406 newValueArray = [newValueOrValues];
26407 }
26408
26409 //We need to handle reference integrity if this is on.
26410 //In the case of set, we need to see if references were added or removed
26411 //and update the reference tracking map accordingly.
26412 if(this.referenceIntegrity){
26413 if(oldValueOrValues){
26414 var oldValues = oldValueOrValues;
26415 if(!dojo.isArray(oldValues)){
26416 oldValues = [oldValues];
26417 }
26418 //Use an associative map to determine what was added/removed from the list.
26419 //Should be O(n) performant. First look at all the old values and make a list of them
26420 //Then for any item not in the old list, we add it. If it was already present, we remove it.
26421 //Then we pass over the map and any references left it it need to be removed (IE, no match in
26422 //the new values list).
26423 var map = {};
26424 dojo.forEach(oldValues, function(possibleItem){
26425 if(this.isItem(possibleItem)){
26426 var id = this.getIdentity(possibleItem);
26427 map[id.toString()] = true;
26428 }
26429 }, this);
26430 dojo.forEach(newValueArray, function(possibleItem){
26431 if(this.isItem(possibleItem)){
26432 var id = this.getIdentity(possibleItem);
26433 if(map[id.toString()]){
26434 delete map[id.toString()];
26435 }else{
26436 this._addReferenceToMap(possibleItem, item, attribute);
26437 }
26438 }
26439 }, this);
26440 for(var rId in map){
26441 var removedItem;
26442 if(this._itemsByIdentity){
26443 removedItem = this._itemsByIdentity[rId];
26444 }else{
26445 removedItem = this._arrayOfAllItems[rId];
26446 }
26447 this._removeReferenceFromMap(removedItem, item, attribute);
26448 }
26449 }else{
26450 //Everything is new (no old values) so we have to just
26451 //insert all the references, if any.
26452 for(var i = 0; i < newValueArray.length; i++){
26453 var value = newValueArray[i];
26454 if(this.isItem(value)){
26455 this._addReferenceToMap(value, item, attribute);
26456 }
26457 }
26458 }
26459 }
26460 item[attribute] = newValueArray;
26461 success = true;
26462 }
26463
26464 // Now we make the dojo.data.api.Notification call
26465 if(callOnSet){
26466 this.onSet(item, attribute, oldValueOrValues, newValueOrValues);
26467 }
26468 return success; // boolean
26469 },
26470
26471 _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){
26472 // summary:
26473 // Method to add an reference map entry for an item and attribute.
26474 // description:
26475 // Method to add an reference map entry for an item and attribute. //
26476 // refItem:
26477 // The item that is referenced.
26478 // parentItem:
26479 // The item that holds the new reference to refItem.
26480 // attribute:
26481 // The attribute on parentItem that contains the new reference.
26482
26483 var parentId = this.getIdentity(parentItem);
26484 var references = refItem[this._reverseRefMap];
26485
26486 if(!references){
26487 references = refItem[this._reverseRefMap] = {};
26488 }
26489 var itemRef = references[parentId];
26490 if(!itemRef){
26491 itemRef = references[parentId] = {};
26492 }
26493 itemRef[attribute] = true;
26494 },
26495
26496 _removeReferenceFromMap: function(/* item */ refItem, /* item */ parentItem, /*strin*/ attribute){
26497 // summary:
26498 // Method to remove an reference map entry for an item and attribute.
26499 // description:
26500 // Method to remove an reference map entry for an item and attribute. This will
26501 // also perform cleanup on the map such that if there are no more references at all to
26502 // the item, its reference object and entry are removed.
26503 //
26504 // refItem:
26505 // The item that is referenced.
26506 // parentItem:
26507 // The item holding a reference to refItem.
26508 // attribute:
26509 // The attribute on parentItem that contains the reference.
26510 var identity = this.getIdentity(parentItem);
26511 var references = refItem[this._reverseRefMap];
26512 var itemId;
26513 if(references){
26514 for(itemId in references){
26515 if(itemId == identity){
26516 delete references[itemId][attribute];
26517 if(this._isEmpty(references[itemId])){
26518 delete references[itemId];
26519 }
26520 }
26521 }
26522 if(this._isEmpty(references)){
26523 delete refItem[this._reverseRefMap];
26524 }
26525 }
26526 },
26527
26528 _dumpReferenceMap: function(){
26529 // summary:
26530 // Function to dump the reverse reference map of all items in the store for debug purposes.
26531 // description:
26532 // Function to dump the reverse reference map of all items in the store for debug purposes.
26533 var i;
26534 for(i = 0; i < this._arrayOfAllItems.length; i++){
26535 var item = this._arrayOfAllItems[i];
26536 if(item && item[this._reverseRefMap]){
26537 console.log("Item: [" + this.getIdentity(item) + "] is referenced by: " + dojo.toJson(item[this._reverseRefMap]));
26538 }
26539 }
26540 },
26541
26542 _getValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute){
26543 var valueOrValues = undefined;
26544 if(this.hasAttribute(item, attribute)){
26545 var valueArray = this.getValues(item, attribute);
26546 if(valueArray.length == 1){
26547 valueOrValues = valueArray[0];
26548 }else{
26549 valueOrValues = valueArray;
26550 }
26551 }
26552 return valueOrValues;
26553 },
26554
26555 _flatten: function(/* anything */ value){
26556 if(this.isItem(value)){
26557 var item = value;
26558 // Given an item, return an serializable object that provides a
26559 // reference to the item.
26560 // For example, given kermit:
26561 // var kermit = store.newItem({id:2, name:"Kermit"});
26562 // we want to return
26563 // {_reference:2}
26564 var identity = this.getIdentity(item);
26565 var referenceObject = {_reference: identity};
26566 return referenceObject;
26567 }else{
26568 if(typeof value === "object"){
26569 for(var type in this._datatypeMap){
26570 var typeMap = this._datatypeMap[type];
26571 if(dojo.isObject(typeMap) && !dojo.isFunction(typeMap)){
26572 if(value instanceof typeMap.type){
26573 if(!typeMap.serialize){
26574 throw new Error("ItemFileWriteStore: No serializer defined for type mapping: [" + type + "]");
26575 }
26576 return {_type: type, _value: typeMap.serialize(value)};
26577 }
26578 } else if(value instanceof typeMap){
26579 //SImple mapping, therefore, return as a toString serialization.
26580 return {_type: type, _value: value.toString()};
26581 }
26582 }
26583 }
26584 return value;
26585 }
26586 },
26587
26588 _getNewFileContentString: function(){
26589 // summary:
26590 // Generate a string that can be saved to a file.
26591 // The result should look similar to:
26592 // http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json
26593 var serializableStructure = {};
26594
26595 var identifierAttribute = this._getIdentifierAttribute();
26596 if(identifierAttribute !== Number){
26597 serializableStructure.identifier = identifierAttribute;
26598 }
26599 if(this._labelAttr){
26600 serializableStructure.label = this._labelAttr;
26601 }
26602 serializableStructure.items = [];
26603 for(var i = 0; i < this._arrayOfAllItems.length; ++i){
26604 var item = this._arrayOfAllItems[i];
26605 if(item !== null){
26606 var serializableItem = {};
26607 for(var key in item){
26608 if(key !== this._storeRefPropName && key !== this._itemNumPropName && key !== this._reverseRefMap && key !== this._rootItemPropName){
26609 var attribute = key;
26610 var valueArray = this.getValues(item, attribute);
26611 if(valueArray.length == 1){
26612 serializableItem[attribute] = this._flatten(valueArray[0]);
26613 }else{
26614 var serializableArray = [];
26615 for(var j = 0; j < valueArray.length; ++j){
26616 serializableArray.push(this._flatten(valueArray[j]));
26617 serializableItem[attribute] = serializableArray;
26618 }
26619 }
26620 }
26621 }
26622 serializableStructure.items.push(serializableItem);
26623 }
26624 }
26625 var prettyPrint = true;
26626 return dojo.toJson(serializableStructure, prettyPrint);
26627 },
26628
26629 _isEmpty: function(something){
26630 // summary:
26631 // Function to determine if an array or object has no properties or values.
26632 // something:
26633 // The array or object to examine.
26634 var empty = true;
26635 if(dojo.isObject(something)){
26636 var i;
26637 for(i in something){
26638 empty = false;
26639 break;
26640 }
26641 }else if(dojo.isArray(something)){
26642 if(something.length > 0){
26643 empty = false;
26644 }
26645 }
26646 return empty; //boolean
26647 },
26648
26649 save: function(/* object */ keywordArgs){
26650 // summary: See dojo.data.api.Write.save()
26651 this._assert(!this._saveInProgress);
26652
26653 // this._saveInProgress is set to true, briefly, from when save is first called to when it completes
26654 this._saveInProgress = true;
26655
26656 var self = this;
26657 var saveCompleteCallback = function(){
26658 self._pending = {
26659 _newItems:{},
26660 _modifiedItems:{},
26661 _deletedItems:{}
26662 };
26663
26664 self._saveInProgress = false; // must come after this._pending is cleared, but before any callbacks
26665 if(keywordArgs && keywordArgs.onComplete){
26666 var scope = keywordArgs.scope || dojo.global;
26667 keywordArgs.onComplete.call(scope);
26668 }
26669 };
26670 var saveFailedCallback = function(err){
26671 self._saveInProgress = false;
26672 if(keywordArgs && keywordArgs.onError){
26673 var scope = keywordArgs.scope || dojo.global;
26674 keywordArgs.onError.call(scope, err);
26675 }
26676 };
26677
26678 if(this._saveEverything){
26679 var newFileContentString = this._getNewFileContentString();
26680 this._saveEverything(saveCompleteCallback, saveFailedCallback, newFileContentString);
26681 }
26682 if(this._saveCustom){
26683 this._saveCustom(saveCompleteCallback, saveFailedCallback);
26684 }
26685 if(!this._saveEverything && !this._saveCustom){
26686 // Looks like there is no user-defined save-handler function.
26687 // That's fine, it just means the datastore is acting as a "mock-write"
26688 // store -- changes get saved in memory but don't get saved to disk.
26689 saveCompleteCallback();
26690 }
26691 },
26692
26693 revert: function(){
26694 // summary: See dojo.data.api.Write.revert()
26695 this._assert(!this._saveInProgress);
26696
26697 var identity;
26698 for(identity in this._pending._modifiedItems){
26699 // find the original item and the modified item that replaced it
26700 var copyOfItemState = this._pending._modifiedItems[identity];
26701 var modifiedItem = null;
26702 if(this._itemsByIdentity){
26703 modifiedItem = this._itemsByIdentity[identity];
26704 }else{
26705 modifiedItem = this._arrayOfAllItems[identity];
26706 }
26707
26708 // Restore the original item into a full-fledged item again, we want to try to
26709 // keep the same object instance as if we don't it, causes bugs like #9022.
26710 copyOfItemState[this._storeRefPropName] = this;
26711 for(key in modifiedItem){
26712 delete modifiedItem[key];
26713 }
26714 dojo.mixin(modifiedItem, copyOfItemState);
26715 }
26716 var deletedItem;
26717 for(identity in this._pending._deletedItems){
26718 deletedItem = this._pending._deletedItems[identity];
26719 deletedItem[this._storeRefPropName] = this;
26720 var index = deletedItem[this._itemNumPropName];
26721
26722 //Restore the reverse refererence map, if any.
26723 if(deletedItem["backup_" + this._reverseRefMap]){
26724 deletedItem[this._reverseRefMap] = deletedItem["backup_" + this._reverseRefMap];
26725 delete deletedItem["backup_" + this._reverseRefMap];
26726 }
26727 this._arrayOfAllItems[index] = deletedItem;
26728 if(this._itemsByIdentity){
26729 this._itemsByIdentity[identity] = deletedItem;
26730 }
26731 if(deletedItem[this._rootItemPropName]){
26732 this._arrayOfTopLevelItems.push(deletedItem);
26733 }
26734 }
26735 //We have to pass through it again and restore the reference maps after all the
26736 //undeletes have occurred.
26737 for(identity in this._pending._deletedItems){
26738 deletedItem = this._pending._deletedItems[identity];
26739 if(deletedItem["backupRefs_" + this._reverseRefMap]){
26740 dojo.forEach(deletedItem["backupRefs_" + this._reverseRefMap], function(reference){
26741 var refItem;
26742 if(this._itemsByIdentity){
26743 refItem = this._itemsByIdentity[reference.id];
26744 }else{
26745 refItem = this._arrayOfAllItems[reference.id];
26746 }
26747 this._addReferenceToMap(refItem, deletedItem, reference.attr);
26748 }, this);
26749 delete deletedItem["backupRefs_" + this._reverseRefMap];
26750 }
26751 }
26752
26753 for(identity in this._pending._newItems){
26754 var newItem = this._pending._newItems[identity];
26755 newItem[this._storeRefPropName] = null;
26756 // null out the new item, but don't change the array index so
26757 // so we can keep using _arrayOfAllItems.length.
26758 this._arrayOfAllItems[newItem[this._itemNumPropName]] = null;
26759 if(newItem[this._rootItemPropName]){
26760 this._removeArrayElement(this._arrayOfTopLevelItems, newItem);
26761 }
26762 if(this._itemsByIdentity){
26763 delete this._itemsByIdentity[identity];
26764 }
26765 }
26766
26767 this._pending = {
26768 _newItems:{},
26769 _modifiedItems:{},
26770 _deletedItems:{}
26771 };
26772 return true; // boolean
26773 },
26774
26775 isDirty: function(/* item? */ item){
26776 // summary: See dojo.data.api.Write.isDirty()
26777 if(item){
26778 // return true if the item is dirty
26779 var identity = this.getIdentity(item);
26780 return new Boolean(this._pending._newItems[identity] ||
26781 this._pending._modifiedItems[identity] ||
26782 this._pending._deletedItems[identity]).valueOf(); // boolean
26783 }else{
26784 // return true if the store is dirty -- which means return true
26785 // if there are any new items, dirty items, or modified items
26786 if(!this._isEmpty(this._pending._newItems) ||
26787 !this._isEmpty(this._pending._modifiedItems) ||
26788 !this._isEmpty(this._pending._deletedItems)){
26789 return true;
26790 }
26791 return false; // boolean
26792 }
26793 },
26794
26795 /* dojo.data.api.Notification */
26796
26797 onSet: function(/* item */ item,
26798 /*attribute-name-string*/ attribute,
26799 /*object | array*/ oldValue,
26800 /*object | array*/ newValue){
26801 // summary: See dojo.data.api.Notification.onSet()
26802
26803 // No need to do anything. This method is here just so that the
26804 // client code can connect observers to it.
26805 },
26806
26807 onNew: function(/* item */ newItem, /*object?*/ parentInfo){
26808 // summary: See dojo.data.api.Notification.onNew()
26809
26810 // No need to do anything. This method is here just so that the
26811 // client code can connect observers to it.
26812 },
26813
26814 onDelete: function(/* item */ deletedItem){
26815 // summary: See dojo.data.api.Notification.onDelete()
26816
26817 // No need to do anything. This method is here just so that the
26818 // client code can connect observers to it.
26819 },
26820
26821 close: function(/* object? */ request){
26822 // summary:
26823 // Over-ride of base close function of ItemFileReadStore to add in check for store state.
26824 // description:
26825 // Over-ride of base close function of ItemFileReadStore to add in check for store state.
26826 // If the store is still dirty (unsaved changes), then an error will be thrown instead of
26827 // clearing the internal state for reload from the url.
26828
26829 //Clear if not dirty ... or throw an error
26830 if(this.clearOnClose){
26831 if(!this.isDirty()){
26832 this.inherited(arguments);
26833 }else{
26834 //Only throw an error if the store was dirty and we were loading from a url (cannot reload from url until state is saved).
26835 throw new Error("dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close.");
26836 }
26837 }
26838 }
26839 });
26840
26841 }
26842
26843
26844 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"]);