]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/tt-rss-layer.js.uncompressed.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dojo / tt-rss-layer.js.uncompressed.js
CommitLineData
a089699c 1/*
81bea17a 2 Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
a089699c
AD
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
14dojo.provide("tt-rss-layer");
15if(!dojo._hasResource["dojo.date.stamp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16dojo._hasResource["dojo.date.stamp"] = true;
17dojo.provide("dojo.date.stamp");
18
81bea17a
AD
19dojo.getObject("date.stamp", true, dojo);
20
a089699c
AD
21// Methods to convert dates to or from a wire (string) format using well-known conventions
22
23dojo.date.stamp.fromISOString = function(/*String*/formattedString, /*Number?*/defaultTime){
24 // summary:
25 // Returns a Date object given a string formatted according to a subset of the ISO-8601 standard.
26 //
27 // description:
28 // Accepts a string formatted according to a profile of ISO8601 as defined by
29 // [RFC3339](http://www.ietf.org/rfc/rfc3339.txt), except that partial input is allowed.
30 // Can also process dates as specified [by the W3C](http://www.w3.org/TR/NOTE-datetime)
31 // The following combinations are valid:
32 //
33 // * dates only
34 // | * yyyy
35 // | * yyyy-MM
36 // | * yyyy-MM-dd
37 // * times only, with an optional time zone appended
38 // | * THH:mm
39 // | * THH:mm:ss
40 // | * THH:mm:ss.SSS
41 // * and "datetimes" which could be any combination of the above
42 //
43 // timezones may be specified as Z (for UTC) or +/- followed by a time expression HH:mm
44 // Assumes the local time zone if not specified. Does not validate. Improperly formatted
45 // input may return null. Arguments which are out of bounds will be handled
46 // by the Date constructor (e.g. January 32nd typically gets resolved to February 1st)
47 // Only years between 100 and 9999 are supported.
48 //
49 // formattedString:
50 // A string such as 2005-06-30T08:05:00-07:00 or 2005-06-30 or T08:05:00
51 //
52 // defaultTime:
53 // Used for defaults for fields omitted in the formattedString.
54 // Uses 1970-01-01T00:00:00.0Z by default.
55
56 if(!dojo.date.stamp._isoRegExp){
57 dojo.date.stamp._isoRegExp =
58//TODO: could be more restrictive and check for 00-59, etc.
59 /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/;
60 }
61
62 var match = dojo.date.stamp._isoRegExp.exec(formattedString),
63 result = null;
64
65 if(match){
66 match.shift();
67 if(match[1]){match[1]--;} // Javascript Date months are 0-based
68 if(match[6]){match[6] *= 1000;} // Javascript Date expects fractional seconds as milliseconds
69
70 if(defaultTime){
71 // mix in defaultTime. Relatively expensive, so use || operators for the fast path of defaultTime === 0
72 defaultTime = new Date(defaultTime);
73 dojo.forEach(dojo.map(["FullYear", "Month", "Date", "Hours", "Minutes", "Seconds", "Milliseconds"], function(prop){
74 return defaultTime["get" + prop]();
75 }), function(value, index){
76 match[index] = match[index] || value;
77 });
78 }
79 result = new Date(match[0]||1970, match[1]||0, match[2]||1, match[3]||0, match[4]||0, match[5]||0, match[6]||0); //TODO: UTC defaults
80 if(match[0] < 100){
81 result.setFullYear(match[0] || 1970);
82 }
83
84 var offset = 0,
85 zoneSign = match[7] && match[7].charAt(0);
86 if(zoneSign != 'Z'){
87 offset = ((match[8] || 0) * 60) + (Number(match[9]) || 0);
88 if(zoneSign != '-'){ offset *= -1; }
89 }
90 if(zoneSign){
91 offset -= result.getTimezoneOffset();
92 }
93 if(offset){
94 result.setTime(result.getTime() + offset * 60000);
95 }
96 }
97
98 return result; // Date or null
81bea17a 99};
a089699c
AD
100
101/*=====
102 dojo.date.stamp.__Options = function(){
103 // selector: String
104 // "date" or "time" for partial formatting of the Date object.
105 // Both date and time will be formatted by default.
106 // zulu: Boolean
107 // if true, UTC/GMT is used for a timezone
108 // milliseconds: Boolean
109 // if true, output milliseconds
110 this.selector = selector;
111 this.zulu = zulu;
112 this.milliseconds = milliseconds;
113 }
114=====*/
115
116dojo.date.stamp.toISOString = function(/*Date*/dateObject, /*dojo.date.stamp.__Options?*/options){
117 // summary:
118 // Format a Date object as a string according a subset of the ISO-8601 standard
119 //
120 // description:
121 // When options.selector is omitted, output follows [RFC3339](http://www.ietf.org/rfc/rfc3339.txt)
122 // The local time zone is included as an offset from GMT, except when selector=='time' (time without a date)
123 // Does not check bounds. Only years between 100 and 9999 are supported.
124 //
125 // dateObject:
126 // A Date object
127
128 var _ = function(n){ return (n < 10) ? "0" + n : n; };
129 options = options || {};
130 var formattedDate = [],
131 getter = options.zulu ? "getUTC" : "get",
132 date = "";
133 if(options.selector != "time"){
134 var year = dateObject[getter+"FullYear"]();
135 date = ["0000".substr((year+"").length)+year, _(dateObject[getter+"Month"]()+1), _(dateObject[getter+"Date"]())].join('-');
136 }
137 formattedDate.push(date);
138 if(options.selector != "date"){
139 var time = [_(dateObject[getter+"Hours"]()), _(dateObject[getter+"Minutes"]()), _(dateObject[getter+"Seconds"]())].join(':');
140 var millis = dateObject[getter+"Milliseconds"]();
141 if(options.milliseconds){
142 time += "."+ (millis < 100 ? "0" : "") + _(millis);
143 }
144 if(options.zulu){
145 time += "Z";
146 }else if(options.selector != "time"){
147 var timezoneOffset = dateObject.getTimezoneOffset();
148 var absOffset = Math.abs(timezoneOffset);
81bea17a 149 time += (timezoneOffset > 0 ? "-" : "+") +
a089699c
AD
150 _(Math.floor(absOffset/60)) + ":" + _(absOffset%60);
151 }
152 formattedDate.push(time);
153 }
154 return formattedDate.join('T'); // String
81bea17a 155};
a089699c
AD
156
157}
158
159if(!dojo._hasResource["dojo.parser"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
160dojo._hasResource["dojo.parser"] = true;
161dojo.provide("dojo.parser");
162
163
81bea17a 164
a089699c
AD
165new Date("X"); // workaround for #11279, new Date("") == NaN
166
167dojo.parser = new function(){
81bea17a
AD
168 // summary:
169 // The Dom/Widget parsing package
a089699c
AD
170
171 var d = dojo;
a089699c
AD
172
173 function val2type(/*Object*/ value){
174 // summary:
175 // Returns name of type of given value.
176
177 if(d.isString(value)){ return "string"; }
178 if(typeof value == "number"){ return "number"; }
179 if(typeof value == "boolean"){ return "boolean"; }
180 if(d.isFunction(value)){ return "function"; }
181 if(d.isArray(value)){ return "array"; } // typeof [] == "object"
182 if(value instanceof Date) { return "date"; } // assume timestamp
183 if(value instanceof d._Url){ return "url"; }
184 return "object";
185 }
186
187 function str2obj(/*String*/ value, /*String*/ type){
188 // summary:
189 // Convert given string value to given type
190 switch(type){
191 case "string":
192 return value;
193 case "number":
194 return value.length ? Number(value) : NaN;
195 case "boolean":
81bea17a 196 // for checked/disabled value might be "" or "checked". interpret as true.
a089699c
AD
197 return typeof value == "boolean" ? value : !(value.toLowerCase()=="false");
198 case "function":
199 if(d.isFunction(value)){
200 // IE gives us a function, even when we say something like onClick="foo"
81bea17a
AD
201 // (in which case it gives us an invalid function "function(){ foo }").
202 // Therefore, convert to string
a089699c
AD
203 value=value.toString();
204 value=d.trim(value.substring(value.indexOf('{')+1, value.length-1));
205 }
206 try{
207 if(value === "" || value.search(/[^\w\.]+/i) != -1){
208 // The user has specified some text for a function like "return x+5"
209 return new Function(value);
210 }else{
211 // The user has specified the name of a function like "myOnClick"
212 // or a single word function "return"
213 return d.getObject(value, false) || new Function(value);
214 }
215 }catch(e){ return new Function(); }
216 case "array":
217 return value ? value.split(/\s*,\s*/) : [];
218 case "date":
219 switch(value){
220 case "": return new Date(""); // the NaN of dates
221 case "now": return new Date(); // current date
222 default: return d.date.stamp.fromISOString(value);
223 }
224 case "url":
225 return d.baseUrl + value;
226 default:
227 return d.fromJson(value);
228 }
229 }
230
81bea17a 231 var dummyClass = {}, instanceClasses = {
a089699c
AD
232 // map from fully qualified name (like "dijit.Button") to structure like
233 // { cls: dijit.Button, params: {label: "string", disabled: "boolean"} }
234 };
235
236 // Widgets like BorderContainer add properties to _Widget via dojo.extend().
237 // If BorderContainer is loaded after _Widget's parameter list has been cached,
238 // we need to refresh that parameter list (for _Widget and all widgets that extend _Widget).
81bea17a
AD
239 // TODO: remove this in 2.0, when we stop caching parameters.
240 d.connect(d, "extend", function(){
a089699c
AD
241 instanceClasses = {};
242 });
243
81bea17a
AD
244 function getProtoInfo(cls, params){
245 // cls: A prototype
246 // The prototype of the class to check props on
247 // params: Object
248 // The parameters object to mix found parameters onto.
249 for(var name in cls){
250 if(name.charAt(0)=="_"){ continue; } // skip internal properties
251 if(name in dummyClass){ continue; } // skip "constructor" and "toString"
252 params[name] = val2type(cls[name]);
253 }
254 return params;
255 }
256
257 function getClassInfo(/*String*/ className, /*Boolean*/ skipParamsLookup){
258 // summary:
259 // Maps a widget name string like "dijit.form.Button" to the widget constructor itself,
260 // and a list of that widget's parameters and their types
a089699c
AD
261 // className:
262 // fully qualified name (like "dijit.form.Button")
263 // returns:
264 // structure like
81bea17a
AD
265 // {
266 // cls: dijit.Button,
a089699c
AD
267 // params: { label: "string", disabled: "boolean"}
268 // }
269
81bea17a
AD
270 var c = instanceClasses[className];
271 if(!c){
a089699c 272 // get pointer to widget class
81bea17a 273 var cls = d.getObject(className), params = null;
a089699c 274 if(!cls){ return null; } // class not defined [yet]
81bea17a
AD
275 if(!skipParamsLookup){ // from fastpath, we don't need to lookup the attrs on the proto because they are explicit
276 params = getProtoInfo(cls.prototype, {})
a089699c 277 }
81bea17a
AD
278 c = { cls: cls, params: params };
279
280 }else if(!skipParamsLookup && !c.params){
281 // if we're calling getClassInfo and have a cls proto, but no params info, scan that cls for params now
282 // and update the pointer in instanceClasses[className]. This happens when a widget appears in another
283 // widget's template which still uses dojoType, but an instance of the widget appears prior with a data-dojo-type,
284 // skipping this lookup the first time.
285 c.params = getProtoInfo(c.cls.prototype, {});
a089699c 286 }
81bea17a
AD
287
288 return c;
a089699c
AD
289 }
290
81bea17a
AD
291 this._functionFromScript = function(script, attrData){
292 // summary:
293 // Convert a <script type="dojo/method" args="a, b, c"> ... </script>
294 // into a function
295 // script: DOMNode
296 // The <script> DOMNode
297 // attrData: String
298 // For HTML5 compliance, searches for attrData + "args" (typically
299 // "data-dojo-args") instead of "args"
a089699c
AD
300 var preamble = "";
301 var suffix = "";
81bea17a 302 var argsStr = (script.getAttribute(attrData + "args") || script.getAttribute("args"));
a089699c
AD
303 if(argsStr){
304 d.forEach(argsStr.split(/\s*,\s*/), function(part, idx){
305 preamble += "var "+part+" = arguments["+idx+"]; ";
306 });
307 }
308 var withStr = script.getAttribute("with");
309 if(withStr && withStr.length){
310 d.forEach(withStr.split(/\s*,\s*/), function(part){
311 preamble += "with("+part+"){";
312 suffix += "}";
313 });
314 }
315 return new Function(preamble+script.innerHTML+suffix);
81bea17a 316 };
a089699c
AD
317
318 this.instantiate = function(/* Array */nodes, /* Object? */mixin, /* Object? */args){
319 // summary:
320 // Takes array of nodes, and turns them into class instances and
321 // potentially calls a startup method to allow them to connect with
322 // any children.
323 // nodes: Array
324 // Array of nodes or objects like
325 // | {
326 // | type: "dijit.form.Button",
327 // | node: DOMNode,
328 // | scripts: [ ... ], // array of <script type="dojo/..."> children of node
329 // | inherited: { ... } // settings inherited from ancestors like dir, theme, etc.
330 // | }
331 // mixin: Object?
332 // An object that will be mixed in with each node in the array.
333 // Values in the mixin will override values in the node, if they
334 // exist.
335 // args: Object?
336 // An object used to hold kwArgs for instantiation.
81bea17a
AD
337 // See parse.args argument for details.
338
339 var thelist = [],
a089699c
AD
340 mixin = mixin||{};
341 args = args||{};
81bea17a
AD
342
343 // TODO: for 2.0 default to data-dojo- regardless of scopeName (or maybe scopeName won't exist in 2.0)
344 var attrName = (args.scope || d._scopeName) + "Type", // typically "dojoType"
345 attrData = "data-" + (args.scope || d._scopeName) + "-"; // typically "data-dojo-"
346
a089699c
AD
347 d.forEach(nodes, function(obj){
348 if(!obj){ return; }
349
81bea17a
AD
350 // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc.
351 var node, type, clsInfo, clazz, scripts, fastpath;
a089699c
AD
352 if(obj.node){
353 // new format of nodes[] array, object w/lots of properties pre-computed for me
354 node = obj.node;
355 type = obj.type;
81bea17a
AD
356 fastpath = obj.fastpath;
357 clsInfo = obj.clsInfo || (type && getClassInfo(type, fastpath));
a089699c
AD
358 clazz = clsInfo && clsInfo.cls;
359 scripts = obj.scripts;
360 }else{
81bea17a 361 // old (backwards compatible) format of nodes[] array, simple array of DOMNodes. no fastpath/data-dojo-type support here.
a089699c 362 node = obj;
81bea17a 363 type = attrName in mixin ? mixin[attrName] : node.getAttribute(attrName);
a089699c
AD
364 clsInfo = type && getClassInfo(type);
365 clazz = clsInfo && clsInfo.cls;
81bea17a 366 scripts = (clazz && (clazz._noScript || clazz.prototype._noScript) ? [] :
a089699c
AD
367 d.query("> script[type^='dojo/']", node));
368 }
369 if(!clsInfo){
370 throw new Error("Could not load class '" + type);
371 }
372
81bea17a 373 // Setup hash to hold parameter settings for this widget. Start with the parameter
a089699c
AD
374 // settings inherited from ancestors ("dir" and "lang").
375 // Inherited setting may later be overridden by explicit settings on node itself.
81bea17a
AD
376 var params = {};
377
a089699c
AD
378 if(args.defaults){
379 // settings for the document itself (or whatever subtree is being parsed)
81bea17a 380 d._mixin(params, args.defaults);
a089699c
AD
381 }
382 if(obj.inherited){
383 // settings from dir=rtl or lang=... on a node above this node
81bea17a 384 d._mixin(params, obj.inherited);
a089699c 385 }
81bea17a
AD
386
387 // mix things found in data-dojo-props into the params
388 if(fastpath){
389 var extra = node.getAttribute(attrData + "props");
390 if(extra && extra.length){
391 try{
392 extra = d.fromJson.call(args.propsThis, "{" + extra + "}");
393 d._mixin(params, extra);
394 }catch(e){
395 // give the user a pointer to their invalid parameters. FIXME: can we kill this in production?
396 throw new Error(e.toString() + " in data-dojo-props='" + extra + "'");
397 }
398 }
a089699c 399
81bea17a
AD
400 // For the benefit of _Templated, check if node has data-dojo-attach-point/data-dojo-attach-event
401 // and mix those in as though they were parameters
402 var attachPoint = node.getAttribute(attrData + "attach-point");
403 if(attachPoint){
404 params.dojoAttachPoint = attachPoint;
a089699c 405 }
81bea17a
AD
406 var attachEvent = node.getAttribute(attrData + "attach-event");
407 if(attachEvent){
408 params.dojoAttachEvent = attachEvent;
409 }
410 dojo.mixin(params, mixin);
411 }else{
412 // FIXME: we need something like "deprecateOnce()" to throw dojo.deprecation for something.
413 // remove this logic in 2.0
414 // read parameters (ie, attributes) specified on DOMNode
415
416 var attributes = node.attributes;
417
418 // clsInfo.params lists expected params like {"checked": "boolean", "n": "number"}
419 for(var name in clsInfo.params){
420 var item = name in mixin ? { value:mixin[name], specified:true } : attributes.getNamedItem(name);
421 if(!item || (!item.specified && (!dojo.isIE || name.toLowerCase()!="value"))){ continue; }
422 var value = item.value;
423 // Deal with IE quirks for 'class' and 'style'
424 switch(name){
425 case "class":
426 value = "className" in mixin ? mixin.className : node.className;
427 break;
428 case "style":
429 value = "style" in mixin ? mixin.style : (node.style && node.style.cssText); // FIXME: Opera?
430 }
431 var _type = clsInfo.params[name];
432 if(typeof value == "string"){
433 params[name] = str2obj(value, _type);
434 }else{
435 params[name] = value;
436 }
a089699c
AD
437 }
438 }
439
440 // Process <script type="dojo/*"> script tags
441 // <script type="dojo/method" event="foo"> tags are added to params, and passed to
442 // the widget on instantiation.
443 // <script type="dojo/method"> tags (with no event) are executed after instantiation
444 // <script type="dojo/connect" event="foo"> tags are dojo.connected after instantiation
445 // note: dojo/* script tags cannot exist in self closing widgets, like <input />
446 var connects = [], // functions to connect after instantiation
447 calls = []; // functions to call after instantiation
448
449 d.forEach(scripts, function(script){
450 node.removeChild(script);
81bea17a
AD
451 // FIXME: drop event="" support in 2.0. use data-dojo-event="" instead
452 var event = (script.getAttribute(attrData + "event") || script.getAttribute("event")),
a089699c 453 type = script.getAttribute("type"),
81bea17a 454 nf = d.parser._functionFromScript(script, attrData);
a089699c
AD
455 if(event){
456 if(type == "dojo/connect"){
457 connects.push({event: event, func: nf});
458 }else{
459 params[event] = nf;
460 }
461 }else{
462 calls.push(nf);
463 }
464 });
465
466 var markupFactory = clazz.markupFactory || clazz.prototype && clazz.prototype.markupFactory;
467 // create the instance
468 var instance = markupFactory ? markupFactory(params, node, clazz) : new clazz(params, node);
469 thelist.push(instance);
470
471 // map it to the JS namespace if that makes sense
81bea17a
AD
472 // FIXME: in 2.0, drop jsId support. use data-dojo-id instead
473 var jsname = (node.getAttribute(attrData + "id") || node.getAttribute("jsId"));
a089699c
AD
474 if(jsname){
475 d.setObject(jsname, instance);
476 }
477
478 // process connections and startup functions
479 d.forEach(connects, function(connect){
480 d.connect(instance, connect.event, null, connect.func);
481 });
482 d.forEach(calls, function(func){
483 func.call(instance);
484 });
485 });
486
487 // Call startup on each top level instance if it makes sense (as for
488 // widgets). Parent widgets will recursively call startup on their
489 // (non-top level) children
490 if(!mixin._started){
491 // TODO: for 2.0, when old instantiate() API is desupported, store parent-child
492 // relationships in the nodes[] array so that no getParent() call is needed.
493 // Note that will require a parse() call from ContentPane setting a param that the
494 // ContentPane is the parent widget (so that the parse doesn't call startup() on the
495 // ContentPane's children)
496 d.forEach(thelist, function(instance){
81bea17a
AD
497 if( !args.noStart && instance &&
498 dojo.isFunction(instance.startup) &&
499 !instance._started &&
a089699c
AD
500 (!instance.getParent || !instance.getParent())
501 ){
502 instance.startup();
503 }
504 });
505 }
506 return thelist;
507 };
508
81bea17a 509 this.parse = function(rootNode, args){
a089699c
AD
510 // summary:
511 // Scan the DOM for class instances, and instantiate them.
512 //
513 // description:
514 // Search specified node (or root node) recursively for class instances,
81bea17a
AD
515 // and instantiate them. Searches for either data-dojo-type="Class" or
516 // dojoType="Class" where "Class" is a a fully qualified class name,
517 // like `dijit.form.Button`
518 //
519 // Using `data-dojo-type`:
520 // Attributes using can be mixed into the parameters used to instantitate the
521 // Class by using a `data-dojo-props` attribute on the node being converted.
522 // `data-dojo-props` should be a string attribute to be converted from JSON.
523 //
524 // Using `dojoType`:
525 // Attributes are read from the original domNode and converted to appropriate
526 // types by looking up the Class prototype values. This is the default behavior
527 // from Dojo 1.0 to Dojo 1.5. `dojoType` support is deprecated, and will
528 // go away in Dojo 2.0.
a089699c
AD
529 //
530 // rootNode: DomNode?
531 // A default starting root node from which to start the parsing. Can be
532 // omitted, defaulting to the entire document. If omitted, the `args`
81bea17a 533 // object can be passed in this place. If the `args` object has a
a089699c
AD
534 // `rootNode` member, that is used.
535 //
81bea17a 536 // args: Object
a089699c 537 // a kwArgs object passed along to instantiate()
81bea17a 538 //
a089699c
AD
539 // * noStart: Boolean?
540 // when set will prevent the parser from calling .startup()
81bea17a 541 // when locating the nodes.
a089699c
AD
542 // * rootNode: DomNode?
543 // identical to the function's `rootNode` argument, though
81bea17a
AD
544 // allowed to be passed in via this `args object.
545 // * template: Boolean
546 // If true, ignores ContentPane's stopParser flag and parses contents inside of
547 // a ContentPane inside of a template. This allows dojoAttachPoint on widgets/nodes
548 // nested inside the ContentPane to work.
a089699c
AD
549 // * inherited: Object
550 // Hash possibly containing dir and lang settings to be applied to
551 // parsed widgets, unless there's another setting on a sub-node that overrides
81bea17a
AD
552 // * scope: String
553 // Root for attribute names to search for. If scopeName is dojo,
554 // will search for data-dojo-type (or dojoType). For backwards compatibility
555 // reasons defaults to dojo._scopeName (which is "dojo" except when
556 // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
557 // * propsThis: Object
558 // If specified, "this" referenced from data-dojo-props will refer to propsThis.
559 // Intended for use from the widgets-in-template feature of `dijit._Templated`
a089699c
AD
560 //
561 // example:
562 // Parse all widgets on a page:
563 // | dojo.parser.parse();
564 //
565 // example:
566 // Parse all classes within the node with id="foo"
81bea17a 567 // | dojo.parser.parse(dojo.byId('foo'));
a089699c
AD
568 //
569 // example:
81bea17a 570 // Parse all classes in a page, but do not call .startup() on any
a089699c
AD
571 // child
572 // | dojo.parser.parse({ noStart: true })
573 //
574 // example:
575 // Parse all classes in a node, but do not call .startup()
576 // | dojo.parser.parse(someNode, { noStart:true });
577 // | // or
81bea17a 578 // | dojo.parser.parse({ noStart:true, rootNode: someNode });
a089699c
AD
579
580 // determine the root node based on the passed arguments.
581 var root;
582 if(!args && rootNode && rootNode.rootNode){
583 args = rootNode;
584 root = args.rootNode;
585 }else{
586 root = rootNode;
587 }
81bea17a
AD
588 root = root ? dojo.byId(root) : dojo.body();
589 args = args || {};
590
591 var attrName = (args.scope || d._scopeName) + "Type", // typically "dojoType"
592 attrData = "data-" + (args.scope || d._scopeName) + "-"; // typically "data-dojo-"
a089699c 593
a089699c
AD
594 function scan(parent, list){
595 // summary:
596 // Parent is an Object representing a DOMNode, with or without a dojoType specified.
597 // Scan parent's children looking for nodes with dojoType specified, storing in list[].
598 // If parent has a dojoType, also collects <script type=dojo/*> children and stores in parent.scripts[].
599 // parent: Object
600 // Object representing the parent node, like
601 // | {
81bea17a 602 // | node: DomNode, // scan children of this node
a089699c
AD
603 // | inherited: {dir: "rtl"}, // dir/lang setting inherited from above node
604 // |
605 // | // attributes only set if node has dojoType specified
606 // | scripts: [], // empty array, put <script type=dojo/*> in here
607 // | clsInfo: { cls: dijit.form.Button, ...}
608 // | }
609 // list: DomNode[]
610 // Output array of objects (same format as parent) representing nodes to be turned into widgets
611
612 // Effective dir and lang settings on parent node, either set directly or inherited from grandparent
613 var inherited = dojo.clone(parent.inherited);
614 dojo.forEach(["dir", "lang"], function(name){
81bea17a 615 // TODO: what if this is a widget and dir/lang are declared in data-dojo-props?
a089699c
AD
616 var val = parent.node.getAttribute(name);
617 if(val){
618 inherited[name] = val;
619 }
620 });
621
622 // if parent is a widget, then search for <script type=dojo/*> tags and put them in scripts[].
81bea17a 623 var scripts = parent.clsInfo && !parent.clsInfo.cls.prototype._noScript ? parent.scripts : null;
a089699c
AD
624
625 // unless parent is a widget with the stopParser flag set, continue search for dojoType, recursively
81bea17a 626 var recurse = (!parent.clsInfo || !parent.clsInfo.cls.prototype.stopParser) || (args && args.template);
a089699c
AD
627
628 // scan parent's children looking for dojoType and <script type=dojo/*>
629 for(var child = parent.node.firstChild; child; child = child.nextSibling){
630 if(child.nodeType == 1){
81bea17a
AD
631 // FIXME: desupport dojoType in 2.0. use data-dojo-type instead
632 var type, html5 = recurse && child.getAttribute(attrData + "type");
633 if(html5){
634 type = html5;
635 }else{
636 // fallback to backward compatible mode, using dojoType. remove in 2.0
637 type = recurse && child.getAttribute(attrName);
638 }
639
640 var fastpath = html5 == type;
641
a089699c 642 if(type){
81bea17a 643 // if dojoType/data-dojo-type specified, add to output array of nodes to instantiate
a089699c
AD
644 var params = {
645 "type": type,
81bea17a
AD
646 fastpath: fastpath,
647 clsInfo: getClassInfo(type, fastpath), // note: won't find classes declared via dojo.Declaration
a089699c
AD
648 node: child,
649 scripts: [], // <script> nodes that are parent's children
650 inherited: inherited // dir & lang attributes inherited from parent
651 };
652 list.push(params);
653
654 // Recurse, collecting <script type="dojo/..."> children, and also looking for
655 // descendant nodes with dojoType specified (unless the widget has the stopParser flag),
656 scan(params, list);
657 }else if(scripts && child.nodeName.toLowerCase() == "script"){
658 // if <script type="dojo/...">, save in scripts[]
659 type = child.getAttribute("type");
81bea17a 660 if (type && /^dojo\/\w/i.test(type)) {
a089699c
AD
661 scripts.push(child);
662 }
663 }else if(recurse){
664 // Recurse, looking for grandchild nodes with dojoType specified
665 scan({
666 node: child,
667 inherited: inherited
668 }, list);
669 }
670 }
671 }
672 }
673
81bea17a
AD
674 // Ignore bogus entries in inherited hash like {dir: ""}
675 var inherited = {};
676 if(args && args.inherited){
677 for(var key in args.inherited){
678 if(args.inherited[key]){ inherited[key] = args.inherited[key]; }
679 }
680 }
681
a089699c
AD
682 // Make list of all nodes on page w/dojoType specified
683 var list = [];
684 scan({
81bea17a
AD
685 node: root,
686 inherited: inherited
a089699c
AD
687 }, list);
688
689 // go build the object instances
81bea17a
AD
690 var mixin = args && args.template ? {template: true} : null;
691 return this.instantiate(list, mixin, args); // Array
a089699c
AD
692 };
693}();
694
695//Register the parser callback. It should be the first callback
696//after the a11y test.
697
698(function(){
81bea17a 699 var parseRunner = function(){
a089699c 700 if(dojo.config.parseOnLoad){
81bea17a 701 dojo.parser.parse();
a089699c
AD
702 }
703 };
704
705 // FIXME: need to clobber cross-dependency!!
81bea17a 706 if(dojo.getObject("dijit.wai.onload") === dojo._loaders[0]){
a089699c
AD
707 dojo._loaders.splice(1, 0, parseRunner);
708 }else{
709 dojo._loaders.unshift(parseRunner);
710 }
711})();
712
713}
714
715if(!dojo._hasResource["dojo.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
716dojo._hasResource["dojo.window"] = true;
717dojo.provide("dojo.window");
718
81bea17a
AD
719dojo.getObject("window", true, dojo);
720
a089699c
AD
721dojo.window.getBox = function(){
722 // summary:
723 // Returns the dimensions and scroll position of the viewable area of a browser window
724
725 var scrollRoot = (dojo.doc.compatMode == 'BackCompat') ? dojo.body() : dojo.doc.documentElement;
726
727 // get scroll position
728 var scroll = dojo._docScroll(); // scrollRoot.scrollTop/Left should work
729 return { w: scrollRoot.clientWidth, h: scrollRoot.clientHeight, l: scroll.x, t: scroll.y };
730};
731
732dojo.window.get = function(doc){
733 // summary:
734 // Get window object associated with document doc
735
736 // In some IE versions (at least 6.0), document.parentWindow does not return a
737 // reference to the real window object (maybe a copy), so we must fix it as well
738 // We use IE specific execScript to attach the real window reference to
739 // document._parentWindow for later use
740 if(dojo.isIE && window !== document.parentWindow){
741 /*
742 In IE 6, only the variable "window" can be used to connect events (others
743 may be only copies).
744 */
745 doc.parentWindow.execScript("document._parentWindow = window;", "Javascript");
746 //to prevent memory leak, unset it after use
747 //another possibility is to add an onUnload handler which seems overkill to me (liucougar)
748 var win = doc._parentWindow;
749 doc._parentWindow = null;
750 return win; // Window
751 }
752
753 return doc.parentWindow || doc.defaultView; // Window
754};
755
756dojo.window.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
757 // summary:
758 // Scroll the passed node into view, if it is not already.
759
760 // don't rely on node.scrollIntoView working just because the function is there
761
762 try{ // catch unexpected/unrecreatable errors (#7808) since we can recover using a semi-acceptable native method
763 node = dojo.byId(node);
764 var doc = node.ownerDocument || dojo.doc,
765 body = doc.body || dojo.body(),
766 html = doc.documentElement || body.parentNode,
767 isIE = dojo.isIE, isWK = dojo.isWebKit;
768 // if an untested browser, then use the native method
769 if((!(dojo.isMoz || isIE || isWK || dojo.isOpera) || node == body || node == html) && (typeof node.scrollIntoView != "undefined")){
770 node.scrollIntoView(false); // short-circuit to native if possible
771 return;
772 }
773 var backCompat = doc.compatMode == 'BackCompat',
81bea17a
AD
774 clientAreaRoot = (isIE >= 9 && node.ownerDocument.parentWindow.frameElement)
775 ? ((html.clientHeight > 0 && html.clientWidth > 0 && (body.clientHeight == 0 || body.clientWidth == 0 || body.clientHeight > html.clientHeight || body.clientWidth > html.clientWidth)) ? html : body)
776 : (backCompat ? body : html),
a089699c
AD
777 scrollRoot = isWK ? body : clientAreaRoot,
778 rootWidth = clientAreaRoot.clientWidth,
779 rootHeight = clientAreaRoot.clientHeight,
780 rtl = !dojo._isBodyLtr(),
781 nodePos = pos || dojo.position(node),
782 el = node.parentNode,
783 isFixed = function(el){
784 return ((isIE <= 6 || (isIE && backCompat))? false : (dojo.style(el, 'position').toLowerCase() == "fixed"));
785 };
786 if(isFixed(node)){ return; } // nothing to do
787
788 while(el){
789 if(el == body){ el = scrollRoot; }
790 var elPos = dojo.position(el),
791 fixedPos = isFixed(el);
792
793 if(el == scrollRoot){
794 elPos.w = rootWidth; elPos.h = rootHeight;
795 if(scrollRoot == html && isIE && rtl){ elPos.x += scrollRoot.offsetWidth-elPos.w; } // IE workaround where scrollbar causes negative x
796 if(elPos.x < 0 || !isIE){ elPos.x = 0; } // IE can have values > 0
797 if(elPos.y < 0 || !isIE){ elPos.y = 0; }
798 }else{
799 var pb = dojo._getPadBorderExtents(el);
800 elPos.w -= pb.w; elPos.h -= pb.h; elPos.x += pb.l; elPos.y += pb.t;
a089699c
AD
801 var clientSize = el.clientWidth,
802 scrollBarSize = elPos.w - clientSize;
803 if(clientSize > 0 && scrollBarSize > 0){
804 elPos.w = clientSize;
81bea17a 805 elPos.x += (rtl && (isIE || el.clientLeft > pb.l/*Chrome*/)) ? scrollBarSize : 0;
a089699c
AD
806 }
807 clientSize = el.clientHeight;
808 scrollBarSize = elPos.h - clientSize;
809 if(clientSize > 0 && scrollBarSize > 0){
810 elPos.h = clientSize;
811 }
812 }
813 if(fixedPos){ // bounded by viewport, not parents
814 if(elPos.y < 0){
815 elPos.h += elPos.y; elPos.y = 0;
816 }
817 if(elPos.x < 0){
818 elPos.w += elPos.x; elPos.x = 0;
819 }
820 if(elPos.y + elPos.h > rootHeight){
821 elPos.h = rootHeight - elPos.y;
822 }
823 if(elPos.x + elPos.w > rootWidth){
824 elPos.w = rootWidth - elPos.x;
825 }
826 }
827 // calculate overflow in all 4 directions
828 var l = nodePos.x - elPos.x, // beyond left: < 0
829 t = nodePos.y - Math.max(elPos.y, 0), // beyond top: < 0
830 r = l + nodePos.w - elPos.w, // beyond right: > 0
831 bot = t + nodePos.h - elPos.h; // beyond bottom: > 0
832 if(r * l > 0){
833 var s = Math[l < 0? "max" : "min"](l, r);
81bea17a 834 if(rtl && ((isIE == 8 && !backCompat) || isIE >= 9)){ s = -s; }
a089699c 835 nodePos.x += el.scrollLeft;
81bea17a 836 el.scrollLeft += s;
a089699c
AD
837 nodePos.x -= el.scrollLeft;
838 }
839 if(bot * t > 0){
840 nodePos.y += el.scrollTop;
841 el.scrollTop += Math[t < 0? "max" : "min"](t, bot);
842 nodePos.y -= el.scrollTop;
843 }
844 el = (el != scrollRoot) && !fixedPos && el.parentNode;
81bea17a 845 }
a089699c
AD
846 }catch(error){
847 console.error('scrollIntoView: ' + error);
848 node.scrollIntoView(false);
849 }
850};
851
852}
853
854if(!dojo._hasResource["dijit._base.manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
855dojo._hasResource["dijit._base.manager"] = true;
856dojo.provide("dijit._base.manager");
857
81bea17a 858
a089699c
AD
859dojo.declare("dijit.WidgetSet", null, {
860 // summary:
861 // A set of widgets indexed by id. A default instance of this class is
862 // available as `dijit.registry`
863 //
864 // example:
865 // Create a small list of widgets:
866 // | var ws = new dijit.WidgetSet();
867 // | ws.add(dijit.byId("one"));
868 // | ws.add(dijit.byId("two"));
869 // | // destroy both:
870 // | ws.forEach(function(w){ w.destroy(); });
871 //
872 // example:
873 // Using dijit.registry:
874 // | dijit.registry.forEach(function(w){ /* do something */ });
875
876 constructor: function(){
877 this._hash = {};
878 this.length = 0;
879 },
880
881 add: function(/*dijit._Widget*/ widget){
882 // summary:
883 // Add a widget to this list. If a duplicate ID is detected, a error is thrown.
884 //
885 // widget: dijit._Widget
886 // Any dijit._Widget subclass.
887 if(this._hash[widget.id]){
888 throw new Error("Tried to register widget with id==" + widget.id + " but that id is already registered");
889 }
890 this._hash[widget.id] = widget;
891 this.length++;
892 },
893
894 remove: function(/*String*/ id){
895 // summary:
896 // Remove a widget from this WidgetSet. Does not destroy the widget; simply
897 // removes the reference.
898 if(this._hash[id]){
899 delete this._hash[id];
900 this.length--;
901 }
902 },
903
904 forEach: function(/*Function*/ func, /* Object? */thisObj){
905 // summary:
906 // Call specified function for each widget in this set.
907 //
908 // func:
909 // A callback function to run for each item. Is passed the widget, the index
910 // in the iteration, and the full hash, similar to `dojo.forEach`.
911 //
912 // thisObj:
913 // An optional scope parameter
914 //
915 // example:
916 // Using the default `dijit.registry` instance:
917 // | dijit.registry.forEach(function(widget){
918 // | console.log(widget.declaredClass);
919 // | });
920 //
921 // returns:
922 // Returns self, in order to allow for further chaining.
923
924 thisObj = thisObj || dojo.global;
925 var i = 0, id;
926 for(id in this._hash){
927 func.call(thisObj, this._hash[id], i++, this._hash);
928 }
929 return this; // dijit.WidgetSet
930 },
931
932 filter: function(/*Function*/ filter, /* Object? */thisObj){
933 // summary:
934 // Filter down this WidgetSet to a smaller new WidgetSet
935 // Works the same as `dojo.filter` and `dojo.NodeList.filter`
936 //
937 // filter:
938 // Callback function to test truthiness. Is passed the widget
939 // reference and the pseudo-index in the object.
940 //
941 // thisObj: Object?
942 // Option scope to use for the filter function.
943 //
944 // example:
945 // Arbitrary: select the odd widgets in this list
946 // | dijit.registry.filter(function(w, i){
947 // | return i % 2 == 0;
948 // | }).forEach(function(w){ /* odd ones */ });
949
950 thisObj = thisObj || dojo.global;
951 var res = new dijit.WidgetSet(), i = 0, id;
952 for(id in this._hash){
953 var w = this._hash[id];
954 if(filter.call(thisObj, w, i++, this._hash)){
955 res.add(w);
956 }
957 }
958 return res; // dijit.WidgetSet
959 },
960
961 byId: function(/*String*/ id){
962 // summary:
963 // Find a widget in this list by it's id.
964 // example:
965 // Test if an id is in a particular WidgetSet
966 // | var ws = new dijit.WidgetSet();
967 // | ws.add(dijit.byId("bar"));
968 // | var t = ws.byId("bar") // returns a widget
969 // | var x = ws.byId("foo"); // returns undefined
970
971 return this._hash[id]; // dijit._Widget
972 },
973
974 byClass: function(/*String*/ cls){
975 // summary:
976 // Reduce this widgetset to a new WidgetSet of a particular `declaredClass`
977 //
978 // cls: String
979 // The Class to scan for. Full dot-notated string.
980 //
981 // example:
982 // Find all `dijit.TitlePane`s in a page:
983 // | dijit.registry.byClass("dijit.TitlePane").forEach(function(tp){ tp.close(); });
984
985 var res = new dijit.WidgetSet(), id, widget;
986 for(id in this._hash){
987 widget = this._hash[id];
988 if(widget.declaredClass == cls){
989 res.add(widget);
990 }
991 }
992 return res; // dijit.WidgetSet
993},
994
995 toArray: function(){
996 // summary:
997 // Convert this WidgetSet into a true Array
998 //
999 // example:
1000 // Work with the widget .domNodes in a real Array
1001 // | dojo.map(dijit.registry.toArray(), function(w){ return w.domNode; });
1002
1003 var ar = [];
1004 for(var id in this._hash){
1005 ar.push(this._hash[id]);
1006 }
1007 return ar; // dijit._Widget[]
1008},
1009
1010 map: function(/* Function */func, /* Object? */thisObj){
1011 // summary:
1012 // Create a new Array from this WidgetSet, following the same rules as `dojo.map`
1013 // example:
1014 // | var nodes = dijit.registry.map(function(w){ return w.domNode; });
1015 //
1016 // returns:
1017 // A new array of the returned values.
1018 return dojo.map(this.toArray(), func, thisObj); // Array
1019 },
1020
1021 every: function(func, thisObj){
1022 // summary:
1023 // A synthetic clone of `dojo.every` acting explicitly on this WidgetSet
1024 //
1025 // func: Function
1026 // A callback function run for every widget in this list. Exits loop
1027 // when the first false return is encountered.
1028 //
1029 // thisObj: Object?
1030 // Optional scope parameter to use for the callback
1031
1032 thisObj = thisObj || dojo.global;
1033 var x = 0, i;
1034 for(i in this._hash){
1035 if(!func.call(thisObj, this._hash[i], x++, this._hash)){
1036 return false; // Boolean
1037 }
1038 }
1039 return true; // Boolean
1040 },
1041
1042 some: function(func, thisObj){
1043 // summary:
1044 // A synthetic clone of `dojo.some` acting explictly on this WidgetSet
1045 //
1046 // func: Function
1047 // A callback function run for every widget in this list. Exits loop
1048 // when the first true return is encountered.
1049 //
1050 // thisObj: Object?
1051 // Optional scope parameter to use for the callback
1052
1053 thisObj = thisObj || dojo.global;
1054 var x = 0, i;
1055 for(i in this._hash){
1056 if(func.call(thisObj, this._hash[i], x++, this._hash)){
1057 return true; // Boolean
1058 }
1059 }
1060 return false; // Boolean
1061 }
1062
1063});
1064
1065(function(){
1066
1067 /*=====
1068 dijit.registry = {
1069 // summary:
1070 // A list of widgets on a page.
1071 // description:
1072 // Is an instance of `dijit.WidgetSet`
1073 };
1074 =====*/
1075 dijit.registry = new dijit.WidgetSet();
1076
1077 var hash = dijit.registry._hash,
1078 attr = dojo.attr,
1079 hasAttr = dojo.hasAttr,
1080 style = dojo.style;
1081
1082 dijit.byId = function(/*String|dijit._Widget*/ id){
1083 // summary:
1084 // Returns a widget by it's id, or if passed a widget, no-op (like dojo.byId())
1085 return typeof id == "string" ? hash[id] : id; // dijit._Widget
1086 };
1087
1088 var _widgetTypeCtr = {};
1089 dijit.getUniqueId = function(/*String*/widgetType){
1090 // summary:
1091 // Generates a unique id for a given widgetType
1092
1093 var id;
1094 do{
1095 id = widgetType + "_" +
1096 (widgetType in _widgetTypeCtr ?
1097 ++_widgetTypeCtr[widgetType] : _widgetTypeCtr[widgetType] = 0);
1098 }while(hash[id]);
1099 return dijit._scopeName == "dijit" ? id : dijit._scopeName + "_" + id; // String
1100 };
1101
1102 dijit.findWidgets = function(/*DomNode*/ root){
1103 // summary:
1104 // Search subtree under root returning widgets found.
1105 // Doesn't search for nested widgets (ie, widgets inside other widgets).
1106
1107 var outAry = [];
1108
1109 function getChildrenHelper(root){
1110 for(var node = root.firstChild; node; node = node.nextSibling){
1111 if(node.nodeType == 1){
1112 var widgetId = node.getAttribute("widgetId");
1113 if(widgetId){
81bea17a
AD
1114 var widget = hash[widgetId];
1115 if(widget){ // may be null on page w/multiple dojo's loaded
1116 outAry.push(widget);
1117 }
a089699c
AD
1118 }else{
1119 getChildrenHelper(node);
1120 }
1121 }
1122 }
1123 }
1124
1125 getChildrenHelper(root);
1126 return outAry;
1127 };
1128
1129 dijit._destroyAll = function(){
1130 // summary:
1131 // Code to destroy all widgets and do other cleanup on page unload
1132
1133 // Clean up focus manager lingering references to widgets and nodes
1134 dijit._curFocus = null;
1135 dijit._prevFocus = null;
1136 dijit._activeStack = [];
1137
1138 // Destroy all the widgets, top down
1139 dojo.forEach(dijit.findWidgets(dojo.body()), function(widget){
1140 // Avoid double destroy of widgets like Menu that are attached to <body>
1141 // even though they are logically children of other widgets.
1142 if(!widget._destroyed){
1143 if(widget.destroyRecursive){
1144 widget.destroyRecursive();
1145 }else if(widget.destroy){
1146 widget.destroy();
1147 }
1148 }
1149 });
1150 };
1151
1152 if(dojo.isIE){
1153 // Only run _destroyAll() for IE because we think it's only necessary in that case,
1154 // and because it causes problems on FF. See bug #3531 for details.
1155 dojo.addOnWindowUnload(function(){
1156 dijit._destroyAll();
1157 });
1158 }
1159
1160 dijit.byNode = function(/*DOMNode*/ node){
1161 // summary:
1162 // Returns the widget corresponding to the given DOMNode
1163 return hash[node.getAttribute("widgetId")]; // dijit._Widget
1164 };
1165
1166 dijit.getEnclosingWidget = function(/*DOMNode*/ node){
1167 // summary:
1168 // Returns the widget whose DOM tree contains the specified DOMNode, or null if
1169 // the node is not contained within the DOM tree of any widget
1170 while(node){
1171 var id = node.getAttribute && node.getAttribute("widgetId");
1172 if(id){
1173 return hash[id];
1174 }
1175 node = node.parentNode;
1176 }
1177 return null;
1178 };
1179
1180 var shown = (dijit._isElementShown = function(/*Element*/ elem){
1181 var s = style(elem);
1182 return (s.visibility != "hidden")
1183 && (s.visibility != "collapsed")
1184 && (s.display != "none")
1185 && (attr(elem, "type") != "hidden");
1186 });
1187
1188 dijit.hasDefaultTabStop = function(/*Element*/ elem){
1189 // summary:
1190 // Tests if element is tab-navigable even without an explicit tabIndex setting
1191
1192 // No explicit tabIndex setting, need to investigate node type
1193 switch(elem.nodeName.toLowerCase()){
1194 case "a":
1195 // An <a> w/out a tabindex is only navigable if it has an href
1196 return hasAttr(elem, "href");
1197 case "area":
1198 case "button":
1199 case "input":
1200 case "object":
1201 case "select":
1202 case "textarea":
1203 // These are navigable by default
1204 return true;
1205 case "iframe":
1206 // If it's an editor <iframe> then it's tab navigable.
81bea17a
AD
1207 var body;
1208 try{
1209 // non-IE
1210 var contentDocument = elem.contentDocument;
1211 if("designMode" in contentDocument && contentDocument.designMode == "on"){
1212 return true;
a089699c 1213 }
81bea17a
AD
1214 body = contentDocument.body;
1215 }catch(e1){
a089699c
AD
1216 // contentWindow.document isn't accessible within IE7/8
1217 // if the iframe.src points to a foreign url and this
1218 // page contains an element, that could get focus
1219 try{
81bea17a
AD
1220 body = elem.contentWindow.document.body;
1221 }catch(e2){
a089699c
AD
1222 return false;
1223 }
1224 }
81bea17a 1225 return body.contentEditable == 'true' || (body.firstChild && body.firstChild.contentEditable == 'true');
a089699c
AD
1226 default:
1227 return elem.contentEditable == 'true';
1228 }
1229 };
1230
1231 var isTabNavigable = (dijit.isTabNavigable = function(/*Element*/ elem){
1232 // summary:
1233 // Tests if an element is tab-navigable
1234
1235 // TODO: convert (and rename method) to return effective tabIndex; will save time in _getTabNavigable()
1236 if(attr(elem, "disabled")){
1237 return false;
1238 }else if(hasAttr(elem, "tabIndex")){
1239 // Explicit tab index setting
1240 return attr(elem, "tabIndex") >= 0; // boolean
1241 }else{
1242 // No explicit tabIndex setting, so depends on node type
1243 return dijit.hasDefaultTabStop(elem);
1244 }
1245 });
1246
1247 dijit._getTabNavigable = function(/*DOMNode*/ root){
1248 // summary:
1249 // Finds descendants of the specified root node.
1250 //
1251 // description:
1252 // Finds the following descendants of the specified root node:
1253 // * the first tab-navigable element in document order
1254 // without a tabIndex or with tabIndex="0"
1255 // * the last tab-navigable element in document order
1256 // without a tabIndex or with tabIndex="0"
1257 // * the first element in document order with the lowest
1258 // positive tabIndex value
1259 // * the last element in document order with the highest
1260 // positive tabIndex value
81bea17a
AD
1261 var first, last, lowest, lowestTabindex, highest, highestTabindex, radioSelected = {};
1262 function radioName(node) {
1263 // If this element is part of a radio button group, return the name for that group.
1264 return node && node.tagName.toLowerCase() == "input" &&
1265 node.type && node.type.toLowerCase() == "radio" &&
1266 node.name && node.name.toLowerCase();
1267 }
a089699c
AD
1268 var walkTree = function(/*DOMNode*/parent){
1269 dojo.query("> *", parent).forEach(function(child){
1270 // Skip hidden elements, and also non-HTML elements (those in custom namespaces) in IE,
1271 // since show() invokes getAttribute("type"), which crash on VML nodes in IE.
1272 if((dojo.isIE && child.scopeName!=="HTML") || !shown(child)){
1273 return;
1274 }
1275
1276 if(isTabNavigable(child)){
1277 var tabindex = attr(child, "tabIndex");
1278 if(!hasAttr(child, "tabIndex") || tabindex == 0){
1279 if(!first){ first = child; }
1280 last = child;
1281 }else if(tabindex > 0){
1282 if(!lowest || tabindex < lowestTabindex){
1283 lowestTabindex = tabindex;
1284 lowest = child;
1285 }
1286 if(!highest || tabindex >= highestTabindex){
1287 highestTabindex = tabindex;
1288 highest = child;
1289 }
1290 }
81bea17a
AD
1291 var rn = radioName(child);
1292 if(dojo.attr(child, "checked") && rn) {
1293 radioSelected[rn] = child;
1294 }
a089699c
AD
1295 }
1296 if(child.nodeName.toUpperCase() != 'SELECT'){
1297 walkTree(child);
1298 }
1299 });
1300 };
1301 if(shown(root)){ walkTree(root) }
81bea17a
AD
1302 function rs(node) {
1303 // substitute checked radio button for unchecked one, if there is a checked one with the same name.
1304 return radioSelected[radioName(node)] || node;
1305 }
1306 return { first: rs(first), last: rs(last), lowest: rs(lowest), highest: rs(highest) };
a089699c
AD
1307 }
1308 dijit.getFirstInTabbingOrder = function(/*String|DOMNode*/ root){
1309 // summary:
1310 // Finds the descendant of the specified root node
1311 // that is first in the tabbing order
1312 var elems = dijit._getTabNavigable(dojo.byId(root));
1313 return elems.lowest ? elems.lowest : elems.first; // DomNode
1314 };
1315
1316 dijit.getLastInTabbingOrder = function(/*String|DOMNode*/ root){
1317 // summary:
1318 // Finds the descendant of the specified root node
1319 // that is last in the tabbing order
1320 var elems = dijit._getTabNavigable(dojo.byId(root));
1321 return elems.last ? elems.last : elems.highest; // DomNode
1322 };
1323
1324 /*=====
1325 dojo.mixin(dijit, {
1326 // defaultDuration: Integer
1327 // The default animation speed (in ms) to use for all Dijit
1328 // transitional animations, unless otherwise specified
1329 // on a per-instance basis. Defaults to 200, overrided by
1330 // `djConfig.defaultDuration`
1331 defaultDuration: 200
1332 });
1333 =====*/
1334
1335 dijit.defaultDuration = dojo.config["defaultDuration"] || 200;
1336
1337})();
1338
1339}
1340
1341if(!dojo._hasResource["dijit._base.focus"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1342dojo._hasResource["dijit._base.focus"] = true;
1343dojo.provide("dijit._base.focus");
1344
1345
81bea17a 1346
a089699c
AD
1347
1348// summary:
1349// These functions are used to query or set the focus and selection.
1350//
1351// Also, they trace when widgets become activated/deactivated,
1352// so that the widget can fire _onFocus/_onBlur events.
1353// "Active" here means something similar to "focused", but
1354// "focus" isn't quite the right word because we keep track of
1355// a whole stack of "active" widgets. Example: ComboButton --> Menu -->
1356// MenuItem. The onBlur event for ComboButton doesn't fire due to focusing
1357// on the Menu or a MenuItem, since they are considered part of the
1358// ComboButton widget. It only happens when focus is shifted
1359// somewhere completely different.
1360
1361dojo.mixin(dijit, {
1362 // _curFocus: DomNode
1363 // Currently focused item on screen
1364 _curFocus: null,
1365
1366 // _prevFocus: DomNode
1367 // Previously focused item on screen
1368 _prevFocus: null,
1369
1370 isCollapsed: function(){
1371 // summary:
1372 // Returns true if there is no text selected
1373 return dijit.getBookmark().isCollapsed;
1374 },
1375
1376 getBookmark: function(){
1377 // summary:
1378 // Retrieves a bookmark that can be used with moveToBookmark to return to the same range
1379 var bm, rg, tg, sel = dojo.doc.selection, cf = dijit._curFocus;
1380
1381 if(dojo.global.getSelection){
1382 //W3C Range API for selections.
1383 sel = dojo.global.getSelection();
1384 if(sel){
1385 if(sel.isCollapsed){
1386 tg = cf? cf.tagName : "";
1387 if(tg){
1388 //Create a fake rangelike item to restore selections.
1389 tg = tg.toLowerCase();
1390 if(tg == "textarea" ||
1391 (tg == "input" && (!cf.type || cf.type.toLowerCase() == "text"))){
1392 sel = {
1393 start: cf.selectionStart,
1394 end: cf.selectionEnd,
1395 node: cf,
1396 pRange: true
1397 };
1398 return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object.
1399 }
1400 }
1401 bm = {isCollapsed:true};
81bea17a
AD
1402 if(sel.rangeCount){
1403 bm.mark = sel.getRangeAt(0).cloneRange();
1404 }
a089699c
AD
1405 }else{
1406 rg = sel.getRangeAt(0);
1407 bm = {isCollapsed: false, mark: rg.cloneRange()};
1408 }
1409 }
1410 }else if(sel){
1411 // If the current focus was a input of some sort and no selection, don't bother saving
1412 // a native bookmark. This is because it causes issues with dialog/page selection restore.
1413 // So, we need to create psuedo bookmarks to work with.
1414 tg = cf ? cf.tagName : "";
1415 tg = tg.toLowerCase();
1416 if(cf && tg && (tg == "button" || tg == "textarea" || tg == "input")){
1417 if(sel.type && sel.type.toLowerCase() == "none"){
1418 return {
1419 isCollapsed: true,
1420 mark: null
1421 }
1422 }else{
1423 rg = sel.createRange();
1424 return {
1425 isCollapsed: rg.text && rg.text.length?false:true,
1426 mark: {
1427 range: rg,
1428 pRange: true
1429 }
1430 };
1431 }
1432 }
1433 bm = {};
1434
1435 //'IE' way for selections.
1436 try{
1437 // createRange() throws exception when dojo in iframe
1438 //and nothing selected, see #9632
1439 rg = sel.createRange();
1440 bm.isCollapsed = !(sel.type == 'Text' ? rg.htmlText.length : rg.length);
1441 }catch(e){
1442 bm.isCollapsed = true;
1443 return bm;
1444 }
1445 if(sel.type.toUpperCase() == 'CONTROL'){
1446 if(rg.length){
1447 bm.mark=[];
1448 var i=0,len=rg.length;
1449 while(i<len){
1450 bm.mark.push(rg.item(i++));
1451 }
1452 }else{
1453 bm.isCollapsed = true;
1454 bm.mark = null;
1455 }
1456 }else{
1457 bm.mark = rg.getBookmark();
1458 }
1459 }else{
1460 console.warn("No idea how to store the current selection for this browser!");
1461 }
1462 return bm; // Object
1463 },
1464
1465 moveToBookmark: function(/*Object*/bookmark){
1466 // summary:
1467 // Moves current selection to a bookmark
1468 // bookmark:
1469 // This should be a returned object from dijit.getBookmark()
1470
1471 var _doc = dojo.doc,
1472 mark = bookmark.mark;
1473 if(mark){
1474 if(dojo.global.getSelection){
1475 //W3C Rangi API (FF, WebKit, Opera, etc)
1476 var sel = dojo.global.getSelection();
1477 if(sel && sel.removeAllRanges){
1478 if(mark.pRange){
1479 var r = mark;
1480 var n = r.node;
1481 n.selectionStart = r.start;
1482 n.selectionEnd = r.end;
1483 }else{
1484 sel.removeAllRanges();
1485 sel.addRange(mark);
1486 }
1487 }else{
1488 console.warn("No idea how to restore selection for this browser!");
1489 }
1490 }else if(_doc.selection && mark){
1491 //'IE' way.
1492 var rg;
1493 if(mark.pRange){
1494 rg = mark.range;
1495 }else if(dojo.isArray(mark)){
1496 rg = _doc.body.createControlRange();
1497 //rg.addElement does not have call/apply method, so can not call it directly
1498 //rg is not available in "range.addElement(item)", so can't use that either
1499 dojo.forEach(mark, function(n){
1500 rg.addElement(n);
1501 });
1502 }else{
1503 rg = _doc.body.createTextRange();
1504 rg.moveToBookmark(mark);
1505 }
1506 rg.select();
1507 }
1508 }
1509 },
1510
1511 getFocus: function(/*Widget?*/ menu, /*Window?*/ openedForWindow){
1512 // summary:
1513 // Called as getFocus(), this returns an Object showing the current focus
1514 // and selected text.
1515 //
1516 // Called as getFocus(widget), where widget is a (widget representing) a button
1517 // that was just pressed, it returns where focus was before that button
1518 // was pressed. (Pressing the button may have either shifted focus to the button,
1519 // or removed focus altogether.) In this case the selected text is not returned,
1520 // since it can't be accurately determined.
1521 //
1522 // menu: dijit._Widget or {domNode: DomNode} structure
1523 // The button that was just pressed. If focus has disappeared or moved
1524 // to this button, returns the previous focus. In this case the bookmark
1525 // information is already lost, and null is returned.
1526 //
1527 // openedForWindow:
1528 // iframe in which menu was opened
1529 //
1530 // returns:
1531 // A handle to restore focus/selection, to be passed to `dijit.focus`
1532 var node = !dijit._curFocus || (menu && dojo.isDescendant(dijit._curFocus, menu.domNode)) ? dijit._prevFocus : dijit._curFocus;
1533 return {
1534 node: node,
1535 bookmark: (node == dijit._curFocus) && dojo.withGlobal(openedForWindow || dojo.global, dijit.getBookmark),
1536 openedForWindow: openedForWindow
1537 }; // Object
1538 },
1539
1540 focus: function(/*Object || DomNode */ handle){
1541 // summary:
1542 // Sets the focused node and the selection according to argument.
1543 // To set focus to an iframe's content, pass in the iframe itself.
1544 // handle:
1545 // object returned by get(), or a DomNode
1546
1547 if(!handle){ return; }
1548
1549 var node = "node" in handle ? handle.node : handle, // because handle is either DomNode or a composite object
1550 bookmark = handle.bookmark,
1551 openedForWindow = handle.openedForWindow,
1552 collapsed = bookmark ? bookmark.isCollapsed : false;
1553
1554 // Set the focus
1555 // Note that for iframe's we need to use the <iframe> to follow the parentNode chain,
1556 // but we need to set focus to iframe.contentWindow
1557 if(node){
1558 var focusNode = (node.tagName.toLowerCase() == "iframe") ? node.contentWindow : node;
1559 if(focusNode && focusNode.focus){
1560 try{
1561 // Gecko throws sometimes if setting focus is impossible,
1562 // node not displayed or something like that
1563 focusNode.focus();
1564 }catch(e){/*quiet*/}
1565 }
1566 dijit._onFocusNode(node);
1567 }
1568
1569 // set the selection
1570 // do not need to restore if current selection is not empty
1571 // (use keyboard to select a menu item) or if previous selection was collapsed
1572 // as it may cause focus shift (Esp in IE).
1573 if(bookmark && dojo.withGlobal(openedForWindow || dojo.global, dijit.isCollapsed) && !collapsed){
1574 if(openedForWindow){
1575 openedForWindow.focus();
1576 }
1577 try{
1578 dojo.withGlobal(openedForWindow || dojo.global, dijit.moveToBookmark, null, [bookmark]);
1579 }catch(e2){
1580 /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */
1581 }
1582 }
1583 },
1584
1585 // _activeStack: dijit._Widget[]
1586 // List of currently active widgets (focused widget and it's ancestors)
1587 _activeStack: [],
1588
1589 registerIframe: function(/*DomNode*/ iframe){
1590 // summary:
1591 // Registers listeners on the specified iframe so that any click
1592 // or focus event on that iframe (or anything in it) is reported
1593 // as a focus/click event on the <iframe> itself.
1594 // description:
1595 // Currently only used by editor.
1596 // returns:
1597 // Handle to pass to unregisterIframe()
1598 return dijit.registerWin(iframe.contentWindow, iframe);
1599 },
1600
1601 unregisterIframe: function(/*Object*/ handle){
1602 // summary:
1603 // Unregisters listeners on the specified iframe created by registerIframe.
1604 // After calling be sure to delete or null out the handle itself.
1605 // handle:
1606 // Handle returned by registerIframe()
1607
1608 dijit.unregisterWin(handle);
1609 },
1610
1611 registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){
1612 // summary:
1613 // Registers listeners on the specified window (either the main
1614 // window or an iframe's window) to detect when the user has clicked somewhere
1615 // or focused somewhere.
1616 // description:
1617 // Users should call registerIframe() instead of this method.
1618 // targetWindow:
1619 // If specified this is the window associated with the iframe,
1620 // i.e. iframe.contentWindow.
1621 // effectiveNode:
1622 // If specified, report any focus events inside targetWindow as
1623 // an event on effectiveNode, rather than on evt.target.
1624 // returns:
1625 // Handle to pass to unregisterWin()
1626
1627 // TODO: make this function private in 2.0; Editor/users should call registerIframe(),
1628
1629 var mousedownListener = function(evt){
1630 dijit._justMouseDowned = true;
1631 setTimeout(function(){ dijit._justMouseDowned = false; }, 0);
1632
1633 // workaround weird IE bug where the click is on an orphaned node
1634 // (first time clicking a Select/DropDownButton inside a TooltipDialog)
1635 if(dojo.isIE && evt && evt.srcElement && evt.srcElement.parentNode == null){
1636 return;
1637 }
1638
1639 dijit._onTouchNode(effectiveNode || evt.target || evt.srcElement, "mouse");
1640 };
1641 //dojo.connect(targetWindow, "onscroll", ???);
1642
1643 // Listen for blur and focus events on targetWindow's document.
1644 // IIRC, I'm using attachEvent() rather than dojo.connect() because focus/blur events don't bubble
1645 // through dojo.connect(), and also maybe to catch the focus events early, before onfocus handlers
1646 // fire.
1647 // Connect to <html> (rather than document) on IE to avoid memory leaks, but document on other browsers because
1648 // (at least for FF) the focus event doesn't fire on <html> or <body>.
1649 var doc = dojo.isIE ? targetWindow.document.documentElement : targetWindow.document;
1650 if(doc){
1651 if(dojo.isIE){
81bea17a 1652 targetWindow.document.body.attachEvent('onmousedown', mousedownListener);
a089699c
AD
1653 var activateListener = function(evt){
1654 // IE reports that nodes like <body> have gotten focus, even though they have tabIndex=-1,
1655 // Should consider those more like a mouse-click than a focus....
1656 if(evt.srcElement.tagName.toLowerCase() != "#document" &&
1657 dijit.isTabNavigable(evt.srcElement)){
1658 dijit._onFocusNode(effectiveNode || evt.srcElement);
1659 }else{
1660 dijit._onTouchNode(effectiveNode || evt.srcElement);
1661 }
1662 };
1663 doc.attachEvent('onactivate', activateListener);
1664 var deactivateListener = function(evt){
1665 dijit._onBlurNode(effectiveNode || evt.srcElement);
1666 };
1667 doc.attachEvent('ondeactivate', deactivateListener);
1668
1669 return function(){
81bea17a 1670 targetWindow.document.detachEvent('onmousedown', mousedownListener);
a089699c
AD
1671 doc.detachEvent('onactivate', activateListener);
1672 doc.detachEvent('ondeactivate', deactivateListener);
1673 doc = null; // prevent memory leak (apparent circular reference via closure)
1674 };
1675 }else{
81bea17a 1676 doc.body.addEventListener('mousedown', mousedownListener, true);
a089699c
AD
1677 var focusListener = function(evt){
1678 dijit._onFocusNode(effectiveNode || evt.target);
1679 };
1680 doc.addEventListener('focus', focusListener, true);
1681 var blurListener = function(evt){
1682 dijit._onBlurNode(effectiveNode || evt.target);
1683 };
1684 doc.addEventListener('blur', blurListener, true);
1685
1686 return function(){
81bea17a 1687 doc.body.removeEventListener('mousedown', mousedownListener, true);
a089699c
AD
1688 doc.removeEventListener('focus', focusListener, true);
1689 doc.removeEventListener('blur', blurListener, true);
1690 doc = null; // prevent memory leak (apparent circular reference via closure)
1691 };
1692 }
1693 }
1694 },
1695
1696 unregisterWin: function(/*Handle*/ handle){
1697 // summary:
1698 // Unregisters listeners on the specified window (either the main
1699 // window or an iframe's window) according to handle returned from registerWin().
1700 // After calling be sure to delete or null out the handle itself.
1701
1702 // Currently our handle is actually a function
1703 handle && handle();
1704 },
1705
1706 _onBlurNode: function(/*DomNode*/ node){
1707 // summary:
1708 // Called when focus leaves a node.
1709 // Usually ignored, _unless_ it *isn't* follwed by touching another node,
1710 // which indicates that we tabbed off the last field on the page,
1711 // in which case every widget is marked inactive
1712 dijit._prevFocus = dijit._curFocus;
1713 dijit._curFocus = null;
1714
1715 if(dijit._justMouseDowned){
1716 // the mouse down caused a new widget to be marked as active; this blur event
1717 // is coming late, so ignore it.
1718 return;
1719 }
1720
1721 // if the blur event isn't followed by a focus event then mark all widgets as inactive.
1722 if(dijit._clearActiveWidgetsTimer){
1723 clearTimeout(dijit._clearActiveWidgetsTimer);
1724 }
1725 dijit._clearActiveWidgetsTimer = setTimeout(function(){
1726 delete dijit._clearActiveWidgetsTimer;
1727 dijit._setStack([]);
1728 dijit._prevFocus = null;
1729 }, 100);
1730 },
1731
1732 _onTouchNode: function(/*DomNode*/ node, /*String*/ by){
1733 // summary:
1734 // Callback when node is focused or mouse-downed
1735 // node:
1736 // The node that was touched.
1737 // by:
1738 // "mouse" if the focus/touch was caused by a mouse down event
1739
1740 // ignore the recent blurNode event
1741 if(dijit._clearActiveWidgetsTimer){
1742 clearTimeout(dijit._clearActiveWidgetsTimer);
1743 delete dijit._clearActiveWidgetsTimer;
1744 }
1745
1746 // compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem)
1747 var newStack=[];
1748 try{
1749 while(node){
1750 var popupParent = dojo.attr(node, "dijitPopupParent");
1751 if(popupParent){
1752 node=dijit.byId(popupParent).domNode;
1753 }else if(node.tagName && node.tagName.toLowerCase() == "body"){
1754 // is this the root of the document or just the root of an iframe?
1755 if(node === dojo.body()){
1756 // node is the root of the main document
1757 break;
1758 }
1759 // otherwise, find the iframe this node refers to (can't access it via parentNode,
1760 // need to do this trick instead). window.frameElement is supported in IE/FF/Webkit
1761 node=dojo.window.get(node.ownerDocument).frameElement;
1762 }else{
1763 // if this node is the root node of a widget, then add widget id to stack,
1764 // except ignore clicks on disabled widgets (actually focusing a disabled widget still works,
1765 // to support MenuItem)
1766 var id = node.getAttribute && node.getAttribute("widgetId"),
1767 widget = id && dijit.byId(id);
1768 if(widget && !(by == "mouse" && widget.get("disabled"))){
1769 newStack.unshift(id);
1770 }
1771 node=node.parentNode;
1772 }
1773 }
1774 }catch(e){ /* squelch */ }
1775
1776 dijit._setStack(newStack, by);
1777 },
1778
1779 _onFocusNode: function(/*DomNode*/ node){
1780 // summary:
1781 // Callback when node is focused
1782
1783 if(!node){
1784 return;
1785 }
1786
1787 if(node.nodeType == 9){
1788 // Ignore focus events on the document itself. This is here so that
1789 // (for example) clicking the up/down arrows of a spinner
1790 // (which don't get focus) won't cause that widget to blur. (FF issue)
1791 return;
1792 }
1793
1794 dijit._onTouchNode(node);
1795
1796 if(node == dijit._curFocus){ return; }
1797 if(dijit._curFocus){
1798 dijit._prevFocus = dijit._curFocus;
1799 }
1800 dijit._curFocus = node;
1801 dojo.publish("focusNode", [node]);
1802 },
1803
1804 _setStack: function(/*String[]*/ newStack, /*String*/ by){
1805 // summary:
1806 // The stack of active widgets has changed. Send out appropriate events and records new stack.
1807 // newStack:
1808 // array of widget id's, starting from the top (outermost) widget
1809 // by:
1810 // "mouse" if the focus/touch was caused by a mouse down event
1811
1812 var oldStack = dijit._activeStack;
1813 dijit._activeStack = newStack;
1814
1815 // compare old stack to new stack to see how many elements they have in common
1816 for(var nCommon=0; nCommon<Math.min(oldStack.length, newStack.length); nCommon++){
1817 if(oldStack[nCommon] != newStack[nCommon]){
1818 break;
1819 }
1820 }
1821
1822 var widget;
1823 // for all elements that have gone out of focus, send blur event
1824 for(var i=oldStack.length-1; i>=nCommon; i--){
1825 widget = dijit.byId(oldStack[i]);
1826 if(widget){
1827 widget._focused = false;
81bea17a 1828 widget.set("focused", false);
a089699c
AD
1829 widget._hasBeenBlurred = true;
1830 if(widget._onBlur){
1831 widget._onBlur(by);
1832 }
1833 dojo.publish("widgetBlur", [widget, by]);
1834 }
1835 }
1836
1837 // for all element that have come into focus, send focus event
1838 for(i=nCommon; i<newStack.length; i++){
1839 widget = dijit.byId(newStack[i]);
1840 if(widget){
1841 widget._focused = true;
81bea17a 1842 widget.set("focused", true);
a089699c
AD
1843 if(widget._onFocus){
1844 widget._onFocus(by);
1845 }
1846 dojo.publish("widgetFocus", [widget, by]);
1847 }
1848 }
1849 }
1850});
1851
1852// register top window and all the iframes it contains
1853dojo.addOnLoad(function(){
1854 var handle = dijit.registerWin(window);
1855 if(dojo.isIE){
1856 dojo.addOnWindowUnload(function(){
1857 dijit.unregisterWin(handle);
1858 handle = null;
1859 })
1860 }
1861});
1862
1863}
1864
1865if(!dojo._hasResource["dojo.AdapterRegistry"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1866dojo._hasResource["dojo.AdapterRegistry"] = true;
1867dojo.provide("dojo.AdapterRegistry");
1868
81bea17a 1869
a089699c
AD
1870dojo.AdapterRegistry = function(/*Boolean?*/ returnWrappers){
1871 // summary:
1872 // A registry to make contextual calling/searching easier.
1873 // description:
1874 // Objects of this class keep list of arrays in the form [name, check,
1875 // wrap, directReturn] that are used to determine what the contextual
1876 // result of a set of checked arguments is. All check/wrap functions
1877 // in this registry should be of the same arity.
1878 // example:
1879 // | // create a new registry
1880 // | var reg = new dojo.AdapterRegistry();
1881 // | reg.register("handleString",
1882 // | dojo.isString,
1883 // | function(str){
1884 // | // do something with the string here
1885 // | }
1886 // | );
1887 // | reg.register("handleArr",
1888 // | dojo.isArray,
1889 // | function(arr){
1890 // | // do something with the array here
1891 // | }
1892 // | );
1893 // |
1894 // | // now we can pass reg.match() *either* an array or a string and
1895 // | // the value we pass will get handled by the right function
1896 // | reg.match("someValue"); // will call the first function
1897 // | reg.match(["someValue"]); // will call the second
1898
1899 this.pairs = [];
1900 this.returnWrappers = returnWrappers || false; // Boolean
81bea17a 1901};
a089699c
AD
1902
1903dojo.extend(dojo.AdapterRegistry, {
1904 register: function(/*String*/ name, /*Function*/ check, /*Function*/ wrap, /*Boolean?*/ directReturn, /*Boolean?*/ override){
81bea17a 1905 // summary:
a089699c
AD
1906 // register a check function to determine if the wrap function or
1907 // object gets selected
1908 // name:
1909 // a way to identify this matcher.
1910 // check:
1911 // a function that arguments are passed to from the adapter's
1912 // match() function. The check function should return true if the
1913 // given arguments are appropriate for the wrap function.
1914 // directReturn:
1915 // If directReturn is true, the value passed in for wrap will be
1916 // returned instead of being called. Alternately, the
1917 // AdapterRegistry can be set globally to "return not call" using
1918 // the returnWrappers property. Either way, this behavior allows
1919 // the registry to act as a "search" function instead of a
1920 // function interception library.
1921 // override:
1922 // If override is given and true, the check function will be given
1923 // highest priority. Otherwise, it will be the lowest priority
1924 // adapter.
1925 this.pairs[((override) ? "unshift" : "push")]([name, check, wrap, directReturn]);
1926 },
1927
1928 match: function(/* ... */){
1929 // summary:
1930 // Find an adapter for the given arguments. If no suitable adapter
1931 // is found, throws an exception. match() accepts any number of
1932 // arguments, all of which are passed to all matching functions
1933 // from the registered pairs.
1934 for(var i = 0; i < this.pairs.length; i++){
1935 var pair = this.pairs[i];
1936 if(pair[1].apply(this, arguments)){
1937 if((pair[3])||(this.returnWrappers)){
1938 return pair[2];
1939 }else{
1940 return pair[2].apply(this, arguments);
1941 }
1942 }
1943 }
1944 throw new Error("No match found");
1945 },
1946
1947 unregister: function(name){
1948 // summary: Remove a named adapter from the registry
1949
1950 // FIXME: this is kind of a dumb way to handle this. On a large
1951 // registry this will be slow-ish and we can use the name as a lookup
1952 // should we choose to trade memory for speed.
1953 for(var i = 0; i < this.pairs.length; i++){
1954 var pair = this.pairs[i];
1955 if(pair[0] == name){
1956 this.pairs.splice(i, 1);
1957 return true;
1958 }
1959 }
1960 return false;
1961 }
1962});
1963
1964}
1965
1966if(!dojo._hasResource["dijit._base.place"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
1967dojo._hasResource["dijit._base.place"] = true;
1968dojo.provide("dijit._base.place");
1969
1970
1971
1972
a089699c
AD
1973dijit.getViewport = function(){
1974 // summary:
1975 // Returns the dimensions and scroll position of the viewable area of a browser window
1976
1977 return dojo.window.getBox();
1978};
1979
1980/*=====
1981dijit.__Position = function(){
1982 // x: Integer
1983 // horizontal coordinate in pixels, relative to document body
1984 // y: Integer
1985 // vertical coordinate in pixels, relative to document body
1986
1987 thix.x = x;
1988 this.y = y;
1989}
1990=====*/
1991
1992
1993dijit.placeOnScreen = function(
1994 /* DomNode */ node,
1995 /* dijit.__Position */ pos,
1996 /* String[] */ corners,
1997 /* dijit.__Position? */ padding){
1998 // summary:
1999 // Positions one of the node's corners at specified position
2000 // such that node is fully visible in viewport.
2001 // description:
2002 // NOTE: node is assumed to be absolutely or relatively positioned.
2003 // pos:
2004 // Object like {x: 10, y: 20}
2005 // corners:
2006 // Array of Strings representing order to try corners in, like ["TR", "BL"].
2007 // Possible values are:
2008 // * "BL" - bottom left
2009 // * "BR" - bottom right
2010 // * "TL" - top left
2011 // * "TR" - top right
2012 // padding:
2013 // set padding to put some buffer around the element you want to position.
2014 // example:
2015 // Try to place node's top right corner at (10,20).
2016 // If that makes node go (partially) off screen, then try placing
2017 // bottom left corner at (10,20).
2018 // | placeOnScreen(node, {x: 10, y: 20}, ["TR", "BL"])
2019
2020 var choices = dojo.map(corners, function(corner){
2021 var c = { corner: corner, pos: {x:pos.x,y:pos.y} };
2022 if(padding){
2023 c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x;
2024 c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y;
2025 }
2026 return c;
2027 });
2028
2029 return dijit._place(node, choices);
2030}
2031
81bea17a 2032dijit._place = function(/*DomNode*/ node, choices, layoutNode, /*Object*/ aroundNodeCoords){
a089699c
AD
2033 // summary:
2034 // Given a list of spots to put node, put it at the first spot where it fits,
2035 // of if it doesn't fit anywhere then the place with the least overflow
2036 // choices: Array
2037 // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} }
2038 // Above example says to put the top-left corner of the node at (10,20)
81bea17a 2039 // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size)
a089699c
AD
2040 // for things like tooltip, they are displayed differently (and have different dimensions)
2041 // based on their orientation relative to the parent. This adjusts the popup based on orientation.
81bea17a
AD
2042 // It also passes in the available size for the popup, which is useful for tooltips to
2043 // tell them that their width is limited to a certain amount. layoutNode() may return a value expressing
2044 // how much the popup had to be modified to fit into the available space. This is used to determine
2045 // what the best placement is.
2046 // aroundNodeCoords: Object
2047 // Size of aroundNode, ex: {w: 200, h: 50}
a089699c
AD
2048
2049 // get {x: 10, y: 10, w: 100, h:100} type obj representing position of
2050 // viewport over document
2051 var view = dojo.window.getBox();
2052
2053 // This won't work if the node is inside a <div style="position: relative">,
2054 // so reattach it to dojo.doc.body. (Otherwise, the positioning will be wrong
2055 // and also it might get cutoff)
2056 if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){
2057 dojo.body().appendChild(node);
2058 }
2059
2060 var best = null;
2061 dojo.some(choices, function(choice){
2062 var corner = choice.corner;
2063 var pos = choice.pos;
81bea17a
AD
2064 var overflow = 0;
2065
2066 // calculate amount of space available given specified position of node
2067 var spaceAvailable = {
2068 w: corner.charAt(1) == 'L' ? (view.l + view.w) - pos.x : pos.x - view.l,
2069 h: corner.charAt(1) == 'T' ? (view.t + view.h) - pos.y : pos.y - view.t
2070 };
a089699c
AD
2071
2072 // configure node to be displayed in given position relative to button
2073 // (need to do this in order to get an accurate size for the node, because
81bea17a 2074 // a tooltip's size changes based on position, due to triangle)
a089699c 2075 if(layoutNode){
81bea17a
AD
2076 var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords);
2077 overflow = typeof res == "undefined" ? 0 : res;
a089699c
AD
2078 }
2079
2080 // get node's size
2081 var style = node.style;
2082 var oldDisplay = style.display;
2083 var oldVis = style.visibility;
2084 style.visibility = "hidden";
2085 style.display = "";
2086 var mb = dojo.marginBox(node);
2087 style.display = oldDisplay;
2088 style.visibility = oldVis;
2089
2090 // coordinates and size of node with specified corner placed at pos,
2091 // and clipped by viewport
2092 var startX = Math.max(view.l, corner.charAt(1) == 'L' ? pos.x : (pos.x - mb.w)),
2093 startY = Math.max(view.t, corner.charAt(0) == 'T' ? pos.y : (pos.y - mb.h)),
2094 endX = Math.min(view.l + view.w, corner.charAt(1) == 'L' ? (startX + mb.w) : pos.x),
2095 endY = Math.min(view.t + view.h, corner.charAt(0) == 'T' ? (startY + mb.h) : pos.y),
2096 width = endX - startX,
81bea17a
AD
2097 height = endY - startY;
2098
2099 overflow += (mb.w - width) + (mb.h - height);
a089699c
AD
2100
2101 if(best == null || overflow < best.overflow){
2102 best = {
2103 corner: corner,
2104 aroundCorner: choice.aroundCorner,
2105 x: startX,
2106 y: startY,
2107 w: width,
2108 h: height,
81bea17a
AD
2109 overflow: overflow,
2110 spaceAvailable: spaceAvailable
a089699c
AD
2111 };
2112 }
81bea17a 2113
a089699c
AD
2114 return !overflow;
2115 });
2116
81bea17a
AD
2117 // In case the best position is not the last one we checked, need to call
2118 // layoutNode() again.
a089699c 2119 if(best.overflow && layoutNode){
81bea17a 2120 layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords);
a089699c 2121 }
81bea17a
AD
2122
2123 // And then position the node. Do this last, after the layoutNode() above
2124 // has sized the node, due to browser quirks when the viewport is scrolled
2125 // (specifically that a Tooltip will shrink to fit as though the window was
2126 // scrolled to the left).
2127 //
2128 // In RTL mode, set style.right rather than style.left so in the common case,
2129 // window resizes move the popup along with the aroundNode.
2130 var l = dojo._isBodyLtr(),
2131 s = node.style;
2132 s.top = best.y + "px";
2133 s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px";
2134
a089699c
AD
2135 return best;
2136}
2137
2138dijit.placeOnScreenAroundNode = function(
2139 /* DomNode */ node,
2140 /* DomNode */ aroundNode,
2141 /* Object */ aroundCorners,
2142 /* Function? */ layoutNode){
2143
2144 // summary:
2145 // Position node adjacent or kitty-corner to aroundNode
2146 // such that it's fully visible in viewport.
2147 //
2148 // description:
2149 // Place node such that corner of node touches a corner of
2150 // aroundNode, and that node is fully visible.
2151 //
2152 // aroundCorners:
2153 // Ordered list of pairs of corners to try matching up.
2154 // Each pair of corners is represented as a key/value in the hash,
2155 // where the key corresponds to the aroundNode's corner, and
2156 // the value corresponds to the node's corner:
2157 //
2158 // | { aroundNodeCorner1: nodeCorner1, aroundNodeCorner2: nodeCorner2, ...}
2159 //
2160 // The following strings are used to represent the four corners:
2161 // * "BL" - bottom left
2162 // * "BR" - bottom right
2163 // * "TL" - top left
2164 // * "TR" - top right
2165 //
2166 // layoutNode: Function(node, aroundNodeCorner, nodeCorner)
2167 // For things like tooltip, they are displayed differently (and have different dimensions)
2168 // based on their orientation relative to the parent. This adjusts the popup based on orientation.
2169 //
2170 // example:
2171 // | dijit.placeOnScreenAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'});
2172 // This will try to position node such that node's top-left corner is at the same position
2173 // as the bottom left corner of the aroundNode (ie, put node below
2174 // aroundNode, with left edges aligned). If that fails it will try to put
2175 // the bottom-right corner of node where the top right corner of aroundNode is
2176 // (ie, put node above aroundNode, with right edges aligned)
2177 //
2178
2179 // get coordinates of aroundNode
2180 aroundNode = dojo.byId(aroundNode);
a089699c 2181 var aroundNodePos = dojo.position(aroundNode, true);
a089699c
AD
2182
2183 // place the node around the calculated rectangle
2184 return dijit._placeOnScreenAroundRect(node,
2185 aroundNodePos.x, aroundNodePos.y, aroundNodePos.w, aroundNodePos.h, // rectangle
2186 aroundCorners, layoutNode);
2187};
2188
2189/*=====
2190dijit.__Rectangle = function(){
2191 // x: Integer
2192 // horizontal offset in pixels, relative to document body
2193 // y: Integer
2194 // vertical offset in pixels, relative to document body
2195 // width: Integer
2196 // width in pixels
2197 // height: Integer
2198 // height in pixels
2199
2200 this.x = x;
2201 this.y = y;
2202 this.width = width;
2203 this.height = height;
2204}
2205=====*/
2206
2207
2208dijit.placeOnScreenAroundRectangle = function(
2209 /* DomNode */ node,
2210 /* dijit.__Rectangle */ aroundRect,
2211 /* Object */ aroundCorners,
2212 /* Function */ layoutNode){
2213
2214 // summary:
2215 // Like dijit.placeOnScreenAroundNode(), except that the "around"
2216 // parameter is an arbitrary rectangle on the screen (x, y, width, height)
2217 // instead of a dom node.
2218
2219 return dijit._placeOnScreenAroundRect(node,
2220 aroundRect.x, aroundRect.y, aroundRect.width, aroundRect.height, // rectangle
2221 aroundCorners, layoutNode);
2222};
2223
2224dijit._placeOnScreenAroundRect = function(
2225 /* DomNode */ node,
2226 /* Number */ x,
2227 /* Number */ y,
2228 /* Number */ width,
2229 /* Number */ height,
2230 /* Object */ aroundCorners,
2231 /* Function */ layoutNode){
2232
2233 // summary:
2234 // Like dijit.placeOnScreenAroundNode(), except it accepts coordinates
2235 // of a rectangle to place node adjacent to.
2236
2237 // TODO: combine with placeOnScreenAroundRectangle()
2238
2239 // Generate list of possible positions for node
2240 var choices = [];
2241 for(var nodeCorner in aroundCorners){
2242 choices.push( {
2243 aroundCorner: nodeCorner,
2244 corner: aroundCorners[nodeCorner],
2245 pos: {
2246 x: x + (nodeCorner.charAt(1) == 'L' ? 0 : width),
2247 y: y + (nodeCorner.charAt(0) == 'T' ? 0 : height)
2248 }
2249 });
2250 }
2251
81bea17a 2252 return dijit._place(node, choices, layoutNode, {w: width, h: height});
a089699c
AD
2253};
2254
2255dijit.placementRegistry= new dojo.AdapterRegistry();
2256dijit.placementRegistry.register("node",
2257 function(n, x){
2258 return typeof x == "object" &&
2259 typeof x.offsetWidth != "undefined" && typeof x.offsetHeight != "undefined";
2260 },
2261 dijit.placeOnScreenAroundNode);
2262dijit.placementRegistry.register("rect",
2263 function(n, x){
2264 return typeof x == "object" &&
2265 "x" in x && "y" in x && "width" in x && "height" in x;
2266 },
2267 dijit.placeOnScreenAroundRectangle);
2268
2269dijit.placeOnScreenAroundElement = function(
2270 /* DomNode */ node,
2271 /* Object */ aroundElement,
2272 /* Object */ aroundCorners,
2273 /* Function */ layoutNode){
2274
2275 // summary:
2276 // Like dijit.placeOnScreenAroundNode(), except it accepts an arbitrary object
2277 // for the "around" argument and finds a proper processor to place a node.
2278
2279 return dijit.placementRegistry.match.apply(dijit.placementRegistry, arguments);
2280};
2281
2282dijit.getPopupAroundAlignment = function(/*Array*/ position, /*Boolean*/ leftToRight){
2283 // summary:
2284 // Transforms the passed array of preferred positions into a format suitable for passing as the aroundCorners argument to dijit.placeOnScreenAroundElement.
2285 //
2286 // position: String[]
2287 // This variable controls the position of the drop down.
2288 // It's an array of strings with the following values:
2289 //
2290 // * before: places drop down to the left of the target node/widget, or to the right in
2291 // the case of RTL scripts like Hebrew and Arabic
2292 // * after: places drop down to the right of the target node/widget, or to the left in
2293 // the case of RTL scripts like Hebrew and Arabic
2294 // * above: drop down goes above target node
2295 // * below: drop down goes below target node
2296 //
2297 // The list is positions is tried, in order, until a position is found where the drop down fits
2298 // within the viewport.
2299 //
2300 // leftToRight: Boolean
2301 // Whether the popup will be displaying in leftToRight mode.
2302 //
2303 var align = {};
2304 dojo.forEach(position, function(pos){
2305 switch(pos){
2306 case "after":
2307 align[leftToRight ? "BR" : "BL"] = leftToRight ? "BL" : "BR";
2308 break;
2309 case "before":
2310 align[leftToRight ? "BL" : "BR"] = leftToRight ? "BR" : "BL";
2311 break;
81bea17a
AD
2312 case "below-alt":
2313 leftToRight = !leftToRight;
2314 // fall through
a089699c
AD
2315 case "below":
2316 // first try to align left borders, next try to align right borders (or reverse for RTL mode)
2317 align[leftToRight ? "BL" : "BR"] = leftToRight ? "TL" : "TR";
2318 align[leftToRight ? "BR" : "BL"] = leftToRight ? "TR" : "TL";
2319 break;
81bea17a
AD
2320 case "above-alt":
2321 leftToRight = !leftToRight;
2322 // fall through
a089699c
AD
2323 case "above":
2324 default:
2325 // first try to align left borders, next try to align right borders (or reverse for RTL mode)
2326 align[leftToRight ? "TL" : "TR"] = leftToRight ? "BL" : "BR";
2327 align[leftToRight ? "TR" : "TL"] = leftToRight ? "BR" : "BL";
2328 break;
2329 }
2330 });
2331 return align;
2332};
2333
2334}
2335
2336if(!dojo._hasResource["dijit._base.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2337dojo._hasResource["dijit._base.window"] = true;
2338dojo.provide("dijit._base.window");
2339
2340
2341
2342dijit.getDocumentWindow = function(doc){
2343 return dojo.window.get(doc);
2344};
2345
2346}
2347
2348if(!dojo._hasResource["dijit._base.popup"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2349dojo._hasResource["dijit._base.popup"] = true;
2350dojo.provide("dijit._base.popup");
2351
2352
2353
2354
2355
2356/*=====
2357dijit.popup.__OpenArgs = function(){
2358 // popup: Widget
2359 // widget to display
2360 // parent: Widget
2361 // the button etc. that is displaying this popup
2362 // around: DomNode
2363 // DOM node (typically a button); place popup relative to this node. (Specify this *or* "x" and "y" parameters.)
2364 // x: Integer
2365 // Absolute horizontal position (in pixels) to place node at. (Specify this *or* "around" parameter.)
2366 // y: Integer
2367 // Absolute vertical position (in pixels) to place node at. (Specify this *or* "around" parameter.)
2368 // orient: Object|String
2369 // When the around parameter is specified, orient should be an
2370 // ordered list of tuples of the form (around-node-corner, popup-node-corner).
2371 // dijit.popup.open() tries to position the popup according to each tuple in the list, in order,
2372 // until the popup appears fully within the viewport.
2373 //
2374 // The default value is {BL:'TL', TL:'BL'}, which represents a list of two tuples:
2375 // 1. (BL, TL)
2376 // 2. (TL, BL)
2377 // where BL means "bottom left" and "TL" means "top left".
2378 // So by default, it first tries putting the popup below the around node, left-aligning them,
2379 // and then tries to put it above the around node, still left-aligning them. Note that the
2380 // default is horizontally reversed when in RTL mode.
2381 //
2382 // When an (x,y) position is specified rather than an around node, orient is either
2383 // "R" or "L". R (for right) means that it tries to put the popup to the right of the mouse,
2384 // specifically positioning the popup's top-right corner at the mouse position, and if that doesn't
2385 // fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner,
2386 // and the top-right corner.
2387 // onCancel: Function
2388 // callback when user has canceled the popup by
2389 // 1. hitting ESC or
2390 // 2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog);
2391 // i.e. whenever popupWidget.onCancel() is called, args.onCancel is called
2392 // onClose: Function
2393 // callback whenever this popup is closed
2394 // onExecute: Function
2395 // callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only)
2396 // padding: dijit.__Position
2397 // adding a buffer around the opening position. This is only useful when around is not set.
2398 this.popup = popup;
2399 this.parent = parent;
2400 this.around = around;
2401 this.x = x;
2402 this.y = y;
2403 this.orient = orient;
2404 this.onCancel = onCancel;
2405 this.onClose = onClose;
2406 this.onExecute = onExecute;
2407 this.padding = padding;
2408}
2409=====*/
2410
2411dijit.popup = {
2412 // summary:
2413 // This singleton is used to show/hide widgets as popups.
2414
2415 // _stack: dijit._Widget[]
2416 // Stack of currently popped up widgets.
2417 // (someone opened _stack[0], and then it opened _stack[1], etc.)
2418 _stack: [],
2419
2420 // _beginZIndex: Number
2421 // Z-index of the first popup. (If first popup opens other
2422 // popups they get a higher z-index.)
2423 _beginZIndex: 1000,
2424
2425 _idGen: 1,
2426
81bea17a 2427 _createWrapper: function(/*Widget || DomNode*/ widget){
a089699c 2428 // summary:
81bea17a
AD
2429 // Initialization for widgets that will be used as popups.
2430 // Puts widget inside a wrapper DIV (if not already in one),
2431 // and returns pointer to that wrapper DIV.
2432
2433 var wrapper = widget.declaredClass ? widget._popupWrapper : (widget.parentNode && dojo.hasClass(widget.parentNode, "dijitPopup")),
2434 node = widget.domNode || widget;
2435
2436 if(!wrapper){
2437 // Create wrapper <div> for when this widget [in the future] will be used as a popup.
2438 // This is done early because of IE bugs where creating/moving DOM nodes causes focus
2439 // to go wonky, see tests/robot/Toolbar.html to reproduce
a089699c
AD
2440 wrapper = dojo.create("div",{
2441 "class":"dijitPopup",
81bea17a
AD
2442 style:{ display: "none"},
2443 role: "presentation"
a089699c 2444 }, dojo.body());
a089699c 2445 wrapper.appendChild(node);
81bea17a
AD
2446
2447 var s = node.style;
2448 s.display = "";
2449 s.visibility = "";
2450 s.position = "";
2451 s.top = "0px";
2452
2453 if(widget.declaredClass){ // TODO: in 2.0 change signature to always take widget, then remove if()
2454 widget._popupWrapper = wrapper;
2455 dojo.connect(widget, "destroy", function(){
2456 dojo.destroy(wrapper);
2457 delete widget._popupWrapper;
2458 });
2459 }
a089699c 2460 }
81bea17a
AD
2461
2462 return wrapper;
2463 },
a089699c 2464
81bea17a
AD
2465 moveOffScreen: function(/*Widget || DomNode*/ widget){
2466 // summary:
2467 // Moves the popup widget off-screen.
2468 // Do not use this method to hide popups when not in use, because
2469 // that will create an accessibility issue: the offscreen popup is
2470 // still in the tabbing order.
a089699c 2471
81bea17a
AD
2472 // Create wrapper if not already there
2473 var wrapper = this._createWrapper(widget);
a089699c
AD
2474
2475 dojo.style(wrapper, {
2476 visibility: "hidden",
81bea17a
AD
2477 top: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
2478 display: ""
a089699c
AD
2479 });
2480 },
2481
81bea17a
AD
2482 hide: function(/*dijit._Widget*/ widget){
2483 // summary:
2484 // Hide this popup widget (until it is ready to be shown).
2485 // Initialization for widgets that will be used as popups
2486 //
2487 // Also puts widget inside a wrapper DIV (if not already in one)
2488 //
2489 // If popup widget needs to layout it should
2490 // do so when it is made visible, and popup._onShow() is called.
2491
2492 // Create wrapper if not already there
2493 var wrapper = this._createWrapper(widget);
2494
2495 dojo.style(wrapper, "display", "none");
2496 },
2497
a089699c
AD
2498 getTopPopup: function(){
2499 // summary:
2500 // Compute the closest ancestor popup that's *not* a child of another popup.
2501 // Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button.
2502 var stack = this._stack;
2503 for(var pi=stack.length-1; pi > 0 && stack[pi].parent === stack[pi-1].widget; pi--){
2504 /* do nothing, just trying to get right value for pi */
2505 }
2506 return stack[pi];
2507 },
2508
2509 open: function(/*dijit.popup.__OpenArgs*/ args){
2510 // summary:
2511 // Popup the widget at the specified position
2512 //
2513 // example:
2514 // opening at the mouse position
2515 // | dijit.popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
2516 //
2517 // example:
2518 // opening the widget as a dropdown
2519 // | dijit.popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
2520 //
2521 // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback
2522 // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed.
2523
2524 var stack = this._stack,
2525 widget = args.popup,
2526 orient = args.orient || (
2527 (args.parent ? args.parent.isLeftToRight() : dojo._isBodyLtr()) ?
2528 {'BL':'TL', 'BR':'TR', 'TL':'BL', 'TR':'BR'} :
2529 {'BR':'TR', 'BL':'TL', 'TR':'BR', 'TL':'BL'}
2530 ),
2531 around = args.around,
2532 id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++);
2533
81bea17a
AD
2534 // If we are opening a new popup that isn't a child of a currently opened popup, then
2535 // close currently opened popup(s). This should happen automatically when the old popups
2536 // gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198].
2537 while(stack.length && (!args.parent || !dojo.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){
2538 dijit.popup.close(stack[stack.length-1].widget);
a089699c
AD
2539 }
2540
81bea17a
AD
2541 // Get pointer to popup wrapper, and create wrapper if it doesn't exist
2542 var wrapper = this._createWrapper(widget);
2543
2544
a089699c
AD
2545 dojo.attr(wrapper, {
2546 id: id,
2547 style: {
2548 zIndex: this._beginZIndex + stack.length
2549 },
2550 "class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup",
2551 dijitPopupParent: args.parent ? args.parent.id : ""
2552 });
2553
2554 if(dojo.isIE || dojo.isMoz){
81bea17a
AD
2555 if(!widget.bgIframe){
2556 // setting widget.bgIframe triggers cleanup in _Widget.destroy()
2557 widget.bgIframe = new dijit.BackgroundIframe(wrapper);
a089699c
AD
2558 }
2559 }
2560
2561 // position the wrapper node and make it visible
2562 var best = around ?
2563 dijit.placeOnScreenAroundElement(wrapper, around, orient, widget.orient ? dojo.hitch(widget, "orient") : null) :
2564 dijit.placeOnScreen(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding);
2565
81bea17a 2566 wrapper.style.display = "";
a089699c
AD
2567 wrapper.style.visibility = "visible";
2568 widget.domNode.style.visibility = "visible"; // counteract effects from _HasDropDown
2569
2570 var handlers = [];
2571
2572 // provide default escape and tab key handling
2573 // (this will work for any widget, not just menu)
2574 handlers.push(dojo.connect(wrapper, "onkeypress", this, function(evt){
2575 if(evt.charOrCode == dojo.keys.ESCAPE && args.onCancel){
2576 dojo.stopEvent(evt);
2577 args.onCancel();
2578 }else if(evt.charOrCode === dojo.keys.TAB){
2579 dojo.stopEvent(evt);
2580 var topPopup = this.getTopPopup();
2581 if(topPopup && topPopup.onCancel){
2582 topPopup.onCancel();
2583 }
2584 }
2585 }));
2586
2587 // watch for cancel/execute events on the popup and notify the caller
2588 // (for a menu, "execute" means clicking an item)
2589 if(widget.onCancel){
2590 handlers.push(dojo.connect(widget, "onCancel", args.onCancel));
2591 }
2592
2593 handlers.push(dojo.connect(widget, widget.onExecute ? "onExecute" : "onChange", this, function(){
2594 var topPopup = this.getTopPopup();
2595 if(topPopup && topPopup.onExecute){
2596 topPopup.onExecute();
2597 }
2598 }));
2599
2600 stack.push({
a089699c
AD
2601 widget: widget,
2602 parent: args.parent,
2603 onExecute: args.onExecute,
2604 onCancel: args.onCancel,
2605 onClose: args.onClose,
2606 handlers: handlers
2607 });
2608
2609 if(widget.onOpen){
2610 // TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here)
2611 widget.onOpen(best);
2612 }
2613
2614 return best;
2615 },
2616
81bea17a 2617 close: function(/*dijit._Widget?*/ popup){
a089699c 2618 // summary:
81bea17a
AD
2619 // Close specified popup and any popups that it parented.
2620 // If no popup is specified, closes all popups.
a089699c
AD
2621
2622 var stack = this._stack;
2623
2624 // Basically work backwards from the top of the stack closing popups
2625 // until we hit the specified popup, but IIRC there was some issue where closing
2626 // a popup would cause others to close too. Thus if we are trying to close B in [A,B,C]
2627 // closing C might close B indirectly and then the while() condition will run where stack==[A]...
2628 // so the while condition is constructed defensively.
81bea17a
AD
2629 while((popup && dojo.some(stack, function(elem){return elem.widget == popup;})) ||
2630 (!popup && stack.length)){
a089699c 2631 var top = stack.pop(),
a089699c
AD
2632 widget = top.widget,
2633 onClose = top.onClose;
2634
2635 if(widget.onClose){
2636 // TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here)
2637 widget.onClose();
2638 }
2639 dojo.forEach(top.handlers, dojo.disconnect);
2640
81bea17a 2641 // Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc.
a089699c 2642 if(widget && widget.domNode){
81bea17a 2643 this.hide(widget);
a089699c
AD
2644 }
2645
2646 if(onClose){
2647 onClose();
2648 }
2649 }
2650 }
2651};
2652
81bea17a
AD
2653// TODO: remove dijit._frames, it isn't being used much, since popups never release their
2654// iframes (see [22236])
a089699c
AD
2655dijit._frames = new function(){
2656 // summary:
2657 // cache of iframes
81bea17a 2658
a089699c
AD
2659 var queue = [];
2660
2661 this.pop = function(){
2662 var iframe;
2663 if(queue.length){
2664 iframe = queue.pop();
2665 iframe.style.display="";
2666 }else{
81bea17a 2667 if(dojo.isIE < 9){
a089699c
AD
2668 var burl = dojo.config["dojoBlankHtmlUrl"] || (dojo.moduleUrl("dojo", "resources/blank.html")+"") || "javascript:\"\"";
2669 var html="<iframe src='" + burl + "'"
2670 + " style='position: absolute; left: 0px; top: 0px;"
2671 + "z-index: -1; filter:Alpha(Opacity=\"0\");'>";
2672 iframe = dojo.doc.createElement(html);
2673 }else{
2674 iframe = dojo.create("iframe");
2675 iframe.src = 'javascript:""';
2676 iframe.className = "dijitBackgroundIframe";
2677 dojo.style(iframe, "opacity", 0.1);
2678 }
81bea17a 2679 iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didn't work.
a089699c
AD
2680 dijit.setWaiRole(iframe,"presentation");
2681 }
2682 return iframe;
2683 };
2684
2685 this.push = function(iframe){
2686 iframe.style.display="none";
2687 queue.push(iframe);
2688 }
2689}();
2690
2691
81bea17a 2692dijit.BackgroundIframe = function(/*DomNode*/ node){
a089699c
AD
2693 // summary:
2694 // For IE/FF z-index schenanigans. id attribute is required.
2695 //
2696 // description:
2697 // new dijit.BackgroundIframe(node)
2698 // Makes a background iframe as a child of node, that fills
2699 // area (and position) of node
2700
2701 if(!node.id){ throw new Error("no id"); }
2702 if(dojo.isIE || dojo.isMoz){
81bea17a 2703 var iframe = (this.iframe = dijit._frames.pop());
a089699c 2704 node.appendChild(iframe);
81bea17a 2705 if(dojo.isIE<7 || dojo.isQuirks){
a089699c
AD
2706 this.resize(node);
2707 this._conn = dojo.connect(node, 'onresize', this, function(){
2708 this.resize(node);
2709 });
2710 }else{
2711 dojo.style(iframe, {
2712 width: '100%',
2713 height: '100%'
2714 });
2715 }
a089699c
AD
2716 }
2717};
2718
2719dojo.extend(dijit.BackgroundIframe, {
2720 resize: function(node){
2721 // summary:
81bea17a
AD
2722 // Resize the iframe so it's the same size as node.
2723 // Needed on IE6 and IE/quirks because height:100% doesn't work right.
2724 if(this.iframe){
a089699c
AD
2725 dojo.style(this.iframe, {
2726 width: node.offsetWidth + 'px',
2727 height: node.offsetHeight + 'px'
2728 });
2729 }
2730 },
2731 destroy: function(){
2732 // summary:
2733 // destroy the iframe
2734 if(this._conn){
2735 dojo.disconnect(this._conn);
2736 this._conn = null;
2737 }
2738 if(this.iframe){
2739 dijit._frames.push(this.iframe);
2740 delete this.iframe;
2741 }
2742 }
2743});
2744
2745}
2746
2747if(!dojo._hasResource["dijit._base.scroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2748dojo._hasResource["dijit._base.scroll"] = true;
2749dojo.provide("dijit._base.scroll");
2750
2751
2752
2753dijit.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){
2754 // summary:
2755 // Scroll the passed node into view, if it is not already.
2756 // Deprecated, use `dojo.window.scrollIntoView` instead.
2757
2758 dojo.window.scrollIntoView(node, pos);
2759};
2760
2761}
2762
2763if(!dojo._hasResource["dojo.uacss"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2764dojo._hasResource["dojo.uacss"] = true;
2765dojo.provide("dojo.uacss");
2766
81bea17a 2767
a089699c
AD
2768(function(){
2769 // summary:
2770 // Applies pre-set CSS classes to the top-level HTML node, based on:
2771 // - browser (ex: dj_ie)
2772 // - browser version (ex: dj_ie6)
2773 // - box model (ex: dj_contentBox)
2774 // - text direction (ex: dijitRtl)
2775 //
2776 // In addition, browser, browser version, and box model are
2777 // combined with an RTL flag when browser text is RTL. ex: dj_ie-rtl.
2778
2779 var d = dojo,
2780 html = d.doc.documentElement,
2781 ie = d.isIE,
2782 opera = d.isOpera,
2783 maj = Math.floor,
2784 ff = d.isFF,
2785 boxModel = d.boxModel.replace(/-/,''),
2786
2787 classes = {
2788 dj_ie: ie,
2789 dj_ie6: maj(ie) == 6,
2790 dj_ie7: maj(ie) == 7,
2791 dj_ie8: maj(ie) == 8,
81bea17a 2792 dj_ie9: maj(ie) == 9,
a089699c
AD
2793 dj_quirks: d.isQuirks,
2794 dj_iequirks: ie && d.isQuirks,
2795
2796 // NOTE: Opera not supported by dijit
2797 dj_opera: opera,
2798
2799 dj_khtml: d.isKhtml,
2800
2801 dj_webkit: d.isWebKit,
2802 dj_safari: d.isSafari,
2803 dj_chrome: d.isChrome,
2804
2805 dj_gecko: d.isMozilla,
2806 dj_ff3: maj(ff) == 3
2807 }; // no dojo unsupported browsers
2808
2809 classes["dj_" + boxModel] = true;
2810
2811 // apply browser, browser version, and box model class names
2812 var classStr = "";
2813 for(var clz in classes){
2814 if(classes[clz]){
2815 classStr += clz + " ";
2816 }
2817 }
2818 html.className = d.trim(html.className + " " + classStr);
2819
2820 // If RTL mode, then add dj_rtl flag plus repeat existing classes with -rtl extension.
81bea17a 2821 // We can't run the code below until the <body> tag has loaded (so we can check for dir=rtl).
a089699c
AD
2822 // Unshift() is to run sniff code before the parser.
2823 dojo._loaders.unshift(function(){
2824 if(!dojo._isBodyLtr()){
2825 var rtlClassStr = "dj_rtl dijitRtl " + classStr.replace(/ /g, "-rtl ")
2826 html.className = d.trim(html.className + " " + rtlClassStr);
2827 }
2828 });
2829})();
2830
2831}
2832
2833if(!dojo._hasResource["dijit._base.sniff"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2834dojo._hasResource["dijit._base.sniff"] = true;
81bea17a
AD
2835dojo.provide("dijit._base.sniff");
2836
2837
2838
a089699c
AD
2839// summary:
2840// Applies pre-set CSS classes to the top-level HTML node, see
2841// `dojo.uacss` for details.
2842//
2843// Simply doing a require on this module will
2844// establish this CSS. Modified version of Morris' CSS hack.
2845
a089699c
AD
2846}
2847
2848if(!dojo._hasResource["dijit._base.typematic"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2849dojo._hasResource["dijit._base.typematic"] = true;
2850dojo.provide("dijit._base.typematic");
2851
81bea17a 2852
a089699c
AD
2853dijit.typematic = {
2854 // summary:
2855 // These functions are used to repetitively call a user specified callback
2856 // method when a specific key or mouse click over a specific DOM node is
2857 // held down for a specific amount of time.
2858 // Only 1 such event is allowed to occur on the browser page at 1 time.
2859
2860 _fireEventAndReload: function(){
2861 this._timer = null;
2862 this._callback(++this._count, this._node, this._evt);
2863
2864 // Schedule next event, timer is at most minDelay (default 10ms) to avoid
2865 // browser overload (particularly avoiding starving DOH robot so it never gets to send a mouseup)
2866 this._currentTimeout = Math.max(
2867 this._currentTimeout < 0 ? this._initialDelay :
2868 (this._subsequentDelay > 1 ? this._subsequentDelay : Math.round(this._currentTimeout * this._subsequentDelay)),
2869 this._minDelay);
2870 this._timer = setTimeout(dojo.hitch(this, "_fireEventAndReload"), this._currentTimeout);
2871 },
2872
2873 trigger: function(/*Event*/ evt, /*Object*/ _this, /*DOMNode*/ node, /*Function*/ callback, /*Object*/ obj, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
2874 // summary:
2875 // Start a timed, repeating callback sequence.
2876 // If already started, the function call is ignored.
2877 // This method is not normally called by the user but can be
2878 // when the normal listener code is insufficient.
2879 // evt:
2880 // key or mouse event object to pass to the user callback
2881 // _this:
2882 // pointer to the user's widget space.
2883 // node:
2884 // the DOM node object to pass the the callback function
2885 // callback:
2886 // function to call until the sequence is stopped called with 3 parameters:
2887 // count:
2888 // integer representing number of repeated calls (0..n) with -1 indicating the iteration has stopped
2889 // node:
2890 // the DOM node object passed in
2891 // evt:
2892 // key or mouse event object
2893 // obj:
2894 // user space object used to uniquely identify each typematic sequence
2895 // subsequentDelay (optional):
2896 // if > 1, the number of milliseconds until the 3->n events occur
2897 // or else the fractional time multiplier for the next event's delay, default=0.9
2898 // initialDelay (optional):
2899 // the number of milliseconds until the 2nd event occurs, default=500ms
2900 // minDelay (optional):
2901 // the maximum delay in milliseconds for event to fire, default=10ms
2902 if(obj != this._obj){
2903 this.stop();
2904 this._initialDelay = initialDelay || 500;
2905 this._subsequentDelay = subsequentDelay || 0.90;
2906 this._minDelay = minDelay || 10;
2907 this._obj = obj;
2908 this._evt = evt;
2909 this._node = node;
2910 this._currentTimeout = -1;
2911 this._count = -1;
2912 this._callback = dojo.hitch(_this, callback);
2913 this._fireEventAndReload();
2914 this._evt = dojo.mixin({faux: true}, evt);
2915 }
2916 },
2917
2918 stop: function(){
2919 // summary:
2920 // Stop an ongoing timed, repeating callback sequence.
2921 if(this._timer){
2922 clearTimeout(this._timer);
2923 this._timer = null;
2924 }
2925 if(this._obj){
2926 this._callback(-1, this._node, this._evt);
2927 this._obj = null;
2928 }
2929 },
2930
2931 addKeyListener: function(/*DOMNode*/ node, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
2932 // summary:
2933 // Start listening for a specific typematic key.
2934 // See also the trigger method for other parameters.
2935 // keyObject:
2936 // an object defining the key to listen for:
2937 // charOrCode:
2938 // the printable character (string) or keyCode (number) to listen for.
2939 // keyCode:
2940 // (deprecated - use charOrCode) the keyCode (number) to listen for (implies charCode = 0).
2941 // charCode:
2942 // (deprecated - use charOrCode) the charCode (number) to listen for.
2943 // ctrlKey:
2944 // desired ctrl key state to initiate the callback sequence:
2945 // - pressed (true)
2946 // - released (false)
2947 // - either (unspecified)
2948 // altKey:
2949 // same as ctrlKey but for the alt key
2950 // shiftKey:
2951 // same as ctrlKey but for the shift key
2952 // returns:
2953 // an array of dojo.connect handles
2954 if(keyObject.keyCode){
2955 keyObject.charOrCode = keyObject.keyCode;
2956 dojo.deprecated("keyCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0");
2957 }else if(keyObject.charCode){
2958 keyObject.charOrCode = String.fromCharCode(keyObject.charCode);
2959 dojo.deprecated("charCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0");
2960 }
2961 return [
2962 dojo.connect(node, "onkeypress", this, function(evt){
2963 if(evt.charOrCode == keyObject.charOrCode &&
2964 (keyObject.ctrlKey === undefined || keyObject.ctrlKey == evt.ctrlKey) &&
2965 (keyObject.altKey === undefined || keyObject.altKey == evt.altKey) &&
2966 (keyObject.metaKey === undefined || keyObject.metaKey == (evt.metaKey || false)) && // IE doesn't even set metaKey
2967 (keyObject.shiftKey === undefined || keyObject.shiftKey == evt.shiftKey)){
2968 dojo.stopEvent(evt);
2969 dijit.typematic.trigger(evt, _this, node, callback, keyObject, subsequentDelay, initialDelay, minDelay);
2970 }else if(dijit.typematic._obj == keyObject){
2971 dijit.typematic.stop();
2972 }
2973 }),
2974 dojo.connect(node, "onkeyup", this, function(evt){
2975 if(dijit.typematic._obj == keyObject){
2976 dijit.typematic.stop();
2977 }
2978 })
2979 ];
2980 },
2981
2982 addMouseListener: function(/*DOMNode*/ node, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
2983 // summary:
2984 // Start listening for a typematic mouse click.
2985 // See the trigger method for other parameters.
2986 // returns:
2987 // an array of dojo.connect handles
2988 var dc = dojo.connect;
2989 return [
2990 dc(node, "mousedown", this, function(evt){
2991 dojo.stopEvent(evt);
2992 dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
2993 }),
2994 dc(node, "mouseup", this, function(evt){
2995 dojo.stopEvent(evt);
2996 dijit.typematic.stop();
2997 }),
2998 dc(node, "mouseout", this, function(evt){
2999 dojo.stopEvent(evt);
3000 dijit.typematic.stop();
3001 }),
3002 dc(node, "mousemove", this, function(evt){
3003 evt.preventDefault();
3004 }),
3005 dc(node, "dblclick", this, function(evt){
3006 dojo.stopEvent(evt);
3007 if(dojo.isIE){
3008 dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
3009 setTimeout(dojo.hitch(this, dijit.typematic.stop), 50);
3010 }
3011 })
3012 ];
3013 },
3014
3015 addListener: function(/*Node*/ mouseNode, /*Node*/ keyNode, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
3016 // summary:
3017 // Start listening for a specific typematic key and mouseclick.
3018 // This is a thin wrapper to addKeyListener and addMouseListener.
3019 // See the addMouseListener and addKeyListener methods for other parameters.
3020 // mouseNode:
3021 // the DOM node object to listen on for mouse events.
3022 // keyNode:
3023 // the DOM node object to listen on for key events.
3024 // returns:
3025 // an array of dojo.connect handles
3026 return this.addKeyListener(keyNode, keyObject, _this, callback, subsequentDelay, initialDelay, minDelay).concat(
3027 this.addMouseListener(mouseNode, _this, callback, subsequentDelay, initialDelay, minDelay));
3028 }
3029};
3030
3031}
3032
3033if(!dojo._hasResource["dijit._base.wai"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3034dojo._hasResource["dijit._base.wai"] = true;
3035dojo.provide("dijit._base.wai");
3036
81bea17a 3037
a089699c
AD
3038dijit.wai = {
3039 onload: function(){
3040 // summary:
3041 // Detects if we are in high-contrast mode or not
3042
3043 // This must be a named function and not an anonymous
3044 // function, so that the widget parsing code can make sure it
3045 // registers its onload function after this function.
3046 // DO NOT USE "this" within this function.
3047
3048 // create div for testing if high contrast mode is on or images are turned off
3049 var div = dojo.create("div",{
3050 id: "a11yTestNode",
3051 style:{
3052 cssText:'border: 1px solid;'
3053 + 'border-color:red green;'
3054 + 'position: absolute;'
3055 + 'height: 5px;'
3056 + 'top: -999px;'
3057 + 'background-image: url("' + (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")) + '");'
3058 }
3059 }, dojo.body());
3060
3061 // test it
3062 var cs = dojo.getComputedStyle(div);
3063 if(cs){
3064 var bkImg = cs.backgroundImage;
3065 var needsA11y = (cs.borderTopColor == cs.borderRightColor) || (bkImg != null && (bkImg == "none" || bkImg == "url(invalid-url:)" ));
3066 dojo[needsA11y ? "addClass" : "removeClass"](dojo.body(), "dijit_a11y");
3067 if(dojo.isIE){
3068 div.outerHTML = ""; // prevent mixed-content warning, see http://support.microsoft.com/kb/925014
3069 }else{
3070 dojo.body().removeChild(div);
3071 }
3072 }
3073 }
3074};
3075
3076// Test if computer is in high contrast mode.
3077// Make sure the a11y test runs first, before widgets are instantiated.
3078if(dojo.isIE || dojo.isMoz){ // NOTE: checking in Safari messes things up
3079 dojo._loaders.unshift(dijit.wai.onload);
3080}
3081
3082dojo.mixin(dijit, {
81bea17a 3083 hasWaiRole: function(/*Element*/ elem, /*String?*/ role){
a089699c 3084 // summary:
81bea17a 3085 // Determines if an element has a particular role.
a089699c 3086 // returns:
81bea17a 3087 // True if elem has the specific role attribute and false if not.
a089699c 3088 // For backwards compatibility if role parameter not provided,
81bea17a 3089 // returns true if has a role
a089699c
AD
3090 var waiRole = this.getWaiRole(elem);
3091 return role ? (waiRole.indexOf(role) > -1) : (waiRole.length > 0);
3092 },
3093
3094 getWaiRole: function(/*Element*/ elem){
3095 // summary:
81bea17a 3096 // Gets the role for an element (which should be a wai role).
a089699c 3097 // returns:
81bea17a 3098 // The role of elem or an empty string if elem
a089699c 3099 // does not have a role.
81bea17a 3100 return dojo.trim((dojo.attr(elem, "role") || "").replace("wairole:",""));
a089699c
AD
3101 },
3102
3103 setWaiRole: function(/*Element*/ elem, /*String*/ role){
3104 // summary:
3105 // Sets the role on an element.
3106 // description:
3107 // Replace existing role attribute with new role.
a089699c 3108
a089699c 3109 dojo.attr(elem, "role", role);
a089699c
AD
3110 },
3111
3112 removeWaiRole: function(/*Element*/ elem, /*String*/ role){
3113 // summary:
81bea17a 3114 // Removes the specified role from an element.
a089699c
AD
3115 // Removes role attribute if no specific role provided (for backwards compat.)
3116
3117 var roleValue = dojo.attr(elem, "role");
3118 if(!roleValue){ return; }
3119 if(role){
3120 var t = dojo.trim((" " + roleValue + " ").replace(" " + role + " ", " "));
3121 dojo.attr(elem, "role", t);
3122 }else{
3123 elem.removeAttribute("role");
3124 }
3125 },
3126
3127 hasWaiState: function(/*Element*/ elem, /*String*/ state){
3128 // summary:
3129 // Determines if an element has a given state.
3130 // description:
3131 // Checks for an attribute called "aria-"+state.
3132 // returns:
3133 // true if elem has a value for the given state and
3134 // false if it does not.
3135
3136 return elem.hasAttribute ? elem.hasAttribute("aria-"+state) : !!elem.getAttribute("aria-"+state);
3137 },
3138
3139 getWaiState: function(/*Element*/ elem, /*String*/ state){
3140 // summary:
3141 // Gets the value of a state on an element.
3142 // description:
3143 // Checks for an attribute called "aria-"+state.
3144 // returns:
3145 // The value of the requested state on elem
3146 // or an empty string if elem has no value for state.
3147
3148 return elem.getAttribute("aria-"+state) || "";
3149 },
3150
3151 setWaiState: function(/*Element*/ elem, /*String*/ state, /*String*/ value){
3152 // summary:
3153 // Sets a state on an element.
3154 // description:
3155 // Sets an attribute called "aria-"+state.
3156
3157 elem.setAttribute("aria-"+state, value);
3158 },
3159
3160 removeWaiState: function(/*Element*/ elem, /*String*/ state){
3161 // summary:
3162 // Removes a state from an element.
3163 // description:
3164 // Sets an attribute called "aria-"+state.
3165
3166 elem.removeAttribute("aria-"+state);
3167 }
3168});
3169
3170}
3171
3172if(!dojo._hasResource["dijit._base"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3173dojo._hasResource["dijit._base"] = true;
3174dojo.provide("dijit._base");
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
a089699c 3186
81bea17a 3187}
a089699c 3188
81bea17a
AD
3189if(!dojo._hasResource["dojo.Stateful"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3190dojo._hasResource["dojo.Stateful"] = true;
3191dojo.provide("dojo.Stateful");
a089699c
AD
3192
3193
81bea17a
AD
3194dojo.declare("dojo.Stateful", null, {
3195 // summary:
3196 // Base class for objects that provide named properties with optional getter/setter
3197 // control and the ability to watch for property changes
3198 // example:
3199 // | var obj = new dojo.Stateful();
3200 // | obj.watch("foo", function(){
3201 // | console.log("foo changed to " + this.get("foo"));
3202 // | });
3203 // | obj.set("foo","bar");
3204 postscript: function(mixin){
3205 if(mixin){
3206 dojo.mixin(this, mixin);
a089699c 3207 }
81bea17a
AD
3208 },
3209
3210 get: function(/*String*/name){
3211 // summary:
3212 // Get a property on a Stateful instance.
3213 // name:
3214 // The property to get.
3215 // description:
3216 // Get a named property on a Stateful object. The property may
3217 // potentially be retrieved via a getter method in subclasses. In the base class
3218 // this just retrieves the object's property.
3219 // For example:
3220 // | stateful = new dojo.Stateful({foo: 3});
3221 // | stateful.get("foo") // returns 3
3222 // | stateful.foo // returns 3
3223
3224 return this[name];
3225 },
3226 set: function(/*String*/name, /*Object*/value){
3227 // summary:
3228 // Set a property on a Stateful instance
3229 // name:
3230 // The property to set.
3231 // value:
3232 // The value to set in the property.
3233 // description:
3234 // Sets named properties on a stateful object and notifies any watchers of
3235 // the property. A programmatic setter may be defined in subclasses.
3236 // For example:
3237 // | stateful = new dojo.Stateful();
3238 // | stateful.watch(function(name, oldValue, value){
3239 // | // this will be called on the set below
3240 // | }
3241 // | stateful.set(foo, 5);
3242 //
3243 // set() may also be called with a hash of name/value pairs, ex:
3244 // | myObj.set({
3245 // | foo: "Howdy",
3246 // | bar: 3
3247 // | })
3248 // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
3249 if(typeof name === "object"){
3250 for(var x in name){
3251 this.set(x, name[x]);
3252 }
3253 return this;
3254 }
3255 var oldValue = this[name];
3256 this[name] = value;
3257 if(this._watchCallbacks){
3258 this._watchCallbacks(name, oldValue, value);
3259 }
3260 return this;
3261 },
3262 watch: function(/*String?*/name, /*Function*/callback){
3263 // summary:
3264 // Watches a property for changes
3265 // name:
3266 // Indicates the property to watch. This is optional (the callback may be the
3267 // only parameter), and if omitted, all the properties will be watched
3268 // returns:
3269 // An object handle for the watch. The unwatch method of this object
3270 // can be used to discontinue watching this property:
3271 // | var watchHandle = obj.watch("foo", callback);
3272 // | watchHandle.unwatch(); // callback won't be called now
3273 // callback:
3274 // The function to execute when the property changes. This will be called after
3275 // the property has been changed. The callback will be called with the |this|
3276 // set to the instance, the first argument as the name of the property, the
3277 // second argument as the old value and the third argument as the new value.
3278
3279 var callbacks = this._watchCallbacks;
3280 if(!callbacks){
3281 var self = this;
3282 callbacks = this._watchCallbacks = function(name, oldValue, value, ignoreCatchall){
3283 var notify = function(propertyCallbacks){
3284 if(propertyCallbacks){
3285 propertyCallbacks = propertyCallbacks.slice();
3286 for(var i = 0, l = propertyCallbacks.length; i < l; i++){
3287 try{
3288 propertyCallbacks[i].call(self, name, oldValue, value);
3289 }catch(e){
3290 console.error(e);
3291 }
3292 }
3293 }
3294 };
3295 notify(callbacks['_' + name]);
3296 if(!ignoreCatchall){
3297 notify(callbacks["*"]); // the catch-all
3298 }
3299 }; // we use a function instead of an object so it will be ignored by JSON conversion
3300 }
3301 if(!callback && typeof name === "function"){
3302 callback = name;
3303 name = "*";
3304 }else{
3305 // prepend with dash to prevent name conflicts with function (like "name" property)
3306 name = '_' + name;
3307 }
3308 var propertyCallbacks = callbacks[name];
3309 if(typeof propertyCallbacks !== "object"){
3310 propertyCallbacks = callbacks[name] = [];
3311 }
3312 propertyCallbacks.push(callback);
3313 return {
3314 unwatch: function(){
3315 propertyCallbacks.splice(dojo.indexOf(propertyCallbacks, callback), 1);
3316 }
a089699c 3317 };
81bea17a
AD
3318 }
3319
3320});
3321
a089699c
AD
3322}
3323
81bea17a
AD
3324if(!dojo._hasResource["dijit._WidgetBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
3325dojo._hasResource["dijit._WidgetBase"] = true;
3326dojo.provide("dijit._WidgetBase");
3327
3328
a089699c 3329
a089699c 3330
81bea17a
AD
3331(function(){
3332
3333dojo.declare("dijit._WidgetBase", dojo.Stateful, {
a089699c 3334 // summary:
81bea17a
AD
3335 // Future base class for all Dijit widgets.
3336 // _Widget extends this class adding support for various features needed by desktop.
a089699c
AD
3337
3338 // id: [const] String
3339 // A unique, opaque ID string that can be assigned by users or by the
3340 // system. If the developer passes an ID which is known not to be
3341 // unique, the specified ID is ignored and the system-generated ID is
3342 // used instead.
3343 id: "",
3344
3345 // lang: [const] String
3346 // Rarely used. Overrides the default Dojo locale used to render this widget,
3347 // as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute.
3348 // Value must be among the list of locales specified during by the Dojo bootstrap,
3349 // formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us).
3350 lang: "",
3351
3352 // dir: [const] String
3353 // Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir)
3354 // attribute. Either left-to-right "ltr" or right-to-left "rtl". If undefined, widgets renders in page's
3355 // default direction.
3356 dir: "",
3357
3358 // class: String
3359 // HTML class attribute
3360 "class": "",
3361
3362 // style: String||Object
3363 // HTML style attributes as cssText string or name/value hash
3364 style: "",
3365
3366 // title: String
3367 // HTML title attribute.
3368 //
3369 // For form widgets this specifies a tooltip to display when hovering over
3370 // the widget (just like the native HTML title attribute).
3371 //
3372 // For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer,
3373 // etc., it's used to specify the tab label, accordion pane title, etc.
3374 title: "",
3375
3376 // tooltip: String
3377 // When this widget's title attribute is used to for a tab label, accordion pane title, etc.,
3378 // this specifies the tooltip to appear when the mouse is hovered over that text.
3379 tooltip: "",
3380
3381 // baseClass: [protected] String
3382 // Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate
3383 // widget state.
3384 baseClass: "",
3385
3386 // srcNodeRef: [readonly] DomNode
3387 // pointer to original DOM node
3388 srcNodeRef: null,
3389
3390 // domNode: [readonly] DomNode
3391 // This is our visible representation of the widget! Other DOM
3392 // Nodes may by assigned to other properties, usually through the
3393 // template system's dojoAttachPoint syntax, but the domNode
3394 // property is the canonical "top level" node in widget UI.
3395 domNode: null,
3396
3397 // containerNode: [readonly] DomNode
3398 // Designates where children of the source DOM node will be placed.
3399 // "Children" in this case refers to both DOM nodes and widgets.
3400 // For example, for myWidget:
3401 //
3402 // | <div dojoType=myWidget>
3403 // | <b> here's a plain DOM node
3404 // | <span dojoType=subWidget>and a widget</span>
3405 // | <i> and another plain DOM node </i>
3406 // | </div>
3407 //
3408 // containerNode would point to:
3409 //
3410 // | <b> here's a plain DOM node
3411 // | <span dojoType=subWidget>and a widget</span>
3412 // | <i> and another plain DOM node </i>
3413 //
3414 // In templated widgets, "containerNode" is set via a
3415 // dojoAttachPoint assignment.
3416 //
3417 // containerNode must be defined for any widget that accepts innerHTML
3418 // (like ContentPane or BorderContainer or even Button), and conversely
3419 // is null for widgets that don't, like TextBox.
3420 containerNode: null,
3421
3422/*=====
3423 // _started: Boolean
3424 // startup() has completed.
3425 _started: false,
3426=====*/
3427
3428 // attributeMap: [protected] Object
3429 // attributeMap sets up a "binding" between attributes (aka properties)
3430 // of the widget and the widget's DOM.
3431 // Changes to widget attributes listed in attributeMap will be
3432 // reflected into the DOM.
3433 //
81bea17a 3434 // For example, calling set('title', 'hello')
a089699c
AD
3435 // on a TitlePane will automatically cause the TitlePane's DOM to update
3436 // with the new title.
3437 //
3438 // attributeMap is a hash where the key is an attribute of the widget,
3439 // and the value reflects a binding to a:
3440 //
3441 // - DOM node attribute
3442 // | focus: {node: "focusNode", type: "attribute"}
3443 // Maps this.focus to this.focusNode.focus
3444 //
3445 // - DOM node innerHTML
3446 // | title: { node: "titleNode", type: "innerHTML" }
3447 // Maps this.title to this.titleNode.innerHTML
3448 //
3449 // - DOM node innerText
3450 // | title: { node: "titleNode", type: "innerText" }
3451 // Maps this.title to this.titleNode.innerText
3452 //
3453 // - DOM node CSS class
3454 // | myClass: { node: "domNode", type: "class" }
3455 // Maps this.myClass to this.domNode.className
3456 //
3457 // If the value is an array, then each element in the array matches one of the
3458 // formats of the above list.
3459 //
3460 // There are also some shorthands for backwards compatibility:
3461 // - string --> { node: string, type: "attribute" }, for example:
3462 // | "focusNode" ---> { node: "focusNode", type: "attribute" }
3463 // - "" --> { node: "domNode", type: "attribute" }
3464 attributeMap: {id:"", dir:"", lang:"", "class":"", style:"", title:""},
3465
81bea17a
AD
3466 // _blankGif: [protected] String
3467 // Path to a blank 1x1 image.
3468 // Used by <img> nodes in templates that really get their image via CSS background-image.
3469 _blankGif: (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")).toString(),
a089699c 3470
81bea17a
AD
3471 //////////// INITIALIZATION METHODS ///////////////////////////////////////
3472
3473 postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
a089699c
AD
3474 // summary:
3475 // Kicks off widget instantiation. See create() for details.
3476 // tags:
3477 // private
3478 this.create(params, srcNodeRef);
3479 },
3480
3481 create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){
3482 // summary:
3483 // Kick off the life-cycle of a widget
3484 // params:
3485 // Hash of initialization parameters for widget, including
3486 // scalar values (like title, duration etc.) and functions,
3487 // typically callbacks like onClick.
3488 // srcNodeRef:
3489 // If a srcNodeRef (DOM node) is specified:
3490 // - use srcNodeRef.innerHTML as my contents
3491 // - if this is a behavioral widget then apply behavior
3492 // to that srcNodeRef
3493 // - otherwise, replace srcNodeRef with my generated DOM
3494 // tree
3495 // description:
3496 // Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate,
3497 // etc.), some of which of you'll want to override. See http://docs.dojocampus.org/dijit/_Widget
3498 // for a discussion of the widget creation lifecycle.
3499 //
3500 // Of course, adventurous developers could override create entirely, but this should
3501 // only be done as a last resort.
3502 // tags:
3503 // private
3504
3505 // store pointer to original DOM tree
3506 this.srcNodeRef = dojo.byId(srcNodeRef);
3507
3508 // For garbage collection. An array of handles returned by Widget.connect()
3509 // Each handle returned from Widget.connect() is an array of handles from dojo.connect()
3510 this._connects = [];
3511
3512 // For garbage collection. An array of handles returned by Widget.subscribe()
3513 // The handle returned from Widget.subscribe() is the handle returned from dojo.subscribe()
3514 this._subscribes = [];
3515
81bea17a 3516 // mix in our passed parameters
a089699c
AD
3517 if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; }
3518 if(params){
3519 this.params = params;
81bea17a 3520 dojo._mixin(this, params);
a089699c
AD
3521 }
3522 this.postMixInProperties();
3523
3524 // generate an id for the widget if one wasn't specified
3525 // (be sure to do this before buildRendering() because that function might
3526 // expect the id to be there.)
3527 if(!this.id){
3528 this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
3529 }
3530 dijit.registry.add(this);
3531
3532 this.buildRendering();
3533
3534 if(this.domNode){
3535 // Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
81bea17a 3536 // Also calls custom setters for all attributes with custom setters.
a089699c
AD
3537 this._applyAttributes();
3538
81bea17a
AD
3539 // If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree.
3540 // For 2.0, move this after postCreate(). postCreate() shouldn't depend on the
3541 // widget being attached to the DOM since it isn't when a widget is created programmatically like
3542 // new MyWidget({}). See #11635.
a089699c 3543 var source = this.srcNodeRef;
81bea17a 3544 if(source && source.parentNode && this.domNode !== source){
a089699c
AD
3545 source.parentNode.replaceChild(this.domNode, source);
3546 }
a089699c
AD
3547 }
3548
3549 if(this.domNode){
81bea17a
AD
3550 // Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId",
3551 // assuming that dojo._scopeName even exists in 2.0
a089699c
AD
3552 this.domNode.setAttribute("widgetId", this.id);
3553 }
3554 this.postCreate();
3555
3556 // If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC.
3557 if(this.srcNodeRef && !this.srcNodeRef.parentNode){
3558 delete this.srcNodeRef;
3559 }
3560
3561 this._created = true;
3562 },
3563
3564 _applyAttributes: function(){
3565 // summary:
3566 // Step during widget creation to copy all widget attributes to the
3567 // DOM as per attributeMap and _setXXXAttr functions.
3568 // description:
3569 // Skips over blank/false attribute values, unless they were explicitly specified
3570 // as parameters to the widget, since those are the default anyway,
3571 // and setting tabIndex="" is different than not setting tabIndex at all.
3572 //
3573 // It processes the attributes in the attribute map first, and then
3574 // it goes through and processes the attributes for the _setXXXAttr
3575 // functions that have been specified
3576 // tags:
3577 // private
3578 var condAttrApply = function(attr, scope){
3579 if((scope.params && attr in scope.params) || scope[attr]){
3580 scope.set(attr, scope[attr]);
3581 }
3582 };
3583
3584 // Do the attributes in attributeMap
3585 for(var attr in this.attributeMap){
3586 condAttrApply(attr, this);
3587 }
3588
3589 // And also any attributes with custom setters
81bea17a 3590 dojo.forEach(this._getSetterAttributes(), function(a){
a089699c
AD
3591 if(!(a in this.attributeMap)){
3592 condAttrApply(a, this);
3593 }
3594 }, this);
3595 },
3596
81bea17a
AD
3597 _getSetterAttributes: function(){
3598 // summary:
3599 // Returns list of attributes with custom setters for this widget
3600 var ctor = this.constructor;
3601 if(!ctor._setterAttrs){
3602 var r = (ctor._setterAttrs = []),
3603 attrs,
3604 proto = ctor.prototype;
3605 for(var fxName in proto){
3606 if(dojo.isFunction(proto[fxName]) && (attrs = fxName.match(/^_set([a-zA-Z]*)Attr$/)) && attrs[1]){
3607 r.push(attrs[1].charAt(0).toLowerCase() + attrs[1].substr(1));
3608 }
3609 }
3610 }
3611 return ctor._setterAttrs; // String[]
3612 },
3613
a089699c
AD
3614 postMixInProperties: function(){
3615 // summary:
3616 // Called after the parameters to the widget have been read-in,
3617 // but before the widget template is instantiated. Especially
3618 // useful to set properties that are referenced in the widget
3619 // template.
3620 // tags:
3621 // protected
3622 },
3623
3624 buildRendering: function(){
3625 // summary:
3626 // Construct the UI for this widget, setting this.domNode
3627 // description:
3628 // Most widgets will mixin `dijit._Templated`, which implements this
3629 // method.
3630 // tags:
3631 // protected
81bea17a
AD
3632
3633 if(!this.domNode){
3634 // Create root node if it wasn't created by _Templated
3635 this.domNode = this.srcNodeRef || dojo.create('div');
3636 }
3637
3638 // baseClass is a single class name or occasionally a space-separated list of names.
3639 // Add those classes to the DOMNode. If RTL mode then also add with Rtl suffix.
3640 // TODO: make baseClass custom setter
3641 if(this.baseClass){
3642 var classes = this.baseClass.split(" ");
3643 if(!this.isLeftToRight()){
3644 classes = classes.concat( dojo.map(classes, function(name){ return name+"Rtl"; }));
3645 }
3646 dojo.addClass(this.domNode, classes);
3647 }
a089699c
AD
3648 },
3649
3650 postCreate: function(){
3651 // summary:
3652 // Processing after the DOM fragment is created
3653 // description:
3654 // Called after the DOM fragment has been created, but not necessarily
3655 // added to the document. Do not include any operations which rely on
3656 // node dimensions or placement.
3657 // tags:
3658 // protected
a089699c
AD
3659 },
3660
3661 startup: function(){
3662 // summary:
3663 // Processing after the DOM fragment is added to the document
3664 // description:
3665 // Called after a widget and its children have been created and added to the page,
3666 // and all related widgets have finished their create() cycle, up through postCreate().
3667 // This is useful for composite widgets that need to control or layout sub-widgets.
3668 // Many layout widgets can use this as a wiring phase.
3669 this._started = true;
3670 },
3671
3672 //////////// DESTROY FUNCTIONS ////////////////////////////////
3673
3674 destroyRecursive: function(/*Boolean?*/ preserveDom){
3675 // summary:
3676 // Destroy this widget and its descendants
3677 // description:
3678 // This is the generic "destructor" function that all widget users
3679 // should call to cleanly discard with a widget. Once a widget is
3680 // destroyed, it is removed from the manager object.
3681 // preserveDom:
3682 // If true, this method will leave the original DOM structure
3683 // alone of descendant Widgets. Note: This will NOT work with
3684 // dijit._Templated widgets.
3685
3686 this._beingDestroyed = true;
3687 this.destroyDescendants(preserveDom);
3688 this.destroy(preserveDom);
3689 },
3690
3691 destroy: function(/*Boolean*/ preserveDom){
3692 // summary:
3693 // Destroy this widget, but not its descendants.
3694 // This method will, however, destroy internal widgets such as those used within a template.
3695 // preserveDom: Boolean
3696 // If true, this method will leave the original DOM structure alone.
3697 // Note: This will not yet work with _Templated widgets
3698
3699 this._beingDestroyed = true;
3700 this.uninitialize();
3701 var d = dojo,
3702 dfe = d.forEach,
3703 dun = d.unsubscribe;
3704 dfe(this._connects, function(array){
3705 dfe(array, d.disconnect);
3706 });
3707 dfe(this._subscribes, function(handle){
3708 dun(handle);
3709 });
3710
3711 // destroy widgets created as part of template, etc.
3712 dfe(this._supportingWidgets || [], function(w){
3713 if(w.destroyRecursive){
3714 w.destroyRecursive();
3715 }else if(w.destroy){
3716 w.destroy();
3717 }
3718 });
3719
3720 this.destroyRendering(preserveDom);
3721 dijit.registry.remove(this.id);
3722 this._destroyed = true;
3723 },
3724
3725 destroyRendering: function(/*Boolean?*/ preserveDom){
3726 // summary:
3727 // Destroys the DOM nodes associated with this widget
3728 // preserveDom:
3729 // If true, this method will leave the original DOM structure alone
3730 // during tear-down. Note: this will not work with _Templated
3731 // widgets yet.
3732 // tags:
3733 // protected
3734
3735 if(this.bgIframe){
3736 this.bgIframe.destroy(preserveDom);
3737 delete this.bgIframe;
3738 }
3739
3740 if(this.domNode){
3741 if(preserveDom){
3742 dojo.removeAttr(this.domNode, "widgetId");
3743 }else{
3744 dojo.destroy(this.domNode);
3745 }
3746 delete this.domNode;
3747 }
3748
3749 if(this.srcNodeRef){
3750 if(!preserveDom){
3751 dojo.destroy(this.srcNodeRef);
3752 }
3753 delete this.srcNodeRef;
3754 }
3755 },
3756
3757 destroyDescendants: function(/*Boolean?*/ preserveDom){
3758 // summary:
3759 // Recursively destroy the children of this widget and their
3760 // descendants.
3761 // preserveDom:
3762 // If true, the preserveDom attribute is passed to all descendant
3763 // widget's .destroy() method. Not for use with _Templated
3764 // widgets.
3765
3766 // get all direct descendants and destroy them recursively
3767 dojo.forEach(this.getChildren(), function(widget){
3768 if(widget.destroyRecursive){
3769 widget.destroyRecursive(preserveDom);
3770 }
3771 });
3772 },
3773
a089699c
AD
3774 uninitialize: function(){
3775 // summary:
3776 // Stub function. Override to implement custom widget tear-down
3777 // behavior.
3778 // tags:
3779 // protected
3780 return false;
3781 },
3782
81bea17a 3783 ////////////////// GET/SET, CUSTOM SETTERS, ETC. ///////////////////
a089699c
AD
3784
3785 _setClassAttr: function(/*String*/ value){
3786 // summary:
3787 // Custom setter for the CSS "class" attribute
3788 // tags:
3789 // protected
3790 var mapNode = this[this.attributeMap["class"] || 'domNode'];
81bea17a
AD
3791 dojo.replaceClass(mapNode, value, this["class"]);
3792 this._set("class", value);
a089699c
AD
3793 },
3794
3795 _setStyleAttr: function(/*String||Object*/ value){
3796 // summary:
81bea17a 3797 // Sets the style attribute of the widget according to value,
a089699c
AD
3798 // which is either a hash like {height: "5px", width: "3px"}
3799 // or a plain string
3800 // description:
3801 // Determines which node to set the style on based on style setting
3802 // in attributeMap.
3803 // tags:
3804 // protected
3805
3806 var mapNode = this[this.attributeMap.style || 'domNode'];
3807
3808 // Note: technically we should revert any style setting made in a previous call
3809 // to his method, but that's difficult to keep track of.
3810
3811 if(dojo.isObject(value)){
3812 dojo.style(mapNode, value);
3813 }else{
3814 if(mapNode.style.cssText){
3815 mapNode.style.cssText += "; " + value;
3816 }else{
3817 mapNode.style.cssText = value;
3818 }
3819 }
3820
81bea17a 3821 this._set("style", value);
a089699c
AD
3822 },
3823
3824 _attrToDom: function(/*String*/ attr, /*String*/ value){
3825 // summary:
3826 // Reflect a widget attribute (title, tabIndex, duration etc.) to
3827 // the widget DOM, as specified in attributeMap.
a089699c
AD
3828 // Note some attributes like "type"
3829 // cannot be processed this way as they are not mutable.
3830 //
3831 // tags:
3832 // private
3833
3834 var commands = this.attributeMap[attr];
3835 dojo.forEach(dojo.isArray(commands) ? commands : [commands], function(command){
3836
3837 // Get target node and what we are doing to that node
3838 var mapNode = this[command.node || command || "domNode"]; // DOM node
3839 var type = command.type || "attribute"; // class, innerHTML, innerText, or attribute
3840
3841 switch(type){
3842 case "attribute":
3843 if(dojo.isFunction(value)){ // functions execute in the context of the widget
3844 value = dojo.hitch(this, value);
3845 }
3846
3847 // Get the name of the DOM node attribute; usually it's the same
3848 // as the name of the attribute in the widget (attr), but can be overridden.
3849 // Also maps handler names to lowercase, like onSubmit --> onsubmit
3850 var attrName = command.attribute ? command.attribute :
3851 (/^on[A-Z][a-zA-Z]*$/.test(attr) ? attr.toLowerCase() : attr);
3852
3853 dojo.attr(mapNode, attrName, value);
3854 break;
3855 case "innerText":
3856 mapNode.innerHTML = "";
3857 mapNode.appendChild(dojo.doc.createTextNode(value));
3858 break;
3859 case "innerHTML":
3860 mapNode.innerHTML = value;
3861 break;
3862 case "class":
81bea17a 3863 dojo.replaceClass(mapNode, value, this[attr]);
a089699c
AD
3864 break;
3865 }
3866 }, this);
a089699c
AD
3867 },
3868
81bea17a 3869 get: function(name){
a089699c
AD
3870 // summary:
3871 // Get a property from a widget.
3872 // name:
3873 // The property to get.
3874 // description:
3875 // Get a named property from a widget. The property may
3876 // potentially be retrieved via a getter method. If no getter is defined, this
81bea17a 3877 // just retrieves the object's property.
a089699c
AD
3878 // For example, if the widget has a properties "foo"
3879 // and "bar" and a method named "_getFooAttr", calling:
3880 // | myWidget.get("foo");
3881 // would be equivalent to writing:
3882 // | widget._getFooAttr();
3883 // and:
3884 // | myWidget.get("bar");
3885 // would be equivalent to writing:
3886 // | widget.bar;
3887 var names = this._getAttrNames(name);
3888 return this[names.g] ? this[names.g]() : this[name];
3889 },
3890
3891 set: function(name, value){
3892 // summary:
3893 // Set a property on a widget
3894 // name:
81bea17a 3895 // The property to set.
a089699c
AD
3896 // value:
3897 // The value to set in the property.
3898 // description:
81bea17a 3899 // Sets named properties on a widget which may potentially be handled by a
a089699c
AD
3900 // setter in the widget.
3901 // For example, if the widget has a properties "foo"
3902 // and "bar" and a method named "_setFooAttr", calling:
3903 // | myWidget.set("foo", "Howdy!");
3904 // would be equivalent to writing:
3905 // | widget._setFooAttr("Howdy!");
3906 // and:
3907 // | myWidget.set("bar", 3);
3908 // would be equivalent to writing:
3909 // | widget.bar = 3;
3910 //
3911 // set() may also be called with a hash of name/value pairs, ex:
3912 // | myWidget.set({
3913 // | foo: "Howdy",
3914 // | bar: 3
3915 // | })
3916 // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
3917
3918 if(typeof name === "object"){
3919 for(var x in name){
81bea17a 3920 this.set(x, name[x]);
a089699c
AD
3921 }
3922 return this;
3923 }
3924 var names = this._getAttrNames(name);
3925 if(this[names.s]){
3926 // use the explicit setter
3927 var result = this[names.s].apply(this, Array.prototype.slice.call(arguments, 1));
3928 }else{
3929 // if param is specified as DOM node attribute, copy it
3930 if(name in this.attributeMap){
3931 this._attrToDom(name, value);
3932 }
81bea17a 3933 this._set(name, value);
a089699c
AD
3934 }
3935 return result || this;
3936 },
3937
3938 _attrPairNames: {}, // shared between all widgets
3939 _getAttrNames: function(name){
3940 // summary:
3941 // Helper function for get() and set().
3942 // Caches attribute name values so we don't do the string ops every time.
3943 // tags:
3944 // private
3945
3946 var apn = this._attrPairNames;
3947 if(apn[name]){ return apn[name]; }
3948 var uc = name.charAt(0).toUpperCase() + name.substr(1);
3949 return (apn[name] = {
3950 n: name+"Node",
3951 s: "_set"+uc+"Attr",
3952 g: "_get"+uc+"Attr"
3953 });
3954 },
3955
81bea17a
AD
3956 _set: function(/*String*/ name, /*anything*/ value){
3957 // summary:
3958 // Helper function to set new value for specified attribute, and call handlers
3959 // registered with watch() if the value has changed.
3960 var oldValue = this[name];
3961 this[name] = value;
3962 if(this._watchCallbacks && this._created && value !== oldValue){
3963 this._watchCallbacks(name, oldValue, value);
3964 }
3965 },
3966
a089699c
AD
3967 toString: function(){
3968 // summary:
3969 // Returns a string that represents the widget
3970 // description:
3971 // When a widget is cast to a string, this method will be used to generate the
3972 // output. Currently, it does not implement any sort of reversible
3973 // serialization.
3974 return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String
3975 },
3976
3977 getDescendants: function(){
3978 // summary:
3979 // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
3980 // This method should generally be avoided as it returns widgets declared in templates, which are
3981 // supposed to be internal/hidden, but it's left here for back-compat reasons.
3982
3983 return this.containerNode ? dojo.query('[widgetId]', this.containerNode).map(dijit.byNode) : []; // dijit._Widget[]
3984 },
3985
3986 getChildren: function(){
3987 // summary:
3988 // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode.
3989 // Does not return nested widgets, nor widgets that are part of this widget's template.
3990 return this.containerNode ? dijit.findWidgets(this.containerNode) : []; // dijit._Widget[]
3991 },
3992
a089699c
AD
3993 connect: function(
3994 /*Object|null*/ obj,
3995 /*String|Function*/ event,
3996 /*String|Function*/ method){
3997 // summary:
3998 // Connects specified obj/event to specified method of this object
3999 // and registers for disconnect() on widget destroy.
4000 // description:
4001 // Provide widget-specific analog to dojo.connect, except with the
4002 // implicit use of this widget as the target object.
81bea17a
AD
4003 // Events connected with `this.connect` are disconnected upon
4004 // destruction.
a089699c
AD
4005 // returns:
4006 // A handle that can be passed to `disconnect` in order to disconnect before
4007 // the widget is destroyed.
4008 // example:
4009 // | var btn = new dijit.form.Button();
4010 // | // when foo.bar() is called, call the listener we're going to
4011 // | // provide in the scope of btn
4012 // | btn.connect(foo, "bar", function(){
4013 // | console.debug(this.toString());
4014 // | });
4015 // tags:
4016 // protected
4017
81bea17a 4018 var handles = [dojo._connect(obj, event, this, method)];
a089699c
AD
4019 this._connects.push(handles);
4020 return handles; // _Widget.Handle
4021 },
4022
4023 disconnect: function(/* _Widget.Handle */ handles){
4024 // summary:
4025 // Disconnects handle created by `connect`.
4026 // Also removes handle from this widget's list of connects.
4027 // tags:
4028 // protected
4029 for(var i=0; i<this._connects.length; i++){
4030 if(this._connects[i] == handles){
4031 dojo.forEach(handles, dojo.disconnect);
4032 this._connects.splice(i, 1);
4033 return;
4034 }
4035 }
4036 },
4037
4038 subscribe: function(
4039 /*String*/ topic,
4040 /*String|Function*/ method){
4041 // summary:
4042 // Subscribes to the specified topic and calls the specified method
4043 // of this object and registers for unsubscribe() on widget destroy.
4044 // description:
4045 // Provide widget-specific analog to dojo.subscribe, except with the
4046 // implicit use of this widget as the target object.
4047 // example:
4048 // | var btn = new dijit.form.Button();
4049 // | // when /my/topic is published, this button changes its label to
4050 // | // be the parameter of the topic.
4051 // | btn.subscribe("/my/topic", function(v){
4052 // | this.set("label", v);
4053 // | });
81bea17a 4054 var handle = dojo.subscribe(topic, this, method);
a089699c
AD
4055
4056 // return handles for Any widget that may need them
4057 this._subscribes.push(handle);
4058 return handle;
4059 },
4060
4061 unsubscribe: function(/*Object*/ handle){
4062 // summary:
4063 // Unsubscribes handle created by this.subscribe.
4064 // Also removes handle from this widget's list of subscriptions
4065 for(var i=0; i<this._subscribes.length; i++){
4066 if(this._subscribes[i] == handle){
4067 dojo.unsubscribe(handle);
4068 this._subscribes.splice(i, 1);
4069 return;
4070 }
4071 }
4072 },
4073
4074 isLeftToRight: function(){
4075 // summary:
4076 // Return this widget's explicit or implicit orientation (true for LTR, false for RTL)
4077 // tags:
4078 // protected
4079 return this.dir ? (this.dir == "ltr") : dojo._isBodyLtr(); //Boolean
4080 },
4081
a089699c
AD
4082 placeAt: function(/* String|DomNode|_Widget */reference, /* String?|Int? */position){
4083 // summary:
4084 // Place this widget's domNode reference somewhere in the DOM based
4085 // on standard dojo.place conventions, or passing a Widget reference that
4086 // contains and addChild member.
4087 //
4088 // description:
4089 // A convenience function provided in all _Widgets, providing a simple
4090 // shorthand mechanism to put an existing (or newly created) Widget
4091 // somewhere in the dom, and allow chaining.
4092 //
4093 // reference:
4094 // The String id of a domNode, a domNode reference, or a reference to a Widget posessing
4095 // an addChild method.
4096 //
4097 // position:
4098 // If passed a string or domNode reference, the position argument
4099 // accepts a string just as dojo.place does, one of: "first", "last",
4100 // "before", or "after".
4101 //
4102 // If passed a _Widget reference, and that widget reference has an ".addChild" method,
4103 // it will be called passing this widget instance into that method, supplying the optional
4104 // position index passed.
4105 //
4106 // returns:
4107 // dijit._Widget
4108 // Provides a useful return of the newly created dijit._Widget instance so you
4109 // can "chain" this function by instantiating, placing, then saving the return value
4110 // to a variable.
4111 //
4112 // example:
4113 // | // create a Button with no srcNodeRef, and place it in the body:
4114 // | var button = new dijit.form.Button({ label:"click" }).placeAt(dojo.body());
4115 // | // now, 'button' is still the widget reference to the newly created button
4116 // | dojo.connect(button, "onClick", function(e){ console.log('click'); });
4117 //
4118 // example:
4119 // | // create a button out of a node with id="src" and append it to id="wrapper":
4120 // | var button = new dijit.form.Button({},"src").placeAt("wrapper");
4121 //
4122 // example:
4123 // | // place a new button as the first element of some div
4124 // | var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first");
4125 //
4126 // example:
4127 // | // create a contentpane and add it to a TabContainer
4128 // | var tc = dijit.byId("myTabs");
4129 // | new dijit.layout.ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc)
4130
4131 if(reference.declaredClass && reference.addChild){
4132 reference.addChild(this, position);
4133 }else{
4134 dojo.place(this.domNode, reference, position);
4135 }
4136 return this;
a089699c
AD
4137 }
4138});
4139
4140})();
4141
4142}
4143
81bea17a
AD
4144if(!dojo._hasResource["dijit._Widget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4145dojo._hasResource["dijit._Widget"] = true;
4146dojo.provide("dijit._Widget");
a089699c 4147
a089699c 4148
a089699c 4149
a089699c 4150
a089699c 4151
81bea17a 4152////////////////// DEFERRED CONNECTS ///////////////////
a089699c 4153
81bea17a
AD
4154// This code is to assist deferring dojo.connect() calls in widgets (connecting to events on the widgets'
4155// DOM nodes) until someone actually needs to monitor that event.
4156dojo.connect(dojo, "_connect",
4157 function(/*dijit._Widget*/ widget, /*String*/ event){
4158 if(widget && dojo.isFunction(widget._onConnect)){
4159 widget._onConnect(event);
4160 }
4161 });
a089699c 4162
81bea17a 4163dijit._connectOnUseEventHandler = function(/*Event*/ event){};
a089699c 4164
81bea17a
AD
4165////////////////// ONDIJITCLICK SUPPORT ///////////////////
4166
4167// Keep track of where the last keydown event was, to help avoid generating
4168// spurious ondijitclick events when:
4169// 1. focus is on a <button> or <a>
4170// 2. user presses then releases the ENTER key
4171// 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler
4172// 4. onkeyup event fires, causing the ondijitclick handler to fire
4173dijit._lastKeyDownNode = null;
4174if(dojo.isIE){
4175 (function(){
4176 var keydownCallback = function(evt){
4177 dijit._lastKeyDownNode = evt.srcElement;
4178 };
4179 dojo.doc.attachEvent('onkeydown', keydownCallback);
4180 dojo.addOnWindowUnload(function(){
4181 dojo.doc.detachEvent('onkeydown', keydownCallback);
4182 });
4183 })();
4184}else{
4185 dojo.doc.addEventListener('keydown', function(evt){
4186 dijit._lastKeyDownNode = evt.target;
4187 }, true);
4188}
4189
4190(function(){
4191
4192dojo.declare("dijit._Widget", dijit._WidgetBase, {
4193 // summary:
4194 // Base class for all Dijit widgets.
4195 //
4196 // Extends _WidgetBase, adding support for:
4197 // - deferred connections
4198 // A call like dojo.connect(myWidget, "onMouseMove", func)
4199 // will essentially do a dojo.connect(myWidget.domNode, "onMouseMove", func)
4200 // - ondijitclick
4201 // Support new dojoAttachEvent="ondijitclick: ..." that is triggered by a mouse click or a SPACE/ENTER keypress
4202 // - focus related functions
4203 // In particular, the onFocus()/onBlur() callbacks. Driven internally by
4204 // dijit/_base/focus.js.
4205 // - deprecated methods
4206 // - onShow(), onHide(), onClose()
4207 //
4208 // Also, by loading code in dijit/_base, turns on:
4209 // - browser sniffing (putting browser id like .dj_ie on <html> node)
4210 // - high contrast mode sniffing (add .dijit_a11y class to <body> if machine is in high contrast mode)
4211
4212
4213 ////////////////// DEFERRED CONNECTS ///////////////////
4214
4215 // _deferredConnects: [protected] Object
4216 // attributeMap addendum for event handlers that should be connected only on first use
4217 _deferredConnects: {
4218 onClick: "",
4219 onDblClick: "",
4220 onKeyDown: "",
4221 onKeyPress: "",
4222 onKeyUp: "",
4223 onMouseMove: "",
4224 onMouseDown: "",
4225 onMouseOut: "",
4226 onMouseOver: "",
4227 onMouseLeave: "",
4228 onMouseEnter: "",
4229 onMouseUp: ""
4230 },
4231
4232 onClick: dijit._connectOnUseEventHandler,
4233 /*=====
4234 onClick: function(event){
4235 // summary:
4236 // Connect to this function to receive notifications of mouse click events.
4237 // event:
4238 // mouse Event
4239 // tags:
4240 // callback
4241 },
4242 =====*/
4243 onDblClick: dijit._connectOnUseEventHandler,
4244 /*=====
4245 onDblClick: function(event){
4246 // summary:
4247 // Connect to this function to receive notifications of mouse double click events.
4248 // event:
4249 // mouse Event
4250 // tags:
4251 // callback
4252 },
4253 =====*/
4254 onKeyDown: dijit._connectOnUseEventHandler,
4255 /*=====
4256 onKeyDown: function(event){
4257 // summary:
4258 // Connect to this function to receive notifications of keys being pressed down.
4259 // event:
4260 // key Event
4261 // tags:
4262 // callback
4263 },
4264 =====*/
4265 onKeyPress: dijit._connectOnUseEventHandler,
4266 /*=====
4267 onKeyPress: function(event){
4268 // summary:
4269 // Connect to this function to receive notifications of printable keys being typed.
4270 // event:
4271 // key Event
4272 // tags:
4273 // callback
4274 },
4275 =====*/
4276 onKeyUp: dijit._connectOnUseEventHandler,
4277 /*=====
4278 onKeyUp: function(event){
4279 // summary:
4280 // Connect to this function to receive notifications of keys being released.
4281 // event:
4282 // key Event
4283 // tags:
4284 // callback
4285 },
4286 =====*/
4287 onMouseDown: dijit._connectOnUseEventHandler,
4288 /*=====
4289 onMouseDown: function(event){
4290 // summary:
4291 // Connect to this function to receive notifications of when the mouse button is pressed down.
4292 // event:
4293 // mouse Event
4294 // tags:
4295 // callback
4296 },
4297 =====*/
4298 onMouseMove: dijit._connectOnUseEventHandler,
4299 /*=====
4300 onMouseMove: function(event){
4301 // summary:
4302 // Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget.
4303 // event:
4304 // mouse Event
4305 // tags:
4306 // callback
4307 },
4308 =====*/
4309 onMouseOut: dijit._connectOnUseEventHandler,
4310 /*=====
4311 onMouseOut: function(event){
4312 // summary:
4313 // Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget.
4314 // event:
4315 // mouse Event
4316 // tags:
4317 // callback
4318 },
4319 =====*/
4320 onMouseOver: dijit._connectOnUseEventHandler,
4321 /*=====
4322 onMouseOver: function(event){
4323 // summary:
4324 // Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget.
4325 // event:
4326 // mouse Event
4327 // tags:
4328 // callback
4329 },
4330 =====*/
4331 onMouseLeave: dijit._connectOnUseEventHandler,
4332 /*=====
4333 onMouseLeave: function(event){
4334 // summary:
4335 // Connect to this function to receive notifications of when the mouse moves off of this widget.
4336 // event:
4337 // mouse Event
4338 // tags:
4339 // callback
4340 },
4341 =====*/
4342 onMouseEnter: dijit._connectOnUseEventHandler,
4343 /*=====
4344 onMouseEnter: function(event){
4345 // summary:
4346 // Connect to this function to receive notifications of when the mouse moves onto this widget.
4347 // event:
4348 // mouse Event
4349 // tags:
4350 // callback
4351 },
4352 =====*/
4353 onMouseUp: dijit._connectOnUseEventHandler,
4354 /*=====
4355 onMouseUp: function(event){
4356 // summary:
4357 // Connect to this function to receive notifications of when the mouse button is released.
4358 // event:
4359 // mouse Event
4360 // tags:
4361 // callback
4362 },
4363 =====*/
4364
4365 create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){
4366 // To avoid double-connects, remove entries from _deferredConnects
4367 // that have been setup manually by a subclass (ex, by dojoAttachEvent).
4368 // If a subclass has redefined a callback (ex: onClick) then assume it's being
4369 // connected to manually.
4370 this._deferredConnects = dojo.clone(this._deferredConnects);
4371 for(var attr in this.attributeMap){
4372 delete this._deferredConnects[attr]; // can't be in both attributeMap and _deferredConnects
4373 }
4374 for(attr in this._deferredConnects){
4375 if(this[attr] !== dijit._connectOnUseEventHandler){
4376 delete this._deferredConnects[attr]; // redefined, probably dojoAttachEvent exists
4377 }
4378 }
4379
4380 this.inherited(arguments);
4381
4382 if(this.domNode){
4383 // If the developer has specified a handler as a widget parameter
4384 // (ex: new Button({onClick: ...})
4385 // then naturally need to connect from DOM node to that handler immediately,
4386 for(attr in this.params){
4387 this._onConnect(attr);
4388 }
4389 }
4390 },
4391
4392 _onConnect: function(/*String*/ event){
4393 // summary:
4394 // Called when someone connects to one of my handlers.
4395 // "Turn on" that handler if it isn't active yet.
4396 //
4397 // This is also called for every single initialization parameter
4398 // so need to do nothing for parameters like "id".
4399 // tags:
4400 // private
4401 if(event in this._deferredConnects){
4402 var mapNode = this[this._deferredConnects[event] || 'domNode'];
4403 this.connect(mapNode, event.toLowerCase(), event);
4404 delete this._deferredConnects[event];
4405 }
4406 },
4407
4408 ////////////////// FOCUS RELATED ///////////////////
4409 // _onFocus() and _onBlur() are called by the focus manager
4410
4411 // focused: [readonly] Boolean
4412 // This widget or a widget it contains has focus, or is "active" because
4413 // it was recently clicked.
4414 focused: false,
4415
4416 isFocusable: function(){
4417 // summary:
4418 // Return true if this widget can currently be focused
4419 // and false if not
4420 return this.focus && (dojo.style(this.domNode, "display") != "none");
4421 },
4422
4423 onFocus: function(){
4424 // summary:
4425 // Called when the widget becomes "active" because
4426 // it or a widget inside of it either has focus, or has recently
4427 // been clicked.
4428 // tags:
4429 // callback
4430 },
4431
4432 onBlur: function(){
4433 // summary:
4434 // Called when the widget stops being "active" because
4435 // focus moved to something outside of it, or the user
4436 // clicked somewhere outside of it, or the widget was
4437 // hidden.
4438 // tags:
4439 // callback
4440 },
4441
4442 _onFocus: function(e){
4443 // summary:
4444 // This is where widgets do processing for when they are active,
4445 // such as changing CSS classes. See onFocus() for more details.
4446 // tags:
4447 // protected
4448 this.onFocus();
4449 },
4450
4451 _onBlur: function(){
4452 // summary:
4453 // This is where widgets do processing for when they stop being active,
4454 // such as changing CSS classes. See onBlur() for more details.
4455 // tags:
4456 // protected
4457 this.onBlur();
4458 },
4459
4460 ////////////////// DEPRECATED METHODS ///////////////////
4461
4462 setAttribute: function(/*String*/ attr, /*anything*/ value){
4463 // summary:
4464 // Deprecated. Use set() instead.
4465 // tags:
4466 // deprecated
4467 dojo.deprecated(this.declaredClass+"::setAttribute(attr, value) is deprecated. Use set() instead.", "", "2.0");
4468 this.set(attr, value);
4469 },
4470
4471 attr: function(/*String|Object*/name, /*Object?*/value){
4472 // summary:
4473 // Set or get properties on a widget instance.
4474 // name:
4475 // The property to get or set. If an object is passed here and not
4476 // a string, its keys are used as names of attributes to be set
4477 // and the value of the object as values to set in the widget.
4478 // value:
4479 // Optional. If provided, attr() operates as a setter. If omitted,
4480 // the current value of the named property is returned.
4481 // description:
4482 // This method is deprecated, use get() or set() directly.
4483
4484 // Print deprecation warning but only once per calling function
4485 if(dojo.config.isDebug){
4486 var alreadyCalledHash = arguments.callee._ach || (arguments.callee._ach = {}),
4487 caller = (arguments.callee.caller || "unknown caller").toString();
4488 if(!alreadyCalledHash[caller]){
4489 dojo.deprecated(this.declaredClass + "::attr() is deprecated. Use get() or set() instead, called from " +
4490 caller, "", "2.0");
4491 alreadyCalledHash[caller] = true;
4492 }
4493 }
4494
4495 var args = arguments.length;
4496 if(args >= 2 || typeof name === "object"){ // setter
4497 return this.set.apply(this, arguments);
4498 }else{ // getter
4499 return this.get(name);
4500 }
4501 },
4502
4503 ////////////////// ONDIJITCLICK SUPPORT ///////////////////
4504
4505 // nodesWithKeyClick: [private] String[]
4506 // List of nodes that correctly handle click events via native browser support,
4507 // and don't need dijit's help
4508 nodesWithKeyClick: ["input", "button"],
4509
4510 connect: function(
4511 /*Object|null*/ obj,
4512 /*String|Function*/ event,
4513 /*String|Function*/ method){
4514 // summary:
4515 // Connects specified obj/event to specified method of this object
4516 // and registers for disconnect() on widget destroy.
4517 // description:
4518 // Provide widget-specific analog to dojo.connect, except with the
4519 // implicit use of this widget as the target object.
4520 // This version of connect also provides a special "ondijitclick"
4521 // event which triggers on a click or space or enter keyup.
4522 // Events connected with `this.connect` are disconnected upon
4523 // destruction.
4524 // returns:
4525 // A handle that can be passed to `disconnect` in order to disconnect before
4526 // the widget is destroyed.
4527 // example:
4528 // | var btn = new dijit.form.Button();
4529 // | // when foo.bar() is called, call the listener we're going to
4530 // | // provide in the scope of btn
4531 // | btn.connect(foo, "bar", function(){
4532 // | console.debug(this.toString());
4533 // | });
4534 // tags:
4535 // protected
4536
4537 var d = dojo,
4538 dc = d._connect,
4539 handles = this.inherited(arguments, [obj, event == "ondijitclick" ? "onclick" : event, method]);
4540
4541 if(event == "ondijitclick"){
4542 // add key based click activation for unsupported nodes.
4543 // do all processing onkey up to prevent spurious clicks
4544 // for details see comments at top of this file where _lastKeyDownNode is defined
4545 if(d.indexOf(this.nodesWithKeyClick, obj.nodeName.toLowerCase()) == -1){ // is NOT input or button
4546 var m = d.hitch(this, method);
4547 handles.push(
4548 dc(obj, "onkeydown", this, function(e){
4549 //console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
4550 if((e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) &&
4551 !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){
4552 // needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
4553 dijit._lastKeyDownNode = e.target;
4554
4555 // Stop event to prevent scrolling on space key in IE.
4556 // But don't do this for _HasDropDown because it surpresses the onkeypress
4557 // event needed to open the drop down when the user presses the SPACE key.
4558 if(!("openDropDown" in this && obj == this._buttonNode)){
4559 e.preventDefault();
4560 }
4561 }
4562 }),
4563 dc(obj, "onkeyup", this, function(e){
4564 //console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode));
4565 if( (e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) &&
4566 e.target == dijit._lastKeyDownNode && // === breaks greasemonkey
4567 !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){
4568 //need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
4569 dijit._lastKeyDownNode = null;
4570 return m(e);
4571 }
4572 })
4573 );
4574 }
4575 }
4576
4577 return handles; // _Widget.Handle
4578 },
4579
4580 ////////////////// MISCELLANEOUS METHODS ///////////////////
4581
4582 _onShow: function(){
4583 // summary:
4584 // Internal method called when this widget is made visible.
4585 // See `onShow` for details.
4586 this.onShow();
4587 },
4588
4589 onShow: function(){
4590 // summary:
4591 // Called when this widget becomes the selected pane in a
4592 // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`,
4593 // `dijit.layout.AccordionContainer`, etc.
4594 //
4595 // Also called to indicate display of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
4596 // tags:
4597 // callback
4598 },
4599
4600 onHide: function(){
4601 // summary:
4602 // Called when another widget becomes the selected pane in a
4603 // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`,
4604 // `dijit.layout.AccordionContainer`, etc.
4605 //
4606 // Also called to indicate hide of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`.
4607 // tags:
4608 // callback
4609 },
4610
4611 onClose: function(){
4612 // summary:
4613 // Called when this widget is being displayed as a popup (ex: a Calendar popped
4614 // up from a DateTextBox), and it is hidden.
4615 // This is called from the dijit.popup code, and should not be called directly.
4616 //
4617 // Also used as a parameter for children of `dijit.layout.StackContainer` or subclasses.
4618 // Callback if a user tries to close the child. Child will be closed if this function returns true.
4619 // tags:
4620 // extension
4621
4622 return true; // Boolean
4623 }
4624});
4625
4626})();
4627
4628}
4629
4630if(!dojo._hasResource["dojo.string"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4631dojo._hasResource["dojo.string"] = true;
4632dojo.provide("dojo.string");
4633
4634dojo.getObject("string", true, dojo);
4635
4636/*=====
4637dojo.string = {
4638 // summary: String utilities for Dojo
4639};
4640=====*/
4641
4642dojo.string.rep = function(/*String*/str, /*Integer*/num){
4643 // summary:
4644 // Efficiently replicate a string `n` times.
4645 // str:
4646 // the string to replicate
4647 // num:
4648 // number of times to replicate the string
4649
4650 if(num <= 0 || !str){ return ""; }
4651
4652 var buf = [];
4653 for(;;){
4654 if(num & 1){
4655 buf.push(str);
4656 }
4657 if(!(num >>= 1)){ break; }
4658 str += str;
4659 }
4660 return buf.join(""); // String
4661};
4662
4663dojo.string.pad = function(/*String*/text, /*Integer*/size, /*String?*/ch, /*Boolean?*/end){
4664 // summary:
4665 // Pad a string to guarantee that it is at least `size` length by
4666 // filling with the character `ch` at either the start or end of the
4667 // string. Pads at the start, by default.
4668 // text:
4669 // the string to pad
4670 // size:
4671 // length to provide padding
4672 // ch:
4673 // character to pad, defaults to '0'
4674 // end:
4675 // adds padding at the end if true, otherwise pads at start
4676 // example:
4677 // | // Fill the string to length 10 with "+" characters on the right. Yields "Dojo++++++".
4678 // | dojo.string.pad("Dojo", 10, "+", true);
4679
4680 if(!ch){
4681 ch = '0';
4682 }
4683 var out = String(text),
4684 pad = dojo.string.rep(ch, Math.ceil((size - out.length) / ch.length));
4685 return end ? out + pad : pad + out; // String
4686};
4687
4688dojo.string.substitute = function( /*String*/ template,
4689 /*Object|Array*/map,
4690 /*Function?*/ transform,
4691 /*Object?*/ thisObject){
4692 // summary:
4693 // Performs parameterized substitutions on a string. Throws an
4694 // exception if any parameter is unmatched.
4695 // template:
4696 // a string with expressions in the form `${key}` to be replaced or
4697 // `${key:format}` which specifies a format function. keys are case-sensitive.
4698 // map:
4699 // hash to search for substitutions
4700 // transform:
4701 // a function to process all parameters before substitution takes
4702 // place, e.g. mylib.encodeXML
4703 // thisObject:
4704 // where to look for optional format function; default to the global
4705 // namespace
4706 // example:
4707 // Substitutes two expressions in a string from an Array or Object
4708 // | // returns "File 'foo.html' is not found in directory '/temp'."
4709 // | // by providing substitution data in an Array
4710 // | dojo.string.substitute(
4711 // | "File '${0}' is not found in directory '${1}'.",
4712 // | ["foo.html","/temp"]
4713 // | );
4714 // |
4715 // | // also returns "File 'foo.html' is not found in directory '/temp'."
4716 // | // but provides substitution data in an Object structure. Dotted
4717 // | // notation may be used to traverse the structure.
4718 // | dojo.string.substitute(
4719 // | "File '${name}' is not found in directory '${info.dir}'.",
4720 // | { name: "foo.html", info: { dir: "/temp" } }
4721 // | );
4722 // example:
4723 // Use a transform function to modify the values:
4724 // | // returns "file 'foo.html' is not found in directory '/temp'."
4725 // | dojo.string.substitute(
4726 // | "${0} is not found in ${1}.",
4727 // | ["foo.html","/temp"],
4728 // | function(str){
4729 // | // try to figure out the type
4730 // | var prefix = (str.charAt(0) == "/") ? "directory": "file";
4731 // | return prefix + " '" + str + "'";
4732 // | }
4733 // | );
4734 // example:
4735 // Use a formatter
4736 // | // returns "thinger -- howdy"
4737 // | dojo.string.substitute(
4738 // | "${0:postfix}", ["thinger"], null, {
4739 // | postfix: function(value, key){
4740 // | return value + " -- howdy";
4741 // | }
4742 // | }
4743 // | );
4744
4745 thisObject = thisObject || dojo.global;
4746 transform = transform ?
4747 dojo.hitch(thisObject, transform) : function(v){ return v; };
4748
4749 return template.replace(/\$\{([^\s\:\}]+)(?:\:([^\s\:\}]+))?\}/g,
4750 function(match, key, format){
4751 var value = dojo.getObject(key, false, map);
4752 if(format){
4753 value = dojo.getObject(format, false, thisObject).call(thisObject, value, key);
4754 }
4755 return transform(value, key).toString();
4756 }); // String
4757};
4758
4759/*=====
4760dojo.string.trim = function(str){
4761 // summary:
4762 // Trims whitespace from both sides of the string
4763 // str: String
4764 // String to be trimmed
4765 // returns: String
4766 // Returns the trimmed string
4767 // description:
4768 // This version of trim() was taken from [Steven Levithan's blog](http://blog.stevenlevithan.com/archives/faster-trim-javascript).
4769 // The short yet performant version of this function is dojo.trim(),
4770 // which is part of Dojo base. Uses String.prototype.trim instead, if available.
4771 return ""; // String
a089699c
AD
4772}
4773=====*/
4774
4775dojo.string.trim = String.prototype.trim ?
4776 dojo.trim : // aliasing to the native function
4777 function(str){
4778 str = str.replace(/^\s+/, '');
4779 for(var i = str.length - 1; i >= 0; i--){
4780 if(/\S/.test(str.charAt(i))){
4781 str = str.substring(0, i + 1);
4782 break;
4783 }
4784 }
4785 return str;
4786 };
4787
4788}
4789
4790if(!dojo._hasResource["dojo.cache"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4791dojo._hasResource["dojo.cache"] = true;
4792dojo.provide("dojo.cache");
4793
81bea17a 4794
a089699c 4795/*=====
81bea17a 4796dojo.cache = {
a089699c
AD
4797 // summary:
4798 // A way to cache string content that is fetchable via `dojo.moduleUrl`.
4799};
4800=====*/
4801
a089699c
AD
4802 var cache = {};
4803 dojo.cache = function(/*String||Object*/module, /*String*/url, /*String||Object?*/value){
4804 // summary:
4805 // A getter and setter for storing the string content associated with the
4806 // module and url arguments.
4807 // description:
4808 // module and url are used to call `dojo.moduleUrl()` to generate a module URL.
4809 // If value is specified, the cache value for the moduleUrl will be set to
4810 // that value. Otherwise, dojo.cache will fetch the moduleUrl and store it
4811 // in its internal cache and return that cached value for the URL. To clear
4812 // a cache value pass null for value. Since XMLHttpRequest (XHR) is used to fetch the
4813 // the URL contents, only modules on the same domain of the page can use this capability.
4814 // The build system can inline the cache values though, to allow for xdomain hosting.
4815 // module: String||Object
4816 // If a String, the module name to use for the base part of the URL, similar to module argument
4817 // to `dojo.moduleUrl`. If an Object, something that has a .toString() method that
4818 // generates a valid path for the cache item. For example, a dojo._Url object.
4819 // url: String
4820 // The rest of the path to append to the path derived from the module argument. If
4821 // module is an object, then this second argument should be the "value" argument instead.
4822 // value: String||Object?
4823 // If a String, the value to use in the cache for the module/url combination.
4824 // If an Object, it can have two properties: value and sanitize. The value property
4825 // should be the value to use in the cache, and sanitize can be set to true or false,
4826 // to indicate if XML declarations should be removed from the value and if the HTML
4827 // inside a body tag in the value should be extracted as the real value. The value argument
4828 // or the value property on the value argument are usually only used by the build system
4829 // as it inlines cache content.
4830 // example:
4831 // To ask dojo.cache to fetch content and store it in the cache (the dojo["cache"] style
4832 // of call is used to avoid an issue with the build system erroneously trying to intern
4833 // this example. To get the build system to intern your dojo.cache calls, use the
4834 // "dojo.cache" style of call):
4835 // | //If template.html contains "<h1>Hello</h1>" that will be
4836 // | //the value for the text variable.
4837 // | var text = dojo["cache"]("my.module", "template.html");
4838 // example:
4839 // To ask dojo.cache to fetch content and store it in the cache, and sanitize the input
81bea17a 4840 // (the dojo["cache"] style of call is used to avoid an issue with the build system
a089699c
AD
4841 // erroneously trying to intern this example. To get the build system to intern your
4842 // dojo.cache calls, use the "dojo.cache" style of call):
4843 // | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
4844 // | //text variable will contain just "<h1>Hello</h1>".
4845 // | var text = dojo["cache"]("my.module", "template.html", {sanitize: true});
4846 // example:
4847 // Same example as previous, but demostrates how an object can be passed in as
4848 // the first argument, then the value argument can then be the second argument.
4849 // | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the
4850 // | //text variable will contain just "<h1>Hello</h1>".
4851 // | var text = dojo["cache"](new dojo._Url("my/module/template.html"), {sanitize: true});
4852
4853 //Module could be a string, or an object that has a toString() method
4854 //that will return a useful path. If it is an object, then the "url" argument
4855 //will actually be the value argument.
4856 if(typeof module == "string"){
4857 var pathObj = dojo.moduleUrl(module, url);
4858 }else{
4859 pathObj = module;
4860 value = url;
4861 }
4862 var key = pathObj.toString();
4863
4864 var val = value;
4865 if(value != undefined && !dojo.isString(value)){
4866 val = ("value" in value ? value.value : undefined);
4867 }
4868
4869 var sanitize = value && value.sanitize ? true : false;
4870
4871 if(typeof val == "string"){
4872 //We have a string, set cache value
4873 val = cache[key] = sanitize ? dojo.cache._sanitize(val) : val;
4874 }else if(val === null){
4875 //Remove cached value
4876 delete cache[key];
4877 }else{
4878 //Allow cache values to be empty strings. If key property does
4879 //not exist, fetch it.
4880 if(!(key in cache)){
4881 val = dojo._getText(key);
4882 cache[key] = sanitize ? dojo.cache._sanitize(val) : val;
4883 }
4884 val = cache[key];
4885 }
4886 return val; //String
4887 };
4888
4889 dojo.cache._sanitize = function(/*String*/val){
81bea17a 4890 // summary:
a089699c
AD
4891 // Strips <?xml ...?> declarations so that external SVG and XML
4892 // documents can be added to a document without worry. Also, if the string
4893 // is an HTML document, only the part inside the body tag is returned.
4894 // description:
4895 // Copied from dijit._Templated._sanitizeTemplateString.
4896 if(val){
4897 val = val.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, "");
4898 var matches = val.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
4899 if(matches){
4900 val = matches[1];
4901 }
4902 }else{
4903 val = "";
4904 }
4905 return val; //String
4906 };
a089699c
AD
4907
4908}
4909
4910if(!dojo._hasResource["dijit._Templated"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
4911dojo._hasResource["dijit._Templated"] = true;
4912dojo.provide("dijit._Templated");
4913
4914
4915
4916
4917
4918
4919dojo.declare("dijit._Templated",
4920 null,
4921 {
4922 // summary:
4923 // Mixin for widgets that are instantiated from a template
4924
4925 // templateString: [protected] String
4926 // A string that represents the widget template. Pre-empts the
4927 // templatePath. In builds that have their strings "interned", the
4928 // templatePath is converted to an inline templateString, thereby
4929 // preventing a synchronous network call.
4930 //
4931 // Use in conjunction with dojo.cache() to load from a file.
4932 templateString: null,
4933
4934 // templatePath: [protected deprecated] String
4935 // Path to template (HTML file) for this widget relative to dojo.baseUrl.
4936 // Deprecated: use templateString with dojo.cache() instead.
4937 templatePath: null,
4938
4939 // widgetsInTemplate: [protected] Boolean
4940 // Should we parse the template to find widgets that might be
4941 // declared in markup inside it? False by default.
4942 widgetsInTemplate: false,
4943
4944 // skipNodeCache: [protected] Boolean
4945 // If using a cached widget template node poses issues for a
4946 // particular widget class, it can set this property to ensure
4947 // that its template is always re-built from a string
4948 _skipNodeCache: false,
4949
4950 // _earlyTemplatedStartup: Boolean
4951 // A fallback to preserve the 1.0 - 1.3 behavior of children in
4952 // templates having their startup called before the parent widget
4953 // fires postCreate. Defaults to 'false', causing child widgets to
4954 // have their .startup() called immediately before a parent widget
4955 // .startup(), but always after the parent .postCreate(). Set to
4956 // 'true' to re-enable to previous, arguably broken, behavior.
4957 _earlyTemplatedStartup: false,
4958
81bea17a 4959/*=====
a089699c
AD
4960 // _attachPoints: [private] String[]
4961 // List of widget attribute names associated with dojoAttachPoint=... in the
4962 // template, ex: ["containerNode", "labelNode"]
a089699c
AD
4963 _attachPoints: [],
4964 =====*/
4965
81bea17a
AD
4966/*=====
4967 // _attachEvents: [private] Handle[]
4968 // List of connections associated with dojoAttachEvent=... in the
4969 // template
4970 _attachEvents: [],
4971 =====*/
4972
a089699c
AD
4973 constructor: function(){
4974 this._attachPoints = [];
81bea17a 4975 this._attachEvents = [];
a089699c
AD
4976 },
4977
4978 _stringRepl: function(tmpl){
4979 // summary:
4980 // Does substitution of ${foo} type properties in template string
4981 // tags:
4982 // private
4983 var className = this.declaredClass, _this = this;
4984 // Cache contains a string because we need to do property replacement
4985 // do the property replacement
4986 return dojo.string.substitute(tmpl, this, function(value, key){
4987 if(key.charAt(0) == '!'){ value = dojo.getObject(key.substr(1), false, _this); }
4988 if(typeof value == "undefined"){ throw new Error(className+" template:"+key); } // a debugging aide
4989 if(value == null){ return ""; }
4990
4991 // Substitution keys beginning with ! will skip the transform step,
4992 // in case a user wishes to insert unescaped markup, e.g. ${!foo}
4993 return key.charAt(0) == "!" ? value :
4994 // Safer substitution, see heading "Attribute values" in
4995 // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
4996 value.toString().replace(/"/g,"&quot;"); //TODO: add &amp? use encodeXML method?
4997 }, this);
4998 },
4999
a089699c
AD
5000 buildRendering: function(){
5001 // summary:
5002 // Construct the UI for this widget from a template, setting this.domNode.
5003 // tags:
5004 // protected
5005
5006 // Lookup cached version of template, and download to cache if it
5007 // isn't there already. Returns either a DomNode or a string, depending on
5008 // whether or not the template contains ${foo} replacement parameters.
5009 var cached = dijit._Templated.getCachedTemplate(this.templatePath, this.templateString, this._skipNodeCache);
5010
5011 var node;
5012 if(dojo.isString(cached)){
5013 node = dojo._toDom(this._stringRepl(cached));
5014 if(node.nodeType != 1){
5015 // Flag common problems such as templates with multiple top level nodes (nodeType == 11)
5016 throw new Error("Invalid template: " + cached);
5017 }
5018 }else{
5019 // if it's a node, all we have to do is clone it
5020 node = cached.cloneNode(true);
5021 }
5022
5023 this.domNode = node;
5024
81bea17a
AD
5025 // Call down to _Widget.buildRendering() to get base classes assigned
5026 // TODO: change the baseClass assignment to attributeMap
5027 this.inherited(arguments);
5028
a089699c
AD
5029 // recurse through the node, looking for, and attaching to, our
5030 // attachment points and events, which should be defined on the template node.
5031 this._attachTemplateNodes(node);
5032
5033 if(this.widgetsInTemplate){
a089699c
AD
5034 // Store widgets that we need to start at a later point in time
5035 var cw = (this._startupWidgets = dojo.parser.parse(node, {
5036 noStart: !this._earlyTemplatedStartup,
81bea17a
AD
5037 template: true,
5038 inherited: {dir: this.dir, lang: this.lang},
5039 propsThis: this, // so data-dojo-props of widgets in the template can reference "this" to refer to me
5040 scope: "dojo" // even in multi-version mode templates use dojoType/data-dojo-type
a089699c
AD
5041 }));
5042
a089699c
AD
5043 this._supportingWidgets = dijit.findWidgets(node);
5044
5045 this._attachTemplateNodes(cw, function(n,p){
5046 return n[p];
5047 });
5048 }
5049
5050 this._fillContent(this.srcNodeRef);
5051 },
5052
5053 _fillContent: function(/*DomNode*/ source){
5054 // summary:
5055 // Relocate source contents to templated container node.
5056 // this.containerNode must be able to receive children, or exceptions will be thrown.
5057 // tags:
5058 // protected
5059 var dest = this.containerNode;
5060 if(source && dest){
5061 while(source.hasChildNodes()){
5062 dest.appendChild(source.firstChild);
5063 }
5064 }
5065 },
5066
5067 _attachTemplateNodes: function(rootNode, getAttrFunc){
5068 // summary:
5069 // Iterate through the template and attach functions and nodes accordingly.
81bea17a
AD
5070 // Alternately, if rootNode is an array of widgets, then will process dojoAttachPoint
5071 // etc. for those widgets.
a089699c
AD
5072 // description:
5073 // Map widget properties and functions to the handlers specified in
5074 // the dom node and it's descendants. This function iterates over all
5075 // nodes and looks for these properties:
5076 // * dojoAttachPoint
5077 // * dojoAttachEvent
5078 // * waiRole
5079 // * waiState
5080 // rootNode: DomNode|Array[Widgets]
5081 // the node to search for properties. All children will be searched.
5082 // getAttrFunc: Function?
5083 // a function which will be used to obtain property for a given
5084 // DomNode/Widget
5085 // tags:
5086 // private
5087
5088 getAttrFunc = getAttrFunc || function(n,p){ return n.getAttribute(p); };
5089
5090 var nodes = dojo.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*"));
5091 var x = dojo.isArray(rootNode) ? 0 : -1;
5092 for(; x<nodes.length; x++){
5093 var baseNode = (x == -1) ? rootNode : nodes[x];
81bea17a 5094 if(this.widgetsInTemplate && (getAttrFunc(baseNode, "dojoType") || getAttrFunc(baseNode, "data-dojo-type"))){
a089699c
AD
5095 continue;
5096 }
5097 // Process dojoAttachPoint
81bea17a 5098 var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point");
a089699c
AD
5099 if(attachPoint){
5100 var point, points = attachPoint.split(/\s*,\s*/);
5101 while((point = points.shift())){
5102 if(dojo.isArray(this[point])){
5103 this[point].push(baseNode);
5104 }else{
5105 this[point]=baseNode;
5106 }
5107 this._attachPoints.push(point);
5108 }
5109 }
5110
5111 // Process dojoAttachEvent
81bea17a 5112 var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event");;
a089699c
AD
5113 if(attachEvent){
5114 // NOTE: we want to support attributes that have the form
5115 // "domEvent: nativeEvent; ..."
5116 var event, events = attachEvent.split(/\s*,\s*/);
5117 var trim = dojo.trim;
5118 while((event = events.shift())){
5119 if(event){
5120 var thisFunc = null;
5121 if(event.indexOf(":") != -1){
5122 // oh, if only JS had tuple assignment
5123 var funcNameArr = event.split(":");
5124 event = trim(funcNameArr[0]);
5125 thisFunc = trim(funcNameArr[1]);
5126 }else{
5127 event = trim(event);
5128 }
5129 if(!thisFunc){
5130 thisFunc = event;
5131 }
81bea17a 5132 this._attachEvents.push(this.connect(baseNode, event, thisFunc));
a089699c
AD
5133 }
5134 }
5135 }
5136
5137 // waiRole, waiState
81bea17a 5138 // TODO: remove this in 2.0, templates are now using role=... and aria-XXX=... attributes directicly
a089699c
AD
5139 var role = getAttrFunc(baseNode, "waiRole");
5140 if(role){
5141 dijit.setWaiRole(baseNode, role);
5142 }
5143 var values = getAttrFunc(baseNode, "waiState");
5144 if(values){
5145 dojo.forEach(values.split(/\s*,\s*/), function(stateValue){
5146 if(stateValue.indexOf('-') != -1){
5147 var pair = stateValue.split('-');
5148 dijit.setWaiState(baseNode, pair[0], pair[1]);
5149 }
5150 });
5151 }
5152 }
5153 },
5154
5155 startup: function(){
5156 dojo.forEach(this._startupWidgets, function(w){
5157 if(w && !w._started && w.startup){
5158 w.startup();
5159 }
5160 });
5161 this.inherited(arguments);
5162 },
5163
5164 destroyRendering: function(){
5165 // Delete all attach points to prevent IE6 memory leaks.
5166 dojo.forEach(this._attachPoints, function(point){
5167 delete this[point];
5168 }, this);
5169 this._attachPoints = [];
5170
81bea17a
AD
5171 // And same for event handlers
5172 dojo.forEach(this._attachEvents, this.disconnect, this);
5173 this._attachEvents = [];
5174
a089699c
AD
5175 this.inherited(arguments);
5176 }
5177 }
5178);
5179
5180// key is either templatePath or templateString; object is either string or DOM tree
5181dijit._Templated._templateCache = {};
5182
5183dijit._Templated.getCachedTemplate = function(templatePath, templateString, alwaysUseString){
5184 // summary:
5185 // Static method to get a template based on the templatePath or
5186 // templateString key
5187 // templatePath: String||dojo.uri.Uri
5188 // The URL to get the template from.
5189 // templateString: String?
5190 // a string to use in lieu of fetching the template from a URL. Takes precedence
5191 // over templatePath
5192 // returns: Mixed
5193 // Either string (if there are ${} variables that need to be replaced) or just
5194 // a DOM tree (if the node can be cloned directly)
5195
5196 // is it already cached?
5197 var tmplts = dijit._Templated._templateCache;
5198 var key = templateString || templatePath;
5199 var cached = tmplts[key];
5200 if(cached){
5201 try{
5202 // if the cached value is an innerHTML string (no ownerDocument) or a DOM tree created within the current document, then use the current cached value
5203 if(!cached.ownerDocument || cached.ownerDocument == dojo.doc){
5204 // string or node of the same document
5205 return cached;
5206 }
5207 }catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded
5208 dojo.destroy(cached);
5209 }
5210
5211 // If necessary, load template string from template path
5212 if(!templateString){
5213 templateString = dojo.cache(templatePath, {sanitize: true});
5214 }
5215 templateString = dojo.string.trim(templateString);
5216
5217 if(alwaysUseString || templateString.match(/\$\{([^\}]+)\}/g)){
5218 // there are variables in the template so all we can do is cache the string
5219 return (tmplts[key] = templateString); //String
5220 }else{
5221 // there are no variables in the template so we can cache the DOM tree
5222 var node = dojo._toDom(templateString);
5223 if(node.nodeType != 1){
5224 throw new Error("Invalid template: " + templateString);
5225 }
5226 return (tmplts[key] = node); //Node
5227 }
5228};
5229
5230if(dojo.isIE){
5231 dojo.addOnWindowUnload(function(){
5232 var cache = dijit._Templated._templateCache;
5233 for(var key in cache){
5234 var value = cache[key];
5235 if(typeof value == "object"){ // value is either a string or a DOM node template
5236 dojo.destroy(value);
5237 }
5238 delete cache[key];
5239 }
5240 });
5241}
5242
5243// These arguments can be specified for widgets which are used in templates.
5244// Since any widget can be specified as sub widgets in template, mix it
5245// into the base widget class. (This is a hack, but it's effective.)
5246dojo.extend(dijit._Widget,{
5247 dojoAttachEvent: "",
5248 dojoAttachPoint: "",
5249 waiRole: "",
5250 waiState:""
5251});
5252
5253}
5254
5255if(!dojo._hasResource["dijit._Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5256dojo._hasResource["dijit._Container"] = true;
5257dojo.provide("dijit._Container");
5258
81bea17a 5259
a089699c
AD
5260dojo.declare("dijit._Container",
5261 null,
5262 {
5263 // summary:
5264 // Mixin for widgets that contain a set of widget children.
5265 // description:
5266 // Use this mixin for widgets that needs to know about and
5267 // keep track of their widget children. Suitable for widgets like BorderContainer
5268 // and TabContainer which contain (only) a set of child widgets.
5269 //
5270 // It's not suitable for widgets like ContentPane
5271 // which contains mixed HTML (plain DOM nodes in addition to widgets),
5272 // and where contained widgets are not necessarily directly below
5273 // this.containerNode. In that case calls like addChild(node, position)
5274 // wouldn't make sense.
5275
5276 // isContainer: [protected] Boolean
5277 // Indicates that this widget acts as a "parent" to the descendant widgets.
5278 // When the parent is started it will call startup() on the child widgets.
5279 // See also `isLayoutContainer`.
5280 isContainer: true,
5281
5282 buildRendering: function(){
5283 this.inherited(arguments);
5284 if(!this.containerNode){
5285 // all widgets with descendants must set containerNode
5286 this.containerNode = this.domNode;
5287 }
5288 },
5289
5290 addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
5291 // summary:
5292 // Makes the given widget a child of this widget.
5293 // description:
5294 // Inserts specified child widget's dom node as a child of this widget's
5295 // container node, and possibly does other processing (such as layout).
5296
5297 var refNode = this.containerNode;
5298 if(insertIndex && typeof insertIndex == "number"){
5299 var children = this.getChildren();
5300 if(children && children.length >= insertIndex){
5301 refNode = children[insertIndex-1].domNode;
5302 insertIndex = "after";
5303 }
5304 }
5305 dojo.place(widget.domNode, refNode, insertIndex);
5306
5307 // If I've been started but the child widget hasn't been started,
5308 // start it now. Make sure to do this after widget has been
5309 // inserted into the DOM tree, so it can see that it's being controlled by me,
5310 // so it doesn't try to size itself.
5311 if(this._started && !widget._started){
5312 widget.startup();
5313 }
5314 },
5315
5316 removeChild: function(/*Widget or int*/ widget){
5317 // summary:
5318 // Removes the passed widget instance from this widget but does
5319 // not destroy it. You can also pass in an integer indicating
5320 // the index within the container to remove
5321
81bea17a 5322 if(typeof widget == "number"){
a089699c
AD
5323 widget = this.getChildren()[widget];
5324 }
5325
5326 if(widget){
5327 var node = widget.domNode;
5328 if(node && node.parentNode){
5329 node.parentNode.removeChild(node); // detach but don't destroy
5330 }
5331 }
5332 },
5333
5334 hasChildren: function(){
5335 // summary:
5336 // Returns true if widget has children, i.e. if this.containerNode contains something.
5337 return this.getChildren().length > 0; // Boolean
5338 },
5339
5340 destroyDescendants: function(/*Boolean*/ preserveDom){
5341 // summary:
5342 // Destroys all the widgets inside this.containerNode,
5343 // but not this widget itself
5344 dojo.forEach(this.getChildren(), function(child){ child.destroyRecursive(preserveDom); });
5345 },
5346
5347 _getSiblingOfChild: function(/*dijit._Widget*/ child, /*int*/ dir){
5348 // summary:
5349 // Get the next or previous widget sibling of child
5350 // dir:
5351 // if 1, get the next sibling
5352 // if -1, get the previous sibling
5353 // tags:
5354 // private
5355 var node = child.domNode,
5356 which = (dir>0 ? "nextSibling" : "previousSibling");
5357 do{
5358 node = node[which];
5359 }while(node && (node.nodeType != 1 || !dijit.byNode(node)));
5360 return node && dijit.byNode(node); // dijit._Widget
5361 },
5362
5363 getIndexOfChild: function(/*dijit._Widget*/ child){
5364 // summary:
5365 // Gets the index of the child in this container or -1 if not found
5366 return dojo.indexOf(this.getChildren(), child); // int
5367 },
5368
5369 startup: function(){
5370 // summary:
5371 // Called after all the widgets have been instantiated and their
5372 // dom nodes have been inserted somewhere under dojo.doc.body.
5373 //
5374 // Widgets should override this method to do any initialization
5375 // dependent on other widgets existing, and then call
5376 // this superclass method to finish things off.
5377 //
5378 // startup() in subclasses shouldn't do anything
5379 // size related because the size of the widget hasn't been set yet.
5380
5381 if(this._started){ return; }
5382
5383 // Startup all children of this widget
5384 dojo.forEach(this.getChildren(), function(child){ child.startup(); });
5385
5386 this.inherited(arguments);
5387 }
5388 }
5389);
5390
5391}
5392
5393if(!dojo._hasResource["dijit._Contained"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5394dojo._hasResource["dijit._Contained"] = true;
5395dojo.provide("dijit._Contained");
5396
81bea17a 5397
a089699c
AD
5398dojo.declare("dijit._Contained",
5399 null,
5400 {
5401 // summary:
5402 // Mixin for widgets that are children of a container widget
5403 //
5404 // example:
5405 // | // make a basic custom widget that knows about it's parents
5406 // | dojo.declare("my.customClass",[dijit._Widget,dijit._Contained],{});
5407
5408 getParent: function(){
5409 // summary:
5410 // Returns the parent widget of this widget, assuming the parent
5411 // specifies isContainer
5412 var parent = dijit.getEnclosingWidget(this.domNode.parentNode);
5413 return parent && parent.isContainer ? parent : null;
5414 },
5415
5416 _getSibling: function(/*String*/ which){
5417 // summary:
5418 // Returns next or previous sibling
5419 // which:
5420 // Either "next" or "previous"
5421 // tags:
5422 // private
5423 var node = this.domNode;
5424 do{
5425 node = node[which+"Sibling"];
5426 }while(node && node.nodeType != 1);
5427 return node && dijit.byNode(node); // dijit._Widget
5428 },
5429
5430 getPreviousSibling: function(){
5431 // summary:
5432 // Returns null if this is the first child of the parent,
5433 // otherwise returns the next element sibling to the "left".
5434
5435 return this._getSibling("previous"); // dijit._Widget
5436 },
5437
5438 getNextSibling: function(){
5439 // summary:
5440 // Returns null if this is the last child of the parent,
5441 // otherwise returns the next element sibling to the "right".
5442
5443 return this._getSibling("next"); // dijit._Widget
5444 },
5445
5446 getIndexInParent: function(){
5447 // summary:
5448 // Returns the index of this widget within its container parent.
5449 // It returns -1 if the parent does not exist, or if the parent
5450 // is not a dijit._Container
5451
5452 var p = this.getParent();
5453 if(!p || !p.getIndexOfChild){
5454 return -1; // int
5455 }
5456 return p.getIndexOfChild(this); // int
5457 }
5458 }
5459 );
5460
a089699c
AD
5461}
5462
5463if(!dojo._hasResource["dijit.layout._LayoutWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5464dojo._hasResource["dijit.layout._LayoutWidget"] = true;
5465dojo.provide("dijit.layout._LayoutWidget");
5466
5467
5468
5469
5470
5471dojo.declare("dijit.layout._LayoutWidget",
5472 [dijit._Widget, dijit._Container, dijit._Contained],
5473 {
5474 // summary:
5475 // Base class for a _Container widget which is responsible for laying out its children.
5476 // Widgets which mixin this code must define layout() to manage placement and sizing of the children.
5477
5478 // baseClass: [protected extension] String
5479 // This class name is applied to the widget's domNode
5480 // and also may be used to generate names for sub nodes,
5481 // for example dijitTabContainer-content.
5482 baseClass: "dijitLayoutContainer",
5483
5484 // isLayoutContainer: [protected] Boolean
5485 // Indicates that this widget is going to call resize() on its
5486 // children widgets, setting their size, when they become visible.
5487 isLayoutContainer: true,
5488
81bea17a 5489 buildRendering: function(){
a089699c 5490 this.inherited(arguments);
81bea17a 5491 dojo.addClass(this.domNode, "dijitContainer");
a089699c
AD
5492 },
5493
5494 startup: function(){
5495 // summary:
5496 // Called after all the widgets have been instantiated and their
5497 // dom nodes have been inserted somewhere under dojo.doc.body.
5498 //
5499 // Widgets should override this method to do any initialization
5500 // dependent on other widgets existing, and then call
5501 // this superclass method to finish things off.
5502 //
5503 // startup() in subclasses shouldn't do anything
5504 // size related because the size of the widget hasn't been set yet.
5505
5506 if(this._started){ return; }
5507
5508 // Need to call inherited first - so that child widgets get started
5509 // up correctly
5510 this.inherited(arguments);
5511
5512 // If I am a not being controlled by a parent layout widget...
5513 var parent = this.getParent && this.getParent()
5514 if(!(parent && parent.isLayoutContainer)){
5515 // Do recursive sizing and layout of all my descendants
5516 // (passing in no argument to resize means that it has to glean the size itself)
5517 this.resize();
5518
5519 // Since my parent isn't a layout container, and my style *may be* width=height=100%
5520 // or something similar (either set directly or via a CSS class),
5521 // monitor when my size changes so that I can re-layout.
5522 // For browsers where I can't directly monitor when my size changes,
5523 // monitor when the viewport changes size, which *may* indicate a size change for me.
5524 this.connect(dojo.isIE ? this.domNode : dojo.global, 'onresize', function(){
5525 // Using function(){} closure to ensure no arguments to resize.
5526 this.resize();
5527 });
5528 }
5529 },
5530
5531 resize: function(changeSize, resultSize){
5532 // summary:
5533 // Call this to resize a widget, or after its size has changed.
5534 // description:
5535 // Change size mode:
5536 // When changeSize is specified, changes the marginBox of this widget
5537 // and forces it to relayout its contents accordingly.
5538 // changeSize may specify height, width, or both.
5539 //
5540 // If resultSize is specified it indicates the size the widget will
5541 // become after changeSize has been applied.
5542 //
5543 // Notification mode:
5544 // When changeSize is null, indicates that the caller has already changed
5545 // the size of the widget, or perhaps it changed because the browser
5546 // window was resized. Tells widget to relayout its contents accordingly.
5547 //
5548 // If resultSize is also specified it indicates the size the widget has
5549 // become.
5550 //
5551 // In either mode, this method also:
5552 // 1. Sets this._borderBox and this._contentBox to the new size of
5553 // the widget. Queries the current domNode size if necessary.
5554 // 2. Calls layout() to resize contents (and maybe adjust child widgets).
5555 //
5556 // changeSize: Object?
5557 // Sets the widget to this margin-box size and position.
5558 // May include any/all of the following properties:
5559 // | {w: int, h: int, l: int, t: int}
5560 //
5561 // resultSize: Object?
5562 // The margin-box size of this widget after applying changeSize (if
5563 // changeSize is specified). If caller knows this size and
5564 // passes it in, we don't need to query the browser to get the size.
5565 // | {w: int, h: int}
5566
5567 var node = this.domNode;
5568
5569 // set margin box size, unless it wasn't specified, in which case use current size
5570 if(changeSize){
5571 dojo.marginBox(node, changeSize);
5572
5573 // set offset of the node
5574 if(changeSize.t){ node.style.top = changeSize.t + "px"; }
5575 if(changeSize.l){ node.style.left = changeSize.l + "px"; }
5576 }
5577
5578 // If either height or width wasn't specified by the user, then query node for it.
5579 // But note that setting the margin box and then immediately querying dimensions may return
5580 // inaccurate results, so try not to depend on it.
5581 var mb = resultSize || {};
5582 dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize
5583 if( !("h" in mb) || !("w" in mb) ){
5584 mb = dojo.mixin(dojo.marginBox(node), mb); // just use dojo.marginBox() to fill in missing values
5585 }
5586
5587 // Compute and save the size of my border box and content box
5588 // (w/out calling dojo.contentBox() since that may fail if size was recently set)
5589 var cs = dojo.getComputedStyle(node);
5590 var me = dojo._getMarginExtents(node, cs);
5591 var be = dojo._getBorderExtents(node, cs);
5592 var bb = (this._borderBox = {
5593 w: mb.w - (me.w + be.w),
5594 h: mb.h - (me.h + be.h)
5595 });
5596 var pe = dojo._getPadExtents(node, cs);
5597 this._contentBox = {
5598 l: dojo._toPixelValue(node, cs.paddingLeft),
5599 t: dojo._toPixelValue(node, cs.paddingTop),
5600 w: bb.w - pe.w,
5601 h: bb.h - pe.h
5602 };
5603
5604 // Callback for widget to adjust size of its children
5605 this.layout();
5606 },
5607
5608 layout: function(){
5609 // summary:
5610 // Widgets override this method to size and position their contents/children.
5611 // When this is called this._contentBox is guaranteed to be set (see resize()).
5612 //
5613 // This is called after startup(), and also when the widget's size has been
5614 // changed.
5615 // tags:
5616 // protected extension
5617 },
5618
5619 _setupChild: function(/*dijit._Widget*/child){
5620 // summary:
5621 // Common setup for initial children and children which are added after startup
5622 // tags:
5623 // protected extension
5624
81bea17a
AD
5625 var cls = this.baseClass + "-child "
5626 + (child.baseClass ? this.baseClass + "-" + child.baseClass : "");
5627 dojo.addClass(child.domNode, cls);
a089699c
AD
5628 },
5629
5630 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
5631 // Overrides _Container.addChild() to call _setupChild()
5632 this.inherited(arguments);
5633 if(this._started){
5634 this._setupChild(child);
5635 }
5636 },
5637
5638 removeChild: function(/*dijit._Widget*/ child){
5639 // Overrides _Container.removeChild() to remove class added by _setupChild()
81bea17a
AD
5640 var cls = this.baseClass + "-child"
5641 + (child.baseClass ?
5642 " " + this.baseClass + "-" + child.baseClass : "");
5643 dojo.removeClass(child.domNode, cls);
5644
a089699c
AD
5645 this.inherited(arguments);
5646 }
5647 }
5648);
5649
5650dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
5651 // summary:
5652 // Given the margin-box size of a node, return its content box size.
5653 // Functions like dojo.contentBox() but is more reliable since it doesn't have
5654 // to wait for the browser to compute sizes.
5655 var cs = dojo.getComputedStyle(node);
5656 var me = dojo._getMarginExtents(node, cs);
5657 var pb = dojo._getPadBorderExtents(node, cs);
5658 return {
5659 l: dojo._toPixelValue(node, cs.paddingLeft),
5660 t: dojo._toPixelValue(node, cs.paddingTop),
5661 w: mb.w - (me.w + pb.w),
5662 h: mb.h - (me.h + pb.h)
5663 };
5664};
5665
5666(function(){
5667 var capitalize = function(word){
5668 return word.substring(0,1).toUpperCase() + word.substring(1);
5669 };
5670
5671 var size = function(widget, dim){
5672 // size the child
81bea17a 5673 var newSize = widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim);
a089699c 5674
81bea17a
AD
5675 // record child's size
5676 if(newSize){
5677 // if the child returned it's new size then use that
5678 dojo.mixin(widget, newSize);
5679 }else{
5680 // otherwise, call marginBox(), but favor our own numbers when we have them.
5681 // the browser lies sometimes
5682 dojo.mixin(widget, dojo.marginBox(widget.domNode));
5683 dojo.mixin(widget, dim);
5684 }
a089699c
AD
5685 };
5686
81bea17a
AD
5687 dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Widget[]*/ children,
5688 /*String?*/ changedRegionId, /*Number?*/ changedRegionSize){
a089699c
AD
5689 // summary
5690 // Layout a bunch of child dom nodes within a parent dom node
5691 // container:
5692 // parent node
5693 // dim:
5694 // {l, t, w, h} object specifying dimensions of container into which to place children
5695 // children:
81bea17a
AD
5696 // an array of Widgets or at least objects containing:
5697 // * domNode: pointer to DOM node to position
5698 // * region or layoutAlign: position to place DOM node
5699 // * resize(): (optional) method to set size of node
5700 // * id: (optional) Id of widgets, referenced from resize object, below.
5701 // changedRegionId:
5702 // If specified, the slider for the region with the specified id has been dragged, and thus
5703 // the region's height or width should be adjusted according to changedRegionSize
5704 // changedRegionSize:
5705 // See changedRegionId.
a089699c
AD
5706
5707 // copy dim because we are going to modify it
5708 dim = dojo.mixin({}, dim);
5709
5710 dojo.addClass(container, "dijitLayoutContainer");
5711
5712 // Move "client" elements to the end of the array for layout. a11y dictates that the author
5713 // needs to be able to put them in the document in tab-order, but this algorithm requires that
81bea17a
AD
5714 // client be last. TODO: move these lines to LayoutContainer? Unneeded other places I think.
5715 children = dojo.filter(children, function(item){ return item.region != "center" && item.layoutAlign != "client"; })
5716 .concat(dojo.filter(children, function(item){ return item.region == "center" || item.layoutAlign == "client"; }));
a089699c
AD
5717
5718 // set positions/sizes
5719 dojo.forEach(children, function(child){
5720 var elm = child.domNode,
81bea17a 5721 pos = (child.region || child.layoutAlign);
a089699c
AD
5722
5723 // set elem to upper left corner of unused space; may move it later
5724 var elmStyle = elm.style;
5725 elmStyle.left = dim.l+"px";
5726 elmStyle.top = dim.t+"px";
81bea17a 5727 elmStyle.position = "absolute";
a089699c
AD
5728
5729 dojo.addClass(elm, "dijitAlign" + capitalize(pos));
5730
81bea17a
AD
5731 // Size adjustments to make to this child widget
5732 var sizeSetting = {};
5733
5734 // Check for optional size adjustment due to splitter drag (height adjustment for top/bottom align
5735 // panes and width adjustment for left/right align panes.
5736 if(changedRegionId && changedRegionId == child.id){
5737 sizeSetting[child.region == "top" || child.region == "bottom" ? "h" : "w"] = changedRegionSize;
5738 }
5739
a089699c
AD
5740 // set size && adjust record of remaining space.
5741 // note that setting the width of a <div> may affect its height.
5742 if(pos == "top" || pos == "bottom"){
81bea17a
AD
5743 sizeSetting.w = dim.w;
5744 size(child, sizeSetting);
a089699c
AD
5745 dim.h -= child.h;
5746 if(pos == "top"){
5747 dim.t += child.h;
5748 }else{
5749 elmStyle.top = dim.t + dim.h + "px";
5750 }
5751 }else if(pos == "left" || pos == "right"){
81bea17a
AD
5752 sizeSetting.h = dim.h;
5753 size(child, sizeSetting);
a089699c
AD
5754 dim.w -= child.w;
5755 if(pos == "left"){
5756 dim.l += child.w;
5757 }else{
5758 elmStyle.left = dim.l + dim.w + "px";
5759 }
81bea17a 5760 }else if(pos == "client" || pos == "center"){
a089699c
AD
5761 size(child, dim);
5762 }
5763 });
5764 };
5765
5766})();
5767
5768}
5769
5770if(!dojo._hasResource["dijit._CssStateMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
5771dojo._hasResource["dijit._CssStateMixin"] = true;
5772dojo.provide("dijit._CssStateMixin");
5773
5774
5775dojo.declare("dijit._CssStateMixin", [], {
5776 // summary:
5777 // Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus
5778 // state changes, and also higher-level state changes such becoming disabled or selected.
5779 //
5780 // description:
5781 // By mixing this class into your widget, and setting the this.baseClass attribute, it will automatically
5782 // maintain CSS classes on the widget root node (this.domNode) depending on hover,
5783 // active, focus, etc. state. Ex: with a baseClass of dijitButton, it will apply the classes
5784 // dijitButtonHovered and dijitButtonActive, as the user moves the mouse over the widget and clicks it.
5785 //
5786 // It also sets CSS like dijitButtonDisabled based on widget semantic state.
5787 //
5788 // By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons
5789 // within the widget).
5790
5791 // cssStateNodes: [protected] Object
5792 // List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus
5793 //.
5794 // Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names
5795 // (like "dijitUpArrowButton"). Example:
5796 // | {
5797 // | "upArrowButton": "dijitUpArrowButton",
5798 // | "downArrowButton": "dijitDownArrowButton"
5799 // | }
5800 // The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it
5801 // is hovered, etc.
5802 cssStateNodes: {},
5803
81bea17a
AD
5804 // hovering: [readonly] Boolean
5805 // True if cursor is over this widget
5806 hovering: false,
5807
5808 // active: [readonly] Boolean
5809 // True if mouse was pressed while over this widget, and hasn't been released yet
5810 active: false,
5811
5812 _applyAttributes: function(){
5813 // This code would typically be in postCreate(), but putting in _applyAttributes() for
5814 // performance: so the class changes happen before DOM is inserted into the document.
5815 // Change back to postCreate() in 2.0. See #11635.
5816
a089699c
AD
5817 this.inherited(arguments);
5818
5819 // Automatically monitor mouse events (essentially :hover and :active) on this.domNode
5820 dojo.forEach(["onmouseenter", "onmouseleave", "onmousedown"], function(e){
5821 this.connect(this.domNode, e, "_cssMouseEvent");
5822 }, this);
5823
5824 // Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node
81bea17a
AD
5825 dojo.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active"], function(attr){
5826 this.watch(attr, dojo.hitch(this, "_setStateClass"));
a089699c
AD
5827 }, this);
5828
5829 // Events on sub nodes within the widget
5830 for(var ap in this.cssStateNodes){
5831 this._trackMouseState(this[ap], this.cssStateNodes[ap]);
5832 }
5833 // Set state initially; there's probably no hover/active/focus state but widget might be
81bea17a 5834 // disabled/readonly/checked/selected so we want to set CSS classes for those conditions.
a089699c
AD
5835 this._setStateClass();
5836 },
5837
5838 _cssMouseEvent: function(/*Event*/ event){
5839 // summary:
81bea17a
AD
5840 // Sets hovering and active properties depending on mouse state,
5841 // which triggers _setStateClass() to set appropriate CSS classes for this.domNode.
a089699c
AD
5842
5843 if(!this.disabled){
5844 switch(event.type){
5845 case "mouseenter":
5846 case "mouseover": // generated on non-IE browsers even though we connected to mouseenter
81bea17a
AD
5847 this._set("hovering", true);
5848 this._set("active", this._mouseDown);
a089699c
AD
5849 break;
5850
5851 case "mouseleave":
5852 case "mouseout": // generated on non-IE browsers even though we connected to mouseleave
81bea17a
AD
5853 this._set("hovering", false);
5854 this._set("active", false);
a089699c
AD
5855 break;
5856
5857 case "mousedown" :
81bea17a 5858 this._set("active", true);
a089699c
AD
5859 this._mouseDown = true;
5860 // Set a global event to handle mouseup, so it fires properly
5861 // even if the cursor leaves this.domNode before the mouse up event.
5862 // Alternately could set active=false on mouseout.
5863 var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){
a089699c 5864 this._mouseDown = false;
81bea17a 5865 this._set("active", false);
a089699c
AD
5866 this.disconnect(mouseUpConnector);
5867 });
5868 break;
5869 }
a089699c
AD
5870 }
5871 },
5872
5873 _setStateClass: function(){
5874 // summary:
5875 // Update the visual state of the widget by setting the css classes on this.domNode
5876 // (or this.stateNode if defined) by combining this.baseClass with
5877 // various suffixes that represent the current widget state(s).
5878 //
5879 // description:
5880 // In the case where a widget has multiple
5881 // states, it sets the class based on all possible
5882 // combinations. For example, an invalid form widget that is being hovered
5883 // will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover".
5884 //
5885 // The widget may have one or more of the following states, determined
5886 // by this.state, this.checked, this.valid, and this.selected:
5887 // - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid
81bea17a 5888 // - Incomplete - ValidationTextBox sets this.state to "Incomplete" if the current input value is not finished yet
a089699c
AD
5889 // - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true
5890 // - Selected - ex: currently selected tab will have this.selected==true
5891 //
5892 // In addition, it may have one or more of the following states,
81bea17a 5893 // based on this.disabled and flags set in _onMouse (this.active, this.hovering) and from focus manager (this.focused):
a089699c
AD
5894 // - Disabled - if the widget is disabled
5895 // - Active - if the mouse (or space/enter key?) is being pressed down
5896 // - Focused - if the widget has focus
5897 // - Hover - if the mouse is over the widget
5898
5899 // Compute new set of classes
5900 var newStateClasses = this.baseClass.split(" ");
5901
5902 function multiply(modifier){
5903 newStateClasses = newStateClasses.concat(dojo.map(newStateClasses, function(c){ return c+modifier; }), "dijit"+modifier);
5904 }
5905
5906 if(!this.isLeftToRight()){
5907 // For RTL mode we need to set an addition class like dijitTextBoxRtl.
5908 multiply("Rtl");
5909 }
5910
5911 if(this.checked){
5912 multiply("Checked");
5913 }
5914 if(this.state){
5915 multiply(this.state);
5916 }
5917 if(this.selected){
5918 multiply("Selected");
5919 }
5920
5921 if(this.disabled){
5922 multiply("Disabled");
5923 }else if(this.readOnly){
5924 multiply("ReadOnly");
5925 }else{
81bea17a 5926 if(this.active){
a089699c 5927 multiply("Active");
81bea17a 5928 }else if(this.hovering){
a089699c
AD
5929 multiply("Hover");
5930 }
5931 }
5932
5933 if(this._focused){
5934 multiply("Focused");
5935 }
5936
5937 // Remove old state classes and add new ones.
5938 // For performance concerns we only write into domNode.className once.
5939 var tn = this.stateNode || this.domNode,
5940 classHash = {}; // set of all classes (state and otherwise) for node
5941
5942 dojo.forEach(tn.className.split(" "), function(c){ classHash[c] = true; });
5943
5944 if("_stateClasses" in this){
5945 dojo.forEach(this._stateClasses, function(c){ delete classHash[c]; });
5946 }
5947
5948 dojo.forEach(newStateClasses, function(c){ classHash[c] = true; });
5949
5950 var newClasses = [];
5951 for(var c in classHash){
5952 newClasses.push(c);
5953 }
5954 tn.className = newClasses.join(" ");
5955
5956 this._stateClasses = newStateClasses;
5957 },
5958
5959 _trackMouseState: function(/*DomNode*/ node, /*String*/ clazz){
5960 // summary:
5961 // Track mouse/focus events on specified node and set CSS class on that node to indicate
5962 // current state. Usually not called directly, but via cssStateNodes attribute.
5963 // description:
5964 // Given class=foo, will set the following CSS class on the node
5965 // - fooActive: if the user is currently pressing down the mouse button while over the node
5966 // - fooHover: if the user is hovering the mouse over the node, but not pressing down a button
5967 // - fooFocus: if the node is focused
5968 //
5969 // Note that it won't set any classes if the widget is disabled.
5970 // node: DomNode
5971 // Should be a sub-node of the widget, not the top node (this.domNode), since the top node
5972 // is handled specially and automatically just by mixing in this class.
5973 // clazz: String
5974 // CSS class name (ex: dijitSliderUpArrow).
5975
5976 // Current state of node (initially false)
5977 // NB: setting specifically to false because dojo.toggleClass() needs true boolean as third arg
5978 var hovering=false, active=false, focused=false;
5979
5980 var self = this,
5981 cn = dojo.hitch(this, "connect", node);
5982
5983 function setClass(){
5984 var disabled = ("disabled" in self && self.disabled) || ("readonly" in self && self.readonly);
5985 dojo.toggleClass(node, clazz+"Hover", hovering && !active && !disabled);
5986 dojo.toggleClass(node, clazz+"Active", active && !disabled);
5987 dojo.toggleClass(node, clazz+"Focused", focused && !disabled);
5988 }
5989
5990 // Mouse
5991 cn("onmouseenter", function(){
5992 hovering = true;
5993 setClass();
5994 });
5995 cn("onmouseleave", function(){
5996 hovering = false;
5997 active = false;
5998 setClass();
5999 });
6000 cn("onmousedown", function(){
6001 active = true;
6002 setClass();
6003 });
6004 cn("onmouseup", function(){
6005 active = false;
6006 setClass();
6007 });
6008
6009 // Focus
6010 cn("onfocus", function(){
6011 focused = true;
6012 setClass();
6013 });
6014 cn("onblur", function(){
6015 focused = false;
6016 setClass();
6017 });
6018
6019 // Just in case widget is enabled/disabled while it has focus/hover/active state.
6020 // Maybe this is overkill.
81bea17a
AD
6021 this.watch("disabled", setClass);
6022 this.watch("readOnly", setClass);
a089699c
AD
6023 }
6024});
6025
6026}
6027
6028if(!dojo._hasResource["dijit.form._FormWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6029dojo._hasResource["dijit.form._FormWidget"] = true;
6030dojo.provide("dijit.form._FormWidget");
6031
6032
6033
6034
6035
6036
a089699c
AD
6037dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
6038 {
6039 // summary:
6040 // Base class for widgets corresponding to native HTML elements such as <checkbox> or <button>,
6041 // which can be children of a <form> node or a `dijit.form.Form` widget.
6042 //
6043 // description:
6044 // Represents a single HTML element.
6045 // All these widgets should have these attributes just like native HTML input elements.
6046 // You can set them during widget construction or afterwards, via `dijit._Widget.attr`.
6047 //
6048 // They also share some common methods.
6049
81bea17a 6050 // name: [const] String
a089699c
AD
6051 // Name used when submitting form; same as "name" attribute or plain HTML elements
6052 name: "",
6053
6054 // alt: String
6055 // Corresponds to the native HTML <input> element's attribute.
6056 alt: "",
6057
6058 // value: String
6059 // Corresponds to the native HTML <input> element's attribute.
6060 value: "",
6061
6062 // type: String
6063 // Corresponds to the native HTML <input> element's attribute.
6064 type: "text",
6065
6066 // tabIndex: Integer
6067 // Order fields are traversed when user hits the tab key
6068 tabIndex: "0",
6069
6070 // disabled: Boolean
6071 // Should this widget respond to user input?
6072 // In markup, this is specified as "disabled='disabled'", or just "disabled".
6073 disabled: false,
6074
6075 // intermediateChanges: Boolean
6076 // Fires onChange for each value change or only on demand
6077 intermediateChanges: false,
6078
6079 // scrollOnFocus: Boolean
6080 // On focus, should this widget scroll into view?
6081 scrollOnFocus: true,
6082
6083 // These mixins assume that the focus node is an INPUT, as many but not all _FormWidgets are.
6084 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
6085 value: "focusNode",
6086 id: "focusNode",
6087 tabIndex: "focusNode",
6088 alt: "focusNode",
6089 title: "focusNode"
6090 }),
6091
6092 postMixInProperties: function(){
6093 // Setup name=foo string to be referenced from the template (but only if a name has been specified)
6094 // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660
6095 // Regarding escaping, see heading "Attribute values" in
6096 // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
6097 this.nameAttrSetting = this.name ? ('name="' + this.name.replace(/'/g, "&quot;") + '"') : '';
6098 this.inherited(arguments);
6099 },
6100
6101 postCreate: function(){
6102 this.inherited(arguments);
6103 this.connect(this.domNode, "onmousedown", "_onMouseDown");
6104 },
6105
6106 _setDisabledAttr: function(/*Boolean*/ value){
81bea17a 6107 this._set("disabled", value);
a089699c
AD
6108 dojo.attr(this.focusNode, 'disabled', value);
6109 if(this.valueNode){
6110 dojo.attr(this.valueNode, 'disabled', value);
6111 }
6112 dijit.setWaiState(this.focusNode, "disabled", value);
6113
6114 if(value){
6115 // reset these, because after the domNode is disabled, we can no longer receive
6116 // mouse related events, see #4200
81bea17a
AD
6117 this._set("hovering", false);
6118 this._set("active", false);
a089699c
AD
6119
6120 // clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes)
6121 var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex : "focusNode";
6122 dojo.forEach(dojo.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){
6123 var node = this[attachPointName];
6124 // complex code because tabIndex=-1 on a <div> doesn't work on FF
6125 if(dojo.isWebKit || dijit.hasDefaultTabStop(node)){ // see #11064 about webkit bug
6126 node.setAttribute('tabIndex', "-1");
6127 }else{
81bea17a 6128 node.removeAttribute('tabIndex');
a089699c
AD
6129 }
6130 }, this);
6131 }else{
81bea17a
AD
6132 if(this.tabIndex != ""){
6133 this.focusNode.setAttribute('tabIndex', this.tabIndex);
6134 }
a089699c
AD
6135 }
6136 },
6137
6138 setDisabled: function(/*Boolean*/ disabled){
6139 // summary:
81bea17a 6140 // Deprecated. Use set('disabled', ...) instead.
a089699c
AD
6141 dojo.deprecated("setDisabled("+disabled+") is deprecated. Use set('disabled',"+disabled+") instead.", "", "2.0");
6142 this.set('disabled', disabled);
6143 },
6144
6145 _onFocus: function(e){
6146 if(this.scrollOnFocus){
6147 dojo.window.scrollIntoView(this.domNode);
6148 }
6149 this.inherited(arguments);
6150 },
6151
6152 isFocusable: function(){
6153 // summary:
81bea17a 6154 // Tells if this widget is focusable or not. Used internally by dijit.
a089699c
AD
6155 // tags:
6156 // protected
81bea17a 6157 return !this.disabled && this.focusNode && (dojo.style(this.domNode, "display") != "none");
a089699c
AD
6158 },
6159
6160 focus: function(){
6161 // summary:
6162 // Put focus on this widget
81bea17a
AD
6163 if(!this.disabled){
6164 dijit.focus(this.focusNode);
6165 }
a089699c
AD
6166 },
6167
81bea17a 6168 compare: function(/*anything*/ val1, /*anything*/ val2){
a089699c 6169 // summary:
81bea17a 6170 // Compare 2 values (as returned by get('value') for this widget).
a089699c
AD
6171 // tags:
6172 // protected
6173 if(typeof val1 == "number" && typeof val2 == "number"){
6174 return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
6175 }else if(val1 > val2){
6176 return 1;
6177 }else if(val1 < val2){
6178 return -1;
6179 }else{
6180 return 0;
6181 }
6182 },
6183
6184 onChange: function(newValue){
6185 // summary:
6186 // Callback when this widget's value is changed.
6187 // tags:
6188 // callback
6189 },
6190
6191 // _onChangeActive: [private] Boolean
6192 // Indicates that changes to the value should call onChange() callback.
6193 // This is false during widget initialization, to avoid calling onChange()
6194 // when the initial value is set.
6195 _onChangeActive: false,
6196
81bea17a 6197 _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
a089699c
AD
6198 // summary:
6199 // Called when the value of the widget is set. Calls onChange() if appropriate
6200 // newValue:
6201 // the new value
6202 // priorityChange:
6203 // For a slider, for example, dragging the slider is priorityChange==false,
81bea17a 6204 // but on mouse up, it's priorityChange==true. If intermediateChanges==false,
a089699c
AD
6205 // onChange is only called form priorityChange=true events.
6206 // tags:
6207 // private
a089699c
AD
6208 if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){
6209 // this block executes not for a change, but during initialization,
6210 // and is used to store away the original value (or for ToggleButton, the original checked state)
6211 this._resetValue = this._lastValueReported = newValue;
6212 }
81bea17a
AD
6213 this._pendingOnChange = this._pendingOnChange
6214 || (typeof newValue != typeof this._lastValueReported)
6215 || (this.compare(newValue, this._lastValueReported) != 0);
6216 if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){
a089699c 6217 this._lastValueReported = newValue;
81bea17a 6218 this._pendingOnChange = false;
a089699c
AD
6219 if(this._onChangeActive){
6220 if(this._onChangeHandle){
6221 clearTimeout(this._onChangeHandle);
6222 }
6223 // setTimout allows hidden value processing to run and
6224 // also the onChange handler can safely adjust focus, etc
6225 this._onChangeHandle = setTimeout(dojo.hitch(this,
6226 function(){
6227 this._onChangeHandle = null;
6228 this.onChange(newValue);
6229 }), 0); // try to collapse multiple onChange's fired faster than can be processed
6230 }
6231 }
6232 },
6233
6234 create: function(){
6235 // Overrides _Widget.create()
6236 this.inherited(arguments);
6237 this._onChangeActive = true;
6238 },
6239
6240 destroy: function(){
6241 if(this._onChangeHandle){ // destroy called before last onChange has fired
6242 clearTimeout(this._onChangeHandle);
6243 this.onChange(this._lastValueReported);
6244 }
6245 this.inherited(arguments);
6246 },
6247
6248 setValue: function(/*String*/ value){
6249 // summary:
81bea17a 6250 // Deprecated. Use set('value', ...) instead.
a089699c
AD
6251 dojo.deprecated("dijit.form._FormWidget:setValue("+value+") is deprecated. Use set('value',"+value+") instead.", "", "2.0");
6252 this.set('value', value);
6253 },
6254
6255 getValue: function(){
6256 // summary:
81bea17a 6257 // Deprecated. Use get('value') instead.
a089699c
AD
6258 dojo.deprecated(this.declaredClass+"::getValue() is deprecated. Use get('value') instead.", "", "2.0");
6259 return this.get('value');
6260 },
6261
6262 _onMouseDown: function(e){
6263 // If user clicks on the button, even if the mouse is released outside of it,
6264 // this button should get focus (to mimics native browser buttons).
6265 // This is also needed on chrome because otherwise buttons won't get focus at all,
6266 // which leads to bizarre focus restore on Dialog close etc.
81bea17a 6267 if(!e.ctrlKey && dojo.mouseButtons.isLeft(e) && this.isFocusable()){ // !e.ctrlKey to ignore right-click on mac
a089699c
AD
6268 // Set a global event to handle mouseup, so it fires properly
6269 // even if the cursor leaves this.domNode before the mouse up event.
6270 var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){
6271 if (this.isFocusable()) {
6272 this.focus();
6273 }
6274 this.disconnect(mouseUpConnector);
6275 });
6276 }
6277 }
6278});
6279
6280dojo.declare("dijit.form._FormValueWidget", dijit.form._FormWidget,
6281{
6282 // summary:
6283 // Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values.
6284 // description:
6285 // Each _FormValueWidget represents a single input value, and has a (possibly hidden) <input> element,
6286 // to which it serializes it's input value, so that form submission (either normal submission or via FormBind?)
6287 // works as expected.
6288
6289 // Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared
6290 // directly in the template as read by the parser in order to function. IE is known to specifically
81bea17a 6291 // require the 'name' attribute at element creation time. See #8484, #8660.
a089699c
AD
6292 // TODO: unclear what that {value: ""} is for; FormWidget.attributeMap copies value to focusNode,
6293 // so maybe {value: ""} is so the value *doesn't* get copied to focusNode?
6294 // Seems like we really want value removed from attributeMap altogether
6295 // (although there's no easy way to do that now)
6296
6297 // readOnly: Boolean
6298 // Should this widget respond to user input?
6299 // In markup, this is specified as "readOnly".
6300 // Similar to disabled except readOnly form values are submitted.
6301 readOnly: false,
6302
6303 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
6304 value: "",
6305 readOnly: "focusNode"
6306 }),
6307
6308 _setReadOnlyAttr: function(/*Boolean*/ value){
a089699c
AD
6309 dojo.attr(this.focusNode, 'readOnly', value);
6310 dijit.setWaiState(this.focusNode, "readonly", value);
81bea17a 6311 this._set("readOnly", value);
a089699c
AD
6312 },
6313
6314 postCreate: function(){
6315 this.inherited(arguments);
6316
81bea17a 6317 if(dojo.isIE < 9 || (dojo.isIE && dojo.isQuirks)){ // IE won't stop the event with keypress
a089699c
AD
6318 this.connect(this.focusNode || this.domNode, "onkeydown", this._onKeyDown);
6319 }
6320 // Update our reset value if it hasn't yet been set (because this.set()
6321 // is only called when there *is* a value)
6322 if(this._resetValue === undefined){
81bea17a 6323 this._lastValueReported = this._resetValue = this.value;
a089699c
AD
6324 }
6325 },
6326
81bea17a 6327 _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
a089699c 6328 // summary:
81bea17a 6329 // Hook so set('value', value) works.
a089699c
AD
6330 // description:
6331 // Sets the value of the widget.
6332 // If the value has changed, then fire onChange event, unless priorityChange
6333 // is specified as null (or false?)
a089699c
AD
6334 this._handleOnChange(newValue, priorityChange);
6335 },
6336
81bea17a 6337 _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
a089699c 6338 // summary:
81bea17a
AD
6339 // Called when the value of the widget has changed. Saves the new value in this.value,
6340 // and calls onChange() if appropriate. See _FormWidget._handleOnChange() for details.
6341 this._set("value", newValue);
6342 this.inherited(arguments);
a089699c
AD
6343 },
6344
6345 undo: function(){
6346 // summary:
6347 // Restore the value to the last value passed to onChange
6348 this._setValueAttr(this._lastValueReported, false);
6349 },
6350
6351 reset: function(){
6352 // summary:
6353 // Reset the widget's value to what it was at initialization time
6354 this._hasBeenBlurred = false;
6355 this._setValueAttr(this._resetValue, true);
6356 },
6357
6358 _onKeyDown: function(e){
6359 if(e.keyCode == dojo.keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){
6360 var te;
6361 if(dojo.isIE){
6362 e.preventDefault(); // default behavior needs to be stopped here since keypress is too late
6363 te = document.createEventObject();
6364 te.keyCode = dojo.keys.ESCAPE;
6365 te.shiftKey = e.shiftKey;
6366 e.srcElement.fireEvent('onkeypress', te);
6367 }
6368 }
6369 },
6370
6371 _layoutHackIE7: function(){
6372 // summary:
6373 // Work around table sizing bugs on IE7 by forcing redraw
6374
6375 if(dojo.isIE == 7){ // fix IE7 layout bug when the widget is scrolled out of sight
6376 var domNode = this.domNode;
6377 var parent = domNode.parentNode;
6378 var pingNode = domNode.firstChild || domNode; // target node most unlikely to have a custom filter
6379 var origFilter = pingNode.style.filter; // save custom filter, most likely nothing
6380 var _this = this;
6381 while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet
6382 (function ping(){
6383 var disconnectHandle = _this.connect(parent, "onscroll",
6384 function(e){
6385 _this.disconnect(disconnectHandle); // only call once
6386 pingNode.style.filter = (new Date()).getMilliseconds(); // set to anything that's unique
6387 setTimeout(function(){ pingNode.style.filter = origFilter }, 0); // restore custom filter, if any
6388 }
6389 );
6390 })();
6391 parent = parent.parentNode;
6392 }
6393 }
6394 }
6395});
6396
6397}
6398
6399if(!dojo._hasResource["dijit.dijit"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6400dojo._hasResource["dijit.dijit"] = true;
6401dojo.provide("dijit.dijit");
6402
81bea17a
AD
6403
6404
6405
6406
6407
6408
6409
6410
a089699c
AD
6411/*=====
6412dijit.dijit = {
6413 // summary:
6414 // A roll-up for common dijit methods
6415 // description:
6416 // A rollup file for the build system including the core and common
6417 // dijit files.
6418 //
6419 // example:
6420 // | <script type="text/javascript" src="js/dojo/dijit/dijit.js"></script>
6421 //
6422};
6423=====*/
6424
6425// All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require)
6426
a089699c
AD
6427// And some other stuff that we tend to pull in all the time anyway
6428
a089699c
AD
6429}
6430
6431if(!dojo._hasResource["dojo.fx.Toggler"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6432dojo._hasResource["dojo.fx.Toggler"] = true;
6433dojo.provide("dojo.fx.Toggler");
6434
81bea17a 6435
a089699c
AD
6436dojo.declare("dojo.fx.Toggler", null, {
6437 // summary:
6438 // A simple `dojo.Animation` toggler API.
6439 //
6440 // description:
6441 // class constructor for an animation toggler. It accepts a packed
6442 // set of arguments about what type of animation to use in each
81bea17a
AD
6443 // direction, duration, etc. All available members are mixed into
6444 // these animations from the constructor (for example, `node`,
6445 // `showDuration`, `hideDuration`).
a089699c
AD
6446 //
6447 // example:
6448 // | var t = new dojo.fx.Toggler({
6449 // | node: "nodeId",
6450 // | showDuration: 500,
6451 // | // hideDuration will default to "200"
81bea17a 6452 // | showFunc: dojo.fx.wipeIn,
a089699c
AD
6453 // | // hideFunc will default to "fadeOut"
6454 // | });
6455 // | t.show(100); // delay showing for 100ms
6456 // | // ...time passes...
6457 // | t.hide();
6458
6459 // node: DomNode
6460 // the node to target for the showing and hiding animations
6461 node: null,
6462
6463 // showFunc: Function
6464 // The function that returns the `dojo.Animation` to show the node
6465 showFunc: dojo.fadeIn,
6466
81bea17a 6467 // hideFunc: Function
a089699c
AD
6468 // The function that returns the `dojo.Animation` to hide the node
6469 hideFunc: dojo.fadeOut,
6470
6471 // showDuration:
6472 // Time in milliseconds to run the show Animation
6473 showDuration: 200,
6474
6475 // hideDuration:
6476 // Time in milliseconds to run the hide Animation
6477 hideDuration: 200,
6478
6479 // FIXME: need a policy for where the toggler should "be" the next
6480 // time show/hide are called if we're stopped somewhere in the
6481 // middle.
6482 // FIXME: also would be nice to specify individual showArgs/hideArgs mixed into
81bea17a 6483 // each animation individually.
a089699c
AD
6484 // FIXME: also would be nice to have events from the animations exposed/bridged
6485
6486 /*=====
6487 _showArgs: null,
6488 _showAnim: null,
6489
6490 _hideArgs: null,
6491 _hideAnim: null,
6492
6493 _isShowing: false,
6494 _isHiding: false,
6495 =====*/
6496
6497 constructor: function(args){
6498 var _t = this;
6499
6500 dojo.mixin(_t, args);
6501 _t.node = args.node;
6502 _t._showArgs = dojo.mixin({}, args);
6503 _t._showArgs.node = _t.node;
6504 _t._showArgs.duration = _t.showDuration;
6505 _t.showAnim = _t.showFunc(_t._showArgs);
6506
6507 _t._hideArgs = dojo.mixin({}, args);
6508 _t._hideArgs.node = _t.node;
6509 _t._hideArgs.duration = _t.hideDuration;
6510 _t.hideAnim = _t.hideFunc(_t._hideArgs);
6511
6512 dojo.connect(_t.showAnim, "beforeBegin", dojo.hitch(_t.hideAnim, "stop", true));
6513 dojo.connect(_t.hideAnim, "beforeBegin", dojo.hitch(_t.showAnim, "stop", true));
6514 },
6515
6516 show: function(delay){
6517 // summary: Toggle the node to showing
6518 // delay: Integer?
6519 // Ammount of time to stall playing the show animation
6520 return this.showAnim.play(delay || 0);
6521 },
6522
6523 hide: function(delay){
6524 // summary: Toggle the node to hidden
6525 // delay: Integer?
6526 // Ammount of time to stall playing the hide animation
6527 return this.hideAnim.play(delay || 0);
6528 }
6529});
6530
6531}
6532
6533if(!dojo._hasResource["dojo.fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6534dojo._hasResource["dojo.fx"] = true;
6535dojo.provide("dojo.fx");
81bea17a
AD
6536
6537
6538
a089699c
AD
6539/*=====
6540dojo.fx = {
6541 // summary: Effects library on top of Base animations
6542};
6543=====*/
6544(function(){
6545
81bea17a 6546 var d = dojo,
a089699c
AD
6547 _baseObj = {
6548 _fire: function(evt, args){
6549 if(this[evt]){
6550 this[evt].apply(this, args||[]);
6551 }
6552 return this;
6553 }
6554 };
6555
6556 var _chain = function(animations){
6557 this._index = -1;
6558 this._animations = animations||[];
6559 this._current = this._onAnimateCtx = this._onEndCtx = null;
6560
6561 this.duration = 0;
6562 d.forEach(this._animations, function(a){
6563 this.duration += a.duration;
6564 if(a.delay){ this.duration += a.delay; }
6565 }, this);
6566 };
6567 d.extend(_chain, {
6568 _onAnimate: function(){
6569 this._fire("onAnimate", arguments);
6570 },
6571 _onEnd: function(){
6572 d.disconnect(this._onAnimateCtx);
6573 d.disconnect(this._onEndCtx);
6574 this._onAnimateCtx = this._onEndCtx = null;
6575 if(this._index + 1 == this._animations.length){
6576 this._fire("onEnd");
6577 }else{
6578 // switch animations
6579 this._current = this._animations[++this._index];
6580 this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate");
6581 this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd");
6582 this._current.play(0, true);
6583 }
6584 },
6585 play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
6586 if(!this._current){ this._current = this._animations[this._index = 0]; }
6587 if(!gotoStart && this._current.status() == "playing"){ return this; }
6588 var beforeBegin = d.connect(this._current, "beforeBegin", this, function(){
6589 this._fire("beforeBegin");
6590 }),
6591 onBegin = d.connect(this._current, "onBegin", this, function(arg){
6592 this._fire("onBegin", arguments);
6593 }),
6594 onPlay = d.connect(this._current, "onPlay", this, function(arg){
6595 this._fire("onPlay", arguments);
6596 d.disconnect(beforeBegin);
6597 d.disconnect(onBegin);
6598 d.disconnect(onPlay);
6599 });
6600 if(this._onAnimateCtx){
6601 d.disconnect(this._onAnimateCtx);
6602 }
6603 this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate");
6604 if(this._onEndCtx){
6605 d.disconnect(this._onEndCtx);
6606 }
6607 this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd");
6608 this._current.play.apply(this._current, arguments);
6609 return this;
6610 },
6611 pause: function(){
6612 if(this._current){
6613 var e = d.connect(this._current, "onPause", this, function(arg){
6614 this._fire("onPause", arguments);
6615 d.disconnect(e);
6616 });
6617 this._current.pause();
6618 }
6619 return this;
6620 },
6621 gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){
6622 this.pause();
6623 var offset = this.duration * percent;
6624 this._current = null;
6625 d.some(this._animations, function(a){
6626 if(a.duration <= offset){
6627 this._current = a;
6628 return true;
6629 }
6630 offset -= a.duration;
6631 return false;
6632 });
6633 if(this._current){
6634 this._current.gotoPercent(offset / this._current.duration, andPlay);
6635 }
6636 return this;
6637 },
6638 stop: function(/*boolean?*/ gotoEnd){
6639 if(this._current){
6640 if(gotoEnd){
6641 for(; this._index + 1 < this._animations.length; ++this._index){
6642 this._animations[this._index].stop(true);
6643 }
6644 this._current = this._animations[this._index];
6645 }
6646 var e = d.connect(this._current, "onStop", this, function(arg){
6647 this._fire("onStop", arguments);
6648 d.disconnect(e);
6649 });
6650 this._current.stop();
6651 }
6652 return this;
6653 },
6654 status: function(){
6655 return this._current ? this._current.status() : "stopped";
6656 },
6657 destroy: function(){
6658 if(this._onAnimateCtx){ d.disconnect(this._onAnimateCtx); }
6659 if(this._onEndCtx){ d.disconnect(this._onEndCtx); }
6660 }
6661 });
6662 d.extend(_chain, _baseObj);
6663
6664 dojo.fx.chain = function(/*dojo.Animation[]*/ animations){
81bea17a 6665 // summary:
a089699c
AD
6666 // Chain a list of `dojo.Animation`s to run in sequence
6667 //
6668 // description:
6669 // Return a `dojo.Animation` which will play all passed
6670 // `dojo.Animation` instances in sequence, firing its own
6671 // synthesized events simulating a single animation. (eg:
81bea17a 6672 // onEnd of this animation means the end of the chain,
a089699c
AD
6673 // not the individual animations within)
6674 //
6675 // example:
6676 // Once `node` is faded out, fade in `otherNode`
6677 // | dojo.fx.chain([
6678 // | dojo.fadeIn({ node:node }),
6679 // | dojo.fadeOut({ node:otherNode })
6680 // | ]).play();
6681 //
6682 return new _chain(animations) // dojo.Animation
6683 };
6684
6685 var _combine = function(animations){
6686 this._animations = animations||[];
6687 this._connects = [];
6688 this._finished = 0;
6689
6690 this.duration = 0;
6691 d.forEach(animations, function(a){
6692 var duration = a.duration;
6693 if(a.delay){ duration += a.delay; }
6694 if(this.duration < duration){ this.duration = duration; }
6695 this._connects.push(d.connect(a, "onEnd", this, "_onEnd"));
6696 }, this);
6697
6698 this._pseudoAnimation = new d.Animation({curve: [0, 1], duration: this.duration});
6699 var self = this;
81bea17a 6700 d.forEach(["beforeBegin", "onBegin", "onPlay", "onAnimate", "onPause", "onStop", "onEnd"],
a089699c
AD
6701 function(evt){
6702 self._connects.push(d.connect(self._pseudoAnimation, evt,
6703 function(){ self._fire(evt, arguments); }
6704 ));
6705 }
6706 );
6707 };
6708 d.extend(_combine, {
6709 _doAction: function(action, args){
6710 d.forEach(this._animations, function(a){
6711 a[action].apply(a, args);
6712 });
6713 return this;
6714 },
6715 _onEnd: function(){
6716 if(++this._finished > this._animations.length){
6717 this._fire("onEnd");
6718 }
6719 },
6720 _call: function(action, args){
6721 var t = this._pseudoAnimation;
6722 t[action].apply(t, args);
6723 },
6724 play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
6725 this._finished = 0;
6726 this._doAction("play", arguments);
6727 this._call("play", arguments);
6728 return this;
6729 },
6730 pause: function(){
6731 this._doAction("pause", arguments);
6732 this._call("pause", arguments);
6733 return this;
6734 },
6735 gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){
6736 var ms = this.duration * percent;
6737 d.forEach(this._animations, function(a){
6738 a.gotoPercent(a.duration < ms ? 1 : (ms / a.duration), andPlay);
6739 });
6740 this._call("gotoPercent", arguments);
6741 return this;
6742 },
6743 stop: function(/*boolean?*/ gotoEnd){
6744 this._doAction("stop", arguments);
6745 this._call("stop", arguments);
6746 return this;
6747 },
6748 status: function(){
6749 return this._pseudoAnimation.status();
6750 },
6751 destroy: function(){
6752 d.forEach(this._connects, dojo.disconnect);
6753 }
6754 });
6755 d.extend(_combine, _baseObj);
6756
6757 dojo.fx.combine = function(/*dojo.Animation[]*/ animations){
81bea17a 6758 // summary:
a089699c
AD
6759 // Combine a list of `dojo.Animation`s to run in parallel
6760 //
6761 // description:
81bea17a 6762 // Combine an array of `dojo.Animation`s to run in parallel,
a089699c
AD
6763 // providing a new `dojo.Animation` instance encompasing each
6764 // animation, firing standard animation events.
6765 //
6766 // example:
6767 // Fade out `node` while fading in `otherNode` simultaneously
6768 // | dojo.fx.combine([
6769 // | dojo.fadeIn({ node:node }),
6770 // | dojo.fadeOut({ node:otherNode })
6771 // | ]).play();
6772 //
6773 // example:
6774 // When the longest animation ends, execute a function:
6775 // | var anim = dojo.fx.combine([
6776 // | dojo.fadeIn({ node: n, duration:700 }),
6777 // | dojo.fadeOut({ node: otherNode, duration: 300 })
6778 // | ]);
6779 // | dojo.connect(anim, "onEnd", function(){
6780 // | // overall animation is done.
6781 // | });
6782 // | anim.play(); // play the animation
6783 //
6784 return new _combine(animations); // dojo.Animation
6785 };
6786
6787 dojo.fx.wipeIn = function(/*Object*/ args){
6788 // summary:
6789 // Expand a node to it's natural height.
6790 //
6791 // description:
6792 // Returns an animation that will expand the
6793 // node defined in 'args' object from it's current height to
6794 // it's natural height (with no scrollbar).
6795 // Node must have no margin/border/padding.
6796 //
6797 // args: Object
6798 // A hash-map of standard `dojo.Animation` constructor properties
6799 // (such as easing: node: duration: and so on)
6800 //
6801 // example:
6802 // | dojo.fx.wipeIn({
6803 // | node:"someId"
6804 // | }).play()
6805 var node = args.node = d.byId(args.node), s = node.style, o;
6806
6807 var anim = d.animateProperty(d.mixin({
6808 properties: {
6809 height: {
6810 // wrapped in functions so we wait till the last second to query (in case value has changed)
6811 start: function(){
6812 // start at current [computed] height, but use 1px rather than 0
6813 // because 0 causes IE to display the whole panel
6814 o = s.overflow;
6815 s.overflow = "hidden";
6816 if(s.visibility == "hidden" || s.display == "none"){
6817 s.height = "1px";
6818 s.display = "";
6819 s.visibility = "";
6820 return 1;
6821 }else{
6822 var height = d.style(node, "height");
6823 return Math.max(height, 1);
6824 }
6825 },
6826 end: function(){
6827 return node.scrollHeight;
6828 }
6829 }
6830 }
6831 }, args));
6832
81bea17a 6833 d.connect(anim, "onEnd", function(){
a089699c
AD
6834 s.height = "auto";
6835 s.overflow = o;
6836 });
6837
6838 return anim; // dojo.Animation
81bea17a 6839 };
a089699c
AD
6840
6841 dojo.fx.wipeOut = function(/*Object*/ args){
6842 // summary:
81bea17a 6843 // Shrink a node to nothing and hide it.
a089699c
AD
6844 //
6845 // description:
6846 // Returns an animation that will shrink node defined in "args"
6847 // from it's current height to 1px, and then hide it.
6848 //
6849 // args: Object
6850 // A hash-map of standard `dojo.Animation` constructor properties
6851 // (such as easing: node: duration: and so on)
81bea17a 6852 //
a089699c
AD
6853 // example:
6854 // | dojo.fx.wipeOut({ node:"someId" }).play()
6855
6856 var node = args.node = d.byId(args.node), s = node.style, o;
6857
6858 var anim = d.animateProperty(d.mixin({
6859 properties: {
6860 height: {
6861 end: 1 // 0 causes IE to display the whole panel
6862 }
6863 }
6864 }, args));
6865
6866 d.connect(anim, "beforeBegin", function(){
6867 o = s.overflow;
6868 s.overflow = "hidden";
6869 s.display = "";
6870 });
6871 d.connect(anim, "onEnd", function(){
6872 s.overflow = o;
6873 s.height = "auto";
6874 s.display = "none";
6875 });
6876
6877 return anim; // dojo.Animation
81bea17a 6878 };
a089699c
AD
6879
6880 dojo.fx.slideTo = function(/*Object*/ args){
6881 // summary:
6882 // Slide a node to a new top/left position
6883 //
6884 // description:
81bea17a 6885 // Returns an animation that will slide "node"
a089699c
AD
6886 // defined in args Object from its current position to
6887 // the position defined by (args.left, args.top).
6888 //
6889 // args: Object
6890 // A hash-map of standard `dojo.Animation` constructor properties
6891 // (such as easing: node: duration: and so on). Special args members
6892 // are `top` and `left`, which indicate the new position to slide to.
6893 //
6894 // example:
6895 // | dojo.fx.slideTo({ node: node, left:"40", top:"50", units:"px" }).play()
6896
81bea17a 6897 var node = args.node = d.byId(args.node),
a089699c
AD
6898 top = null, left = null;
6899
6900 var init = (function(n){
6901 return function(){
6902 var cs = d.getComputedStyle(n);
6903 var pos = cs.position;
6904 top = (pos == 'absolute' ? n.offsetTop : parseInt(cs.top) || 0);
6905 left = (pos == 'absolute' ? n.offsetLeft : parseInt(cs.left) || 0);
6906 if(pos != 'absolute' && pos != 'relative'){
6907 var ret = d.position(n, true);
6908 top = ret.y;
6909 left = ret.x;
6910 n.style.position="absolute";
6911 n.style.top=top+"px";
6912 n.style.left=left+"px";
6913 }
6914 };
6915 })(node);
6916 init();
6917
6918 var anim = d.animateProperty(d.mixin({
6919 properties: {
6920 top: args.top || 0,
6921 left: args.left || 0
6922 }
6923 }, args));
6924 d.connect(anim, "beforeBegin", anim, init);
6925
6926 return anim; // dojo.Animation
81bea17a 6927 };
a089699c
AD
6928
6929})();
6930
6931}
6932
6933if(!dojo._hasResource["dojo.NodeList-fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
6934dojo._hasResource["dojo.NodeList-fx"] = true;
6935dojo.provide("dojo.NodeList-fx");
6936
6937
81bea17a 6938
a089699c
AD
6939/*=====
6940dojo["NodeList-fx"] = {
6941 // summary: Adds dojo.fx animation support to dojo.query()
6942};
6943=====*/
6944
6945dojo.extend(dojo.NodeList, {
6946 _anim: function(obj, method, args){
6947 args = args||{};
6948 var a = dojo.fx.combine(
6949 this.map(function(item){
6950 var tmpArgs = { node: item };
6951 dojo.mixin(tmpArgs, args);
6952 return obj[method](tmpArgs);
6953 })
81bea17a 6954 );
a089699c
AD
6955 return args.auto ? a.play() && this : a; // dojo.Animation|dojo.NodeList
6956 },
6957
6958 wipeIn: function(args){
6959 // summary:
6960 // wipe in all elements of this NodeList via `dojo.fx.wipeIn`
6961 //
6962 // args: Object?
81bea17a 6963 // Additional dojo.Animation arguments to mix into this set with the addition of
a089699c
AD
6964 // an `auto` parameter.
6965 //
6966 // returns: dojo.Animation|dojo.NodeList
6967 // A special args member `auto` can be passed to automatically play the animation.
6968 // If args.auto is present, the original dojo.NodeList will be returned for further
6969 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
6970 //
6971 // example:
6972 // Fade in all tables with class "blah":
6973 // | dojo.query("table.blah").wipeIn().play();
6974 //
6975 // example:
6976 // Utilizing `auto` to get the NodeList back:
6977 // | dojo.query(".titles").wipeIn({ auto:true }).onclick(someFunction);
6978 //
6979 return this._anim(dojo.fx, "wipeIn", args); // dojo.Animation|dojo.NodeList
6980 },
6981
6982 wipeOut: function(args){
6983 // summary:
6984 // wipe out all elements of this NodeList via `dojo.fx.wipeOut`
6985 //
6986 // args: Object?
81bea17a 6987 // Additional dojo.Animation arguments to mix into this set with the addition of
a089699c
AD
6988 // an `auto` parameter.
6989 //
6990 // returns: dojo.Animation|dojo.NodeList
6991 // A special args member `auto` can be passed to automatically play the animation.
6992 // If args.auto is present, the original dojo.NodeList will be returned for further
6993 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
6994 //
6995 // example:
6996 // Wipe out all tables with class "blah":
6997 // | dojo.query("table.blah").wipeOut().play();
6998 return this._anim(dojo.fx, "wipeOut", args); // dojo.Animation|dojo.NodeList
6999 },
7000
7001 slideTo: function(args){
7002 // summary:
7003 // slide all elements of the node list to the specified place via `dojo.fx.slideTo`
7004 //
7005 // args: Object?
81bea17a 7006 // Additional dojo.Animation arguments to mix into this set with the addition of
a089699c
AD
7007 // an `auto` parameter.
7008 //
7009 // returns: dojo.Animation|dojo.NodeList
7010 // A special args member `auto` can be passed to automatically play the animation.
7011 // If args.auto is present, the original dojo.NodeList will be returned for further
7012 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
7013 //
7014 // example:
7015 // | Move all tables with class "blah" to 300/300:
7016 // | dojo.query("table.blah").slideTo({
7017 // | left: 40,
7018 // | top: 50
7019 // | }).play();
7020 return this._anim(dojo.fx, "slideTo", args); // dojo.Animation|dojo.NodeList
7021 },
7022
7023
7024 fadeIn: function(args){
7025 // summary:
7026 // fade in all elements of this NodeList via `dojo.fadeIn`
7027 //
7028 // args: Object?
81bea17a 7029 // Additional dojo.Animation arguments to mix into this set with the addition of
a089699c
AD
7030 // an `auto` parameter.
7031 //
7032 // returns: dojo.Animation|dojo.NodeList
7033 // A special args member `auto` can be passed to automatically play the animation.
7034 // If args.auto is present, the original dojo.NodeList will be returned for further
7035 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
7036 //
7037 // example:
7038 // Fade in all tables with class "blah":
7039 // | dojo.query("table.blah").fadeIn().play();
7040 return this._anim(dojo, "fadeIn", args); // dojo.Animation|dojo.NodeList
7041 },
7042
7043 fadeOut: function(args){
7044 // summary:
7045 // fade out all elements of this NodeList via `dojo.fadeOut`
7046 //
7047 // args: Object?
81bea17a 7048 // Additional dojo.Animation arguments to mix into this set with the addition of
a089699c
AD
7049 // an `auto` parameter.
7050 //
7051 // returns: dojo.Animation|dojo.NodeList
7052 // A special args member `auto` can be passed to automatically play the animation.
7053 // If args.auto is present, the original dojo.NodeList will be returned for further
7054 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
7055 //
7056 // example:
7057 // Fade out all elements with class "zork":
7058 // | dojo.query(".zork").fadeOut().play();
7059 // example:
7060 // Fade them on a delay and do something at the end:
7061 // | var fo = dojo.query(".zork").fadeOut();
7062 // | dojo.connect(fo, "onEnd", function(){ /*...*/ });
7063 // | fo.play();
7064 // example:
7065 // Using `auto`:
7066 // | dojo.query("li").fadeOut({ auto:true }).filter(filterFn).forEach(doit);
7067 //
7068 return this._anim(dojo, "fadeOut", args); // dojo.Animation|dojo.NodeList
7069 },
7070
7071 animateProperty: function(args){
7072 // summary:
7073 // Animate all elements of this NodeList across the properties specified.
7074 // syntax identical to `dojo.animateProperty`
7075 //
7076 // returns: dojo.Animation|dojo.NodeList
7077 // A special args member `auto` can be passed to automatically play the animation.
7078 // If args.auto is present, the original dojo.NodeList will be returned for further
7079 // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed
7080 //
7081 // example:
7082 // | dojo.query(".zork").animateProperty({
7083 // | duration: 500,
81bea17a 7084 // | properties: {
a089699c 7085 // | color: { start: "black", end: "white" },
81bea17a
AD
7086 // | left: { end: 300 }
7087 // | }
a089699c
AD
7088 // | }).play();
7089 //
7090 // example:
81bea17a 7091 // | dojo.query(".grue").animateProperty({
a089699c
AD
7092 // | auto:true,
7093 // | properties: {
7094 // | height:240
7095 // | }
7096 // | }).onclick(handler);
7097 return this._anim(dojo, "animateProperty", args); // dojo.Animation|dojo.NodeList
7098 },
7099
81bea17a
AD
7100 anim: function( /*Object*/ properties,
7101 /*Integer?*/ duration,
7102 /*Function?*/ easing,
a089699c
AD
7103 /*Function?*/ onEnd,
7104 /*Integer?*/ delay){
7105 // summary:
7106 // Animate one or more CSS properties for all nodes in this list.
7107 // The returned animation object will already be playing when it
7108 // is returned. See the docs for `dojo.anim` for full details.
7109 // properties: Object
81bea17a
AD
7110 // the properties to animate. does NOT support the `auto` parameter like other
7111 // NodeList-fx methods.
a089699c
AD
7112 // duration: Integer?
7113 // Optional. The time to run the animations for
7114 // easing: Function?
7115 // Optional. The easing function to use.
7116 // onEnd: Function?
7117 // A function to be called when the animation ends
7118 // delay:
7119 // how long to delay playing the returned animation
7120 // example:
7121 // Another way to fade out:
7122 // | dojo.query(".thinger").anim({ opacity: 0 });
7123 // example:
7124 // animate all elements with the "thigner" class to a width of 500
7125 // pixels over half a second
7126 // | dojo.query(".thinger").anim({ width: 500 }, 700);
7127 var canim = dojo.fx.combine(
7128 this.map(function(item){
7129 return dojo.animateProperty({
7130 node: item,
7131 properties: properties,
7132 duration: duration||350,
7133 easing: easing
7134 });
7135 })
81bea17a 7136 );
a089699c
AD
7137 if(onEnd){
7138 dojo.connect(canim, "onEnd", onEnd);
7139 }
7140 return canim.play(delay||0); // dojo.Animation
7141 }
7142});
7143
7144}
7145
7146if(!dojo._hasResource["dojo.colors"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7147dojo._hasResource["dojo.colors"] = true;
7148dojo.provide("dojo.colors");
7149
81bea17a
AD
7150dojo.getObject("colors", true, dojo);
7151
a089699c
AD
7152//TODO: this module appears to break naming conventions
7153
7154/*=====
7155dojo.colors = {
7156 // summary: Color utilities
7157}
7158=====*/
7159
7160(function(){
7161 // this is a standard conversion prescribed by the CSS3 Color Module
7162 var hue2rgb = function(m1, m2, h){
7163 if(h < 0){ ++h; }
7164 if(h > 1){ --h; }
7165 var h6 = 6 * h;
7166 if(h6 < 1){ return m1 + (m2 - m1) * h6; }
7167 if(2 * h < 1){ return m2; }
7168 if(3 * h < 2){ return m1 + (m2 - m1) * (2 / 3 - h) * 6; }
7169 return m1;
7170 };
7171
7172 dojo.colorFromRgb = function(/*String*/ color, /*dojo.Color?*/ obj){
7173 // summary:
7174 // get rgb(a) array from css-style color declarations
7175 // description:
7176 // this function can handle all 4 CSS3 Color Module formats: rgb,
7177 // rgba, hsl, hsla, including rgb(a) with percentage values.
7178 var m = color.toLowerCase().match(/^(rgba?|hsla?)\(([\s\.\-,%0-9]+)\)/);
7179 if(m){
7180 var c = m[2].split(/\s*,\s*/), l = c.length, t = m[1], a;
7181 if((t == "rgb" && l == 3) || (t == "rgba" && l == 4)){
7182 var r = c[0];
7183 if(r.charAt(r.length - 1) == "%"){
7184 // 3 rgb percentage values
7185 a = dojo.map(c, function(x){
7186 return parseFloat(x) * 2.56;
7187 });
7188 if(l == 4){ a[3] = c[3]; }
7189 return dojo.colorFromArray(a, obj); // dojo.Color
7190 }
7191 return dojo.colorFromArray(c, obj); // dojo.Color
7192 }
7193 if((t == "hsl" && l == 3) || (t == "hsla" && l == 4)){
7194 // normalize hsl values
7195 var H = ((parseFloat(c[0]) % 360) + 360) % 360 / 360,
7196 S = parseFloat(c[1]) / 100,
7197 L = parseFloat(c[2]) / 100,
81bea17a
AD
7198 // calculate rgb according to the algorithm
7199 // recommended by the CSS3 Color Module
7200 m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S,
a089699c
AD
7201 m1 = 2 * L - m2;
7202 a = [
7203 hue2rgb(m1, m2, H + 1 / 3) * 256,
7204 hue2rgb(m1, m2, H) * 256,
7205 hue2rgb(m1, m2, H - 1 / 3) * 256,
7206 1
7207 ];
7208 if(l == 4){ a[3] = c[3]; }
7209 return dojo.colorFromArray(a, obj); // dojo.Color
7210 }
7211 }
7212 return null; // dojo.Color
7213 };
7214
7215 var confine = function(c, low, high){
7216 // summary:
7217 // sanitize a color component by making sure it is a number,
7218 // and clamping it to valid values
7219 c = Number(c);
7220 return isNaN(c) ? high : c < low ? low : c > high ? high : c; // Number
7221 };
7222
7223 dojo.Color.prototype.sanitize = function(){
7224 // summary: makes sure that the object has correct attributes
7225 var t = this;
7226 t.r = Math.round(confine(t.r, 0, 255));
7227 t.g = Math.round(confine(t.g, 0, 255));
7228 t.b = Math.round(confine(t.b, 0, 255));
7229 t.a = confine(t.a, 0, 1);
7230 return this; // dojo.Color
7231 };
7232})();
7233
7234
7235dojo.colors.makeGrey = function(/*Number*/ g, /*Number?*/ a){
7236 // summary: creates a greyscale color with an optional alpha
7237 return dojo.colorFromArray([g, g, g, a]);
7238};
7239
7240// mixin all CSS3 named colors not already in _base, along with SVG 1.0 variant spellings
7241dojo.mixin(dojo.Color.named, {
7242 aliceblue: [240,248,255],
7243 antiquewhite: [250,235,215],
7244 aquamarine: [127,255,212],
7245 azure: [240,255,255],
7246 beige: [245,245,220],
7247 bisque: [255,228,196],
7248 blanchedalmond: [255,235,205],
7249 blueviolet: [138,43,226],
7250 brown: [165,42,42],
7251 burlywood: [222,184,135],
7252 cadetblue: [95,158,160],
7253 chartreuse: [127,255,0],
7254 chocolate: [210,105,30],
7255 coral: [255,127,80],
7256 cornflowerblue: [100,149,237],
7257 cornsilk: [255,248,220],
7258 crimson: [220,20,60],
7259 cyan: [0,255,255],
7260 darkblue: [0,0,139],
7261 darkcyan: [0,139,139],
7262 darkgoldenrod: [184,134,11],
7263 darkgray: [169,169,169],
7264 darkgreen: [0,100,0],
7265 darkgrey: [169,169,169],
7266 darkkhaki: [189,183,107],
7267 darkmagenta: [139,0,139],
7268 darkolivegreen: [85,107,47],
7269 darkorange: [255,140,0],
7270 darkorchid: [153,50,204],
7271 darkred: [139,0,0],
7272 darksalmon: [233,150,122],
7273 darkseagreen: [143,188,143],
7274 darkslateblue: [72,61,139],
7275 darkslategray: [47,79,79],
7276 darkslategrey: [47,79,79],
7277 darkturquoise: [0,206,209],
7278 darkviolet: [148,0,211],
7279 deeppink: [255,20,147],
7280 deepskyblue: [0,191,255],
7281 dimgray: [105,105,105],
7282 dimgrey: [105,105,105],
7283 dodgerblue: [30,144,255],
7284 firebrick: [178,34,34],
7285 floralwhite: [255,250,240],
7286 forestgreen: [34,139,34],
7287 gainsboro: [220,220,220],
7288 ghostwhite: [248,248,255],
7289 gold: [255,215,0],
7290 goldenrod: [218,165,32],
7291 greenyellow: [173,255,47],
7292 grey: [128,128,128],
7293 honeydew: [240,255,240],
7294 hotpink: [255,105,180],
7295 indianred: [205,92,92],
7296 indigo: [75,0,130],
7297 ivory: [255,255,240],
7298 khaki: [240,230,140],
7299 lavender: [230,230,250],
7300 lavenderblush: [255,240,245],
7301 lawngreen: [124,252,0],
7302 lemonchiffon: [255,250,205],
7303 lightblue: [173,216,230],
7304 lightcoral: [240,128,128],
7305 lightcyan: [224,255,255],
7306 lightgoldenrodyellow: [250,250,210],
7307 lightgray: [211,211,211],
7308 lightgreen: [144,238,144],
7309 lightgrey: [211,211,211],
7310 lightpink: [255,182,193],
7311 lightsalmon: [255,160,122],
7312 lightseagreen: [32,178,170],
7313 lightskyblue: [135,206,250],
7314 lightslategray: [119,136,153],
7315 lightslategrey: [119,136,153],
7316 lightsteelblue: [176,196,222],
7317 lightyellow: [255,255,224],
7318 limegreen: [50,205,50],
7319 linen: [250,240,230],
7320 magenta: [255,0,255],
7321 mediumaquamarine: [102,205,170],
7322 mediumblue: [0,0,205],
7323 mediumorchid: [186,85,211],
7324 mediumpurple: [147,112,219],
7325 mediumseagreen: [60,179,113],
7326 mediumslateblue: [123,104,238],
7327 mediumspringgreen: [0,250,154],
7328 mediumturquoise: [72,209,204],
7329 mediumvioletred: [199,21,133],
7330 midnightblue: [25,25,112],
7331 mintcream: [245,255,250],
7332 mistyrose: [255,228,225],
7333 moccasin: [255,228,181],
7334 navajowhite: [255,222,173],
7335 oldlace: [253,245,230],
7336 olivedrab: [107,142,35],
7337 orange: [255,165,0],
7338 orangered: [255,69,0],
7339 orchid: [218,112,214],
7340 palegoldenrod: [238,232,170],
7341 palegreen: [152,251,152],
7342 paleturquoise: [175,238,238],
7343 palevioletred: [219,112,147],
7344 papayawhip: [255,239,213],
7345 peachpuff: [255,218,185],
7346 peru: [205,133,63],
7347 pink: [255,192,203],
7348 plum: [221,160,221],
7349 powderblue: [176,224,230],
7350 rosybrown: [188,143,143],
7351 royalblue: [65,105,225],
7352 saddlebrown: [139,69,19],
7353 salmon: [250,128,114],
7354 sandybrown: [244,164,96],
7355 seagreen: [46,139,87],
7356 seashell: [255,245,238],
7357 sienna: [160,82,45],
7358 skyblue: [135,206,235],
7359 slateblue: [106,90,205],
7360 slategray: [112,128,144],
7361 slategrey: [112,128,144],
7362 snow: [255,250,250],
7363 springgreen: [0,255,127],
7364 steelblue: [70,130,180],
7365 tan: [210,180,140],
7366 thistle: [216,191,216],
7367 tomato: [255,99,71],
7368 transparent: [0, 0, 0, 0],
7369 turquoise: [64,224,208],
7370 violet: [238,130,238],
7371 wheat: [245,222,179],
7372 whitesmoke: [245,245,245],
7373 yellowgreen: [154,205,50]
7374});
7375
7376}
7377
7378if(!dojo._hasResource["dojo.i18n"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7379dojo._hasResource["dojo.i18n"] = true;
7380dojo.provide("dojo.i18n");
7381
81bea17a
AD
7382dojo.getObject("i18n", true, dojo);
7383
a089699c
AD
7384/*=====
7385dojo.i18n = {
7386 // summary: Utility classes to enable loading of resources for internationalization (i18n)
7387};
7388=====*/
7389
81bea17a
AD
7390// when using a real AMD loader, dojo.i18n.getLocalization is already defined by dojo/lib/backCompat
7391dojo.i18n.getLocalization = dojo.i18n.getLocalization || function(/*String*/packageName, /*String*/bundleName, /*String?*/locale){
a089699c
AD
7392 // summary:
7393 // Returns an Object containing the localization for a given resource
7394 // bundle in a package, matching the specified locale.
7395 // description:
7396 // Returns a hash containing name/value pairs in its prototypesuch
7397 // that values can be easily overridden. Throws an exception if the
7398 // bundle is not found. Bundle must have already been loaded by
7399 // `dojo.requireLocalization()` or by a build optimization step. NOTE:
7400 // try not to call this method as part of an object property
7401 // definition (`var foo = { bar: dojo.i18n.getLocalization() }`). In
7402 // some loading situations, the bundle may not be available in time
7403 // for the object definition. Instead, call this method inside a
7404 // function that is run after all modules load or the page loads (like
7405 // in `dojo.addOnLoad()`), or in a widget lifecycle method.
7406 // packageName:
7407 // package which is associated with this resource
7408 // bundleName:
7409 // the base filename of the resource bundle (without the ".js" suffix)
7410 // locale:
7411 // the variant to load (optional). By default, the locale defined by
7412 // the host environment: dojo.locale
7413
7414 locale = dojo.i18n.normalizeLocale(locale);
7415
7416 // look for nearest locale match
7417 var elements = locale.split('-');
7418 var module = [packageName,"nls",bundleName].join('.');
81bea17a 7419 var bundle = dojo._loadedModules[module];
a089699c
AD
7420 if(bundle){
7421 var localization;
7422 for(var i = elements.length; i > 0; i--){
7423 var loc = elements.slice(0, i).join('_');
7424 if(bundle[loc]){
7425 localization = bundle[loc];
7426 break;
7427 }
7428 }
7429 if(!localization){
7430 localization = bundle.ROOT;
7431 }
7432
7433 // make a singleton prototype so that the caller won't accidentally change the values globally
7434 if(localization){
7435 var clazz = function(){};
7436 clazz.prototype = localization;
7437 return new clazz(); // Object
7438 }
7439 }
7440
7441 throw new Error("Bundle not found: " + bundleName + " in " + packageName+" , locale=" + locale);
7442};
7443
7444dojo.i18n.normalizeLocale = function(/*String?*/locale){
7445 // summary:
7446 // Returns canonical form of locale, as used by Dojo.
7447 //
7448 // description:
7449 // All variants are case-insensitive and are separated by '-' as specified in [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt).
7450 // If no locale is specified, the dojo.locale is returned. dojo.locale is defined by
7451 // the user agent's locale unless overridden by djConfig.
7452
7453 var result = locale ? locale.toLowerCase() : dojo.locale;
7454 if(result == "root"){
7455 result = "ROOT";
7456 }
7457 return result; // String
7458};
7459
7460dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundleName, /*String?*/locale, /*String?*/availableFlatLocales){
7461 // summary:
7462 // See dojo.requireLocalization()
7463 // description:
7464 // Called by the bootstrap, but factored out so that it is only
7465 // included in the build when needed.
7466
7467 var targetLocale = dojo.i18n.normalizeLocale(locale);
7468 var bundlePackage = [moduleName, "nls", bundleName].join(".");
81bea17a 7469 // NOTE:
a089699c
AD
7470 // When loading these resources, the packaging does not match what is
7471 // on disk. This is an implementation detail, as this is just a
7472 // private data structure to hold the loaded resources. e.g.
7473 // `tests/hello/nls/en-us/salutations.js` is loaded as the object
7474 // `tests.hello.nls.salutations.en_us={...}` The structure on disk is
7475 // intended to be most convenient for developers and translators, but
7476 // in memory it is more logical and efficient to store in a different
7477 // order. Locales cannot use dashes, since the resulting path will
7478 // not evaluate as valid JS, so we translate them to underscores.
81bea17a 7479
a089699c
AD
7480 //Find the best-match locale to load if we have available flat locales.
7481 var bestLocale = "";
7482 if(availableFlatLocales){
7483 var flatLocales = availableFlatLocales.split(",");
7484 for(var i = 0; i < flatLocales.length; i++){
7485 //Locale must match from start of string.
7486 //Using ["indexOf"] so customBase builds do not see
7487 //this as a dojo._base.array dependency.
7488 if(targetLocale["indexOf"](flatLocales[i]) == 0){
7489 if(flatLocales[i].length > bestLocale.length){
7490 bestLocale = flatLocales[i];
7491 }
7492 }
7493 }
7494 if(!bestLocale){
7495 bestLocale = "ROOT";
81bea17a 7496 }
a089699c
AD
7497 }
7498
7499 //See if the desired locale is already loaded.
7500 var tempLocale = availableFlatLocales ? bestLocale : targetLocale;
7501 var bundle = dojo._loadedModules[bundlePackage];
7502 var localizedBundle = null;
7503 if(bundle){
7504 if(dojo.config.localizationComplete && bundle._built){return;}
7505 var jsLoc = tempLocale.replace(/-/g, '_');
7506 var translationPackage = bundlePackage+"."+jsLoc;
7507 localizedBundle = dojo._loadedModules[translationPackage];
7508 }
7509
7510 if(!localizedBundle){
7511 bundle = dojo["provide"](bundlePackage);
7512 var syms = dojo._getModuleSymbols(moduleName);
7513 var modpath = syms.concat("nls").join("/");
7514 var parent;
7515
7516 dojo.i18n._searchLocalePath(tempLocale, availableFlatLocales, function(loc){
7517 var jsLoc = loc.replace(/-/g, '_');
7518 var translationPackage = bundlePackage + "." + jsLoc;
7519 var loaded = false;
7520 if(!dojo._loadedModules[translationPackage]){
7521 // Mark loaded whether it's found or not, so that further load attempts will not be made
7522 dojo["provide"](translationPackage);
7523 var module = [modpath];
7524 if(loc != "ROOT"){module.push(loc);}
7525 module.push(bundleName);
7526 var filespec = module.join("/") + '.js';
7527 loaded = dojo._loadPath(filespec, null, function(hash){
81bea17a 7528 hash = hash.root || hash;
a089699c
AD
7529 // Use singleton with prototype to point to parent bundle, then mix-in result from loadPath
7530 var clazz = function(){};
7531 clazz.prototype = parent;
7532 bundle[jsLoc] = new clazz();
7533 for(var j in hash){ bundle[jsLoc][j] = hash[j]; }
7534 });
7535 }else{
7536 loaded = true;
7537 }
7538 if(loaded && bundle[jsLoc]){
7539 parent = bundle[jsLoc];
7540 }else{
7541 bundle[jsLoc] = parent;
7542 }
81bea17a 7543
a089699c
AD
7544 if(availableFlatLocales){
7545 //Stop the locale path searching if we know the availableFlatLocales, since
7546 //the first call to this function will load the only bundle that is needed.
7547 return true;
7548 }
7549 });
7550 }
7551
7552 //Save the best locale bundle as the target locale bundle when we know the
7553 //the available bundles.
7554 if(availableFlatLocales && targetLocale != bestLocale){
7555 bundle[targetLocale.replace(/-/g, '_')] = bundle[bestLocale.replace(/-/g, '_')];
7556 }
7557};
7558
7559(function(){
7560 // If other locales are used, dojo.requireLocalization should load them as
81bea17a
AD
7561 // well, by default.
7562 //
a089699c
AD
7563 // Override dojo.requireLocalization to do load the default bundle, then
7564 // iterate through the extraLocale list and load those translations as
7565 // well, unless a particular locale was requested.
7566
7567 var extra = dojo.config.extraLocale;
7568 if(extra){
7569 if(!extra instanceof Array){
7570 extra = [extra];
7571 }
7572
7573 var req = dojo.i18n._requireLocalization;
7574 dojo.i18n._requireLocalization = function(m, b, locale, availableFlatLocales){
7575 req(m,b,locale, availableFlatLocales);
7576 if(locale){return;}
7577 for(var i=0; i<extra.length; i++){
7578 req(m,b,extra[i], availableFlatLocales);
7579 }
7580 };
7581 }
7582})();
7583
7584dojo.i18n._searchLocalePath = function(/*String*/locale, /*Boolean*/down, /*Function*/searchFunc){
7585 // summary:
7586 // A helper method to assist in searching for locale-based resources.
7587 // Will iterate through the variants of a particular locale, either up
7588 // or down, executing a callback function. For example, "en-us" and
7589 // true will try "en-us" followed by "en" and finally "ROOT".
7590
7591 locale = dojo.i18n.normalizeLocale(locale);
7592
7593 var elements = locale.split('-');
7594 var searchlist = [];
7595 for(var i = elements.length; i > 0; i--){
7596 searchlist.push(elements.slice(0, i).join('-'));
7597 }
7598 searchlist.push(false);
7599 if(down){searchlist.reverse();}
7600
7601 for(var j = searchlist.length - 1; j >= 0; j--){
7602 var loc = searchlist[j] || "ROOT";
7603 var stop = searchFunc(loc);
7604 if(stop){ break; }
7605 }
7606};
7607
7608dojo.i18n._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated){
7609 // summary:
7610 // Load built, flattened resource bundles, if available for all
7611 // locales used in the page. Only called by built layer files.
7612
7613 function preload(locale){
7614 locale = dojo.i18n.normalizeLocale(locale);
7615 dojo.i18n._searchLocalePath(locale, true, function(loc){
7616 for(var i=0; i<localesGenerated.length;i++){
7617 if(localesGenerated[i] == loc){
7618 dojo["require"](bundlePrefix+"_"+loc);
7619 return true; // Boolean
7620 }
7621 }
7622 return false; // Boolean
7623 });
7624 }
7625 preload();
7626 var extra = dojo.config.extraLocale||[];
7627 for(var i=0; i<extra.length; i++){
7628 preload(extra[i]);
7629 }
7630};
7631
7632}
7633
7634if(!dojo._hasResource["dijit._PaletteMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7635dojo._hasResource["dijit._PaletteMixin"] = true;
7636dojo.provide("dijit._PaletteMixin");
7637
7638
81bea17a 7639
a089699c
AD
7640dojo.declare("dijit._PaletteMixin",
7641 [dijit._CssStateMixin],
7642 {
7643 // summary:
7644 // A keyboard accessible palette, for picking a color/emoticon/etc.
7645 // description:
7646 // A mixin for a grid showing various entities, so the user can pick a certain entity.
7647
7648 // defaultTimeout: Number
7649 // Number of milliseconds before a held key or button becomes typematic
7650 defaultTimeout: 500,
7651
7652 // timeoutChangeRate: Number
7653 // Fraction of time used to change the typematic timer between events
7654 // 1.0 means that each typematic event fires at defaultTimeout intervals
7655 // < 1.0 means that each typematic event fires at an increasing faster rate
7656 timeoutChangeRate: 0.90,
7657
7658 // value: String
7659 // Currently selected color/emoticon/etc.
7660 value: null,
7661
7662 // _selectedCell: [private] Integer
7663 // Index of the currently selected cell. Initially, none selected
7664 _selectedCell: -1,
7665
81bea17a 7666/*=====
a089699c
AD
7667 // _currentFocus: [private] DomNode
7668 // The currently focused cell (if the palette itself has focus), or otherwise
7669 // the cell to be focused when the palette itself gets focus.
7670 // Different from value, which represents the selected (i.e. clicked) cell.
a089699c
AD
7671 _currentFocus: null,
7672=====*/
7673
81bea17a 7674/*=====
a089699c
AD
7675 // _xDim: [protected] Integer
7676 // This is the number of cells horizontally across.
a089699c
AD
7677 _xDim: null,
7678=====*/
7679
81bea17a 7680/*=====
a089699c
AD
7681 // _yDim: [protected] Integer
7682 // This is the number of cells vertically down.
a089699c
AD
7683 _yDim: null,
7684=====*/
7685
7686 // tabIndex: String
7687 // Widget tab index.
7688 tabIndex: "0",
7689
7690 // cellClass: [protected] String
7691 // CSS class applied to each cell in the palette
7692 cellClass: "dijitPaletteCell",
7693
7694 // dyeClass: [protected] String
7695 // Name of javascript class for Object created for each cell of the palette.
7696 // dyeClass should implements dijit.Dye interface
7697 dyeClass: '',
7698
81bea17a 7699 _preparePalette: function(choices, titles, dyeClassObj) {
a089699c
AD
7700 // summary:
7701 // Subclass must call _preparePalette() from postCreate(), passing in the tooltip
7702 // for each cell
7703 // choices: String[][]
7704 // id's for each cell of the palette, used to create Dye JS object for each cell
7705 // titles: String[]
7706 // Localized tooltip for each cell
81bea17a
AD
7707 // dyeClassObj: Constructor?
7708 // If specified, use this constructor rather than this.dyeClass
a089699c
AD
7709
7710 this._cells = [];
7711 var url = this._blankGif;
7712
81bea17a 7713 dyeClassObj = dyeClassObj || dojo.getObject(this.dyeClass);
a089699c
AD
7714
7715 for(var row=0; row < choices.length; row++){
7716 var rowNode = dojo.create("tr", {tabIndex: "-1"}, this.gridNode);
7717 for(var col=0; col < choices[row].length; col++){
7718 var value = choices[row][col];
7719 if(value){
81bea17a 7720 var cellObject = new dyeClassObj(value, row, col);
a089699c
AD
7721
7722 var cellNode = dojo.create("td", {
7723 "class": this.cellClass,
7724 tabIndex: "-1",
7725 title: titles[value]
7726 });
7727
7728 // prepare cell inner structure
7729 cellObject.fillCell(cellNode, url);
7730
7731 this.connect(cellNode, "ondijitclick", "_onCellClick");
7732 this._trackMouseState(cellNode, this.cellClass);
7733
7734 dojo.place(cellNode, rowNode);
7735
7736 cellNode.index = this._cells.length;
7737
7738 // save cell info into _cells
7739 this._cells.push({node:cellNode, dye:cellObject});
7740 }
7741 }
7742 }
7743 this._xDim = choices[0].length;
7744 this._yDim = choices.length;
7745
7746 // Now set all events
7747 // The palette itself is navigated to with the tab key on the keyboard
7748 // Keyboard navigation within the Palette is with the arrow keys
7749 // Spacebar selects the cell.
7750 // For the up key the index is changed by negative the x dimension.
7751
7752 var keyIncrementMap = {
7753 UP_ARROW: -this._xDim,
7754 // The down key the index is increase by the x dimension.
7755 DOWN_ARROW: this._xDim,
7756 // Right and left move the index by 1.
7757 RIGHT_ARROW: this.isLeftToRight() ? 1 : -1,
7758 LEFT_ARROW: this.isLeftToRight() ? -1 : 1
7759 };
7760 for(var key in keyIncrementMap){
7761 this._connects.push(
7762 dijit.typematic.addKeyListener(
7763 this.domNode,
7764 {charOrCode:dojo.keys[key], ctrlKey:false, altKey:false, shiftKey:false},
7765 this,
7766 function(){
7767 var increment = keyIncrementMap[key];
7768 return function(count){ this._navigateByKey(increment, count); };
7769 }(),
7770 this.timeoutChangeRate,
7771 this.defaultTimeout
7772 )
7773 );
7774 }
7775 },
7776
7777 postCreate: function(){
7778 this.inherited(arguments);
7779
7780 // Set initial navigable node.
7781 this._setCurrent(this._cells[0].node);
7782 },
7783
7784 focus: function(){
7785 // summary:
7786 // Focus this widget. Puts focus on the most recently focused cell.
7787
7788 // The cell already has tabIndex set, just need to set CSS and focus it
7789 dijit.focus(this._currentFocus);
7790 },
7791
7792 _onCellClick: function(/*Event*/ evt){
7793 // summary:
7794 // Handler for click, enter key & space key. Selects the cell.
7795 // evt:
7796 // The event.
7797 // tags:
7798 // private
7799
81bea17a 7800 var target = evt.currentTarget,
a089699c
AD
7801 value = this._getDye(target).getValue();
7802
7803 // First focus the clicked cell, and then send onChange() notification.
7804 // onChange() (via _setValueAttr) must be after the focus call, because
7805 // it may trigger a refocus to somewhere else (like the Editor content area), and that
7806 // second focus should win.
7807 // Use setTimeout because IE doesn't like changing focus inside of an event handler.
7808 this._setCurrent(target);
7809 setTimeout(dojo.hitch(this, function(){
81bea17a
AD
7810 dijit.focus(target);
7811 this._setValueAttr(value, true);
a089699c
AD
7812 }));
7813
7814 // workaround bug where hover class is not removed on popup because the popup is
7815 // closed and then there's no onblur event on the cell
7816 dojo.removeClass(target, "dijitPaletteCellHover");
7817
7818 dojo.stopEvent(evt);
7819 },
7820
7821 _setCurrent: function(/*DomNode*/ node){
7822 // summary:
7823 // Sets which node is the focused cell.
7824 // description:
7825 // At any point in time there's exactly one
7826 // cell with tabIndex != -1. If focus is inside the palette then
7827 // focus is on that cell.
7828 //
7829 // After calling this method, arrow key handlers and mouse click handlers
7830 // should focus the cell in a setTimeout().
7831 // tags:
7832 // protected
7833 if("_currentFocus" in this){
7834 // Remove tabIndex on old cell
7835 dojo.attr(this._currentFocus, "tabIndex", "-1");
7836 }
7837
7838 // Set tabIndex of new cell
7839 this._currentFocus = node;
7840 if(node){
7841 dojo.attr(node, "tabIndex", this.tabIndex);
7842 }
7843 },
7844
7845 _setValueAttr: function(value, priorityChange){
7846 // summary:
7847 // This selects a cell. It triggers the onChange event.
7848 // value: String value of the cell to select
7849 // tags:
7850 // protected
7851 // priorityChange:
7852 // Optional parameter used to tell the select whether or not to fire
7853 // onChange event.
7854
81bea17a 7855 // clear old selected cell
a089699c
AD
7856 if(this._selectedCell >= 0){
7857 dojo.removeClass(this._cells[this._selectedCell].node, "dijitPaletteCellSelected");
7858 }
7859 this._selectedCell = -1;
7860
7861 // search for cell matching specified value
7862 if(value){
7863 for(var i = 0; i < this._cells.length; i++){
7864 if(value == this._cells[i].dye.getValue()){
7865 this._selectedCell = i;
a089699c 7866 dojo.addClass(this._cells[i].node, "dijitPaletteCellSelected");
a089699c
AD
7867 break;
7868 }
7869 }
7870 }
81bea17a
AD
7871
7872 // record new value, or null if no matching cell
7873 this._set("value", this._selectedCell >= 0 ? value : null);
7874
7875 if(priorityChange || priorityChange === undefined){
7876 this.onChange(value);
7877 }
a089699c
AD
7878 },
7879
7880 onChange: function(value){
7881 // summary:
7882 // Callback when a cell is selected.
7883 // value: String
7884 // Value corresponding to cell.
7885 },
7886
7887 _navigateByKey: function(increment, typeCount){
7888 // summary:
7889 // This is the callback for typematic.
7890 // It changes the focus and the highlighed cell.
7891 // increment:
7892 // How much the key is navigated.
7893 // typeCount:
7894 // How many times typematic has fired.
7895 // tags:
7896 // private
7897
7898 // typecount == -1 means the key is released.
7899 if(typeCount == -1){ return; }
7900
7901 var newFocusIndex = this._currentFocus.index + increment;
7902 if(newFocusIndex < this._cells.length && newFocusIndex > -1){
7903 var focusNode = this._cells[newFocusIndex].node;
7904 this._setCurrent(focusNode);
7905
7906 // Actually focus the node, for the benefit of screen readers.
7907 // Use setTimeout because IE doesn't like changing focus inside of an event handler
7908 setTimeout(dojo.hitch(dijit, "focus", focusNode), 0);
7909 }
7910 },
7911
7912 _getDye: function(/*DomNode*/ cell){
7913 // summary:
7914 // Get JS object for given cell DOMNode
7915
7916 return this._cells[cell.index].dye;
7917 }
7918});
7919
7920/*=====
7921dojo.declare("dijit.Dye",
7922 null,
7923 {
7924 // summary:
7925 // Interface for the JS Object associated with a palette cell (i.e. DOMNode)
7926
81bea17a 7927 constructor: function(alias, row, col){
a089699c
AD
7928 // summary:
7929 // Initialize according to value or alias like "white"
7930 // alias: String
7931 },
7932
7933 getValue: function(){
7934 // summary:
7935 // Return "value" of cell; meaning of "value" varies by subclass.
7936 // description:
7937 // For example color hex value, emoticon ascii value etc, entity hex value.
7938 },
7939
7940 fillCell: function(cell, blankGif){
7941 // summary:
7942 // Add cell DOMNode inner structure
7943 // cell: DomNode
7944 // The surrounding cell
7945 // blankGif: String
7946 // URL for blank cell image
7947 }
7948 }
7949);
7950=====*/
7951
7952}
7953
7954if(!dojo._hasResource["dijit.ColorPalette"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
7955dojo._hasResource["dijit.ColorPalette"] = true;
7956dojo.provide("dijit.ColorPalette");
7957
7958
7959
7960
7961
7962
7963
7964
7965
a089699c
AD
7966dojo.declare("dijit.ColorPalette",
7967 [dijit._Widget, dijit._Templated, dijit._PaletteMixin],
7968 {
7969 // summary:
7970 // A keyboard accessible color-picking widget
7971 // description:
7972 // Grid showing various colors, so the user can pick a certain color.
7973 // Can be used standalone, or as a popup.
7974 //
7975 // example:
7976 // | <div dojoType="dijit.ColorPalette"></div>
7977 //
7978 // example:
7979 // | var picker = new dijit.ColorPalette({ },srcNode);
7980 // | picker.startup();
7981
7982
81bea17a 7983 // palette: [const] String
a089699c
AD
7984 // Size of grid, either "7x10" or "3x4".
7985 palette: "7x10",
7986
7987 // _palettes: [protected] Map
7988 // This represents the value of the colors.
7989 // The first level is a hashmap of the different palettes available.
7990 // The next two dimensions represent the columns and rows of colors.
7991 _palettes: {
7992 "7x10": [["white", "seashell", "cornsilk", "lemonchiffon","lightyellow", "palegreen", "paleturquoise", "lightcyan", "lavender", "plum"],
7993 ["lightgray", "pink", "bisque", "moccasin", "khaki", "lightgreen", "lightseagreen", "lightskyblue", "cornflowerblue", "violet"],
7994 ["silver", "lightcoral", "sandybrown", "orange", "palegoldenrod", "chartreuse", "mediumturquoise", "skyblue", "mediumslateblue","orchid"],
7995 ["gray", "red", "orangered", "darkorange", "yellow", "limegreen", "darkseagreen", "royalblue", "slateblue", "mediumorchid"],
7996 ["dimgray", "crimson", "chocolate", "coral", "gold", "forestgreen", "seagreen", "blue", "blueviolet", "darkorchid"],
7997 ["darkslategray","firebrick","saddlebrown", "sienna", "olive", "green", "darkcyan", "mediumblue","darkslateblue", "darkmagenta" ],
7998 ["black", "darkred", "maroon", "brown", "darkolivegreen", "darkgreen", "midnightblue", "navy", "indigo", "purple"]],
7999
8000 "3x4": [["white", "lime", "green", "blue"],
8001 ["silver", "yellow", "fuchsia", "navy"],
8002 ["gray", "red", "purple", "black"]]
8003 },
8004
a089699c
AD
8005 // templateString: String
8006 // The template of this widget.
81bea17a 8007 templateString: dojo.cache("dijit", "templates/ColorPalette.html", "<div class=\"dijitInline dijitColorPalette\">\n\t<table class=\"dijitPaletteTable\" cellSpacing=\"0\" cellPadding=\"0\">\n\t\t<tbody dojoAttachPoint=\"gridNode\"></tbody>\n\t</table>\n</div>\n"),
a089699c
AD
8008
8009 baseClass: "dijitColorPalette",
8010
a089699c
AD
8011 buildRendering: function(){
8012 // Instantiate the template, which makes a skeleton into which we'll insert a bunch of
8013 // <img> nodes
a089699c
AD
8014 this.inherited(arguments);
8015
81bea17a
AD
8016 // Creates <img> nodes in each cell of the template.
8017 // Pass in "customized" dijit._Color constructor for specified palette and high-contrast vs. normal mode
a089699c
AD
8018 this._preparePalette(
8019 this._palettes[this.palette],
81bea17a
AD
8020 dojo.i18n.getLocalization("dojo", "colors", this.lang),
8021 dojo.declare(dijit._Color, {
8022 hc: dojo.hasClass(dojo.body(), "dijit_a11y"),
8023 palette: this.palette
8024 })
a089699c
AD
8025 );
8026 }
8027});
8028
81bea17a 8029dojo.declare("dijit._Color", dojo.Color, {
a089699c
AD
8030 // summary:
8031 // Object associated with each cell in a ColorPalette palette.
8032 // Implements dijit.Dye.
a089699c 8033
81bea17a
AD
8034 // Template for each cell in normal (non-high-contrast mode). Each cell contains a wrapper
8035 // node for showing the border (called dijitPaletteImg for back-compat), and dijitColorPaletteSwatch
8036 // for showing the color.
8037 template:
8038 "<span class='dijitInline dijitPaletteImg'>" +
8039 "<img src='${blankGif}' alt='${alt}' class='dijitColorPaletteSwatch' style='background-color: ${color}'/>" +
8040 "</span>",
8041
8042 // Template for each cell in high contrast mode. Each cell contains an image with the whole palette,
8043 // but scrolled and clipped to show the correct color only
8044 hcTemplate:
8045 "<span class='dijitInline dijitPaletteImg' style='position: relative; overflow: hidden; height: 12px; width: 14px;'>" +
8046 "<img src='${image}' alt='${alt}' style='position: absolute; left: ${left}px; top: ${top}px; ${size}'/>" +
8047 "</span>",
a089699c 8048
81bea17a
AD
8049 // _imagePaths: [protected] Map
8050 // This is stores the path to the palette images used for high-contrast mode display
8051 _imagePaths: {
8052 "7x10": dojo.moduleUrl("dijit.themes", "a11y/colors7x10.png"),
8053 "3x4": dojo.moduleUrl("dijit.themes", "a11y/colors3x4.png")
8054 },
8055
8056 constructor: function(/*String*/alias, /*Number*/ row, /*Number*/ col){
8057 this._alias = alias;
8058 this._row = row;
8059 this._col = col;
8060 this.setColor(dojo.Color.named[alias]);
8061 },
8062
8063 getValue: function(){
8064 // summary:
8065 // Note that although dijit._Color is initialized with a value like "white" getValue() always
8066 // returns a hex value
8067 return this.toHex();
8068 },
8069
8070 fillCell: function(/*DOMNode*/ cell, /*String*/ blankGif){
8071 var html = dojo.string.substitute(this.hc ? this.hcTemplate : this.template, {
8072 // substitution variables for normal mode
8073 color: this.toHex(),
8074 blankGif: blankGif,
8075 alt: this._alias,
8076
8077 // variables used for high contrast mode
8078 image: this._imagePaths[this.palette].toString(),
8079 left: this._col * -20 - 5,
8080 top: this._row * -20 - 5,
8081 size: this.palette == "7x10" ? "height: 145px; width: 206px" : "height: 64px; width: 86px"
8082 });
8083
8084 dojo.place(html, cell);
a089699c 8085 }
81bea17a 8086});
a089699c
AD
8087
8088}
8089
8090if(!dojo._hasResource["dojo.dnd.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8091dojo._hasResource["dojo.dnd.common"] = true;
8092dojo.provide("dojo.dnd.common");
8093
81bea17a
AD
8094dojo.getObject("dnd", true, dojo);
8095
a089699c
AD
8096dojo.dnd.getCopyKeyState = dojo.isCopyKey;
8097
8098dojo.dnd._uniqueId = 0;
8099dojo.dnd.getUniqueId = function(){
8100 // summary:
8101 // returns a unique string for use with any DOM element
8102 var id;
8103 do{
8104 id = dojo._scopeName + "Unique" + (++dojo.dnd._uniqueId);
8105 }while(dojo.byId(id));
8106 return id;
8107};
8108
8109dojo.dnd._empty = {};
8110
8111dojo.dnd.isFormElement = function(/*Event*/ e){
8112 // summary:
8113 // returns true if user clicked on a form element
8114 var t = e.target;
8115 if(t.nodeType == 3 /*TEXT_NODE*/){
8116 t = t.parentNode;
8117 }
8118 return " button textarea input select option ".indexOf(" " + t.tagName.toLowerCase() + " ") >= 0; // Boolean
8119};
8120
8121}
8122
8123if(!dojo._hasResource["dojo.dnd.autoscroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8124dojo._hasResource["dojo.dnd.autoscroll"] = true;
8125dojo.provide("dojo.dnd.autoscroll");
8126
81bea17a
AD
8127
8128dojo.getObject("dnd", true, dojo);
8129
8130dojo.dnd.getViewport = dojo.window.getBox;
a089699c
AD
8131
8132dojo.dnd.V_TRIGGER_AUTOSCROLL = 32;
8133dojo.dnd.H_TRIGGER_AUTOSCROLL = 32;
8134
8135dojo.dnd.V_AUTOSCROLL_VALUE = 16;
8136dojo.dnd.H_AUTOSCROLL_VALUE = 16;
8137
8138dojo.dnd.autoScroll = function(e){
8139 // summary:
8140 // a handler for onmousemove event, which scrolls the window, if
8141 // necesary
8142 // e: Event
8143 // onmousemove event
8144
8145 // FIXME: needs more docs!
81bea17a 8146 var v = dojo.window.getBox(), dx = 0, dy = 0;
a089699c
AD
8147 if(e.clientX < dojo.dnd.H_TRIGGER_AUTOSCROLL){
8148 dx = -dojo.dnd.H_AUTOSCROLL_VALUE;
8149 }else if(e.clientX > v.w - dojo.dnd.H_TRIGGER_AUTOSCROLL){
8150 dx = dojo.dnd.H_AUTOSCROLL_VALUE;
8151 }
8152 if(e.clientY < dojo.dnd.V_TRIGGER_AUTOSCROLL){
8153 dy = -dojo.dnd.V_AUTOSCROLL_VALUE;
8154 }else if(e.clientY > v.h - dojo.dnd.V_TRIGGER_AUTOSCROLL){
8155 dy = dojo.dnd.V_AUTOSCROLL_VALUE;
8156 }
8157 window.scrollBy(dx, dy);
8158};
8159
8160dojo.dnd._validNodes = {"div": 1, "p": 1, "td": 1};
8161dojo.dnd._validOverflow = {"auto": 1, "scroll": 1};
8162
8163dojo.dnd.autoScrollNodes = function(e){
8164 // summary:
8165 // a handler for onmousemove event, which scrolls the first avaialble
8166 // Dom element, it falls back to dojo.dnd.autoScroll()
8167 // e: Event
8168 // onmousemove event
8169
8170 // FIXME: needs more docs!
8171 for(var n = e.target; n;){
8172 if(n.nodeType == 1 && (n.tagName.toLowerCase() in dojo.dnd._validNodes)){
8173 var s = dojo.getComputedStyle(n);
8174 if(s.overflow.toLowerCase() in dojo.dnd._validOverflow){
8175 var b = dojo._getContentBox(n, s), t = dojo.position(n, true);
8176 //console.log(b.l, b.t, t.x, t.y, n.scrollLeft, n.scrollTop);
81bea17a 8177 var w = Math.min(dojo.dnd.H_TRIGGER_AUTOSCROLL, b.w / 2),
a089699c
AD
8178 h = Math.min(dojo.dnd.V_TRIGGER_AUTOSCROLL, b.h / 2),
8179 rx = e.pageX - t.x, ry = e.pageY - t.y, dx = 0, dy = 0;
8180 if(dojo.isWebKit || dojo.isOpera){
81bea17a 8181 // FIXME: this code should not be here, it should be taken into account
a089699c
AD
8182 // either by the event fixing code, or the dojo.position()
8183 // FIXME: this code doesn't work on Opera 9.5 Beta
81bea17a
AD
8184 rx += dojo.body().scrollLeft;
8185 ry += dojo.body().scrollTop;
a089699c
AD
8186 }
8187 if(rx > 0 && rx < b.w){
8188 if(rx < w){
8189 dx = -w;
8190 }else if(rx > b.w - w){
8191 dx = w;
8192 }
8193 }
8194 //console.log("ry =", ry, "b.h =", b.h, "h =", h);
8195 if(ry > 0 && ry < b.h){
8196 if(ry < h){
8197 dy = -h;
8198 }else if(ry > b.h - h){
8199 dy = h;
8200 }
8201 }
8202 var oldLeft = n.scrollLeft, oldTop = n.scrollTop;
8203 n.scrollLeft = n.scrollLeft + dx;
8204 n.scrollTop = n.scrollTop + dy;
8205 if(oldLeft != n.scrollLeft || oldTop != n.scrollTop){ return; }
8206 }
8207 }
8208 try{
8209 n = n.parentNode;
8210 }catch(x){
8211 n = null;
8212 }
8213 }
8214 dojo.dnd.autoScroll(e);
8215};
8216
8217}
8218
8219if(!dojo._hasResource["dojo.dnd.Mover"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8220dojo._hasResource["dojo.dnd.Mover"] = true;
8221dojo.provide("dojo.dnd.Mover");
8222
8223
8224
8225
8226dojo.declare("dojo.dnd.Mover", null, {
8227 constructor: function(node, e, host){
8228 // summary:
81bea17a 8229 // an object which makes a node follow the mouse, or touch-drag on touch devices.
a089699c
AD
8230 // Used as a default mover, and as a base class for custom movers.
8231 // node: Node
8232 // a node (or node's id) to be moved
8233 // e: Event
8234 // a mouse event, which started the move;
8235 // only pageX and pageY properties are used
8236 // host: Object?
8237 // object which implements the functionality of the move,
8238 // and defines proper events (onMoveStart and onMoveStop)
8239 this.node = dojo.byId(node);
81bea17a
AD
8240 var pos = e.touches ? e.touches[0] : e;
8241 this.marginBox = {l: pos.pageX, t: pos.pageY};
a089699c 8242 this.mouseButton = e.button;
81bea17a 8243 var h = (this.host = host), d = node.ownerDocument;
a089699c 8244 this.events = [
81bea17a
AD
8245 // At the start of a drag, onFirstMove is called, and then the following two
8246 // connects are disconnected
8247 dojo.connect(d, "onmousemove", this, "onFirstMove"),
8248 dojo.connect(d, "ontouchmove", this, "onFirstMove"),
8249
8250 // These are called continually during the drag
a089699c 8251 dojo.connect(d, "onmousemove", this, "onMouseMove"),
81bea17a
AD
8252 dojo.connect(d, "ontouchmove", this, "onMouseMove"),
8253
8254 // And these are called at the end of the drag
a089699c 8255 dojo.connect(d, "onmouseup", this, "onMouseUp"),
81bea17a
AD
8256 dojo.connect(d, "ontouchend", this, "onMouseUp"),
8257
a089699c
AD
8258 // cancel text selection and text dragging
8259 dojo.connect(d, "ondragstart", dojo.stopEvent),
81bea17a 8260 dojo.connect(d.body, "onselectstart", dojo.stopEvent)
a089699c
AD
8261 ];
8262 // notify that the move has started
8263 if(h && h.onMoveStart){
8264 h.onMoveStart(this);
8265 }
8266 },
8267 // mouse event processors
8268 onMouseMove: function(e){
8269 // summary:
81bea17a 8270 // event processor for onmousemove/ontouchmove
a089699c 8271 // e: Event
81bea17a 8272 // mouse/touch event
a089699c 8273 dojo.dnd.autoScroll(e);
81bea17a
AD
8274 var m = this.marginBox,
8275 pos = e.touches ? e.touches[0] : e;
8276 this.host.onMove(this, {l: m.l + pos.pageX, t: m.t + pos.pageY}, e);
a089699c
AD
8277 dojo.stopEvent(e);
8278 },
8279 onMouseUp: function(e){
81bea17a
AD
8280 if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ?
8281 e.button == 0 : this.mouseButton == e.button){ // TODO Should condition be met for touch devices, too?
a089699c
AD
8282 this.destroy();
8283 }
8284 dojo.stopEvent(e);
8285 },
8286 // utilities
8287 onFirstMove: function(e){
8288 // summary:
81bea17a 8289 // makes the node absolute; it is meant to be called only once.
a089699c
AD
8290 // relative and absolutely positioned nodes are assumed to use pixel units
8291 var s = this.node.style, l, t, h = this.host;
8292 switch(s.position){
8293 case "relative":
8294 case "absolute":
8295 // assume that left and top values are in pixels already
8296 l = Math.round(parseFloat(s.left)) || 0;
8297 t = Math.round(parseFloat(s.top)) || 0;
8298 break;
8299 default:
8300 s.position = "absolute"; // enforcing the absolute mode
8301 var m = dojo.marginBox(this.node);
8302 // event.pageX/pageY (which we used to generate the initial
8303 // margin box) includes padding and margin set on the body.
8304 // However, setting the node's position to absolute and then
8305 // doing dojo.marginBox on it *doesn't* take that additional
8306 // space into account - so we need to subtract the combined
8307 // padding and margin. We use getComputedStyle and
8308 // _getMarginBox/_getContentBox to avoid the extra lookup of
81bea17a 8309 // the computed style.
a089699c
AD
8310 var b = dojo.doc.body;
8311 var bs = dojo.getComputedStyle(b);
8312 var bm = dojo._getMarginBox(b, bs);
8313 var bc = dojo._getContentBox(b, bs);
8314 l = m.l - (bc.l - bm.l);
8315 t = m.t - (bc.t - bm.t);
8316 break;
8317 }
8318 this.marginBox.l = l - this.marginBox.l;
8319 this.marginBox.t = t - this.marginBox.t;
8320 if(h && h.onFirstMove){
8321 h.onFirstMove(this, e);
8322 }
81bea17a
AD
8323
8324 // Disconnect onmousemove and ontouchmove events that call this function
8325 dojo.disconnect(this.events.shift());
8326 dojo.disconnect(this.events.shift());
a089699c
AD
8327 },
8328 destroy: function(){
8329 // summary:
8330 // stops the move, deletes all references, so the object can be garbage-collected
8331 dojo.forEach(this.events, dojo.disconnect);
8332 // undo global settings
8333 var h = this.host;
8334 if(h && h.onMoveStop){
8335 h.onMoveStop(this);
8336 }
8337 // destroy objects
8338 this.events = this.node = this.host = null;
8339 }
8340});
8341
8342}
8343
8344if(!dojo._hasResource["dojo.dnd.Moveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8345dojo._hasResource["dojo.dnd.Moveable"] = true;
8346dojo.provide("dojo.dnd.Moveable");
8347
8348
8349
8350/*=====
8351dojo.declare("dojo.dnd.__MoveableArgs", [], {
8352 // handle: Node||String
8353 // A node (or node's id), which is used as a mouse handle.
8354 // If omitted, the node itself is used as a handle.
8355 handle: null,
8356
8357 // delay: Number
8358 // delay move by this number of pixels
8359 delay: 0,
8360
8361 // skip: Boolean
8362 // skip move of form elements
8363 skip: false,
8364
8365 // mover: Object
8366 // a constructor of custom Mover
8367 mover: dojo.dnd.Mover
8368});
8369=====*/
8370
8371dojo.declare("dojo.dnd.Moveable", null, {
8372 // object attributes (for markup)
8373 handle: "",
8374 delay: 0,
8375 skip: false,
8376
8377 constructor: function(node, params){
8378 // summary:
8379 // an object, which makes a node moveable
8380 // node: Node
8381 // a node (or node's id) to be moved
8382 // params: dojo.dnd.__MoveableArgs?
8383 // optional parameters
8384 this.node = dojo.byId(node);
8385 if(!params){ params = {}; }
8386 this.handle = params.handle ? dojo.byId(params.handle) : null;
8387 if(!this.handle){ this.handle = this.node; }
8388 this.delay = params.delay > 0 ? params.delay : 0;
8389 this.skip = params.skip;
8390 this.mover = params.mover ? params.mover : dojo.dnd.Mover;
8391 this.events = [
8392 dojo.connect(this.handle, "onmousedown", this, "onMouseDown"),
81bea17a 8393 dojo.connect(this.handle, "ontouchstart", this, "onMouseDown"),
a089699c
AD
8394 // cancel text selection and text dragging
8395 dojo.connect(this.handle, "ondragstart", this, "onSelectStart"),
8396 dojo.connect(this.handle, "onselectstart", this, "onSelectStart")
8397 ];
8398 },
8399
8400 // markup methods
8401 markupFactory: function(params, node){
8402 return new dojo.dnd.Moveable(node, params);
8403 },
8404
8405 // methods
8406 destroy: function(){
8407 // summary:
8408 // stops watching for possible move, deletes all references, so the object can be garbage-collected
8409 dojo.forEach(this.events, dojo.disconnect);
8410 this.events = this.node = this.handle = null;
8411 },
8412
8413 // mouse event processors
8414 onMouseDown: function(e){
8415 // summary:
81bea17a 8416 // event processor for onmousedown/ontouchstart, creates a Mover for the node
a089699c 8417 // e: Event
81bea17a 8418 // mouse/touch event
a089699c
AD
8419 if(this.skip && dojo.dnd.isFormElement(e)){ return; }
8420 if(this.delay){
8421 this.events.push(
8422 dojo.connect(this.handle, "onmousemove", this, "onMouseMove"),
81bea17a
AD
8423 dojo.connect(this.handle, "ontouchmove", this, "onMouseMove"),
8424 dojo.connect(this.handle, "onmouseup", this, "onMouseUp"),
8425 dojo.connect(this.handle, "ontouchend", this, "onMouseUp")
a089699c 8426 );
81bea17a
AD
8427 var pos = e.touches ? e.touches[0] : e;
8428 this._lastX = pos.pageX;
8429 this._lastY = pos.pageY;
a089699c
AD
8430 }else{
8431 this.onDragDetected(e);
8432 }
8433 dojo.stopEvent(e);
8434 },
8435 onMouseMove: function(e){
8436 // summary:
81bea17a 8437 // event processor for onmousemove/ontouchmove, used only for delayed drags
a089699c 8438 // e: Event
81bea17a
AD
8439 // mouse/touch event
8440 var pos = e.touches ? e.touches[0] : e;
8441 if(Math.abs(pos.pageX - this._lastX) > this.delay || Math.abs(pos.pageY - this._lastY) > this.delay){
a089699c
AD
8442 this.onMouseUp(e);
8443 this.onDragDetected(e);
8444 }
8445 dojo.stopEvent(e);
8446 },
8447 onMouseUp: function(e){
8448 // summary:
8449 // event processor for onmouseup, used only for delayed drags
8450 // e: Event
8451 // mouse event
8452 for(var i = 0; i < 2; ++i){
8453 dojo.disconnect(this.events.pop());
8454 }
8455 dojo.stopEvent(e);
8456 },
8457 onSelectStart: function(e){
8458 // summary:
8459 // event processor for onselectevent and ondragevent
8460 // e: Event
8461 // mouse event
8462 if(!this.skip || !dojo.dnd.isFormElement(e)){
8463 dojo.stopEvent(e);
8464 }
8465 },
8466
8467 // local events
8468 onDragDetected: function(/* Event */ e){
8469 // summary:
8470 // called when the drag is detected;
8471 // responsible for creation of the mover
8472 new this.mover(this.node, e, this);
8473 },
8474 onMoveStart: function(/* dojo.dnd.Mover */ mover){
8475 // summary:
8476 // called before every move operation
8477 dojo.publish("/dnd/move/start", [mover]);
81bea17a
AD
8478 dojo.addClass(dojo.body(), "dojoMove");
8479 dojo.addClass(this.node, "dojoMoveItem");
a089699c
AD
8480 },
8481 onMoveStop: function(/* dojo.dnd.Mover */ mover){
8482 // summary:
8483 // called after every move operation
8484 dojo.publish("/dnd/move/stop", [mover]);
8485 dojo.removeClass(dojo.body(), "dojoMove");
8486 dojo.removeClass(this.node, "dojoMoveItem");
8487 },
8488 onFirstMove: function(/* dojo.dnd.Mover */ mover, /* Event */ e){
8489 // summary:
8490 // called during the very first move notification;
8491 // can be used to initialize coordinates, can be overwritten.
8492
8493 // default implementation does nothing
8494 },
8495 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop, /* Event */ e){
8496 // summary:
8497 // called during every move notification;
8498 // should actually move the node; can be overwritten.
8499 this.onMoving(mover, leftTop);
8500 var s = mover.node.style;
8501 s.left = leftTop.l + "px";
8502 s.top = leftTop.t + "px";
8503 this.onMoved(mover, leftTop);
8504 },
8505 onMoving: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8506 // summary:
8507 // called before every incremental move; can be overwritten.
8508
8509 // default implementation does nothing
8510 },
8511 onMoved: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8512 // summary:
8513 // called after every incremental move; can be overwritten.
8514
8515 // default implementation does nothing
8516 }
8517});
8518
8519}
8520
8521if(!dojo._hasResource["dojo.dnd.move"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8522dojo._hasResource["dojo.dnd.move"] = true;
8523dojo.provide("dojo.dnd.move");
8524
8525
8526
8527
8528/*=====
8529dojo.declare("dojo.dnd.move.__constrainedMoveableArgs", [dojo.dnd.__MoveableArgs], {
8530 // constraints: Function
8531 // Calculates a constraint box.
8532 // It is called in a context of the moveable object.
8533 constraints: function(){},
8534
8535 // within: Boolean
8536 // restrict move within boundaries.
8537 within: false
8538});
8539=====*/
8540
8541dojo.declare("dojo.dnd.move.constrainedMoveable", dojo.dnd.Moveable, {
8542 // object attributes (for markup)
8543 constraints: function(){},
8544 within: false,
8545
8546 // markup methods
8547 markupFactory: function(params, node){
8548 return new dojo.dnd.move.constrainedMoveable(node, params);
8549 },
8550
8551 constructor: function(node, params){
8552 // summary:
8553 // an object that makes a node moveable
8554 // node: Node
8555 // a node (or node's id) to be moved
8556 // params: dojo.dnd.move.__constrainedMoveableArgs?
8557 // an optional object with additional parameters;
8558 // the rest is passed to the base class
8559 if(!params){ params = {}; }
8560 this.constraints = params.constraints;
8561 this.within = params.within;
8562 },
8563 onFirstMove: function(/* dojo.dnd.Mover */ mover){
8564 // summary:
8565 // called during the very first move notification;
8566 // can be used to initialize coordinates, can be overwritten.
8567 var c = this.constraintBox = this.constraints.call(this, mover);
8568 c.r = c.l + c.w;
8569 c.b = c.t + c.h;
8570 if(this.within){
81bea17a 8571 var mb = dojo._getMarginSize(mover.node);
a089699c
AD
8572 c.r -= mb.w;
8573 c.b -= mb.h;
8574 }
8575 },
8576 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8577 // summary:
8578 // called during every move notification;
8579 // should actually move the node; can be overwritten.
8580 var c = this.constraintBox, s = mover.node.style;
81bea17a
AD
8581 this.onMoving(mover, leftTop);
8582 leftTop.l = leftTop.l < c.l ? c.l : c.r < leftTop.l ? c.r : leftTop.l;
8583 leftTop.t = leftTop.t < c.t ? c.t : c.b < leftTop.t ? c.b : leftTop.t;
8584 s.left = leftTop.l + "px";
8585 s.top = leftTop.t + "px";
8586 this.onMoved(mover, leftTop);
a089699c
AD
8587 }
8588});
8589
8590/*=====
8591dojo.declare("dojo.dnd.move.__boxConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], {
8592 // box: Object
8593 // a constraint box
8594 box: {}
8595});
8596=====*/
8597
8598dojo.declare("dojo.dnd.move.boxConstrainedMoveable", dojo.dnd.move.constrainedMoveable, {
8599 // box:
8600 // object attributes (for markup)
8601 box: {},
8602
8603 // markup methods
8604 markupFactory: function(params, node){
8605 return new dojo.dnd.move.boxConstrainedMoveable(node, params);
8606 },
8607
8608 constructor: function(node, params){
8609 // summary:
8610 // an object, which makes a node moveable
8611 // node: Node
8612 // a node (or node's id) to be moved
8613 // params: dojo.dnd.move.__boxConstrainedMoveableArgs?
8614 // an optional object with parameters
8615 var box = params && params.box;
8616 this.constraints = function(){ return box; };
8617 }
8618});
8619
8620/*=====
8621dojo.declare("dojo.dnd.move.__parentConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], {
8622 // area: String
8623 // A parent's area to restrict the move.
8624 // Can be "margin", "border", "padding", or "content".
8625 area: ""
8626});
8627=====*/
8628
8629dojo.declare("dojo.dnd.move.parentConstrainedMoveable", dojo.dnd.move.constrainedMoveable, {
8630 // area:
8631 // object attributes (for markup)
8632 area: "content",
8633
8634 // markup methods
8635 markupFactory: function(params, node){
8636 return new dojo.dnd.move.parentConstrainedMoveable(node, params);
8637 },
8638
8639 constructor: function(node, params){
8640 // summary:
8641 // an object, which makes a node moveable
8642 // node: Node
8643 // a node (or node's id) to be moved
8644 // params: dojo.dnd.move.__parentConstrainedMoveableArgs?
8645 // an optional object with parameters
8646 var area = params && params.area;
8647 this.constraints = function(){
81bea17a
AD
8648 var n = this.node.parentNode,
8649 s = dojo.getComputedStyle(n),
a089699c
AD
8650 mb = dojo._getMarginBox(n, s);
8651 if(area == "margin"){
8652 return mb; // Object
8653 }
8654 var t = dojo._getMarginExtents(n, s);
8655 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8656 if(area == "border"){
8657 return mb; // Object
8658 }
8659 t = dojo._getBorderExtents(n, s);
8660 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8661 if(area == "padding"){
8662 return mb; // Object
8663 }
8664 t = dojo._getPadExtents(n, s);
8665 mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h;
8666 return mb; // Object
8667 };
8668 }
8669});
8670
a089699c
AD
8671// patching functions one level up for compatibility
8672
8673dojo.dnd.constrainedMover = dojo.dnd.move.constrainedMover;
8674dojo.dnd.boxConstrainedMover = dojo.dnd.move.boxConstrainedMover;
8675dojo.dnd.parentConstrainedMover = dojo.dnd.move.parentConstrainedMover;
8676
8677}
8678
8679if(!dojo._hasResource["dojo.dnd.TimedMoveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8680dojo._hasResource["dojo.dnd.TimedMoveable"] = true;
8681dojo.provide("dojo.dnd.TimedMoveable");
8682
8683
8684
8685/*=====
8686dojo.declare("dojo.dnd.__TimedMoveableArgs", [dojo.dnd.__MoveableArgs], {
8687 // timeout: Number
8688 // delay move by this number of ms,
8689 // accumulating position changes during the timeout
8690 timeout: 0
8691});
8692=====*/
8693
8694(function(){
8695 // precalculate long expressions
8696 var oldOnMove = dojo.dnd.Moveable.prototype.onMove;
8697
8698 dojo.declare("dojo.dnd.TimedMoveable", dojo.dnd.Moveable, {
8699 // summary:
8700 // A specialized version of Moveable to support an FPS throttling.
81bea17a 8701 // This class puts an upper restriction on FPS, which may reduce
a089699c
AD
8702 // the CPU load. The additional parameter "timeout" regulates
8703 // the delay before actually moving the moveable object.
8704
8705 // object attributes (for markup)
8706 timeout: 40, // in ms, 40ms corresponds to 25 fps
8707
8708 constructor: function(node, params){
8709 // summary:
8710 // an object that makes a node moveable with a timer
8711 // node: Node||String
8712 // a node (or node's id) to be moved
8713 // params: dojo.dnd.__TimedMoveableArgs
8714 // object with additional parameters.
8715
8716 // sanitize parameters
8717 if(!params){ params = {}; }
8718 if(params.timeout && typeof params.timeout == "number" && params.timeout >= 0){
8719 this.timeout = params.timeout;
8720 }
8721 },
8722
8723 // markup methods
8724 markupFactory: function(params, node){
8725 return new dojo.dnd.TimedMoveable(node, params);
8726 },
8727
8728 onMoveStop: function(/* dojo.dnd.Mover */ mover){
8729 if(mover._timer){
8730 // stop timer
8731 clearTimeout(mover._timer)
8732 // reflect the last received position
8733 oldOnMove.call(this, mover, mover._leftTop)
8734 }
8735 dojo.dnd.Moveable.prototype.onMoveStop.apply(this, arguments);
8736 },
8737 onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){
8738 mover._leftTop = leftTop;
8739 if(!mover._timer){
8740 var _t = this; // to avoid using dojo.hitch()
8741 mover._timer = setTimeout(function(){
8742 // we don't have any pending requests
8743 mover._timer = null;
8744 // reflect the last received position
8745 oldOnMove.call(_t, mover, mover._leftTop);
8746 }, this.timeout);
8747 }
8748 }
8749 });
8750})();
8751
8752}
8753
8754if(!dojo._hasResource["dijit.form._FormMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
8755dojo._hasResource["dijit.form._FormMixin"] = true;
8756dojo.provide("dijit.form._FormMixin");
8757
8758
8759
81bea17a 8760dojo.declare("dijit.form._FormMixin", null, {
a089699c
AD
8761 // summary:
8762 // Mixin for containers of form widgets (i.e. widgets that represent a single value
8763 // and can be children of a <form> node or dijit.form.Form widget)
8764 // description:
8765 // Can extract all the form widgets
8766 // values and combine them into a single javascript object, or alternately
8767 // take such an object and set the values for all the contained
8768 // form widgets
8769
8770/*=====
81bea17a 8771 // value: Object
a089699c
AD
8772 // Name/value hash for each child widget with a name and value.
8773 // Child widgets without names are not part of the hash.
81bea17a 8774 //
a089699c
AD
8775 // If there are multiple child widgets w/the same name, value is an array,
8776 // unless they are radio buttons in which case value is a scalar (since only
8777 // one radio button can be checked at a time).
8778 //
8779 // If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure.
8780 //
8781 // Example:
8782 // | { name: "John Smith", interests: ["sports", "movies"] }
8783=====*/
8784
81bea17a
AD
8785 // state: [readonly] String
8786 // Will be "Error" if one or more of the child widgets has an invalid value,
8787 // "Incomplete" if not all of the required child widgets are filled in. Otherwise, "",
8788 // which indicates that the form is ready to be submitted.
8789 state: "",
8790
a089699c
AD
8791 // TODO:
8792 // * Repeater
8793 // * better handling for arrays. Often form elements have names with [] like
8794 // * people[3].sex (for a list of people [{name: Bill, sex: M}, ...])
8795 //
8796 //
8797
8798 reset: function(){
8799 dojo.forEach(this.getDescendants(), function(widget){
8800 if(widget.reset){
8801 widget.reset();
8802 }
8803 });
8804 },
8805
8806 validate: function(){
8807 // summary:
8808 // returns if the form is valid - same as isValid - but
81bea17a
AD
8809 // provides a few additional (ui-specific) features.
8810 // 1 - it will highlight any sub-widgets that are not
8811 // valid
8812 // 2 - it will call focus() on the first invalid
8813 // sub-widget
a089699c
AD
8814 var didFocus = false;
8815 return dojo.every(dojo.map(this.getDescendants(), function(widget){
8816 // Need to set this so that "required" widgets get their
8817 // state set.
8818 widget._hasBeenBlurred = true;
8819 var valid = widget.disabled || !widget.validate || widget.validate();
8820 if(!valid && !didFocus){
8821 // Set focus of the first non-valid widget
8822 dojo.window.scrollIntoView(widget.containerNode || widget.domNode);
8823 widget.focus();
8824 didFocus = true;
8825 }
8826 return valid;
8827 }), function(item){ return item; });
8828 },
8829
8830 setValues: function(val){
8831 dojo.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.", "", "2.0");
8832 return this.set('value', val);
8833 },
81bea17a 8834 _setValueAttr: function(/*Object*/ obj){
a089699c 8835 // summary:
81bea17a 8836 // Fill in form values from according to an Object (in the format returned by get('value'))
a089699c
AD
8837
8838 // generate map from name --> [list of widgets with that name]
8839 var map = { };
8840 dojo.forEach(this.getDescendants(), function(widget){
8841 if(!widget.name){ return; }
8842 var entry = map[widget.name] || (map[widget.name] = [] );
8843 entry.push(widget);
8844 });
8845
8846 for(var name in map){
8847 if(!map.hasOwnProperty(name)){
8848 continue;
8849 }
8850 var widgets = map[name], // array of widgets w/this name
8851 values = dojo.getObject(name, false, obj); // list of values for those widgets
8852
8853 if(values === undefined){
8854 continue;
8855 }
8856 if(!dojo.isArray(values)){
8857 values = [ values ];
8858 }
8859 if(typeof widgets[0].checked == 'boolean'){
8860 // for checkbox/radio, values is a list of which widgets should be checked
8861 dojo.forEach(widgets, function(w, i){
8862 w.set('value', dojo.indexOf(values, w.value) != -1);
8863 });
8864 }else if(widgets[0].multiple){
8865 // it takes an array (e.g. multi-select)
8866 widgets[0].set('value', values);
8867 }else{
8868 // otherwise, values is a list of values to be assigned sequentially to each widget
8869 dojo.forEach(widgets, function(w, i){
8870 w.set('value', values[i]);
8871 });
8872 }
8873 }
8874
8875 /***
8876 * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets)
8877
8878 dojo.forEach(this.containerNode.elements, function(element){
8879 if(element.name == ''){return}; // like "continue"
8880 var namePath = element.name.split(".");
8881 var myObj=obj;
8882 var name=namePath[namePath.length-1];
8883 for(var j=1,len2=namePath.length;j<len2;++j){
8884 var p=namePath[j - 1];
8885 // repeater support block
8886 var nameA=p.split("[");
8887 if(nameA.length > 1){
8888 if(typeof(myObj[nameA[0]]) == "undefined"){
8889 myObj[nameA[0]]=[ ];
8890 } // if
8891
8892 nameIndex=parseInt(nameA[1]);
8893 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
8894 myObj[nameA[0]][nameIndex] = { };
8895 }
8896 myObj=myObj[nameA[0]][nameIndex];
8897 continue;
8898 } // repeater support ends
8899
8900 if(typeof(myObj[p]) == "undefined"){
8901 myObj=undefined;
8902 break;
8903 };
8904 myObj=myObj[p];
8905 }
8906
8907 if(typeof(myObj) == "undefined"){
8908 return; // like "continue"
8909 }
8910 if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){
8911 return; // like "continue"
8912 }
8913
81bea17a 8914 // TODO: widget values (just call set('value', ...) on the widget)
a089699c
AD
8915
8916 // TODO: maybe should call dojo.getNodeProp() instead
8917 switch(element.type){
8918 case "checkbox":
8919 element.checked = (name in myObj) &&
8920 dojo.some(myObj[name], function(val){ return val == element.value; });
8921 break;
8922 case "radio":
8923 element.checked = (name in myObj) && myObj[name] == element.value;
8924 break;
8925 case "select-multiple":
8926 element.selectedIndex=-1;
8927 dojo.forEach(element.options, function(option){
8928 option.selected = dojo.some(myObj[name], function(val){ return option.value == val; });
8929 });
8930 break;
8931 case "select-one":
8932 element.selectedIndex="0";
8933 dojo.forEach(element.options, function(option){
8934 option.selected = option.value == myObj[name];
8935 });
8936 break;
8937 case "hidden":
8938 case "text":
8939 case "textarea":
8940 case "password":
8941 element.value = myObj[name] || "";
8942 break;
8943 }
8944 });
8945 */
81bea17a
AD
8946
8947 // Note: no need to call this._set("value", ...) as the child updates will trigger onChange events
8948 // which I am monitoring.
a089699c
AD
8949 },
8950
8951 getValues: function(){
8952 dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0");
8953 return this.get('value');
8954 },
8955 _getValueAttr: function(){
8956 // summary:
81bea17a 8957 // Returns Object representing form values. See description of `value` for details.
a089699c 8958 // description:
81bea17a
AD
8959
8960 // The value is updated into this.value every time a child has an onChange event,
8961 // so in the common case this function could just return this.value. However,
8962 // that wouldn't work when:
a089699c 8963 //
81bea17a
AD
8964 // 1. User presses return key to submit a form. That doesn't fire an onchange event,
8965 // and even if it did it would come too late due to the setTimout(..., 0) in _handleOnChange()
8966 //
8967 // 2. app for some reason calls this.get("value") while the user is typing into a
8968 // form field. Not sure if that case needs to be supported or not.
a089699c
AD
8969
8970 // get widget values
8971 var obj = { };
8972 dojo.forEach(this.getDescendants(), function(widget){
8973 var name = widget.name;
8974 if(!name || widget.disabled){ return; }
8975
81bea17a 8976 // Single value widget (checkbox, radio, or plain <input> type widget)
a089699c
AD
8977 var value = widget.get('value');
8978
8979 // Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays
8980 if(typeof widget.checked == 'boolean'){
8981 if(/Radio/.test(widget.declaredClass)){
8982 // radio button
8983 if(value !== false){
8984 dojo.setObject(name, value, obj);
8985 }else{
8986 // give radio widgets a default of null
8987 value = dojo.getObject(name, false, obj);
8988 if(value === undefined){
8989 dojo.setObject(name, null, obj);
8990 }
8991 }
8992 }else{
8993 // checkbox/toggle button
8994 var ary=dojo.getObject(name, false, obj);
8995 if(!ary){
8996 ary=[];
8997 dojo.setObject(name, ary, obj);
8998 }
8999 if(value !== false){
9000 ary.push(value);
9001 }
9002 }
9003 }else{
9004 var prev=dojo.getObject(name, false, obj);
9005 if(typeof prev != "undefined"){
9006 if(dojo.isArray(prev)){
9007 prev.push(value);
9008 }else{
9009 dojo.setObject(name, [prev, value], obj);
9010 }
9011 }else{
9012 // unique name
9013 dojo.setObject(name, value, obj);
9014 }
9015 }
9016 });
9017
9018 /***
9019 * code for plain input boxes (see also dojo.formToObject, can we use that instead of this code?
9020 * but it doesn't understand [] notation, presumably)
9021 var obj = { };
9022 dojo.forEach(this.containerNode.elements, function(elm){
9023 if(!elm.name) {
9024 return; // like "continue"
9025 }
9026 var namePath = elm.name.split(".");
9027 var myObj=obj;
9028 var name=namePath[namePath.length-1];
9029 for(var j=1,len2=namePath.length;j<len2;++j){
9030 var nameIndex = null;
9031 var p=namePath[j - 1];
9032 var nameA=p.split("[");
9033 if(nameA.length > 1){
9034 if(typeof(myObj[nameA[0]]) == "undefined"){
9035 myObj[nameA[0]]=[ ];
9036 } // if
9037 nameIndex=parseInt(nameA[1]);
9038 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
9039 myObj[nameA[0]][nameIndex] = { };
9040 }
9041 } else if(typeof(myObj[nameA[0]]) == "undefined"){
9042 myObj[nameA[0]] = { }
9043 } // if
9044
9045 if(nameA.length == 1){
9046 myObj=myObj[nameA[0]];
9047 } else{
9048 myObj=myObj[nameA[0]][nameIndex];
9049 } // if
9050 } // for
9051
9052 if((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type == "radio" && elm.checked)){
9053 if(name == name.split("[")[0]){
9054 myObj[name]=elm.value;
9055 } else{
9056 // can not set value when there is no name
9057 }
9058 } else if(elm.type == "checkbox" && elm.checked){
9059 if(typeof(myObj[name]) == 'undefined'){
9060 myObj[name]=[ ];
9061 }
9062 myObj[name].push(elm.value);
9063 } else if(elm.type == "select-multiple"){
9064 if(typeof(myObj[name]) == 'undefined'){
9065 myObj[name]=[ ];
9066 }
9067 for(var jdx=0,len3=elm.options.length; jdx<len3; ++jdx){
9068 if(elm.options[jdx].selected){
9069 myObj[name].push(elm.options[jdx].value);
9070 }
9071 }
9072 } // if
9073 name=undefined;
9074 }); // forEach
9075 ***/
9076 return obj;
9077 },
9078
a089699c
AD
9079 isValid: function(){
9080 // summary:
81bea17a
AD
9081 // Returns true if all of the widgets are valid.
9082 // Deprecated, will be removed in 2.0. Use get("state") instead.
a089699c 9083
81bea17a 9084 return this.state == "";
a089699c
AD
9085 },
9086
a089699c
AD
9087 onValidStateChange: function(isValid){
9088 // summary:
9089 // Stub function to connect to if you want to do something
9090 // (like disable/enable a submit button) when the valid
9091 // state changes on the form as a whole.
81bea17a
AD
9092 //
9093 // Deprecated. Will be removed in 2.0. Use watch("state", ...) instead.
a089699c
AD
9094 },
9095
81bea17a 9096 _getState: function(){
a089699c 9097 // summary:
81bea17a
AD
9098 // Compute what this.state should be based on state of children
9099 var states = dojo.map(this._descendants, function(w){
9100 return w.get("state") || "";
9101 });
9102
9103 return dojo.indexOf(states, "Error") >= 0 ? "Error" :
9104 dojo.indexOf(states, "Incomplete") >= 0 ? "Incomplete" : "";
a089699c
AD
9105 },
9106
81bea17a 9107 disconnectChildren: function(){
a089699c 9108 // summary:
81bea17a
AD
9109 // Remove connections to monitor changes to children's value, error state, and disabled state,
9110 // in order to update Form.value and Form.state.
9111 dojo.forEach(this._childConnections || [], dojo.hitch(this, "disconnect"));
9112 dojo.forEach(this._childWatches || [], function(w){ w.unwatch(); });
9113 },
9114
9115 connectChildren: function(/*Boolean*/ inStartup){
9116 // summary:
9117 // Setup connections to monitor changes to children's value, error state, and disabled state,
9118 // in order to update Form.value and Form.state.
9119 //
9120 // You can call this function directly, ex. in the event that you
9121 // programmatically add a widget to the form *after* the form has been
a089699c 9122 // initialized.
81bea17a 9123
a089699c
AD
9124 var _this = this;
9125
81bea17a
AD
9126 // Remove old connections, if any
9127 this.disconnectChildren();
9128
9129 this._descendants = this.getDescendants();
9130
9131 // (Re)set this.value and this.state. Send watch() notifications but not on startup.
9132 var set = inStartup ? function(name, val){ _this[name] = val; } : dojo.hitch(this, "_set");
9133 set("value", this.get("value"));
9134 set("state", this._getState());
9135
9136 // Monitor changes to error state and disabled state in order to update
9137 // Form.state
9138 var conns = (this._childConnections = []),
9139 watches = (this._childWatches = []);
9140 dojo.forEach(dojo.filter(this._descendants,
a089699c
AD
9141 function(item){ return item.validate; }
9142 ),
9143 function(widget){
81bea17a
AD
9144 // We are interested in whenever the widget changes validity state - or
9145 // whenever the disabled attribute on that widget is changed.
9146 dojo.forEach(["state", "disabled"], function(attr){
9147 watches.push(widget.watch(attr, function(attr, oldVal, newVal){
9148 _this.set("state", _this._getState());
9149 }));
9150 });
a089699c
AD
9151 });
9152
81bea17a
AD
9153 // And monitor calls to child.onChange so we can update this.value
9154 var onChange = function(){
9155 // summary:
9156 // Called when child's value or disabled state changes
9157
9158 // Use setTimeout() to collapse value changes in multiple children into a single
9159 // update to my value. Multiple updates will occur on:
9160 // 1. Form.set()
9161 // 2. Form.reset()
9162 // 3. user selecting a radio button (which will de-select another radio button,
9163 // causing two onChange events)
9164 if(_this._onChangeDelayTimer){
9165 clearTimeout(_this._onChangeDelayTimer);
9166 }
9167 _this._onChangeDelayTimer = setTimeout(function(){
9168 delete _this._onChangeDelayTimer;
9169 _this._set("value", _this.get("value"));
9170 }, 10);
9171 };
9172 dojo.forEach(
9173 dojo.filter(this._descendants, function(item){ return item.onChange; } ),
9174 function(widget){
9175 // When a child widget's value changes,
9176 // the efficient thing to do is to just update that one attribute in this.value,
9177 // but that gets a little complicated when a checkbox is checked/unchecked
9178 // since this.value["checkboxName"] contains an array of all the checkboxes w/the same name.
9179 // Doing simple thing for now.
9180 conns.push(_this.connect(widget, "onChange", onChange));
9181
9182 // Disabling/enabling a child widget should remove it's value from this.value.
9183 // Again, this code could be more efficient, doing simple thing for now.
9184 watches.push(widget.watch("disabled", onChange));
9185 }
9186 );
a089699c
AD
9187 },
9188
9189 startup: function(){
9190 this.inherited(arguments);
81bea17a
AD
9191
9192 // Initialize value and valid/invalid state tracking. Needs to be done in startup()
9193 // so that children are initialized.
9194 this.connectChildren(true);
9195
9196 // Make state change call onValidStateChange(), will be removed in 2.0
9197 this.watch("state", function(attr, oldVal, newVal){ this.onValidStateChange(newVal == ""); });
9198 },
9199
9200 destroy: function(){
9201 this.disconnectChildren();
9202 this.inherited(arguments);
a089699c 9203 }
81bea17a 9204
a089699c
AD
9205 });
9206
9207}
9208
9209if(!dojo._hasResource["dijit._DialogMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9210dojo._hasResource["dijit._DialogMixin"] = true;
9211dojo.provide("dijit._DialogMixin");
9212
9213
9214
9215dojo.declare("dijit._DialogMixin", null,
9216 {
9217 // summary:
9218 // This provides functions useful to Dialog and TooltipDialog
9219
9220 attributeMap: dijit._Widget.prototype.attributeMap,
9221
9222 execute: function(/*Object*/ formContents){
9223 // summary:
9224 // Callback when the user hits the submit button.
9225 // Override this method to handle Dialog execution.
9226 // description:
9227 // After the user has pressed the submit button, the Dialog
9228 // first calls onExecute() to notify the container to hide the
9229 // dialog and restore focus to wherever it used to be.
9230 //
9231 // *Then* this method is called.
9232 // type:
9233 // callback
9234 },
9235
9236 onCancel: function(){
9237 // summary:
9238 // Called when user has pressed the Dialog's cancel button, to notify container.
9239 // description:
9240 // Developer shouldn't override or connect to this method;
9241 // it's a private communication device between the TooltipDialog
9242 // and the thing that opened it (ex: `dijit.form.DropDownButton`)
9243 // type:
9244 // protected
9245 },
9246
9247 onExecute: function(){
9248 // summary:
9249 // Called when user has pressed the dialog's OK button, to notify container.
9250 // description:
9251 // Developer shouldn't override or connect to this method;
9252 // it's a private communication device between the TooltipDialog
9253 // and the thing that opened it (ex: `dijit.form.DropDownButton`)
9254 // type:
9255 // protected
9256 },
9257
9258 _onSubmit: function(){
9259 // summary:
9260 // Callback when user hits submit button
9261 // type:
9262 // protected
9263 this.onExecute(); // notify container that we are about to execute
9264 this.execute(this.get('value'));
9265 },
9266
81bea17a 9267 _getFocusItems: function(){
a089699c 9268 // summary:
81bea17a
AD
9269 // Finds focusable items in dialog,
9270 // and sets this._firstFocusItem and this._lastFocusItem
a089699c
AD
9271 // tags:
9272 // protected
9273
81bea17a
AD
9274 var elems = dijit._getTabNavigable(this.containerNode);
9275 this._firstFocusItem = elems.lowest || elems.first || this.closeButtonNode || this.domNode;
a089699c 9276 this._lastFocusItem = elems.last || elems.highest || this._firstFocusItem;
a089699c
AD
9277 }
9278 }
9279);
9280
9281}
9282
9283if(!dojo._hasResource["dijit.DialogUnderlay"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9284dojo._hasResource["dijit.DialogUnderlay"] = true;
9285dojo.provide("dijit.DialogUnderlay");
9286
9287
9288
9289
9290
a089699c
AD
9291dojo.declare(
9292 "dijit.DialogUnderlay",
9293 [dijit._Widget, dijit._Templated],
9294 {
9295 // summary:
9296 // The component that blocks the screen behind a `dijit.Dialog`
9297 //
9298 // description:
9299 // A component used to block input behind a `dijit.Dialog`. Only a single
9300 // instance of this widget is created by `dijit.Dialog`, and saved as
9301 // a reference to be shared between all Dialogs as `dijit._underlay`
9302 //
9303 // The underlay itself can be styled based on and id:
9304 // | #myDialog_underlay { background-color:red; }
9305 //
9306 // In the case of `dijit.Dialog`, this id is based on the id of the Dialog,
9307 // suffixed with _underlay.
9308
81bea17a
AD
9309 // Template has two divs; outer div is used for fade-in/fade-out, and also to hold background iframe.
9310 // Inner div has opacity specified in CSS file.
9311 templateString: "<div class='dijitDialogUnderlayWrapper'><div class='dijitDialogUnderlay' dojoAttachPoint='node'></div></div>",
9312
9313 // Parameters on creation or updatable later
9314
9315 // dialogId: String
9316 // Id of the dialog.... DialogUnderlay's id is based on this id
9317 dialogId: "",
9318
9319 // class: String
9320 // This class name is used on the DialogUnderlay node, in addition to dijitDialogUnderlay
9321 "class": "",
9322
9323 attributeMap: { id: "domNode" },
9324
9325 _setDialogIdAttr: function(id){
9326 dojo.attr(this.node, "id", id + "_underlay");
9327 this._set("dialogId", id);
9328 },
9329
9330 _setClassAttr: function(clazz){
9331 this.node.className = "dijitDialogUnderlay " + clazz;
9332 this._set("class", clazz);
9333 },
9334
9335 postCreate: function(){
9336 // summary:
9337 // Append the underlay to the body
9338 dojo.body().appendChild(this.domNode);
9339 },
9340
9341 layout: function(){
9342 // summary:
9343 // Sets the background to the size of the viewport
9344 //
9345 // description:
9346 // Sets the background to the size of the viewport (rather than the size
9347 // of the document) since we need to cover the whole browser window, even
9348 // if the document is only a few lines long.
9349 // tags:
9350 // private
9351
9352 var is = this.node.style,
9353 os = this.domNode.style;
9354
9355 // hide the background temporarily, so that the background itself isn't
9356 // causing scrollbars to appear (might happen when user shrinks browser
9357 // window and then we are called to resize)
9358 os.display = "none";
9359
9360 // then resize and show
9361 var viewport = dojo.window.getBox();
9362 os.top = viewport.t + "px";
9363 os.left = viewport.l + "px";
9364 is.width = viewport.w + "px";
9365 is.height = viewport.h + "px";
9366 os.display = "block";
9367 },
9368
9369 show: function(){
9370 // summary:
9371 // Show the dialog underlay
9372 this.domNode.style.display = "block";
9373 this.layout();
9374 this.bgIframe = new dijit.BackgroundIframe(this.domNode);
9375 },
9376
9377 hide: function(){
9378 // summary:
9379 // Hides the dialog underlay
9380 this.bgIframe.destroy();
9381 delete this.bgIframe;
9382 this.domNode.style.display = "none";
9383 }
9384 }
9385);
9386
9387}
9388
9389if(!dojo._hasResource["dijit.layout._ContentPaneResizeMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9390dojo._hasResource["dijit.layout._ContentPaneResizeMixin"] = true;
9391dojo.provide("dijit.layout._ContentPaneResizeMixin");
9392
9393
9394
9395
9396dojo.declare("dijit.layout._ContentPaneResizeMixin", null, {
9397 // summary:
9398 // Resize() functionality of ContentPane. If there's a single layout widget
9399 // child then it will call resize() with the same dimensions as the ContentPane.
9400 // Otherwise just calls resize on each child.
9401 //
9402 // Also implements basic startup() functionality, where starting the parent
9403 // will start the children
9404
9405 // doLayout: Boolean
9406 // - false - don't adjust size of children
9407 // - true - if there is a single visible child widget, set it's size to
9408 // however big the ContentPane is
9409 doLayout: true,
9410
9411 // isContainer: [protected] Boolean
9412 // Indicates that this widget acts as a "parent" to the descendant widgets.
9413 // When the parent is started it will call startup() on the child widgets.
9414 // See also `isLayoutContainer`.
9415 isContainer: true,
9416
9417 // isLayoutContainer: [protected] Boolean
9418 // Indicates that this widget will call resize() on it's child widgets
9419 // when they become visible.
9420 isLayoutContainer: true,
9421
9422 _startChildren: function(){
9423 // summary:
9424 // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
9425
9426 // This starts all the widgets
9427 dojo.forEach(this.getChildren(), function(child){
9428 child.startup();
9429 child._started = true;
9430 });
9431 },
9432
9433 startup: function(){
9434 // summary:
9435 // See `dijit.layout._LayoutWidget.startup` for description.
9436 // Although ContentPane doesn't extend _LayoutWidget, it does implement
9437 // the same API.
9438
9439 if(this._started){ return; }
9440
9441 var parent = dijit._Contained.prototype.getParent.call(this);
9442 this._childOfLayoutWidget = parent && parent.isLayoutContainer;
9443
9444 // I need to call resize() on my child/children (when I become visible), unless
9445 // I'm the child of a layout widget in which case my parent will call resize() on me and I'll do it then.
9446 this._needLayout = !this._childOfLayoutWidget;
9447
9448 this.inherited(arguments);
9449
9450 this._startChildren();
9451
9452 if(this._isShown()){
9453 this._onShow();
9454 }
9455
9456 if(!this._childOfLayoutWidget){
9457 // If my parent isn't a layout container, since my style *may be* width=height=100%
9458 // or something similar (either set directly or via a CSS class),
9459 // monitor when my size changes so that I can re-layout.
9460 // For browsers where I can't directly monitor when my size changes,
9461 // monitor when the viewport changes size, which *may* indicate a size change for me.
9462 this.connect(dojo.isIE ? this.domNode : dojo.global, 'onresize', function(){
9463 // Using function(){} closure to ensure no arguments to resize.
9464 this._needLayout = !this._childOfLayoutWidget;
9465 this.resize();
9466 });
9467 }
9468 },
9469
9470 _checkIfSingleChild: function(){
9471 // summary:
9472 // Test if we have exactly one visible widget as a child,
9473 // and if so assume that we are a container for that widget,
9474 // and should propagate startup() and resize() calls to it.
9475 // Skips over things like data stores since they aren't visible.
9476
9477 var childNodes = dojo.query("> *", this.containerNode).filter(function(node){
9478 return node.tagName !== "SCRIPT"; // or a regexp for hidden elements like script|area|map|etc..
9479 }),
9480 childWidgetNodes = childNodes.filter(function(node){
9481 return dojo.hasAttr(node, "data-dojo-type") || dojo.hasAttr(node, "dojoType") || dojo.hasAttr(node, "widgetId");
9482 }),
9483 candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){
9484 return widget && widget.domNode && widget.resize;
9485 });
9486
9487 if(
9488 // all child nodes are widgets
9489 childNodes.length == childWidgetNodes.length &&
9490
9491 // all but one are invisible (like dojo.data)
9492 candidateWidgets.length == 1
9493 ){
9494 this._singleChild = candidateWidgets[0];
9495 }else{
9496 delete this._singleChild;
9497 }
9498
9499 // So we can set overflow: hidden to avoid a safari bug w/scrollbars showing up (#9449)
9500 dojo.toggleClass(this.containerNode, this.baseClass + "SingleChild", !!this._singleChild);
9501 },
9502
9503 resize: function(changeSize, resultSize){
9504 // summary:
9505 // See `dijit.layout._LayoutWidget.resize` for description.
9506 // Although ContentPane doesn't extend _LayoutWidget, it does implement
9507 // the same API.
9508
9509 // For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is
9510 // never called, so resize() is our trigger to do the initial href download (see [20099]).
9511 // However, don't load href for closed TitlePanes.
9512 if(!this._wasShown && this.open !== false){
9513 this._onShow();
9514 }
a089699c 9515
81bea17a 9516 this._resizeCalled = true;
a089699c 9517
81bea17a
AD
9518 this._scheduleLayout(changeSize, resultSize);
9519 },
a089699c 9520
81bea17a
AD
9521 _scheduleLayout: function(changeSize, resultSize){
9522 // summary:
9523 // Resize myself, and call resize() on each of my child layout widgets, either now
9524 // (if I'm currently visible) or when I become visible
9525 if(this._isShown()){
9526 this._layout(changeSize, resultSize);
9527 }else{
9528 this._needLayout = true;
9529 this._changeSize = changeSize;
9530 this._resultSize = resultSize;
9531 }
9532 },
a089699c 9533
81bea17a
AD
9534 _layout: function(changeSize, resultSize){
9535 // summary:
9536 // Resize myself according to optional changeSize/resultSize parameters, like a layout widget.
9537 // Also, since I am a Container widget, each of my children expects me to
9538 // call resize() or layout() on them.
9539 //
9540 // Should be called on initialization and also whenever we get new content
9541 // (from an href, or from set('content', ...))... but deferred until
9542 // the ContentPane is visible
a089699c 9543
81bea17a
AD
9544 // Set margin box size, unless it wasn't specified, in which case use current size.
9545 if(changeSize){
9546 dojo.marginBox(this.domNode, changeSize);
9547 }
a089699c 9548
81bea17a
AD
9549 // Compute content box size of containerNode in case we [later] need to size our single child.
9550 var cn = this.containerNode;
9551 if(cn === this.domNode){
9552 // If changeSize or resultSize was passed to this method and this.containerNode ==
9553 // this.domNode then we can compute the content-box size without querying the node,
9554 // which is more reliable (similar to LayoutWidget.resize) (see for example #9449).
9555 var mb = resultSize || {};
9556 dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize
9557 if(!("h" in mb) || !("w" in mb)){
9558 mb = dojo.mixin(dojo.marginBox(cn), mb); // just use dojo.marginBox() to fill in missing values
9559 }
9560 this._contentBox = dijit.layout.marginBox2contentBox(cn, mb);
9561 }else{
9562 this._contentBox = dojo.contentBox(cn);
9563 }
a089699c 9564
81bea17a 9565 this._layoutChildren();
a089699c 9566
81bea17a
AD
9567 delete this._needLayout;
9568 },
9569
9570 _layoutChildren: function(){
9571 // Call _checkIfSingleChild() again in case app has manually mucked w/the content
9572 // of the ContentPane (rather than changing it through the set("content", ...) API.
9573 if(this.doLayout){
9574 this._checkIfSingleChild();
9575 }
a089699c 9576
81bea17a
AD
9577 if(this._singleChild && this._singleChild.resize){
9578 var cb = this._contentBox || dojo.contentBox(this.containerNode);
a089699c 9579
81bea17a
AD
9580 // note: if widget has padding this._contentBox will have l and t set,
9581 // but don't pass them to resize() or it will doubly-offset the child
9582 this._singleChild.resize({w: cb.w, h: cb.h});
9583 }else{
9584 // All my child widgets are independently sized (rather than matching my size),
9585 // but I still need to call resize() on each child to make it layout.
9586 dojo.forEach(this.getChildren(), function(widget){
9587 if(widget.resize){
9588 widget.resize();
9589 }
9590 });
9591 }
9592 },
a089699c 9593
81bea17a
AD
9594 _isShown: function(){
9595 // summary:
9596 // Returns true if the content is currently shown.
9597 // description:
9598 // If I am a child of a layout widget then it actually returns true if I've ever been visible,
9599 // not whether I'm currently visible, since that's much faster than tracing up the DOM/widget
9600 // tree every call, and at least solves the performance problem on page load by deferring loading
9601 // hidden ContentPanes until they are first shown
a089699c 9602
81bea17a
AD
9603 if(this._childOfLayoutWidget){
9604 // If we are TitlePane, etc - we return that only *IF* we've been resized
9605 if(this._resizeCalled && "open" in this){
9606 return this.open;
9607 }
9608 return this._resizeCalled;
9609 }else if("open" in this){
9610 return this.open; // for TitlePane, etc.
9611 }else{
9612 var node = this.domNode, parent = this.domNode.parentNode;
9613 return (node.style.display != 'none') && (node.style.visibility != 'hidden') && !dojo.hasClass(node, "dijitHidden") &&
9614 parent && parent.style && (parent.style.display != 'none');
9615 }
9616 },
a089699c 9617
81bea17a
AD
9618 _onShow: function(){
9619 // summary:
9620 // Called when the ContentPane is made visible
9621 // description:
9622 // For a plain ContentPane, this is called on initialization, from startup().
9623 // If the ContentPane is a hidden pane of a TabContainer etc., then it's
9624 // called whenever the pane is made visible.
9625 //
9626 // Does layout/resize of child widget(s)
a089699c 9627
81bea17a
AD
9628 if(this._needLayout){
9629 // If a layout has been scheduled for when we become visible, do it now
9630 this._layout(this._changeSize, this._resultSize);
a089699c 9631 }
81bea17a
AD
9632
9633 this.inherited(arguments);
9634
9635 // Need to keep track of whether ContentPane has been shown (which is different than
9636 // whether or not it's currently visible).
9637 this._wasShown = true;
a089699c 9638 }
81bea17a 9639});
a089699c
AD
9640
9641}
9642
9643if(!dojo._hasResource["dojo.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9644dojo._hasResource["dojo.html"] = true;
9645dojo.provide("dojo.html");
9646
a089699c 9647
81bea17a
AD
9648dojo.getObject("html", true, dojo);
9649
9650// the parser might be needed..
a089699c
AD
9651(function(){ // private scope, sort of a namespace
9652
9653 // idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes
81bea17a 9654 var idCounter = 0,
a089699c
AD
9655 d = dojo;
9656
9657 dojo.html._secureForInnerHtml = function(/*String*/ cont){
9658 // summary:
9659 // removes !DOCTYPE and title elements from the html string.
81bea17a 9660 //
a089699c
AD
9661 // khtml is picky about dom faults, you can't attach a style or <title> node as child of body
9662 // must go into head, so we need to cut out those tags
9663 // cont:
9664 // An html string for insertion into the dom
81bea17a 9665 //
a089699c
AD
9666 return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String
9667 };
9668
9669/*====
9670 dojo.html._emptyNode = function(node){
9671 // summary:
9672 // removes all child nodes from the given node
9673 // node: DOMNode
9674 // the parent element
9675 };
9676=====*/
9677 dojo.html._emptyNode = dojo.empty;
9678
9679 dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){
9680 // summary:
9681 // inserts the given content into the given node
9682 // node:
9683 // the parent element
9684 // content:
81bea17a 9685 // the content to be set on the parent element.
a089699c
AD
9686 // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
9687
9688 // always empty
9689 d.empty(node);
9690
9691 if(cont) {
9692 if(typeof cont == "string") {
9693 cont = d._toDom(cont, node.ownerDocument);
9694 }
9695 if(!cont.nodeType && d.isArrayLike(cont)) {
9696 // handle as enumerable, but it may shrink as we enumerate it
9697 for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0) {
9698 d.place( cont[i], node, "last");
9699 }
9700 } else {
9701 // pass nodes, documentFragments and unknowns through to dojo.place
9702 d.place(cont, node, "last");
9703 }
9704 }
9705
9706 // return DomNode
9707 return node;
9708 };
9709
9710 // we wrap up the content-setting operation in a object
81bea17a 9711 dojo.declare("dojo.html._ContentSetter", null,
a089699c
AD
9712 {
9713 // node: DomNode|String
9714 // An node which will be the parent element that we set content into
9715 node: "",
9716
9717 // content: String|DomNode|DomNode[]
9718 // The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes
9719 content: "",
9720
9721 // id: String?
81bea17a 9722 // Usually only used internally, and auto-generated with each instance
a089699c
AD
9723 id: "",
9724
9725 // cleanContent: Boolean
81bea17a 9726 // Should the content be treated as a full html document,
a089699c
AD
9727 // and the real content stripped of <html>, <body> wrapper before injection
9728 cleanContent: false,
9729
9730 // extractContent: Boolean
9731 // Should the content be treated as a full html document, and the real content stripped of <html>, <body> wrapper before injection
9732 extractContent: false,
9733
9734 // parseContent: Boolean
9735 // Should the node by passed to the parser after the new content is set
9736 parseContent: false,
81bea17a
AD
9737
9738 // parserScope: String
9739 // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo,
9740 // will search for data-dojo-type (or dojoType). For backwards compatibility
9741 // reasons defaults to dojo._scopeName (which is "dojo" except when
9742 // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
9743 parserScope: dojo._scopeName,
9744
9745 // startup: Boolean
9746 // Start the child widgets after parsing them. Only obeyed if parseContent is true.
9747 startup: true,
a089699c
AD
9748
9749 // lifecyle methods
9750 constructor: function(/* Object */params, /* String|DomNode */node){
9751 // summary:
9752 // Provides a configurable, extensible object to wrap the setting on content on a node
9753 // call the set() method to actually set the content..
9754
9755 // the original params are mixed directly into the instance "this"
9756 dojo.mixin(this, params || {});
9757
9758 // give precedence to params.node vs. the node argument
9759 // and ensure its a node, not an id string
9760 node = this.node = dojo.byId( this.node || node );
9761
9762 if(!this.id){
9763 this.id = [
9764 "Setter",
81bea17a 9765 (node) ? node.id || node.tagName : "",
a089699c
AD
9766 idCounter++
9767 ].join("_");
9768 }
9769 },
9770 set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){
9771 // summary:
81bea17a 9772 // front-end to the set-content sequence
a089699c
AD
9773 // cont:
9774 // An html string, node or enumerable list of nodes for insertion into the dom
9775 // If not provided, the object's content property will be used
9776 if(undefined !== cont){
9777 this.content = cont;
9778 }
9779 // in the re-use scenario, set needs to be able to mixin new configuration
9780 if(params){
9781 this._mixin(params);
9782 }
9783
9784 this.onBegin();
9785 this.setContent();
9786 this.onEnd();
9787
9788 return this.node;
9789 },
9790 setContent: function(){
9791 // summary:
81bea17a 9792 // sets the content on the node
a089699c 9793
81bea17a 9794 var node = this.node;
a089699c
AD
9795 if(!node) {
9796 // can't proceed
9797 throw new Error(this.declaredClass + ": setContent given no node");
9798 }
9799 try{
9800 node = dojo.html._setNodeContent(node, this.content);
9801 }catch(e){
9802 // check if a domfault occurs when we are appending this.errorMessage
9803 // like for instance if domNode is a UL and we try append a DIV
9804
9805 // FIXME: need to allow the user to provide a content error message string
81bea17a 9806 var errMess = this.onContentError(e);
a089699c
AD
9807 try{
9808 node.innerHTML = errMess;
9809 }catch(e){
9810 console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e);
9811 }
9812 }
9813 // always put back the node for the next method
9814 this.node = node; // DomNode
9815 },
9816
9817 empty: function() {
9818 // summary
9819 // cleanly empty out existing content
9820
9821 // destroy any widgets from a previous run
81bea17a 9822 // NOTE: if you dont want this you'll need to empty
a089699c
AD
9823 // the parseResults array property yourself to avoid bad things happenning
9824 if(this.parseResults && this.parseResults.length) {
9825 dojo.forEach(this.parseResults, function(w) {
9826 if(w.destroy){
9827 w.destroy();
9828 }
9829 });
9830 delete this.parseResults;
9831 }
81bea17a 9832 // this is fast, but if you know its already empty or safe, you could
a089699c
AD
9833 // override empty to skip this step
9834 dojo.html._emptyNode(this.node);
9835 },
9836
9837 onBegin: function(){
9838 // summary
81bea17a
AD
9839 // Called after instantiation, but before set();
9840 // It allows modification of any of the object properties
a089699c 9841 // - including the node and content provided - before the set operation actually takes place
81bea17a 9842 // This default implementation checks for cleanContent and extractContent flags to
a089699c
AD
9843 // optionally pre-process html string content
9844 var cont = this.content;
9845
9846 if(dojo.isString(cont)){
9847 if(this.cleanContent){
9848 cont = dojo.html._secureForInnerHtml(cont);
9849 }
9850
9851 if(this.extractContent){
9852 var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
9853 if(match){ cont = match[1]; }
9854 }
9855 }
9856
9857 // clean out the node and any cruft associated with it - like widgets
9858 this.empty();
9859
9860 this.content = cont;
9861 return this.node; /* DomNode */
9862 },
9863
9864 onEnd: function(){
9865 // summary
9866 // Called after set(), when the new content has been pushed into the node
9867 // It provides an opportunity for post-processing before handing back the node to the caller
9868 // This default implementation checks a parseContent flag to optionally run the dojo parser over the new content
9869 if(this.parseContent){
9870 // populates this.parseResults if you need those..
9871 this._parse();
9872 }
9873 return this.node; /* DomNode */
9874 },
9875
9876 tearDown: function(){
9877 // summary
9878 // manually reset the Setter instance if its being re-used for example for another set()
9879 // description
81bea17a 9880 // tearDown() is not called automatically.
a089699c
AD
9881 // In normal use, the Setter instance properties are simply allowed to fall out of scope
9882 // but the tearDown method can be called to explicitly reset this instance.
81bea17a
AD
9883 delete this.parseResults;
9884 delete this.node;
9885 delete this.content;
a089699c
AD
9886 },
9887
9888 onContentError: function(err){
81bea17a 9889 return "Error occured setting content: " + err;
a089699c
AD
9890 },
9891
9892 _mixin: function(params){
9893 // mix properties/methods into the instance
81bea17a 9894 // TODO: the intention with tearDown is to put the Setter's state
a089699c
AD
9895 // back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params)
9896 // so we could do something here to move the original properties aside for later restoration
9897 var empty = {}, key;
9898 for(key in params){
9899 if(key in empty){ continue; }
9900 // TODO: here's our opportunity to mask the properties we dont consider configurable/overridable
9901 // .. but history shows we'll almost always guess wrong
81bea17a 9902 this[key] = params[key];
a089699c
AD
9903 }
9904 },
9905 _parse: function(){
81bea17a 9906 // summary:
a089699c
AD
9907 // runs the dojo parser over the node contents, storing any results in this.parseResults
9908 // Any errors resulting from parsing are passed to _onError for handling
9909
9910 var rootNode = this.node;
9911 try{
9912 // store the results (widgets, whatever) for potential retrieval
81bea17a
AD
9913 var inherited = {};
9914 dojo.forEach(["dir", "lang", "textDir"], function(name){
9915 if(this[name]){
9916 inherited[name] = this[name];
9917 }
9918 }, this);
a089699c
AD
9919 this.parseResults = dojo.parser.parse({
9920 rootNode: rootNode,
81bea17a
AD
9921 noStart: !this.startup,
9922 inherited: inherited,
9923 scope: this.parserScope
a089699c
AD
9924 });
9925 }catch(e){
9926 this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id);
9927 }
9928 },
9929
9930 _onError: function(type, err, consoleText){
9931 // summary:
9932 // shows user the string that is returned by on[type]Error
9933 // overide/implement on[type]Error and return your own string to customize
9934 var errText = this['on' + type + 'Error'].call(this, err);
9935 if(consoleText){
9936 console.error(consoleText, err);
9937 }else if(errText){ // a empty string won't change current content
9938 dojo.html._setNodeContent(this.node, errText, true);
9939 }
9940 }
9941 }); // end dojo.declare()
9942
9943 dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){
9944 // summary:
9945 // inserts (replaces) the given content into the given node. dojo.place(cont, node, "only")
9946 // may be a better choice for simple HTML insertion.
9947 // description:
9948 // Unless you need to use the params capabilities of this method, you should use
9949 // dojo.place(cont, node, "only"). dojo.place() has more robust support for injecting
9950 // an HTML string into the DOM, but it only handles inserting an HTML string as DOM
9951 // elements, or inserting a DOM node. dojo.place does not handle NodeList insertions
9952 // or the other capabilities as defined by the params object for this method.
9953 // node:
9954 // the parent element that will receive the content
9955 // cont:
81bea17a 9956 // the content to be set on the parent element.
a089699c 9957 // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
81bea17a 9958 // params:
a089699c
AD
9959 // Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter
9960 // example:
9961 // A safe string/node/nodelist content replacement/injection with hooks for extension
81bea17a
AD
9962 // Example Usage:
9963 // dojo.html.set(node, "some string");
9964 // dojo.html.set(node, contentNode, {options});
9965 // dojo.html.set(node, myNode.childNodes, {options});
a089699c
AD
9966 if(undefined == cont){
9967 console.warn("dojo.html.set: no cont argument provided, using empty string");
9968 cont = "";
81bea17a 9969 }
a089699c
AD
9970 if(!params){
9971 // simple and fast
9972 return dojo.html._setNodeContent(node, cont, true);
81bea17a 9973 }else{
a089699c
AD
9974 // more options but slower
9975 // note the arguments are reversed in order, to match the convention for instantiation via the parser
81bea17a
AD
9976 var op = new dojo.html._ContentSetter(dojo.mixin(
9977 params,
9978 { content: cont, node: node }
a089699c
AD
9979 ));
9980 return op.set();
9981 }
9982 };
9983})();
9984
9985}
9986
9987if(!dojo._hasResource["dijit.layout.ContentPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9988dojo._hasResource["dijit.layout.ContentPane"] = true;
9989dojo.provide("dijit.layout.ContentPane");
9990
9991
9992
a089699c
AD
9993
9994
9995
9996
9997dojo.declare(
81bea17a 9998 "dijit.layout.ContentPane", [dijit._Widget, dijit.layout._ContentPaneResizeMixin],
a089699c
AD
9999{
10000 // summary:
81bea17a
AD
10001 // A widget containing an HTML fragment, specified inline
10002 // or by uri. Fragment may include widgets.
10003 //
a089699c 10004 // description:
81bea17a
AD
10005 // This widget embeds a document fragment in the page, specified
10006 // either by uri, javascript generated markup or DOM reference.
10007 // Any widgets within this content are instantiated and managed,
10008 // but laid out according to the HTML structure. Unlike IFRAME,
10009 // ContentPane embeds a document fragment as would be found
10010 // inside the BODY tag of a full HTML document. It should not
10011 // contain the HTML, HEAD, or BODY tags.
10012 // For more advanced functionality with scripts and
10013 // stylesheets, see dojox.layout.ContentPane. This widget may be
10014 // used stand alone or as a base class for other widgets.
10015 // ContentPane is useful as a child of other layout containers
10016 // such as BorderContainer or TabContainer, but note that those
10017 // widgets can contain any widget as a child.
a089699c 10018 //
a089699c
AD
10019 // example:
10020 // Some quick samples:
81bea17a 10021 // To change the innerHTML: cp.set('content', '<b>new content</b>')
a089699c 10022 //
81bea17a 10023 // Or you can send it a NodeList: cp.set('content', dojo.query('div [class=selected]', userSelection))
a089699c 10024 //
81bea17a 10025 // To do an ajax update: cp.set('href', url)
a089699c
AD
10026
10027 // href: String
10028 // The href of the content that displays now.
10029 // Set this at construction if you want to load data externally when the
10030 // pane is shown. (Set preload=true to load it immediately.)
10031 // Changing href after creation doesn't have any effect; Use set('href', ...);
10032 href: "",
10033
10034/*=====
10035 // content: String || DomNode || NodeList || dijit._Widget
10036 // The innerHTML of the ContentPane.
81bea17a 10037 // Note that the initialization parameter / argument to set("content", ...)
a089699c
AD
10038 // can be a String, DomNode, Nodelist, or _Widget.
10039 content: "",
10040=====*/
10041
10042 // extractContent: Boolean
10043 // Extract visible content from inside of <body> .... </body>.
10044 // I.e., strip <html> and <head> (and it's contents) from the href
10045 extractContent: false,
10046
10047 // parseOnLoad: Boolean
10048 // Parse content and create the widgets, if any.
10049 parseOnLoad: true,
10050
81bea17a
AD
10051 // parserScope: String
10052 // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo,
10053 // will search for data-dojo-type (or dojoType). For backwards compatibility
10054 // reasons defaults to dojo._scopeName (which is "dojo" except when
10055 // multi-version support is used, when it will be something like dojo16, dojo20, etc.)
10056 parserScope: dojo._scopeName,
10057
a089699c
AD
10058 // preventCache: Boolean
10059 // Prevent caching of data from href's by appending a timestamp to the href.
10060 preventCache: false,
10061
10062 // preload: Boolean
10063 // Force load of data on initialization even if pane is hidden.
10064 preload: false,
10065
10066 // refreshOnShow: Boolean
10067 // Refresh (re-download) content when pane goes from hidden to shown
10068 refreshOnShow: false,
10069
10070 // loadingMessage: String
10071 // Message that shows while downloading
10072 loadingMessage: "<span class='dijitContentPaneLoading'>${loadingState}</span>",
10073
10074 // errorMessage: String
10075 // Message that shows if an error occurs
10076 errorMessage: "<span class='dijitContentPaneError'>${errorState}</span>",
10077
10078 // isLoaded: [readonly] Boolean
10079 // True if the ContentPane has data in it, either specified
10080 // during initialization (via href or inline content), or set
81bea17a 10081 // via set('content', ...) / set('href', ...)
a089699c
AD
10082 //
10083 // False if it doesn't have any content, or if ContentPane is
10084 // still in the process of downloading href.
10085 isLoaded: false,
10086
10087 baseClass: "dijitContentPane",
10088
a089699c
AD
10089 // ioArgs: Object
10090 // Parameters to pass to xhrGet() request, for example:
10091 // | <div dojoType="dijit.layout.ContentPane" href="./bar" ioArgs="{timeout: 500}">
10092 ioArgs: {},
10093
a089699c 10094 // onLoadDeferred: [readonly] dojo.Deferred
81bea17a 10095 // This is the `dojo.Deferred` returned by set('href', ...) and refresh().
a089699c 10096 // Calling onLoadDeferred.addCallback() or addErrback() registers your
81bea17a 10097 // callback to be called only once, when the prior set('href', ...) call or
a089699c
AD
10098 // the initial href parameter to the constructor finishes loading.
10099 //
81bea17a
AD
10100 // This is different than an onLoad() handler which gets called any time any href
10101 // or content is loaded.
a089699c
AD
10102 onLoadDeferred: null,
10103
10104 // Override _Widget's attributeMap because we don't want the title attribute (used to specify
10105 // tab labels) to be copied to ContentPane.domNode... otherwise a tooltip shows up over the
10106 // entire pane.
10107 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
10108 title: []
10109 }),
10110
81bea17a
AD
10111 // Flag to parser that I'll parse my contents, so it shouldn't.
10112 stopParser: true,
10113
10114 // template: [private] Boolean
10115 // Flag from the parser that this ContentPane is inside a template
10116 // so the contents are pre-parsed.
10117 // (TODO: this declaration can be commented out in 2.0)
10118 template: false,
10119
10120 create: function(params, srcNodeRef){
10121 // Convert a srcNodeRef argument into a content parameter, so that the original contents are
10122 // processed in the same way as contents set via set("content", ...), calling the parser etc.
10123 // Avoid modifying original params object since that breaks NodeList instantiation, see #11906.
10124 if((!params || !params.template) && srcNodeRef && !("href" in params) && !("content" in params)){
10125 var df = dojo.doc.createDocumentFragment();
10126 srcNodeRef = dojo.byId(srcNodeRef)
10127 while(srcNodeRef.firstChild){
10128 df.appendChild(srcNodeRef.firstChild);
10129 }
10130 params = dojo.delegate(params, {content: df});
10131 }
10132 this.inherited(arguments, [params, srcNodeRef]);
10133 },
10134
a089699c
AD
10135 postMixInProperties: function(){
10136 this.inherited(arguments);
10137 var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang);
10138 this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages);
10139 this.errorMessage = dojo.string.substitute(this.errorMessage, messages);
a089699c
AD
10140 },
10141
10142 buildRendering: function(){
a089699c 10143 this.inherited(arguments);
81bea17a
AD
10144
10145 // Since we have no template we need to set this.containerNode ourselves, to make getChildren() work.
10146 // For subclasses of ContentPane that do have a template, does nothing.
a089699c 10147 if(!this.containerNode){
a089699c
AD
10148 this.containerNode = this.domNode;
10149 }
a089699c 10150
a089699c 10151 // remove the title attribute so it doesn't show up when hovering
81bea17a 10152 // over a node (TODO: remove in 2.0, no longer needed after #11490)
a089699c
AD
10153 this.domNode.title = "";
10154
10155 if(!dojo.attr(this.domNode,"role")){
10156 dijit.setWaiRole(this.domNode, "group");
10157 }
a089699c
AD
10158 },
10159
81bea17a 10160 _startChildren: function(){
a089699c 10161 // summary:
81bea17a 10162 // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
a089699c 10163
81bea17a 10164 // This starts all the widgets
a089699c 10165 this.inherited(arguments);
a089699c 10166
81bea17a
AD
10167 // And this catches stuff like dojo.dnd.Source
10168 if(this._contentSetter){
10169 dojo.forEach(this._contentSetter.parseResults, function(obj){
10170 if(!obj._started && !obj._destroyed && dojo.isFunction(obj.startup)){
10171 obj.startup();
10172 obj._started = true;
10173 }
10174 }, this);
10175 }
a089699c
AD
10176 },
10177
10178 setHref: function(/*String|Uri*/ href){
10179 // summary:
10180 // Deprecated. Use set('href', ...) instead.
10181 dojo.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.", "", "2.0");
10182 return this.set("href", href);
10183 },
10184 _setHrefAttr: function(/*String|Uri*/ href){
10185 // summary:
81bea17a 10186 // Hook so set("href", ...) works.
a089699c
AD
10187 // description:
10188 // Reset the (external defined) content of this pane and replace with new url
10189 // Note: It delays the download until widget is shown if preload is false.
10190 // href:
10191 // url to the page you want to get, must be within the same domain as your mainpage
10192
81bea17a 10193 // Cancel any in-flight requests (a set('href', ...) will cancel any in-flight set('href', ...))
a089699c
AD
10194 this.cancel();
10195
10196 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
81bea17a 10197 this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad"));
a089699c 10198
81bea17a 10199 this._set("href", href);
a089699c
AD
10200
10201 // _setHrefAttr() is called during creation and by the user, after creation.
81bea17a
AD
10202 // Assuming preload == false, only in the second case do we actually load the URL;
10203 // otherwise it's done in startup(), and only if this widget is shown.
10204 if(this.preload || (this._created && this._isShown())){
a089699c
AD
10205 this._load();
10206 }else{
10207 // Set flag to indicate that href needs to be loaded the next time the
10208 // ContentPane is made visible
10209 this._hrefChanged = true;
10210 }
10211
10212 return this.onLoadDeferred; // dojo.Deferred
10213 },
10214
10215 setContent: function(/*String|DomNode|Nodelist*/data){
10216 // summary:
10217 // Deprecated. Use set('content', ...) instead.
10218 dojo.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use set('content', ...) instead.", "", "2.0");
10219 this.set("content", data);
10220 },
10221 _setContentAttr: function(/*String|DomNode|Nodelist*/data){
10222 // summary:
81bea17a 10223 // Hook to make set("content", ...) work.
a089699c
AD
10224 // Replaces old content with data content, include style classes from old content
10225 // data:
10226 // the new Content may be String, DomNode or NodeList
10227 //
10228 // if data is a NodeList (or an array of nodes) nodes are copied
10229 // so you can import nodes from another document implicitly
10230
10231 // clear href so we can't run refresh and clear content
10232 // refresh should only work if we downloaded the content
81bea17a 10233 this._set("href", "");
a089699c 10234
81bea17a 10235 // Cancel any in-flight requests (a set('content', ...) will cancel any in-flight set('href', ...))
a089699c
AD
10236 this.cancel();
10237
10238 // Even though user is just setting content directly, still need to define an onLoadDeferred
10239 // because the _onLoadHandler() handler is still getting called from setContent()
10240 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
81bea17a
AD
10241 if(this._created){
10242 // For back-compat reasons, call onLoad() for set('content', ...)
10243 // calls but not for content specified in srcNodeRef (ie: <div dojoType=ContentPane>...</div>)
10244 // or as initialization parameter (ie: new ContentPane({content: ...})
10245 this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad"));
10246 }
a089699c
AD
10247
10248 this._setContent(data || "");
10249
81bea17a 10250 this._isDownloaded = false; // mark that content is from a set('content') not a set('href')
a089699c
AD
10251
10252 return this.onLoadDeferred; // dojo.Deferred
10253 },
10254 _getContentAttr: function(){
10255 // summary:
81bea17a 10256 // Hook to make get("content") work
a089699c
AD
10257 return this.containerNode.innerHTML;
10258 },
10259
10260 cancel: function(){
10261 // summary:
10262 // Cancels an in-flight download of content
10263 if(this._xhrDfd && (this._xhrDfd.fired == -1)){
10264 this._xhrDfd.cancel();
10265 }
10266 delete this._xhrDfd; // garbage collect
10267
10268 this.onLoadDeferred = null;
10269 },
10270
10271 uninitialize: function(){
10272 if(this._beingDestroyed){
10273 this.cancel();
10274 }
10275 this.inherited(arguments);
10276 },
10277
10278 destroyRecursive: function(/*Boolean*/ preserveDom){
10279 // summary:
10280 // Destroy the ContentPane and its contents
10281
10282 // if we have multiple controllers destroying us, bail after the first
10283 if(this._beingDestroyed){
10284 return;
10285 }
10286 this.inherited(arguments);
10287 },
10288
a089699c
AD
10289 _onShow: function(){
10290 // summary:
10291 // Called when the ContentPane is made visible
10292 // description:
10293 // For a plain ContentPane, this is called on initialization, from startup().
10294 // If the ContentPane is a hidden pane of a TabContainer etc., then it's
10295 // called whenever the pane is made visible.
10296 //
10297 // Does necessary processing, including href download and layout/resize of
10298 // child widget(s)
10299
81bea17a
AD
10300 this.inherited(arguments);
10301
a089699c
AD
10302 if(this.href){
10303 if(!this._xhrDfd && // if there's an href that isn't already being loaded
10304 (!this.isLoaded || this._hrefChanged || this.refreshOnShow)
10305 ){
81bea17a 10306 return this.refresh(); // If child has an href, promise that fires when the load is complete
a089699c
AD
10307 }
10308 }
a089699c
AD
10309 },
10310
10311 refresh: function(){
10312 // summary:
10313 // [Re]download contents of href and display
10314 // description:
10315 // 1. cancels any currently in-flight requests
10316 // 2. posts "loading..." message
10317 // 3. sends XHR to download new data
10318
10319 // Cancel possible prior in-flight request
10320 this.cancel();
10321
10322 this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel"));
81bea17a 10323 this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad"));
a089699c 10324 this._load();
81bea17a 10325 return this.onLoadDeferred; // If child has an href, promise that fires when refresh is complete
a089699c
AD
10326 },
10327
10328 _load: function(){
10329 // summary:
10330 // Load/reload the href specified in this.href
10331
10332 // display loading message
10333 this._setContent(this.onDownloadStart(), true);
10334
10335 var self = this;
10336 var getArgs = {
10337 preventCache: (this.preventCache || this.refreshOnShow),
10338 url: this.href,
10339 handleAs: "text"
10340 };
10341 if(dojo.isObject(this.ioArgs)){
10342 dojo.mixin(getArgs, this.ioArgs);
10343 }
10344
10345 var hand = (this._xhrDfd = (this.ioMethod || dojo.xhrGet)(getArgs));
10346
10347 hand.addCallback(function(html){
10348 try{
10349 self._isDownloaded = true;
10350 self._setContent(html, false);
10351 self.onDownloadEnd();
10352 }catch(err){
10353 self._onError('Content', err); // onContentError
10354 }
10355 delete self._xhrDfd;
10356 return html;
10357 });
10358
10359 hand.addErrback(function(err){
10360 if(!hand.canceled){
10361 // show error message in the pane
10362 self._onError('Download', err); // onDownloadError
10363 }
10364 delete self._xhrDfd;
10365 return err;
10366 });
10367
10368 // Remove flag saying that a load is needed
10369 delete this._hrefChanged;
10370 },
10371
10372 _onLoadHandler: function(data){
10373 // summary:
10374 // This is called whenever new content is being loaded
81bea17a 10375 this._set("isLoaded", true);
a089699c
AD
10376 try{
10377 this.onLoadDeferred.callback(data);
a089699c
AD
10378 }catch(e){
10379 console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message);
10380 }
10381 },
10382
10383 _onUnloadHandler: function(){
10384 // summary:
10385 // This is called whenever the content is being unloaded
81bea17a 10386 this._set("isLoaded", false);
a089699c
AD
10387 try{
10388 this.onUnload();
10389 }catch(e){
10390 console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message);
10391 }
10392 },
10393
10394 destroyDescendants: function(){
10395 // summary:
10396 // Destroy all the widgets inside the ContentPane and empty containerNode
10397
10398 // Make sure we call onUnload (but only when the ContentPane has real content)
10399 if(this.isLoaded){
10400 this._onUnloadHandler();
10401 }
10402
10403 // Even if this.isLoaded == false there might still be a "Loading..." message
10404 // to erase, so continue...
10405
10406 // For historical reasons we need to delete all widgets under this.containerNode,
10407 // even ones that the user has created manually.
10408 var setter = this._contentSetter;
10409 dojo.forEach(this.getChildren(), function(widget){
10410 if(widget.destroyRecursive){
10411 widget.destroyRecursive();
10412 }
10413 });
10414 if(setter){
10415 // Most of the widgets in setter.parseResults have already been destroyed, but
10416 // things like Menu that have been moved to <body> haven't yet
10417 dojo.forEach(setter.parseResults, function(widget){
10418 if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == dojo.body()){
10419 widget.destroyRecursive();
10420 }
10421 });
10422 delete setter.parseResults;
10423 }
10424
10425 // And then clear away all the DOM nodes
10426 dojo.html._emptyNode(this.containerNode);
10427
10428 // Delete any state information we have about current contents
10429 delete this._singleChild;
10430 },
10431
81bea17a 10432 _setContent: function(/*String|DocumentFragment*/ cont, /*Boolean*/ isFakeContent){
a089699c
AD
10433 // summary:
10434 // Insert the content into the container node
10435
10436 // first get rid of child widgets
10437 this.destroyDescendants();
10438
10439 // dojo.html.set will take care of the rest of the details
10440 // we provide an override for the error handling to ensure the widget gets the errors
10441 // configure the setter instance with only the relevant widget instance properties
10442 // NOTE: unless we hook into attr, or provide property setters for each property,
10443 // we need to re-configure the ContentSetter with each use
10444 var setter = this._contentSetter;
10445 if(! (setter && setter instanceof dojo.html._ContentSetter)){
10446 setter = this._contentSetter = new dojo.html._ContentSetter({
10447 node: this.containerNode,
10448 _onError: dojo.hitch(this, this._onError),
10449 onContentError: dojo.hitch(this, function(e){
10450 // fires if a domfault occurs when we are appending this.errorMessage
10451 // like for instance if domNode is a UL and we try append a DIV
10452 var errMess = this.onContentError(e);
10453 try{
10454 this.containerNode.innerHTML = errMess;
10455 }catch(e){
10456 console.error('Fatal '+this.id+' could not change content due to '+e.message, e);
10457 }
10458 })/*,
10459 _onError */
10460 });
10461 };
10462
10463 var setterParams = dojo.mixin({
10464 cleanContent: this.cleanContent,
10465 extractContent: this.extractContent,
10466 parseContent: this.parseOnLoad,
81bea17a
AD
10467 parserScope: this.parserScope,
10468 startup: false,
a089699c
AD
10469 dir: this.dir,
10470 lang: this.lang
10471 }, this._contentSetterParams || {});
10472
81bea17a 10473 setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams );
a089699c
AD
10474
10475 // setter params must be pulled afresh from the ContentPane each time
10476 delete this._contentSetterParams;
10477
81bea17a
AD
10478 if(this.doLayout){
10479 this._checkIfSingleChild();
10480 }
a089699c 10481
81bea17a
AD
10482 if(!isFakeContent){
10483 if(this._started){
10484 // Startup each top level child widget (and they will start their children, recursively)
10485 this._startChildren();
10486
10487 // Call resize() on each of my child layout widgets,
10488 // or resize() on my single child layout widget...
10489 // either now (if I'm currently visible) or when I become visible
10490 this._scheduleLayout();
10491 }
a089699c
AD
10492
10493 this._onLoadHandler(cont);
10494 }
10495 },
10496
10497 _onError: function(type, err, consoleText){
10498 this.onLoadDeferred.errback(err);
10499
10500 // shows user the string that is returned by on[type]Error
81bea17a 10501 // override on[type]Error and return your own string to customize
a089699c
AD
10502 var errText = this['on' + type + 'Error'].call(this, err);
10503 if(consoleText){
10504 console.error(consoleText, err);
10505 }else if(errText){// a empty string won't change current content
10506 this._setContent(errText, true);
10507 }
10508 },
10509
a089699c
AD
10510 // EVENT's, should be overide-able
10511 onLoad: function(data){
10512 // summary:
10513 // Event hook, is called after everything is loaded and widgetified
10514 // tags:
10515 // callback
10516 },
10517
10518 onUnload: function(){
10519 // summary:
10520 // Event hook, is called before old content is cleared
10521 // tags:
10522 // callback
10523 },
10524
10525 onDownloadStart: function(){
10526 // summary:
10527 // Called before download starts.
10528 // description:
10529 // The string returned by this function will be the html
10530 // that tells the user we are loading something.
10531 // Override with your own function if you want to change text.
10532 // tags:
10533 // extension
10534 return this.loadingMessage;
10535 },
10536
10537 onContentError: function(/*Error*/ error){
10538 // summary:
10539 // Called on DOM faults, require faults etc. in content.
10540 //
10541 // In order to display an error message in the pane, return
10542 // the error message from this method, as an HTML string.
10543 //
10544 // By default (if this method is not overriden), it returns
10545 // nothing, so the error message is just printed to the console.
10546 // tags:
10547 // extension
10548 },
10549
10550 onDownloadError: function(/*Error*/ error){
10551 // summary:
10552 // Called when download error occurs.
10553 //
10554 // In order to display an error message in the pane, return
10555 // the error message from this method, as an HTML string.
10556 //
10557 // Default behavior (if this method is not overriden) is to display
10558 // the error message inside the pane.
10559 // tags:
10560 // extension
10561 return this.errorMessage;
10562 },
10563
10564 onDownloadEnd: function(){
10565 // summary:
10566 // Called when download is finished.
10567 // tags:
10568 // callback
10569 }
10570});
10571
10572}
10573
10574if(!dojo._hasResource["dijit.TooltipDialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
10575dojo._hasResource["dijit.TooltipDialog"] = true;
10576dojo.provide("dijit.TooltipDialog");
10577
10578
10579
10580
10581
10582
10583dojo.declare(
10584 "dijit.TooltipDialog",
10585 [dijit.layout.ContentPane, dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin],
10586 {
10587 // summary:
10588 // Pops up a dialog that appears like a Tooltip
10589
10590 // title: String
10591 // Description of tooltip dialog (required for a11y)
10592 title: "",
10593
10594 // doLayout: [protected] Boolean
10595 // Don't change this parameter from the default value.
10596 // This ContentPane parameter doesn't make sense for TooltipDialog, since TooltipDialog
10597 // is never a child of a layout container, nor can you specify the size of
10598 // TooltipDialog in order to control the size of an inner widget.
10599 doLayout: false,
10600
10601 // autofocus: Boolean
10602 // A Toggle to modify the default focus behavior of a Dialog, which
10603 // is to focus on the first dialog element after opening the dialog.
10604 // False will disable autofocusing. Default: true
10605 autofocus: true,
10606
10607 // baseClass: [protected] String
10608 // The root className to use for the various states of this widget
10609 baseClass: "dijitTooltipDialog",
10610
10611 // _firstFocusItem: [private] [readonly] DomNode
10612 // The pointer to the first focusable node in the dialog.
10613 // Set by `dijit._DialogMixin._getFocusItems`.
10614 _firstFocusItem: null,
10615
10616 // _lastFocusItem: [private] [readonly] DomNode
10617 // The pointer to which node has focus prior to our dialog.
10618 // Set by `dijit._DialogMixin._getFocusItems`.
10619 _lastFocusItem: null,
10620
81bea17a
AD
10621 templateString: dojo.cache("dijit", "templates/TooltipDialog.html", "<div role=\"presentation\" tabIndex=\"-1\">\n\t<div class=\"dijitTooltipContainer\" role=\"presentation\">\n\t\t<div class =\"dijitTooltipContents dijitTooltipFocusNode\" dojoAttachPoint=\"containerNode\" role=\"dialog\"></div>\n\t</div>\n\t<div class=\"dijitTooltipConnector\" role=\"presentation\"></div>\n</div>\n"),
10622
10623 _setTitleAttr: function(/*String*/ title){
10624 this.containerNode.title = title;
10625 this._set("title", title)
10626 },
a089699c
AD
10627
10628 postCreate: function(){
10629 this.inherited(arguments);
10630 this.connect(this.containerNode, "onkeypress", "_onKey");
a089699c
AD
10631 },
10632
10633 orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ corner){
10634 // summary:
10635 // Configure widget to be displayed in given position relative to the button.
10636 // This is called from the dijit.popup code, and should not be called
10637 // directly.
10638 // tags:
10639 // protected
81bea17a
AD
10640 var newC = "dijitTooltipAB" + (corner.charAt(1) == 'L' ? "Left" : "Right")
10641 + " dijitTooltip"
10642 + (corner.charAt(0) == 'T' ? "Below" : "Above");
10643
10644 dojo.replaceClass(this.domNode, newC, this._currentOrientClass || "");
10645 this._currentOrientClass = newC;
10646 },
10647
10648 focus: function(){
10649 // summary:
10650 // Focus on first field
10651 this._getFocusItems(this.containerNode);
10652 dijit.focus(this._firstFocusItem);
a089699c
AD
10653 },
10654
10655 onOpen: function(/*Object*/ pos){
10656 // summary:
10657 // Called when dialog is displayed.
10658 // This is called from the dijit.popup code, and should not be called directly.
10659 // tags:
10660 // protected
10661
10662 this.orient(this.domNode,pos.aroundCorner, pos.corner);
10663 this._onShow(); // lazy load trigger
a089699c
AD
10664 },
10665
10666 onClose: function(){
10667 // summary:
10668 // Called when dialog is hidden.
10669 // This is called from the dijit.popup code, and should not be called directly.
10670 // tags:
10671 // protected
10672 this.onHide();
10673 },
10674
10675 _onKey: function(/*Event*/ evt){
10676 // summary:
10677 // Handler for keyboard events
10678 // description:
10679 // Keep keyboard focus in dialog; close dialog on escape key
10680 // tags:
10681 // private
10682
10683 var node = evt.target;
10684 var dk = dojo.keys;
10685 if(evt.charOrCode === dk.TAB){
10686 this._getFocusItems(this.containerNode);
10687 }
10688 var singleFocusItem = (this._firstFocusItem == this._lastFocusItem);
10689 if(evt.charOrCode == dk.ESCAPE){
10690 // Use setTimeout to avoid crash on IE, see #10396.
10691 setTimeout(dojo.hitch(this, "onCancel"), 0);
10692 dojo.stopEvent(evt);
10693 }else if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){
10694 if(!singleFocusItem){
10695 dijit.focus(this._lastFocusItem); // send focus to last item in dialog
10696 }
10697 dojo.stopEvent(evt);
10698 }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){
10699 if(!singleFocusItem){
10700 dijit.focus(this._firstFocusItem); // send focus to first item in dialog
10701 }
10702 dojo.stopEvent(evt);
10703 }else if(evt.charOrCode === dk.TAB){
10704 // we want the browser's default tab handling to move focus
10705 // but we don't want the tab to propagate upwards
10706 evt.stopPropagation();
10707 }
10708 }
10709 }
10710 );
10711
10712}
10713
10714if(!dojo._hasResource["dijit.Dialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
10715dojo._hasResource["dijit.Dialog"] = true;
10716dojo.provide("dijit.Dialog");
10717
10718
10719
10720
10721
10722
10723
10724
10725
10726
10727
10728
10729
10730
10731
81bea17a
AD
10732// dijit/TooltipDialog required for back-compat. TODO: remove in 2.0
10733
a089699c
AD
10734/*=====
10735dijit._underlay = function(kwArgs){
10736 // summary:
10737 // A shared instance of a `dijit.DialogUnderlay`
10738 //
10739 // description:
10740 // A shared instance of a `dijit.DialogUnderlay` created and
10741 // used by `dijit.Dialog`, though never created until some Dialog
10742 // or subclass thereof is shown.
10743};
10744=====*/
a089699c
AD
10745dojo.declare(
10746 "dijit._DialogBase",
10747 [dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin, dijit._CssStateMixin],
10748 {
10749 // summary:
10750 // A modal dialog Widget
10751 //
10752 // description:
10753 // Pops up a modal dialog window, blocking access to the screen
10754 // and also graying out the screen Dialog is extended from
10755 // ContentPane so it supports all the same parameters (href, etc.)
10756 //
10757 // example:
10758 // | <div dojoType="dijit.Dialog" href="test.html"></div>
10759 //
10760 // example:
10761 // | var foo = new dijit.Dialog({ title: "test dialog", content: "test content" };
10762 // | dojo.body().appendChild(foo.domNode);
10763 // | foo.startup();
10764
81bea17a 10765 templateString: dojo.cache("dijit", "templates/Dialog.html", "<div class=\"dijitDialog\" role=\"dialog\" aria-labelledby=\"${id}_title\">\n\t<div dojoAttachPoint=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t<span dojoAttachPoint=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"></span>\n\t<span dojoAttachPoint=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" dojoAttachEvent=\"ondijitclick: onCancel\" title=\"${buttonCancel}\" role=\"button\" tabIndex=\"-1\">\n\t\t<span dojoAttachPoint=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t</span>\n\t</div>\n\t\t<div dojoAttachPoint=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n"),
a089699c
AD
10766
10767 baseClass: "dijitDialog",
10768
10769 cssStateNodes: {
10770 closeButtonNode: "dijitDialogCloseIcon"
10771 },
10772
10773 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
10774 title: [
10775 { node: "titleNode", type: "innerHTML" },
10776 { node: "titleBar", type: "attribute" }
10777 ],
10778 "aria-describedby":""
10779 }),
10780
81bea17a 10781 // open: [readonly] Boolean
a089699c
AD
10782 // True if Dialog is currently displayed on screen.
10783 open: false,
10784
10785 // duration: Integer
10786 // The time in milliseconds it takes the dialog to fade in and out
10787 duration: dijit.defaultDuration,
10788
10789 // refocus: Boolean
10790 // A Toggle to modify the default focus behavior of a Dialog, which
10791 // is to re-focus the element which had focus before being opened.
10792 // False will disable refocusing. Default: true
10793 refocus: true,
10794
10795 // autofocus: Boolean
10796 // A Toggle to modify the default focus behavior of a Dialog, which
10797 // is to focus on the first dialog element after opening the dialog.
10798 // False will disable autofocusing. Default: true
10799 autofocus: true,
10800
81bea17a 10801 // _firstFocusItem: [private readonly] DomNode
a089699c
AD
10802 // The pointer to the first focusable node in the dialog.
10803 // Set by `dijit._DialogMixin._getFocusItems`.
10804 _firstFocusItem: null,
10805
81bea17a 10806 // _lastFocusItem: [private readonly] DomNode
a089699c
AD
10807 // The pointer to which node has focus prior to our dialog.
10808 // Set by `dijit._DialogMixin._getFocusItems`.
10809 _lastFocusItem: null,
10810
10811 // doLayout: [protected] Boolean
10812 // Don't change this parameter from the default value.
10813 // This ContentPane parameter doesn't make sense for Dialog, since Dialog
10814 // is never a child of a layout container, nor can you specify the size of
10815 // Dialog in order to control the size of an inner widget.
10816 doLayout: false,
10817
10818 // draggable: Boolean
10819 // Toggles the moveable aspect of the Dialog. If true, Dialog
10820 // can be dragged by it's title. If false it will remain centered
10821 // in the viewport.
10822 draggable: true,
10823
10824 //aria-describedby: String
10825 // Allows the user to add an aria-describedby attribute onto the dialog. The value should
10826 // be the id of the container element of text that describes the dialog purpose (usually
10827 // the first text in the dialog).
10828 // <div dojoType="dijit.Dialog" aria-describedby="intro" .....>
10829 // <div id="intro">Introductory text</div>
10830 // <div>rest of dialog contents</div>
10831 // </div>
10832 "aria-describedby":"",
10833
10834 postMixInProperties: function(){
10835 var _nlsResources = dojo.i18n.getLocalization("dijit", "common");
10836 dojo.mixin(this, _nlsResources);
10837 this.inherited(arguments);
10838 },
10839
10840 postCreate: function(){
10841 dojo.style(this.domNode, {
10842 display: "none",
10843 position:"absolute"
10844 });
10845 dojo.body().appendChild(this.domNode);
10846
10847 this.inherited(arguments);
10848
10849 this.connect(this, "onExecute", "hide");
10850 this.connect(this, "onCancel", "hide");
10851 this._modalconnects = [];
10852 },
10853
10854 onLoad: function(){
10855 // summary:
10856 // Called when data has been loaded from an href.
10857 // Unlike most other callbacks, this function can be connected to (via `dojo.connect`)
81bea17a 10858 // but should *not* be overridden.
a089699c
AD
10859 // tags:
10860 // callback
10861
10862 // when href is specified we need to reposition the dialog after the data is loaded
10863 // and find the focusable elements
10864 this._position();
81bea17a 10865 if(this.autofocus && dijit._DialogLevelManager.isTop(this)){
a089699c
AD
10866 this._getFocusItems(this.domNode);
10867 dijit.focus(this._firstFocusItem);
10868 }
10869 this.inherited(arguments);
10870 },
10871
10872 _endDrag: function(e){
10873 // summary:
10874 // Called after dragging the Dialog. Saves the position of the dialog in the viewport.
10875 // tags:
10876 // private
10877 if(e && e.node && e.node === this.domNode){
10878 this._relativePosition = dojo.position(e.node);
10879 }
10880 },
10881
10882 _setup: function(){
10883 // summary:
10884 // Stuff we need to do before showing the Dialog for the first
10885 // time (but we defer it until right beforehand, for
10886 // performance reasons).
10887 // tags:
10888 // private
10889
10890 var node = this.domNode;
10891
10892 if(this.titleBar && this.draggable){
10893 this._moveable = (dojo.isIE == 6) ?
10894 new dojo.dnd.TimedMoveable(node, { handle: this.titleBar }) : // prevent overload, see #5285
10895 new dojo.dnd.Moveable(node, { handle: this.titleBar, timeout: 0 });
81bea17a 10896 this._dndListener = dojo.subscribe("/dnd/move/stop",this,"_endDrag");
a089699c
AD
10897 }else{
10898 dojo.addClass(node,"dijitDialogFixed");
10899 }
10900
10901 this.underlayAttrs = {
10902 dialogId: this.id,
10903 "class": dojo.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" ")
10904 };
a089699c
AD
10905 },
10906
10907 _size: function(){
10908 // summary:
10909 // If necessary, shrink dialog contents so dialog fits in viewport
10910 // tags:
10911 // private
10912
10913 this._checkIfSingleChild();
10914
10915 // If we resized the dialog contents earlier, reset them back to original size, so
10916 // that if the user later increases the viewport size, the dialog can display w/out a scrollbar.
10917 // Need to do this before the dojo.marginBox(this.domNode) call below.
10918 if(this._singleChild){
10919 if(this._singleChildOriginalStyle){
10920 this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle;
10921 }
10922 delete this._singleChildOriginalStyle;
10923 }else{
10924 dojo.style(this.containerNode, {
10925 width:"auto",
10926 height:"auto"
10927 });
10928 }
10929
81bea17a 10930 var mb = dojo._getMarginSize(this.domNode);
a089699c
AD
10931 var viewport = dojo.window.getBox();
10932 if(mb.w >= viewport.w || mb.h >= viewport.h){
10933 // Reduce size of dialog contents so that dialog fits in viewport
10934
10935 var w = Math.min(mb.w, Math.floor(viewport.w * 0.75)),
10936 h = Math.min(mb.h, Math.floor(viewport.h * 0.75));
10937
10938 if(this._singleChild && this._singleChild.resize){
10939 this._singleChildOriginalStyle = this._singleChild.domNode.style.cssText;
10940 this._singleChild.resize({w: w, h: h});
10941 }else{
10942 dojo.style(this.containerNode, {
10943 width: w + "px",
10944 height: h + "px",
10945 overflow: "auto",
10946 position: "relative" // workaround IE bug moving scrollbar or dragging dialog
10947 });
10948 }
10949 }else{
10950 if(this._singleChild && this._singleChild.resize){
10951 this._singleChild.resize();
10952 }
10953 }
10954 },
10955
10956 _position: function(){
10957 // summary:
10958 // Position modal dialog in the viewport. If no relative offset
10959 // in the viewport has been determined (by dragging, for instance),
10960 // center the node. Otherwise, use the Dialog's stored relative offset,
10961 // and position the node to top: left: values based on the viewport.
10962 // tags:
10963 // private
10964 if(!dojo.hasClass(dojo.body(),"dojoMove")){
10965 var node = this.domNode,
10966 viewport = dojo.window.getBox(),
10967 p = this._relativePosition,
10968 bb = p ? null : dojo._getBorderBox(node),
10969 l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)),
10970 t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2))
10971 ;
10972 dojo.style(node,{
10973 left: l + "px",
10974 top: t + "px"
10975 });
10976 }
10977 },
10978
10979 _onKey: function(/*Event*/ evt){
10980 // summary:
10981 // Handles the keyboard events for accessibility reasons
10982 // tags:
10983 // private
10984
a089699c
AD
10985 if(evt.charOrCode){
10986 var dk = dojo.keys;
10987 var node = evt.target;
10988 if(evt.charOrCode === dk.TAB){
10989 this._getFocusItems(this.domNode);
10990 }
10991 var singleFocusItem = (this._firstFocusItem == this._lastFocusItem);
10992 // see if we are shift-tabbing from first focusable item on dialog
10993 if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){
10994 if(!singleFocusItem){
10995 dijit.focus(this._lastFocusItem); // send focus to last item in dialog
10996 }
10997 dojo.stopEvent(evt);
10998 }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){
10999 if(!singleFocusItem){
11000 dijit.focus(this._firstFocusItem); // send focus to first item in dialog
11001 }
11002 dojo.stopEvent(evt);
11003 }else{
11004 // see if the key is for the dialog
11005 while(node){
11006 if(node == this.domNode || dojo.hasClass(node, "dijitPopup")){
11007 if(evt.charOrCode == dk.ESCAPE){
11008 this.onCancel();
11009 }else{
11010 return; // just let it go
11011 }
11012 }
11013 node = node.parentNode;
11014 }
11015 // this key is for the disabled document window
11016 if(evt.charOrCode !== dk.TAB){ // allow tabbing into the dialog for a11y
11017 dojo.stopEvent(evt);
11018 // opera won't tab to a div
11019 }else if(!dojo.isOpera){
11020 try{
11021 this._firstFocusItem.focus();
11022 }catch(e){ /*squelch*/ }
11023 }
11024 }
11025 }
11026 },
11027
11028 show: function(){
11029 // summary:
11030 // Display the dialog
81bea17a
AD
11031 // returns: dojo.Deferred
11032 // Deferred object that resolves when the display animation is complete
11033
a089699c
AD
11034 if(this.open){ return; }
11035
81bea17a
AD
11036 if(!this._started){
11037 this.startup();
11038 }
11039
a089699c
AD
11040 // first time we show the dialog, there's some initialization stuff to do
11041 if(!this._alreadyInitialized){
11042 this._setup();
11043 this._alreadyInitialized=true;
11044 }
11045
81bea17a
AD
11046 if(this._fadeOutDeferred){
11047 this._fadeOutDeferred.cancel();
a089699c
AD
11048 }
11049
11050 this._modalconnects.push(dojo.connect(window, "onscroll", this, "layout"));
11051 this._modalconnects.push(dojo.connect(window, "onresize", this, function(){
11052 // IE gives spurious resize events and can actually get stuck
11053 // in an infinite loop if we don't ignore them
11054 var viewport = dojo.window.getBox();
11055 if(!this._oldViewport ||
11056 viewport.h != this._oldViewport.h ||
11057 viewport.w != this._oldViewport.w){
11058 this.layout();
11059 this._oldViewport = viewport;
11060 }
11061 }));
81bea17a 11062 this._modalconnects.push(dojo.connect(this.domNode, "onkeypress", this, "_onKey"));
a089699c
AD
11063
11064 dojo.style(this.domNode, {
11065 opacity:0,
11066 display:""
11067 });
11068
81bea17a 11069 this._set("open", true);
a089699c
AD
11070 this._onShow(); // lazy load trigger
11071
11072 this._size();
11073 this._position();
a089699c 11074
81bea17a
AD
11075 // fade-in Animation object, setup below
11076 var fadeIn;
11077
11078 this._fadeInDeferred = new dojo.Deferred(dojo.hitch(this, function(){
11079 fadeIn.stop();
11080 delete this._fadeInDeferred;
11081 }));
11082
11083 fadeIn = dojo.fadeIn({
11084 node: this.domNode,
11085 duration: this.duration,
11086 beforeBegin: dojo.hitch(this, function(){
11087 dijit._DialogLevelManager.show(this, this.underlayAttrs);
11088 }),
11089 onEnd: dojo.hitch(this, function(){
11090 if(this.autofocus && dijit._DialogLevelManager.isTop(this)){
11091 // find focusable items each time dialog is shown since if dialog contains a widget the
11092 // first focusable items can change
11093 this._getFocusItems(this.domNode);
11094 dijit.focus(this._firstFocusItem);
11095 }
11096 this._fadeInDeferred.callback(true);
11097 delete this._fadeInDeferred;
11098 })
11099 }).play();
11100
11101 return this._fadeInDeferred;
a089699c
AD
11102 },
11103
11104 hide: function(){
11105 // summary:
11106 // Hide the dialog
81bea17a
AD
11107 // returns: dojo.Deferred
11108 // Deferred object that resolves when the hide animation is complete
a089699c
AD
11109
11110 // if we haven't been initialized yet then we aren't showing and we can just return
81bea17a 11111 if(!this._alreadyInitialized){
a089699c
AD
11112 return;
11113 }
81bea17a
AD
11114 if(this._fadeInDeferred){
11115 this._fadeInDeferred.cancel();
a089699c
AD
11116 }
11117
81bea17a
AD
11118 // fade-in Animation object, setup below
11119 var fadeOut;
11120
11121 this._fadeOutDeferred = new dojo.Deferred(dojo.hitch(this, function(){
11122 fadeOut.stop();
11123 delete this._fadeOutDeferred;
11124 }));
a089699c 11125
81bea17a
AD
11126 fadeOut = dojo.fadeOut({
11127 node: this.domNode,
11128 duration: this.duration,
11129 onEnd: dojo.hitch(this, function(){
11130 this.domNode.style.display = "none";
11131 dijit._DialogLevelManager.hide(this);
11132 this.onHide();
11133 this._fadeOutDeferred.callback(true);
11134 delete this._fadeOutDeferred;
11135 })
11136 }).play();
a089699c
AD
11137
11138 if(this._scrollConnected){
11139 this._scrollConnected = false;
11140 }
11141 dojo.forEach(this._modalconnects, dojo.disconnect);
11142 this._modalconnects = [];
11143
11144 if(this._relativePosition){
11145 delete this._relativePosition;
11146 }
81bea17a 11147 this._set("open", false);
a089699c 11148
81bea17a 11149 return this._fadeOutDeferred;
a089699c
AD
11150 },
11151
11152 layout: function(){
11153 // summary:
11154 // Position the Dialog and the underlay
11155 // tags:
11156 // private
11157 if(this.domNode.style.display != "none"){
11158 if(dijit._underlay){ // avoid race condition during show()
11159 dijit._underlay.layout();
11160 }
81bea17a
AD
11161 this._position();
11162 }
11163 },
11164
11165 destroy: function(){
11166 if(this._fadeInDeferred){
11167 this._fadeInDeferred.cancel();
11168 }
11169 if(this._fadeOutDeferred){
11170 this._fadeOutDeferred.cancel();
11171 }
11172 if(this._moveable){
11173 this._moveable.destroy();
11174 }
11175 if(this._dndListener){
11176 dojo.unsubscribe(this._dndListener);
11177 }
11178 dojo.forEach(this._modalconnects, dojo.disconnect);
11179
11180 dijit._DialogLevelManager.hide(this);
11181
11182 this.inherited(arguments);
11183 }
11184 }
11185);
11186
11187dojo.declare(
11188 "dijit.Dialog",
11189 [dijit.layout.ContentPane, dijit._DialogBase],
11190 {}
11191);
11192
11193dijit._DialogLevelManager = {
11194 // summary:
11195 // Controls the various active "levels" on the page, starting with the
11196 // stuff initially visible on the page (at z-index 0), and then having an entry for
11197 // each Dialog shown.
11198
11199 show: function(/*dijit._Widget*/ dialog, /*Object*/ underlayAttrs){
11200 // summary:
11201 // Call right before fade-in animation for new dialog.
11202 // Saves current focus, displays/adjusts underlay for new dialog,
11203 // and sets the z-index of the dialog itself.
11204 //
11205 // New dialog will be displayed on top of all currently displayed dialogs.
11206 //
11207 // Caller is responsible for setting focus in new dialog after the fade-in
11208 // animation completes.
11209
11210 var ds = dijit._dialogStack;
11211
11212 // Save current focus
11213 ds[ds.length-1].focus = dijit.getFocus(dialog);
11214
11215 // Display the underlay, or if already displayed then adjust for this new dialog
11216 var underlay = dijit._underlay;
11217 if(!underlay || underlay._destroyed){
11218 underlay = dijit._underlay = new dijit.DialogUnderlay(underlayAttrs);
11219 }else{
11220 underlay.set(dialog.underlayAttrs);
11221 }
11222
11223 // Set z-index a bit above previous dialog
11224 var zIndex = ds[ds.length-1].dialog ? ds[ds.length-1].zIndex + 2 : 950;
11225 if(ds.length == 1){ // first dialog
11226 underlay.show();
11227 }
11228 dojo.style(dijit._underlay.domNode, 'zIndex', zIndex - 1);
11229
11230 // Dialog
11231 dojo.style(dialog.domNode, 'zIndex', zIndex);
11232
11233 ds.push({dialog: dialog, underlayAttrs: underlayAttrs, zIndex: zIndex});
11234 },
11235
11236 hide: function(/*dijit._Widget*/ dialog){
11237 // summary:
11238 // Called when the specified dialog is hidden/destroyed, after the fade-out
11239 // animation ends, in order to reset page focus, fix the underlay, etc.
11240 // If the specified dialog isn't open then does nothing.
11241 //
11242 // Caller is responsible for either setting display:none on the dialog domNode,
11243 // or calling dijit.popup.hide(), or removing it from the page DOM.
11244
11245 var ds = dijit._dialogStack;
11246
11247 if(ds[ds.length-1].dialog == dialog){
11248 // Removing the top (or only) dialog in the stack, return focus
11249 // to previous dialog
11250
11251 ds.pop();
11252
11253 var pd = ds[ds.length-1]; // the new active dialog (or the base page itself)
11254
11255 // Adjust underlay
11256 if(ds.length == 1){
11257 // Returning to original page.
11258 // Hide the underlay, unless the underlay widget has already been destroyed
11259 // because we are being called during page unload (when all widgets are destroyed)
11260 if(!dijit._underlay._destroyed){
11261 dijit._underlay.hide();
11262 }
11263 }else{
11264 // Popping back to previous dialog, adjust underlay
11265 dojo.style(dijit._underlay.domNode, 'zIndex', pd.zIndex - 1);
11266 dijit._underlay.set(pd.underlayAttrs);
11267 }
11268
11269 // Adjust focus
11270 if(dialog.refocus){
11271 // If we are returning control to a previous dialog but for some reason
11272 // that dialog didn't have a focused field, set focus to first focusable item.
11273 // This situation could happen if two dialogs appeared at nearly the same time,
11274 // since a dialog doesn't set it's focus until the fade-in is finished.
11275 var focus = pd.focus;
11276 if(!focus || (pd.dialog && !dojo.isDescendant(focus.node, pd.dialog.domNode))){
11277 pd.dialog._getFocusItems(pd.dialog.domNode);
11278 focus = pd.dialog._firstFocusItem;
11279 }
11280
11281 try{
11282 dijit.focus(focus);
11283 }catch(e){
11284 /* focus() will fail if user opened the dialog by clicking a non-focusable element */
11285 }
a089699c 11286 }
81bea17a
AD
11287 }else{
11288 // Removing a dialog out of order (#9944, #10705).
11289 // Don't need to mess with underlay or z-index or anything.
11290 var idx = dojo.indexOf(dojo.map(ds, function(elem){return elem.dialog}), dialog);
11291 if(idx != -1){
11292 ds.splice(idx, 1);
a089699c 11293 }
a089699c 11294 }
81bea17a 11295 },
a089699c 11296
81bea17a
AD
11297 isTop: function(/*dijit._Widget*/ dialog){
11298 // summary:
11299 // Returns true if specified Dialog is the top in the task
11300 var ds = dijit._dialogStack;
11301 return ds[ds.length-1].dialog == dialog;
11302 }
11303};
a089699c 11304
81bea17a
AD
11305// Stack representing the various active "levels" on the page, starting with the
11306// stuff initially visible on the page (at z-index 0), and then having an entry for
11307// each Dialog shown.
11308// Each element in stack has form {
11309// dialog: dialogWidget,
11310// focus: returnFromGetFocus(),
11311// underlayAttrs: attributes to set on underlay (when this widget is active)
11312// }
11313dijit._dialogStack = [
11314 {dialog: null, focus: null, underlayAttrs: null} // entry for stuff at z-index: 0
11315];
a089699c
AD
11316
11317}
11318
11319if(!dojo._hasResource["dijit._HasDropDown"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11320dojo._hasResource["dijit._HasDropDown"] = true;
11321dojo.provide("dijit._HasDropDown");
11322
11323
11324
a089699c
AD
11325dojo.declare("dijit._HasDropDown",
11326 null,
11327 {
11328 // summary:
11329 // Mixin for widgets that need drop down ability.
11330
11331 // _buttonNode: [protected] DomNode
11332 // The button/icon/node to click to display the drop down.
11333 // Can be set via a dojoAttachPoint assignment.
11334 // If missing, then either focusNode or domNode (if focusNode is also missing) will be used.
11335 _buttonNode: null,
11336
11337 // _arrowWrapperNode: [protected] DomNode
11338 // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending
11339 // on where the drop down is set to be positioned.
11340 // Can be set via a dojoAttachPoint assignment.
11341 // If missing, then _buttonNode will be used.
11342 _arrowWrapperNode: null,
11343
11344 // _popupStateNode: [protected] DomNode
11345 // The node to set the popupActive class on.
11346 // Can be set via a dojoAttachPoint assignment.
11347 // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used.
11348 _popupStateNode: null,
11349
11350 // _aroundNode: [protected] DomNode
11351 // The node to display the popup around.
11352 // Can be set via a dojoAttachPoint assignment.
11353 // If missing, then domNode will be used.
11354 _aroundNode: null,
11355
11356 // dropDown: [protected] Widget
11357 // The widget to display as a popup. This widget *must* be
11358 // defined before the startup function is called.
11359 dropDown: null,
11360
11361 // autoWidth: [protected] Boolean
11362 // Set to true to make the drop down at least as wide as this
11363 // widget. Set to false if the drop down should just be its
11364 // default width
11365 autoWidth: true,
11366
11367 // forceWidth: [protected] Boolean
11368 // Set to true to make the drop down exactly as wide as this
11369 // widget. Overrides autoWidth.
11370 forceWidth: false,
11371
11372 // maxHeight: [protected] Integer
81bea17a
AD
11373 // The max height for our dropdown.
11374 // Any dropdown taller than this will have scrollbars.
11375 // Set to 0 for no max height, or -1 to limit height to available space in viewport
a089699c
AD
11376 maxHeight: 0,
11377
11378 // dropDownPosition: [const] String[]
11379 // This variable controls the position of the drop down.
11380 // It's an array of strings with the following values:
11381 //
11382 // * before: places drop down to the left of the target node/widget, or to the right in
11383 // the case of RTL scripts like Hebrew and Arabic
11384 // * after: places drop down to the right of the target node/widget, or to the left in
11385 // the case of RTL scripts like Hebrew and Arabic
11386 // * above: drop down goes above target node
11387 // * below: drop down goes below target node
11388 //
11389 // The list is positions is tried, in order, until a position is found where the drop down fits
11390 // within the viewport.
11391 //
11392 dropDownPosition: ["below","above"],
11393
11394 // _stopClickEvents: Boolean
11395 // When set to false, the click events will not be stopped, in
11396 // case you want to use them in your subwidget
11397 _stopClickEvents: true,
11398
11399 _onDropDownMouseDown: function(/*Event*/ e){
11400 // summary:
11401 // Callback when the user mousedown's on the arrow icon
11402
11403 if(this.disabled || this.readOnly){ return; }
11404
81bea17a
AD
11405 dojo.stopEvent(e);
11406
a089699c
AD
11407 this._docHandler = this.connect(dojo.doc, "onmouseup", "_onDropDownMouseUp");
11408
11409 this.toggleDropDown();
11410 },
11411
11412 _onDropDownMouseUp: function(/*Event?*/ e){
11413 // summary:
11414 // Callback when the user lifts their mouse after mouse down on the arrow icon.
11415 // If the drop is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our
11416 // dropDown node. If the event is missing, then we are not
11417 // a mouseup event.
11418 //
11419 // This is useful for the common mouse movement pattern
11420 // with native browser <select> nodes:
11421 // 1. mouse down on the select node (probably on the arrow)
11422 // 2. move mouse to a menu item while holding down the mouse button
11423 // 3. mouse up. this selects the menu item as though the user had clicked it.
11424 if(e && this._docHandler){
11425 this.disconnect(this._docHandler);
11426 }
11427 var dropDown = this.dropDown, overMenu = false;
11428
11429 if(e && this._opened){
11430 // This code deals with the corner-case when the drop down covers the original widget,
11431 // because it's so large. In that case mouse-up shouldn't select a value from the menu.
11432 // Find out if our target is somewhere in our dropdown widget,
11433 // but not over our _buttonNode (the clickable node)
11434 var c = dojo.position(this._buttonNode, true);
11435 if(!(e.pageX >= c.x && e.pageX <= c.x + c.w) ||
11436 !(e.pageY >= c.y && e.pageY <= c.y + c.h)){
11437 var t = e.target;
11438 while(t && !overMenu){
11439 if(dojo.hasClass(t, "dijitPopup")){
11440 overMenu = true;
11441 }else{
11442 t = t.parentNode;
11443 }
11444 }
11445 if(overMenu){
11446 t = e.target;
11447 if(dropDown.onItemClick){
11448 var menuItem;
11449 while(t && !(menuItem = dijit.byNode(t))){
11450 t = t.parentNode;
11451 }
11452 if(menuItem && menuItem.onClick && menuItem.getParent){
11453 menuItem.getParent().onItemClick(menuItem, e);
11454 }
11455 }
11456 return;
11457 }
11458 }
11459 }
81bea17a 11460 if(this._opened && dropDown.focus && dropDown.autoFocus !== false){
a089699c
AD
11461 // Focus the dropdown widget - do it on a delay so that we
11462 // don't steal our own focus.
11463 window.setTimeout(dojo.hitch(dropDown, "focus"), 1);
11464 }
11465 },
11466
11467 _onDropDownClick: function(/*Event*/ e){
11468 // the drop down was already opened on mousedown/keydown; just need to call stopEvent()
11469 if(this._stopClickEvents){
11470 dojo.stopEvent(e);
81bea17a 11471 }
a089699c
AD
11472 },
11473
81bea17a
AD
11474 buildRendering: function(){
11475 this.inherited(arguments);
11476
a089699c
AD
11477 this._buttonNode = this._buttonNode || this.focusNode || this.domNode;
11478 this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode;
a089699c
AD
11479
11480 // Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow
11481 // based on where drop down will normally appear
11482 var defaultPos = {
11483 "after" : this.isLeftToRight() ? "Right" : "Left",
11484 "before" : this.isLeftToRight() ? "Left" : "Right",
11485 "above" : "Up",
11486 "below" : "Down",
11487 "left" : "Left",
11488 "right" : "Right"
11489 }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down";
11490 dojo.addClass(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton");
11491 },
11492
11493 postCreate: function(){
81bea17a
AD
11494 // summary:
11495 // set up nodes and connect our mouse and keypress events
11496
a089699c 11497 this.inherited(arguments);
81bea17a
AD
11498
11499 this.connect(this._buttonNode, "onmousedown", "_onDropDownMouseDown");
11500 this.connect(this._buttonNode, "onclick", "_onDropDownClick");
11501 this.connect(this.focusNode, "onkeypress", "_onKey");
11502 this.connect(this.focusNode, "onkeyup", "_onKeyUp");
a089699c
AD
11503 },
11504
81bea17a 11505 destroy: function(){
a089699c
AD
11506 if(this.dropDown){
11507 // Destroy the drop down, unless it's already been destroyed. This can happen because
11508 // the drop down is a direct child of <body> even though it's logically my child.
11509 if(!this.dropDown._destroyed){
11510 this.dropDown.destroyRecursive();
11511 }
11512 delete this.dropDown;
11513 }
11514 this.inherited(arguments);
11515 },
11516
a089699c
AD
11517 _onKey: function(/*Event*/ e){
11518 // summary:
11519 // Callback when the user presses a key while focused on the button node
11520
11521 if(this.disabled || this.readOnly){ return; }
81bea17a
AD
11522
11523 var d = this.dropDown, target = e.target;
a089699c 11524 if(d && this._opened && d.handleKey){
81bea17a
AD
11525 if(d.handleKey(e) === false){
11526 /* false return code means that the drop down handled the key */
11527 dojo.stopEvent(e);
11528 return;
11529 }
a089699c 11530 }
81bea17a
AD
11531 if(d && this._opened && e.charOrCode == dojo.keys.ESCAPE){
11532 this.closeDropDown();
11533 dojo.stopEvent(e);
11534 }else if(!this._opened &&
11535 (e.charOrCode == dojo.keys.DOWN_ARROW ||
11536 ( (e.charOrCode == dojo.keys.ENTER || e.charOrCode == " ") &&
11537 //ignore enter and space if the event is for a text input
11538 ((target.tagName || "").toLowerCase() !== 'input' ||
11539 (target.type && target.type.toLowerCase() !== 'text'))))){
11540 // Toggle the drop down, but wait until keyup so that the drop down doesn't
11541 // get a stray keyup event, or in the case of key-repeat (because user held
11542 // down key for too long), stray keydown events
11543 this._toggleOnKeyUp = true;
11544 dojo.stopEvent(e);
11545 }
11546 },
11547
11548 _onKeyUp: function(){
11549 if(this._toggleOnKeyUp){
11550 delete this._toggleOnKeyUp;
a089699c 11551 this.toggleDropDown();
81bea17a
AD
11552 var d = this.dropDown; // drop down may not exist until toggleDropDown() call
11553 if(d && d.focus){
a089699c
AD
11554 setTimeout(dojo.hitch(d, "focus"), 1);
11555 }
11556 }
11557 },
11558
11559 _onBlur: function(){
11560 // summary:
11561 // Called magically when focus has shifted away from this widget and it's dropdown
11562
81bea17a
AD
11563 // Don't focus on button if the user has explicitly focused on something else (happens
11564 // when user clicks another control causing the current popup to close)..
11565 // But if focus is inside of the drop down then reset focus to me, because IE doesn't like
11566 // it when you display:none a node with focus.
11567 var focusMe = dijit._curFocus && this.dropDown && dojo.isDescendant(dijit._curFocus, this.dropDown.domNode);
11568
11569 this.closeDropDown(focusMe);
11570
a089699c
AD
11571 this.inherited(arguments);
11572 },
11573
11574 isLoaded: function(){
11575 // summary:
11576 // Returns whether or not the dropdown is loaded. This can
11577 // be overridden in order to force a call to loadDropDown().
11578 // tags:
11579 // protected
11580
11581 return true;
11582 },
11583
11584 loadDropDown: function(/* Function */ loadCallback){
11585 // summary:
11586 // Loads the data for the dropdown, and at some point, calls
81bea17a
AD
11587 // the given callback. This is basically a callback when the
11588 // user presses the down arrow button to open the drop down.
a089699c
AD
11589 // tags:
11590 // protected
11591
11592 loadCallback();
11593 },
11594
11595 toggleDropDown: function(){
11596 // summary:
81bea17a
AD
11597 // Callback when the user presses the down arrow button or presses
11598 // the down arrow key to open/close the drop down.
a089699c
AD
11599 // Toggle the drop-down widget; if it is up, close it, if not, open it
11600 // tags:
11601 // protected
11602
11603 if(this.disabled || this.readOnly){ return; }
a089699c
AD
11604 if(!this._opened){
11605 // If we aren't loaded, load it first so there isn't a flicker
11606 if(!this.isLoaded()){
11607 this.loadDropDown(dojo.hitch(this, "openDropDown"));
11608 return;
11609 }else{
11610 this.openDropDown();
11611 }
11612 }else{
11613 this.closeDropDown();
11614 }
11615 },
11616
11617 openDropDown: function(){
11618 // summary:
81bea17a
AD
11619 // Opens the dropdown for this widget. To be called only when this.dropDown
11620 // has been created and is ready to display (ie, it's data is loaded).
11621 // returns:
11622 // return value of dijit.popup.open()
a089699c
AD
11623 // tags:
11624 // protected
11625
81bea17a
AD
11626 var dropDown = this.dropDown,
11627 ddNode = dropDown.domNode,
11628 aroundNode = this._aroundNode || this.domNode,
11629 self = this;
a089699c
AD
11630
11631 // Prepare our popup's height and honor maxHeight if it exists.
11632
11633 // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(),
11634 // ie, dependent on how much space is available (BK)
11635
11636 if(!this._preparedNode){
81bea17a 11637 this._preparedNode = true;
a089699c
AD
11638 // Check if we have explicitly set width and height on the dropdown widget dom node
11639 if(ddNode.style.width){
11640 this._explicitDDWidth = true;
11641 }
11642 if(ddNode.style.height){
11643 this._explicitDDHeight = true;
11644 }
11645 }
11646
11647 // Code for resizing dropdown (height limitation, or increasing width to match my width)
11648 if(this.maxHeight || this.forceWidth || this.autoWidth){
11649 var myStyle = {
11650 display: "",
11651 visibility: "hidden"
11652 };
11653 if(!this._explicitDDWidth){
11654 myStyle.width = "";
11655 }
11656 if(!this._explicitDDHeight){
11657 myStyle.height = "";
11658 }
11659 dojo.style(ddNode, myStyle);
11660
81bea17a
AD
11661 // Figure out maximum height allowed (if there is a height restriction)
11662 var maxHeight = this.maxHeight;
11663 if(maxHeight == -1){
11664 // limit height to space available in viewport either above or below my domNode
11665 // (whichever side has more room)
11666 var viewport = dojo.window.getBox(),
11667 position = dojo.position(aroundNode, false);
11668 maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h)));
11669 }
11670
11671 // Attach dropDown to DOM and make make visibility:hidden rather than display:none
11672 // so we call startup() and also get the size
11673 if(dropDown.startup && !dropDown._started){
11674 dropDown.startup();
11675 }
11676
11677 dijit.popup.moveOffScreen(dropDown);
a089699c 11678 // Get size of drop down, and determine if vertical scroll bar needed
81bea17a
AD
11679 var mb = dojo._getMarginSize(ddNode);
11680 var overHeight = (maxHeight && mb.h > maxHeight);
a089699c
AD
11681 dojo.style(ddNode, {
11682 overflowX: "hidden",
11683 overflowY: overHeight ? "auto" : "hidden"
11684 });
11685 if(overHeight){
81bea17a 11686 mb.h = maxHeight;
a089699c
AD
11687 if("w" in mb){
11688 mb.w += 16; // room for vertical scrollbar
11689 }
11690 }else{
11691 delete mb.h;
11692 }
a089699c
AD
11693
11694 // Adjust dropdown width to match or be larger than my width
11695 if(this.forceWidth){
81bea17a 11696 mb.w = aroundNode.offsetWidth;
a089699c 11697 }else if(this.autoWidth){
81bea17a 11698 mb.w = Math.max(mb.w, aroundNode.offsetWidth);
a089699c
AD
11699 }else{
11700 delete mb.w;
11701 }
11702
11703 // And finally, resize the dropdown to calculated height and width
11704 if(dojo.isFunction(dropDown.resize)){
11705 dropDown.resize(mb);
11706 }else{
11707 dojo.marginBox(ddNode, mb);
11708 }
11709 }
11710
11711 var retVal = dijit.popup.open({
11712 parent: this,
11713 popup: dropDown,
81bea17a 11714 around: aroundNode,
a089699c
AD
11715 orient: dijit.getPopupAroundAlignment((this.dropDownPosition && this.dropDownPosition.length) ? this.dropDownPosition : ["below"],this.isLeftToRight()),
11716 onExecute: function(){
11717 self.closeDropDown(true);
11718 },
11719 onCancel: function(){
11720 self.closeDropDown(true);
11721 },
11722 onClose: function(){
11723 dojo.attr(self._popupStateNode, "popupActive", false);
11724 dojo.removeClass(self._popupStateNode, "dijitHasDropDownOpen");
11725 self._opened = false;
a089699c
AD
11726 }
11727 });
11728 dojo.attr(this._popupStateNode, "popupActive", "true");
11729 dojo.addClass(self._popupStateNode, "dijitHasDropDownOpen");
11730 this._opened=true;
81bea17a 11731
a089699c
AD
11732 // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown
11733 return retVal;
11734 },
11735
11736 closeDropDown: function(/*Boolean*/ focus){
11737 // summary:
11738 // Closes the drop down on this widget
81bea17a
AD
11739 // focus:
11740 // If true, refocuses the button widget
a089699c
AD
11741 // tags:
11742 // protected
11743
11744 if(this._opened){
11745 if(focus){ this.focus(); }
11746 dijit.popup.close(this.dropDown);
11747 this._opened = false;
a089699c
AD
11748 }
11749 }
11750
11751 }
11752);
11753
11754}
11755
11756if(!dojo._hasResource["dijit.form.Button"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
11757dojo._hasResource["dijit.form.Button"] = true;
11758dojo.provide("dijit.form.Button");
11759
11760
11761
11762
11763
11764dojo.declare("dijit.form.Button",
11765 dijit.form._FormWidget,
11766 {
11767 // summary:
11768 // Basically the same thing as a normal HTML button, but with special styling.
11769 // description:
11770 // Buttons can display a label, an icon, or both.
11771 // A label should always be specified (through innerHTML) or the label
11772 // attribute. It can be hidden via showLabel=false.
11773 // example:
11774 // | <button dojoType="dijit.form.Button" onClick="...">Hello world</button>
11775 //
11776 // example:
11777 // | var button1 = new dijit.form.Button({label: "hello world", onClick: foo});
11778 // | dojo.body().appendChild(button1.domNode);
11779
11780 // label: HTML String
11781 // Text to display in button.
11782 // If the label is hidden (showLabel=false) then and no title has
11783 // been specified, then label is also set as title attribute of icon.
11784 label: "",
11785
11786 // showLabel: Boolean
11787 // Set this to true to hide the label text and display only the icon.
11788 // (If showLabel=false then iconClass must be specified.)
11789 // Especially useful for toolbars.
11790 // If showLabel=true, the label will become the title (a.k.a. tooltip/hint) of the icon.
11791 //
11792 // The exception case is for computers in high-contrast mode, where the label
11793 // will still be displayed, since the icon doesn't appear.
11794 showLabel: true,
11795
11796 // iconClass: String
11797 // Class to apply to DOMNode in button to make it display an icon
11798 iconClass: "",
11799
11800 // type: String
11801 // Defines the type of button. "button", "submit", or "reset".
11802 type: "button",
11803
11804 baseClass: "dijitButton",
11805
81bea17a 11806 templateString: dojo.cache("dijit.form", "templates/Button.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode,focusNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">&#x25CF;</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdojoAttachPoint=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),
a089699c
AD
11807
11808 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
81bea17a 11809 value: "valueNode"
a089699c
AD
11810 }),
11811
a089699c
AD
11812 _onClick: function(/*Event*/ e){
11813 // summary:
11814 // Internal function to handle click actions
11815 if(this.disabled){
11816 return false;
11817 }
11818 this._clicked(); // widget click actions
11819 return this.onClick(e); // user click actions
11820 },
11821
11822 _onButtonClick: function(/*Event*/ e){
11823 // summary:
11824 // Handler when the user activates the button portion.
11825 if(this._onClick(e) === false){ // returning nothing is same as true
11826 e.preventDefault(); // needed for checkbox
11827 }else if(this.type == "submit" && !(this.valueNode||this.focusNode).form){ // see if a nonform widget needs to be signalled
11828 for(var node=this.domNode; node.parentNode/*#5935*/; node=node.parentNode){
11829 var widget=dijit.byNode(node);
11830 if(widget && typeof widget._onSubmit == "function"){
11831 widget._onSubmit(e);
11832 break;
11833 }
11834 }
11835 }else if(this.valueNode){
11836 this.valueNode.click();
11837 e.preventDefault(); // cancel BUTTON click and continue with hidden INPUT click
11838 }
11839 },
11840
81bea17a
AD
11841 buildRendering: function(){
11842 this.inherited(arguments);
11843 dojo.setSelectable(this.focusNode, false);
11844 },
11845
a089699c
AD
11846 _fillContent: function(/*DomNode*/ source){
11847 // Overrides _Templated._fillContent().
11848 // If button label is specified as srcNodeRef.innerHTML rather than
11849 // this.params.label, handle it here.
81bea17a 11850 // TODO: remove the method in 2.0, parser will do it all for me
a089699c
AD
11851 if(source && (!this.params || !("label" in this.params))){
11852 this.set('label', source.innerHTML);
11853 }
11854 },
11855
a089699c
AD
11856 _setShowLabelAttr: function(val){
11857 if(this.containerNode){
11858 dojo.toggleClass(this.containerNode, "dijitDisplayNone", !val);
11859 }
81bea17a 11860 this._set("showLabel", val);
a089699c
AD
11861 },
11862
11863 onClick: function(/*Event*/ e){
11864 // summary:
11865 // Callback for when button is clicked.
11866 // If type="submit", return true to perform submit, or false to cancel it.
11867 // type:
11868 // callback
11869 return true; // Boolean
11870 },
11871
11872 _clicked: function(/*Event*/ e){
11873 // summary:
11874 // Internal overridable function for when the button is clicked
11875 },
11876
11877 setLabel: function(/*String*/ content){
11878 // summary:
11879 // Deprecated. Use set('label', ...) instead.
11880 dojo.deprecated("dijit.form.Button.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0");
11881 this.set("label", content);
11882 },
11883
11884 _setLabelAttr: function(/*String*/ content){
11885 // summary:
81bea17a 11886 // Hook for set('label', ...) to work.
a089699c
AD
11887 // description:
11888 // Set the label (text) of the button; takes an HTML string.
81bea17a
AD
11889 this._set("label", content);
11890 this.containerNode.innerHTML = content;
a089699c
AD
11891 if(this.showLabel == false && !this.params.title){
11892 this.titleNode.title = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || '');
11893 }
81bea17a
AD
11894 },
11895
11896 _setIconClassAttr: function(/*String*/ val){
11897 // Custom method so that icon node is hidden when not in use, to avoid excess padding/margin
11898 // appearing around it (even if it's a 0x0 sized <img> node)
11899
11900 var oldVal = this.iconClass || "dijitNoIcon",
11901 newVal = val || "dijitNoIcon";
11902 dojo.replaceClass(this.iconNode, newVal, oldVal);
11903 this._set("iconClass", val);
a089699c
AD
11904 }
11905});
11906
11907
11908dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container, dijit._HasDropDown], {
11909 // summary:
11910 // A button with a drop down
11911 //
11912 // example:
11913 // | <button dojoType="dijit.form.DropDownButton" label="Hello world">
11914 // | <div dojotype="dijit.Menu">...</div>
11915 // | </button>
11916 //
11917 // example:
11918 // | var button1 = new dijit.form.DropDownButton({ label: "hi", dropDown: new dijit.Menu(...) });
11919 // | dojo.body().appendChild(button1);
11920 //
11921
11922 baseClass : "dijitDropDownButton",
11923
81bea17a 11924 templateString: dojo.cache("dijit.form", "templates/DropDownButton.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\" dojoAttachPoint=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"focusNode,titleNode,_arrowWrapperNode\"\n\t\t\trole=\"button\" aria-haspopup=\"true\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\"\n\t\t\t\tdojoAttachPoint=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdojoAttachPoint=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">&#9660;</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),
a089699c
AD
11925
11926 _fillContent: function(){
11927 // Overrides Button._fillContent().
11928 //
11929 // My inner HTML contains both the button contents and a drop down widget, like
11930 // <DropDownButton> <span>push me</span> <Menu> ... </Menu> </DropDownButton>
11931 // The first node is assumed to be the button content. The widget is the popup.
11932
11933 if(this.srcNodeRef){ // programatically created buttons might not define srcNodeRef
11934 //FIXME: figure out how to filter out the widget and use all remaining nodes as button
11935 // content, not just nodes[0]
11936 var nodes = dojo.query("*", this.srcNodeRef);
11937 dijit.form.DropDownButton.superclass._fillContent.call(this, nodes[0]);
11938
11939 // save pointer to srcNode so we can grab the drop down widget after it's instantiated
11940 this.dropDownContainer = this.srcNodeRef;
11941 }
11942 },
11943
11944 startup: function(){
11945 if(this._started){ return; }
11946
11947 // the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM,
11948 // make it invisible, and store a reference to pass to the popup code.
81bea17a 11949 if(!this.dropDown && this.dropDownContainer){
a089699c
AD
11950 var dropDownNode = dojo.query("[widgetId]", this.dropDownContainer)[0];
11951 this.dropDown = dijit.byNode(dropDownNode);
11952 delete this.dropDownContainer;
11953 }
81bea17a
AD
11954 if(this.dropDown){
11955 dijit.popup.hide(this.dropDown);
11956 }
a089699c
AD
11957
11958 this.inherited(arguments);
11959 },
11960
11961 isLoaded: function(){
11962 // Returns whether or not we are loaded - if our dropdown has an href,
11963 // then we want to check that.
11964 var dropDown = this.dropDown;
81bea17a 11965 return (!!dropDown && (!dropDown.href || dropDown.isLoaded));
a089699c
AD
11966 },
11967
11968 loadDropDown: function(){
11969 // Loads our dropdown
11970 var dropDown = this.dropDown;
11971 if(!dropDown){ return; }
11972 if(!this.isLoaded()){
11973 var handler = dojo.connect(dropDown, "onLoad", this, function(){
11974 dojo.disconnect(handler);
11975 this.openDropDown();
11976 });
11977 dropDown.refresh();
11978 }else{
11979 this.openDropDown();
11980 }
11981 },
11982
11983 isFocusable: function(){
11984 // Overridden so that focus is handled by the _HasDropDown mixin, not by
11985 // the _FormWidget mixin.
11986 return this.inherited(arguments) && !this._mouseDown;
11987 }
11988});
11989
11990dojo.declare("dijit.form.ComboButton", dijit.form.DropDownButton, {
11991 // summary:
11992 // A combination button and drop-down button.
11993 // Users can click one side to "press" the button, or click an arrow
11994 // icon to display the drop down.
11995 //
11996 // example:
11997 // | <button dojoType="dijit.form.ComboButton" onClick="...">
11998 // | <span>Hello world</span>
11999 // | <div dojoType="dijit.Menu">...</div>
12000 // | </button>
12001 //
12002 // example:
12003 // | var button1 = new dijit.form.ComboButton({label: "hello world", onClick: foo, dropDown: "myMenu"});
12004 // | dojo.body().appendChild(button1.domNode);
12005 //
12006
81bea17a 12007 templateString: dojo.cache("dijit.form", "templates/ComboButton.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tcellspacing='0' cellpadding='0' role=\"presentation\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonNode\" dojoAttachPoint=\"buttonNode\" dojoAttachEvent=\"ondijitclick:_onButtonClick,onkeypress:_onButtonKeyPress\"\n\t\t><div id=\"${id}_button\" class=\"dijitReset dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><div class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitInline dijitButtonText\" id=\"${id}_label\" dojoAttachPoint=\"containerNode\" role=\"presentation\"></div\n\t\t></div\n\t\t></td\n\t\t><td id=\"${id}_arrow\" class='dijitReset dijitRight dijitButtonNode dijitArrowButton'\n\t\t\tdojoAttachPoint=\"_popupStateNode,focusNode,_buttonNode\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onArrowKeyPress\"\n\t\t\ttitle=\"${optionsTitle}\"\n\t\t\trole=\"button\" aria-haspopup=\"true\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">&#9660;</div\n\t\t></td\n\t\t><td style=\"display:none !important;\"\n\t\t\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" dojoAttachPoint=\"valueNode\"\n\t\t/></td></tr></tbody\n></table>\n"),
a089699c
AD
12008
12009 attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), {
12010 id: "",
12011 tabIndex: ["focusNode", "titleNode"],
12012 title: "titleNode"
12013 }),
12014
12015 // optionsTitle: String
12016 // Text that describes the options menu (accessibility)
12017 optionsTitle: "",
12018
12019 baseClass: "dijitComboButton",
12020
12021 // Set classes like dijitButtonContentsHover or dijitArrowButtonActive depending on
12022 // mouse action over specified node
12023 cssStateNodes: {
12024 "buttonNode": "dijitButtonNode",
12025 "titleNode": "dijitButtonContents",
12026 "_popupStateNode": "dijitDownArrowButton"
12027 },
12028
12029 _focusedNode: null,
12030
12031 _onButtonKeyPress: function(/*Event*/ evt){
12032 // summary:
12033 // Handler for right arrow key when focus is on left part of button
12034 if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "RIGHT_ARROW" : "LEFT_ARROW"]){
12035 dijit.focus(this._popupStateNode);
12036 dojo.stopEvent(evt);
12037 }
12038 },
12039
12040 _onArrowKeyPress: function(/*Event*/ evt){
12041 // summary:
12042 // Handler for left arrow key when focus is on right part of button
12043 if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "LEFT_ARROW" : "RIGHT_ARROW"]){
12044 dijit.focus(this.titleNode);
12045 dojo.stopEvent(evt);
12046 }
12047 },
12048
12049 focus: function(/*String*/ position){
12050 // summary:
12051 // Focuses this widget to according to position, if specified,
12052 // otherwise on arrow node
12053 // position:
12054 // "start" or "end"
81bea17a
AD
12055 if(!this.disabled){
12056 dijit.focus(position == "start" ? this.titleNode : this._popupStateNode);
12057 }
a089699c
AD
12058 }
12059});
12060
12061dojo.declare("dijit.form.ToggleButton", dijit.form.Button, {
12062 // summary:
12063 // A button that can be in two states (checked or not).
12064 // Can be base class for things like tabs or checkbox or radio buttons
12065
12066 baseClass: "dijitToggleButton",
12067
12068 // checked: Boolean
12069 // Corresponds to the native HTML <input> element's attribute.
12070 // In markup, specified as "checked='checked'" or just "checked".
12071 // True if the button is depressed, or the checkbox is checked,
12072 // or the radio button is selected, etc.
12073 checked: false,
12074
12075 attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), {
12076 checked:"focusNode"
12077 }),
12078
12079 _clicked: function(/*Event*/ evt){
12080 this.set('checked', !this.checked);
12081 },
12082
81bea17a
AD
12083 _setCheckedAttr: function(/*Boolean*/ value, /*Boolean?*/ priorityChange){
12084 this._set("checked", value);
a089699c
AD
12085 dojo.attr(this.focusNode || this.domNode, "checked", value);
12086 dijit.setWaiState(this.focusNode || this.domNode, "pressed", value);
12087 this._handleOnChange(value, priorityChange);
12088 },
12089
12090 setChecked: function(/*Boolean*/ checked){
12091 // summary:
81bea17a 12092 // Deprecated. Use set('checked', true/false) instead.
a089699c
AD
12093 dojo.deprecated("setChecked("+checked+") is deprecated. Use set('checked',"+checked+") instead.", "", "2.0");
12094 this.set('checked', checked);
12095 },
12096
12097 reset: function(){
12098 // summary:
12099 // Reset the widget's value to what it was at initialization time
12100
12101 this._hasBeenBlurred = false;
12102
12103 // set checked state to original setting
12104 this.set('checked', this.params.checked || false);
12105 }
12106});
12107
12108}
12109
12110if(!dojo._hasResource["dijit.form.ToggleButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12111dojo._hasResource["dijit.form.ToggleButton"] = true;
12112dojo.provide("dijit.form.ToggleButton");
12113
12114
81bea17a
AD
12115
12116
a089699c
AD
12117}
12118
12119if(!dojo._hasResource["dijit.form.CheckBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12120dojo._hasResource["dijit.form.CheckBox"] = true;
12121dojo.provide("dijit.form.CheckBox");
12122
12123
12124
12125dojo.declare(
12126 "dijit.form.CheckBox",
12127 dijit.form.ToggleButton,
12128 {
12129 // summary:
12130 // Same as an HTML checkbox, but with fancy styling.
12131 //
12132 // description:
12133 // User interacts with real html inputs.
12134 // On onclick (which occurs by mouse click, space-bar, or
12135 // using the arrow keys to switch the selected radio button),
12136 // we update the state of the checkbox/radio.
12137 //
12138 // There are two modes:
12139 // 1. High contrast mode
12140 // 2. Normal mode
12141 //
12142 // In case 1, the regular html inputs are shown and used by the user.
12143 // In case 2, the regular html inputs are invisible but still used by
12144 // the user. They are turned quasi-invisible and overlay the background-image.
12145
81bea17a 12146 templateString: dojo.cache("dijit.form", "templates/CheckBox.html", "<div class=\"dijit dijitReset dijitInline\" role=\"presentation\"\n\t><input\n\t \t${!nameAttrSetting} type=\"${type}\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdojoAttachPoint=\"focusNode\"\n\t \tdojoAttachEvent=\"onclick:_onClick\"\n/></div>\n"),
a089699c
AD
12147
12148 baseClass: "dijitCheckBox",
12149
12150 // type: [private] String
12151 // type attribute on <input> node.
81bea17a 12152 // Overrides `dijit.form.Button.type`. Users should not change this value.
a089699c
AD
12153 type: "checkbox",
12154
12155 // value: String
12156 // As an initialization parameter, equivalent to value field on normal checkbox
12157 // (if checked, the value is passed as the value when form is submitted).
12158 //
81bea17a 12159 // However, get('value') will return either the string or false depending on
a089699c
AD
12160 // whether or not the checkbox is checked.
12161 //
81bea17a 12162 // set('value', string) will check the checkbox and change the value to the
a089699c
AD
12163 // specified string
12164 //
81bea17a 12165 // set('value', boolean) will change the checked state.
a089699c
AD
12166 value: "on",
12167
12168 // readOnly: Boolean
12169 // Should this widget respond to user input?
12170 // In markup, this is specified as "readOnly".
12171 // Similar to disabled except readOnly form values are submitted.
12172 readOnly: false,
12173
81bea17a 12174 // the attributeMap should inherit from dijit.form._FormWidget.prototype.attributeMap
a089699c
AD
12175 // instead of ToggleButton as the icon mapping has no meaning for a CheckBox
12176 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
12177 readOnly: "focusNode"
12178 }),
12179
12180 _setReadOnlyAttr: function(/*Boolean*/ value){
81bea17a 12181 this._set("readOnly", value);
a089699c
AD
12182 dojo.attr(this.focusNode, 'readOnly', value);
12183 dijit.setWaiState(this.focusNode, "readonly", value);
12184 },
12185
81bea17a 12186 _setValueAttr: function(/*String|Boolean*/ newValue, /*Boolean*/ priorityChange){
a089699c
AD
12187 // summary:
12188 // Handler for value= attribute to constructor, and also calls to
81bea17a 12189 // set('value', val).
a089699c
AD
12190 // description:
12191 // During initialization, just saves as attribute to the <input type=checkbox>.
12192 //
12193 // After initialization,
12194 // when passed a boolean, controls whether or not the CheckBox is checked.
12195 // If passed a string, changes the value attribute of the CheckBox (the one
12196 // specified as "value" when the CheckBox was constructed (ex: <input
12197 // dojoType="dijit.CheckBox" value="chicken">)
12198 if(typeof newValue == "string"){
81bea17a 12199 this._set("value", newValue);
a089699c
AD
12200 dojo.attr(this.focusNode, 'value', newValue);
12201 newValue = true;
12202 }
12203 if(this._created){
12204 this.set('checked', newValue, priorityChange);
12205 }
12206 },
12207 _getValueAttr: function(){
12208 // summary:
81bea17a 12209 // Hook so get('value') works.
a089699c
AD
12210 // description:
12211 // If the CheckBox is checked, returns the value attribute.
12212 // Otherwise returns false.
12213 return (this.checked ? this.value : false);
12214 },
12215
12216 // Override dijit.form.Button._setLabelAttr() since we don't even have a containerNode.
12217 // Normally users won't try to set label, except when CheckBox or RadioButton is the child of a dojox.layout.TabContainer
12218 _setLabelAttr: undefined,
12219
12220 postMixInProperties: function(){
12221 if(this.value == ""){
12222 this.value = "on";
12223 }
12224
12225 // Need to set initial checked state as part of template, so that form submit works.
12226 // dojo.attr(node, "checked", bool) doesn't work on IEuntil node has been attached
12227 // to <body>, see #8666
12228 this.checkedAttrSetting = this.checked ? "checked" : "";
12229
12230 this.inherited(arguments);
12231 },
12232
12233 _fillContent: function(/*DomNode*/ source){
12234 // Override Button::_fillContent() since it doesn't make sense for CheckBox,
12235 // since CheckBox doesn't even have a container
12236 },
12237
12238 reset: function(){
12239 // Override ToggleButton.reset()
12240
12241 this._hasBeenBlurred = false;
12242
12243 this.set('checked', this.params.checked || false);
12244
12245 // Handle unlikely event that the <input type=checkbox> value attribute has changed
81bea17a 12246 this._set("value", this.params.value || "on");
a089699c
AD
12247 dojo.attr(this.focusNode, 'value', this.value);
12248 },
12249
12250 _onFocus: function(){
12251 if(this.id){
12252 dojo.query("label[for='"+this.id+"']").addClass("dijitFocusedLabel");
12253 }
12254 this.inherited(arguments);
12255 },
12256
12257 _onBlur: function(){
12258 if(this.id){
12259 dojo.query("label[for='"+this.id+"']").removeClass("dijitFocusedLabel");
12260 }
12261 this.inherited(arguments);
12262 },
12263
12264 _onClick: function(/*Event*/ e){
12265 // summary:
12266 // Internal function to handle click actions - need to check
12267 // readOnly, since button no longer does that check.
12268 if(this.readOnly){
81bea17a 12269 dojo.stopEvent(e);
a089699c
AD
12270 return false;
12271 }
12272 return this.inherited(arguments);
12273 }
12274 }
12275);
12276
12277dojo.declare(
12278 "dijit.form.RadioButton",
12279 dijit.form.CheckBox,
12280 {
12281 // summary:
12282 // Same as an HTML radio, but with fancy styling.
12283
12284 type: "radio",
12285 baseClass: "dijitRadio",
12286
12287 _setCheckedAttr: function(/*Boolean*/ value){
12288 // If I am being checked then have to deselect currently checked radio button
12289 this.inherited(arguments);
12290 if(!this._created){ return; }
12291 if(value){
12292 var _this = this;
12293 // search for radio buttons with the same name that need to be unchecked
12294 dojo.query("INPUT[type=radio]", this.focusNode.form || dojo.doc).forEach( // can't use name= since dojo.query doesn't support [] in the name
12295 function(inputNode){
12296 if(inputNode.name == _this.name && inputNode != _this.focusNode && inputNode.form == _this.focusNode.form){
12297 var widget = dijit.getEnclosingWidget(inputNode);
12298 if(widget && widget.checked){
12299 widget.set('checked', false);
12300 }
12301 }
12302 }
12303 );
12304 }
12305 },
12306
12307 _clicked: function(/*Event*/ e){
12308 if(!this.checked){
12309 this.set('checked', true);
12310 }
12311 }
12312 }
12313);
12314
12315}
12316
12317if(!dojo._hasResource["dijit.form.DropDownButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12318dojo._hasResource["dijit.form.DropDownButton"] = true;
12319dojo.provide("dijit.form.DropDownButton");
12320
12321
12322
81bea17a 12323
a089699c
AD
12324}
12325
12326if(!dojo._hasResource["dojo.regexp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12327dojo._hasResource["dojo.regexp"] = true;
12328dojo.provide("dojo.regexp");
12329
81bea17a
AD
12330dojo.getObject("regexp", true, dojo);
12331
a089699c
AD
12332/*=====
12333dojo.regexp = {
12334 // summary: Regular expressions and Builder resources
12335};
12336=====*/
12337
12338dojo.regexp.escapeString = function(/*String*/str, /*String?*/except){
12339 // summary:
12340 // Adds escape sequences for special characters in regular expressions
12341 // except:
12342 // a String with special characters to be left unescaped
12343
12344 return str.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(ch){
12345 if(except && except.indexOf(ch) != -1){
12346 return ch;
12347 }
12348 return "\\" + ch;
12349 }); // String
81bea17a 12350};
a089699c
AD
12351
12352dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boolean?*/nonCapture){
12353 // summary:
12354 // Builds a regular expression that groups subexpressions
12355 // description:
12356 // A utility function used by some of the RE generators. The
12357 // subexpressions are constructed by the function, re, in the second
12358 // parameter. re builds one subexpression for each elem in the array
12359 // a, in the first parameter. Returns a string for a regular
12360 // expression that groups all the subexpressions.
12361 // arr:
12362 // A single value or an array of values.
12363 // re:
12364 // A function. Takes one parameter and converts it to a regular
81bea17a 12365 // expression.
a089699c
AD
12366 // nonCapture:
12367 // If true, uses non-capturing match, otherwise matches are retained
12368 // by regular expression. Defaults to false
12369
12370 // case 1: a is a single value.
12371 if(!(arr instanceof Array)){
12372 return re(arr); // String
12373 }
12374
12375 // case 2: a is an array
12376 var b = [];
12377 for(var i = 0; i < arr.length; i++){
12378 // convert each elem to a RE
12379 b.push(re(arr[i]));
12380 }
12381
12382 // join the REs as alternatives in a RE group.
12383 return dojo.regexp.group(b.join("|"), nonCapture); // String
81bea17a 12384};
a089699c
AD
12385
12386dojo.regexp.group = function(/*String*/expression, /*Boolean?*/nonCapture){
12387 // summary:
12388 // adds group match to expression
12389 // nonCapture:
12390 // If true, uses non-capturing match, otherwise matches are retained
81bea17a 12391 // by regular expression.
a089699c 12392 return "(" + (nonCapture ? "?:":"") + expression + ")"; // String
81bea17a 12393};
a089699c
AD
12394
12395}
12396
12397if(!dojo._hasResource["dojo.data.util.sorter"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12398dojo._hasResource["dojo.data.util.sorter"] = true;
12399dojo.provide("dojo.data.util.sorter");
12400
81bea17a
AD
12401dojo.getObject("data.util.sorter", true, dojo);
12402
12403dojo.data.util.sorter.basicComparator = function( /*anything*/ a,
a089699c 12404 /*anything*/ b){
81bea17a 12405 // summary:
a089699c 12406 // Basic comparision function that compares if an item is greater or less than another item
81bea17a 12407 // description:
a089699c
AD
12408 // returns 1 if a > b, -1 if a < b, 0 if equal.
12409 // 'null' values (null, undefined) are treated as larger values so that they're pushed to the end of the list.
12410 // And compared to each other, null is equivalent to undefined.
12411
12412 //null is a problematic compare, so if null, we set to undefined.
12413 //Makes the check logic simple, compact, and consistent
12414 //And (null == undefined) === true, so the check later against null
12415 //works for undefined and is less bytes.
12416 var r = -1;
12417 if(a === null){
12418 a = undefined;
12419 }
12420 if(b === null){
12421 b = undefined;
12422 }
12423 if(a == b){
81bea17a 12424 r = 0;
a089699c 12425 }else if(a > b || a == null){
81bea17a 12426 r = 1;
a089699c
AD
12427 }
12428 return r; //int {-1,0,1}
12429};
12430
12431dojo.data.util.sorter.createSortFunction = function( /* attributes array */sortSpec,
12432 /*dojo.data.core.Read*/ store){
81bea17a 12433 // summary:
a089699c 12434 // Helper function to generate the sorting function based off the list of sort attributes.
81bea17a 12435 // description:
a089699c
AD
12436 // The sort function creation will look for a property on the store called 'comparatorMap'. If it exists
12437 // it will look in the mapping for comparisons function for the attributes. If one is found, it will
12438 // use it instead of the basic comparator, which is typically used for strings, ints, booleans, and dates.
12439 // Returns the sorting function for this particular list of attributes and sorting directions.
12440 //
12441 // sortSpec: array
12442 // A JS object that array that defines out what attribute names to sort on and whether it should be descenting or asending.
12443 // The objects should be formatted as follows:
12444 // {
12445 // attribute: "attributeName-string" || attribute,
12446 // descending: true|false; // Default is false.
12447 // }
12448 // store: object
12449 // The datastore object to look up item values from.
12450 //
12451 var sortFunctions=[];
12452
12453 function createSortFunction(attr, dir, comp, s){
12454 //Passing in comp and s (comparator and store), makes this
12455 //function much faster.
12456 return function(itemA, itemB){
12457 var a = s.getValue(itemA, attr);
12458 var b = s.getValue(itemB, attr);
12459 return dir * comp(a,b); //int
12460 };
12461 }
12462 var sortAttribute;
12463 var map = store.comparatorMap;
12464 var bc = dojo.data.util.sorter.basicComparator;
12465 for(var i = 0; i < sortSpec.length; i++){
12466 sortAttribute = sortSpec[i];
12467 var attr = sortAttribute.attribute;
12468 if(attr){
12469 var dir = (sortAttribute.descending) ? -1 : 1;
12470 var comp = bc;
12471 if(map){
12472 if(typeof attr !== "string" && ("toString" in attr)){
12473 attr = attr.toString();
12474 }
12475 comp = map[attr] || bc;
12476 }
81bea17a 12477 sortFunctions.push(createSortFunction(attr,
a089699c
AD
12478 dir, comp, store));
12479 }
12480 }
12481 return function(rowA, rowB){
12482 var i=0;
12483 while(i < sortFunctions.length){
12484 var ret = sortFunctions[i++](rowA, rowB);
12485 if(ret !== 0){
12486 return ret;//int
12487 }
12488 }
81bea17a 12489 return 0; //int
a089699c
AD
12490 }; // Function
12491};
12492
12493}
12494
12495if(!dojo._hasResource["dojo.data.util.simpleFetch"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12496dojo._hasResource["dojo.data.util.simpleFetch"] = true;
12497dojo.provide("dojo.data.util.simpleFetch");
12498
12499
81bea17a
AD
12500dojo.getObject("data.util.simpleFetch", true, dojo);
12501
a089699c
AD
12502dojo.data.util.simpleFetch.fetch = function(/* Object? */ request){
12503 // summary:
12504 // The simpleFetch mixin is designed to serve as a set of function(s) that can
81bea17a
AD
12505 // be mixed into other datastore implementations to accelerate their development.
12506 // The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems()
a089699c
AD
12507 // call by returning an array of all the found items that matched the query. The simpleFetch mixin
12508 // is not designed to work for datastores that respond to a fetch() call by incrementally
12509 // loading items, or sequentially loading partial batches of the result
81bea17a 12510 // set. For datastores that mixin simpleFetch, simpleFetch
a089699c
AD
12511 // implements a fetch method that automatically handles eight of the fetch()
12512 // arguments -- onBegin, onItem, onComplete, onError, start, count, sort and scope
12513 // The class mixing in simpleFetch should not implement fetch(),
81bea17a
AD
12514 // but should instead implement a _fetchItems() method. The _fetchItems()
12515 // method takes three arguments, the keywordArgs object that was passed
a089699c
AD
12516 // to fetch(), a callback function to be called when the result array is
12517 // available, and an error callback to be called if something goes wrong.
12518 // The _fetchItems() method should ignore any keywordArgs parameters for
81bea17a 12519 // start, count, onBegin, onItem, onComplete, onError, sort, and scope.
a089699c 12520 // The _fetchItems() method needs to correctly handle any other keywordArgs
81bea17a
AD
12521 // parameters, including the query parameter and any optional parameters
12522 // (such as includeChildren). The _fetchItems() method should create an array of
12523 // result items and pass it to the fetchHandler along with the original request object
12524 // -- or, the _fetchItems() method may, if it wants to, create an new request object
12525 // with other specifics about the request that are specific to the datastore and pass
a089699c
AD
12526 // that as the request object to the handler.
12527 //
12528 // For more information on this specific function, see dojo.data.api.Read.fetch()
12529 request = request || {};
12530 if(!request.store){
12531 request.store = this;
12532 }
12533 var self = this;
12534
12535 var _errorHandler = function(errorData, requestObject){
12536 if(requestObject.onError){
12537 var scope = requestObject.scope || dojo.global;
12538 requestObject.onError.call(scope, errorData, requestObject);
12539 }
12540 };
12541
12542 var _fetchHandler = function(items, requestObject){
12543 var oldAbortFunction = requestObject.abort || null;
12544 var aborted = false;
12545
12546 var startIndex = requestObject.start?requestObject.start:0;
12547 var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length;
12548
12549 requestObject.abort = function(){
12550 aborted = true;
12551 if(oldAbortFunction){
12552 oldAbortFunction.call(requestObject);
12553 }
12554 };
12555
12556 var scope = requestObject.scope || dojo.global;
12557 if(!requestObject.store){
12558 requestObject.store = self;
12559 }
12560 if(requestObject.onBegin){
12561 requestObject.onBegin.call(scope, items.length, requestObject);
12562 }
12563 if(requestObject.sort){
12564 items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self));
12565 }
12566 if(requestObject.onItem){
12567 for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){
12568 var item = items[i];
12569 if(!aborted){
12570 requestObject.onItem.call(scope, item, requestObject);
12571 }
12572 }
12573 }
12574 if(requestObject.onComplete && !aborted){
12575 var subset = null;
12576 if(!requestObject.onItem){
12577 subset = items.slice(startIndex, endIndex);
12578 }
12579 requestObject.onComplete.call(scope, subset, requestObject);
12580 }
12581 };
12582 this._fetchItems(request, _fetchHandler, _errorHandler);
12583 return request; // Object
12584};
12585
12586}
12587
12588if(!dojo._hasResource["dojo.data.util.filter"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12589dojo._hasResource["dojo.data.util.filter"] = true;
12590dojo.provide("dojo.data.util.filter");
12591
81bea17a
AD
12592dojo.getObject("data.util.filter", true, dojo);
12593
a089699c 12594dojo.data.util.filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/ ignoreCase){
81bea17a 12595 // summary:
a089699c
AD
12596 // Helper function to convert a simple pattern to a regular expression for matching.
12597 // description:
12598 // Returns a regular expression object that conforms to the defined conversion rules.
81bea17a 12599 // For example:
a089699c
AD
12600 // ca* -> /^ca.*$/
12601 // *ca* -> /^.*ca.*$/
12602 // *c\*a* -> /^.*c\*a.*$/
12603 // *c\*a?* -> /^.*c\*a..*$/
12604 // and so on.
12605 //
12606 // pattern: string
12607 // A simple matching pattern to convert that follows basic rules:
12608 // * Means match anything, so ca* means match anything starting with ca
12609 // ? Means match single character. So, b?b will match to bob and bab, and so on.
12610 // \ is an escape character. So for example, \* means do not treat * as a match, but literal character *.
81bea17a 12611 // To use a \ as a character in the string, it must be escaped. So in the pattern it should be
a089699c
AD
12612 // represented by \\ to be treated as an ordinary \ character instead of an escape.
12613 //
12614 // ignoreCase:
12615 // An optional flag to indicate if the pattern matching should be treated as case-sensitive or not when comparing
12616 // By default, it is assumed case sensitive.
12617
12618 var rxp = "^";
12619 var c = null;
12620 for(var i = 0; i < pattern.length; i++){
12621 c = pattern.charAt(i);
12622 switch(c){
12623 case '\\':
12624 rxp += c;
12625 i++;
12626 rxp += pattern.charAt(i);
12627 break;
12628 case '*':
12629 rxp += ".*"; break;
12630 case '?':
12631 rxp += "."; break;
12632 case '$':
12633 case '^':
12634 case '/':
12635 case '+':
12636 case '.':
12637 case '|':
12638 case '(':
12639 case ')':
12640 case '{':
12641 case '}':
12642 case '[':
12643 case ']':
12644 rxp += "\\"; //fallthrough
12645 default:
12646 rxp += c;
12647 }
12648 }
12649 rxp += "$";
12650 if(ignoreCase){
12651 return new RegExp(rxp,"mi"); //RegExp
12652 }else{
12653 return new RegExp(rxp,"m"); //RegExp
12654 }
12655
12656};
12657
12658}
12659
12660if(!dojo._hasResource["dijit.form.TextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
12661dojo._hasResource["dijit.form.TextBox"] = true;
12662dojo.provide("dijit.form.TextBox");
12663
12664
12665
12666dojo.declare(
12667 "dijit.form.TextBox",
12668 dijit.form._FormValueWidget,
12669 {
12670 // summary:
12671 // A base class for textbox form inputs
12672
12673 // trim: Boolean
12674 // Removes leading and trailing whitespace if true. Default is false.
12675 trim: false,
12676
12677 // uppercase: Boolean
12678 // Converts all characters to uppercase if true. Default is false.
12679 uppercase: false,
12680
12681 // lowercase: Boolean
12682 // Converts all characters to lowercase if true. Default is false.
12683 lowercase: false,
12684
12685 // propercase: Boolean
12686 // Converts the first character of each word to uppercase if true.
12687 propercase: false,
12688
81bea17a 12689 // maxLength: String
a089699c
AD
12690 // HTML INPUT tag maxLength declaration.
12691 maxLength: "",
12692
81bea17a 12693 // selectOnClick: [const] Boolean
a089699c
AD
12694 // If true, all text will be selected when focused with mouse
12695 selectOnClick: false,
12696
81bea17a 12697 // placeHolder: String
a089699c
AD
12698 // Defines a hint to help users fill out the input field (as defined in HTML 5).
12699 // This should only contain plain text (no html markup).
12700 placeHolder: "",
12701
81bea17a 12702 templateString: dojo.cache("dijit.form", "templates/TextBox.html", "<div class=\"dijit dijitReset dijitInline dijitLeft\" id=\"widget_${id}\" role=\"presentation\"\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
a089699c
AD
12703 _singleNodeTemplate: '<input class="dijit dijitReset dijitLeft dijitInputField" dojoAttachPoint="textbox,focusNode" autocomplete="off" type="${type}" ${!nameAttrSetting} />',
12704
12705 _buttonInputDisabled: dojo.isIE ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events
12706
12707 baseClass: "dijitTextBox",
12708
12709 attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, {
12710 maxLength: "focusNode"
12711 }),
12712
12713 postMixInProperties: function(){
12714 var type = this.type.toLowerCase();
81bea17a 12715 if(this.templateString && this.templateString.toLowerCase() == "input" || ((type == "hidden" || type == "file") && this.templateString == dijit.form.TextBox.prototype.templateString)){
a089699c
AD
12716 this.templateString = this._singleNodeTemplate;
12717 }
12718 this.inherited(arguments);
12719 },
12720
12721 _setPlaceHolderAttr: function(v){
81bea17a 12722 this._set("placeHolder", v);
a089699c
AD
12723 if(!this._phspan){
12724 this._attachPoints.push('_phspan');
12725 /* dijitInputField class gives placeHolder same padding as the input field
12726 * parent node already has dijitInputField class but it doesn't affect this <span>
12727 * since it's position: absolute.
12728 */
12729 this._phspan = dojo.create('span',{className:'dijitPlaceHolder dijitInputField'},this.textbox,'after');
12730 }
12731 this._phspan.innerHTML="";
12732 this._phspan.appendChild(document.createTextNode(v));
12733
12734 this._updatePlaceHolder();
12735 },
12736
12737 _updatePlaceHolder: function(){
12738 if(this._phspan){
12739 this._phspan.style.display=(this.placeHolder&&!this._focused&&!this.textbox.value)?"":"none";
12740 }
12741 },
12742
12743 _getValueAttr: function(){
12744 // summary:
81bea17a 12745 // Hook so get('value') works as we like.
a089699c
AD
12746 // description:
12747 // For `dijit.form.TextBox` this basically returns the value of the <input>.
12748 //
12749 // For `dijit.form.MappedTextBox` subclasses, which have both
12750 // a "displayed value" and a separate "submit value",
12751 // This treats the "displayed value" as the master value, computing the
12752 // submit value from it via this.parse().
12753 return this.parse(this.get('displayedValue'), this.constraints);
12754 },
12755
12756 _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
12757 // summary:
81bea17a 12758 // Hook so set('value', ...) works.
a089699c
AD
12759 //
12760 // description:
12761 // Sets the value of the widget to "value" which can be of
12762 // any type as determined by the widget.
12763 //
12764 // value:
12765 // The visual element value is also set to a corresponding,
12766 // but not necessarily the same, value.
12767 //
12768 // formattedValue:
12769 // If specified, used to set the visual element value,
12770 // otherwise a computed visual value is used.
12771 //
12772 // priorityChange:
12773 // If true, an onChange event is fired immediately instead of
12774 // waiting for the next blur event.
12775
12776 var filteredValue;
12777 if(value !== undefined){
12778 // TODO: this is calling filter() on both the display value and the actual value.
12779 // I added a comment to the filter() definition about this, but it should be changed.
12780 filteredValue = this.filter(value);
12781 if(typeof formattedValue != "string"){
12782 if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){
12783 formattedValue = this.filter(this.format(filteredValue, this.constraints));
12784 }else{ formattedValue = ''; }
12785 }
12786 }
12787 if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){
12788 this.textbox.value = formattedValue;
81bea17a 12789 this._set("displayedValue", this.get("displayedValue"));
a089699c
AD
12790 }
12791
12792 this._updatePlaceHolder();
12793
12794 this.inherited(arguments, [filteredValue, priorityChange]);
12795 },
12796
12797 // displayedValue: String
12798 // For subclasses like ComboBox where the displayed value
12799 // (ex: Kentucky) and the serialized value (ex: KY) are different,
12800 // this represents the displayed value.
12801 //
81bea17a 12802 // Setting 'displayedValue' through set('displayedValue', ...)
a089699c
AD
12803 // updates 'value', and vice-versa. Otherwise 'value' is updated
12804 // from 'displayedValue' periodically, like onBlur etc.
12805 //
12806 // TODO: move declaration to MappedTextBox?
12807 // Problem is that ComboBox references displayedValue,
12808 // for benefit of FilteringSelect.
12809 displayedValue: "",
12810
12811 getDisplayedValue: function(){
12812 // summary:
81bea17a 12813 // Deprecated. Use get('displayedValue') instead.
a089699c
AD
12814 // tags:
12815 // deprecated
12816 dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use set('displayedValue') instead.", "", "2.0");
12817 return this.get('displayedValue');
12818 },
12819
12820 _getDisplayedValueAttr: function(){
12821 // summary:
81bea17a 12822 // Hook so get('displayedValue') works.
a089699c
AD
12823 // description:
12824 // Returns the displayed value (what the user sees on the screen),
12825 // after filtering (ie, trimming spaces etc.).
12826 //
12827 // For some subclasses of TextBox (like ComboBox), the displayed value
12828 // is different from the serialized value that's actually
12829 // sent to the server (see dijit.form.ValidationTextBox.serialize)
12830
81bea17a
AD
12831 // TODO: maybe we should update this.displayedValue on every keystroke so that we don't need
12832 // this method
12833 // TODO: this isn't really the displayed value when the user is typing
a089699c
AD
12834 return this.filter(this.textbox.value);
12835 },
12836
81bea17a 12837 setDisplayedValue: function(/*String*/ value){
a089699c 12838 // summary:
81bea17a 12839 // Deprecated. Use set('displayedValue', ...) instead.
a089699c
AD
12840 // tags:
12841 // deprecated
12842 dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use set('displayedValue', ...) instead.", "", "2.0");
12843 this.set('displayedValue', value);
12844 },
12845
81bea17a 12846 _setDisplayedValueAttr: function(/*String*/ value){
a089699c 12847 // summary:
81bea17a 12848 // Hook so set('displayedValue', ...) works.
a089699c
AD
12849 // description:
12850 // Sets the value of the visual element to the string "value".
12851 // The widget value is also set to a corresponding,
12852 // but not necessarily the same, value.
12853
12854 if(value === null || value === undefined){ value = '' }
12855 else if(typeof value != "string"){ value = String(value) }
81bea17a 12856
a089699c 12857 this.textbox.value = value;
81bea17a
AD
12858
12859 // sets the serialized value to something corresponding to specified displayedValue
12860 // (if possible), and also updates the textbox.value, for example converting "123"
12861 // to "123.00"
12862 this._setValueAttr(this.get('value'), undefined);
12863
12864 this._set("displayedValue", this.get('displayedValue'));
a089699c
AD
12865 },
12866
81bea17a 12867 format: function(/*String*/ value, /*Object*/ constraints){
a089699c
AD
12868 // summary:
12869 // Replacable function to convert a value to a properly formatted string.
12870 // tags:
12871 // protected extension
12872 return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
12873 },
12874
81bea17a 12875 parse: function(/*String*/ value, /*Object*/ constraints){
a089699c
AD
12876 // summary:
12877 // Replacable function to convert a formatted string to a value
12878 // tags:
12879 // protected extension
12880
12881 return value; // String
12882 },
12883
12884 _refreshState: function(){
12885 // summary:
12886 // After the user types some characters, etc., this method is
12887 // called to check the field for validity etc. The base method
12888 // in `dijit.form.TextBox` does nothing, but subclasses override.
12889 // tags:
12890 // protected
12891 },
12892
12893 _onInput: function(e){
12894 if(e && e.type && /key/i.test(e.type) && e.keyCode){
12895 switch(e.keyCode){
12896 case dojo.keys.SHIFT:
12897 case dojo.keys.ALT:
12898 case dojo.keys.CTRL:
12899 case dojo.keys.TAB:
12900 return;
12901 }
12902 }
12903 if(this.intermediateChanges){
12904 var _this = this;
12905 // the setTimeout allows the key to post to the widget input box
12906 setTimeout(function(){ _this._handleOnChange(_this.get('value'), false); }, 0);
12907 }
12908 this._refreshState();
81bea17a
AD
12909
12910 // In case someone is watch()'ing for changes to displayedValue
12911 this._set("displayedValue", this.get("displayedValue"));
a089699c
AD
12912 },
12913
12914 postCreate: function(){
a089699c 12915 if(dojo.isIE){ // IE INPUT tag fontFamily has to be set directly using STYLE
81bea17a
AD
12916 // the setTimeout gives IE a chance to render the TextBox and to deal with font inheritance
12917 setTimeout(dojo.hitch(this, function(){
a089699c
AD
12918 var s = dojo.getComputedStyle(this.domNode);
12919 if(s){
12920 var ff = s.fontFamily;
12921 if(ff){
12922 var inputs = this.domNode.getElementsByTagName("INPUT");
12923 if(inputs){
12924 for(var i=0; i < inputs.length; i++){
12925 inputs[i].style.fontFamily = ff;
12926 }
12927 }
12928 }
12929 }
81bea17a 12930 }), 0);
a089699c 12931 }
81bea17a
AD
12932
12933 // setting the value here is needed since value="" in the template causes "undefined"
12934 // and setting in the DOM (instead of the JS object) helps with form reset actions
12935 this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values should be the same
12936
a089699c 12937 this.inherited(arguments);
81bea17a 12938
a089699c 12939 if(dojo.isMoz || dojo.isOpera){
81bea17a 12940 this.connect(this.textbox, "oninput", "_onInput");
a089699c 12941 }else{
81bea17a
AD
12942 this.connect(this.textbox, "onkeydown", "_onInput");
12943 this.connect(this.textbox, "onkeyup", "_onInput");
12944 this.connect(this.textbox, "onpaste", "_onInput");
12945 this.connect(this.textbox, "oncut", "_onInput");
a089699c
AD
12946 }
12947 },
12948
12949 _blankValue: '', // if the textbox is blank, what value should be reported
12950 filter: function(val){
12951 // summary:
12952 // Auto-corrections (such as trimming) that are applied to textbox
12953 // value on blur or form submit.
12954 // description:
12955 // For MappedTextBox subclasses, this is called twice
12956 // - once with the display value
81bea17a
AD
12957 // - once the value as set/returned by set('value', ...)
12958 // and get('value'), ex: a Number for NumberTextBox.
a089699c
AD
12959 //
12960 // In the latter case it does corrections like converting null to NaN. In
12961 // the former case the NumberTextBox.filter() method calls this.inherited()
12962 // to execute standard trimming code in TextBox.filter().
12963 //
12964 // TODO: break this into two methods in 2.0
12965 //
12966 // tags:
12967 // protected extension
12968 if(val === null){ return this._blankValue; }
12969 if(typeof val != "string"){ return val; }
12970 if(this.trim){
12971 val = dojo.trim(val);
12972 }
12973 if(this.uppercase){
12974 val = val.toUpperCase();
12975 }
12976 if(this.lowercase){
12977 val = val.toLowerCase();
12978 }
12979 if(this.propercase){
12980 val = val.replace(/[^\s]+/g, function(word){
12981 return word.substring(0,1).toUpperCase() + word.substring(1);
12982 });
12983 }
12984 return val;
12985 },
12986
12987 _setBlurValue: function(){
12988 this._setValueAttr(this.get('value'), true);
12989 },
12990
12991 _onBlur: function(e){
12992 if(this.disabled){ return; }
12993 this._setBlurValue();
12994 this.inherited(arguments);
12995
12996 if(this._selectOnClickHandle){
12997 this.disconnect(this._selectOnClickHandle);
12998 }
12999 if(this.selectOnClick && dojo.isMoz){
13000 this.textbox.selectionStart = this.textbox.selectionEnd = undefined; // clear selection so that the next mouse click doesn't reselect
13001 }
13002
13003 this._updatePlaceHolder();
13004 },
13005
13006 _onFocus: function(/*String*/ by){
13007 if(this.disabled || this.readOnly){ return; }
13008
13009 // Select all text on focus via click if nothing already selected.
13010 // Since mouse-up will clear the selection need to defer selection until after mouse-up.
13011 // Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event.
13012 if(this.selectOnClick && by == "mouse"){
13013 this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){
13014 // Only select all text on first click; otherwise users would have no way to clear
13015 // the selection.
13016 this.disconnect(this._selectOnClickHandle);
13017
13018 // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up)
13019 // and if not, then select all the text
13020 var textIsNotSelected;
13021 if(dojo.isIE){
13022 var range = dojo.doc.selection.createRange();
13023 var parent = range.parentElement();
13024 textIsNotSelected = parent == this.textbox && range.text.length == 0;
13025 }else{
13026 textIsNotSelected = this.textbox.selectionStart == this.textbox.selectionEnd;
13027 }
13028 if(textIsNotSelected){
13029 dijit.selectInputText(this.textbox);
13030 }
13031 });
13032 }
13033
13034 this._updatePlaceHolder();
13035
81bea17a
AD
13036 // call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport
13037 // (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip
a089699c 13038 this.inherited(arguments);
81bea17a
AD
13039
13040 this._refreshState();
a089699c
AD
13041 },
13042
13043 reset: function(){
13044 // Overrides dijit._FormWidget.reset().
13045 // Additionally resets the displayed textbox value to ''
13046 this.textbox.value = '';
13047 this.inherited(arguments);
13048 }
13049 }
13050);
13051
81bea17a 13052dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){
a089699c
AD
13053 // summary:
13054 // Select text in the input element argument, from start (default 0), to stop (default end).
13055
13056 // TODO: use functions in _editor/selection.js?
13057 var _window = dojo.global;
13058 var _document = dojo.doc;
13059 element = dojo.byId(element);
13060 if(isNaN(start)){ start = 0; }
13061 if(isNaN(stop)){ stop = element.value ? element.value.length : 0; }
13062 dijit.focus(element);
13063 if(_document["selection"] && dojo.body()["createTextRange"]){ // IE
13064 if(element.createTextRange){
81bea17a
AD
13065 var r = element.createTextRange();
13066 r.collapse(true);
13067 r.moveStart("character", -99999); // move to 0
13068 r.moveStart("character", start); // delta from 0 is the correct position
13069 r.moveEnd("character", stop-start);
13070 r.select();
a089699c
AD
13071 }
13072 }else if(_window["getSelection"]){
13073 if(element.setSelectionRange){
13074 element.setSelectionRange(start, stop);
13075 }
13076 }
13077};
13078
13079}
13080
13081if(!dojo._hasResource["dijit.Tooltip"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
13082dojo._hasResource["dijit.Tooltip"] = true;
13083dojo.provide("dijit.Tooltip");
13084
13085
13086
13087
13088dojo.declare(
13089 "dijit._MasterTooltip",
13090 [dijit._Widget, dijit._Templated],
13091 {
13092 // summary:
13093 // Internal widget that holds the actual tooltip markup,
13094 // which occurs once per page.
13095 // Called by Tooltip widgets which are just containers to hold
13096 // the markup
13097 // tags:
13098 // protected
13099
13100 // duration: Integer
13101 // Milliseconds to fade in/fade out
13102 duration: dijit.defaultDuration,
13103
81bea17a 13104 templateString: dojo.cache("dijit", "templates/Tooltip.html", "<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\"\n\t><div class=\"dijitTooltipContainer dijitTooltipContents\" dojoAttachPoint=\"containerNode\" role='alert'></div\n\t><div class=\"dijitTooltipConnector\" dojoAttachPoint=\"connectorNode\"></div\n></div>\n"),
a089699c
AD
13105
13106 postCreate: function(){
13107 dojo.body().appendChild(this.domNode);
13108
13109 this.bgIframe = new dijit.BackgroundIframe(this.domNode);
13110
13111 // Setup fade-in and fade-out functions.
13112 this.fadeIn = dojo.fadeIn({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onShow") });
13113 this.fadeOut = dojo.fadeOut({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onHide") });
a089699c
AD
13114 },
13115
13116 show: function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
13117 // summary:
13118 // Display tooltip w/specified contents to right of specified node
13119 // (To left if there's no space on the right, or if rtl == true)
13120
13121 if(this.aroundNode && this.aroundNode === aroundNode){
13122 return;
13123 }
13124
81bea17a
AD
13125 // reset width; it may have been set by orient() on a previous tooltip show()
13126 this.domNode.width = "auto";
13127
a089699c
AD
13128 if(this.fadeOut.status() == "playing"){
13129 // previous tooltip is being hidden; wait until the hide completes then show new one
13130 this._onDeck=arguments;
13131 return;
13132 }
13133 this.containerNode.innerHTML=innerHTML;
13134
13135 var pos = dijit.placeOnScreenAroundElement(this.domNode, aroundNode, dijit.getPopupAroundAlignment((position && position.length) ? position : dijit.Tooltip.defaultPosition, !rtl), dojo.hitch(this, "orient"));
13136
13137 // show it
13138 dojo.style(this.domNode, "opacity", 0);
13139 this.fadeIn.play();
13140 this.isShowingNow = true;
13141 this.aroundNode = aroundNode;
13142 },
13143
81bea17a 13144 orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ tooltipCorner, /*Object*/ spaceAvailable, /*Object*/ aroundNodeCoords){
a089699c
AD
13145 // summary:
13146 // Private function to set CSS for tooltip node based on which position it's in.
81bea17a
AD
13147 // This is called by the dijit popup code. It will also reduce the tooltip's
13148 // width to whatever width is available
a089699c
AD
13149 // tags:
13150 // protected
81bea17a
AD
13151 this.connectorNode.style.top = ""; //reset to default
13152
13153 //Adjust the spaceAvailable width, without changing the spaceAvailable object
13154 var tooltipSpaceAvaliableWidth = spaceAvailable.w - this.connectorNode.offsetWidth;
a089699c
AD
13155
13156 node.className = "dijitTooltip " +
13157 {
13158 "BL-TL": "dijitTooltipBelow dijitTooltipABLeft",
13159 "TL-BL": "dijitTooltipAbove dijitTooltipABLeft",
13160 "BR-TR": "dijitTooltipBelow dijitTooltipABRight",
13161 "TR-BR": "dijitTooltipAbove dijitTooltipABRight",
13162 "BR-BL": "dijitTooltipRight",
13163 "BL-BR": "dijitTooltipLeft"
13164 }[aroundCorner + "-" + tooltipCorner];
81bea17a
AD
13165
13166 // reduce tooltip's width to the amount of width available, so that it doesn't overflow screen
13167 this.domNode.style.width = "auto";
13168 var size = dojo.contentBox(this.domNode);
13169
13170 var width = Math.min((Math.max(tooltipSpaceAvaliableWidth,1)), size.w);
13171 var widthWasReduced = width < size.w;
13172
13173 this.domNode.style.width = width+"px";
13174
13175 //Adjust width for tooltips that have a really long word or a nowrap setting
13176 if(widthWasReduced){
13177 this.containerNode.style.overflow = "auto"; //temp change to overflow to detect if our tooltip needs to be wider to support the content
13178 var scrollWidth = this.containerNode.scrollWidth;
13179 this.containerNode.style.overflow = "visible"; //change it back
13180 if(scrollWidth > width){
13181 scrollWidth = scrollWidth + dojo.style(this.domNode,"paddingLeft") + dojo.style(this.domNode,"paddingRight");
13182 this.domNode.style.width = scrollWidth + "px";
13183 }
13184 }
13185
13186 // Reposition the tooltip connector.
13187 if(tooltipCorner.charAt(0) == 'B' && aroundCorner.charAt(0) == 'B'){
13188 var mb = dojo.marginBox(node);
13189 var tooltipConnectorHeight = this.connectorNode.offsetHeight;
13190 if(mb.h > spaceAvailable.h){
13191 // The tooltip starts at the top of the page and will extend past the aroundNode
13192 var aroundNodePlacement = spaceAvailable.h - (aroundNodeCoords.h / 2) - (tooltipConnectorHeight / 2);
13193 this.connectorNode.style.top = aroundNodePlacement + "px";
13194 this.connectorNode.style.bottom = "";
13195 }else{
13196 // Align center of connector with center of aroundNode, except don't let bottom
13197 // of connector extend below bottom of tooltip content, or top of connector
13198 // extend past top of tooltip content
13199 this.connectorNode.style.bottom = Math.min(
13200 Math.max(aroundNodeCoords.h/2 - tooltipConnectorHeight/2, 0),
13201 mb.h - tooltipConnectorHeight) + "px";
13202 this.connectorNode.style.top = "";
13203 }
13204 }else{
13205 // reset the tooltip back to the defaults
13206 this.connectorNode.style.top = "";
13207 this.connectorNode.style.bottom = "";
13208 }
13209
13210 return Math.max(0, size.w - tooltipSpaceAvaliableWidth);
a089699c
AD
13211 },
13212
13213 _onShow: function(){
13214 // summary:
13215 // Called at end of fade-in operation
13216 // tags:
13217 // protected
13218 if(dojo.isIE){
13219 // the arrow won't show up on a node w/an opacity filter
13220 this.domNode.style.filter="";
13221 }
13222 },
13223
13224 hide: function(aroundNode){
13225 // summary:
13226 // Hide the tooltip
81bea17a 13227
a089699c
AD
13228 if(this._onDeck && this._onDeck[1] == aroundNode){
13229 // this hide request is for a show() that hasn't even started yet;
13230 // just cancel the pending show()
13231 this._onDeck=null;
13232 }else if(this.aroundNode === aroundNode){
13233 // this hide request is for the currently displayed tooltip
13234 this.fadeIn.stop();
13235 this.isShowingNow = false;
13236 this.aroundNode = null;
13237 this.fadeOut.play();
13238 }else{
13239 // just ignore the call, it's for a tooltip that has already been erased
13240 }
13241 },
13242
13243 _onHide: function(){
13244 // summary:
13245 // Called at end of fade-out operation
13246 // tags:
13247 // protected
13248
13249 this.domNode.style.cssText=""; // to position offscreen again
13250 this.containerNode.innerHTML="";
13251 if(this._onDeck){
13252 // a show request has been queued up; do it now
13253 this.show.apply(this, this._onDeck);
13254 this._onDeck=null;
13255 }
13256 }
13257
13258 }
13259);
13260
13261dijit.showTooltip = function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){
13262 // summary:
13263 // Display tooltip w/specified contents in specified position.
13264 // See description of dijit.Tooltip.defaultPosition for details on position parameter.
13265 // If position is not specified then dijit.Tooltip.defaultPosition is used.
13266 if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); }
13267 return dijit._masterTT.show(innerHTML, aroundNode, position, rtl);
13268};
13269
13270dijit.hideTooltip = function(aroundNode){
13271 // summary:
13272 // Hide the tooltip
13273 if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); }
13274 return dijit._masterTT.hide(aroundNode);
13275};
13276
13277dojo.declare(
13278 "dijit.Tooltip",
13279 dijit._Widget,
13280 {
13281 // summary:
13282 // Pops up a tooltip (a help message) when you hover over a node.
13283
13284 // label: String
13285 // Text to display in the tooltip.
13286 // Specified as innerHTML when creating the widget from markup.
13287 label: "",
13288
13289 // showDelay: Integer
13290 // Number of milliseconds to wait after hovering over/focusing on the object, before
13291 // the tooltip is displayed.
13292 showDelay: 400,
13293
81bea17a
AD
13294 // connectId: String|String[]
13295 // Id of domNode(s) to attach the tooltip to.
13296 // When user hovers over specified dom node, the tooltip will appear.
a089699c
AD
13297 connectId: [],
13298
13299 // position: String[]
13300 // See description of `dijit.Tooltip.defaultPosition` for details on position parameter.
13301 position: [],
13302
81bea17a
AD
13303 _setConnectIdAttr: function(/*String*/ newId){
13304 // summary:
13305 // Connect to node(s) (specified by id)
a089699c 13306
81bea17a
AD
13307 // Remove connections to old nodes (if there are any)
13308 dojo.forEach(this._connections || [], function(nested){
13309 dojo.forEach(nested, dojo.hitch(this, "disconnect"));
13310 }, this);
a089699c 13311
81bea17a
AD
13312 // Make connections to nodes in newIds.
13313 var ary = dojo.isArrayLike(newId) ? newId : (newId ? [newId] : []);
13314 this._connections = dojo.map(ary, function(id){
13315 var node = dojo.byId(id);
13316 return node ? [
13317 this.connect(node, "onmouseenter", "_onTargetMouseEnter"),
13318 this.connect(node, "onmouseleave", "_onTargetMouseLeave"),
13319 this.connect(node, "onfocus", "_onTargetFocus"),
13320 this.connect(node, "onblur", "_onTargetBlur")
13321 ] : [];
13322 }, this);
13323
13324 this._set("connectId", newId);
13325
13326 this._connectIds = ary; // save as array
a089699c
AD
13327 },
13328
81bea17a 13329 addTarget: function(/*DOMNODE || String*/ node){
a089699c 13330 // summary:
81bea17a
AD
13331 // Attach tooltip to specified node if it's not already connected
13332
13333 // TODO: remove in 2.0 and just use set("connectId", ...) interface
13334
13335 var id = node.id || node;
13336 if(dojo.indexOf(this._connectIds, id) == -1){
13337 this.set("connectId", this._connectIds.concat(id));
13338 }
a089699c
AD
13339 },
13340
13341 removeTarget: function(/*DOMNODE || String*/ node){
13342 // summary:
13343 // Detach tooltip from specified node
13344
81bea17a
AD
13345 // TODO: remove in 2.0 and just use set("connectId", ...) interface
13346
13347 var id = node.id || node, // map from DOMNode back to plain id string
13348 idx = dojo.indexOf(this._connectIds, id);
13349 if(idx >= 0){
13350 // remove id (modifies original this._connectIds but that's OK in this case)
13351 this._connectIds.splice(idx, 1);
13352 this.set("connectId", this._connectIds);
a089699c
AD
13353 }
13354 },
13355
81bea17a
AD
13356 buildRendering: function(){
13357 this.inherited(arguments);
a089699c
AD
13358 dojo.addClass(this.domNode,"dijitTooltipData");
13359 },
13360
13361 startup: function(){
13362 this.inherited(arguments);
13363
13364 // If this tooltip was created in a template, or for some other reason the specified connectId[s]
13365 // didn't exist during the widget's initialization, then connect now.
13366 var ids = this.connectId;
13367 dojo.forEach(dojo.isArrayLike(ids) ? ids : [ids], this.addTarget, this);
13368 },
13369
13370 _onTargetMouseEnter: function(/*Event*/ e){
13371 // summary:
13372 // Handler for mouseenter event on the target node
13373 // tags:
13374 // private
13375 this._onHover(e);
13376 },
13377
13378 _onTargetMouseLeave: function(/*Event*/ e){
13379 // summary:
13380 // Handler for mouseleave event on the target node
13381 // tags:
13382 // private
13383 this._onUnHover(e);
13384 },
13385
13386 _onTargetFocus: function(/*Event*/ e){
13387 // summary:
13388 // Handler for focus event on the target node
13389 // tags:
13390 // private
13391
13392 this._focus = true;
13393 this._onHover(e);
13394 },
13395
13396 _onTargetBlur: function(/*Event*/ e){
13397 // summary:
13398 // Handler for blur event on the target node
13399 // tags:
13400 // private
13401
13402 this._focus = false;
13403 this._onUnHover(e);
13404 },
13405
13406 _onHover: function(/*Event*/ e){
13407 // summary:
13408 // Despite the name of this method, it actually handles both hover and focus
13409 // events on the target node, setting a timer to show the tooltip.
13410 // tags:
13411 // private
13412 if(!this._showTimer){
13413 var target = e.target;
13414 this._showTimer = setTimeout(dojo.hitch(this, function(){this.open(target)}), this.showDelay);
13415 }
13416 },
13417
13418 _onUnHover: function(/*Event*/ e){
13419 // summary:
13420 // Despite the name of this method, it actually handles both mouseleave and blur
13421 // events on the target node, hiding the tooltip.
13422 // tags:
13423 // private
13424
13425 // keep a tooltip open if the associated element still has focus (even though the
13426 // mouse moved away)
13427 if(this._focus){ return; }
13428
13429 if(this._showTimer){
13430 clearTimeout(this._showTimer);
13431 delete this._showTimer;
13432 }
13433 this.close();
13434 },
13435
13436 open: function(/*DomNode*/ target){
13437 // summary:
13438 // Display the tooltip; usually not called directly.
13439 // tags:
13440 // private
13441
13442 if(this._showTimer){
13443 clearTimeout(this._showTimer);
13444 delete this._showTimer;
13445 }
13446 dijit.showTooltip(this.label || this.domNode.innerHTML, target, this.position, !this.isLeftToRight());
13447
13448 this._connectNode = target;
13449 this.onShow(target, this.position);
13450 },
13451
13452 close: function(){
13453 // summary:
13454 // Hide the tooltip or cancel timer for show of tooltip
13455 // tags:
13456 // private
13457
13458 if(this._connectNode){
13459 // if tooltip is currently shown
13460 dijit.hideTooltip(this._connectNode);
13461 delete this._connectNode;
13462 this.onHide();
13463 }
13464 if(this._showTimer){
13465 // if tooltip is scheduled to be shown (after a brief delay)
13466 clearTimeout(this._showTimer);
13467 delete this._showTimer;
13468 }
13469 },
13470
13471 onShow: function(target, position){
13472 // summary:
13473 // Called when the tooltip is shown
13474 // tags:
13475 // callback
13476 },
13477
13478 onHide: function(){
13479 // summary:
13480 // Called when the tooltip is hidden
13481 // tags:
13482 // callback
13483 },
13484
13485 uninitialize: function(){
13486 this.close();
13487 this.inherited(arguments);
13488 }
13489 }
13490);
13491
13492// dijit.Tooltip.defaultPosition: String[]
13493// This variable controls the position of tooltips, if the position is not specified to
13494// the Tooltip widget or *TextBox widget itself. It's an array of strings with the following values:
13495//
13496// * before: places tooltip to the left of the target node/widget, or to the right in
13497// the case of RTL scripts like Hebrew and Arabic
13498// * after: places tooltip to the right of the target node/widget, or to the left in
13499// the case of RTL scripts like Hebrew and Arabic
13500// * above: tooltip goes above target node
13501// * below: tooltip goes below target node
13502//
13503// The list is positions is tried, in order, until a position is found where the tooltip fits
13504// within the viewport.
13505//
13506// Be careful setting this parameter. A value of "above" may work fine until the user scrolls
13507// the screen so that there's no room above the target node. Nodes with drop downs, like
13508// DropDownButton or FilteringSelect, are especially problematic, in that you need to be sure
13509// that the drop down and tooltip don't overlap, even when the viewport is scrolled so that there
13510// is only room below (or above) the target node, but not both.
13511dijit.Tooltip.defaultPosition = ["after", "before"];
13512
13513}
13514
13515if(!dojo._hasResource["dijit.form.ValidationTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
13516dojo._hasResource["dijit.form.ValidationTextBox"] = true;
13517dojo.provide("dijit.form.ValidationTextBox");
13518
13519
13520
13521
13522
13523
a089699c
AD
13524/*=====
13525 dijit.form.ValidationTextBox.__Constraints = function(){
13526 // locale: String
13527 // locale used for validation, picks up value from this widget's lang attribute
13528 // _flags_: anything
13529 // various flags passed to regExpGen function
13530 this.locale = "";
13531 this._flags_ = "";
13532 }
13533=====*/
13534
13535dojo.declare(
13536 "dijit.form.ValidationTextBox",
13537 dijit.form.TextBox,
13538 {
13539 // summary:
13540 // Base class for textbox widgets with the ability to validate content of various types and provide user feedback.
13541 // tags:
13542 // protected
13543
81bea17a 13544 templateString: dojo.cache("dijit.form", "templates/ValidationTextBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" role=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&#935; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),
a089699c
AD
13545 baseClass: "dijitTextBox dijitValidationTextBox",
13546
13547 // required: Boolean
13548 // User is required to enter data into this field.
13549 required: false,
13550
13551 // promptMessage: String
13552 // If defined, display this hint string immediately on focus to the textbox, if empty.
81bea17a 13553 // Also displays if the textbox value is Incomplete (not yet valid but will be with additional input).
a089699c
AD
13554 // Think of this like a tooltip that tells the user what to do, not an error message
13555 // that tells the user what they've done wrong.
13556 //
13557 // Message disappears when user starts typing.
13558 promptMessage: "",
13559
13560 // invalidMessage: String
13561 // The message to display if value is invalid.
13562 // The translated string value is read from the message file by default.
13563 // Set to "" to use the promptMessage instead.
13564 invalidMessage: "$_unset_$",
13565
13566 // missingMessage: String
13567 // The message to display if value is empty and the field is required.
13568 // The translated string value is read from the message file by default.
13569 // Set to "" to use the invalidMessage instead.
13570 missingMessage: "$_unset_$",
13571
81bea17a
AD
13572 // message: String
13573 // Currently error/prompt message.
13574 // When using the default tooltip implementation, this will only be
13575 // displayed when the field is focused.
13576 message: "",
13577
a089699c
AD
13578 // constraints: dijit.form.ValidationTextBox.__Constraints
13579 // user-defined object needed to pass parameters to the validator functions
13580 constraints: {},
13581
13582 // regExp: [extension protected] String
13583 // regular expression string used to validate the input
13584 // Do not specify both regExp and regExpGen
13585 regExp: ".*",
13586
81bea17a 13587 regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/ constraints){
a089699c
AD
13588 // summary:
13589 // Overridable function used to generate regExp when dependent on constraints.
13590 // Do not specify both regExp and regExpGen.
13591 // tags:
13592 // extension protected
13593 return this.regExp; // String
13594 },
13595
13596 // state: [readonly] String
81bea17a 13597 // Shows current state (ie, validation result) of input (""=Normal, Incomplete, or Error)
a089699c
AD
13598 state: "",
13599
13600 // tooltipPosition: String[]
13601 // See description of `dijit.Tooltip.defaultPosition` for details on this parameter.
13602 tooltipPosition: [],
13603
13604 _setValueAttr: function(){
13605 // summary:
81bea17a 13606 // Hook so set('value', ...) works.
a089699c
AD
13607 this.inherited(arguments);
13608 this.validate(this._focused);
13609 },
13610
81bea17a 13611 validator: function(/*anything*/ value, /*dijit.form.ValidationTextBox.__Constraints*/ constraints){
a089699c
AD
13612 // summary:
13613 // Overridable function used to validate the text input against the regular expression.
13614 // tags:
13615 // protected
13616 return (new RegExp("^(?:" + this.regExpGen(constraints) + ")"+(this.required?"":"?")+"$")).test(value) &&
13617 (!this.required || !this._isEmpty(value)) &&
13618 (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean
13619 },
13620
13621 _isValidSubset: function(){
13622 // summary:
13623 // Returns true if the value is either already valid or could be made valid by appending characters.
13624 // This is used for validation while the user [may be] still typing.
13625 return this.textbox.value.search(this._partialre) == 0;
13626 },
13627
13628 isValid: function(/*Boolean*/ isFocused){
13629 // summary:
13630 // Tests if value is valid.
13631 // Can override with your own routine in a subclass.
13632 // tags:
13633 // protected
13634 return this.validator(this.textbox.value, this.constraints);
13635 },
13636
13637 _isEmpty: function(value){
13638 // summary:
13639 // Checks for whitespace
81bea17a 13640 return (this.trim ? /^\s*$/ : /^$/).test(value); // Boolean
a089699c
AD
13641 },
13642
13643 getErrorMessage: function(/*Boolean*/ isFocused){
13644 // summary:
13645 // Return an error message to show if appropriate
13646 // tags:
13647 // protected
13648 return (this.required && this._isEmpty(this.textbox.value)) ? this.missingMessage : this.invalidMessage; // String
13649 },
13650
13651 getPromptMessage: function(/*Boolean*/ isFocused){
13652 // summary:
13653 // Return a hint message to show when widget is first focused
13654 // tags:
13655 // protected
13656 return this.promptMessage; // String
13657 },
13658
13659 _maskValidSubsetError: true,
13660 validate: function(/*Boolean*/ isFocused){
13661 // summary:
13662 // Called by oninit, onblur, and onkeypress.
13663 // description:
13664 // Show missing or invalid messages if appropriate, and highlight textbox field.
13665 // tags:
13666 // protected
13667 var message = "";
13668 var isValid = this.disabled || this.isValid(isFocused);
13669 if(isValid){ this._maskValidSubsetError = true; }
13670 var isEmpty = this._isEmpty(this.textbox.value);
81bea17a
AD
13671 var isValidSubset = !isValid && isFocused && this._isValidSubset();
13672 this._set("state", isValid ? "" : (((((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && this._maskValidSubsetError) ? "Incomplete" : "Error"));
a089699c 13673 dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true");
81bea17a
AD
13674
13675 if(this.state == "Error"){
13676 this._maskValidSubsetError = isFocused && isValidSubset; // we want the error to show up after a blur and refocus
13677 message = this.getErrorMessage(isFocused);
13678 }else if(this.state == "Incomplete"){
13679 message = this.getPromptMessage(isFocused); // show the prompt whenever the value is not yet complete
13680 this._maskValidSubsetError = !this._hasBeenBlurred || isFocused; // no Incomplete warnings while focused
13681 }else if(isEmpty){
13682 message = this.getPromptMessage(isFocused); // show the prompt whenever there's no error and no text
a089699c 13683 }
81bea17a
AD
13684 this.set("message", message);
13685
a089699c
AD
13686 return isValid;
13687 },
13688
a089699c
AD
13689 displayMessage: function(/*String*/ message){
13690 // summary:
13691 // Overridable method to display validation errors/hints.
13692 // By default uses a tooltip.
13693 // tags:
13694 // extension
a089699c 13695 dijit.hideTooltip(this.domNode);
81bea17a 13696 if(message && this._focused){
a089699c
AD
13697 dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
13698 }
13699 },
13700
13701 _refreshState: function(){
13702 // Overrides TextBox._refreshState()
13703 this.validate(this._focused);
13704 this.inherited(arguments);
13705 },
13706
13707 //////////// INITIALIZATION METHODS ///////////////////////////////////////
13708
13709 constructor: function(){
13710 this.constraints = {};
13711 },
13712
81bea17a 13713 _setConstraintsAttr: function(/*Object*/ constraints){
a089699c
AD
13714 if(!constraints.locale && this.lang){
13715 constraints.locale = this.lang;
13716 }
81bea17a 13717 this._set("constraints", constraints);
a089699c
AD
13718 this._computePartialRE();
13719 },
13720
13721 _computePartialRE: function(){
13722 var p = this.regExpGen(this.constraints);
13723 this.regExp = p;
13724 var partialre = "";
13725 // parse the regexp and produce a new regexp that matches valid subsets
13726 // if the regexp is .* then there's no use in matching subsets since everything is valid
13727 if(p != ".*"){ this.regExp.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g,
13728 function (re){
13729 switch(re.charAt(0)){
13730 case '{':
13731 case '+':
13732 case '?':
13733 case '*':
13734 case '^':
13735 case '$':
13736 case '|':
13737 case '(':
13738 partialre += re;
13739 break;
13740 case ")":
13741 partialre += "|$)";
13742 break;
13743 default:
13744 partialre += "(?:"+re+"|$)";
13745 break;
13746 }
13747 }
13748 );}
13749 try{ // this is needed for now since the above regexp parsing needs more test verification
13750 "".search(partialre);
13751 }catch(e){ // should never be here unless the original RE is bad or the parsing is bad
13752 partialre = this.regExp;
13753 console.warn('RegExp error in ' + this.declaredClass + ': ' + this.regExp);
13754 } // should never be here unless the original RE is bad or the parsing is bad
13755 this._partialre = "^(?:" + partialre + ")$";
13756 },
13757
13758 postMixInProperties: function(){
13759 this.inherited(arguments);
13760 this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang);
13761 if(this.invalidMessage == "$_unset_$"){ this.invalidMessage = this.messages.invalidMessage; }
13762 if(!this.invalidMessage){ this.invalidMessage = this.promptMessage; }
13763 if(this.missingMessage == "$_unset_$"){ this.missingMessage = this.messages.missingMessage; }
13764 if(!this.missingMessage){ this.missingMessage = this.invalidMessage; }
13765 this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls attachPoints
13766 },
13767
13768 _setDisabledAttr: function(/*Boolean*/ value){
13769 this.inherited(arguments); // call FormValueWidget._setDisabledAttr()
13770 this._refreshState();
13771 },
13772
13773 _setRequiredAttr: function(/*Boolean*/ value){
81bea17a 13774 this._set("required", value);
a089699c
AD
13775 dijit.setWaiState(this.focusNode, "required", value);
13776 this._refreshState();
13777 },
13778
81bea17a
AD
13779 _setMessageAttr: function(/*String*/ message){
13780 this._set("message", message);
13781 this.displayMessage(message);
13782 },
13783
a089699c
AD
13784 reset:function(){
13785 // Overrides dijit.form.TextBox.reset() by also
13786 // hiding errors about partial matches
13787 this._maskValidSubsetError = true;
13788 this.inherited(arguments);
13789 },
13790
13791 _onBlur: function(){
81bea17a
AD
13792 // the message still exists but for back-compat, and to erase the tooltip
13793 // (if the message is being displayed as a tooltip), call displayMessage('')
a089699c 13794 this.displayMessage('');
81bea17a 13795
a089699c
AD
13796 this.inherited(arguments);
13797 }
13798 }
13799);
13800
13801dojo.declare(
13802 "dijit.form.MappedTextBox",
13803 dijit.form.ValidationTextBox,
13804 {
13805 // summary:
13806 // A dijit.form.ValidationTextBox subclass which provides a base class for widgets that have
13807 // a visible formatted display value, and a serializable
13808 // value in a hidden input field which is actually sent to the server.
13809 // description:
13810 // The visible display may
13811 // be locale-dependent and interactive. The value sent to the server is stored in a hidden
13812 // input field which uses the `name` attribute declared by the original widget. That value sent
13813 // to the server is defined by the dijit.form.MappedTextBox.serialize method and is typically
13814 // locale-neutral.
13815 // tags:
13816 // protected
13817
13818 postMixInProperties: function(){
13819 this.inherited(arguments);
13820
13821 // we want the name attribute to go to the hidden <input>, not the displayed <input>,
13822 // so override _FormWidget.postMixInProperties() setting of nameAttrSetting
13823 this.nameAttrSetting = "";
13824 },
13825
81bea17a 13826 serialize: function(/*anything*/ val, /*Object?*/ options){
a089699c 13827 // summary:
81bea17a 13828 // Overridable function used to convert the get('value') result to a canonical
a089699c
AD
13829 // (non-localized) string. For example, will print dates in ISO format, and
13830 // numbers the same way as they are represented in javascript.
13831 // tags:
13832 // protected extension
13833 return val.toString ? val.toString() : ""; // String
13834 },
13835
13836 toString: function(){
13837 // summary:
13838 // Returns widget as a printable string using the widget's value
13839 // tags:
13840 // protected
13841 var val = this.filter(this.get('value')); // call filter in case value is nonstring and filter has been customized
13842 return val != null ? (typeof val == "string" ? val : this.serialize(val, this.constraints)) : ""; // String
13843 },
13844
13845 validate: function(){
13846 // Overrides `dijit.form.TextBox.validate`
13847 this.valueNode.value = this.toString();
13848 return this.inherited(arguments);
13849 },
13850
13851 buildRendering: function(){
13852 // Overrides `dijit._Templated.buildRendering`
13853
13854 this.inherited(arguments);
13855
13856 // Create a hidden <input> node with the serialized value used for submit
13857 // (as opposed to the displayed value).
13858 // Passing in name as markup rather than calling dojo.create() with an attrs argument
13859 // to make dojo.query(input[name=...]) work on IE. (see #8660)
81bea17a 13860 this.valueNode = dojo.place("<input type='hidden'" + (this.name ? " name='" + this.name.replace(/'/g, "&quot;") + "'" : "") + "/>", this.textbox, "after");
a089699c
AD
13861 },
13862
81bea17a 13863 reset: function(){
a089699c
AD
13864 // Overrides `dijit.form.ValidationTextBox.reset` to
13865 // reset the hidden textbox value to ''
13866 this.valueNode.value = '';
13867 this.inherited(arguments);
13868 }
13869 }
13870);
13871
13872/*=====
13873 dijit.form.RangeBoundTextBox.__Constraints = function(){
13874 // min: Number
13875 // Minimum signed value. Default is -Infinity
13876 // max: Number
13877 // Maximum signed value. Default is +Infinity
13878 this.min = min;
13879 this.max = max;
13880 }
13881=====*/
13882
13883dojo.declare(
13884 "dijit.form.RangeBoundTextBox",
13885 dijit.form.MappedTextBox,
13886 {
13887 // summary:
13888 // Base class for textbox form widgets which defines a range of valid values.
13889
13890 // rangeMessage: String
13891 // The message to display if value is out-of-range
13892 rangeMessage: "",
13893
13894 /*=====
13895 // constraints: dijit.form.RangeBoundTextBox.__Constraints
13896 constraints: {},
13897 ======*/
13898
13899 rangeCheck: function(/*Number*/ primitive, /*dijit.form.RangeBoundTextBox.__Constraints*/ constraints){
13900 // summary:
13901 // Overridable function used to validate the range of the numeric input value.
13902 // tags:
13903 // protected
13904 return ("min" in constraints? (this.compare(primitive,constraints.min) >= 0) : true) &&
13905 ("max" in constraints? (this.compare(primitive,constraints.max) <= 0) : true); // Boolean
13906 },
13907
13908 isInRange: function(/*Boolean*/ isFocused){
13909 // summary:
13910 // Tests if the value is in the min/max range specified in constraints
13911 // tags:
13912 // protected
13913 return this.rangeCheck(this.get('value'), this.constraints);
13914 },
13915
13916 _isDefinitelyOutOfRange: function(){
13917 // summary:
13918 // Returns true if the value is out of range and will remain
13919 // out of range even if the user types more characters
13920 var val = this.get('value');
13921 var isTooLittle = false;
13922 var isTooMuch = false;
13923 if("min" in this.constraints){
13924 var min = this.constraints.min;
13925 min = this.compare(val, ((typeof min == "number") && min >= 0 && val !=0) ? 0 : min);
13926 isTooLittle = (typeof min == "number") && min < 0;
13927 }
13928 if("max" in this.constraints){
13929 var max = this.constraints.max;
13930 max = this.compare(val, ((typeof max != "number") || max > 0) ? max : 0);
13931 isTooMuch = (typeof max == "number") && max > 0;
13932 }
13933 return isTooLittle || isTooMuch;
13934 },
13935
13936 _isValidSubset: function(){
13937 // summary:
13938 // Overrides `dijit.form.ValidationTextBox._isValidSubset`.
13939 // Returns true if the input is syntactically valid, and either within
13940 // range or could be made in range by more typing.
13941 return this.inherited(arguments) && !this._isDefinitelyOutOfRange();
13942 },
13943
13944 isValid: function(/*Boolean*/ isFocused){
13945 // Overrides dijit.form.ValidationTextBox.isValid to check that the value is also in range.
13946 return this.inherited(arguments) &&
13947 ((this._isEmpty(this.textbox.value) && !this.required) || this.isInRange(isFocused)); // Boolean
13948 },
13949
13950 getErrorMessage: function(/*Boolean*/ isFocused){
13951 // Overrides dijit.form.ValidationTextBox.getErrorMessage to print "out of range" message if appropriate
13952 var v = this.get('value');
13953 if(v !== null && v !== '' && v !== undefined && (typeof v != "number" || !isNaN(v)) && !this.isInRange(isFocused)){ // don't check isInRange w/o a real value
13954 return this.rangeMessage; // String
13955 }
13956 return this.inherited(arguments);
13957 },
13958
13959 postMixInProperties: function(){
13960 this.inherited(arguments);
13961 if(!this.rangeMessage){
13962 this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang);
13963 this.rangeMessage = this.messages.rangeMessage;
13964 }
13965 },
13966
81bea17a 13967 _setConstraintsAttr: function(/*Object*/ constraints){
a089699c
AD
13968 this.inherited(arguments);
13969 if(this.focusNode){ // not set when called from postMixInProperties
13970 if(this.constraints.min !== undefined){
13971 dijit.setWaiState(this.focusNode, "valuemin", this.constraints.min);
13972 }else{
13973 dijit.removeWaiState(this.focusNode, "valuemin");
13974 }
13975 if(this.constraints.max !== undefined){
13976 dijit.setWaiState(this.focusNode, "valuemax", this.constraints.max);
13977 }else{
13978 dijit.removeWaiState(this.focusNode, "valuemax");
13979 }
13980 }
13981 },
13982
13983 _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){
13984 // summary:
81bea17a 13985 // Hook so set('value', ...) works.
a089699c
AD
13986
13987 dijit.setWaiState(this.focusNode, "valuenow", value);
13988 this.inherited(arguments);
13989 }
13990 }
13991);
13992
13993}
13994
13995if(!dojo._hasResource["dijit.form.ComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
13996dojo._hasResource["dijit.form.ComboBox"] = true;
13997dojo.provide("dijit.form.ComboBox");
13998
13999
14000
14001
14002
14003
14004
14005
14006
14007
14008
a089699c
AD
14009dojo.declare(
14010 "dijit.form.ComboBoxMixin",
81bea17a 14011 dijit._HasDropDown,
a089699c
AD
14012 {
14013 // summary:
14014 // Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect`
14015 // description:
14016 // All widgets that mix in dijit.form.ComboBoxMixin must extend `dijit.form._FormValueWidget`.
14017 // tags:
14018 // protected
14019
14020 // item: Object
14021 // This is the item returned by the dojo.data.store implementation that
14022 // provides the data for this ComboBox, it's the currently selected item.
14023 item: null,
14024
14025 // pageSize: Integer
14026 // Argument to data provider.
14027 // Specifies number of search results per page (before hitting "next" button)
14028 pageSize: Infinity,
14029
81bea17a 14030 // store: [const] Object
a089699c
AD
14031 // Reference to data provider object used by this ComboBox
14032 store: null,
14033
14034 // fetchProperties: Object
14035 // Mixin to the dojo.data store's fetch.
14036 // For example, to set the sort order of the ComboBox menu, pass:
81bea17a 14037 // | { sort: [{attribute:"name",descending: true}] }
a089699c
AD
14038 // To override the default queryOptions so that deep=false, do:
14039 // | { queryOptions: {ignoreCase: true, deep: false} }
14040 fetchProperties:{},
14041
14042 // query: Object
14043 // A query that can be passed to 'store' to initially filter the items,
14044 // before doing further filtering based on `searchAttr` and the key.
14045 // Any reference to the `searchAttr` is ignored.
14046 query: {},
14047
14048 // autoComplete: Boolean
14049 // If user types in a partial string, and then tab out of the `<input>` box,
14050 // automatically copy the first entry displayed in the drop down list to
14051 // the `<input>` field
14052 autoComplete: true,
14053
14054 // highlightMatch: String
14055 // One of: "first", "all" or "none".
14056 //
14057 // If the ComboBox/FilteringSelect opens with the search results and the searched
14058 // string can be found, it will be highlighted. If set to "all"
14059 // then will probably want to change `queryExpr` parameter to '*${0}*'
14060 //
14061 // Highlighting is only performed when `labelType` is "text", so as to not
14062 // interfere with any HTML markup an HTML label might contain.
14063 highlightMatch: "first",
14064
14065 // searchDelay: Integer
14066 // Delay in milliseconds between when user types something and we start
14067 // searching based on that value
14068 searchDelay: 100,
14069
14070 // searchAttr: String
14071 // Search for items in the data store where this attribute (in the item)
14072 // matches what the user typed
14073 searchAttr: "name",
14074
14075 // labelAttr: String?
14076 // The entries in the drop down list come from this attribute in the
14077 // dojo.data items.
14078 // If not specified, the searchAttr attribute is used instead.
14079 labelAttr: "",
14080
14081 // labelType: String
14082 // Specifies how to interpret the labelAttr in the data store items.
14083 // Can be "html" or "text".
14084 labelType: "text",
14085
14086 // queryExpr: String
14087 // This specifies what query ComboBox/FilteringSelect sends to the data store,
14088 // based on what the user has typed. Changing this expression will modify
14089 // whether the drop down shows only exact matches, a "starting with" match,
81bea17a 14090 // etc. Use it in conjunction with highlightMatch.
a089699c
AD
14091 // dojo.data query expression pattern.
14092 // `${0}` will be substituted for the user text.
14093 // `*` is used for wildcards.
14094 // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is"
14095 queryExpr: "${0}*",
14096
14097 // ignoreCase: Boolean
14098 // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items
14099 ignoreCase: true,
14100
81bea17a 14101 // hasDownArrow: Boolean
a089699c
AD
14102 // Set this textbox to have a down arrow button, to display the drop down list.
14103 // Defaults to true.
14104 hasDownArrow: true,
14105
81bea17a 14106 templateString: dojo.cache("dijit.form", "templates/DropDownBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"&#9660; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&#935; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n"),
a089699c
AD
14107
14108 baseClass: "dijitTextBox dijitComboBox",
14109
81bea17a
AD
14110 // dropDownClass: [protected extension] String
14111 // Name of the dropdown widget class used to select a date/time.
14112 // Subclasses should specify this.
14113 dropDownClass: "dijit.form._ComboBoxMenu",
14114
a089699c
AD
14115 // Set classes like dijitDownArrowButtonHover depending on
14116 // mouse action over button node
14117 cssStateNodes: {
81bea17a 14118 "_buttonNode": "dijitDownArrowButton"
a089699c
AD
14119 },
14120
81bea17a
AD
14121 // Flags to _HasDropDown to limit height of drop down to make it fit in viewport
14122 maxHeight: -1,
14123
14124 // For backwards compatibility let onClick events propagate, even clicks on the down arrow button
14125 _stopClickEvents: false,
14126
a089699c
AD
14127 _getCaretPos: function(/*DomNode*/ element){
14128 // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
14129 var pos = 0;
14130 if(typeof(element.selectionStart) == "number"){
14131 // FIXME: this is totally borked on Moz < 1.3. Any recourse?
14132 pos = element.selectionStart;
14133 }else if(dojo.isIE){
14134 // in the case of a mouse click in a popup being handled,
14135 // then the dojo.doc.selection is not the textarea, but the popup
14136 // var r = dojo.doc.selection.createRange();
14137 // hack to get IE 6 to play nice. What a POS browser.
14138 var tr = dojo.doc.selection.createRange().duplicate();
14139 var ntr = element.createTextRange();
14140 tr.move("character",0);
14141 ntr.move("character",0);
14142 try{
81bea17a 14143 // If control doesn't have focus, you get an exception.
a089699c
AD
14144 // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
14145 // There appears to be no workaround for this - googled for quite a while.
14146 ntr.setEndPoint("EndToEnd", tr);
14147 pos = String(ntr.text).replace(/\r/g,"").length;
14148 }catch(e){
14149 // If focus has shifted, 0 is fine for caret pos.
14150 }
14151 }
14152 return pos;
14153 },
14154
14155 _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
14156 location = parseInt(location);
14157 dijit.selectInputText(element, location, location);
14158 },
14159
14160 _setDisabledAttr: function(/*Boolean*/ value){
14161 // Additional code to set disabled state of ComboBox node.
14162 // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr().
14163 this.inherited(arguments);
81bea17a 14164 dijit.setWaiState(this.domNode, "disabled", value);
a089699c
AD
14165 },
14166
14167 _abortQuery: function(){
14168 // stop in-progress query
14169 if(this.searchTimer){
14170 clearTimeout(this.searchTimer);
14171 this.searchTimer = null;
14172 }
14173 if(this._fetchHandle){
14174 if(this._fetchHandle.abort){ this._fetchHandle.abort(); }
14175 this._fetchHandle = null;
14176 }
14177 },
14178
14179 _onInput: function(/*Event*/ evt){
14180 // summary:
14181 // Handles paste events
14182 if(!this.searchTimer && (evt.type == 'paste'/*IE|WebKit*/ || evt.type == 'input'/*Firefox*/) && this._lastInput != this.textbox.value){
14183 this.searchTimer = setTimeout(dojo.hitch(this, function(){
81bea17a 14184 this._onKey({charOrCode: 229}); // fake IME key to cause a search
a089699c
AD
14185 }), 100); // long delay that will probably be preempted by keyboard input
14186 }
14187 this.inherited(arguments);
14188 },
14189
81bea17a 14190 _onKey: function(/*Event*/ evt){
a089699c
AD
14191 // summary:
14192 // Handles keyboard events
81bea17a 14193
a089699c 14194 var key = evt.charOrCode;
81bea17a 14195
a089699c
AD
14196 // except for cutting/pasting case - ctrl + x/v
14197 if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == dojo.keys.SHIFT){
14198 return; // throw out weird key combinations and spurious events
14199 }
81bea17a 14200
a089699c 14201 var doSearch = false;
81bea17a 14202 var pw = this.dropDown;
a089699c
AD
14203 var dk = dojo.keys;
14204 var highlighted = null;
14205 this._prev_key_backspace = false;
14206 this._abortQuery();
81bea17a
AD
14207
14208 // _HasDropDown will do some of the work:
14209 // 1. when drop down is not yet shown:
14210 // - if user presses the down arrow key, call loadDropDown()
14211 // 2. when drop down is already displayed:
14212 // - on ESC key, call closeDropDown()
14213 // - otherwise, call dropDown.handleKey() to process the keystroke
14214 this.inherited(arguments);
14215
14216 if(this._opened){
a089699c
AD
14217 highlighted = pw.getHighlightedOption();
14218 }
14219 switch(key){
14220 case dk.PAGE_DOWN:
14221 case dk.DOWN_ARROW:
14222 case dk.PAGE_UP:
14223 case dk.UP_ARROW:
81bea17a
AD
14224 // Keystroke caused ComboBox_menu to move to a different item.
14225 // Copy new item to <input> box.
14226 if(this._opened){
a089699c
AD
14227 this._announceOption(highlighted);
14228 }
14229 dojo.stopEvent(evt);
14230 break;
14231
14232 case dk.ENTER:
14233 // prevent submitting form if user presses enter. Also
14234 // prevent accepting the value if either Next or Previous
14235 // are selected
14236 if(highlighted){
14237 // only stop event on prev/next
14238 if(highlighted == pw.nextButton){
14239 this._nextSearch(1);
14240 dojo.stopEvent(evt);
14241 break;
14242 }else if(highlighted == pw.previousButton){
14243 this._nextSearch(-1);
14244 dojo.stopEvent(evt);
14245 break;
14246 }
14247 }else{
14248 // Update 'value' (ex: KY) according to currently displayed text
14249 this._setBlurValue(); // set value if needed
14250 this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting
14251 }
14252 // default case:
81bea17a
AD
14253 // if enter pressed while drop down is open, or for FilteringSelect,
14254 // if we are in the middle of a query to convert a directly typed in value to an item,
a089699c 14255 // prevent submit, but allow event to bubble
81bea17a 14256 if(this._opened || this._fetchHandle){
a089699c 14257 evt.preventDefault();
81bea17a 14258 }
a089699c
AD
14259 // fall through
14260
14261 case dk.TAB:
14262 var newvalue = this.get('displayedValue');
14263 // if the user had More Choices selected fall into the
14264 // _onBlur handler
14265 if(pw && (
14266 newvalue == pw._messages["previousMessage"] ||
14267 newvalue == pw._messages["nextMessage"])
14268 ){
14269 break;
14270 }
14271 if(highlighted){
14272 this._selectOption();
14273 }
81bea17a 14274 if(this._opened){
a089699c 14275 this._lastQuery = null; // in case results come back later
81bea17a 14276 this.closeDropDown();
a089699c
AD
14277 }
14278 break;
14279
14280 case ' ':
14281 if(highlighted){
81bea17a 14282 // user is effectively clicking a choice in the drop down menu
a089699c
AD
14283 dojo.stopEvent(evt);
14284 this._selectOption();
81bea17a 14285 this.closeDropDown();
a089699c 14286 }else{
81bea17a 14287 // user typed a space into the input box, treat as normal character
a089699c
AD
14288 doSearch = true;
14289 }
14290 break;
14291
a089699c
AD
14292 case dk.DELETE:
14293 case dk.BACKSPACE:
14294 this._prev_key_backspace = true;
14295 doSearch = true;
14296 break;
14297
14298 default:
14299 // Non char keys (F1-F12 etc..) shouldn't open list.
14300 // Ascii characters and IME input (Chinese, Japanese etc.) should.
81bea17a 14301 //IME input produces keycode == 229.
a089699c
AD
14302 doSearch = typeof key == 'string' || key == 229;
14303 }
14304 if(doSearch){
14305 // need to wait a tad before start search so that the event
14306 // bubbles through DOM and we have value visible
14307 this.item = undefined; // undefined means item needs to be set
81bea17a 14308 this.searchTimer = setTimeout(dojo.hitch(this, "_startSearchFromInput"),1);
a089699c
AD
14309 }
14310 },
14311
14312 _autoCompleteText: function(/*String*/ text){
14313 // summary:
14314 // Fill in the textbox with the first item from the drop down
14315 // list, and highlight the characters that were
14316 // auto-completed. For example, if user typed "CA" and the
14317 // drop down list appeared, the textbox would be changed to
14318 // "California" and "ifornia" would be highlighted.
14319
14320 var fn = this.focusNode;
14321
14322 // IE7: clear selection so next highlight works all the time
14323 dijit.selectInputText(fn, fn.value.length);
14324 // does text autoComplete the value in the textbox?
14325 var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr';
14326 if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){
14327 var cpos = this._getCaretPos(fn);
14328 // only try to extend if we added the last character at the end of the input
14329 if((cpos+1) > fn.value.length){
14330 // only add to input node as we would overwrite Capitalisation of chars
14331 // actually, that is ok
14332 fn.value = text;//.substr(cpos);
14333 // visually highlight the autocompleted characters
14334 dijit.selectInputText(fn, cpos);
14335 }
14336 }else{
14337 // text does not autoComplete; replace the whole value and highlight
14338 fn.value = text;
14339 dijit.selectInputText(fn);
14340 }
14341 },
14342
14343 _openResultList: function(/*Object*/ results, /*Object*/ dataObject){
81bea17a
AD
14344 // summary:
14345 // Callback when a search completes.
14346 // description:
14347 // 1. generates drop-down list and calls _showResultList() to display it
14348 // 2. if this result list is from user pressing "more choices"/"previous choices"
14349 // then tell screen reader to announce new option
a089699c
AD
14350 this._fetchHandle = null;
14351 if( this.disabled ||
14352 this.readOnly ||
14353 (dataObject.query[this.searchAttr] != this._lastQuery)
14354 ){
14355 return;
14356 }
81bea17a
AD
14357 var wasSelected = this.dropDown._highlighted_option && dojo.hasClass(this.dropDown._highlighted_option, "dijitMenuItemSelected");
14358 this.dropDown.clearResultList();
14359 if(!results.length && !this._maxOptions){ // if no results and not just the previous choices button
14360 this.closeDropDown();
a089699c
AD
14361 return;
14362 }
14363
a089699c
AD
14364 // Fill in the textbox with the first item from the drop down list,
14365 // and highlight the characters that were auto-completed. For
14366 // example, if user typed "CA" and the drop down list appeared, the
14367 // textbox would be changed to "California" and "ifornia" would be
14368 // highlighted.
14369
14370 dataObject._maxOptions = this._maxOptions;
81bea17a 14371 var nodes = this.dropDown.createOptions(
a089699c
AD
14372 results,
14373 dataObject,
14374 dojo.hitch(this, "_getMenuLabelFromItem")
14375 );
14376
14377 // show our list (only if we have content, else nothing)
14378 this._showResultList();
14379
14380 // #4091:
14381 // tell the screen reader that the paging callback finished by
14382 // shouting the next choice
14383 if(dataObject.direction){
14384 if(1 == dataObject.direction){
81bea17a 14385 this.dropDown.highlightFirstOption();
a089699c 14386 }else if(-1 == dataObject.direction){
81bea17a
AD
14387 this.dropDown.highlightLastOption();
14388 }
14389 if(wasSelected){
14390 this._announceOption(this.dropDown.getHighlightedOption());
a089699c 14391 }
81bea17a 14392 }else if(this.autoComplete && !this._prev_key_backspace
a089699c
AD
14393 // when the user clicks the arrow button to show the full list,
14394 // startSearch looks for "*".
14395 // it does not make sense to autocomplete
14396 // if they are just previewing the options available.
14397 && !/^[*]+$/.test(dataObject.query[this.searchAttr])){
14398 this._announceOption(nodes[1]); // 1st real item
14399 }
14400 },
14401
14402 _showResultList: function(){
81bea17a
AD
14403 // summary:
14404 // Display the drop down if not already displayed, or if it is displayed, then
14405 // reposition it if necessary (reposition may be necessary if drop down's height changed).
14406
14407 this.closeDropDown(true);
14408
a089699c
AD
14409 // hide the tooltip
14410 this.displayMessage("");
14411
81bea17a
AD
14412 this.openDropDown();
14413
14414 dijit.setWaiState(this.domNode, "expanded", "true");
14415 },
14416
14417 loadDropDown: function(/*Function*/ callback){
14418 // Overrides _HasDropDown.loadDropDown().
14419 // This is called when user has pressed button icon or pressed the down arrow key
14420 // to open the drop down.
a089699c 14421
81bea17a
AD
14422 this._startSearchAll();
14423 },
a089699c 14424
81bea17a
AD
14425 isLoaded: function(){
14426 // signal to _HasDropDown that it needs to call loadDropDown() to load the
14427 // drop down asynchronously before displaying it
14428 return false;
a089699c
AD
14429 },
14430
81bea17a
AD
14431 closeDropDown: function(){
14432 // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open).
14433 // This method is the callback when the user types ESC or clicking
14434 // the button icon while the drop down is open. It's also called by other code.
a089699c 14435 this._abortQuery();
81bea17a
AD
14436 if(this._opened){
14437 this.inherited(arguments);
14438 dijit.setWaiState(this.domNode, "expanded", "false");
a089699c
AD
14439 dijit.removeWaiState(this.focusNode,"activedescendant");
14440 }
14441 },
14442
14443 _setBlurValue: function(){
14444 // if the user clicks away from the textbox OR tabs away, set the
14445 // value to the textbox value
14446 // #4617:
14447 // if value is now more choices or previous choices, revert
14448 // the value
14449 var newvalue = this.get('displayedValue');
81bea17a 14450 var pw = this.dropDown;
a089699c
AD
14451 if(pw && (
14452 newvalue == pw._messages["previousMessage"] ||
14453 newvalue == pw._messages["nextMessage"]
14454 )
14455 ){
14456 this._setValueAttr(this._lastValueReported, true);
14457 }else if(typeof this.item == "undefined"){
14458 // Update 'value' (ex: KY) according to currently displayed text
14459 this.item = null;
14460 this.set('displayedValue', newvalue);
14461 }else{
14462 if(this.value != this._lastValueReported){
14463 dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true);
14464 }
14465 this._refreshState();
14466 }
14467 },
14468
14469 _onBlur: function(){
14470 // summary:
14471 // Called magically when focus has shifted away from this widget and it's drop down
81bea17a 14472 this.closeDropDown();
a089699c
AD
14473 this.inherited(arguments);
14474 },
14475
14476 _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
14477 // summary:
81bea17a
AD
14478 // Set the displayed valued in the input box, and the hidden value
14479 // that gets submitted, based on a dojo.data store item.
a089699c 14480 // description:
81bea17a
AD
14481 // Users shouldn't call this function; they should be calling
14482 // set('item', value)
a089699c 14483 // tags:
81bea17a
AD
14484 // private
14485 if(!displayedValue){
14486 displayedValue = this.store.getValue(item, this.searchAttr);
14487 }
14488 var value = this._getValueField() != this.searchAttr? this.store.getIdentity(item) : displayedValue;
14489 this._set("item", item);
14490 dijit.form.ComboBox.superclass._setValueAttr.call(this, value, priorityChange, displayedValue);
a089699c
AD
14491 },
14492
14493 _announceOption: function(/*Node*/ node){
14494 // summary:
14495 // a11y code that puts the highlighted option in the textbox.
14496 // This way screen readers will know what is happening in the
14497 // menu.
14498
14499 if(!node){
14500 return;
14501 }
14502 // pull the text value from the item attached to the DOM node
14503 var newValue;
81bea17a
AD
14504 if(node == this.dropDown.nextButton ||
14505 node == this.dropDown.previousButton){
a089699c
AD
14506 newValue = node.innerHTML;
14507 this.item = undefined;
14508 this.value = '';
14509 }else{
81bea17a 14510 newValue = this.store.getValue(node.item, this.searchAttr).toString();
a089699c
AD
14511 this.set('item', node.item, false, newValue);
14512 }
14513 // get the text that the user manually entered (cut off autocompleted text)
14514 this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length);
14515 // set up ARIA activedescendant
14516 dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id"));
14517 // autocomplete the rest of the option to announce change
14518 this._autoCompleteText(newValue);
14519 },
14520
14521 _selectOption: function(/*Event*/ evt){
14522 // summary:
14523 // Menu callback function, called when an item in the menu is selected.
14524 if(evt){
14525 this._announceOption(evt.target);
14526 }
81bea17a 14527 this.closeDropDown();
a089699c
AD
14528 this._setCaretPos(this.focusNode, this.focusNode.value.length);
14529 dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); // set this.value and fire onChange
14530 },
14531
a089699c
AD
14532 _startSearchAll: function(){
14533 this._startSearch('');
14534 },
14535
14536 _startSearchFromInput: function(){
14537 this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1"));
14538 },
14539
14540 _getQueryString: function(/*String*/ text){
14541 return dojo.string.substitute(this.queryExpr, [text]);
14542 },
14543
14544 _startSearch: function(/*String*/ key){
81bea17a
AD
14545 // summary:
14546 // Starts a search for elements matching key (key=="" means to return all items),
14547 // and calls _openResultList() when the search completes, to display the results.
14548 if(!this.dropDown){
14549 var popupId = this.id + "_popup",
14550 dropDownConstructor = dojo.getObject(this.dropDownClass, false);
14551 this.dropDown = new dropDownConstructor({
a089699c
AD
14552 onChange: dojo.hitch(this, this._selectOption),
14553 id: popupId,
14554 dir: this.dir
14555 });
14556 dijit.removeWaiState(this.focusNode,"activedescendant");
14557 dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox
14558 }
14559 // create a new query to prevent accidentally querying for a hidden
14560 // value from FilteringSelect's keyField
14561 var query = dojo.clone(this.query); // #5970
14562 this._lastInput = key; // Store exactly what was entered by the user.
14563 this._lastQuery = query[this.searchAttr] = this._getQueryString(key);
14564 // #5970: set _lastQuery, *then* start the timeout
14565 // otherwise, if the user types and the last query returns before the timeout,
14566 // _lastQuery won't be set and their input gets rewritten
14567 this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){
14568 this.searchTimer = null;
14569 var fetch = {
14570 queryOptions: {
14571 ignoreCase: this.ignoreCase,
14572 deep: true
14573 },
14574 query: query,
14575 onBegin: dojo.hitch(this, "_setMaxOptions"),
14576 onComplete: dojo.hitch(this, "_openResultList"),
14577 onError: function(errText){
14578 _this._fetchHandle = null;
14579 console.error('dijit.form.ComboBox: ' + errText);
81bea17a 14580 _this.closeDropDown();
a089699c
AD
14581 },
14582 start: 0,
14583 count: this.pageSize
14584 };
14585 dojo.mixin(fetch, _this.fetchProperties);
14586 this._fetchHandle = _this.store.fetch(fetch);
14587
14588 var nextSearch = function(dataObject, direction){
14589 dataObject.start += dataObject.count*direction;
14590 // #4091:
14591 // tell callback the direction of the paging so the screen
14592 // reader knows which menu option to shout
14593 dataObject.direction = direction;
14594 this._fetchHandle = this.store.fetch(dataObject);
81bea17a 14595 this.focus();
a089699c 14596 };
81bea17a 14597 this._nextSearch = this.dropDown.onPage = dojo.hitch(this, nextSearch, this._fetchHandle);
a089699c
AD
14598 }, query, this), this.searchDelay);
14599 },
14600
14601 _setMaxOptions: function(size, request){
14602 this._maxOptions = size;
14603 },
14604
14605 _getValueField: function(){
81bea17a 14606 // summary:
a089699c
AD
14607 // Helper for postMixInProperties() to set this.value based on data inlined into the markup.
14608 // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value.
14609 return this.searchAttr;
14610 },
14611
a089699c
AD
14612 //////////// INITIALIZATION METHODS ///////////////////////////////////////
14613
14614 constructor: function(){
14615 this.query={};
14616 this.fetchProperties={};
14617 },
14618
14619 postMixInProperties: function(){
14620 if(!this.store){
14621 var srcNodeRef = this.srcNodeRef;
14622
14623 // if user didn't specify store, then assume there are option tags
14624 this.store = new dijit.form._ComboBoxDataStore(srcNodeRef);
14625
14626 // if there is no value set and there is an option list, set
14627 // the value to the first value to be consistent with native
14628 // Select
14629
14630 // Firefox and Safari set value
14631 // IE6 and Opera set selectedIndex, which is automatically set
14632 // by the selected attribute of an option tag
14633 // IE6 does not set value, Opera sets value = selectedIndex
14634 if(!("value" in this.params)){
81bea17a 14635 var item = (this.item = this.store.fetchSelectedItem());
a089699c
AD
14636 if(item){
14637 var valueField = this._getValueField();
81bea17a 14638 this.value = this.store.getValue(item, valueField);
a089699c
AD
14639 }
14640 }
14641 }
81bea17a 14642
a089699c
AD
14643 this.inherited(arguments);
14644 },
14645
14646 postCreate: function(){
14647 // summary:
14648 // Subclasses must call this method from their postCreate() methods
14649 // tags:
14650 // protected
14651
a089699c
AD
14652 // find any associated label element and add to ComboBox node.
14653 var label=dojo.query('label[for="'+this.id+'"]');
14654 if(label.length){
14655 label[0].id = (this.id+"_label");
81bea17a 14656 dijit.setWaiState(this.domNode, "labelledby", label[0].id);
a089699c
AD
14657
14658 }
14659 this.inherited(arguments);
14660 },
14661
81bea17a
AD
14662 _setHasDownArrowAttr: function(val){
14663 this.hasDownArrow = val;
14664 this._buttonNode.style.display = val ? "" : "none";
a089699c
AD
14665 },
14666
14667 _getMenuLabelFromItem: function(/*Item*/ item){
81bea17a
AD
14668 var label = this.labelFunc(item, this.store),
14669 labelType = this.labelType;
a089699c
AD
14670 // If labelType is not "text" we don't want to screw any markup ot whatever.
14671 if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){
14672 label = this.doHighlight(label, this._escapeHtml(this._lastInput));
14673 labelType = "html";
14674 }
14675 return {html: labelType == "html", label: label};
14676 },
14677
81bea17a 14678 doHighlight: function(/*String*/ label, /*String*/ find){
a089699c
AD
14679 // summary:
14680 // Highlights the string entered by the user in the menu. By default this
81bea17a
AD
14681 // highlights the first occurrence found. Override this method
14682 // to implement your custom highlighting.
a089699c
AD
14683 // tags:
14684 // protected
14685
81bea17a
AD
14686 var
14687 // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true
14688 modifiers = (this.ignoreCase ? "i" : "") + (this.highlightMatch == "all" ? "g" : ""),
14689 i = this.queryExpr.indexOf("${0}");
a089699c 14690 find = dojo.regexp.escapeString(find); // escape regexp special chars
81bea17a
AD
14691 return this._escapeHtml(label).replace(
14692 // prepend ^ when this.queryExpr == "${0}*" and append $ when this.queryExpr == "*${0}"
14693 new RegExp((i == 0 ? "^" : "") + "("+ find +")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers),
14694 '<span class="dijitComboBoxHighlightMatch">$1</span>'
14695 ); // returns String, (almost) valid HTML (entities encoded)
a089699c
AD
14696 },
14697
81bea17a 14698 _escapeHtml: function(/*String*/ str){
a089699c
AD
14699 // TODO Should become dojo.html.entities(), when exists use instead
14700 // summary:
14701 // Adds escape sequences for special characters in XML: &<>"'
14702 str = String(str).replace(/&/gm, "&amp;").replace(/</gm, "&lt;")
14703 .replace(/>/gm, "&gt;").replace(/"/gm, "&quot;");
14704 return str; // string
14705 },
14706
a089699c
AD
14707 reset: function(){
14708 // Overrides the _FormWidget.reset().
14709 // Additionally reset the .item (to clean up).
14710 this.item = null;
14711 this.inherited(arguments);
14712 },
14713
14714 labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){
14715 // summary:
81bea17a 14716 // Computes the label to display based on the dojo.data store item.
a089699c 14717 // returns:
81bea17a 14718 // The label that the ComboBox should display
a089699c 14719 // tags:
81bea17a 14720 // private
a089699c
AD
14721
14722 // Use toString() because XMLStore returns an XMLItem whereas this
14723 // method is expected to return a String (#9354)
81bea17a 14724 return store.getValue(item, this.labelAttr || this.searchAttr).toString(); // String
a089699c
AD
14725 }
14726 }
14727);
14728
14729dojo.declare(
14730 "dijit.form._ComboBoxMenu",
14731 [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
14732 {
14733 // summary:
14734 // Focus-less menu for internal use in `dijit.form.ComboBox`
14735 // tags:
14736 // private
14737
81bea17a
AD
14738 templateString: "<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' style='overflow: \"auto\"; overflow-x: \"hidden\";'>"
14739 +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' role='option'></li>"
14740 +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' role='option'></li>"
a089699c
AD
14741 +"</ul>",
14742
14743 // _messages: Object
14744 // Holds "next" and "previous" text for paging buttons on drop down
14745 _messages: null,
14746
14747 baseClass: "dijitComboBoxMenu",
14748
14749 postMixInProperties: function(){
81bea17a 14750 this.inherited(arguments);
a089699c 14751 this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang);
81bea17a
AD
14752 },
14753
14754 buildRendering: function(){
a089699c 14755 this.inherited(arguments);
81bea17a
AD
14756
14757 // fill in template with i18n messages
14758 this.previousButton.innerHTML = this._messages["previousMessage"];
14759 this.nextButton.innerHTML = this._messages["nextMessage"];
a089699c
AD
14760 },
14761
14762 _setValueAttr: function(/*Object*/ value){
14763 this.value = value;
14764 this.onChange(value);
14765 },
14766
14767 // stubs
14768 onChange: function(/*Object*/ value){
14769 // summary:
14770 // Notifies ComboBox/FilteringSelect that user clicked an option in the drop down menu.
14771 // Probably should be called onSelect.
14772 // tags:
14773 // callback
14774 },
14775 onPage: function(/*Number*/ direction){
14776 // summary:
14777 // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page.
14778 // tags:
14779 // callback
14780 },
14781
a089699c
AD
14782 onClose: function(){
14783 // summary:
14784 // Callback from dijit.popup code to this widget, notifying it that it closed
14785 // tags:
14786 // private
14787 this._blurOptionNode();
14788 },
14789
14790 _createOption: function(/*Object*/ item, labelFunc){
14791 // summary:
14792 // Creates an option to appear on the popup menu subclassed by
14793 // `dijit.form.FilteringSelect`.
14794
81bea17a
AD
14795 var menuitem = dojo.create("li", {
14796 "class": "dijitReset dijitMenuItem" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl"),
14797 role: "option"
14798 });
a089699c 14799 var labelObject = labelFunc(item);
a089699c
AD
14800 if(labelObject.html){
14801 menuitem.innerHTML = labelObject.label;
14802 }else{
14803 menuitem.appendChild(
14804 dojo.doc.createTextNode(labelObject.label)
14805 );
14806 }
14807 // #3250: in blank options, assign a normal height
14808 if(menuitem.innerHTML == ""){
14809 menuitem.innerHTML = "&nbsp;";
14810 }
14811 menuitem.item=item;
14812 return menuitem;
14813 },
14814
14815 createOptions: function(results, dataObject, labelFunc){
14816 // summary:
14817 // Fills in the items in the drop down list
14818 // results:
14819 // Array of dojo.data items
14820 // dataObject:
14821 // dojo.data store
14822 // labelFunc:
14823 // Function to produce a label in the drop down list from a dojo.data item
14824
14825 //this._dataObject=dataObject;
14826 //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList);
14827 // display "Previous . . ." button
14828 this.previousButton.style.display = (dataObject.start == 0) ? "none" : "";
14829 dojo.attr(this.previousButton, "id", this.id + "_prev");
14830 // create options using _createOption function defined by parent
14831 // ComboBox (or FilteringSelect) class
14832 // #2309:
14833 // iterate over cache nondestructively
14834 dojo.forEach(results, function(item, i){
14835 var menuitem = this._createOption(item, labelFunc);
a089699c
AD
14836 dojo.attr(menuitem, "id", this.id + i);
14837 this.domNode.insertBefore(menuitem, this.nextButton);
14838 }, this);
14839 // display "Next . . ." button
14840 var displayMore = false;
14841 //Try to determine if we should show 'more'...
14842 if(dataObject._maxOptions && dataObject._maxOptions != -1){
14843 if((dataObject.start + dataObject.count) < dataObject._maxOptions){
14844 displayMore = true;
14845 }else if((dataObject.start + dataObject.count) > dataObject._maxOptions && dataObject.count == results.length){
14846 //Weird return from a datastore, where a start + count > maxOptions
14847 // implies maxOptions isn't really valid and we have to go into faking it.
14848 //And more or less assume more if count == results.length
14849 displayMore = true;
14850 }
14851 }else if(dataObject.count == results.length){
14852 //Don't know the size, so we do the best we can based off count alone.
14853 //So, if we have an exact match to count, assume more.
14854 displayMore = true;
14855 }
14856
14857 this.nextButton.style.display = displayMore ? "" : "none";
14858 dojo.attr(this.nextButton,"id", this.id + "_next");
14859 return this.domNode.childNodes;
14860 },
14861
14862 clearResultList: function(){
14863 // summary:
14864 // Clears the entries in the drop down list, but of course keeps the previous and next buttons.
14865 while(this.domNode.childNodes.length>2){
14866 this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]);
14867 }
81bea17a 14868 this._blurOptionNode();
a089699c
AD
14869 },
14870
14871 _onMouseDown: function(/*Event*/ evt){
14872 dojo.stopEvent(evt);
14873 },
14874
14875 _onMouseUp: function(/*Event*/ evt){
14876 if(evt.target === this.domNode || !this._highlighted_option){
81bea17a
AD
14877 // !this._highlighted_option check to prevent immediate selection when menu appears on top
14878 // of <input>, see #9898. Note that _HasDropDown also has code to prevent this.
a089699c
AD
14879 return;
14880 }else if(evt.target == this.previousButton){
81bea17a 14881 this._blurOptionNode();
a089699c
AD
14882 this.onPage(-1);
14883 }else if(evt.target == this.nextButton){
81bea17a 14884 this._blurOptionNode();
a089699c
AD
14885 this.onPage(1);
14886 }else{
14887 var tgt = evt.target;
14888 // while the clicked node is inside the div
14889 while(!tgt.item){
14890 // recurse to the top
14891 tgt = tgt.parentNode;
14892 }
14893 this._setValueAttr({ target: tgt }, true);
14894 }
14895 },
14896
14897 _onMouseOver: function(/*Event*/ evt){
14898 if(evt.target === this.domNode){ return; }
14899 var tgt = evt.target;
14900 if(!(tgt == this.previousButton || tgt == this.nextButton)){
14901 // while the clicked node is inside the div
14902 while(!tgt.item){
14903 // recurse to the top
14904 tgt = tgt.parentNode;
14905 }
14906 }
14907 this._focusOptionNode(tgt);
14908 },
14909
14910 _onMouseOut: function(/*Event*/ evt){
14911 if(evt.target === this.domNode){ return; }
14912 this._blurOptionNode();
14913 },
14914
14915 _focusOptionNode: function(/*DomNode*/ node){
14916 // summary:
14917 // Does the actual highlight.
14918 if(this._highlighted_option != node){
14919 this._blurOptionNode();
14920 this._highlighted_option = node;
14921 dojo.addClass(this._highlighted_option, "dijitMenuItemSelected");
14922 }
14923 },
14924
14925 _blurOptionNode: function(){
14926 // summary:
14927 // Removes highlight on highlighted option.
14928 if(this._highlighted_option){
14929 dojo.removeClass(this._highlighted_option, "dijitMenuItemSelected");
14930 this._highlighted_option = null;
14931 }
14932 },
14933
14934 _highlightNextOption: function(){
14935 // summary:
14936 // Highlight the item just below the current selection.
14937 // If nothing selected, highlight first option.
14938
14939 // because each press of a button clears the menu,
14940 // the highlighted option sometimes becomes detached from the menu!
14941 // test to see if the option has a parent to see if this is the case.
14942 if(!this.getHighlightedOption()){
14943 var fc = this.domNode.firstChild;
14944 this._focusOptionNode(fc.style.display == "none" ? fc.nextSibling : fc);
14945 }else{
14946 var ns = this._highlighted_option.nextSibling;
14947 if(ns && ns.style.display != "none"){
14948 this._focusOptionNode(ns);
14949 }else{
14950 this.highlightFirstOption();
14951 }
14952 }
14953 // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover
14954 dojo.window.scrollIntoView(this._highlighted_option);
14955 },
14956
14957 highlightFirstOption: function(){
14958 // summary:
14959 // Highlight the first real item in the list (not Previous Choices).
14960 var first = this.domNode.firstChild;
14961 var second = first.nextSibling;
14962 this._focusOptionNode(second.style.display == "none" ? first : second); // remotely possible that Previous Choices is the only thing in the list
14963 dojo.window.scrollIntoView(this._highlighted_option);
14964 },
14965
14966 highlightLastOption: function(){
14967 // summary:
14968 // Highlight the last real item in the list (not More Choices).
14969 this._focusOptionNode(this.domNode.lastChild.previousSibling);
14970 dojo.window.scrollIntoView(this._highlighted_option);
14971 },
14972
14973 _highlightPrevOption: function(){
14974 // summary:
14975 // Highlight the item just above the current selection.
14976 // If nothing selected, highlight last option (if
14977 // you select Previous and try to keep scrolling up the list).
14978 if(!this.getHighlightedOption()){
14979 var lc = this.domNode.lastChild;
14980 this._focusOptionNode(lc.style.display == "none" ? lc.previousSibling : lc);
14981 }else{
14982 var ps = this._highlighted_option.previousSibling;
14983 if(ps && ps.style.display != "none"){
14984 this._focusOptionNode(ps);
14985 }else{
14986 this.highlightLastOption();
14987 }
14988 }
14989 dojo.window.scrollIntoView(this._highlighted_option);
14990 },
14991
14992 _page: function(/*Boolean*/ up){
14993 // summary:
14994 // Handles page-up and page-down keypresses
14995
14996 var scrollamount = 0;
14997 var oldscroll = this.domNode.scrollTop;
14998 var height = dojo.style(this.domNode, "height");
14999 // if no item is highlighted, highlight the first option
15000 if(!this.getHighlightedOption()){
15001 this._highlightNextOption();
15002 }
15003 while(scrollamount<height){
15004 if(up){
15005 // stop at option 1
15006 if(!this.getHighlightedOption().previousSibling ||
15007 this._highlighted_option.previousSibling.style.display == "none"){
15008 break;
15009 }
15010 this._highlightPrevOption();
15011 }else{
15012 // stop at last option
15013 if(!this.getHighlightedOption().nextSibling ||
15014 this._highlighted_option.nextSibling.style.display == "none"){
15015 break;
15016 }
15017 this._highlightNextOption();
15018 }
15019 // going backwards
15020 var newscroll=this.domNode.scrollTop;
15021 scrollamount+=(newscroll-oldscroll)*(up ? -1:1);
15022 oldscroll=newscroll;
15023 }
15024 },
15025
15026 pageUp: function(){
15027 // summary:
15028 // Handles pageup keypress.
15029 // TODO: just call _page directly from handleKey().
15030 // tags:
15031 // private
15032 this._page(true);
15033 },
15034
15035 pageDown: function(){
15036 // summary:
15037 // Handles pagedown keypress.
15038 // TODO: just call _page directly from handleKey().
15039 // tags:
15040 // private
15041 this._page(false);
15042 },
15043
15044 getHighlightedOption: function(){
15045 // summary:
15046 // Returns the highlighted option.
15047 var ho = this._highlighted_option;
15048 return (ho && ho.parentNode) ? ho : null;
15049 },
15050
81bea17a
AD
15051 handleKey: function(evt){
15052 // summary:
15053 // Handle keystroke event forwarded from ComboBox, returning false if it's
15054 // a keystroke I recognize and process, true otherwise.
15055 switch(evt.charOrCode){
a089699c
AD
15056 case dojo.keys.DOWN_ARROW:
15057 this._highlightNextOption();
81bea17a 15058 return false;
a089699c
AD
15059 case dojo.keys.PAGE_DOWN:
15060 this.pageDown();
81bea17a 15061 return false;
a089699c
AD
15062 case dojo.keys.UP_ARROW:
15063 this._highlightPrevOption();
81bea17a 15064 return false;
a089699c
AD
15065 case dojo.keys.PAGE_UP:
15066 this.pageUp();
81bea17a
AD
15067 return false;
15068 default:
15069 return true;
a089699c
AD
15070 }
15071 }
15072 }
15073);
15074
15075dojo.declare(
15076 "dijit.form.ComboBox",
15077 [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin],
15078 {
15079 // summary:
15080 // Auto-completing text box, and base class for dijit.form.FilteringSelect.
15081 //
15082 // description:
15083 // The drop down box's values are populated from an class called
15084 // a data provider, which returns a list of values based on the characters
15085 // that the user has typed into the input box.
15086 // If OPTION tags are used as the data provider via markup,
15087 // then the OPTION tag's child text node is used as the widget value
15088 // when selected. The OPTION tag's value attribute is ignored.
15089 // To set the default value when using OPTION tags, specify the selected
15090 // attribute on 1 of the child OPTION tags.
15091 //
15092 // Some of the options to the ComboBox are actually arguments to the data
15093 // provider.
15094
15095 _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
15096 // summary:
81bea17a 15097 // Hook so set('value', value) works.
a089699c
AD
15098 // description:
15099 // Sets the value of the select.
81bea17a 15100 this._set("item", null); // value not looked up in store
a089699c
AD
15101 if(!value){ value = ''; } // null translates to blank
15102 dijit.form.ValidationTextBox.prototype._setValueAttr.call(this, value, priorityChange, displayedValue);
15103 }
15104 }
15105);
15106
15107dojo.declare("dijit.form._ComboBoxDataStore", null, {
15108 // summary:
15109 // Inefficient but small data store specialized for inlined `dijit.form.ComboBox` data
15110 //
15111 // description:
15112 // Provides a store for inlined data like:
15113 //
15114 // | <select>
15115 // | <option value="AL">Alabama</option>
15116 // | ...
15117 //
15118 // Actually. just implements the subset of dojo.data.Read/Notification
15119 // needed for ComboBox and FilteringSelect to work.
15120 //
15121 // Note that an item is just a pointer to the <option> DomNode.
15122
15123 constructor: function( /*DomNode*/ root){
15124 this.root = root;
15125 if(root.tagName != "SELECT" && root.firstChild){
15126 root = dojo.query("select", root);
15127 if(root.length > 0){ // SELECT is a child of srcNodeRef
15128 root = root[0];
15129 }else{ // no select, so create 1 to parent the option tags to define selectedIndex
15130 this.root.innerHTML = "<SELECT>"+this.root.innerHTML+"</SELECT>";
15131 root = this.root.firstChild;
15132 }
15133 this.root = root;
15134 }
15135 dojo.query("> option", root).forEach(function(node){
15136 // TODO: this was added in #3858 but unclear why/if it's needed; doesn't seem to be.
15137 // If it is needed then can we just hide the select itself instead?
15138 //node.style.display="none";
15139 node.innerHTML = dojo.trim(node.innerHTML);
15140 });
15141
15142 },
15143
81bea17a
AD
15144 getValue: function( /*item*/ item,
15145 /*attribute-name-string*/ attribute,
15146 /*value?*/ defaultValue){
a089699c
AD
15147 return (attribute == "value") ? item.value : (item.innerText || item.textContent || '');
15148 },
15149
81bea17a 15150 isItemLoaded: function(/*anything*/ something){
a089699c
AD
15151 return true;
15152 },
15153
15154 getFeatures: function(){
15155 return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true};
15156 },
15157
81bea17a
AD
15158 _fetchItems: function( /*Object*/ args,
15159 /*Function*/ findCallback,
15160 /*Function*/ errorCallback){
a089699c
AD
15161 // summary:
15162 // See dojo.data.util.simpleFetch.fetch()
15163 if(!args.query){ args.query = {}; }
15164 if(!args.query.name){ args.query.name = ""; }
15165 if(!args.queryOptions){ args.queryOptions = {}; }
15166 var matcher = dojo.data.util.filter.patternToRegExp(args.query.name, args.queryOptions.ignoreCase),
15167 items = dojo.query("> option", this.root).filter(function(option){
15168 return (option.innerText || option.textContent || '').match(matcher);
15169 } );
15170 if(args.sort){
15171 items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this));
15172 }
15173 findCallback(items, args);
15174 },
15175
81bea17a 15176 close: function(/*dojo.data.api.Request || args || null*/ request){
a089699c
AD
15177 return;
15178 },
15179
81bea17a 15180 getLabel: function(/*item*/ item){
a089699c
AD
15181 return item.innerHTML;
15182 },
15183
81bea17a 15184 getIdentity: function(/*item*/ item){
a089699c
AD
15185 return dojo.attr(item, "value");
15186 },
15187
81bea17a 15188 fetchItemByIdentity: function(/*Object*/ args){
a089699c
AD
15189 // summary:
15190 // Given the identity of an item, this method returns the item that has
15191 // that identity through the onItem callback.
15192 // Refer to dojo.data.api.Identity.fetchItemByIdentity() for more details.
15193 //
15194 // description:
15195 // Given arguments like:
15196 //
15197 // | {identity: "CA", onItem: function(item){...}
15198 //
15199 // Call `onItem()` with the DOM node `<option value="CA">California</option>`
15200 var item = dojo.query("> option[value='" + args.identity + "']", this.root)[0];
15201 args.onItem(item);
15202 },
15203
15204 fetchSelectedItem: function(){
15205 // summary:
15206 // Get the option marked as selected, like `<option selected>`.
15207 // Not part of dojo.data API.
15208 var root = this.root,
15209 si = root.selectedIndex;
15210 return typeof si == "number"
15211 ? dojo.query("> option:nth-child(" + (si != -1 ? si+1 : 1) + ")", root)[0]
15212 : null; // dojo.data.Item
15213 }
15214});
15215//Mix in the simple fetch implementation to this class.
15216dojo.extend(dijit.form._ComboBoxDataStore,dojo.data.util.simpleFetch);
15217
15218}
15219
15220if(!dojo._hasResource["dijit.form.FilteringSelect"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15221dojo._hasResource["dijit.form.FilteringSelect"] = true;
15222dojo.provide("dijit.form.FilteringSelect");
15223
15224
15225
15226dojo.declare(
15227 "dijit.form.FilteringSelect",
15228 [dijit.form.MappedTextBox, dijit.form.ComboBoxMixin],
15229 {
15230 // summary:
15231 // An enhanced version of the HTML SELECT tag, populated dynamically
15232 //
15233 // description:
15234 // An enhanced version of the HTML SELECT tag, populated dynamically. It works
15235 // very nicely with very large data sets because it can load and page data as needed.
15236 // It also resembles ComboBox, but does not allow values outside of the provided ones.
15237 // If OPTION tags are used as the data provider via markup, then the
15238 // OPTION tag's child text node is used as the displayed value when selected
15239 // while the OPTION tag's value attribute is used as the widget value on form submit.
15240 // To set the default value when using OPTION tags, specify the selected
15241 // attribute on 1 of the child OPTION tags.
15242 //
15243 // Similar features:
15244 // - There is a drop down list of possible values.
15245 // - You can only enter a value from the drop down list. (You can't
15246 // enter an arbitrary value.)
15247 // - The value submitted with the form is the hidden value (ex: CA),
15248 // not the displayed value a.k.a. label (ex: California)
15249 //
15250 // Enhancements over plain HTML version:
15251 // - If you type in some text then it will filter down the list of
15252 // possible values in the drop down list.
15253 // - List can be specified either as a static list or via a javascript
15254 // function (that can get the list from a server)
15255
a089699c
AD
15256 // required: Boolean
15257 // True (default) if user is required to enter a value into this field.
15258 required: true,
15259
15260 _lastDisplayedValue: "",
15261
81bea17a
AD
15262 _isValidSubset: function(){
15263 return this._opened;
15264 },
15265
a089699c
AD
15266 isValid: function(){
15267 // Overrides ValidationTextBox.isValid()
81bea17a 15268 return this.item || (!this.required && this.get('displayedValue') == ""); // #5974
a089699c
AD
15269 },
15270
15271 _refreshState: function(){
15272 if(!this.searchTimer){ // state will be refreshed after results are returned
15273 this.inherited(arguments);
15274 }
15275 },
15276
81bea17a
AD
15277 _callbackSetLabel: function(
15278 /*Array*/ result,
a089699c
AD
15279 /*Object*/ dataObject,
15280 /*Boolean?*/ priorityChange){
15281 // summary:
81bea17a 15282 // Callback from dojo.data after lookup of user entered value finishes
a089699c
AD
15283
15284 // setValue does a synchronous lookup,
15285 // so it calls _callbackSetLabel directly,
15286 // and so does not pass dataObject
15287 // still need to test against _lastQuery in case it came too late
15288 if((dataObject && dataObject.query[this.searchAttr] != this._lastQuery) || (!dataObject && result.length && this.store.getIdentity(result[0]) != this._lastQuery)){
15289 return;
15290 }
15291 if(!result.length){
81bea17a 15292 //#3268: don't modify display value on bad input
a089699c
AD
15293 //#3285: change CSS to indicate error
15294 this.valueNode.value = "";
15295 dijit.form.TextBox.superclass._setValueAttr.call(this, "", priorityChange || (priorityChange === undefined && !this._focused));
81bea17a 15296 this._set("item", null);
a089699c 15297 this.validate(this._focused);
a089699c
AD
15298 }else{
15299 this.set('item', result[0], priorityChange);
15300 }
15301 },
15302
15303 _openResultList: function(/*Object*/ results, /*Object*/ dataObject){
81bea17a 15304 // Callback when a data store query completes.
a089699c
AD
15305 // Overrides ComboBox._openResultList()
15306
15307 // #3285: tap into search callback to see if user's query resembles a match
15308 if(dataObject.query[this.searchAttr] != this._lastQuery){
15309 return;
15310 }
81bea17a
AD
15311 dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments);
15312
a089699c 15313 if(this.item === undefined){ // item == undefined for keyboard search
81bea17a
AD
15314 // If the search returned no items that means that the user typed
15315 // in something invalid (and they can't make it valid by typing more characters),
15316 // so flag the FilteringSelect as being in an invalid state
a089699c
AD
15317 this.validate(true);
15318 }
a089699c
AD
15319 },
15320
15321 _getValueAttr: function(){
15322 // summary:
81bea17a 15323 // Hook for get('value') to work.
a089699c
AD
15324
15325 // don't get the textbox value but rather the previously set hidden value.
15326 // Use this.valueNode.value which isn't always set for other MappedTextBox widgets until blur
15327 return this.valueNode.value;
15328 },
15329
15330 _getValueField: function(){
15331 // Overrides ComboBox._getValueField()
15332 return "value";
15333 },
15334
15335 _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){
15336 // summary:
81bea17a 15337 // Hook so set('value', value) works.
a089699c
AD
15338 // description:
15339 // Sets the value of the select.
15340 // Also sets the label to the corresponding value by reverse lookup.
15341 if(!this._onChangeActive){ priorityChange = null; }
15342 this._lastQuery = value;
15343
15344 if(value === null || value === ''){
15345 this._setDisplayedValueAttr('', priorityChange);
15346 return;
15347 }
15348
15349 //#3347: fetchItemByIdentity if no keyAttr specified
15350 var self = this;
15351 this.store.fetchItemByIdentity({
15352 identity: value,
15353 onItem: function(item){
15354 self._callbackSetLabel(item? [item] : [], undefined, priorityChange);
15355 }
15356 });
15357 },
15358
15359 _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
15360 // summary:
15361 // Set the displayed valued in the input box, and the hidden value
15362 // that gets submitted, based on a dojo.data store item.
15363 // description:
15364 // Users shouldn't call this function; they should be calling
81bea17a 15365 // set('item', value)
a089699c
AD
15366 // tags:
15367 // private
a089699c
AD
15368 this.inherited(arguments);
15369 this.valueNode.value = this.value;
15370 this._lastDisplayedValue = this.textbox.value;
15371 },
15372
15373 _getDisplayQueryString: function(/*String*/ text){
15374 return text.replace(/([\\\*\?])/g, "\\$1");
15375 },
15376
15377 _setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){
15378 // summary:
81bea17a 15379 // Hook so set('displayedValue', label) works.
a089699c
AD
15380 // description:
15381 // Sets textbox to display label. Also performs reverse lookup
81bea17a 15382 // to set the hidden value. label should corresponding to item.searchAttr.
a089699c 15383
81bea17a
AD
15384 if(label == null){ label = ''; }
15385
15386 // This is called at initialization along with every custom setter.
15387 // Usually (or always?) the call can be ignored. If it needs to be
15388 // processed then at least make sure that the XHR request doesn't trigger an onChange()
15389 // event, even if it returns after creation has finished
a089699c 15390 if(!this._created){
81bea17a
AD
15391 if(!("displayedValue" in this.params)){
15392 return;
15393 }
a089699c
AD
15394 priorityChange = false;
15395 }
15396
81bea17a
AD
15397 // Do a reverse lookup to map the specified displayedValue to the hidden value.
15398 // Note that if there's a custom labelFunc() this code
a089699c 15399 if(this.store){
81bea17a 15400 this.closeDropDown();
a089699c
AD
15401 var query = dojo.clone(this.query); // #6196: populate query with user-specifics
15402 // escape meta characters of dojo.data.util.filter.patternToRegExp().
15403 this._lastQuery = query[this.searchAttr] = this._getDisplayQueryString(label);
81bea17a
AD
15404 // If the label is not valid, the callback will never set it,
15405 // so the last valid value will get the warning textbox. Set the
a089699c
AD
15406 // textbox value now so that the impending warning will make
15407 // sense to the user
15408 this.textbox.value = label;
15409 this._lastDisplayedValue = label;
81bea17a 15410 this._set("displayedValue", label); // for watch("displayedValue") notification
a089699c
AD
15411 var _this = this;
15412 var fetch = {
15413 query: query,
15414 queryOptions: {
15415 ignoreCase: this.ignoreCase,
15416 deep: true
15417 },
15418 onComplete: function(result, dataObject){
15419 _this._fetchHandle = null;
15420 dojo.hitch(_this, "_callbackSetLabel")(result, dataObject, priorityChange);
15421 },
15422 onError: function(errText){
15423 _this._fetchHandle = null;
15424 console.error('dijit.form.FilteringSelect: ' + errText);
15425 dojo.hitch(_this, "_callbackSetLabel")([], undefined, false);
15426 }
15427 };
15428 dojo.mixin(fetch, this.fetchProperties);
15429 this._fetchHandle = this.store.fetch(fetch);
15430 }
15431 },
15432
a089699c
AD
15433 undo: function(){
15434 this.set('displayedValue', this._lastDisplayedValue);
15435 }
15436 }
15437);
15438
15439}
15440
15441if(!dojo._hasResource["dijit.form.Form"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15442dojo._hasResource["dijit.form.Form"] = true;
15443dojo.provide("dijit.form.Form");
15444
15445
15446
15447
15448
81bea17a 15449
a089699c
AD
15450dojo.declare(
15451 "dijit.form.Form",
81bea17a 15452 [dijit._Widget, dijit._Templated, dijit.form._FormMixin, dijit.layout._ContentPaneResizeMixin],
a089699c
AD
15453 {
15454 // summary:
15455 // Widget corresponding to HTML form tag, for validation and serialization
15456 //
15457 // example:
15458 // | <form dojoType="dijit.form.Form" id="myForm">
15459 // | Name: <input type="text" name="name" />
15460 // | </form>
15461 // | myObj = {name: "John Doe"};
15462 // | dijit.byId('myForm').set('value', myObj);
15463 // |
15464 // | myObj=dijit.byId('myForm').get('value');
15465
15466 // HTML <FORM> attributes
15467
15468 // name: String?
15469 // Name of form for scripting.
15470 name: "",
15471
15472 // action: String?
15473 // Server-side form handler.
15474 action: "",
15475
15476 // method: String?
15477 // HTTP method used to submit the form, either "GET" or "POST".
15478 method: "",
15479
15480 // encType: String?
15481 // Encoding type for the form, ex: application/x-www-form-urlencoded.
15482 encType: "",
15483
15484 // accept-charset: String?
15485 // List of supported charsets.
15486 "accept-charset": "",
15487
15488 // accept: String?
15489 // List of MIME types for file upload.
15490 accept: "",
15491
15492 // target: String?
15493 // Target frame for the document to be opened in.
15494 target: "",
15495
15496 templateString: "<form dojoAttachPoint='containerNode' dojoAttachEvent='onreset:_onReset,onsubmit:_onSubmit' ${!nameAttrSetting}></form>",
15497
15498 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
15499 action: "",
15500 method: "",
15501 encType: "",
15502 "accept-charset": "",
15503 accept: "",
15504 target: ""
15505 }),
15506
15507 postMixInProperties: function(){
15508 // Setup name=foo string to be referenced from the template (but only if a name has been specified)
15509 // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660
15510 this.nameAttrSetting = this.name ? ("name='" + this.name + "'") : "";
15511 this.inherited(arguments);
15512 },
15513
15514 execute: function(/*Object*/ formContents){
15515 // summary:
15516 // Deprecated: use submit()
15517 // tags:
15518 // deprecated
15519 },
15520
15521 onExecute: function(){
15522 // summary:
15523 // Deprecated: use onSubmit()
15524 // tags:
15525 // deprecated
15526 },
15527
15528 _setEncTypeAttr: function(/*String*/ value){
15529 this.encType = value;
15530 dojo.attr(this.domNode, "encType", value);
15531 if(dojo.isIE){ this.domNode.encoding = value; }
15532 },
15533
15534 postCreate: function(){
15535 // IE tries to hide encType
81bea17a 15536 // TODO: remove in 2.0, no longer necessary with data-dojo-params
a089699c
AD
15537 if(dojo.isIE && this.srcNodeRef && this.srcNodeRef.attributes){
15538 var item = this.srcNodeRef.attributes.getNamedItem('encType');
15539 if(item && !item.specified && (typeof item.value == "string")){
15540 this.set('encType', item.value);
15541 }
15542 }
15543 this.inherited(arguments);
15544 },
15545
15546 reset: function(/*Event?*/ e){
15547 // summary:
15548 // restores all widget values back to their init values,
15549 // calls onReset() which can cancel the reset by returning false
15550
15551 // create fake event so we can know if preventDefault() is called
15552 var faux = {
15553 returnValue: true, // the IE way
15554 preventDefault: function(){ // not IE
15555 this.returnValue = false;
15556 },
81bea17a
AD
15557 stopPropagation: function(){},
15558 currentTarget: e ? e.target : this.domNode,
a089699c
AD
15559 target: e ? e.target : this.domNode
15560 };
15561 // if return value is not exactly false, and haven't called preventDefault(), then reset
15562 if(!(this.onReset(faux) === false) && faux.returnValue){
15563 this.inherited(arguments, []);
15564 }
15565 },
15566
15567 onReset: function(/*Event?*/ e){
15568 // summary:
15569 // Callback when user resets the form. This method is intended
15570 // to be over-ridden. When the `reset` method is called
15571 // programmatically, the return value from `onReset` is used
15572 // to compute whether or not resetting should proceed
15573 // tags:
15574 // callback
15575 return true; // Boolean
15576 },
15577
15578 _onReset: function(e){
15579 this.reset(e);
15580 dojo.stopEvent(e);
15581 return false;
15582 },
15583
15584 _onSubmit: function(e){
15585 var fp = dijit.form.Form.prototype;
15586 // TODO: remove this if statement beginning with 2.0
15587 if(this.execute != fp.execute || this.onExecute != fp.onExecute){
15588 dojo.deprecated("dijit.form.Form:execute()/onExecute() are deprecated. Use onSubmit() instead.", "", "2.0");
15589 this.onExecute();
15590 this.execute(this.getValues());
15591 }
15592 if(this.onSubmit(e) === false){ // only exactly false stops submit
15593 dojo.stopEvent(e);
15594 }
15595 },
15596
81bea17a 15597 onSubmit: function(/*Event?*/ e){
a089699c
AD
15598 // summary:
15599 // Callback when user submits the form.
15600 // description:
15601 // This method is intended to be over-ridden, but by default it checks and
15602 // returns the validity of form elements. When the `submit`
15603 // method is called programmatically, the return value from
15604 // `onSubmit` is used to compute whether or not submission
15605 // should proceed
15606 // tags:
15607 // extension
15608
15609 return this.isValid(); // Boolean
15610 },
15611
15612 submit: function(){
15613 // summary:
15614 // programmatically submit form if and only if the `onSubmit` returns true
15615 if(!(this.onSubmit() === false)){
15616 this.containerNode.submit();
15617 }
15618 }
15619 }
15620);
15621
15622}
15623
15624if(!dojo._hasResource["dijit.form.RadioButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15625dojo._hasResource["dijit.form.RadioButton"] = true;
15626dojo.provide("dijit.form.RadioButton");
15627
15628
81bea17a 15629
a089699c
AD
15630// TODO: for 2.0, move the RadioButton code into this file
15631
15632}
15633
15634if(!dojo._hasResource["dijit.form._FormSelectWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
15635dojo._hasResource["dijit.form._FormSelectWidget"] = true;
15636dojo.provide("dijit.form._FormSelectWidget");
15637
15638
15639
15640
15641/*=====
15642dijit.form.__SelectOption = function(){
15643 // value: String
15644 // The value of the option. Setting to empty (or missing) will
15645 // place a separator at that location
15646 // label: String
15647 // The label for our option. It can contain html tags.
81bea17a 15648 // selected: Boolean
a089699c
AD
15649 // Whether or not we are a selected option
15650 // disabled: Boolean
15651 // Whether or not this specific option is disabled
15652 this.value = value;
15653 this.label = label;
15654 this.selected = selected;
15655 this.disabled = disabled;
15656}
15657=====*/
15658
15659dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, {
15660 // summary:
15661 // Extends _FormValueWidget in order to provide "select-specific"
15662 // values - i.e., those values that are unique to <select> elements.
15663 // This also provides the mechanism for reading the elements from
15664 // a store, if desired.
15665
81bea17a 15666 // multiple: [const] Boolean
a089699c
AD
15667 // Whether or not we are multi-valued
15668 multiple: false,
15669
15670 // options: dijit.form.__SelectOption[]
15671 // The set of options for our select item. Roughly corresponds to
81bea17a 15672 // the html <option> tag.
a089699c
AD
15673 options: null,
15674
15675 // store: dojo.data.api.Identity
15676 // A store which, at the very least impelements dojo.data.api.Identity
15677 // to use for getting our list of options - rather than reading them
15678 // from the <option> html tags.
15679 store: null,
15680
15681 // query: object
15682 // A query to use when fetching items from our store
15683 query: null,
15684
15685 // queryOptions: object
15686 // Query options to use when fetching from the store
15687 queryOptions: null,
15688
15689 // onFetch: Function
15690 // A callback to do with an onFetch - but before any items are actually
15691 // iterated over (i.e. to filter even futher what you want to add)
15692 onFetch: null,
15693
81bea17a 15694 // sortByLabel: Boolean
a089699c
AD
15695 // Flag to sort the options returned from a store by the label of
15696 // the store.
15697 sortByLabel: true,
15698
15699
81bea17a 15700 // loadChildrenOnOpen: Boolean
a089699c
AD
15701 // By default loadChildren is called when the items are fetched from the
15702 // store. This property allows delaying loadChildren (and the creation
81bea17a
AD
15703 // of the options/menuitems) until the user clicks the button to open the
15704 // dropdown.
a089699c
AD
15705 loadChildrenOnOpen: false,
15706
81bea17a 15707 getOptions: function(/*anything*/ valueOrIdx){
a089699c
AD
15708 // summary:
15709 // Returns a given option (or options).
15710 // valueOrIdx:
15711 // If passed in as a string, that string is used to look up the option
15712 // in the array of options - based on the value property.
15713 // (See dijit.form.__SelectOption).
15714 //
15715 // If passed in a number, then the option with the given index (0-based)
15716 // within this select will be returned.
15717 //
15718 // If passed in a dijit.form.__SelectOption, the same option will be
15719 // returned if and only if it exists within this select.
15720 //
15721 // If passed an array, then an array will be returned with each element
15722 // in the array being looked up.
15723 //
15724 // If not passed a value, then all options will be returned
15725 //
15726 // returns:
15727 // The option corresponding with the given value or index. null
15728 // is returned if any of the following are true:
15729 // - A string value is passed in which doesn't exist
15730 // - An index is passed in which is outside the bounds of the array of options
15731 // - A dijit.form.__SelectOption is passed in which is not a part of the select
15732
15733 // NOTE: the compare for passing in a dijit.form.__SelectOption checks
15734 // if the value property matches - NOT if the exact option exists
15735 // NOTE: if passing in an array, null elements will be placed in the returned
15736 // array when a value is not found.
15737 var lookupValue = valueOrIdx, opts = this.options || [], l = opts.length;
15738
15739 if(lookupValue === undefined){
15740 return opts; // dijit.form.__SelectOption[]
15741 }
15742 if(dojo.isArray(lookupValue)){
15743 return dojo.map(lookupValue, "return this.getOptions(item);", this); // dijit.form.__SelectOption[]
15744 }
15745 if(dojo.isObject(valueOrIdx)){
15746 // We were passed an option - so see if it's in our array (directly),
15747 // and if it's not, try and find it by value.
15748 if(!dojo.some(this.options, function(o, idx){
15749 if(o === lookupValue ||
15750 (o.value && o.value === lookupValue.value)){
15751 lookupValue = idx;
15752 return true;
15753 }
15754 return false;
15755 })){
15756 lookupValue = -1;
15757 }
15758 }
15759 if(typeof lookupValue == "string"){
15760 for(var i=0; i<l; i++){
15761 if(opts[i].value === lookupValue){
15762 lookupValue = i;
15763 break;
15764 }
15765 }
15766 }
15767 if(typeof lookupValue == "number" && lookupValue >= 0 && lookupValue < l){
15768 return this.options[lookupValue] // dijit.form.__SelectOption
15769 }
15770 return null; // null
15771 },
15772
81bea17a 15773 addOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ option){
a089699c
AD
15774 // summary:
15775 // Adds an option or options to the end of the select. If value
15776 // of the option is empty or missing, a separator is created instead.
15777 // Passing in an array of options will yield slightly better performance
15778 // since the children are only loaded once.
15779 if(!dojo.isArray(option)){ option = [option]; }
15780 dojo.forEach(option, function(i){
15781 if(i && dojo.isObject(i)){
15782 this.options.push(i);
15783 }
15784 }, this);
15785 this._loadChildren();
15786 },
15787
81bea17a 15788 removeOption: function(/*String|dijit.form.__SelectOption|Number|Array*/ valueOrIdx){
a089699c
AD
15789 // summary:
15790 // Removes the given option or options. You can remove by string
15791 // (in which case the value is removed), number (in which case the
15792 // index in the options array is removed), or select option (in
15793 // which case, the select option with a matching value is removed).
15794 // You can also pass in an array of those values for a slightly
15795 // better performance since the children are only loaded once.
15796 if(!dojo.isArray(valueOrIdx)){ valueOrIdx = [valueOrIdx]; }
15797 var oldOpts = this.getOptions(valueOrIdx);
15798 dojo.forEach(oldOpts, function(i){
15799 // We can get null back in our array - if our option was not found. In
15800 // that case, we don't want to blow up...
15801 if(i){
15802 this.options = dojo.filter(this.options, function(node, idx){
81bea17a 15803 return (node.value !== i.value || node.label !== i.label);
a089699c
AD
15804 });
15805 this._removeOptionItem(i);
15806 }
15807 }, this);
15808 this._loadChildren();
15809 },
15810
81bea17a 15811 updateOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ newOption){
a089699c
AD
15812 // summary:
15813 // Updates the values of the given option. The option to update
15814 // is matched based on the value of the entered option. Passing
15815 // in an array of new options will yeild better performance since
15816 // the children will only be loaded once.
15817 if(!dojo.isArray(newOption)){ newOption = [newOption]; }
15818 dojo.forEach(newOption, function(i){
15819 var oldOpt = this.getOptions(i), k;
15820 if(oldOpt){
15821 for(k in i){ oldOpt[k] = i[k]; }
15822 }
15823 }, this);
15824 this._loadChildren();
15825 },
15826
81bea17a
AD
15827 setStore: function(/*dojo.data.api.Identity*/ store,
15828 /*anything?*/ selectedValue,
15829 /*Object?*/ fetchArgs){
a089699c
AD
15830 // summary:
15831 // Sets the store you would like to use with this select widget.
15832 // The selected value is the value of the new store to set. This
15833 // function returns the original store, in case you want to reuse
15834 // it or something.
15835 // store: dojo.data.api.Identity
15836 // The store you would like to use - it MUST implement Identity,
15837 // and MAY implement Notification.
15838 // selectedValue: anything?
15839 // The value that this widget should set itself to *after* the store
15840 // has been loaded
15841 // fetchArgs: Object?
15842 // The arguments that will be passed to the store's fetch() function
15843 var oStore = this.store;
15844 fetchArgs = fetchArgs || {};
15845 if(oStore !== store){
15846 // Our store has changed, so update our notifications
15847 dojo.forEach(this._notifyConnections || [], dojo.disconnect);
15848 delete this._notifyConnections;
15849 if(store && store.getFeatures()["dojo.data.api.Notification"]){
15850 this._notifyConnections = [
15851 dojo.connect(store, "onNew", this, "_onNewItem"),
15852 dojo.connect(store, "onDelete", this, "_onDeleteItem"),
15853 dojo.connect(store, "onSet", this, "_onSetItem")
15854 ];
15855 }
81bea17a 15856 this._set("store", store);
a089699c
AD
15857 }
15858
15859 // Turn off change notifications while we make all these changes
15860 this._onChangeActive = false;
15861
15862 // Remove existing options (if there are any)
15863 if(this.options && this.options.length){
15864 this.removeOption(this.options);
15865 }
15866
15867 // Add our new options
15868 if(store){
a089699c 15869 this._loadingStore = true;
81bea17a
AD
15870 store.fetch(dojo.delegate(fetchArgs, {
15871 onComplete: function(items, opts){
15872 if(this.sortByLabel && !fetchArgs.sort && items.length){
15873 items.sort(dojo.data.util.sorter.createSortFunction([{
15874 attribute: store.getLabelAttributes(items[0])[0]
15875 }], store));
15876 }
15877
15878 if(fetchArgs.onFetch){
15879 items = fetchArgs.onFetch.call(this, items, opts);
15880 }
15881 // TODO: Add these guys as a batch, instead of separately
15882 dojo.forEach(items, function(i){
15883 this._addOptionForItem(i);
15884 }, this);
15885
15886 // Set our value (which might be undefined), and then tweak
15887 // it to send a change event with the real value
15888 this._loadingStore = false;
15889 this.set("value", "_pendingValue" in this ? this._pendingValue : selectedValue);
15890 delete this._pendingValue;
15891
15892 if(!this.loadChildrenOnOpen){
15893 this._loadChildren();
15894 }else{
15895 this._pseudoLoadChildren(items);
15896 }
15897 this._fetchedWith = opts;
15898 this._lastValueReported = this.multiple ? [] : null;
15899 this._onChangeActive = true;
15900 this.onSetStore();
15901 this._handleOnChange(this.value);
15902 },
15903 scope: this
15904 }));
a089699c
AD
15905 }else{
15906 delete this._fetchedWith;
15907 }
15908 return oStore; // dojo.data.api.Identity
15909 },
15910
81bea17a
AD
15911 // TODO: implement set() and watch() for store and query, although not sure how to handle
15912 // setting them individually rather than together (as in setStore() above)
15913
15914 _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
a089699c
AD
15915 // summary:
15916 // set the value of the widget.
15917 // If a string is passed, then we set our value from looking it up.
15918 if(this._loadingStore){
15919 // Our store is loading - so save our value, and we'll set it when
15920 // we're done
15921 this._pendingValue = newValue;
15922 return;
15923 }
15924 var opts = this.getOptions() || [];
15925 if(!dojo.isArray(newValue)){
15926 newValue = [newValue];
15927 }
15928 dojo.forEach(newValue, function(i, idx){
15929 if(!dojo.isObject(i)){
15930 i = i + "";
15931 }
15932 if(typeof i === "string"){
15933 newValue[idx] = dojo.filter(opts, function(node){
15934 return node.value === i;
15935 })[0] || {value: "", label: ""};
15936 }
15937 }, this);
15938
15939 // Make sure some sane default is set
15940 newValue = dojo.filter(newValue, function(i){ return i && i.value; });
15941 if(!this.multiple && (!newValue[0] || !newValue[0].value) && opts.length){
15942 newValue[0] = opts[0];
15943 }
15944 dojo.forEach(opts, function(i){
15945 i.selected = dojo.some(newValue, function(v){ return v.value === i.value; });
15946 });
15947 var val = dojo.map(newValue, function(i){ return i.value; }),
15948 disp = dojo.map(newValue, function(i){ return i.label; });
15949
81bea17a 15950 this._set("value", this.multiple ? val : val[0]);
a089699c
AD
15951 this._setDisplay(this.multiple ? disp : disp[0]);
15952 this._updateSelection();
15953 this._handleOnChange(this.value, priorityChange);
15954 },
15955
15956 _getDisplayedValueAttr: function(){
15957 // summary:
15958 // returns the displayed value of the widget
15959 var val = this.get("value");
15960 if(!dojo.isArray(val)){
15961 val = [val];
15962 }
15963 var ret = dojo.map(this.getOptions(val), function(v){
15964 if(v && "label" in v){
15965 return v.label;
15966 }else if(v){
15967 return v.value;
15968 }
15969 return null;
15970 }, this);
15971 return this.multiple ? ret : ret[0];
15972 },
15973
a089699c
AD
15974 _loadChildren: function(){
15975 // summary:
15976 // Loads the children represented by this widget's options.
81bea17a 15977 // reset the menu to make it populatable on the next click
a089699c
AD
15978 if(this._loadingStore){ return; }
15979 dojo.forEach(this._getChildren(), function(child){
15980 child.destroyRecursive();
15981 });
15982 // Add each menu item
15983 dojo.forEach(this.options, this._addOptionItem, this);
15984
15985 // Update states
15986 this._updateSelection();
15987 },
15988
15989 _updateSelection: function(){
15990 // summary:
15991 // Sets the "selected" class on the item for styling purposes
81bea17a 15992 this._set("value", this._getValueFromOpts());
a089699c
AD
15993 var val = this.value;
15994 if(!dojo.isArray(val)){
15995 val = [val];
15996 }
15997 if(val && val[0]){
15998 dojo.forEach(this._getChildren(), function(child){
15999 var isSelected = dojo.some(val, function(v){
16000 return child.option && (v === child.option.value);
16001 });
16002 dojo.toggleClass(child.domNode, this.baseClass + "SelectedOption", isSelected);
16003 dijit.setWaiState(child.domNode, "selected", isSelected);
16004 }, this);
16005 }
a089699c
AD
16006 },
16007
16008 _getValueFromOpts: function(){
16009 // summary:
16010 // Returns the value of the widget by reading the options for
16011 // the selected flag
16012 var opts = this.getOptions() || [];
16013 if(!this.multiple && opts.length){
16014 // Mirror what a select does - choose the first one
16015 var opt = dojo.filter(opts, function(i){
16016 return i.selected;
16017 })[0];
16018 if(opt && opt.value){
16019 return opt.value
16020 }else{
16021 opts[0].selected = true;
16022 return opts[0].value;
16023 }
16024 }else if(this.multiple){
16025 // Set value to be the sum of all selected
16026 return dojo.map(dojo.filter(opts, function(i){
16027 return i.selected;
16028 }), function(i){
16029 return i.value;
16030 }) || [];
16031 }
16032 return "";
16033 },
16034
16035 // Internal functions to call when we have store notifications come in
81bea17a 16036 _onNewItem: function(/*item*/ item, /*Object?*/ parentInfo){
a089699c
AD
16037 if(!parentInfo || !parentInfo.parent){
16038 // Only add it if we are top-level
16039 this._addOptionForItem(item);
16040 }
16041 },
81bea17a 16042 _onDeleteItem: function(/*item*/ item){
a089699c
AD
16043 var store = this.store;
16044 this.removeOption(store.getIdentity(item));
16045 },
81bea17a 16046 _onSetItem: function(/*item*/ item){
a089699c
AD
16047 this.updateOption(this._getOptionObjForItem(item));
16048 },
16049
16050 _getOptionObjForItem: function(item){
16051 // summary:
16052 // Returns an option object based off the given item. The "value"
16053 // of the option item will be the identity of the item, the "label"
16054 // of the option will be the label of the item. If the item contains
16055 // children, the children value of the item will be set
16056 var store = this.store, label = store.getLabel(item),
16057 value = (label ? store.getIdentity(item) : null);
16058 return {value: value, label: label, item:item}; // dijit.form.__SelectOption
16059 },
16060
81bea17a 16061 _addOptionForItem: function(/*item*/ item){
a089699c
AD
16062 // summary:
16063 // Creates (and adds) the option for the given item
16064 var store = this.store;
16065 if(!store.isItemLoaded(item)){
16066 // We are not loaded - so let's load it and add later
16067 store.loadItem({item: item, onComplete: function(i){
16068 this._addOptionForItem(item);
16069 },
16070 scope: this});
16071 return;
16072 }
16073 var newOpt = this._getOptionObjForItem(item);
16074 this.addOption(newOpt);
16075 },
16076
81bea17a 16077 constructor: function(/*Object*/ keywordArgs){
a089699c
AD
16078 // summary:
16079 // Saves off our value, if we have an initial one set so we
16080 // can use it if we have a store as well (see startup())
16081 this._oValue = (keywordArgs || {}).value || null;
16082 },
16083
81bea17a
AD
16084 buildRendering: function(){
16085 this.inherited(arguments);
16086 dojo.setSelectable(this.focusNode, false);
16087 },
16088
a089699c
AD
16089 _fillContent: function(){
16090 // summary:
16091 // Loads our options and sets up our dropdown correctly. We
16092 // don't want any content, so we don't call any inherit chain
16093 // function.
16094 var opts = this.options;
16095 if(!opts){
16096 opts = this.options = this.srcNodeRef ? dojo.query(">",
16097 this.srcNodeRef).map(function(node){
16098 if(node.getAttribute("type") === "separator"){
16099 return { value: "", label: "", selected: false, disabled: false };
16100 }
81bea17a
AD
16101 return {
16102 value: (node.getAttribute("data-" + dojo._scopeName + "-value") || node.getAttribute("value")),
a089699c 16103 label: String(node.innerHTML),
81bea17a
AD
16104 // FIXME: disabled and selected are not valid on complex markup children (which is why we're
16105 // looking for data-dojo-value above. perhaps we should data-dojo-props="" this whole thing?)
16106 // decide before 1.6
a089699c 16107 selected: node.getAttribute("selected") || false,
81bea17a
AD
16108 disabled: node.getAttribute("disabled") || false
16109 };
a089699c
AD
16110 }, this) : [];
16111 }
16112 if(!this.value){
81bea17a 16113 this._set("value", this._getValueFromOpts());
a089699c 16114 }else if(this.multiple && typeof this.value == "string"){
81bea17a 16115 this_set("value", this.value.split(","));
a089699c
AD
16116 }
16117 },
16118
16119 postCreate: function(){
16120 // summary:
16121 // sets up our event handling that we need for functioning
16122 // as a select
a089699c
AD
16123 this.inherited(arguments);
16124
16125 // Make our event connections for updating state
16126 this.connect(this, "onChange", "_updateSelection");
16127 this.connect(this, "startup", "_loadChildren");
16128
16129 this._setValueAttr(this.value, null);
16130 },
16131
16132 startup: function(){
16133 // summary:
16134 // Connects in our store, if we have one defined
16135 this.inherited(arguments);
16136 var store = this.store, fetchArgs = {};
16137 dojo.forEach(["query", "queryOptions", "onFetch"], function(i){
16138 if(this[i]){
16139 fetchArgs[i] = this[i];
16140 }
16141 delete this[i];
16142 }, this);
16143 if(store && store.getFeatures()["dojo.data.api.Identity"]){
16144 // Temporarily set our store to null so that it will get set
16145 // and connected appropriately
16146 this.store = null;
16147 this.setStore(store, this._oValue, fetchArgs);
16148 }
16149 },
16150
16151 destroy: function(){
16152 // summary:
16153 // Clean up our connections
16154 dojo.forEach(this._notifyConnections || [], dojo.disconnect);
16155 this.inherited(arguments);
16156 },
16157
81bea17a 16158 _addOptionItem: function(/*dijit.form.__SelectOption*/ option){
a089699c
AD
16159 // summary:
16160 // User-overridable function which, for the given option, adds an
16161 // item to the select. If the option doesn't have a value, then a
16162 // separator is added in that place. Make sure to store the option
16163 // in the created option widget.
16164 },
16165
81bea17a 16166 _removeOptionItem: function(/*dijit.form.__SelectOption*/ option){
a089699c
AD
16167 // summary:
16168 // User-overridable function which, for the given option, removes
16169 // its item from the select.
16170 },
16171
16172 _setDisplay: function(/*String or String[]*/ newDisplay){
16173 // summary:
16174 // Overridable function which will set the display for the
16175 // widget. newDisplay is either a string (in the case of
16176 // single selects) or array of strings (in the case of multi-selects)
16177 },
16178
16179 _getChildren: function(){
16180 // summary:
16181 // Overridable function to return the children that this widget contains.
16182 return [];
16183 },
16184
16185 _getSelectedOptionsAttr: function(){
16186 // summary:
16187 // hooks into this.attr to provide a mechanism for getting the
16188 // option items for the current value of the widget.
16189 return this.getOptions(this.get("value"));
16190 },
16191
81bea17a 16192 _pseudoLoadChildren: function(/*item[]*/ items){
a089699c
AD
16193 // summary:
16194 // a function that will "fake" loading children, if needed, and
16195 // if we have set to not load children until the widget opens.
16196 // items:
16197 // An array of items that will be loaded, when needed
16198 },
16199
16200 onSetStore: function(){
16201 // summary:
16202 // a function that can be connected to in order to receive a
16203 // notification that the store has finished loading and all options
16204 // from that store are available
16205 }
16206});
16207
16208}
16209
16210if(!dojo._hasResource["dijit._KeyNavContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16211dojo._hasResource["dijit._KeyNavContainer"] = true;
16212dojo.provide("dijit._KeyNavContainer");
16213
16214
81bea17a 16215
a089699c
AD
16216dojo.declare("dijit._KeyNavContainer",
16217 dijit._Container,
16218 {
16219
16220 // summary:
16221 // A _Container with keyboard navigation of its children.
16222 // description:
16223 // To use this mixin, call connectKeyNavHandlers() in
16224 // postCreate() and call startupKeyNavChildren() in startup().
16225 // It provides normalized keyboard and focusing code for Container
16226 // widgets.
16227/*=====
16228 // focusedChild: [protected] Widget
16229 // The currently focused child widget, or null if there isn't one
16230 focusedChild: null,
16231=====*/
16232
16233 // tabIndex: Integer
16234 // Tab index of the container; same as HTML tabIndex attribute.
16235 // Note then when user tabs into the container, focus is immediately
16236 // moved to the first item in the container.
16237 tabIndex: "0",
16238
16239 _keyNavCodes: {},
16240
16241 connectKeyNavHandlers: function(/*dojo.keys[]*/ prevKeyCodes, /*dojo.keys[]*/ nextKeyCodes){
16242 // summary:
16243 // Call in postCreate() to attach the keyboard handlers
16244 // to the container.
16245 // preKeyCodes: dojo.keys[]
16246 // Key codes for navigating to the previous child.
16247 // nextKeyCodes: dojo.keys[]
16248 // Key codes for navigating to the next child.
16249 // tags:
16250 // protected
16251
16252 var keyCodes = (this._keyNavCodes = {});
16253 var prev = dojo.hitch(this, this.focusPrev);
16254 var next = dojo.hitch(this, this.focusNext);
16255 dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; });
16256 dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; });
81bea17a
AD
16257 keyCodes[dojo.keys.HOME] = dojo.hitch(this, "focusFirstChild");
16258 keyCodes[dojo.keys.END] = dojo.hitch(this, "focusLastChild");
a089699c
AD
16259 this.connect(this.domNode, "onkeypress", "_onContainerKeypress");
16260 this.connect(this.domNode, "onfocus", "_onContainerFocus");
16261 },
16262
16263 startupKeyNavChildren: function(){
16264 // summary:
16265 // Call in startup() to set child tabindexes to -1
16266 // tags:
16267 // protected
16268 dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild"));
16269 },
16270
16271 addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
16272 // summary:
16273 // Add a child to our _Container
16274 dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
16275 this._startupChild(widget);
16276 },
16277
16278 focus: function(){
16279 // summary:
16280 // Default focus() implementation: focus the first child.
16281 this.focusFirstChild();
16282 },
16283
16284 focusFirstChild: function(){
16285 // summary:
16286 // Focus the first focusable child in the container.
16287 // tags:
16288 // protected
16289 var child = this._getFirstFocusableChild();
16290 if(child){ // edge case: Menu could be empty or hidden
16291 this.focusChild(child);
16292 }
16293 },
16294
81bea17a
AD
16295 focusLastChild: function(){
16296 // summary:
16297 // Focus the last focusable child in the container.
16298 // tags:
16299 // protected
16300 var child = this._getLastFocusableChild();
16301 if(child){ // edge case: Menu could be empty or hidden
16302 this.focusChild(child);
16303 }
16304 },
16305
a089699c
AD
16306 focusNext: function(){
16307 // summary:
16308 // Focus the next widget
16309 // tags:
16310 // protected
16311 var child = this._getNextFocusableChild(this.focusedChild, 1);
16312 this.focusChild(child);
16313 },
16314
16315 focusPrev: function(){
16316 // summary:
16317 // Focus the last focusable node in the previous widget
16318 // (ex: go to the ComboButton icon section rather than button section)
16319 // tags:
16320 // protected
16321 var child = this._getNextFocusableChild(this.focusedChild, -1);
16322 this.focusChild(child, true);
16323 },
16324
16325 focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){
16326 // summary:
16327 // Focus widget.
16328 // widget:
16329 // Reference to container's child widget
16330 // last:
16331 // If true and if widget has multiple focusable nodes, focus the
16332 // last one instead of the first one
16333 // tags:
16334 // protected
16335
16336 if(this.focusedChild && widget !== this.focusedChild){
16337 this._onChildBlur(this.focusedChild);
16338 }
81bea17a 16339 widget.set("tabIndex", this.tabIndex); // for IE focus outline to appear, must set tabIndex before focs
a089699c 16340 widget.focus(last ? "end" : "start");
81bea17a 16341 this._set("focusedChild", widget);
a089699c
AD
16342 },
16343
16344 _startupChild: function(/*dijit._Widget*/ widget){
16345 // summary:
16346 // Setup for each child widget
16347 // description:
81bea17a 16348 // Sets tabIndex=-1 on each child, so that the tab key will
a089699c
AD
16349 // leave the container rather than visiting each child.
16350 // tags:
16351 // private
16352
16353 widget.set("tabIndex", "-1");
16354
16355 this.connect(widget, "_onFocus", function(){
16356 // Set valid tabIndex so tabbing away from widget goes to right place, see #10272
16357 widget.set("tabIndex", this.tabIndex);
16358 });
16359 this.connect(widget, "_onBlur", function(){
16360 widget.set("tabIndex", "-1");
16361 });
16362 },
16363
16364 _onContainerFocus: function(evt){
16365 // summary:
16366 // Handler for when the container gets focus
16367 // description:
16368 // Initially the container itself has a tabIndex, but when it gets
16369 // focus, switch focus to first child...
16370 // tags:
16371 // private
16372
16373 // Note that we can't use _onFocus() because switching focus from the
16374 // _onFocus() handler confuses the focus.js code
16375 // (because it causes _onFocusNode() to be called recursively)
16376
16377 // focus bubbles on Firefox,
16378 // so just make sure that focus has really gone to the container
16379 if(evt.target !== this.domNode){ return; }
16380
16381 this.focusFirstChild();
16382
16383 // and then set the container's tabIndex to -1,
16384 // (don't remove as that breaks Safari 4)
16385 // so that tab or shift-tab will go to the fields after/before
16386 // the container, rather than the container itself
16387 dojo.attr(this.domNode, "tabIndex", "-1");
16388 },
16389
16390 _onBlur: function(evt){
16391 // When focus is moved away the container, and its descendant (popup) widgets,
16392 // then restore the container's tabIndex so that user can tab to it again.
16393 // Note that using _onBlur() so that this doesn't happen when focus is shifted
16394 // to one of my child widgets (typically a popup)
16395 if(this.tabIndex){
16396 dojo.attr(this.domNode, "tabIndex", this.tabIndex);
16397 }
16398 this.inherited(arguments);
16399 },
16400
16401 _onContainerKeypress: function(evt){
16402 // summary:
16403 // When a key is pressed, if it's an arrow key etc. then
16404 // it's handled here.
16405 // tags:
16406 // private
16407 if(evt.ctrlKey || evt.altKey){ return; }
16408 var func = this._keyNavCodes[evt.charOrCode];
16409 if(func){
16410 func();
16411 dojo.stopEvent(evt);
16412 }
16413 },
16414
16415 _onChildBlur: function(/*dijit._Widget*/ widget){
16416 // summary:
16417 // Called when focus leaves a child widget to go
16418 // to a sibling widget.
16419 // tags:
16420 // protected
16421 },
16422
16423 _getFirstFocusableChild: function(){
16424 // summary:
16425 // Returns first child that can be focused
16426 return this._getNextFocusableChild(null, 1); // dijit._Widget
16427 },
16428
81bea17a
AD
16429 _getLastFocusableChild: function(){
16430 // summary:
16431 // Returns last child that can be focused
16432 return this._getNextFocusableChild(null, -1); // dijit._Widget
16433 },
16434
a089699c
AD
16435 _getNextFocusableChild: function(child, dir){
16436 // summary:
16437 // Returns the next or previous focusable child, compared
16438 // to "child"
16439 // child: Widget
16440 // The current widget
16441 // dir: Integer
16442 // * 1 = after
16443 // * -1 = before
16444 if(child){
16445 child = this._getSiblingOfChild(child, dir);
16446 }
16447 var children = this.getChildren();
16448 for(var i=0; i < children.length; i++){
16449 if(!child){
16450 child = children[(dir>0) ? 0 : (children.length-1)];
16451 }
16452 if(child.isFocusable()){
16453 return child; // dijit._Widget
16454 }
16455 child = this._getSiblingOfChild(child, dir);
16456 }
16457 // no focusable child found
16458 return null; // dijit._Widget
16459 }
16460 }
16461);
16462
16463}
16464
16465if(!dojo._hasResource["dijit.MenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16466dojo._hasResource["dijit.MenuItem"] = true;
16467dojo.provide("dijit.MenuItem");
16468
16469
16470
16471
16472
16473
16474dojo.declare("dijit.MenuItem",
16475 [dijit._Widget, dijit._Templated, dijit._Contained, dijit._CssStateMixin],
16476 {
16477 // summary:
16478 // A line item in a Menu Widget
16479
16480 // Make 3 columns
16481 // icon, label, and expand arrow (BiDi-dependent) indicating sub-menu
81bea17a 16482 templateString: dojo.cache("dijit", "templates/MenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" role=\"menuitem\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" role=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\">\n\t\t<div dojoAttachPoint=\"arrowWrapper\" style=\"visibility: hidden\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuExpand\"/>\n\t\t\t<span class=\"dijitMenuExpandA11y\">+</span>\n\t\t</div>\n\t</td>\n</tr>\n"),
a089699c
AD
16483
16484 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
16485 label: { node: "containerNode", type: "innerHTML" },
16486 iconClass: { node: "iconNode", type: "class" }
16487 }),
16488
16489 baseClass: "dijitMenuItem",
16490
16491 // label: String
16492 // Menu text
16493 label: '',
16494
16495 // iconClass: String
16496 // Class to apply to DOMNode to make it display an icon.
16497 iconClass: "",
16498
16499 // accelKey: String
16500 // Text for the accelerator (shortcut) key combination.
16501 // Note that although Menu can display accelerator keys there
16502 // is no infrastructure to actually catch and execute these
16503 // accelerators.
16504 accelKey: "",
16505
16506 // disabled: Boolean
16507 // If true, the menu item is disabled.
16508 // If false, the menu item is enabled.
16509 disabled: false,
16510
16511 _fillContent: function(/*DomNode*/ source){
16512 // If button label is specified as srcNodeRef.innerHTML rather than
16513 // this.params.label, handle it here.
16514 if(source && !("label" in this.params)){
16515 this.set('label', source.innerHTML);
16516 }
16517 },
16518
81bea17a 16519 buildRendering: function(){
a089699c 16520 this.inherited(arguments);
a089699c
AD
16521 var label = this.id+"_text";
16522 dojo.attr(this.containerNode, "id", label);
16523 if(this.accelKeyNode){
16524 dojo.attr(this.accelKeyNode, "id", this.id + "_accel");
16525 label += " " + this.id + "_accel";
16526 }
16527 dijit.setWaiState(this.domNode, "labelledby", label);
81bea17a 16528 dojo.setSelectable(this.domNode, false);
a089699c
AD
16529 },
16530
16531 _onHover: function(){
16532 // summary:
16533 // Handler when mouse is moved onto menu item
16534 // tags:
16535 // protected
16536 this.getParent().onItemHover(this);
16537 },
16538
16539 _onUnhover: function(){
16540 // summary:
16541 // Handler when mouse is moved off of menu item,
16542 // possibly to a child menu, or maybe to a sibling
16543 // menuitem or somewhere else entirely.
16544 // tags:
16545 // protected
16546
16547 // if we are unhovering the currently selected item
16548 // then unselect it
16549 this.getParent().onItemUnhover(this);
16550
81bea17a
AD
16551 // When menu is hidden (collapsed) due to clicking a MenuItem and having it execute,
16552 // FF and IE don't generate an onmouseout event for the MenuItem.
16553 // So, help out _CssStateMixin in this case.
16554 this._set("hovering", false);
a089699c
AD
16555 },
16556
16557 _onClick: function(evt){
16558 // summary:
16559 // Internal handler for click events on MenuItem.
16560 // tags:
16561 // private
16562 this.getParent().onItemClick(this, evt);
16563 dojo.stopEvent(evt);
16564 },
16565
16566 onClick: function(/*Event*/ evt){
16567 // summary:
16568 // User defined function to handle clicks
16569 // tags:
16570 // callback
16571 },
16572
16573 focus: function(){
16574 // summary:
16575 // Focus on this MenuItem
16576 try{
16577 if(dojo.isIE == 8){
16578 // needed for IE8 which won't scroll TR tags into view on focus yet calling scrollIntoView creates flicker (#10275)
16579 this.containerNode.focus();
16580 }
16581 dijit.focus(this.focusNode);
16582 }catch(e){
16583 // this throws on IE (at least) in some scenarios
16584 }
16585 },
16586
16587 _onFocus: function(){
16588 // summary:
16589 // This is called by the focus manager when focus
16590 // goes to this MenuItem or a child menu.
16591 // tags:
16592 // protected
16593 this._setSelected(true);
16594 this.getParent()._onItemFocus(this);
16595
16596 this.inherited(arguments);
16597 },
16598
16599 _setSelected: function(selected){
16600 // summary:
16601 // Indicate that this node is the currently selected one
16602 // tags:
16603 // private
16604
16605 /***
16606 * TODO: remove this method and calls to it, when _onBlur() is working for MenuItem.
16607 * Currently _onBlur() gets called when focus is moved from the MenuItem to a child menu.
16608 * That's not supposed to happen, but the problem is:
16609 * In order to allow dijit.popup's getTopPopup() to work,a sub menu's popupParent
16610 * points to the parent Menu, bypassing the parent MenuItem... thus the
16611 * MenuItem is not in the chain of active widgets and gets a premature call to
16612 * _onBlur()
16613 */
16614
16615 dojo.toggleClass(this.domNode, "dijitMenuItemSelected", selected);
16616 },
16617
16618 setLabel: function(/*String*/ content){
16619 // summary:
16620 // Deprecated. Use set('label', ...) instead.
16621 // tags:
16622 // deprecated
16623 dojo.deprecated("dijit.MenuItem.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0");
16624 this.set("label", content);
16625 },
16626
16627 setDisabled: function(/*Boolean*/ disabled){
16628 // summary:
16629 // Deprecated. Use set('disabled', bool) instead.
16630 // tags:
16631 // deprecated
16632 dojo.deprecated("dijit.Menu.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0");
16633 this.set('disabled', disabled);
16634 },
16635 _setDisabledAttr: function(/*Boolean*/ value){
16636 // summary:
16637 // Hook for attr('disabled', ...) to work.
16638 // Enable or disable this menu item.
81bea17a 16639
a089699c 16640 dijit.setWaiState(this.focusNode, 'disabled', value ? 'true' : 'false');
81bea17a 16641 this._set("disabled", value);
a089699c
AD
16642 },
16643 _setAccelKeyAttr: function(/*String*/ value){
16644 // summary:
16645 // Hook for attr('accelKey', ...) to work.
16646 // Set accelKey on this menu item.
a089699c
AD
16647
16648 this.accelKeyNode.style.display=value?"":"none";
16649 this.accelKeyNode.innerHTML=value;
16650 //have to use colSpan to make it work in IE
16651 dojo.attr(this.containerNode,'colSpan',value?"1":"2");
81bea17a
AD
16652
16653 this._set("accelKey", value);
a089699c
AD
16654 }
16655 });
16656
16657}
16658
16659if(!dojo._hasResource["dijit.PopupMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16660dojo._hasResource["dijit.PopupMenuItem"] = true;
16661dojo.provide("dijit.PopupMenuItem");
16662
16663
16664
16665dojo.declare("dijit.PopupMenuItem",
16666 dijit.MenuItem,
16667 {
16668 _fillContent: function(){
16669 // summary:
16670 // When Menu is declared in markup, this code gets the menu label and
16671 // the popup widget from the srcNodeRef.
16672 // description:
16673 // srcNodeRefinnerHTML contains both the menu item text and a popup widget
16674 // The first part holds the menu item text and the second part is the popup
16675 // example:
16676 // | <div dojoType="dijit.PopupMenuItem">
16677 // | <span>pick me</span>
16678 // | <popup> ... </popup>
16679 // | </div>
16680 // tags:
16681 // protected
16682
16683 if(this.srcNodeRef){
16684 var nodes = dojo.query("*", this.srcNodeRef);
16685 dijit.PopupMenuItem.superclass._fillContent.call(this, nodes[0]);
16686
16687 // save pointer to srcNode so we can grab the drop down widget after it's instantiated
16688 this.dropDownContainer = this.srcNodeRef;
16689 }
16690 },
16691
16692 startup: function(){
16693 if(this._started){ return; }
16694 this.inherited(arguments);
16695
16696 // we didn't copy the dropdown widget from the this.srcNodeRef, so it's in no-man's
16697 // land now. move it to dojo.doc.body.
16698 if(!this.popup){
16699 var node = dojo.query("[widgetId]", this.dropDownContainer)[0];
16700 this.popup = dijit.byNode(node);
16701 }
16702 dojo.body().appendChild(this.popup.domNode);
16703 this.popup.startup();
16704
16705 this.popup.domNode.style.display="none";
16706 if(this.arrowWrapper){
16707 dojo.style(this.arrowWrapper, "visibility", "");
16708 }
16709 dijit.setWaiState(this.focusNode, "haspopup", "true");
16710 },
16711
16712 destroyDescendants: function(){
16713 if(this.popup){
16714 // Destroy the popup, unless it's already been destroyed. This can happen because
16715 // the popup is a direct child of <body> even though it's logically my child.
16716 if(!this.popup._destroyed){
16717 this.popup.destroyRecursive();
16718 }
16719 delete this.popup;
16720 }
16721 this.inherited(arguments);
16722 }
16723 });
16724
a089699c
AD
16725}
16726
16727if(!dojo._hasResource["dijit.CheckedMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16728dojo._hasResource["dijit.CheckedMenuItem"] = true;
16729dojo.provide("dijit.CheckedMenuItem");
16730
16731
16732
16733dojo.declare("dijit.CheckedMenuItem",
16734 dijit.MenuItem,
16735 {
16736 // summary:
16737 // A checkbox-like menu item for toggling on and off
16738
81bea17a 16739 templateString: dojo.cache("dijit", "templates/CheckedMenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" role=\"menuitemcheckbox\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" role=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuItemIcon dijitCheckedMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t<span class=\"dijitCheckedMenuItemIconChar\">&#10003;</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\">&nbsp;</td>\n</tr>\n"),
a089699c
AD
16740
16741 // checked: Boolean
16742 // Our checked state
16743 checked: false,
16744 _setCheckedAttr: function(/*Boolean*/ checked){
16745 // summary:
16746 // Hook so attr('checked', bool) works.
16747 // Sets the class and state for the check box.
16748 dojo.toggleClass(this.domNode, "dijitCheckedMenuItemChecked", checked);
16749 dijit.setWaiState(this.domNode, "checked", checked);
81bea17a 16750 this._set("checked", checked);
a089699c
AD
16751 },
16752
16753 onChange: function(/*Boolean*/ checked){
16754 // summary:
16755 // User defined function to handle check/uncheck events
16756 // tags:
16757 // callback
16758 },
16759
16760 _onClick: function(/*Event*/ e){
16761 // summary:
16762 // Clicking this item just toggles its state
16763 // tags:
16764 // private
16765 if(!this.disabled){
16766 this.set("checked", !this.checked);
16767 this.onChange(this.checked);
16768 }
16769 this.inherited(arguments);
16770 }
16771 });
16772
16773}
16774
16775if(!dojo._hasResource["dijit.MenuSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16776dojo._hasResource["dijit.MenuSeparator"] = true;
16777dojo.provide("dijit.MenuSeparator");
16778
16779
16780
16781
16782
16783dojo.declare("dijit.MenuSeparator",
16784 [dijit._Widget, dijit._Templated, dijit._Contained],
16785 {
16786 // summary:
16787 // A line between two menu items
16788
16789 templateString: dojo.cache("dijit", "templates/MenuSeparator.html", "<tr class=\"dijitMenuSeparator\">\n\t<td class=\"dijitMenuSeparatorIconCell\">\n\t\t<div class=\"dijitMenuSeparatorTop\"></div>\n\t\t<div class=\"dijitMenuSeparatorBottom\"></div>\n\t</td>\n\t<td colspan=\"3\" class=\"dijitMenuSeparatorLabelCell\">\n\t\t<div class=\"dijitMenuSeparatorTop dijitMenuSeparatorLabel\"></div>\n\t\t<div class=\"dijitMenuSeparatorBottom\"></div>\n\t</td>\n</tr>\n"),
16790
81bea17a
AD
16791 buildRendering: function(){
16792 this.inherited(arguments);
a089699c
AD
16793 dojo.setSelectable(this.domNode, false);
16794 },
16795
16796 isFocusable: function(){
16797 // summary:
16798 // Override to always return false
16799 // tags:
16800 // protected
16801
16802 return false; // Boolean
16803 }
16804 });
16805
a089699c
AD
16806}
16807
16808if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
16809dojo._hasResource["dijit.Menu"] = true;
16810dojo.provide("dijit.Menu");
16811
16812
16813
16814
16815
16816
16817
81bea17a
AD
16818
16819
16820
16821// "dijit/MenuItem", "dijit/PopupMenuItem", "dijit/CheckedMenuItem", "dijit/MenuSeparator" for Back-compat (TODO: remove in 2.0)
16822
a089699c
AD
16823dojo.declare("dijit._MenuBase",
16824 [dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
16825{
16826 // summary:
16827 // Base class for Menu and MenuBar
16828
16829 // parentMenu: [readonly] Widget
16830 // pointer to menu that displayed me
16831 parentMenu: null,
16832
16833 // popupDelay: Integer
16834 // number of milliseconds before hovering (without clicking) causes the popup to automatically open.
16835 popupDelay: 500,
16836
16837 startup: function(){
16838 if(this._started){ return; }
16839
16840 dojo.forEach(this.getChildren(), function(child){ child.startup(); });
16841 this.startupKeyNavChildren();
16842
16843 this.inherited(arguments);
16844 },
16845
16846 onExecute: function(){
16847 // summary:
16848 // Attach point for notification about when a menu item has been executed.
16849 // This is an internal mechanism used for Menus to signal to their parent to
16850 // close them, because they are about to execute the onClick handler. In
16851 // general developers should not attach to or override this method.
16852 // tags:
16853 // protected
16854 },
16855
16856 onCancel: function(/*Boolean*/ closeAll){
16857 // summary:
16858 // Attach point for notification about when the user cancels the current menu
16859 // This is an internal mechanism used for Menus to signal to their parent to
16860 // close them. In general developers should not attach to or override this method.
16861 // tags:
16862 // protected
16863 },
16864
16865 _moveToPopup: function(/*Event*/ evt){
16866 // summary:
16867 // This handles the right arrow key (left arrow key on RTL systems),
16868 // which will either open a submenu, or move to the next item in the
16869 // ancestor MenuBar
16870 // tags:
16871 // private
16872
16873 if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
16874 this.focusedChild._onClick(evt);
16875 }else{
16876 var topMenu = this._getTopMenu();
16877 if(topMenu && topMenu._isMenuBar){
16878 topMenu.focusNext();
16879 }
16880 }
16881 },
16882
16883 _onPopupHover: function(/*Event*/ evt){
16884 // summary:
16885 // This handler is called when the mouse moves over the popup.
16886 // tags:
16887 // private
16888
16889 // if the mouse hovers over a menu popup that is in pending-close state,
16890 // then stop the close operation.
16891 // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker)
16892 if(this.currentPopup && this.currentPopup._pendingClose_timer){
16893 var parentMenu = this.currentPopup.parentMenu;
16894 // highlight the parent menu item pointing to this popup
16895 if(parentMenu.focusedChild){
16896 parentMenu.focusedChild._setSelected(false);
16897 }
16898 parentMenu.focusedChild = this.currentPopup.from_item;
16899 parentMenu.focusedChild._setSelected(true);
16900 // cancel the pending close
16901 this._stopPendingCloseTimer(this.currentPopup);
16902 }
16903 },
16904
16905 onItemHover: function(/*MenuItem*/ item){
16906 // summary:
16907 // Called when cursor is over a MenuItem.
16908 // tags:
16909 // protected
16910
16911 // Don't do anything unless user has "activated" the menu by:
16912 // 1) clicking it
16913 // 2) opening it from a parent menu (which automatically focuses it)
16914 if(this.isActive){
16915 this.focusChild(item);
16916 if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){
16917 this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup"), this.popupDelay);
16918 }
16919 }
16920 // if the user is mixing mouse and keyboard navigation,
16921 // then the menu may not be active but a menu item has focus,
16922 // but it's not the item that the mouse just hovered over.
16923 // To avoid both keyboard and mouse selections, use the latest.
16924 if(this.focusedChild){
16925 this.focusChild(item);
16926 }
16927 this._hoveredChild = item;
16928 },
16929
16930 _onChildBlur: function(item){
16931 // summary:
16932 // Called when a child MenuItem becomes inactive because focus
16933 // has been removed from the MenuItem *and* it's descendant menus.
16934 // tags:
16935 // private
16936 this._stopPopupTimer();
16937 item._setSelected(false);
16938 // Close all popups that are open and descendants of this menu
16939 var itemPopup = item.popup;
16940 if(itemPopup){
16941 this._stopPendingCloseTimer(itemPopup);
16942 itemPopup._pendingClose_timer = setTimeout(function(){
16943 itemPopup._pendingClose_timer = null;
16944 if(itemPopup.parentMenu){
16945 itemPopup.parentMenu.currentPopup = null;
16946 }
16947 dijit.popup.close(itemPopup); // this calls onClose
16948 }, this.popupDelay);
16949 }
16950 },
16951
16952 onItemUnhover: function(/*MenuItem*/ item){
16953 // summary:
16954 // Callback fires when mouse exits a MenuItem
16955 // tags:
16956 // protected
16957
16958 if(this.isActive){
16959 this._stopPopupTimer();
16960 }
16961 if(this._hoveredChild == item){ this._hoveredChild = null; }
16962 },
16963
16964 _stopPopupTimer: function(){
16965 // summary:
16966 // Cancels the popup timer because the user has stop hovering
16967 // on the MenuItem, etc.
16968 // tags:
16969 // private
16970 if(this.hover_timer){
16971 clearTimeout(this.hover_timer);
16972 this.hover_timer = null;
16973 }
16974 },
16975
16976 _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){
16977 // summary:
16978 // Cancels the pending-close timer because the close has been preempted
16979 // tags:
16980 // private
16981 if(popup._pendingClose_timer){
16982 clearTimeout(popup._pendingClose_timer);
16983 popup._pendingClose_timer = null;
16984 }
16985 },
16986
16987 _stopFocusTimer: function(){
16988 // summary:
16989 // Cancels the pending-focus timer because the menu was closed before focus occured
16990 // tags:
16991 // private
16992 if(this._focus_timer){
16993 clearTimeout(this._focus_timer);
16994 this._focus_timer = null;
16995 }
16996 },
16997
16998 _getTopMenu: function(){
16999 // summary:
17000 // Returns the top menu in this chain of Menus
17001 // tags:
17002 // private
17003 for(var top=this; top.parentMenu; top=top.parentMenu);
17004 return top;
17005 },
17006
17007 onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){
17008 // summary:
17009 // Handle clicks on an item.
17010 // tags:
17011 // private
17012
17013 // this can't be done in _onFocus since the _onFocus events occurs asynchronously
17014 if(typeof this.isShowingNow == 'undefined'){ // non-popup menu
17015 this._markActive();
17016 }
17017
17018 this.focusChild(item);
17019
17020 if(item.disabled){ return false; }
17021
17022 if(item.popup){
17023 this._openPopup();
17024 }else{
17025 // before calling user defined handler, close hierarchy of menus
17026 // and restore focus to place it was when menu was opened
17027 this.onExecute();
17028
17029 // user defined handler for click
17030 item.onClick(evt);
17031 }
17032 },
17033
17034 _openPopup: function(){
17035 // summary:
17036 // Open the popup to the side of/underneath the current menu item
17037 // tags:
17038 // protected
17039
17040 this._stopPopupTimer();
17041 var from_item = this.focusedChild;
17042 if(!from_item){ return; } // the focused child lost focus since the timer was started
17043 var popup = from_item.popup;
17044 if(popup.isShowingNow){ return; }
17045 if(this.currentPopup){
17046 this._stopPendingCloseTimer(this.currentPopup);
17047 dijit.popup.close(this.currentPopup);
17048 }
17049 popup.parentMenu = this;
17050 popup.from_item = from_item; // helps finding the parent item that should be focused for this popup
17051 var self = this;
17052 dijit.popup.open({
17053 parent: this,
17054 popup: popup,
17055 around: from_item.domNode,
17056 orient: this._orient || (this.isLeftToRight() ?
17057 {'TR': 'TL', 'TL': 'TR', 'BR': 'BL', 'BL': 'BR'} :
17058 {'TL': 'TR', 'TR': 'TL', 'BL': 'BR', 'BR': 'BL'}),
17059 onCancel: function(){ // called when the child menu is canceled
17060 // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus
17061 // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro)
17062 self.focusChild(from_item); // put focus back on my node
17063 self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved)
17064 from_item._setSelected(true); // oops, _cleanUp() deselected the item
17065 self.focusedChild = from_item; // and unset focusedChild
17066 },
17067 onExecute: dojo.hitch(this, "_cleanUp")
17068 });
17069
17070 this.currentPopup = popup;
17071 // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items
17072 popup.connect(popup.domNode, "onmouseenter", dojo.hitch(self, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close
17073
17074 if(popup.focus){
17075 // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar),
17076 // if the cursor happens to collide with the popup, it will generate an onmouseover event
17077 // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that
17078 // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742)
17079 popup._focus_timer = setTimeout(dojo.hitch(popup, function(){
17080 this._focus_timer = null;
17081 this.focus();
17082 }), 0);
17083 }
17084 },
17085
17086 _markActive: function(){
17087 // summary:
17088 // Mark this menu's state as active.
17089 // Called when this Menu gets focus from:
17090 // 1) clicking it (mouse or via space/arrow key)
17091 // 2) being opened by a parent menu.
17092 // This is not called just from mouse hover.
17093 // Focusing a menu via TAB does NOT automatically set isActive
17094 // since TAB is a navigation operation and not a selection one.
17095 // For Windows apps, pressing the ALT key focuses the menubar
17096 // menus (similar to TAB navigation) but the menu is not active
17097 // (ie no dropdown) until an item is clicked.
17098 this.isActive = true;
81bea17a 17099 dojo.replaceClass(this.domNode, "dijitMenuActive", "dijitMenuPassive");
a089699c
AD
17100 },
17101
17102 onOpen: function(/*Event*/ e){
17103 // summary:
17104 // Callback when this menu is opened.
17105 // This is called by the popup manager as notification that the menu
17106 // was opened.
17107 // tags:
17108 // private
17109
17110 this.isShowingNow = true;
17111 this._markActive();
17112 },
17113
17114 _markInactive: function(){
17115 // summary:
17116 // Mark this menu's state as inactive.
17117 this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here
81bea17a 17118 dojo.replaceClass(this.domNode, "dijitMenuPassive", "dijitMenuActive");
a089699c
AD
17119 },
17120
17121 onClose: function(){
17122 // summary:
17123 // Callback when this menu is closed.
17124 // This is called by the popup manager as notification that the menu
17125 // was closed.
17126 // tags:
17127 // private
17128
17129 this._stopFocusTimer();
17130 this._markInactive();
17131 this.isShowingNow = false;
17132 this.parentMenu = null;
17133 },
17134
17135 _closeChild: function(){
17136 // summary:
17137 // Called when submenu is clicked or focus is lost. Close hierarchy of menus.
17138 // tags:
17139 // private
17140 this._stopPopupTimer();
81bea17a
AD
17141
17142 var fromItem = this.focusedChild && this.focusedChild.from_item;
17143
a089699c 17144 if(this.currentPopup){
81bea17a
AD
17145 // If focus is on my child menu then move focus to me,
17146 // because IE doesn't like it when you display:none a node with focus
17147 if(dijit._curFocus && dojo.isDescendant(dijit._curFocus, this.currentPopup.domNode)){
17148 this.focusedChild.focusNode.focus();
17149 }
a089699c
AD
17150 // Close all popups that are open and descendants of this menu
17151 dijit.popup.close(this.currentPopup);
17152 this.currentPopup = null;
17153 }
81bea17a
AD
17154
17155 if(this.focusedChild){ // unhighlight the focused item
17156 this.focusedChild._setSelected(false);
17157 this.focusedChild._onUnhover();
17158 this.focusedChild = null;
17159 }
a089699c
AD
17160 },
17161
17162 _onItemFocus: function(/*MenuItem*/ item){
17163 // summary:
17164 // Called when child of this Menu gets focus from:
17165 // 1) clicking it
17166 // 2) tabbing into it
17167 // 3) being opened by a parent menu.
17168 // This is not called just from mouse hover.
17169 if(this._hoveredChild && this._hoveredChild != item){
17170 this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection
17171 }
17172 },
17173
17174 _onBlur: function(){
17175 // summary:
17176 // Called when focus is moved away from this Menu and it's submenus.
17177 // tags:
17178 // protected
17179 this._cleanUp();
17180 this.inherited(arguments);
17181 },
17182
17183 _cleanUp: function(){
17184 // summary:
17185 // Called when the user is done with this menu. Closes hierarchy of menus.
17186 // tags:
17187 // private
17188
17189 this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close
17190 if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose
17191 this._markInactive();
17192 }
17193 }
17194});
17195
17196dojo.declare("dijit.Menu",
17197 dijit._MenuBase,
17198 {
17199 // summary
17200 // A context menu you can assign to multiple elements
17201
17202 // TODO: most of the code in here is just for context menu (right-click menu)
17203 // support. In retrospect that should have been a separate class (dijit.ContextMenu).
17204 // Split them for 2.0
17205
17206 constructor: function(){
17207 this._bindings = [];
17208 },
17209
81bea17a 17210 templateString: dojo.cache("dijit", "templates/Menu.html", "<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" role=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\" cellspacing=\"0\">\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"),
a089699c
AD
17211
17212 baseClass: "dijitMenu",
17213
17214 // targetNodeIds: [const] String[]
17215 // Array of dom node ids of nodes to attach to.
17216 // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes.
17217 targetNodeIds: [],
17218
17219 // contextMenuForWindow: [const] Boolean
17220 // If true, right clicking anywhere on the window will cause this context menu to open.
17221 // If false, must specify targetNodeIds.
17222 contextMenuForWindow: false,
17223
17224 // leftClickToOpen: [const] Boolean
17225 // If true, menu will open on left click instead of right click, similiar to a file menu.
17226 leftClickToOpen: false,
17227
17228 // refocus: Boolean
17229 // When this menu closes, re-focus the element which had focus before it was opened.
17230 refocus: true,
17231
17232 postCreate: function(){
17233 if(this.contextMenuForWindow){
17234 this.bindDomNode(dojo.body());
17235 }else{
17236 // TODO: should have _setTargetNodeIds() method to handle initialization and a possible
81bea17a 17237 // later set('targetNodeIds', ...) call. There's also a problem that targetNodeIds[]
a089699c
AD
17238 // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610)
17239 dojo.forEach(this.targetNodeIds, this.bindDomNode, this);
17240 }
17241 var k = dojo.keys, l = this.isLeftToRight();
17242 this._openSubMenuKey = l ? k.RIGHT_ARROW : k.LEFT_ARROW;
17243 this._closeSubMenuKey = l ? k.LEFT_ARROW : k.RIGHT_ARROW;
17244 this.connectKeyNavHandlers([k.UP_ARROW], [k.DOWN_ARROW]);
17245 },
17246
17247 _onKeyPress: function(/*Event*/ evt){
17248 // summary:
17249 // Handle keyboard based menu navigation.
17250 // tags:
17251 // protected
17252
17253 if(evt.ctrlKey || evt.altKey){ return; }
17254
17255 switch(evt.charOrCode){
17256 case this._openSubMenuKey:
17257 this._moveToPopup(evt);
17258 dojo.stopEvent(evt);
17259 break;
17260 case this._closeSubMenuKey:
17261 if(this.parentMenu){
17262 if(this.parentMenu._isMenuBar){
17263 this.parentMenu.focusPrev();
17264 }else{
17265 this.onCancel(false);
17266 }
17267 }else{
17268 dojo.stopEvent(evt);
17269 }
17270 break;
17271 }
17272 },
17273
17274 // thanks burstlib!
17275 _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){
17276 // summary:
17277 // Returns the window reference of the passed iframe
17278 // tags:
17279 // private
17280 var win = dojo.window.get(this._iframeContentDocument(iframe_el)) ||
17281 // Moz. TODO: is this available when defaultView isn't?
17282 this._iframeContentDocument(iframe_el)['__parent__'] ||
17283 (iframe_el.name && dojo.doc.frames[iframe_el.name]) || null;
17284 return win; // Window
17285 },
17286
17287 _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){
17288 // summary:
17289 // Returns a reference to the document object inside iframe_el
17290 // tags:
17291 // protected
17292 var doc = iframe_el.contentDocument // W3
17293 || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE
17294 || (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document)
17295 || null;
17296 return doc; // HTMLDocument
17297 },
17298
17299 bindDomNode: function(/*String|DomNode*/ node){
17300 // summary:
17301 // Attach menu to given node
17302 node = dojo.byId(node);
17303
17304 var cn; // Connect node
17305
17306 // Support context menus on iframes. Rather than binding to the iframe itself we need
17307 // to bind to the <body> node inside the iframe.
17308 if(node.tagName.toLowerCase() == "iframe"){
17309 var iframe = node,
17310 win = this._iframeContentWindow(iframe);
17311 cn = dojo.withGlobal(win, dojo.body);
17312 }else{
17313
17314 // To capture these events at the top level, attach to <html>, not <body>.
17315 // Otherwise right-click context menu just doesn't work.
17316 cn = (node == dojo.body() ? dojo.doc.documentElement : node);
17317 }
17318
17319
17320 // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode())
17321 var binding = {
17322 node: node,
17323 iframe: iframe
17324 };
17325
17326 // Save info about binding in _bindings[], and make node itself record index(+1) into
17327 // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may
17328 // start with a number, which fails on FF/safari.
17329 dojo.attr(node, "_dijitMenu" + this.id, this._bindings.push(binding));
17330
17331 // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished
17332 // loading yet, in which case we need to wait for the onload event first, and then connect
17333 // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so
17334 // we need to monitor keyboard events in addition to the oncontextmenu event.
17335 var doConnects = dojo.hitch(this, function(cn){
17336 return [
17337 // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu,
17338 // rather than shift-F10?
17339 dojo.connect(cn, this.leftClickToOpen ? "onclick" : "oncontextmenu", this, function(evt){
17340 // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler
17341 dojo.stopEvent(evt);
17342 this._scheduleOpen(evt.target, iframe, {x: evt.pageX, y: evt.pageY});
17343 }),
17344 dojo.connect(cn, "onkeydown", this, function(evt){
17345 if(evt.shiftKey && evt.keyCode == dojo.keys.F10){
17346 dojo.stopEvent(evt);
17347 this._scheduleOpen(evt.target, iframe); // no coords - open near target node
17348 }
17349 })
81bea17a 17350 ];
a089699c
AD
17351 });
17352 binding.connects = cn ? doConnects(cn) : [];
17353
17354 if(iframe){
17355 // Setup handler to [re]bind to the iframe when the contents are initially loaded,
17356 // and every time the contents change.
17357 // Need to do this b/c we are actually binding to the iframe's <body> node.
17358 // Note: can't use dojo.connect(), see #9609.
17359
17360 binding.onloadHandler = dojo.hitch(this, function(){
17361 // want to remove old connections, but IE throws exceptions when trying to
17362 // access the <body> node because it's already gone, or at least in a state of limbo
17363
17364 var win = this._iframeContentWindow(iframe);
17365 cn = dojo.withGlobal(win, dojo.body);
17366 binding.connects = doConnects(cn);
17367 });
17368 if(iframe.addEventListener){
17369 iframe.addEventListener("load", binding.onloadHandler, false);
17370 }else{
17371 iframe.attachEvent("onload", binding.onloadHandler);
17372 }
17373 }
17374 },
17375
17376 unBindDomNode: function(/*String|DomNode*/ nodeName){
17377 // summary:
17378 // Detach menu from given node
17379
17380 var node;
17381 try{
17382 node = dojo.byId(nodeName);
17383 }catch(e){
17384 // On IE the dojo.byId() call will get an exception if the attach point was
17385 // the <body> node of an <iframe> that has since been reloaded (and thus the
17386 // <body> node is in a limbo state of destruction.
17387 return;
17388 }
17389
17390 // node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array
17391 var attrName = "_dijitMenu" + this.id;
17392 if(node && dojo.hasAttr(node, attrName)){
17393 var bid = dojo.attr(node, attrName)-1, b = this._bindings[bid];
17394 dojo.forEach(b.connects, dojo.disconnect);
17395
17396 // Remove listener for iframe onload events
17397 var iframe = b.iframe;
17398 if(iframe){
17399 if(iframe.removeEventListener){
17400 iframe.removeEventListener("load", b.onloadHandler, false);
17401 }else{
17402 iframe.detachEvent("onload", b.onloadHandler);
17403 }
17404 }
17405
17406 dojo.removeAttr(node, attrName);
17407 delete this._bindings[bid];
17408 }
17409 },
17410
17411 _scheduleOpen: function(/*DomNode?*/ target, /*DomNode?*/ iframe, /*Object?*/ coords){
17412 // summary:
17413 // Set timer to display myself. Using a timer rather than displaying immediately solves
17414 // two problems:
17415 //
17416 // 1. IE: without the delay, focus work in "open" causes the system
17417 // context menu to appear in spite of stopEvent.
17418 //
17419 // 2. Avoid double-shows on linux, where shift-F10 generates an oncontextmenu event
17420 // even after a dojo.stopEvent(e). (Shift-F10 on windows doesn't generate the
17421 // oncontextmenu event.)
17422
17423 if(!this._openTimer){
17424 this._openTimer = setTimeout(dojo.hitch(this, function(){
17425 delete this._openTimer;
17426 this._openMyself({
17427 target: target,
17428 iframe: iframe,
17429 coords: coords
17430 });
17431 }), 1);
17432 }
17433 },
17434
17435 _openMyself: function(args){
17436 // summary:
17437 // Internal function for opening myself when the user does a right-click or something similar.
17438 // args:
17439 // This is an Object containing:
17440 // * target:
17441 // The node that is being clicked
17442 // * iframe:
17443 // If an <iframe> is being clicked, iframe points to that iframe
17444 // * coords:
17445 // Put menu at specified x/y position in viewport, or if iframe is
17446 // specified, then relative to iframe.
17447 //
17448 // _openMyself() formerly took the event object, and since various code references
17449 // evt.target (after connecting to _openMyself()), using an Object for parameters
17450 // (so that old code still works).
17451
17452 var target = args.target,
17453 iframe = args.iframe,
17454 coords = args.coords;
17455
17456 // Get coordinates to open menu, either at specified (mouse) position or (if triggered via keyboard)
17457 // then near the node the menu is assigned to.
17458 if(coords){
17459 if(iframe){
17460 // Specified coordinates are on <body> node of an <iframe>, convert to match main document
17461 var od = target.ownerDocument,
17462 ifc = dojo.position(iframe, true),
17463 win = this._iframeContentWindow(iframe),
17464 scroll = dojo.withGlobal(win, "_docScroll", dojo);
17465
17466 var cs = dojo.getComputedStyle(iframe),
17467 tp = dojo._toPixelValue,
17468 left = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingLeft)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderLeftWidth) : 0),
17469 top = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingTop)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderTopWidth) : 0);
17470
17471 coords.x += ifc.x + left - scroll.x;
17472 coords.y += ifc.y + top - scroll.y;
17473 }
17474 }else{
17475 coords = dojo.position(target, true);
17476 coords.x += 10;
17477 coords.y += 10;
17478 }
17479
17480 var self=this;
17481 var savedFocus = dijit.getFocus(this);
17482 function closeAndRestoreFocus(){
17483 // user has clicked on a menu or popup
17484 if(self.refocus){
17485 dijit.focus(savedFocus);
17486 }
17487 dijit.popup.close(self);
17488 }
17489 dijit.popup.open({
17490 popup: this,
17491 x: coords.x,
17492 y: coords.y,
17493 onExecute: closeAndRestoreFocus,
17494 onCancel: closeAndRestoreFocus,
17495 orient: this.isLeftToRight() ? 'L' : 'R'
17496 });
17497 this.focus();
17498
17499 this._onBlur = function(){
17500 this.inherited('_onBlur', arguments);
17501 // Usually the parent closes the child widget but if this is a context
17502 // menu then there is no parent
17503 dijit.popup.close(this);
17504 // don't try to restore focus; user has clicked another part of the screen
17505 // and set focus there
17506 };
17507 },
17508
17509 uninitialize: function(){
17510 dojo.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this);
17511 this.inherited(arguments);
17512 }
17513}
17514);
17515
a089699c
AD
17516}
17517
17518if(!dojo._hasResource["dijit.form.Select"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
17519dojo._hasResource["dijit.form.Select"] = true;
17520dojo.provide("dijit.form.Select");
17521
17522
17523
17524
17525
17526
17527
a089699c
AD
17528dojo.declare("dijit.form._SelectMenu", dijit.Menu, {
17529 // summary:
17530 // An internally-used menu for dropdown that allows us a vertical scrollbar
17531 buildRendering: function(){
17532 // summary:
17533 // Stub in our own changes, so that our domNode is not a table
17534 // otherwise, we won't respond correctly to heights/overflows
17535 this.inherited(arguments);
17536 var o = (this.menuTableNode = this.domNode);
17537 var n = (this.domNode = dojo.create("div", {style: {overflowX: "hidden", overflowY: "scroll"}}));
17538 if(o.parentNode){
17539 o.parentNode.replaceChild(n, o);
17540 }
17541 dojo.removeClass(o, "dijitMenuTable");
17542 n.className = o.className + " dijitSelectMenu";
17543 o.className = "dijitReset dijitMenuTable";
17544 dijit.setWaiRole(o,"listbox");
17545 dijit.setWaiRole(n,"presentation");
17546 n.appendChild(o);
17547 },
81bea17a
AD
17548
17549 postCreate: function(){
17550 // summary:
17551 // stop mousemove from selecting text on IE to be consistent with other browsers
17552
17553 this.inherited(arguments);
17554
17555 this.connect(this.domNode, "onmousemove", dojo.stopEvent);
17556 },
17557
a089699c
AD
17558 resize: function(/*Object*/ mb){
17559 // summary:
17560 // Overridden so that we are able to handle resizing our
17561 // internal widget. Note that this is not a "full" resize
17562 // implementation - it only works correctly if you pass it a
17563 // marginBox.
17564 //
17565 // mb: Object
17566 // The margin box to set this dropdown to.
17567 if(mb){
17568 dojo.marginBox(this.domNode, mb);
17569 if("w" in mb){
17570 // We've explicitly set the wrapper <div>'s width, so set <table> width to match.
17571 // 100% is safer than a pixel value because there may be a scroll bar with
17572 // browser/OS specific width.
17573 this.menuTableNode.style.width = "100%";
17574 }
17575 }
17576 }
17577});
17578
17579dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropDown], {
17580 // summary:
17581 // This is a "styleable" select box - it is basically a DropDownButton which
17582 // can take a <select> as its input.
17583
17584 baseClass: "dijitSelect",
17585
81bea17a 17586 templateString: dojo.cache("dijit.form", "templates/Select.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdojoAttachPoint=\"_buttonNode,tableNode,focusNode\" cellspacing='0' cellpadding='0'\n\trole=\"combobox\" aria-haspopup=\"true\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents dijitButtonNode\" role=\"presentation\"\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\" dojoAttachPoint=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} dojoAttachPoint=\"valueNode\" value=\"${value}\" aria-hidden=\"true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdojoAttachPoint=\"titleNode\" role=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">&#9660;</div\n\t\t></td\n\t></tr></tbody\n></table>\n"),
a089699c
AD
17587
17588 // attributeMap: Object
17589 // Add in our style to be applied to the focus node
17590 attributeMap: dojo.mixin(dojo.clone(dijit.form._FormSelectWidget.prototype.attributeMap),{style:"tableNode"}),
17591
17592 // required: Boolean
17593 // Can be true or false, default is false.
17594 required: false,
17595
17596 // state: String
17597 // Shows current state (ie, validation result) of input (Normal, Warning, or Error)
17598 state: "",
17599
81bea17a
AD
17600 // message: String
17601 // Currently displayed error/prompt message
17602 message: "",
17603
a089699c
AD
17604 // tooltipPosition: String[]
17605 // See description of dijit.Tooltip.defaultPosition for details on this parameter.
17606 tooltipPosition: [],
17607
17608 // emptyLabel: string
17609 // What to display in an "empty" dropdown
81bea17a 17610 emptyLabel: "&nbsp;",
a089699c
AD
17611
17612 // _isLoaded: Boolean
17613 // Whether or not we have been loaded
17614 _isLoaded: false,
17615
17616 // _childrenLoaded: Boolean
17617 // Whether or not our children have been loaded
17618 _childrenLoaded: false,
17619
17620 _fillContent: function(){
17621 // summary:
17622 // Set the value to be the first, or the selected index
17623 this.inherited(arguments);
81bea17a 17624 // set value from selected option
a089699c 17625 if(this.options.length && !this.value && this.srcNodeRef){
81bea17a
AD
17626 var si = this.srcNodeRef.selectedIndex || 0; // || 0 needed for when srcNodeRef is not a SELECT
17627 this.value = this.options[si >= 0 ? si : 0].value;
a089699c 17628 }
a089699c
AD
17629 // Create the dropDown widget
17630 this.dropDown = new dijit.form._SelectMenu({id: this.id + "_menu"});
17631 dojo.addClass(this.dropDown.domNode, this.baseClass + "Menu");
17632 },
17633
17634 _getMenuItemForOption: function(/*dijit.form.__SelectOption*/ option){
17635 // summary:
17636 // For the given option, return the menu item that should be
17637 // used to display it. This can be overridden as needed
81bea17a 17638 if(!option.value && !option.label){
a089699c
AD
17639 // We are a separator (no label set for it)
17640 return new dijit.MenuSeparator();
17641 }else{
17642 // Just a regular menu option
17643 var click = dojo.hitch(this, "_setValueAttr", option);
17644 var item = new dijit.MenuItem({
17645 option: option,
81bea17a 17646 label: option.label || this.emptyLabel,
a089699c
AD
17647 onClick: click,
17648 disabled: option.disabled || false
17649 });
17650 dijit.setWaiRole(item.focusNode, "listitem");
17651 return item;
17652 }
17653 },
17654
17655 _addOptionItem: function(/*dijit.form.__SelectOption*/ option){
17656 // summary:
17657 // For the given option, add an option to our dropdown.
17658 // If the option doesn't have a value, then a separator is added
17659 // in that place.
17660 if(this.dropDown){
17661 this.dropDown.addChild(this._getMenuItemForOption(option));
17662 }
17663 },
17664
17665 _getChildren: function(){
17666 if(!this.dropDown){
17667 return [];
17668 }
17669 return this.dropDown.getChildren();
17670 },
17671
17672 _loadChildren: function(/*Boolean*/ loadMenuItems){
17673 // summary:
17674 // Resets the menu and the length attribute of the button - and
17675 // ensures that the label is appropriately set.
17676 // loadMenuItems: Boolean
17677 // actually loads the child menu items - we only do this when we are
17678 // populating for showing the dropdown.
17679
17680 if(loadMenuItems === true){
17681 // this.inherited destroys this.dropDown's child widgets (MenuItems).
17682 // Avoid this.dropDown (Menu widget) having a pointer to a destroyed widget (which will cause
17683 // issues later in _setSelected). (see #10296)
17684 if(this.dropDown){
17685 delete this.dropDown.focusedChild;
17686 }
17687 if(this.options.length){
17688 this.inherited(arguments);
17689 }else{
17690 // Drop down menu is blank but add one blank entry just so something appears on the screen
17691 // to let users know that they are no choices (mimicing native select behavior)
17692 dojo.forEach(this._getChildren(), function(child){ child.destroyRecursive(); });
17693 var item = new dijit.MenuItem({label: "&nbsp;"});
17694 this.dropDown.addChild(item);
17695 }
17696 }else{
17697 this._updateSelection();
17698 }
17699
a089699c
AD
17700 this._isLoaded = false;
17701 this._childrenLoaded = true;
17702
17703 if(!this._loadingStore){
17704 // Don't call this if we are loading - since we will handle it later
17705 this._setValueAttr(this.value);
17706 }
17707 },
17708
17709 _setValueAttr: function(value){
17710 this.inherited(arguments);
17711 dojo.attr(this.valueNode, "value", this.get("value"));
17712 },
17713
17714 _setDisplay: function(/*String*/ newDisplay){
17715 // summary:
17716 // sets the display for the given value (or values)
81bea17a
AD
17717 var lbl = newDisplay || this.emptyLabel;
17718 this.containerNode.innerHTML = '<span class="dijitReset dijitInline ' + this.baseClass + 'Label">' + lbl + '</span>';
17719 dijit.setWaiState(this.focusNode, "valuetext", lbl);
a089699c
AD
17720 },
17721
17722 validate: function(/*Boolean*/ isFocused){
17723 // summary:
17724 // Called by oninit, onblur, and onkeypress.
17725 // description:
17726 // Show missing or invalid messages if appropriate, and highlight textbox field.
17727 // Used when a select is initially set to no value and the user is required to
17728 // set the value.
17729
17730 var isValid = this.isValid(isFocused);
81bea17a 17731 this._set("state", isValid ? "" : "Error");
a089699c
AD
17732 dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true");
17733 var message = isValid ? "" : this._missingMsg;
81bea17a
AD
17734 if(this.message !== message){
17735 this._set("message", message);
a089699c
AD
17736 dijit.hideTooltip(this.domNode);
17737 if(message){
17738 dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
17739 }
17740 }
17741 return isValid;
17742 },
17743
17744 isValid: function(/*Boolean*/ isFocused){
17745 // summary:
81bea17a 17746 // Whether or not this is a valid value. The only way a Select
a089699c 17747 // can be invalid is when it's required but nothing is selected.
81bea17a 17748 return (!this.required || this.value === 0 || !(/^\s*$/.test(this.value || ""))); // handle value is null or undefined
a089699c
AD
17749 },
17750
17751 reset: function(){
17752 // summary:
17753 // Overridden so that the state will be cleared.
17754 this.inherited(arguments);
17755 dijit.hideTooltip(this.domNode);
81bea17a
AD
17756 this._set("state", "");
17757 this._set("message", "")
a089699c
AD
17758 },
17759
17760 postMixInProperties: function(){
17761 // summary:
17762 // set the missing message
17763 this.inherited(arguments);
17764 this._missingMsg = dojo.i18n.getLocalization("dijit.form", "validate",
17765 this.lang).missingMessage;
17766 },
17767
17768 postCreate: function(){
81bea17a
AD
17769 // summary:
17770 // stop mousemove from selecting text on IE to be consistent with other browsers
17771
a089699c 17772 this.inherited(arguments);
81bea17a
AD
17773
17774 this.connect(this.domNode, "onmousemove", dojo.stopEvent);
17775 },
17776
17777 _setStyleAttr: function(/*String||Object*/ value){
17778 this.inherited(arguments);
17779 dojo.toggleClass(this.domNode, this.baseClass + "FixedWidth", !!this.tableNode.style.width);
a089699c
AD
17780 },
17781
17782 isLoaded: function(){
17783 return this._isLoaded;
17784 },
17785
17786 loadDropDown: function(/*Function*/ loadCallback){
17787 // summary:
17788 // populates the menu
17789 this._loadChildren(true);
17790 this._isLoaded = true;
17791 loadCallback();
17792 },
17793
17794 closeDropDown: function(){
17795 // overriding _HasDropDown.closeDropDown()
17796 this.inherited(arguments);
17797
17798 if(this.dropDown && this.dropDown.menuTableNode){
17799 // Erase possible width: 100% setting from _SelectMenu.resize().
17800 // Leaving it would interfere with the next openDropDown() call, which
17801 // queries the natural size of the drop down.
17802 this.dropDown.menuTableNode.style.width = "";
17803 }
17804 },
17805
17806 uninitialize: function(preserveDom){
17807 if(this.dropDown && !this.dropDown._destroyed){
17808 this.dropDown.destroyRecursive(preserveDom);
17809 delete this.dropDown;
17810 }
17811 this.inherited(arguments);
17812 }
17813});
17814
17815}
17816
17817if(!dojo._hasResource["dijit.form.SimpleTextarea"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
17818dojo._hasResource["dijit.form.SimpleTextarea"] = true;
17819dojo.provide("dijit.form.SimpleTextarea");
17820
17821
17822
17823dojo.declare("dijit.form.SimpleTextarea",
17824 dijit.form.TextBox,
17825 {
17826 // summary:
17827 // A simple textarea that degrades, and responds to
17828 // minimal LayoutContainer usage, and works with dijit.form.Form.
17829 // Doesn't automatically size according to input, like Textarea.
17830 //
17831 // example:
17832 // | <textarea dojoType="dijit.form.SimpleTextarea" name="foo" value="bar" rows=30 cols=40></textarea>
17833 //
17834 // example:
17835 // | new dijit.form.SimpleTextarea({ rows:20, cols:30 }, "foo");
17836
17837 baseClass: "dijitTextBox dijitTextArea",
17838
17839 attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, {
17840 rows:"textbox", cols: "textbox"
17841 }),
17842
17843 // rows: Number
17844 // The number of rows of text.
17845 rows: "3",
17846
17847 // rows: Number
17848 // The number of characters per line.
17849 cols: "20",
17850
17851 templateString: "<textarea ${!nameAttrSetting} dojoAttachPoint='focusNode,containerNode,textbox' autocomplete='off'></textarea>",
17852
17853 postMixInProperties: function(){
17854 // Copy value from srcNodeRef, unless user specified a value explicitly (or there is no srcNodeRef)
81bea17a 17855 // TODO: parser will handle this in 2.0
a089699c
AD
17856 if(!this.value && this.srcNodeRef){
17857 this.value = this.srcNodeRef.value;
17858 }
17859 this.inherited(arguments);
17860 },
17861
81bea17a
AD
17862 buildRendering: function(){
17863 this.inherited(arguments);
17864 if(dojo.isIE && this.cols){ // attribute selectors is not supported in IE6
17865 dojo.addClass(this.textbox, "dijitTextAreaCols");
17866 }
17867 },
17868
a089699c
AD
17869 filter: function(/*String*/ value){
17870 // Override TextBox.filter to deal with newlines... specifically (IIRC) this is for IE which writes newlines
17871 // as \r\n instead of just \n
17872 if(value){
17873 value = value.replace(/\r/g,"");
17874 }
17875 return this.inherited(arguments);
17876 },
17877
a089699c
AD
17878 _previousValue: "",
17879 _onInput: function(/*Event?*/ e){
17880 // Override TextBox._onInput() to enforce maxLength restriction
17881 if(this.maxLength){
17882 var maxLength = parseInt(this.maxLength);
17883 var value = this.textbox.value.replace(/\r/g,'');
17884 var overflow = value.length - maxLength;
17885 if(overflow > 0){
17886 if(e){ dojo.stopEvent(e); }
17887 var textarea = this.textbox;
17888 if(textarea.selectionStart){
17889 var pos = textarea.selectionStart;
17890 var cr = 0;
17891 if(dojo.isOpera){
17892 cr = (this.textbox.value.substring(0,pos).match(/\r/g) || []).length;
17893 }
17894 this.textbox.value = value.substring(0,pos-overflow-cr)+value.substring(pos-cr);
17895 textarea.setSelectionRange(pos-overflow, pos-overflow);
17896 }else if(dojo.doc.selection){ //IE
17897 textarea.focus();
17898 var range = dojo.doc.selection.createRange();
17899 // delete overflow characters
17900 range.moveStart("character", -overflow);
17901 range.text = '';
17902 // show cursor
17903 range.select();
17904 }
17905 }
17906 this._previousValue = this.textbox.value;
17907 }
17908 this.inherited(arguments);
17909 }
17910});
17911
17912}
17913
17914if(!dojo._hasResource["dijit.InlineEditBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
17915dojo._hasResource["dijit.InlineEditBox"] = true;
17916dojo.provide("dijit.InlineEditBox");
17917
17918
17919
17920
17921
17922
17923
17924
a089699c
AD
17925dojo.declare("dijit.InlineEditBox",
17926 dijit._Widget,
17927 {
17928 // summary:
17929 // An element with in-line edit capabilites
17930 //
17931 // description:
17932 // Behavior for an existing node (`<p>`, `<div>`, `<span>`, etc.) so that
17933 // when you click it, an editor shows up in place of the original
17934 // text. Optionally, Save and Cancel button are displayed below the edit widget.
17935 // When Save is clicked, the text is pulled from the edit
17936 // widget and redisplayed and the edit widget is again hidden.
17937 // By default a plain Textarea widget is used as the editor (or for
17938 // inline values a TextBox), but you can specify an editor such as
17939 // dijit.Editor (for editing HTML) or a Slider (for adjusting a number).
17940 // An edit widget must support the following API to be used:
17941 // - displayedValue or value as initialization parameter,
17942 // and available through set('displayedValue') / set('value')
17943 // - void focus()
17944 // - DOM-node focusNode = node containing editable text
17945
17946 // editing: [readonly] Boolean
17947 // Is the node currently in edit mode?
17948 editing: false,
17949
17950 // autoSave: Boolean
17951 // Changing the value automatically saves it; don't have to push save button
17952 // (and save button isn't even displayed)
17953 autoSave: true,
17954
17955 // buttonSave: String
17956 // Save button label
17957 buttonSave: "",
17958
17959 // buttonCancel: String
17960 // Cancel button label
17961 buttonCancel: "",
17962
17963 // renderAsHtml: Boolean
17964 // Set this to true if the specified Editor's value should be interpreted as HTML
17965 // rather than plain text (ex: `dijit.Editor`)
17966 renderAsHtml: false,
17967
81bea17a
AD
17968 // editor: String|Function
17969 // Class name (or reference to the Class) for Editor widget
a089699c
AD
17970 editor: "dijit.form.TextBox",
17971
81bea17a
AD
17972 // editorWrapper: String|Function
17973 // Class name (or reference to the Class) for widget that wraps the editor widget, displaying save/cancel
a089699c
AD
17974 // buttons.
17975 editorWrapper: "dijit._InlineEditor",
17976
17977 // editorParams: Object
17978 // Set of parameters for editor, like {required: true}
17979 editorParams: {},
17980
81bea17a
AD
17981 // disabled: Boolean
17982 // If true, clicking the InlineEditBox to edit it will have no effect.
17983 disabled: false,
17984
a089699c
AD
17985 onChange: function(value){
17986 // summary:
17987 // Set this handler to be notified of changes to value.
17988 // tags:
17989 // callback
17990 },
17991
17992 onCancel: function(){
17993 // summary:
17994 // Set this handler to be notified when editing is cancelled.
17995 // tags:
17996 // callback
17997 },
17998
17999 // width: String
18000 // Width of editor. By default it's width=100% (ie, block mode).
18001 width: "100%",
18002
18003 // value: String
18004 // The display value of the widget in read-only mode
18005 value: "",
18006
18007 // noValueIndicator: [const] String
18008 // The text that gets displayed when there is no value (so that the user has a place to click to edit)
18009 noValueIndicator: dojo.isIE <= 6 ? // font-family needed on IE6 but it messes up IE8
18010 "<span style='font-family: wingdings; text-decoration: underline;'>&nbsp;&nbsp;&nbsp;&nbsp;&#x270d;&nbsp;&nbsp;&nbsp;&nbsp;</span>" :
18011 "<span style='text-decoration: underline;'>&nbsp;&nbsp;&nbsp;&nbsp;&#x270d;&nbsp;&nbsp;&nbsp;&nbsp;</span>",
18012
18013 constructor: function(){
18014 // summary:
18015 // Sets up private arrays etc.
18016 // tags:
18017 // private
18018 this.editorParams = {};
18019 },
18020
18021 postMixInProperties: function(){
18022 this.inherited(arguments);
18023
18024 // save pointer to original source node, since Widget nulls-out srcNodeRef
18025 this.displayNode = this.srcNodeRef;
18026
18027 // connect handlers to the display node
18028 var events = {
18029 ondijitclick: "_onClick",
18030 onmouseover: "_onMouseOver",
18031 onmouseout: "_onMouseOut",
18032 onfocus: "_onMouseOver",
18033 onblur: "_onMouseOut"
18034 };
18035 for(var name in events){
18036 this.connect(this.displayNode, name, events[name]);
18037 }
18038 dijit.setWaiRole(this.displayNode, "button");
18039 if(!this.displayNode.getAttribute("tabIndex")){
18040 this.displayNode.setAttribute("tabIndex", 0);
18041 }
18042
18043 if(!this.value && !("value" in this.params)){ // "" is a good value if specified directly so check params){
18044 this.value = dojo.trim(this.renderAsHtml ? this.displayNode.innerHTML :
18045 (this.displayNode.innerText||this.displayNode.textContent||""));
18046 }
18047 if(!this.value){
18048 this.displayNode.innerHTML = this.noValueIndicator;
18049 }
18050
18051 dojo.addClass(this.displayNode, 'dijitInlineEditBoxDisplayMode');
18052 },
18053
18054 setDisabled: function(/*Boolean*/ disabled){
18055 // summary:
18056 // Deprecated. Use set('disabled', ...) instead.
18057 // tags:
18058 // deprecated
18059 dojo.deprecated("dijit.InlineEditBox.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0");
18060 this.set('disabled', disabled);
18061 },
18062
18063 _setDisabledAttr: function(/*Boolean*/ disabled){
18064 // summary:
18065 // Hook to make set("disabled", ...) work.
18066 // Set disabled state of widget.
a089699c
AD
18067 dijit.setWaiState(this.domNode, "disabled", disabled);
18068 if(disabled){
18069 this.displayNode.removeAttribute("tabIndex");
18070 }else{
18071 this.displayNode.setAttribute("tabIndex", 0);
18072 }
18073 dojo.toggleClass(this.displayNode, "dijitInlineEditBoxDisplayModeDisabled", disabled);
81bea17a 18074 this._set("disabled", disabled);
a089699c
AD
18075 },
18076
18077 _onMouseOver: function(){
18078 // summary:
18079 // Handler for onmouseover and onfocus event.
18080 // tags:
18081 // private
18082 if(!this.disabled){
18083 dojo.addClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover");
18084 }
18085 },
18086
18087 _onMouseOut: function(){
18088 // summary:
18089 // Handler for onmouseout and onblur event.
18090 // tags:
18091 // private
18092 dojo.removeClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover");
18093 },
18094
18095 _onClick: function(/*Event*/ e){
18096 // summary:
18097 // Handler for onclick event.
18098 // tags:
18099 // private
18100 if(this.disabled){ return; }
18101 if(e){ dojo.stopEvent(e); }
18102 this._onMouseOut();
18103
18104 // Since FF gets upset if you move a node while in an event handler for that node...
18105 setTimeout(dojo.hitch(this, "edit"), 0);
18106 },
18107
18108 edit: function(){
18109 // summary:
18110 // Display the editor widget in place of the original (read only) markup.
18111 // tags:
18112 // private
18113
18114 if(this.disabled || this.editing){ return; }
18115 this.editing = true;
18116
18117 // save some display node values that can be restored later
18118 this._savedPosition = dojo.style(this.displayNode, "position") || "static";
18119 this._savedOpacity = dojo.style(this.displayNode, "opacity") || "1";
18120 this._savedTabIndex = dojo.attr(this.displayNode, "tabIndex") || "0";
18121
18122 if(this.wrapperWidget){
18123 var ew = this.wrapperWidget.editWidget;
18124 ew.set("displayedValue" in ew ? "displayedValue" : "value", this.value);
18125 }else{
18126 // Placeholder for edit widget
18127 // Put place holder (and eventually editWidget) before the display node so that it's positioned correctly
18128 // when Calendar dropdown appears, which happens automatically on focus.
18129 var placeholder = dojo.create("span", null, this.domNode, "before");
18130
18131 // Create the editor wrapper (the thing that holds the editor widget and the save/cancel buttons)
81bea17a 18132 var ewc = typeof this.editorWrapper == "string" ? dojo.getObject(this.editorWrapper) : this.editorWrapper;
a089699c
AD
18133 this.wrapperWidget = new ewc({
18134 value: this.value,
18135 buttonSave: this.buttonSave,
18136 buttonCancel: this.buttonCancel,
18137 dir: this.dir,
18138 lang: this.lang,
18139 tabIndex: this._savedTabIndex,
18140 editor: this.editor,
18141 inlineEditBox: this,
18142 sourceStyle: dojo.getComputedStyle(this.displayNode),
18143 save: dojo.hitch(this, "save"),
18144 cancel: dojo.hitch(this, "cancel")
18145 }, placeholder);
81bea17a
AD
18146 if(!this._started){
18147 this.startup();
18148 }
a089699c
AD
18149 }
18150 var ww = this.wrapperWidget;
18151
18152 if(dojo.isIE){
18153 dijit.focus(dijit.getFocus()); // IE (at least 8) needs help with tab order changes
18154 }
18155 // to avoid screen jitter, we first create the editor with position:absolute, visibility:hidden,
18156 // and then when it's finished rendering, we switch from display mode to editor
18157 // position:absolute releases screen space allocated to the display node
18158 // opacity:0 is the same as visibility:hidden but is still focusable
18159 // visiblity:hidden removes focus outline
18160
18161 dojo.style(this.displayNode, { position: "absolute", opacity: "0", display: "none" }); // makes display node invisible, display style used for focus-ability
18162 dojo.style(ww.domNode, { position: this._savedPosition, visibility: "visible", opacity: "1" });
18163 dojo.attr(this.displayNode, "tabIndex", "-1"); // needed by WebKit for TAB from editor to skip displayNode
18164
18165 // Replace the display widget with edit widget, leaving them both displayed for a brief time so that
18166 // focus can be shifted without incident. (browser may needs some time to render the editor.)
18167 setTimeout(dojo.hitch(this, function(){
18168 ww.focus(); // both nodes are showing, so we can switch focus safely
18169 ww._resetValue = ww.getValue();
18170 }), 0);
18171 },
18172
18173 _onBlur: function(){
18174 // summary:
18175 // Called when focus moves outside the InlineEditBox.
18176 // Performs garbage collection.
18177 // tags:
18178 // private
18179
18180 this.inherited(arguments);
18181 if(!this.editing){
18182 /* causes IE focus problems, see TooltipDialog_a11y.html...
18183 setTimeout(dojo.hitch(this, function(){
18184 if(this.wrapperWidget){
18185 this.wrapperWidget.destroy();
18186 delete this.wrapperWidget;
18187 }
18188 }), 0);
18189 */
18190 }
18191 },
18192
18193 destroy: function(){
81bea17a 18194 if(this.wrapperWidget && !this.wrapperWidget._destroyed){
a089699c
AD
18195 this.wrapperWidget.destroy();
18196 delete this.wrapperWidget;
18197 }
18198 this.inherited(arguments);
18199 },
18200
18201 _showText: function(/*Boolean*/ focus){
18202 // summary:
18203 // Revert to display mode, and optionally focus on display node
18204 // tags:
18205 // private
18206
18207 var ww = this.wrapperWidget;
18208 dojo.style(ww.domNode, { position: "absolute", visibility: "hidden", opacity: "0" }); // hide the editor from mouse/keyboard events
18209 dojo.style(this.displayNode, { position: this._savedPosition, opacity: this._savedOpacity, display: "" }); // make the original text visible
18210 dojo.attr(this.displayNode, "tabIndex", this._savedTabIndex);
18211 if(focus){
18212 dijit.focus(this.displayNode);
18213 }
18214 },
18215
18216 save: function(/*Boolean*/ focus){
18217 // summary:
18218 // Save the contents of the editor and revert to display mode.
18219 // focus: Boolean
18220 // Focus on the display mode text
18221 // tags:
18222 // private
18223
18224 if(this.disabled || !this.editing){ return; }
18225 this.editing = false;
18226
18227 var ww = this.wrapperWidget;
18228 var value = ww.getValue();
18229 this.set('value', value); // display changed, formatted value
18230
a089699c
AD
18231 this._showText(focus); // set focus as needed
18232 },
18233
18234 setValue: function(/*String*/ val){
18235 // summary:
18236 // Deprecated. Use set('value', ...) instead.
18237 // tags:
18238 // deprecated
18239 dojo.deprecated("dijit.InlineEditBox.setValue() is deprecated. Use set('value', ...) instead.", "", "2.0");
18240 return this.set("value", val);
18241 },
18242
18243 _setValueAttr: function(/*String*/ val){
18244 // summary:
18245 // Hook to make set("value", ...) work.
18246 // Inserts specified HTML value into this node, or an "input needed" character if node is blank.
18247
81bea17a
AD
18248 val = dojo.trim(val);
18249 var renderVal = this.renderAsHtml ? val : val.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;").replace(/\n/g, "<br>");
18250 this.displayNode.innerHTML = renderVal || this.noValueIndicator;
18251 this._set("value", val);
18252
18253 if(this._started){
18254 // tell the world that we have changed
18255 setTimeout(dojo.hitch(this, "onChange", val), 0); // setTimeout prevents browser freeze for long-running event handlers
a089699c 18256 }
a089699c
AD
18257 },
18258
18259 getValue: function(){
18260 // summary:
18261 // Deprecated. Use get('value') instead.
18262 // tags:
18263 // deprecated
18264 dojo.deprecated("dijit.InlineEditBox.getValue() is deprecated. Use get('value') instead.", "", "2.0");
18265 return this.get("value");
18266 },
18267
18268 cancel: function(/*Boolean*/ focus){
18269 // summary:
18270 // Revert to display mode, discarding any changes made in the editor
18271 // tags:
18272 // private
18273
18274 if(this.disabled || !this.editing){ return; }
18275 this.editing = false;
18276
18277 // tell the world that we have no changes
18278 setTimeout(dojo.hitch(this, "onCancel"), 0); // setTimeout prevents browser freeze for long-running event handlers
18279
18280 this._showText(focus);
18281 }
18282});
18283
18284dojo.declare(
18285 "dijit._InlineEditor",
18286 [dijit._Widget, dijit._Templated],
18287{
18288 // summary:
18289 // Internal widget used by InlineEditBox, displayed when in editing mode
18290 // to display the editor and maybe save/cancel buttons. Calling code should
18291 // connect to save/cancel methods to detect when editing is finished
18292 //
18293 // Has mainly the same parameters as InlineEditBox, plus these values:
18294 //
18295 // style: Object
18296 // Set of CSS attributes of display node, to replicate in editor
18297 //
18298 // value: String
18299 // Value as an HTML string or plain text string, depending on renderAsHTML flag
18300
81bea17a 18301 templateString: dojo.cache("dijit", "templates/InlineEditBox.html", "<span data-dojo-attach-point=\"editNode\" role=\"presentation\" style=\"position: absolute; visibility:hidden\" class=\"dijitReset dijitInline\"\n\tdata-dojo-attach-event=\"onkeypress: _onKeyPress\"\n\t><span data-dojo-attach-point=\"editorPlaceholder\"></span\n\t><span data-dojo-attach-point=\"buttonContainer\"\n\t\t><button data-dojo-type=\"dijit.form.Button\" data-dojo-props=\"label: '${buttonSave}', 'class': 'saveButton'\"\n\t\t\tdata-dojo-attach-point=\"saveButton\" data-dojo-attach-event=\"onClick:save\"></button\n\t\t><button data-dojo-type=\"dijit.form.Button\" data-dojo-props=\"label: '${buttonCancel}', 'class': 'cancelButton'\"\n\t\t\tdata-dojo-attach-point=\"cancelButton\" data-dojo-attach-event=\"onClick:cancel\"></button\n\t></span\n></span>\n"),
a089699c
AD
18302 widgetsInTemplate: true,
18303
18304 postMixInProperties: function(){
18305 this.inherited(arguments);
18306 this.messages = dojo.i18n.getLocalization("dijit", "common", this.lang);
18307 dojo.forEach(["buttonSave", "buttonCancel"], function(prop){
18308 if(!this[prop]){ this[prop] = this.messages[prop]; }
18309 }, this);
18310 },
18311
81bea17a
AD
18312 buildRendering: function(){
18313 this.inherited(arguments);
18314
a089699c 18315 // Create edit widget in place in the template
81bea17a 18316 var cls = typeof this.editor == "string" ? dojo.getObject(this.editor) : this.editor;
a089699c
AD
18317
18318 // Copy the style from the source
18319 // Don't copy ALL properties though, just the necessary/applicable ones.
18320 // wrapperStyle/destStyle code is to workaround IE bug where getComputedStyle().fontSize
18321 // is a relative value like 200%, rather than an absolute value like 24px, and
18322 // the 200% can refer *either* to a setting on the node or it's ancestor (see #11175)
18323 var srcStyle = this.sourceStyle,
18324 editStyle = "line-height:" + srcStyle.lineHeight + ";",
18325 destStyle = dojo.getComputedStyle(this.domNode);
18326 dojo.forEach(["Weight","Family","Size","Style"], function(prop){
18327 var textStyle = srcStyle["font"+prop],
18328 wrapperStyle = destStyle["font"+prop];
18329 if(wrapperStyle != textStyle){
18330 editStyle += "font-"+prop+":"+srcStyle["font"+prop]+";";
18331 }
18332 }, this);
18333 dojo.forEach(["marginTop","marginBottom","marginLeft", "marginRight"], function(prop){
18334 this.domNode.style[prop] = srcStyle[prop];
18335 }, this);
18336 var width = this.inlineEditBox.width;
18337 if(width == "100%"){
18338 // block mode
18339 editStyle += "width:100%;";
18340 this.domNode.style.display = "block";
18341 }else{
18342 // inline-block mode
18343 editStyle += "width:" + (width + (Number(width) == width ? "px" : "")) + ";";
18344 }
18345 var editorParams = dojo.delegate(this.inlineEditBox.editorParams, {
18346 style: editStyle,
18347 dir: this.dir,
18348 lang: this.lang
18349 });
18350 editorParams[ "displayedValue" in cls.prototype ? "displayedValue" : "value"] = this.value;
81bea17a 18351 this.editWidget = new cls(editorParams, this.editorPlaceholder);
a089699c
AD
18352
18353 if(this.inlineEditBox.autoSave){
18354 // Remove the save/cancel buttons since saving is done by simply tabbing away or
18355 // selecting a value from the drop down list
18356 dojo.destroy(this.buttonContainer);
81bea17a
AD
18357 }
18358 },
18359
18360 postCreate: function(){
18361 this.inherited(arguments);
18362
18363 var ew = this.editWidget;
a089699c 18364
81bea17a 18365 if(this.inlineEditBox.autoSave){
a089699c
AD
18366 // Selecting a value from a drop down list causes an onChange event and then we save
18367 this.connect(ew, "onChange", "_onChange");
18368
18369 // ESC and TAB should cancel and save. Note that edit widgets do a stopEvent() on ESC key (to
18370 // prevent Dialog from closing when the user just wants to revert the value in the edit widget),
18371 // so this is the only way we can see the key press event.
18372 this.connect(ew, "onKeyPress", "_onKeyPress");
18373 }else{
18374 // If possible, enable/disable save button based on whether the user has changed the value
81bea17a 18375 if("intermediateChanges" in ew){
a089699c
AD
18376 ew.set("intermediateChanges", true);
18377 this.connect(ew, "onChange", "_onIntermediateChange");
18378 this.saveButton.set("disabled", true);
18379 }
18380 }
18381 },
18382
18383 _onIntermediateChange: function(val){
18384 // summary:
18385 // Called for editor widgets that support the intermediateChanges=true flag as a way
18386 // to detect when to enable/disabled the save button
18387 this.saveButton.set("disabled", (this.getValue() == this._resetValue) || !this.enableSave());
18388 },
18389
18390 destroy: function(){
18391 this.editWidget.destroy(true); // let the parent wrapper widget clean up the DOM
18392 this.inherited(arguments);
18393 },
18394
18395 getValue: function(){
18396 // summary:
18397 // Return the [display] value of the edit widget
18398 var ew = this.editWidget;
18399 return String(ew.get("displayedValue" in ew ? "displayedValue" : "value"));
18400 },
18401
18402 _onKeyPress: function(e){
18403 // summary:
18404 // Handler for keypress in the edit box in autoSave mode.
18405 // description:
18406 // For autoSave widgets, if Esc/Enter, call cancel/save.
18407 // tags:
18408 // private
18409
18410 if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){
18411 if(e.altKey || e.ctrlKey){ return; }
18412 // If Enter/Esc pressed, treat as save/cancel.
18413 if(e.charOrCode == dojo.keys.ESCAPE){
18414 dojo.stopEvent(e);
18415 this.cancel(true); // sets editing=false which short-circuits _onBlur processing
18416 }else if(e.charOrCode == dojo.keys.ENTER && e.target.tagName == "INPUT"){
18417 dojo.stopEvent(e);
18418 this._onChange(); // fire _onBlur and then save
18419 }
18420
18421 // _onBlur will handle TAB automatically by allowing
18422 // the TAB to change focus before we mess with the DOM: #6227
18423 // Expounding by request:
18424 // The current focus is on the edit widget input field.
18425 // save() will hide and destroy this widget.
18426 // We want the focus to jump from the currently hidden
18427 // displayNode, but since it's hidden, it's impossible to
18428 // unhide it, focus it, and then have the browser focus
18429 // away from it to the next focusable element since each
18430 // of these events is asynchronous and the focus-to-next-element
18431 // is already queued.
18432 // So we allow the browser time to unqueue the move-focus event
18433 // before we do all the hide/show stuff.
18434 }
18435 },
18436
18437 _onBlur: function(){
18438 // summary:
18439 // Called when focus moves outside the editor
18440 // tags:
18441 // private
18442
18443 this.inherited(arguments);
18444 if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){
18445 if(this.getValue() == this._resetValue){
18446 this.cancel(false);
18447 }else if(this.enableSave()){
18448 this.save(false);
18449 }
18450 }
18451 },
18452
18453 _onChange: function(){
18454 // summary:
18455 // Called when the underlying widget fires an onChange event,
18456 // such as when the user selects a value from the drop down list of a ComboBox,
18457 // which means that the user has finished entering the value and we should save.
18458 // tags:
18459 // private
18460
18461 if(this.inlineEditBox.autoSave && this.inlineEditBox.editing && this.enableSave()){
18462 dojo.style(this.inlineEditBox.displayNode, { display: "" });
18463 dijit.focus(this.inlineEditBox.displayNode); // fires _onBlur which will save the formatted value
18464 }
18465 },
18466
18467 enableSave: function(){
18468 // summary:
18469 // User overridable function returning a Boolean to indicate
18470 // if the Save button should be enabled or not - usually due to invalid conditions
18471 // tags:
18472 // extension
18473 return (
18474 this.editWidget.isValid
18475 ? this.editWidget.isValid()
18476 : true
18477 );
18478 },
18479
18480 focus: function(){
18481 // summary:
18482 // Focus the edit widget.
18483 // tags:
18484 // protected
18485
18486 this.editWidget.focus();
18487 setTimeout(dojo.hitch(this, function(){
18488 if(this.editWidget.focusNode && this.editWidget.focusNode.tagName == "INPUT"){
18489 dijit.selectInputText(this.editWidget.focusNode);
18490 }
18491 }), 0);
18492 }
18493});
18494
18495}
18496
18497if(!dojo._hasResource["dojo.cookie"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
18498dojo._hasResource["dojo.cookie"] = true;
18499dojo.provide("dojo.cookie");
18500
18501
18502
18503/*=====
18504dojo.__cookieProps = function(){
18505 // expires: Date|String|Number?
18506 // If a number, the number of days from today at which the cookie
18507 // will expire. If a date, the date past which the cookie will expire.
18508 // If expires is in the past, the cookie will be deleted.
18509 // If expires is omitted or is 0, the cookie will expire when the browser closes. << FIXME: 0 seems to disappear right away? FF3.
18510 // path: String?
18511 // The path to use for the cookie.
18512 // domain: String?
18513 // The domain to use for the cookie.
18514 // secure: Boolean?
18515 // Whether to only send the cookie on secure connections
18516 this.expires = expires;
18517 this.path = path;
18518 this.domain = domain;
18519 this.secure = secure;
18520}
18521=====*/
18522
18523
18524dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/props){
81bea17a 18525 // summary:
a089699c
AD
18526 // Get or set a cookie.
18527 // description:
18528 // If one argument is passed, returns the value of the cookie
18529 // For two or more arguments, acts as a setter.
18530 // name:
18531 // Name of the cookie
18532 // value:
18533 // Value for the cookie
81bea17a 18534 // props:
a089699c
AD
18535 // Properties for the cookie
18536 // example:
18537 // set a cookie with the JSON-serialized contents of an object which
18538 // will expire 5 days from now:
18539 // | dojo.cookie("configObj", dojo.toJson(config), { expires: 5 });
81bea17a 18540 //
a089699c
AD
18541 // example:
18542 // de-serialize a cookie back into a JavaScript object:
18543 // | var config = dojo.fromJson(dojo.cookie("configObj"));
81bea17a 18544 //
a089699c
AD
18545 // example:
18546 // delete a cookie:
18547 // | dojo.cookie("configObj", null, {expires: -1});
18548 var c = document.cookie;
18549 if(arguments.length == 1){
18550 var matches = c.match(new RegExp("(?:^|; )" + dojo.regexp.escapeString(name) + "=([^;]*)"));
18551 return matches ? decodeURIComponent(matches[1]) : undefined; // String or undefined
18552 }else{
18553 props = props || {};
18554// FIXME: expires=0 seems to disappear right away, not on close? (FF3) Change docs?
18555 var exp = props.expires;
81bea17a 18556 if(typeof exp == "number"){
a089699c
AD
18557 var d = new Date();
18558 d.setTime(d.getTime() + exp*24*60*60*1000);
18559 exp = props.expires = d;
18560 }
18561 if(exp && exp.toUTCString){ props.expires = exp.toUTCString(); }
18562
18563 value = encodeURIComponent(value);
18564 var updatedCookie = name + "=" + value, propName;
18565 for(propName in props){
18566 updatedCookie += "; " + propName;
18567 var propValue = props[propName];
18568 if(propValue !== true){ updatedCookie += "=" + propValue; }
18569 }
18570 document.cookie = updatedCookie;
18571 }
18572};
18573
18574dojo.cookie.isSupported = function(){
18575 // summary:
18576 // Use to determine if the current browser supports cookies or not.
81bea17a 18577 //
a089699c
AD
18578 // Returns true if user allows cookies.
18579 // Returns false if user doesn't allow cookies.
18580
18581 if(!("cookieEnabled" in navigator)){
18582 this("__djCookieTest__", "CookiesAllowed");
18583 navigator.cookieEnabled = this("__djCookieTest__") == "CookiesAllowed";
18584 if(navigator.cookieEnabled){
18585 this("__djCookieTest__", "", {expires: -1});
18586 }
18587 }
18588 return navigator.cookieEnabled;
18589};
18590
18591}
18592
18593if(!dojo._hasResource["dijit.layout.StackController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
18594dojo._hasResource["dijit.layout.StackController"] = true;
18595dojo.provide("dijit.layout.StackController");
18596
18597
18598
18599
18600
18601
18602
18603dojo.declare(
18604 "dijit.layout.StackController",
18605 [dijit._Widget, dijit._Templated, dijit._Container],
18606 {
18607 // summary:
18608 // Set of buttons to select a page in a page list.
18609 // description:
18610 // Monitors the specified StackContainer, and whenever a page is
18611 // added, deleted, or selected, updates itself accordingly.
18612
81bea17a 18613 templateString: "<span role='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>",
a089699c
AD
18614
18615 // containerId: [const] String
18616 // The id of the page container that I point to
18617 containerId: "",
18618
18619 // buttonWidget: [const] String
18620 // The name of the button widget to create to correspond to each page
18621 buttonWidget: "dijit.layout._StackButton",
18622
81bea17a 18623 constructor: function(){
a089699c 18624 this.pane2button = {}; // mapping from pane id to buttons
81bea17a
AD
18625 this.pane2connects = {}; // mapping from pane id to this.connect() handles
18626 this.pane2watches = {}; // mapping from pane id to watch() handles
18627 },
18628
18629 buildRendering: function(){
18630 this.inherited(arguments);
18631 dijit.setWaiRole(this.domNode, "tablist"); // TODO: unneeded? it's in template above.
18632 },
18633
18634 postCreate: function(){
18635 this.inherited(arguments);
a089699c
AD
18636
18637 // Listen to notifications from StackContainer
18638 this.subscribe(this.containerId+"-startup", "onStartup");
18639 this.subscribe(this.containerId+"-addChild", "onAddChild");
18640 this.subscribe(this.containerId+"-removeChild", "onRemoveChild");
18641 this.subscribe(this.containerId+"-selectChild", "onSelectChild");
18642 this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress");
18643 },
18644
18645 onStartup: function(/*Object*/ info){
18646 // summary:
18647 // Called after StackContainer has finished initializing
18648 // tags:
18649 // private
18650 dojo.forEach(info.children, this.onAddChild, this);
18651 if(info.selected){
18652 // Show button corresponding to selected pane (unless selected
18653 // is null because there are no panes)
18654 this.onSelectChild(info.selected);
18655 }
18656 },
18657
18658 destroy: function(){
18659 for(var pane in this.pane2button){
18660 this.onRemoveChild(dijit.byId(pane));
18661 }
18662 this.inherited(arguments);
18663 },
18664
18665 onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){
18666 // summary:
18667 // Called whenever a page is added to the container.
18668 // Create button corresponding to the page.
18669 // tags:
18670 // private
18671
18672 // create an instance of the button widget
18673 var cls = dojo.getObject(this.buttonWidget);
18674 var button = new cls({
18675 id: this.id + "_" + page.id,
18676 label: page.title,
18677 dir: page.dir,
18678 lang: page.lang,
18679 showLabel: page.showTitle,
18680 iconClass: page.iconClass,
18681 closeButton: page.closable,
18682 title: page.tooltip
18683 });
18684 dijit.setWaiState(button.focusNode,"selected", "false");
81bea17a
AD
18685
18686
18687 // map from page attribute to corresponding tab button attribute
18688 var pageAttrList = ["title", "showTitle", "iconClass", "closable", "tooltip"],
18689 buttonAttrList = ["label", "showLabel", "iconClass", "closeButton", "title"];
18690
18691 // watch() so events like page title changes are reflected in tab button
18692 this.pane2watches[page.id] = dojo.map(pageAttrList, function(pageAttr, idx){
18693 return page.watch(pageAttr, function(name, oldVal, newVal){
18694 button.set(buttonAttrList[idx], newVal);
18695 });
18696 });
18697
18698 // connections so that clicking a tab button selects the corresponding page
18699 this.pane2connects[page.id] = [
a089699c
AD
18700 this.connect(button, 'onClick', dojo.hitch(this,"onButtonClick", page)),
18701 this.connect(button, 'onClickCloseButton', dojo.hitch(this,"onCloseButtonClick", page))
18702 ];
81bea17a 18703
a089699c
AD
18704 this.addChild(button, insertIndex);
18705 this.pane2button[page.id] = button;
18706 page.controlButton = button; // this value might be overwritten if two tabs point to same container
18707 if(!this._currentChild){ // put the first child into the tab order
18708 button.focusNode.setAttribute("tabIndex", "0");
18709 dijit.setWaiState(button.focusNode, "selected", "true");
18710 this._currentChild = page;
18711 }
18712 // make sure all tabs have the same length
18713 if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){
18714 this._rectifyRtlTabList();
18715 }
18716 },
18717
18718 onRemoveChild: function(/*dijit._Widget*/ page){
18719 // summary:
18720 // Called whenever a page is removed from the container.
18721 // Remove the button corresponding to the page.
18722 // tags:
18723 // private
18724
18725 if(this._currentChild === page){ this._currentChild = null; }
81bea17a
AD
18726
18727 // disconnect/unwatch connections/watches related to page being removed
18728 dojo.forEach(this.pane2connects[page.id], dojo.hitch(this, "disconnect"));
18729 delete this.pane2connects[page.id];
18730 dojo.forEach(this.pane2watches[page.id], function(w){ w.unwatch(); });
18731 delete this.pane2watches[page.id];
18732
a089699c
AD
18733 var button = this.pane2button[page.id];
18734 if(button){
18735 this.removeChild(button);
18736 delete this.pane2button[page.id];
18737 button.destroy();
18738 }
18739 delete page.controlButton;
18740 },
18741
18742 onSelectChild: function(/*dijit._Widget*/ page){
18743 // summary:
18744 // Called when a page has been selected in the StackContainer, either by me or by another StackController
18745 // tags:
18746 // private
18747
18748 if(!page){ return; }
18749
18750 if(this._currentChild){
18751 var oldButton=this.pane2button[this._currentChild.id];
18752 oldButton.set('checked', false);
18753 dijit.setWaiState(oldButton.focusNode, "selected", "false");
18754 oldButton.focusNode.setAttribute("tabIndex", "-1");
18755 }
18756
18757 var newButton=this.pane2button[page.id];
18758 newButton.set('checked', true);
18759 dijit.setWaiState(newButton.focusNode, "selected", "true");
18760 this._currentChild = page;
18761 newButton.focusNode.setAttribute("tabIndex", "0");
18762 var container = dijit.byId(this.containerId);
18763 dijit.setWaiState(container.containerNode, "labelledby", newButton.id);
18764 },
18765
18766 onButtonClick: function(/*dijit._Widget*/ page){
18767 // summary:
18768 // Called whenever one of my child buttons is pressed in an attempt to select a page
18769 // tags:
18770 // private
18771
18772 var container = dijit.byId(this.containerId);
18773 container.selectChild(page);
18774 },
18775
18776 onCloseButtonClick: function(/*dijit._Widget*/ page){
18777 // summary:
18778 // Called whenever one of my child buttons [X] is pressed in an attempt to close a page
18779 // tags:
18780 // private
18781
18782 var container = dijit.byId(this.containerId);
18783 container.closeChild(page);
18784 if(this._currentChild){
18785 var b = this.pane2button[this._currentChild.id];
18786 if(b){
18787 dijit.focus(b.focusNode || b.domNode);
18788 }
18789 }
18790 },
18791
18792 // TODO: this is a bit redundant with forward, back api in StackContainer
18793 adjacent: function(/*Boolean*/ forward){
18794 // summary:
18795 // Helper for onkeypress to find next/previous button
18796 // tags:
18797 // private
18798
18799 if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; }
18800 // find currently focused button in children array
18801 var children = this.getChildren();
18802 var current = dojo.indexOf(children, this.pane2button[this._currentChild.id]);
18803 // pick next button to focus on
18804 var offset = forward ? 1 : children.length - 1;
18805 return children[ (current + offset) % children.length ]; // dijit._Widget
18806 },
18807
18808 onkeypress: function(/*Event*/ e){
18809 // summary:
18810 // Handle keystrokes on the page list, for advancing to next/previous button
18811 // and closing the current page if the page is closable.
18812 // tags:
18813 // private
18814
18815 if(this.disabled || e.altKey ){ return; }
18816 var forward = null;
18817 if(e.ctrlKey || !e._djpage){
18818 var k = dojo.keys;
18819 switch(e.charOrCode){
18820 case k.LEFT_ARROW:
18821 case k.UP_ARROW:
18822 if(!e._djpage){ forward = false; }
18823 break;
18824 case k.PAGE_UP:
18825 if(e.ctrlKey){ forward = false; }
18826 break;
18827 case k.RIGHT_ARROW:
18828 case k.DOWN_ARROW:
18829 if(!e._djpage){ forward = true; }
18830 break;
18831 case k.PAGE_DOWN:
18832 if(e.ctrlKey){ forward = true; }
18833 break;
81bea17a
AD
18834 case k.HOME:
18835 case k.END:
18836 var children = this.getChildren();
18837 if(children && children.length){
18838 children[e.charOrCode == k.HOME ? 0 : children.length-1].onClick();
18839 }
18840 dojo.stopEvent(e);
18841 break;
a089699c
AD
18842 case k.DELETE:
18843 if(this._currentChild.closable){
18844 this.onCloseButtonClick(this._currentChild);
18845 }
18846 dojo.stopEvent(e);
18847 break;
18848 default:
18849 if(e.ctrlKey){
18850 if(e.charOrCode === k.TAB){
18851 this.adjacent(!e.shiftKey).onClick();
18852 dojo.stopEvent(e);
18853 }else if(e.charOrCode == "w"){
18854 if(this._currentChild.closable){
18855 this.onCloseButtonClick(this._currentChild);
18856 }
18857 dojo.stopEvent(e); // avoid browser tab closing.
18858 }
18859 }
18860 }
81bea17a 18861 // handle next/previous page navigation (left/right arrow, etc.)
a089699c
AD
18862 if(forward !== null){
18863 this.adjacent(forward).onClick();
18864 dojo.stopEvent(e);
18865 }
18866 }
18867 },
18868
18869 onContainerKeyPress: function(/*Object*/ info){
18870 // summary:
18871 // Called when there was a keypress on the container
18872 // tags:
18873 // private
18874 info.e._djpage = info.page;
18875 this.onkeypress(info.e);
18876 }
18877 });
18878
18879
18880dojo.declare("dijit.layout._StackButton",
18881 dijit.form.ToggleButton,
18882 {
18883 // summary:
18884 // Internal widget used by StackContainer.
18885 // description:
18886 // The button-like or tab-like object you click to select or delete a page
18887 // tags:
18888 // private
18889
18890 // Override _FormWidget.tabIndex.
18891 // StackContainer buttons are not in the tab order by default.
18892 // Probably we should be calling this.startupKeyNavChildren() instead.
18893 tabIndex: "-1",
18894
81bea17a 18895 buildRendering: function(/*Event*/ evt){
a089699c 18896 this.inherited(arguments);
81bea17a 18897 dijit.setWaiRole((this.focusNode || this.domNode), "tab");
a089699c
AD
18898 },
18899
18900 onClick: function(/*Event*/ evt){
18901 // summary:
18902 // This is for TabContainer where the tabs are <span> rather than button,
18903 // so need to set focus explicitly (on some browsers)
18904 // Note that you shouldn't override this method, but you can connect to it.
18905 dijit.focus(this.focusNode);
18906
18907 // ... now let StackController catch the event and tell me what to do
18908 },
18909
18910 onClickCloseButton: function(/*Event*/ evt){
18911 // summary:
18912 // StackContainer connects to this function; if your widget contains a close button
18913 // then clicking it should call this function.
18914 // Note that you shouldn't override this method, but you can connect to it.
18915 evt.stopPropagation();
18916 }
18917 });
18918
a089699c
AD
18919}
18920
18921if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
18922dojo._hasResource["dijit.layout.StackContainer"] = true;
18923dojo.provide("dijit.layout.StackContainer");
18924
18925
18926
18927
18928
18929
81bea17a 18930
a089699c
AD
18931dojo.declare(
18932 "dijit.layout.StackContainer",
18933 dijit.layout._LayoutWidget,
18934 {
18935 // summary:
18936 // A container that has multiple children, but shows only
18937 // one child at a time
18938 //
18939 // description:
18940 // A container for widgets (ContentPanes, for example) That displays
18941 // only one Widget at a time.
18942 //
18943 // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild
18944 //
18945 // Can be base class for container, Wizard, Show, etc.
18946
18947 // doLayout: Boolean
18948 // If true, change the size of my currently displayed child to match my size
18949 doLayout: true,
18950
18951 // persist: Boolean
18952 // Remembers the selected child across sessions
18953 persist: false,
18954
18955 baseClass: "dijitStackContainer",
18956
18957/*=====
18958 // selectedChildWidget: [readonly] dijit._Widget
18959 // References the currently selected child widget, if any.
18960 // Adjust selected child with selectChild() method.
18961 selectedChildWidget: null,
18962=====*/
18963
81bea17a 18964 buildRendering: function(){
a089699c
AD
18965 this.inherited(arguments);
18966 dojo.addClass(this.domNode, "dijitLayoutContainer");
18967 dijit.setWaiRole(this.containerNode, "tabpanel");
81bea17a
AD
18968 },
18969
18970 postCreate: function(){
18971 this.inherited(arguments);
a089699c
AD
18972 this.connect(this.domNode, "onkeypress", this._onKeyPress);
18973 },
18974
18975 startup: function(){
18976 if(this._started){ return; }
18977
18978 var children = this.getChildren();
18979
18980 // Setup each page panel to be initially hidden
18981 dojo.forEach(children, this._setupChild, this);
18982
18983 // Figure out which child to initially display, defaulting to first one
18984 if(this.persist){
18985 this.selectedChildWidget = dijit.byId(dojo.cookie(this.id + "_selectedChild"));
18986 }else{
18987 dojo.some(children, function(child){
18988 if(child.selected){
18989 this.selectedChildWidget = child;
18990 }
18991 return child.selected;
18992 }, this);
18993 }
18994 var selected = this.selectedChildWidget;
18995 if(!selected && children[0]){
18996 selected = this.selectedChildWidget = children[0];
18997 selected.selected = true;
18998 }
18999
19000 // Publish information about myself so any StackControllers can initialize.
19001 // This needs to happen before this.inherited(arguments) so that for
19002 // TabContainer, this._contentBox doesn't include the space for the tab labels.
19003 dojo.publish(this.id+"-startup", [{children: children, selected: selected}]);
19004
19005 // Startup each child widget, and do initial layout like setting this._contentBox,
19006 // then calls this.resize() which does the initial sizing on the selected child.
19007 this.inherited(arguments);
19008 },
19009
19010 resize: function(){
19011 // Resize is called when we are first made visible (it's called from startup()
19012 // if we are initially visible). If this is the first time we've been made
19013 // visible then show our first child.
19014 var selected = this.selectedChildWidget;
19015 if(selected && !this._hasBeenShown){
19016 this._hasBeenShown = true;
19017 this._showChild(selected);
19018 }
19019 this.inherited(arguments);
19020 },
19021
19022 _setupChild: function(/*dijit._Widget*/ child){
19023 // Overrides _LayoutWidget._setupChild()
19024
19025 this.inherited(arguments);
19026
81bea17a 19027 dojo.replaceClass(child.domNode, "dijitHidden", "dijitVisible");
a089699c
AD
19028
19029 // remove the title attribute so it doesn't show up when i hover
19030 // over a node
19031 child.domNode.title = "";
19032 },
19033
19034 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
19035 // Overrides _Container.addChild() to do layout and publish events
19036
19037 this.inherited(arguments);
19038
19039 if(this._started){
19040 dojo.publish(this.id+"-addChild", [child, insertIndex]);
19041
19042 // in case the tab titles have overflowed from one line to two lines
19043 // (or, if this if first child, from zero lines to one line)
19044 // TODO: w/ScrollingTabController this is no longer necessary, although
19045 // ScrollTabController.resize() does need to get called to show/hide
19046 // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild()
19047 this.layout();
19048
19049 // if this is the first child, then select it
19050 if(!this.selectedChildWidget){
19051 this.selectChild(child);
19052 }
19053 }
19054 },
19055
19056 removeChild: function(/*dijit._Widget*/ page){
19057 // Overrides _Container.removeChild() to do layout and publish events
19058
19059 this.inherited(arguments);
19060
19061 if(this._started){
19062 // this will notify any tablists to remove a button; do this first because it may affect sizing
19063 dojo.publish(this.id + "-removeChild", [page]);
19064 }
19065
19066 // If we are being destroyed than don't run the code below (to select another page), because we are deleting
19067 // every page one by one
19068 if(this._beingDestroyed){ return; }
19069
19070 // Select new page to display, also updating TabController to show the respective tab.
19071 // Do this before layout call because it can affect the height of the TabController.
19072 if(this.selectedChildWidget === page){
19073 this.selectedChildWidget = undefined;
19074 if(this._started){
19075 var children = this.getChildren();
19076 if(children.length){
19077 this.selectChild(children[0]);
19078 }
19079 }
19080 }
19081
19082 if(this._started){
19083 // In case the tab titles now take up one line instead of two lines
19084 // (note though that ScrollingTabController never overflows to multiple lines),
19085 // or the height has changed slightly because of addition/removal of tab which close icon
19086 this.layout();
19087 }
19088 },
19089
19090 selectChild: function(/*dijit._Widget|String*/ page, /*Boolean*/ animate){
19091 // summary:
19092 // Show the given widget (which must be one of my children)
19093 // page:
19094 // Reference to child widget or id of child widget
19095
19096 page = dijit.byId(page);
19097
19098 if(this.selectedChildWidget != page){
19099 // Deselect old page and select new one
81bea17a
AD
19100 var d = this._transition(page, this.selectedChildWidget, animate);
19101 this._set("selectedChildWidget", page);
a089699c
AD
19102 dojo.publish(this.id+"-selectChild", [page]);
19103
19104 if(this.persist){
19105 dojo.cookie(this.id + "_selectedChild", this.selectedChildWidget.id);
19106 }
19107 }
81bea17a
AD
19108
19109 return d; // If child has an href, promise that fires when the child's href finishes loading
a089699c
AD
19110 },
19111
81bea17a 19112 _transition: function(/*dijit._Widget*/ newWidget, /*dijit._Widget*/ oldWidget, /*Boolean*/ animate){
a089699c
AD
19113 // summary:
19114 // Hide the old widget and display the new widget.
19115 // Subclasses should override this.
19116 // tags:
19117 // protected extension
19118 if(oldWidget){
19119 this._hideChild(oldWidget);
19120 }
81bea17a 19121 var d = this._showChild(newWidget);
a089699c
AD
19122
19123 // Size the new widget, in case this is the first time it's being shown,
19124 // or I have been resized since the last time it was shown.
19125 // Note that page must be visible for resizing to work.
19126 if(newWidget.resize){
19127 if(this.doLayout){
19128 newWidget.resize(this._containerContentBox || this._contentBox);
19129 }else{
19130 // the child should pick it's own size but we still need to call resize()
19131 // (with no arguments) to let the widget lay itself out
19132 newWidget.resize();
19133 }
19134 }
81bea17a
AD
19135
19136 return d; // If child has an href, promise that fires when the child's href finishes loading
a089699c
AD
19137 },
19138
19139 _adjacent: function(/*Boolean*/ forward){
19140 // summary:
19141 // Gets the next/previous child widget in this container from the current selection.
19142 var children = this.getChildren();
19143 var index = dojo.indexOf(children, this.selectedChildWidget);
19144 index += forward ? 1 : children.length - 1;
19145 return children[ index % children.length ]; // dijit._Widget
19146 },
19147
19148 forward: function(){
19149 // summary:
19150 // Advance to next page.
81bea17a 19151 return this.selectChild(this._adjacent(true), true);
a089699c
AD
19152 },
19153
19154 back: function(){
19155 // summary:
19156 // Go back to previous page.
81bea17a 19157 return this.selectChild(this._adjacent(false), true);
a089699c
AD
19158 },
19159
19160 _onKeyPress: function(e){
19161 dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]);
19162 },
19163
19164 layout: function(){
19165 // Implement _LayoutWidget.layout() virtual method.
19166 if(this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize){
19167 this.selectedChildWidget.resize(this._containerContentBox || this._contentBox);
19168 }
19169 },
19170
19171 _showChild: function(/*dijit._Widget*/ page){
19172 // summary:
19173 // Show the specified child by changing it's CSS, and call _onShow()/onShow() so
19174 // it can do any updates it needs regarding loading href's etc.
81bea17a
AD
19175 // returns:
19176 // Promise that fires when page has finished showing, or true if there's no href
a089699c
AD
19177 var children = this.getChildren();
19178 page.isFirstChild = (page == children[0]);
19179 page.isLastChild = (page == children[children.length-1]);
81bea17a 19180 page._set("selected", true);
a089699c 19181
81bea17a 19182 dojo.replaceClass(page.domNode, "dijitVisible", "dijitHidden");
a089699c 19183
81bea17a 19184 return page._onShow() || true;
a089699c
AD
19185 },
19186
19187 _hideChild: function(/*dijit._Widget*/ page){
19188 // summary:
19189 // Hide the specified child by changing it's CSS, and call _onHide() so
19190 // it's notified.
81bea17a
AD
19191 page._set("selected", false);
19192 dojo.replaceClass(page.domNode, "dijitHidden", "dijitVisible");
a089699c
AD
19193
19194 page.onHide();
19195 },
19196
19197 closeChild: function(/*dijit._Widget*/ page){
19198 // summary:
19199 // Callback when user clicks the [X] to remove a page.
19200 // If onClose() returns true then remove and destroy the child.
19201 // tags:
19202 // private
19203 var remove = page.onClose(this, page);
19204 if(remove){
19205 this.removeChild(page);
19206 // makes sure we can clean up executeScripts in ContentPane onUnLoad
19207 page.destroyRecursive();
19208 }
19209 },
19210
81bea17a 19211 destroyDescendants: function(/*Boolean*/ preserveDom){
a089699c
AD
19212 dojo.forEach(this.getChildren(), function(child){
19213 this.removeChild(child);
19214 child.destroyRecursive(preserveDom);
19215 }, this);
19216 }
19217});
19218
19219// For back-compat, remove for 2.0
19220
19221
a089699c
AD
19222// These arguments can be specified for the children of a StackContainer.
19223// Since any widget can be specified as a StackContainer child, mix them
19224// into the base widget class. (This is a hack, but it's effective.)
19225dojo.extend(dijit._Widget, {
19226 // selected: Boolean
19227 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
19228 // Specifies that this widget should be the initially displayed pane.
19229 // Note: to change the selected child use `dijit.layout.StackContainer.selectChild`
19230 selected: false,
19231
19232 // closable: Boolean
19233 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
19234 // True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
19235 closable: false,
19236
19237 // iconClass: String
19238 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
19239 // CSS Class specifying icon to use in label associated with this pane.
19240 iconClass: "",
19241
19242 // showTitle: Boolean
19243 // Parameter for children of `dijit.layout.StackContainer` or subclasses.
19244 // When true, display title of this widget as tab label etc., rather than just using
19245 // icon specified in iconClass
19246 showTitle: true
19247});
19248
19249}
19250
19251if(!dojo._hasResource["dijit.layout.AccordionPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
19252dojo._hasResource["dijit.layout.AccordionPane"] = true;
19253dojo.provide("dijit.layout.AccordionPane");
19254
19255
19256
19257dojo.declare("dijit.layout.AccordionPane", dijit.layout.ContentPane, {
19258 // summary:
19259 // Deprecated widget. Use `dijit.layout.ContentPane` instead.
19260 // tags:
19261 // deprecated
19262
19263 constructor: function(){
19264 dojo.deprecated("dijit.layout.AccordionPane deprecated, use ContentPane instead", "", "2.0");
19265 },
19266
19267 onSelected: function(){
19268 // summary:
19269 // called when this pane is selected
19270 }
19271});
19272
19273}
19274
19275if(!dojo._hasResource["dijit.layout.AccordionContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
19276dojo._hasResource["dijit.layout.AccordionContainer"] = true;
19277dojo.provide("dijit.layout.AccordionContainer");
19278
19279
19280
19281
19282
19283
19284
19285
81bea17a 19286//dojo.require("dijit.layout.AccordionPane "); // for back compat, remove for 2.0
a089699c 19287
81bea17a
AD
19288// Design notes:
19289//
19290// An AccordionContainer is a StackContainer, but each child (typically ContentPane)
19291// is wrapped in a _AccordionInnerContainer. This is hidden from the caller.
19292//
19293// The resulting markup will look like:
19294//
19295// <div class=dijitAccordionContainer>
19296// <div class=dijitAccordionInnerContainer> (one pane)
19297// <div class=dijitAccordionTitle> (title bar) ... </div>
19298// <div class=dijtAccordionChildWrapper> (content pane) </div>
19299// </div>
19300// </div>
19301//
19302// Normally the dijtAccordionChildWrapper is hidden for all but one child (the shown
19303// child), so the space for the content pane is all the title bars + the one dijtAccordionChildWrapper,
19304// which on claro has a 1px border plus a 2px bottom margin.
19305//
19306// During animation there are two dijtAccordionChildWrapper's shown, so we need
19307// to compensate for that.
a089699c
AD
19308
19309dojo.declare(
19310 "dijit.layout.AccordionContainer",
19311 dijit.layout.StackContainer,
19312 {
19313 // summary:
19314 // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time,
19315 // and switching between panes is visualized by sliding the other panes up/down.
19316 // example:
19317 // | <div dojoType="dijit.layout.AccordionContainer">
19318 // | <div dojoType="dijit.layout.ContentPane" title="pane 1">
19319 // | </div>
19320 // | <div dojoType="dijit.layout.ContentPane" title="pane 2">
19321 // | <p>This is some text</p>
19322 // | </div>
19323 // | </div>
19324
19325 // duration: Integer
19326 // Amount of time (in ms) it takes to slide panes
19327 duration: dijit.defaultDuration,
19328
19329 // buttonWidget: [const] String
19330 // The name of the widget used to display the title of each pane
19331 buttonWidget: "dijit.layout._AccordionButton",
19332
81bea17a 19333/*=====
a089699c
AD
19334 // _verticalSpace: Number
19335 // Pixels of space available for the open pane
19336 // (my content box size minus the cumulative size of all the title bars)
19337 _verticalSpace: 0,
81bea17a 19338=====*/
a089699c
AD
19339 baseClass: "dijitAccordionContainer",
19340
81bea17a 19341 buildRendering: function(){
a089699c 19342 this.inherited(arguments);
81bea17a
AD
19343 this.domNode.style.overflow = "hidden"; // TODO: put this in dijit.css
19344 dijit.setWaiRole(this.domNode, "tablist"); // TODO: put this in template
a089699c
AD
19345 },
19346
19347 startup: function(){
19348 if(this._started){ return; }
19349 this.inherited(arguments);
19350 if(this.selectedChildWidget){
19351 var style = this.selectedChildWidget.containerNode.style;
19352 style.display = "";
19353 style.overflow = "auto";
19354 this.selectedChildWidget._wrapperWidget.set("selected", true);
19355 }
19356 },
19357
a089699c
AD
19358 layout: function(){
19359 // Implement _LayoutWidget.layout() virtual method.
19360 // Set the height of the open pane based on what room remains.
19361
19362 var openPane = this.selectedChildWidget;
19363
19364 if(!openPane){ return;}
19365
81bea17a
AD
19366 // space taken up by title, plus wrapper div (with border/margin) for open pane
19367 var wrapperDomNode = openPane._wrapperWidget.domNode,
19368 wrapperDomNodeMargin = dojo._getMarginExtents(wrapperDomNode),
19369 wrapperDomNodePadBorder = dojo._getPadBorderExtents(wrapperDomNode),
19370 wrapperContainerNode = openPane._wrapperWidget.containerNode,
19371 wrapperContainerNodeMargin = dojo._getMarginExtents(wrapperContainerNode),
19372 wrapperContainerNodePadBorder = dojo._getPadBorderExtents(wrapperContainerNode),
a089699c
AD
19373 mySize = this._contentBox;
19374
19375 // get cumulative height of all the unselected title bars
19376 var totalCollapsedHeight = 0;
19377 dojo.forEach(this.getChildren(), function(child){
19378 if(child != openPane){
81bea17a 19379 totalCollapsedHeight += dojo._getMarginSize(child._wrapperWidget.domNode).h;
a089699c
AD
19380 }
19381 });
81bea17a
AD
19382 this._verticalSpace = mySize.h - totalCollapsedHeight - wrapperDomNodeMargin.h
19383 - wrapperDomNodePadBorder.h - wrapperContainerNodeMargin.h - wrapperContainerNodePadBorder.h
19384 - openPane._buttonWidget.getTitleHeight();
a089699c
AD
19385
19386 // Memo size to make displayed child
19387 this._containerContentBox = {
19388 h: this._verticalSpace,
81bea17a
AD
19389 w: this._contentBox.w - wrapperDomNodeMargin.w - wrapperDomNodePadBorder.w
19390 - wrapperContainerNodeMargin.w - wrapperContainerNodePadBorder.w
a089699c
AD
19391 };
19392
19393 if(openPane){
19394 openPane.resize(this._containerContentBox);
19395 }
19396 },
19397
19398 _setupChild: function(child){
19399 // Overrides _LayoutWidget._setupChild().
19400 // Put wrapper widget around the child widget, showing title
19401
19402 child._wrapperWidget = new dijit.layout._AccordionInnerContainer({
19403 contentWidget: child,
19404 buttonWidget: this.buttonWidget,
19405 id: child.id + "_wrapper",
19406 dir: child.dir,
19407 lang: child.lang,
19408 parent: this
19409 });
19410
19411 this.inherited(arguments);
19412 },
19413
81bea17a 19414 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
a089699c
AD
19415 if(this._started){
19416 // Adding a child to a started Accordion is complicated because children have
19417 // wrapper widgets. Default code path (calling this.inherited()) would add
19418 // the new child inside another child's wrapper.
19419
19420 // First add in child as a direct child of this AccordionContainer
19421 dojo.place(child.domNode, this.containerNode, insertIndex);
19422
19423 if(!child._started){
19424 child.startup();
19425 }
19426
19427 // Then stick the wrapper widget around the child widget
19428 this._setupChild(child);
19429
81bea17a 19430 // Code below copied from StackContainer
a089699c
AD
19431 dojo.publish(this.id+"-addChild", [child, insertIndex]);
19432 this.layout();
19433 if(!this.selectedChildWidget){
19434 this.selectChild(child);
19435 }
19436 }else{
19437 // We haven't been started yet so just add in the child widget directly,
19438 // and the wrapper will be created on startup()
19439 this.inherited(arguments);
19440 }
19441 },
19442
19443 removeChild: function(child){
19444 // Overrides _LayoutWidget.removeChild().
19445
81bea17a
AD
19446 // Destroy wrapper widget first, before StackContainer.getChildren() call.
19447 // Replace wrapper widget with true child widget (ContentPane etc.).
19448 // This step only happens if the AccordionContainer has been started; otherwise there's no wrapper.
19449 if(child._wrapperWidget){
19450 dojo.place(child.domNode, child._wrapperWidget.domNode, "after");
19451 child._wrapperWidget.destroy();
19452 delete child._wrapperWidget;
19453 }
19454
a089699c
AD
19455 dojo.removeClass(child.domNode, "dijitHidden");
19456
19457 this.inherited(arguments);
19458 },
19459
19460 getChildren: function(){
19461 // Overrides _Container.getChildren() to return content panes rather than internal AccordionInnerContainer panes
19462 return dojo.map(this.inherited(arguments), function(child){
19463 return child.declaredClass == "dijit.layout._AccordionInnerContainer" ? child.contentWidget : child;
19464 }, this);
19465 },
19466
19467 destroy: function(){
81bea17a
AD
19468 if(this._animation){
19469 this._animation.stop();
19470 }
a089699c 19471 dojo.forEach(this.getChildren(), function(child){
81bea17a
AD
19472 // If AccordionContainer has been started, then each child has a wrapper widget which
19473 // also needs to be destroyed.
19474 if(child._wrapperWidget){
19475 child._wrapperWidget.destroy();
19476 }else{
19477 child.destroyRecursive();
19478 }
a089699c
AD
19479 });
19480 this.inherited(arguments);
19481 },
19482
81bea17a
AD
19483 _showChild: function(child){
19484 // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode
19485 child._wrapperWidget.containerNode.style.display="block";
19486 return this.inherited(arguments);
19487 },
19488
19489 _hideChild: function(child){
19490 // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode
19491 child._wrapperWidget.containerNode.style.display="none";
19492 this.inherited(arguments);
19493 },
19494
19495 _transition: function(/*dijit._Widget?*/ newWidget, /*dijit._Widget?*/ oldWidget, /*Boolean*/ animate){
a089699c
AD
19496 // Overrides StackContainer._transition() to provide sliding of title bars etc.
19497
81bea17a
AD
19498 if(dojo.isIE < 8){
19499 // workaround animation bugs by not animating; not worth supporting animation for IE6 & 7
19500 animate = false;
19501 }
19502
19503 if(this._animation){
19504 // there's an in-progress animation. speedily end it so we can do the newly requested one
19505 this._animation.stop(true);
19506 delete this._animation;
19507 }
19508
19509 var self = this;
19510
a089699c
AD
19511 if(newWidget){
19512 newWidget._wrapperWidget.set("selected", true);
19513
81bea17a 19514 var d = this._showChild(newWidget); // prepare widget to be slid in
a089699c
AD
19515
19516 // Size the new widget, in case this is the first time it's being shown,
19517 // or I have been resized since the last time it was shown.
19518 // Note that page must be visible for resizing to work.
19519 if(this.doLayout && newWidget.resize){
19520 newWidget.resize(this._containerContentBox);
19521 }
a089699c 19522 }
81bea17a 19523
a089699c
AD
19524 if(oldWidget){
19525 oldWidget._wrapperWidget.set("selected", false);
81bea17a
AD
19526 if(!animate){
19527 this._hideChild(oldWidget);
a089699c
AD
19528 }
19529 }
19530
19531 if(animate){
81bea17a
AD
19532 var newContents = newWidget._wrapperWidget.containerNode,
19533 oldContents = oldWidget._wrapperWidget.containerNode;
19534
19535 // During the animation we will be showing two dijitAccordionChildWrapper nodes at once,
19536 // which on claro takes up 4px extra space (compared to stable AccordionContainer).
19537 // Have to compensate for that by immediately shrinking the pane being closed.
19538 var wrapperContainerNode = newWidget._wrapperWidget.containerNode,
19539 wrapperContainerNodeMargin = dojo._getMarginExtents(wrapperContainerNode),
19540 wrapperContainerNodePadBorder = dojo._getPadBorderExtents(wrapperContainerNode),
19541 animationHeightOverhead = wrapperContainerNodeMargin.h + wrapperContainerNodePadBorder.h;
19542
19543 oldContents.style.height = (self._verticalSpace - animationHeightOverhead) + "px";
19544
19545 this._animation = new dojo.Animation({
19546 node: newContents,
19547 duration: this.duration,
19548 curve: [1, this._verticalSpace - animationHeightOverhead - 1],
19549 onAnimate: function(value){
19550 value = Math.floor(value); // avoid fractional values
19551 newContents.style.height = value + "px";
19552 oldContents.style.height = (self._verticalSpace - animationHeightOverhead - value) + "px";
19553 },
19554 onEnd: function(){
19555 delete self._animation;
19556 newContents.style.height = "auto";
19557 oldWidget._wrapperWidget.containerNode.style.display = "none";
19558 oldContents.style.height = "auto";
19559 self._hideChild(oldWidget);
19560 }
a089699c 19561 });
81bea17a
AD
19562 this._animation.onStop = this._animation.onEnd;
19563 this._animation.play();
19564 }
19565
19566 return d; // If child has an href, promise that fires when the widget has finished loading
a089699c
AD
19567 },
19568
19569 // note: we are treating the container as controller here
19570 _onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){
19571 // summary:
19572 // Handle keypress events
19573 // description:
19574 // This is called from a handler on AccordionContainer.domNode
19575 // (setup in StackContainer), and is also called directly from
19576 // the click handler for accordion labels
81bea17a 19577 if(this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){
a089699c
AD
19578 return;
19579 }
19580 var k = dojo.keys,
19581 c = e.charOrCode;
19582 if((fromTitle && (c == k.LEFT_ARROW || c == k.UP_ARROW)) ||
19583 (e.ctrlKey && c == k.PAGE_UP)){
19584 this._adjacent(false)._buttonWidget._onTitleClick();
19585 dojo.stopEvent(e);
19586 }else if((fromTitle && (c == k.RIGHT_ARROW || c == k.DOWN_ARROW)) ||
19587 (e.ctrlKey && (c == k.PAGE_DOWN || c == k.TAB))){
19588 this._adjacent(true)._buttonWidget._onTitleClick();
19589 dojo.stopEvent(e);
19590 }
19591 }
19592 }
19593);
19594
19595dojo.declare("dijit.layout._AccordionInnerContainer",
19596 [dijit._Widget, dijit._CssStateMixin], {
19597 // summary:
19598 // Internal widget placed as direct child of AccordionContainer.containerNode.
19599 // When other widgets are added as children to an AccordionContainer they are wrapped in
19600 // this widget.
19601
81bea17a 19602/*=====
a089699c
AD
19603 // buttonWidget: String
19604 // Name of class to use to instantiate title
19605 // (Wish we didn't have a separate widget for just the title but maintaining it
19606 // for backwards compatibility, is it worth it?)
a089699c
AD
19607 buttonWidget: null,
19608=====*/
81bea17a
AD
19609
19610/*=====
a089699c
AD
19611 // contentWidget: dijit._Widget
19612 // Pointer to the real child widget
a089699c
AD
19613 contentWidget: null,
19614=====*/
19615
19616 baseClass: "dijitAccordionInnerContainer",
19617
19618 // tell nested layout widget that we will take care of sizing
19619 isContainer: true,
19620 isLayoutContainer: true,
19621
81bea17a
AD
19622 buildRendering: function(){
19623 // Builds a template like:
19624 // <div class=dijitAccordionInnerContainer>
19625 // Button
19626 // <div class=dijitAccordionChildWrapper>
19627 // ContentPane
19628 // </div>
19629 // </div>
19630
a089699c
AD
19631 // Create wrapper div, placed where the child is now
19632 this.domNode = dojo.place("<div class='" + this.baseClass + "'>", this.contentWidget.domNode, "after");
19633
19634 // wrapper div's first child is the button widget (ie, the title bar)
19635 var child = this.contentWidget,
19636 cls = dojo.getObject(this.buttonWidget);
19637 this.button = child._buttonWidget = (new cls({
19638 contentWidget: child,
19639 label: child.title,
19640 title: child.tooltip,
19641 dir: child.dir,
19642 lang: child.lang,
19643 iconClass: child.iconClass,
19644 id: child.id + "_button",
19645 parent: this.parent
19646 })).placeAt(this.domNode);
19647
81bea17a
AD
19648 // and then the actual content widget (changing it from prior-sibling to last-child),
19649 // wrapped by a <div class=dijitAccordionChildWrapper>
19650 this.containerNode = dojo.place("<div class='dijitAccordionChildWrapper' style='display:none'>", this.domNode);
19651 dojo.place(this.contentWidget.domNode, this.containerNode);
a089699c
AD
19652 },
19653
19654 postCreate: function(){
19655 this.inherited(arguments);
81bea17a
AD
19656
19657 // Map changes in content widget's title etc. to changes in the button
19658 var button = this.button;
19659 this._contentWidgetWatches = [
19660 this.contentWidget.watch('title', dojo.hitch(this, function(name, oldValue, newValue){
19661 button.set("label", newValue);
19662 })),
19663 this.contentWidget.watch('tooltip', dojo.hitch(this, function(name, oldValue, newValue){
19664 button.set("title", newValue);
19665 })),
19666 this.contentWidget.watch('iconClass', dojo.hitch(this, function(name, oldValue, newValue){
19667 button.set("iconClass", newValue);
19668 }))
19669 ];
a089699c
AD
19670 },
19671
19672 _setSelectedAttr: function(/*Boolean*/ isSelected){
81bea17a 19673 this._set("selected", isSelected);
a089699c
AD
19674 this.button.set("selected", isSelected);
19675 if(isSelected){
19676 var cw = this.contentWidget;
19677 if(cw.onSelected){ cw.onSelected(); }
19678 }
19679 },
19680
19681 startup: function(){
19682 // Called by _Container.addChild()
19683 this.contentWidget.startup();
19684 },
19685
19686 destroy: function(){
19687 this.button.destroyRecursive();
81bea17a
AD
19688
19689 dojo.forEach(this._contentWidgetWatches || [], function(w){ w.unwatch(); });
19690
a089699c
AD
19691 delete this.contentWidget._buttonWidget;
19692 delete this.contentWidget._wrapperWidget;
19693
19694 this.inherited(arguments);
19695 },
19696
19697 destroyDescendants: function(){
19698 // since getChildren isn't working for me, have to code this manually
19699 this.contentWidget.destroyRecursive();
19700 }
19701});
19702
19703dojo.declare("dijit.layout._AccordionButton",
19704 [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
19705 {
19706 // summary:
19707 // The title bar to click to open up an accordion pane.
19708 // Internal widget used by AccordionContainer.
19709 // tags:
19710 // private
19711
81bea17a 19712 templateString: dojo.cache("dijit.layout", "templates/AccordionButton.html", "<div dojoAttachEvent='onclick:_onTitleClick' class='dijitAccordionTitle'>\n\t<div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='onkeypress:_onTitleKeyPress'\n\t\t\tclass='dijitAccordionTitleFocus' role=\"tab\" aria-expanded=\"false\"\n\t\t><span class='dijitInline dijitAccordionArrow' role=\"presentation\"></span\n\t\t><span class='arrowTextUp' role=\"presentation\">+</span\n\t\t><span class='arrowTextDown' role=\"presentation\">-</span\n\t\t><img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' style=\"vertical-align: middle\" role=\"presentation\"/>\n\t\t<span role=\"presentation\" dojoAttachPoint='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n"),
a089699c
AD
19713 attributeMap: dojo.mixin(dojo.clone(dijit.layout.ContentPane.prototype.attributeMap), {
19714 label: {node: "titleTextNode", type: "innerHTML" },
19715 title: {node: "titleTextNode", type: "attribute", attribute: "title"},
19716 iconClass: { node: "iconNode", type: "class" }
19717 }),
19718
19719 baseClass: "dijitAccordionTitle",
19720
19721 getParent: function(){
19722 // summary:
19723 // Returns the AccordionContainer parent.
19724 // tags:
19725 // private
19726 return this.parent;
19727 },
19728
81bea17a 19729 buildRendering: function(){
a089699c 19730 this.inherited(arguments);
81bea17a 19731 var titleTextNodeId = this.id.replace(' ','_');
a089699c
AD
19732 dojo.attr(this.titleTextNode, "id", titleTextNodeId+"_title");
19733 dijit.setWaiState(this.focusNode, "labelledby", dojo.attr(this.titleTextNode, "id"));
81bea17a 19734 dojo.setSelectable(this.domNode, false);
a089699c
AD
19735 },
19736
19737 getTitleHeight: function(){
19738 // summary:
19739 // Returns the height of the title dom node.
81bea17a 19740 return dojo._getMarginSize(this.domNode).h; // Integer
a089699c
AD
19741 },
19742
19743 // TODO: maybe the parent should set these methods directly rather than forcing the code
19744 // into the button widget?
19745 _onTitleClick: function(){
19746 // summary:
19747 // Callback when someone clicks my title.
19748 var parent = this.getParent();
a089699c
AD
19749 parent.selectChild(this.contentWidget, true);
19750 dijit.focus(this.focusNode);
a089699c
AD
19751 },
19752
19753 _onTitleKeyPress: function(/*Event*/ evt){
19754 return this.getParent()._onKeyPress(evt, this.contentWidget);
19755 },
19756
19757 _setSelectedAttr: function(/*Boolean*/ isSelected){
81bea17a 19758 this._set("selected", isSelected);
a089699c
AD
19759 dijit.setWaiState(this.focusNode, "expanded", isSelected);
19760 dijit.setWaiState(this.focusNode, "selected", isSelected);
19761 this.focusNode.setAttribute("tabIndex", isSelected ? "0" : "-1");
19762 }
19763});
19764
19765}
19766
19767if(!dojo._hasResource["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
19768dojo._hasResource["dijit.layout.BorderContainer"] = true;
19769dojo.provide("dijit.layout.BorderContainer");
19770
19771
19772
19773
81bea17a 19774
a089699c
AD
19775dojo.declare(
19776 "dijit.layout.BorderContainer",
19777 dijit.layout._LayoutWidget,
19778{
19779 // summary:
19780 // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides.
19781 //
19782 // description:
19783 // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;",
19784 // that contains a child widget marked region="center" and optionally children widgets marked
19785 // region equal to "top", "bottom", "leading", "trailing", "left" or "right".
19786 // Children along the edges will be laid out according to width or height dimensions and may
19787 // include optional splitters (splitter="true") to make them resizable by the user. The remaining
19788 // space is designated for the center region.
19789 //
a089699c
AD
19790 // The outer size must be specified on the BorderContainer node. Width must be specified for the sides
19791 // and height for the top and bottom, respectively. No dimensions should be specified on the center;
19792 // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like
19793 // "left" and "right" except that they will be reversed in right-to-left environments.
19794 //
81bea17a
AD
19795 // For complex layouts, multiple children can be specified for a single region. In this case, the
19796 // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority)
19797 // and which child is closer to the center (high layoutPriority). layoutPriority can also be used
19798 // instead of the design attribute to conrol layout precedence of horizontal vs. vertical panes.
a089699c
AD
19799 // example:
19800 // | <div dojoType="dijit.layout.BorderContainer" design="sidebar" gutters="false"
19801 // | style="width: 400px; height: 300px;">
81bea17a
AD
19802 // | <div dojoType="dijit.layout.ContentPane" region="top">header text</div>
19803 // | <div dojoType="dijit.layout.ContentPane" region="right" splitter="true" style="width: 200px;">table of contents</div>
19804 // | <div dojoType="dijit.layout.ContentPane" region="center">client area</div>
a089699c
AD
19805 // | </div>
19806
19807 // design: String
19808 // Which design is used for the layout:
19809 // - "headline" (default) where the top and bottom extend
19810 // the full width of the container
19811 // - "sidebar" where the left and right sides extend from top to bottom.
19812 design: "headline",
19813
81bea17a 19814 // gutters: [const] Boolean
a089699c
AD
19815 // Give each pane a border and margin.
19816 // Margin determined by domNode.paddingLeft.
19817 // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing.
19818 gutters: true,
19819
81bea17a 19820 // liveSplitters: [const] Boolean
a089699c
AD
19821 // Specifies whether splitters resize as you drag (true) or only upon mouseup (false)
19822 liveSplitters: true,
19823
19824 // persist: Boolean
19825 // Save splitter positions in a cookie.
19826 persist: false,
19827
19828 baseClass: "dijitBorderContainer",
19829
19830 // _splitterClass: String
19831 // Optional hook to override the default Splitter widget used by BorderContainer
19832 _splitterClass: "dijit.layout._Splitter",
19833
19834 postMixInProperties: function(){
19835 // change class name to indicate that BorderContainer is being used purely for
19836 // layout (like LayoutContainer) rather than for pretty formatting.
19837 if(!this.gutters){
19838 this.baseClass += "NoGutter";
19839 }
19840 this.inherited(arguments);
19841 },
19842
a089699c
AD
19843 startup: function(){
19844 if(this._started){ return; }
19845 dojo.forEach(this.getChildren(), this._setupChild, this);
19846 this.inherited(arguments);
19847 },
19848
19849 _setupChild: function(/*dijit._Widget*/ child){
19850 // Override _LayoutWidget._setupChild().
19851
19852 var region = child.region;
19853 if(region){
19854 this.inherited(arguments);
19855
19856 dojo.addClass(child.domNode, this.baseClass+"Pane");
19857
19858 var ltr = this.isLeftToRight();
19859 if(region == "leading"){ region = ltr ? "left" : "right"; }
19860 if(region == "trailing"){ region = ltr ? "right" : "left"; }
19861
a089699c
AD
19862 // Create draggable splitter for resizing pane,
19863 // or alternately if splitter=false but BorderContainer.gutters=true then
19864 // insert dummy div just for spacing
81bea17a 19865 if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){
a089699c
AD
19866 var _Splitter = dojo.getObject(child.splitter ? this._splitterClass : "dijit.layout._Gutter");
19867 var splitter = new _Splitter({
19868 id: child.id + "_splitter",
19869 container: this,
19870 child: child,
19871 region: region,
19872 live: this.liveSplitters
19873 });
19874 splitter.isSplitter = true;
81bea17a
AD
19875 child._splitterWidget = splitter;
19876
19877 dojo.place(splitter.domNode, child.domNode, "after");
a089699c 19878
81bea17a 19879 // Splitters aren't added as Contained children, so we need to call startup explicitly
a089699c
AD
19880 splitter.startup();
19881 }
81bea17a 19882 child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc.
a089699c
AD
19883 }
19884 },
19885
a089699c
AD
19886 layout: function(){
19887 // Implement _LayoutWidget.layout() virtual method.
a089699c
AD
19888 this._layoutChildren();
19889 },
19890
19891 addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
19892 // Override _LayoutWidget.addChild().
19893 this.inherited(arguments);
19894 if(this._started){
19895 this.layout(); //OPT
19896 }
19897 },
19898
19899 removeChild: function(/*dijit._Widget*/ child){
19900 // Override _LayoutWidget.removeChild().
81bea17a 19901
a089699c 19902 var region = child.region;
81bea17a 19903 var splitter = child._splitterWidget
a089699c 19904 if(splitter){
81bea17a
AD
19905 splitter.destroy();
19906 delete child._splitterWidget;
a089699c
AD
19907 }
19908 this.inherited(arguments);
81bea17a 19909
a089699c
AD
19910 if(this._started){
19911 this._layoutChildren();
19912 }
81bea17a
AD
19913 // Clean up whatever style changes we made to the child pane.
19914 // Unclear how height and width should be handled.
a089699c 19915 dojo.removeClass(child.domNode, this.baseClass+"Pane");
81bea17a
AD
19916 dojo.style(child.domNode, {
19917 top: "auto",
19918 bottom: "auto",
19919 left: "auto",
19920 right: "auto",
19921 position: "static"
19922 });
19923 dojo.style(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto");
a089699c
AD
19924 },
19925
19926 getChildren: function(){
19927 // Override _LayoutWidget.getChildren() to only return real children, not the splitters.
19928 return dojo.filter(this.inherited(arguments), function(widget){
19929 return !widget.isSplitter;
19930 });
19931 },
19932
81bea17a 19933 // TODO: remove in 2.0
a089699c
AD
19934 getSplitter: function(/*String*/region){
19935 // summary:
19936 // Returns the widget responsible for rendering the splitter associated with region
81bea17a
AD
19937 // tags:
19938 // deprecated
19939 return dojo.filter(this.getChildren(), function(child){
19940 return child.region == region;
19941 })[0]._splitterWidget;
a089699c
AD
19942 },
19943
19944 resize: function(newSize, currentSize){
19945 // Overrides _LayoutWidget.resize().
19946
19947 // resetting potential padding to 0px to provide support for 100% width/height + padding
19948 // TODO: this hack doesn't respect the box model and is a temporary fix
19949 if(!this.cs || !this.pe){
19950 var node = this.domNode;
19951 this.cs = dojo.getComputedStyle(node);
19952 this.pe = dojo._getPadExtents(node, this.cs);
19953 this.pe.r = dojo._toPixelValue(node, this.cs.paddingRight);
19954 this.pe.b = dojo._toPixelValue(node, this.cs.paddingBottom);
19955
19956 dojo.style(node, "padding", "0px");
19957 }
19958
19959 this.inherited(arguments);
19960 },
19961
81bea17a 19962 _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){
a089699c
AD
19963 // summary:
19964 // This is the main routine for setting size/position of each child.
19965 // description:
19966 // With no arguments, measures the height of top/bottom panes, the width
19967 // of left/right panes, and then sizes all panes accordingly.
19968 //
19969 // With changedRegion specified (as "left", "top", "bottom", or "right"),
19970 // it changes that region's width/height to changedRegionSize and
19971 // then resizes other regions that were affected.
81bea17a
AD
19972 // changedChildId:
19973 // Id of the child which should be resized because splitter was dragged.
19974 // changedChildSize:
19975 // The new width/height (in pixels) to make specified child
a089699c
AD
19976
19977 if(!this._borderBox || !this._borderBox.h){
19978 // We are currently hidden, or we haven't been sized by our parent yet.
19979 // Abort. Someone will resize us later.
19980 return;
19981 }
19982
81bea17a
AD
19983 // Generate list of wrappers of my children in the order that I want layoutChildren()
19984 // to process them (i.e. from the outside to the inside)
19985 var wrappers = dojo.map(this.getChildren(), function(child, idx){
19986 return {
19987 pane: child,
19988 weight: [
19989 child.region == "center" ? Infinity : 0,
19990 child.layoutPriority,
19991 (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1),
19992 idx
19993 ]
19994 };
19995 }, this);
19996 wrappers.sort(function(a, b){
19997 var aw = a.weight, bw = b.weight;
19998 for(var i=0; i<aw.length; i++){
19999 if(aw[i] != bw[i]){
20000 return aw[i] - bw[i];
a089699c 20001 }
81bea17a
AD
20002 }
20003 return 0;
a089699c
AD
20004 });
20005
81bea17a
AD
20006 // Make new list, combining the externally specified children with splitters and gutters
20007 var childrenAndSplitters = [];
20008 dojo.forEach(wrappers, function(wrapper){
20009 var pane = wrapper.pane;
20010 childrenAndSplitters.push(pane);
20011 if(pane._splitterWidget){
20012 childrenAndSplitters.push(pane._splitterWidget);
20013 }
20014 });
a089699c 20015
81bea17a 20016 // Compute the box in which to lay out my children
a089699c 20017 var dim = {
81bea17a
AD
20018 l: this.pe.l,
20019 t: this.pe.t,
20020 w: this._borderBox.w - this.pe.w,
20021 h: this._borderBox.h - this.pe.h
a089699c
AD
20022 };
20023
81bea17a
AD
20024 // Layout the children, possibly changing size due to a splitter drag
20025 dijit.layout.layoutChildren(this.domNode, dim, childrenAndSplitters,
20026 changedChildId, changedChildSize);
20027 },
a089699c 20028
81bea17a
AD
20029 destroyRecursive: function(){
20030 // Destroy splitters first, while getChildren() still works
20031 dojo.forEach(this.getChildren(), function(child){
20032 var splitter = child._splitterWidget;
20033 if(splitter){
20034 splitter.destroy();
20035 }
20036 delete child._splitterWidget;
a089699c 20037 });
a089699c 20038
81bea17a 20039 // Then destroy the real children, and myself
a089699c
AD
20040 this.inherited(arguments);
20041 }
20042});
20043
20044// This argument can be specified for the children of a BorderContainer.
20045// Since any widget can be specified as a LayoutContainer child, mix it
20046// into the base widget class. (This is a hack, but it's effective.)
20047dojo.extend(dijit._Widget, {
20048 // region: [const] String
20049 // Parameter for children of `dijit.layout.BorderContainer`.
20050 // Values: "top", "bottom", "leading", "trailing", "left", "right", "center".
20051 // See the `dijit.layout.BorderContainer` description for details.
20052 region: '',
20053
81bea17a
AD
20054 // layoutPriority: [const] Number
20055 // Parameter for children of `dijit.layout.BorderContainer`.
20056 // Children with a higher layoutPriority will be placed closer to the BorderContainer center,
20057 // between children with a lower layoutPriority.
20058 layoutPriority: 0,
20059
a089699c
AD
20060 // splitter: [const] Boolean
20061 // Parameter for child of `dijit.layout.BorderContainer` where region != "center".
20062 // If true, enables user to resize the widget by putting a draggable splitter between
20063 // this widget and the region=center widget.
20064 splitter: false,
20065
20066 // minSize: [const] Number
20067 // Parameter for children of `dijit.layout.BorderContainer`.
20068 // Specifies a minimum size (in pixels) for this widget when resized by a splitter.
20069 minSize: 0,
20070
20071 // maxSize: [const] Number
20072 // Parameter for children of `dijit.layout.BorderContainer`.
20073 // Specifies a maximum size (in pixels) for this widget when resized by a splitter.
20074 maxSize: Infinity
20075});
20076
a089699c
AD
20077dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ],
20078{
20079 // summary:
20080 // A draggable spacer between two items in a `dijit.layout.BorderContainer`.
20081 // description:
20082 // This is instantiated by `dijit.layout.BorderContainer`. Users should not
20083 // create it directly.
20084 // tags:
20085 // private
20086
20087/*=====
20088 // container: [const] dijit.layout.BorderContainer
20089 // Pointer to the parent BorderContainer
20090 container: null,
20091
20092 // child: [const] dijit.layout._LayoutWidget
20093 // Pointer to the pane associated with this splitter
20094 child: null,
20095
81bea17a 20096 // region: [const] String
a089699c
AD
20097 // Region of pane associated with this splitter.
20098 // "top", "bottom", "left", "right".
20099 region: null,
20100=====*/
20101
20102 // live: [const] Boolean
20103 // If true, the child's size changes and the child widget is redrawn as you drag the splitter;
20104 // otherwise, the size doesn't change until you drop the splitter (by mouse-up)
20105 live: true,
20106
81bea17a 20107 templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>',
a089699c 20108
81bea17a 20109 postMixInProperties: function(){
a089699c 20110 this.inherited(arguments);
a089699c 20111
81bea17a 20112 this.horizontal = /top|bottom/.test(this.region);
a089699c 20113 this._factor = /top|left/.test(this.region) ? 1 : -1;
a089699c 20114 this._cookieName = this.container.id + "_" + this.region;
81bea17a
AD
20115 },
20116
20117 buildRendering: function(){
20118 this.inherited(arguments);
20119
20120 dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V"));
20121
a089699c
AD
20122 if(this.container.persist){
20123 // restore old size
20124 var persistSize = dojo.cookie(this._cookieName);
20125 if(persistSize){
20126 this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize;
20127 }
20128 }
20129 },
20130
20131 _computeMaxSize: function(){
20132 // summary:
81bea17a 20133 // Return the maximum size that my corresponding pane can be set to
a089699c
AD
20134
20135 var dim = this.horizontal ? 'h' : 'w',
81bea17a
AD
20136 childSize = dojo.marginBox(this.child.domNode)[dim],
20137 center = dojo.filter(this.container.getChildren(), function(child){ return child.region == "center";})[0],
20138 spaceAvailable = dojo.marginBox(center.domNode)[dim]; // can expand until center is crushed to 0
a089699c 20139
81bea17a 20140 return Math.min(this.child.maxSize, childSize + spaceAvailable);
a089699c
AD
20141 },
20142
20143 _startDrag: function(e){
20144 if(!this.cover){
20145 this.cover = dojo.doc.createElement('div');
20146 dojo.addClass(this.cover, "dijitSplitterCover");
20147 dojo.place(this.cover, this.child.domNode, "after");
20148 }
20149 dojo.addClass(this.cover, "dijitSplitterCoverActive");
20150
20151 // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up.
20152 if(this.fake){ dojo.destroy(this.fake); }
20153 if(!(this._resize = this.live)){ //TODO: disable live for IE6?
20154 // create fake splitter to display at old position while we drag
20155 (this.fake = this.domNode.cloneNode(true)).removeAttribute("id");
20156 dojo.addClass(this.domNode, "dijitSplitterShadow");
20157 dojo.place(this.fake, this.domNode, "after");
20158 }
81bea17a 20159 dojo.addClass(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active");
a089699c 20160 if(this.fake){
81bea17a 20161 dojo.removeClass(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover");
a089699c
AD
20162 }
20163
20164 //Performance: load data info local vars for onmousevent function closure
20165 var factor = this._factor,
a089699c
AD
20166 isHorizontal = this.horizontal,
20167 axis = isHorizontal ? "pageY" : "pageX",
20168 pageStart = e[axis],
20169 splitterStyle = this.domNode.style,
20170 dim = isHorizontal ? 'h' : 'w',
20171 childStart = dojo.marginBox(this.child.domNode)[dim],
81bea17a
AD
20172 max = this._computeMaxSize(),
20173 min = this.child.minSize || 20,
a089699c 20174 region = this.region,
81bea17a
AD
20175 splitterAttr = region == "top" || region == "bottom" ? "top" : "left", // style attribute of splitter to adjust
20176 splitterStart = parseInt(splitterStyle[splitterAttr], 10),
a089699c 20177 resize = this._resize,
81bea17a 20178 layoutFunc = dojo.hitch(this.container, "_layoutChildren", this.child.id),
a089699c
AD
20179 de = dojo.doc;
20180
20181 this._handlers = (this._handlers || []).concat([
20182 dojo.connect(de, "onmousemove", this._drag = function(e, forceResize){
20183 var delta = e[axis] - pageStart,
20184 childSize = factor * delta + childStart,
20185 boundChildSize = Math.max(Math.min(childSize, max), min);
20186
20187 if(resize || forceResize){
81bea17a 20188 layoutFunc(boundChildSize);
a089699c 20189 }
81bea17a
AD
20190 // TODO: setting style directly (usually) sets content box size, need to set margin box size
20191 splitterStyle[splitterAttr] = delta + splitterStart + factor*(boundChildSize - childSize) + "px";
a089699c
AD
20192 }),
20193 dojo.connect(de, "ondragstart", dojo.stopEvent),
20194 dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent),
20195 dojo.connect(de, "onmouseup", this, "_stopDrag")
20196 ]);
20197 dojo.stopEvent(e);
20198 },
20199
20200 _onMouse: function(e){
20201 var o = (e.type == "mouseover" || e.type == "mouseenter");
20202 dojo.toggleClass(this.domNode, "dijitSplitterHover", o);
20203 dojo.toggleClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o);
20204 },
20205
20206 _stopDrag: function(e){
20207 try{
20208 if(this.cover){
20209 dojo.removeClass(this.cover, "dijitSplitterCoverActive");
20210 }
20211 if(this.fake){ dojo.destroy(this.fake); }
81bea17a
AD
20212 dojo.removeClass(this.domNode, "dijitSplitterActive dijitSplitter"
20213 + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow");
a089699c
AD
20214 this._drag(e); //TODO: redundant with onmousemove?
20215 this._drag(e, true);
20216 }finally{
20217 this._cleanupHandlers();
20218 delete this._drag;
20219 }
20220
20221 if(this.container.persist){
20222 dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365});
20223 }
20224 },
20225
20226 _cleanupHandlers: function(){
20227 dojo.forEach(this._handlers, dojo.disconnect);
20228 delete this._handlers;
20229 },
20230
20231 _onKeyPress: function(/*Event*/ e){
20232 // should we apply typematic to this?
20233 this._resize = true;
20234 var horizontal = this.horizontal;
20235 var tick = 1;
20236 var dk = dojo.keys;
20237 switch(e.charOrCode){
20238 case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW:
20239 tick *= -1;
20240// break;
20241 case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW:
20242 break;
20243 default:
20244// this.inherited(arguments);
20245 return;
20246 }
81bea17a
AD
20247 var childSize = dojo._getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick;
20248 this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize));
a089699c
AD
20249 dojo.stopEvent(e);
20250 },
20251
20252 destroy: function(){
20253 this._cleanupHandlers();
20254 delete this.child;
20255 delete this.container;
20256 delete this.cover;
20257 delete this.fake;
20258 this.inherited(arguments);
20259 }
20260});
20261
81bea17a 20262dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated],
a089699c
AD
20263{
20264 // summary:
20265 // Just a spacer div to separate side pane from center pane.
20266 // Basically a trick to lookup the gutter/splitter width from the theme.
20267 // description:
20268 // Instantiated by `dijit.layout.BorderContainer`. Users should not
20269 // create directly.
20270 // tags:
20271 // private
20272
81bea17a 20273 templateString: '<div class="dijitGutter" role="presentation"></div>',
a089699c 20274
81bea17a
AD
20275 postMixInProperties: function(){
20276 this.inherited(arguments);
a089699c 20277 this.horizontal = /top|bottom/.test(this.region);
81bea17a
AD
20278 },
20279
20280 buildRendering: function(){
20281 this.inherited(arguments);
a089699c
AD
20282 dojo.addClass(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V"));
20283 }
20284});
20285
20286}
20287
20288if(!dojo._hasResource["dijit.layout._TabContainerBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
20289dojo._hasResource["dijit.layout._TabContainerBase"] = true;
20290dojo.provide("dijit.layout._TabContainerBase");
20291
20292
20293
20294
20295dojo.declare("dijit.layout._TabContainerBase",
20296 [dijit.layout.StackContainer, dijit._Templated],
20297 {
20298 // summary:
20299 // Abstract base class for TabContainer. Must define _makeController() to instantiate
20300 // and return the widget that displays the tab labels
20301 // description:
20302 // A TabContainer is a container that has multiple panes, but shows only
20303 // one pane at a time. There are a set of tabs corresponding to each pane,
20304 // where each tab has the name (aka title) of the pane, and optionally a close button.
20305
20306 // tabPosition: String
20307 // Defines where tabs go relative to tab content.
20308 // "top", "bottom", "left-h", "right-h"
20309 tabPosition: "top",
20310
20311 baseClass: "dijitTabContainer",
20312
81bea17a 20313 // tabStrip: [const] Boolean
a089699c 20314 // Defines whether the tablist gets an extra class for layouting, putting a border/shading
81bea17a 20315 // around the set of tabs. Not supported by claro theme.
a089699c
AD
20316 tabStrip: false,
20317
81bea17a 20318 // nested: [const] Boolean
a089699c
AD
20319 // If true, use styling for a TabContainer nested inside another TabContainer.
20320 // For tundra etc., makes tabs look like links, and hides the outer
20321 // border since the outer TabContainer already has a border.
20322 nested: false,
20323
20324 templateString: dojo.cache("dijit.layout", "templates/TabContainer.html", "<div class=\"dijitTabContainer\">\n\t<div class=\"dijitTabListWrapper\" dojoAttachPoint=\"tablistNode\"></div>\n\t<div dojoAttachPoint=\"tablistSpacer\" class=\"dijitTabSpacer ${baseClass}-spacer\"></div>\n\t<div class=\"dijitTabPaneWrapper ${baseClass}-container\" dojoAttachPoint=\"containerNode\"></div>\n</div>\n"),
20325
20326 postMixInProperties: function(){
20327 // set class name according to tab position, ex: dijitTabContainerTop
20328 this.baseClass += this.tabPosition.charAt(0).toUpperCase() + this.tabPosition.substr(1).replace(/-.*/, "");
20329
20330 this.srcNodeRef && dojo.style(this.srcNodeRef, "visibility", "hidden");
20331
20332 this.inherited(arguments);
20333 },
20334
81bea17a 20335 buildRendering: function(){
a089699c
AD
20336 this.inherited(arguments);
20337
20338 // Create the tab list that will have a tab (a.k.a. tab button) for each tab panel
20339 this.tablist = this._makeController(this.tablistNode);
20340
20341 if(!this.doLayout){ dojo.addClass(this.domNode, "dijitTabContainerNoLayout"); }
20342
20343 if(this.nested){
20344 /* workaround IE's lack of support for "a > b" selectors by
20345 * tagging each node in the template.
20346 */
20347 dojo.addClass(this.domNode, "dijitTabContainerNested");
20348 dojo.addClass(this.tablist.containerNode, "dijitTabContainerTabListNested");
20349 dojo.addClass(this.tablistSpacer, "dijitTabContainerSpacerNested");
20350 dojo.addClass(this.containerNode, "dijitTabPaneWrapperNested");
20351 }else{
20352 dojo.addClass(this.domNode, "tabStrip-" + (this.tabStrip ? "enabled" : "disabled"));
20353 }
20354 },
20355
20356 _setupChild: function(/*dijit._Widget*/ tab){
20357 // Overrides StackContainer._setupChild().
20358 dojo.addClass(tab.domNode, "dijitTabPane");
20359 this.inherited(arguments);
20360 },
20361
20362 startup: function(){
20363 if(this._started){ return; }
20364
20365 // wire up the tablist and its tabs
20366 this.tablist.startup();
20367
20368 this.inherited(arguments);
20369 },
20370
20371 layout: function(){
20372 // Overrides StackContainer.layout().
20373 // Configure the content pane to take up all the space except for where the tabs are
20374
20375 if(!this._contentBox || typeof(this._contentBox.l) == "undefined"){return;}
20376
20377 var sc = this.selectedChildWidget;
20378
20379 if(this.doLayout){
20380 // position and size the titles and the container node
20381 var titleAlign = this.tabPosition.replace(/-h/, "");
20382 this.tablist.layoutAlign = titleAlign;
20383 var children = [this.tablist, {
20384 domNode: this.tablistSpacer,
20385 layoutAlign: titleAlign
20386 }, {
20387 domNode: this.containerNode,
20388 layoutAlign: "client"
20389 }];
20390 dijit.layout.layoutChildren(this.domNode, this._contentBox, children);
20391
20392 // Compute size to make each of my children.
20393 // children[2] is the margin-box size of this.containerNode, set by layoutChildren() call above
20394 this._containerContentBox = dijit.layout.marginBox2contentBox(this.containerNode, children[2]);
20395
20396 if(sc && sc.resize){
20397 sc.resize(this._containerContentBox);
20398 }
20399 }else{
20400 // just layout the tab controller, so it can position left/right buttons etc.
20401 if(this.tablist.resize){
81bea17a
AD
20402 //make the tabs zero width so that they don't interfere with width calc, then reset
20403 var s = this.tablist.domNode.style;
20404 s.width="0";
20405 var width = dojo.contentBox(this.domNode).w;
20406 s.width="";
20407 this.tablist.resize({w: width});
a089699c
AD
20408 }
20409
20410 // and call resize() on the selected pane just to tell it that it's been made visible
20411 if(sc && sc.resize){
20412 sc.resize();
20413 }
20414 }
20415 },
20416
20417 destroy: function(){
20418 if(this.tablist){
20419 this.tablist.destroy();
20420 }
20421 this.inherited(arguments);
20422 }
20423});
20424
a089699c
AD
20425}
20426
20427if(!dojo._hasResource["dijit.layout.TabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
20428dojo._hasResource["dijit.layout.TabController"] = true;
20429dojo.provide("dijit.layout.TabController");
20430
20431
20432
a089699c
AD
20433
20434
20435
81bea17a 20436// Menu is used for an accessible close button, would be nice to have a lighter-weight solution
a089699c
AD
20437
20438
20439dojo.declare("dijit.layout.TabController",
20440 dijit.layout.StackController,
20441{
20442 // summary:
20443 // Set of tabs (the things with titles and a close button, that you click to show a tab panel).
20444 // Used internally by `dijit.layout.TabContainer`.
20445 // description:
20446 // Lets the user select the currently shown pane in a TabContainer or StackContainer.
20447 // TabController also monitors the TabContainer, and whenever a pane is
20448 // added or deleted updates itself accordingly.
20449 // tags:
20450 // private
20451
81bea17a 20452 templateString: "<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>",
a089699c
AD
20453
20454 // tabPosition: String
20455 // Defines where tabs go relative to the content.
20456 // "top", "bottom", "left-h", "right-h"
20457 tabPosition: "top",
20458
20459 // buttonWidget: String
20460 // The name of the tab widget to create to correspond to each page
20461 buttonWidget: "dijit.layout._TabButton",
20462
20463 _rectifyRtlTabList: function(){
20464 // summary:
20465 // For left/right TabContainer when page is RTL mode, rectify the width of all tabs to be equal, otherwise the tab widths are different in IE
20466
20467 if(0 >= this.tabPosition.indexOf('-h')){ return; }
20468 if(!this.pane2button){ return; }
20469
20470 var maxWidth = 0;
20471 for(var pane in this.pane2button){
20472 var ow = this.pane2button[pane].innerDiv.scrollWidth;
20473 maxWidth = Math.max(maxWidth, ow);
20474 }
20475 //unify the length of all the tabs
20476 for(pane in this.pane2button){
20477 this.pane2button[pane].innerDiv.style.width = maxWidth + 'px';
20478 }
20479 }
20480});
20481
20482dojo.declare("dijit.layout._TabButton",
20483 dijit.layout._StackButton,
20484 {
20485 // summary:
20486 // A tab (the thing you click to select a pane).
20487 // description:
20488 // Contains the title of the pane, and optionally a close-button to destroy the pane.
20489 // This is an internal widget and should not be instantiated directly.
20490 // tags:
20491 // private
20492
20493 // baseClass: String
20494 // The CSS class applied to the domNode.
20495 baseClass: "dijitTab",
20496
20497 // Apply dijitTabCloseButtonHover when close button is hovered
20498 cssStateNodes: {
20499 closeNode: "dijitTabCloseButton"
20500 },
20501
81bea17a 20502 templateString: dojo.cache("dijit.layout", "templates/_TabButton.html", "<div role=\"presentation\" dojoAttachPoint=\"titleNode\" dojoAttachEvent='onclick:onClick'>\n <div role=\"presentation\" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'>\n <div role=\"presentation\" class='dijitTabContent' dojoAttachPoint='tabContent'>\n \t<div role=\"presentation\" dojoAttachPoint='focusNode'>\n\t\t <img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitTabButtonIcon\" dojoAttachPoint='iconNode' />\n\t\t <span dojoAttachPoint='containerNode' class='tabLabel'></span>\n\t\t <span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" dojoAttachPoint='closeNode'\n\t\t \t\tdojoAttachEvent='onclick: onClickCloseButton' role=\"presentation\">\n\t\t <span dojoAttachPoint='closeText' class='dijitTabCloseText'>[x]</span\n\t\t ></span>\n\t\t\t</div>\n </div>\n </div>\n</div>\n"),
a089699c
AD
20503
20504 // Override _FormWidget.scrollOnFocus.
20505 // Don't scroll the whole tab container into view when the button is focused.
20506 scrollOnFocus: false,
20507
81bea17a 20508 buildRendering: function(){
a089699c 20509 this.inherited(arguments);
a089699c 20510
81bea17a 20511 dojo.setSelectable(this.containerNode, false);
a089699c
AD
20512 },
20513
20514 startup: function(){
20515 this.inherited(arguments);
20516 var n = this.domNode;
20517
20518 // Required to give IE6 a kick, as it initially hides the
20519 // tabs until they are focused on.
20520 setTimeout(function(){
20521 n.className = n.className;
20522 }, 1);
20523 },
20524
81bea17a
AD
20525 _setCloseButtonAttr: function(/*Boolean*/ disp){
20526 // summary:
20527 // Hide/show close button
20528 this._set("closeButton", disp);
a089699c
AD
20529 dojo.toggleClass(this.innerDiv, "dijitClosable", disp);
20530 this.closeNode.style.display = disp ? "" : "none";
20531 if(disp){
20532 var _nlsResources = dojo.i18n.getLocalization("dijit", "common");
20533 if(this.closeNode){
20534 dojo.attr(this.closeNode,"title", _nlsResources.itemClose);
20535 }
20536 // add context menu onto title button
20537 var _nlsResources = dojo.i18n.getLocalization("dijit", "common");
20538 this._closeMenu = new dijit.Menu({
20539 id: this.id+"_Menu",
20540 dir: this.dir,
20541 lang: this.lang,
20542 targetNodeIds: [this.domNode]
20543 });
20544
20545 this._closeMenu.addChild(new dijit.MenuItem({
20546 label: _nlsResources.itemClose,
20547 dir: this.dir,
20548 lang: this.lang,
20549 onClick: dojo.hitch(this, "onClickCloseButton")
20550 }));
20551 }else{
20552 if(this._closeMenu){
20553 this._closeMenu.destroyRecursive();
20554 delete this._closeMenu;
20555 }
20556 }
20557 },
20558 _setLabelAttr: function(/*String*/ content){
20559 // summary:
81bea17a 20560 // Hook for set('label', ...) to work.
a089699c
AD
20561 // description:
20562 // takes an HTML string.
81bea17a 20563 // Inherited ToggleButton implementation will Set the label (text) of the button;
a089699c 20564 // Need to set the alt attribute of icon on tab buttons if no label displayed
81bea17a
AD
20565 this.inherited(arguments);
20566 if(this.showLabel == false && !this.params.title){
20567 this.iconNode.alt = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || '');
20568 }
20569 },
a089699c
AD
20570
20571 destroy: function(){
20572 if(this._closeMenu){
20573 this._closeMenu.destroyRecursive();
20574 delete this._closeMenu;
20575 }
20576 this.inherited(arguments);
20577 }
20578});
20579
20580}
20581
20582if(!dojo._hasResource["dijit.layout.ScrollingTabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
20583dojo._hasResource["dijit.layout.ScrollingTabController"] = true;
20584dojo.provide("dijit.layout.ScrollingTabController");
20585
20586
20587
20588
81bea17a
AD
20589
20590
a089699c
AD
20591dojo.declare("dijit.layout.ScrollingTabController",
20592 dijit.layout.TabController,
20593 {
20594 // summary:
20595 // Set of tabs with left/right arrow keys and a menu to switch between tabs not
20596 // all fitting on a single row.
20597 // Works only for horizontal tabs (either above or below the content, not to the left
20598 // or right).
20599 // tags:
20600 // private
20601
81bea17a 20602 templateString: dojo.cache("dijit.layout", "templates/ScrollingTabController.html", "<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerMenuButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\" containerId=\"${containerId}\" iconClass=\"dijitTabStripMenuIcon\"\n\t\t\tdropDownPosition=\"below-alt, above-alt\"\n\t\t\tdojoAttachPoint=\"_menuBtn\" showLabel=\"false\">&#9660;</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\" iconClass=\"dijitTabStripSlideLeftIcon\"\n\t\t\tdojoAttachPoint=\"_leftBtn\" dojoAttachEvent=\"onClick: doSlideLeft\" showLabel=\"false\">&#9664;</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\" iconClass=\"dijitTabStripSlideRightIcon\"\n\t\t\tdojoAttachPoint=\"_rightBtn\" dojoAttachEvent=\"onClick: doSlideRight\" showLabel=\"false\">&#9654;</div>\n\t<div class='dijitTabListWrapper' dojoAttachPoint='tablistWrapper'>\n\t\t<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'\n\t\t\t\tdojoAttachPoint='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>\n"),
a089699c 20603
81bea17a 20604 // useMenu: [const] Boolean
a089699c
AD
20605 // True if a menu should be used to select tabs when they are too
20606 // wide to fit the TabContainer, false otherwise.
20607 useMenu: true,
20608
20609 // useSlider: [const] Boolean
20610 // True if a slider should be used to select tabs when they are too
20611 // wide to fit the TabContainer, false otherwise.
20612 useSlider: true,
20613
81bea17a 20614 // tabStripClass: [const] String
a089699c
AD
20615 // The css class to apply to the tab strip, if it is visible.
20616 tabStripClass: "",
20617
20618 widgetsInTemplate: true,
20619
20620 // _minScroll: Number
20621 // The distance in pixels from the edge of the tab strip which,
20622 // if a scroll animation is less than, forces the scroll to
20623 // go all the way to the left/right.
20624 _minScroll: 5,
20625
20626 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
20627 "class": "containerNode"
20628 }),
20629
81bea17a 20630 buildRendering: function(){
a089699c
AD
20631 this.inherited(arguments);
20632 var n = this.domNode;
20633
20634 this.scrollNode = this.tablistWrapper;
20635 this._initButtons();
20636
20637 if(!this.tabStripClass){
20638 this.tabStripClass = "dijitTabContainer" +
20639 this.tabPosition.charAt(0).toUpperCase() +
20640 this.tabPosition.substr(1).replace(/-.*/, "") +
20641 "None";
20642 dojo.addClass(n, "tabStrip-disabled")
20643 }
20644
20645 dojo.addClass(this.tablistWrapper, this.tabStripClass);
20646 },
20647
20648 onStartup: function(){
20649 this.inherited(arguments);
20650
20651 // Do not show the TabController until the related
20652 // StackController has added it's children. This gives
20653 // a less visually jumpy instantiation.
20654 dojo.style(this.domNode, "visibility", "visible");
20655 this._postStartup = true;
20656 },
20657
20658 onAddChild: function(page, insertIndex){
20659 this.inherited(arguments);
a089699c 20660
81bea17a
AD
20661 // changes to the tab button label or iconClass will have changed the width of the
20662 // buttons, so do a resize
20663 dojo.forEach(["label", "iconClass"], function(attr){
20664 this.pane2watches[page.id].push(
20665 this.pane2button[page.id].watch(attr, dojo.hitch(this, function(name, oldValue, newValue){
20666 if(this._postStartup && this._dim){
20667 this.resize(this._dim);
a089699c 20668 }
81bea17a
AD
20669 }))
20670 );
20671 }, this);
a089699c
AD
20672
20673 // Increment the width of the wrapper when a tab is added
20674 // This makes sure that the buttons never wrap.
20675 // The value 200 is chosen as it should be bigger than most
20676 // Tab button widths.
20677 dojo.style(this.containerNode, "width",
20678 (dojo.style(this.containerNode, "width") + 200) + "px");
20679 },
20680
20681 onRemoveChild: function(page, insertIndex){
20682 // null out _selectedTab because we are about to delete that dom node
20683 var button = this.pane2button[page.id];
20684 if(this._selectedTab === button.domNode){
20685 this._selectedTab = null;
20686 }
20687
a089699c
AD
20688 this.inherited(arguments);
20689 },
20690
20691 _initButtons: function(){
20692 // summary:
20693 // Creates the buttons used to scroll to view tabs that
20694 // may not be visible if the TabContainer is too narrow.
a089699c
AD
20695
20696 // Make a list of the buttons to display when the tab labels become
20697 // wider than the TabContainer, and hide the other buttons.
20698 // Also gets the total width of the displayed buttons.
20699 this._btnWidth = 0;
20700 this._buttons = dojo.query("> .tabStripButton", this.domNode).filter(function(btn){
20701 if((this.useMenu && btn == this._menuBtn.domNode) ||
20702 (this.useSlider && (btn == this._rightBtn.domNode || btn == this._leftBtn.domNode))){
81bea17a 20703 this._btnWidth += dojo._getMarginSize(btn).w;
a089699c
AD
20704 return true;
20705 }else{
20706 dojo.style(btn, "display", "none");
20707 return false;
20708 }
20709 }, this);
a089699c
AD
20710 },
20711
20712 _getTabsWidth: function(){
20713 var children = this.getChildren();
20714 if(children.length){
20715 var leftTab = children[this.isLeftToRight() ? 0 : children.length - 1].domNode,
20716 rightTab = children[this.isLeftToRight() ? children.length - 1 : 0].domNode;
20717 return rightTab.offsetLeft + dojo.style(rightTab, "width") - leftTab.offsetLeft;
20718 }else{
20719 return 0;
20720 }
20721 },
20722
20723 _enableBtn: function(width){
20724 // summary:
20725 // Determines if the tabs are wider than the width of the TabContainer, and
20726 // thus that we need to display left/right/menu navigation buttons.
20727 var tabsWidth = this._getTabsWidth();
20728 width = width || dojo.style(this.scrollNode, "width");
20729 return tabsWidth > 0 && width < tabsWidth;
20730 },
20731
20732 resize: function(dim){
20733 // summary:
20734 // Hides or displays the buttons used to scroll the tab list and launch the menu
20735 // that selects tabs.
20736
20737 if(this.domNode.offsetWidth == 0){
20738 return;
20739 }
20740
20741 // Save the dimensions to be used when a child is renamed.
20742 this._dim = dim;
20743
20744 // Set my height to be my natural height (tall enough for one row of tab labels),
20745 // and my content-box width based on margin-box width specified in dim parameter.
20746 // But first reset scrollNode.height in case it was set by layoutChildren() call
20747 // in a previous run of this method.
20748 this.scrollNode.style.height = "auto";
20749 this._contentBox = dijit.layout.marginBox2contentBox(this.domNode, {h: 0, w: dim.w});
20750 this._contentBox.h = this.scrollNode.offsetHeight;
20751 dojo.contentBox(this.domNode, this._contentBox);
20752
20753 // Show/hide the left/right/menu navigation buttons depending on whether or not they
20754 // are needed.
20755 var enable = this._enableBtn(this._contentBox.w);
20756 this._buttons.style("display", enable ? "" : "none");
20757
20758 // Position and size the navigation buttons and the tablist
20759 this._leftBtn.layoutAlign = "left";
20760 this._rightBtn.layoutAlign = "right";
20761 this._menuBtn.layoutAlign = this.isLeftToRight() ? "right" : "left";
20762 dijit.layout.layoutChildren(this.domNode, this._contentBox,
20763 [this._menuBtn, this._leftBtn, this._rightBtn, {domNode: this.scrollNode, layoutAlign: "client"}]);
20764
20765 // set proper scroll so that selected tab is visible
20766 if(this._selectedTab){
20767 if(this._anim && this._anim.status() == "playing"){
20768 this._anim.stop();
20769 }
20770 var w = this.scrollNode,
20771 sl = this._convertToScrollLeft(this._getScrollForSelectedTab());
20772 w.scrollLeft = sl;
20773 }
20774
20775 // Enable/disabled left right buttons depending on whether or not user can scroll to left or right
20776 this._setButtonClass(this._getScroll());
20777
20778 this._postResize = true;
81bea17a
AD
20779
20780 // Return my size so layoutChildren() can use it.
20781 // Also avoids IE9 layout glitch on browser resize when scroll buttons present
20782 return {h: this._contentBox.h, w: dim.w};
a089699c
AD
20783 },
20784
20785 _getScroll: function(){
20786 // summary:
20787 // Returns the current scroll of the tabs where 0 means
20788 // "scrolled all the way to the left" and some positive number, based on #
20789 // of pixels of possible scroll (ex: 1000) means "scrolled all the way to the right"
20790 var sl = (this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit) ? this.scrollNode.scrollLeft :
20791 dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width")
20792 + (dojo.isIE == 8 ? -1 : 1) * this.scrollNode.scrollLeft;
20793 return sl;
20794 },
20795
20796 _convertToScrollLeft: function(val){
20797 // summary:
20798 // Given a scroll value where 0 means "scrolled all the way to the left"
20799 // and some positive number, based on # of pixels of possible scroll (ex: 1000)
20800 // means "scrolled all the way to the right", return value to set this.scrollNode.scrollLeft
20801 // to achieve that scroll.
20802 //
20803 // This method is to adjust for RTL funniness in various browsers and versions.
20804 if(this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit){
20805 return val;
20806 }else{
20807 var maxScroll = dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width");
20808 return (dojo.isIE == 8 ? -1 : 1) * (val - maxScroll);
20809 }
20810 },
20811
20812 onSelectChild: function(/*dijit._Widget*/ page){
20813 // summary:
20814 // Smoothly scrolls to a tab when it is selected.
20815
20816 var tab = this.pane2button[page.id];
20817 if(!tab || !page){return;}
20818
20819 // Scroll to the selected tab, except on startup, when scrolling is handled in resize()
20820 var node = tab.domNode;
20821 if(this._postResize && node != this._selectedTab){
20822 this._selectedTab = node;
20823
20824 var sl = this._getScroll();
20825
20826 if(sl > node.offsetLeft ||
20827 sl + dojo.style(this.scrollNode, "width") <
20828 node.offsetLeft + dojo.style(node, "width")){
20829 this.createSmoothScroll().play();
20830 }
20831 }
20832
20833 this.inherited(arguments);
20834 },
20835
20836 _getScrollBounds: function(){
20837 // summary:
20838 // Returns the minimum and maximum scroll setting to show the leftmost and rightmost
20839 // tabs (respectively)
20840 var children = this.getChildren(),
20841 scrollNodeWidth = dojo.style(this.scrollNode, "width"), // about 500px
20842 containerWidth = dojo.style(this.containerNode, "width"), // 50,000px
20843 maxPossibleScroll = containerWidth - scrollNodeWidth, // scrolling until right edge of containerNode visible
20844 tabsWidth = this._getTabsWidth();
20845
20846 if(children.length && tabsWidth > scrollNodeWidth){
20847 // Scrolling should happen
20848 return {
20849 min: this.isLeftToRight() ? 0 : children[children.length-1].domNode.offsetLeft,
20850 max: this.isLeftToRight() ?
20851 (children[children.length-1].domNode.offsetLeft + dojo.style(children[children.length-1].domNode, "width")) - scrollNodeWidth :
20852 maxPossibleScroll
20853 };
20854 }else{
20855 // No scrolling needed, all tabs visible, we stay either scrolled to far left or far right (depending on dir)
20856 var onlyScrollPosition = this.isLeftToRight() ? 0 : maxPossibleScroll;
20857 return {
20858 min: onlyScrollPosition,
20859 max: onlyScrollPosition
20860 };
20861 }
20862 },
20863
20864 _getScrollForSelectedTab: function(){
20865 // summary:
20866 // Returns the scroll value setting so that the selected tab
20867 // will appear in the center
20868 var w = this.scrollNode,
20869 n = this._selectedTab,
20870 scrollNodeWidth = dojo.style(this.scrollNode, "width"),
20871 scrollBounds = this._getScrollBounds();
20872
20873 // TODO: scroll minimal amount (to either right or left) so that
20874 // selected tab is fully visible, and just return if it's already visible?
20875 var pos = (n.offsetLeft + dojo.style(n, "width")/2) - scrollNodeWidth/2;
20876 pos = Math.min(Math.max(pos, scrollBounds.min), scrollBounds.max);
20877
20878 // TODO:
20879 // If scrolling close to the left side or right side, scroll
20880 // all the way to the left or right. See this._minScroll.
20881 // (But need to make sure that doesn't scroll the tab out of view...)
20882 return pos;
20883 },
20884
81bea17a 20885 createSmoothScroll: function(x){
a089699c
AD
20886 // summary:
20887 // Creates a dojo._Animation object that smoothly scrolls the tab list
20888 // either to a fixed horizontal pixel value, or to the selected tab.
20889 // description:
20890 // If an number argument is passed to the function, that horizontal
20891 // pixel position is scrolled to. Otherwise the currently selected
20892 // tab is scrolled to.
20893 // x: Integer?
20894 // An optional pixel value to scroll to, indicating distance from left.
20895
20896 // Calculate position to scroll to
20897 if(arguments.length > 0){
20898 // position specified by caller, just make sure it's within bounds
20899 var scrollBounds = this._getScrollBounds();
20900 x = Math.min(Math.max(x, scrollBounds.min), scrollBounds.max);
20901 }else{
20902 // scroll to center the current tab
20903 x = this._getScrollForSelectedTab();
20904 }
20905
20906 if(this._anim && this._anim.status() == "playing"){
20907 this._anim.stop();
20908 }
20909
20910 var self = this,
20911 w = this.scrollNode,
20912 anim = new dojo._Animation({
20913 beforeBegin: function(){
20914 if(this.curve){ delete this.curve; }
20915 var oldS = w.scrollLeft,
20916 newS = self._convertToScrollLeft(x);
20917 anim.curve = new dojo._Line(oldS, newS);
20918 },
20919 onAnimate: function(val){
20920 w.scrollLeft = val;
20921 }
20922 });
20923 this._anim = anim;
20924
20925 // Disable/enable left/right buttons according to new scroll position
20926 this._setButtonClass(x);
20927
20928 return anim; // dojo._Animation
20929 },
20930
81bea17a 20931 _getBtnNode: function(/*Event*/ e){
a089699c
AD
20932 // summary:
20933 // Gets a button DOM node from a mouse click event.
20934 // e:
20935 // The mouse click event.
20936 var n = e.target;
20937 while(n && !dojo.hasClass(n, "tabStripButton")){
20938 n = n.parentNode;
20939 }
20940 return n;
20941 },
20942
81bea17a 20943 doSlideRight: function(/*Event*/ e){
a089699c
AD
20944 // summary:
20945 // Scrolls the menu to the right.
20946 // e:
20947 // The mouse click event.
20948 this.doSlide(1, this._getBtnNode(e));
20949 },
20950
81bea17a 20951 doSlideLeft: function(/*Event*/ e){
a089699c
AD
20952 // summary:
20953 // Scrolls the menu to the left.
20954 // e:
20955 // The mouse click event.
20956 this.doSlide(-1,this._getBtnNode(e));
20957 },
20958
81bea17a 20959 doSlide: function(/*Number*/ direction, /*DomNode*/ node){
a089699c
AD
20960 // summary:
20961 // Scrolls the tab list to the left or right by 75% of the widget width.
20962 // direction:
20963 // If the direction is 1, the widget scrolls to the right, if it is
20964 // -1, it scrolls to the left.
20965
20966 if(node && dojo.hasClass(node, "dijitTabDisabled")){return;}
20967
20968 var sWidth = dojo.style(this.scrollNode, "width");
20969 var d = (sWidth * 0.75) * direction;
20970
20971 var to = this._getScroll() + d;
20972
20973 this._setButtonClass(to);
20974
20975 this.createSmoothScroll(to).play();
20976 },
20977
81bea17a 20978 _setButtonClass: function(/*Number*/ scroll){
a089699c
AD
20979 // summary:
20980 // Disables the left scroll button if the tabs are scrolled all the way to the left,
20981 // or the right scroll button in the opposite case.
20982 // scroll: Integer
20983 // amount of horizontal scroll
20984
20985 var scrollBounds = this._getScrollBounds();
20986 this._leftBtn.set("disabled", scroll <= scrollBounds.min);
20987 this._rightBtn.set("disabled", scroll >= scrollBounds.max);
20988 }
20989});
20990
a089699c 20991
81bea17a
AD
20992dojo.declare("dijit.layout._ScrollingTabControllerButtonMixin", null, {
20993 baseClass: "dijitTab tabStripButton",
20994
20995 templateString: dojo.cache("dijit.layout", "templates/_ScrollingTabControllerButton.html", "<div dojoAttachEvent=\"onclick:_onButtonClick\">\n\t<div role=\"presentation\" class=\"dijitTabInnerDiv\" dojoattachpoint=\"innerDiv,focusNode\">\n\t\t<div role=\"presentation\" class=\"dijitTabContent dijitButtonContents\" dojoattachpoint=\"tabContent\">\n\t\t\t<img role=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t\t<span dojoAttachPoint=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>\n"),
a089699c
AD
20996
20997 // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be
20998 // able to tab to the left/right/menu buttons
81bea17a
AD
20999 tabIndex: "",
21000
21001 // Similarly, override FormWidget.isFocusable() because clicking a button shouldn't focus it
21002 // either (this override avoids focus() call in FormWidget.js)
21003 isFocusable: function(){ return false; }
21004});
21005
21006dojo.declare("dijit.layout._ScrollingTabControllerButton",
21007 [dijit.form.Button, dijit.layout._ScrollingTabControllerButtonMixin]);
21008
21009dojo.declare(
21010 "dijit.layout._ScrollingTabControllerMenuButton",
21011 [dijit.form.Button, dijit._HasDropDown, dijit.layout._ScrollingTabControllerButtonMixin],
21012{
21013 // id of the TabContainer itself
21014 containerId: "",
21015
21016 // -1 so user can't tab into the button, but so that button can still be focused programatically.
21017 // Because need to move focus to the button (or somewhere) before the menu is hidden or IE6 will crash.
21018 tabIndex: "-1",
21019
21020 isLoaded: function(){
21021 // recreate menu every time, in case the TabContainer's list of children (or their icons/labels) have changed
21022 return false;
21023 },
21024
21025 loadDropDown: function(callback){
21026 this.dropDown = new dijit.Menu({
21027 id: this.containerId + "_menu",
21028 dir: this.dir,
21029 lang: this.lang
21030 });
21031 var container = dijit.byId(this.containerId);
21032 dojo.forEach(container.getChildren(), function(page){
21033 var menuItem = new dijit.MenuItem({
21034 id: page.id + "_stcMi",
21035 label: page.title,
21036 iconClass: page.iconClass,
21037 dir: page.dir,
21038 lang: page.lang,
21039 onClick: function(){
21040 container.selectChild(page);
21041 }
21042 });
21043 this.dropDown.addChild(menuItem);
21044 }, this);
21045 callback();
21046 },
21047
21048 closeDropDown: function(/*Boolean*/ focus){
21049 this.inherited(arguments);
21050 if(this.dropDown){
21051 this.dropDown.destroyRecursive();
21052 delete this.dropDown;
21053 }
a089699c 21054 }
81bea17a 21055});
a089699c
AD
21056
21057}
21058
21059if(!dojo._hasResource["dijit.layout.TabContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21060dojo._hasResource["dijit.layout.TabContainer"] = true;
21061dojo.provide("dijit.layout.TabContainer");
21062
21063
21064
21065
21066
21067dojo.declare("dijit.layout.TabContainer",
21068 dijit.layout._TabContainerBase,
21069 {
21070 // summary:
21071 // A Container with tabs to select each child (only one of which is displayed at a time).
21072 // description:
21073 // A TabContainer is a container that has multiple panes, but shows only
21074 // one pane at a time. There are a set of tabs corresponding to each pane,
21075 // where each tab has the name (aka title) of the pane, and optionally a close button.
21076
21077 // useMenu: [const] Boolean
21078 // True if a menu should be used to select tabs when they are too
21079 // wide to fit the TabContainer, false otherwise.
21080 useMenu: true,
21081
21082 // useSlider: [const] Boolean
21083 // True if a slider should be used to select tabs when they are too
21084 // wide to fit the TabContainer, false otherwise.
21085 useSlider: true,
21086
21087 // controllerWidget: String
21088 // An optional parameter to override the widget used to display the tab labels
21089 controllerWidget: "",
21090
21091 _makeController: function(/*DomNode*/ srcNode){
21092 // summary:
21093 // Instantiate tablist controller widget and return reference to it.
21094 // Callback from _TabContainerBase.postCreate().
21095 // tags:
21096 // protected extension
21097
21098 var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"),
21099 TabController = dojo.getObject(this.controllerWidget);
21100
21101 return new TabController({
21102 id: this.id + "_tablist",
21103 dir: this.dir,
21104 lang: this.lang,
21105 tabPosition: this.tabPosition,
21106 doLayout: this.doLayout,
21107 containerId: this.id,
21108 "class": cls,
21109 nested: this.nested,
21110 useMenu: this.useMenu,
21111 useSlider: this.useSlider,
21112 tabStripClass: this.tabStrip ? this.baseClass + (this.tabStrip ? "":"No") + "Strip": null
21113 }, srcNode);
21114 },
21115
21116 postMixInProperties: function(){
21117 this.inherited(arguments);
21118
21119 // Scrolling controller only works for horizontal non-nested tabs
21120 if(!this.controllerWidget){
21121 this.controllerWidget = (this.tabPosition == "top" || this.tabPosition == "bottom") && !this.nested ?
21122 "dijit.layout.ScrollingTabController" : "dijit.layout.TabController";
21123 }
21124 }
21125});
21126
a089699c
AD
21127}
21128
21129if(!dojo._hasResource["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21130dojo._hasResource["dojo.number"] = true;
21131dojo.provide("dojo.number");
21132
21133
21134
21135
21136
81bea17a 21137dojo.getObject("number", true, dojo);
a089699c
AD
21138
21139/*=====
21140dojo.number = {
21141 // summary: localized formatting and parsing routines for Number
21142}
21143
21144dojo.number.__FormatOptions = function(){
21145 // pattern: String?
21146 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21147 // with this string. Default value is based on locale. Overriding this property will defeat
21148 // localization. Literal characters in patterns are not supported.
21149 // type: String?
21150 // choose a format type based on the locale from the following:
21151 // decimal, scientific (not yet supported), percent, currency. decimal by default.
21152 // places: Number?
21153 // fixed number of decimal places to show. This overrides any
21154 // information in the provided pattern.
21155 // round: Number?
21156 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
21157 // means do not round.
21158 // locale: String?
21159 // override the locale used to determine formatting rules
21160 // fractional: Boolean?
21161 // If false, show no decimal places, overriding places and pattern settings.
21162 this.pattern = pattern;
21163 this.type = type;
21164 this.places = places;
21165 this.round = round;
21166 this.locale = locale;
21167 this.fractional = fractional;
21168}
21169=====*/
21170
21171dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){
21172 // summary:
21173 // Format a Number as a String, using locale-specific settings
21174 // description:
21175 // Create a string from a Number using a known localized pattern.
21176 // Formatting patterns appropriate to the locale are chosen from the
21177 // [Common Locale Data Repository](http://unicode.org/cldr) as well as the appropriate symbols and
21178 // delimiters.
21179 // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null.
21180 // value:
21181 // the number to be formatted
21182
21183 options = dojo.mixin({}, options || {});
21184 var locale = dojo.i18n.normalizeLocale(options.locale),
21185 bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale);
21186 options.customs = bundle;
21187 var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
21188 if(isNaN(value) || Math.abs(value) == Infinity){ return null; } // null
21189 return dojo.number._applyPattern(value, pattern, options); // String
21190};
21191
21192//dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
21193dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough
21194
21195dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){
21196 // summary:
21197 // Apply pattern to format value as a string using options. Gives no
21198 // consideration to local customs.
21199 // value:
21200 // the number to be formatted.
21201 // pattern:
21202 // a pattern string as described by
21203 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21204 // options: dojo.number.__FormatOptions?
21205 // _applyPattern is usually called via `dojo.number.format()` which
21206 // populates an extra property in the options parameter, "customs".
21207 // The customs object specifies group and decimal parameters if set.
21208
21209 //TODO: support escapes
21210 options = options || {};
21211 var group = options.customs.group,
21212 decimal = options.customs.decimal,
21213 patternList = pattern.split(';'),
21214 positivePattern = patternList[0];
21215 pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern);
21216
21217 //TODO: only test against unescaped
21218 if(pattern.indexOf('%') != -1){
21219 value *= 100;
21220 }else if(pattern.indexOf('\u2030') != -1){
21221 value *= 1000; // per mille
21222 }else if(pattern.indexOf('\u00a4') != -1){
21223 group = options.customs.currencyGroup || group;//mixins instead?
21224 decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead?
21225 pattern = pattern.replace(/\u00a4{1,3}/, function(match){
21226 var prop = ["symbol", "currency", "displayName"][match.length-1];
21227 return options[prop] || options.currency || "";
21228 });
21229 }else if(pattern.indexOf('E') != -1){
21230 throw new Error("exponential notation not supported");
21231 }
21232
21233 //TODO: support @ sig figs?
21234 var numberPatternRE = dojo.number._numberPatternRE;
21235 var numberPattern = positivePattern.match(numberPatternRE);
21236 if(!numberPattern){
21237 throw new Error("unable to find a number expression in pattern: "+pattern);
21238 }
21239 if(options.fractional === false){ options.places = 0; }
21240 return pattern.replace(numberPatternRE,
21241 dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round}));
81bea17a 21242};
a089699c
AD
21243
21244dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){
21245 // summary:
21246 // Rounds to the nearest value with the given number of decimal places, away from zero
21247 // description:
21248 // Rounds to the nearest value with the given number of decimal places, away from zero if equal.
21249 // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by
21250 // fractional increments also, such as the nearest quarter.
21251 // NOTE: Subject to floating point errors. See dojox.math.round for experimental workaround.
21252 // value:
21253 // The number to round
21254 // places:
21255 // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding.
21256 // Must be non-negative.
21257 // increment:
21258 // Rounds next place to nearest value of increment/10. 10 by default.
21259 // example:
21260 // >>> dojo.number.round(-0.5)
21261 // -1
21262 // >>> dojo.number.round(162.295, 2)
21263 // 162.29 // note floating point error. Should be 162.3
21264 // >>> dojo.number.round(10.71, 0, 2.5)
21265 // 10.75
21266 var factor = 10 / (increment || 10);
21267 return (factor * +value).toFixed(places) / factor; // Number
81bea17a 21268};
a089699c
AD
21269
21270if((0.9).toFixed() == 0){
21271 // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit
21272 // is just after the rounding place and is >=5
21273 (function(){
21274 var round = dojo.number.round;
21275 dojo.number.round = function(v, p, m){
21276 var d = Math.pow(10, -p || 0), a = Math.abs(v);
21277 if(!v || a >= d || a * Math.pow(10, p + 1) < 5){
21278 d = 0;
21279 }
21280 return round(v, p, m) + (v > 0 ? d : -d);
81bea17a 21281 };
a089699c
AD
21282 })();
21283}
21284
21285/*=====
21286dojo.number.__FormatAbsoluteOptions = function(){
21287 // decimal: String?
21288 // the decimal separator
21289 // group: String?
21290 // the group separator
21291 // places: Number?|String?
21292 // number of decimal places. the range "n,m" will format to m places.
21293 // round: Number?
21294 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
21295 // means don't round.
21296 this.decimal = decimal;
21297 this.group = group;
21298 this.places = places;
21299 this.round = round;
21300}
21301=====*/
21302
21303dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){
81bea17a 21304 // summary:
a089699c
AD
21305 // Apply numeric pattern to absolute value using options. Gives no
21306 // consideration to local customs.
21307 // value:
21308 // the number to be formatted, ignores sign
21309 // pattern:
21310 // the number portion of a pattern (e.g. `#,##0.00`)
21311 options = options || {};
21312 if(options.places === true){options.places=0;}
21313 if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit
21314
21315 var patternParts = pattern.split("."),
21316 comma = typeof options.places == "string" && options.places.indexOf(","),
21317 maxPlaces = options.places;
21318 if(comma){
21319 maxPlaces = options.places.substring(comma + 1);
21320 }else if(!(maxPlaces >= 0)){
21321 maxPlaces = (patternParts[1] || []).length;
21322 }
21323 if(!(options.round < 0)){
21324 value = dojo.number.round(value, maxPlaces, options.round);
21325 }
21326
21327 var valueParts = String(Math.abs(value)).split("."),
21328 fractional = valueParts[1] || "";
21329 if(patternParts[1] || options.places){
21330 if(comma){
21331 options.places = options.places.substring(0, comma);
21332 }
21333 // Pad fractional with trailing zeros
21334 var pad = options.places !== undefined ? options.places : (patternParts[1] && patternParts[1].lastIndexOf("0") + 1);
21335 if(pad > fractional.length){
21336 valueParts[1] = dojo.string.pad(fractional, pad, '0', true);
21337 }
21338
21339 // Truncate fractional
21340 if(maxPlaces < fractional.length){
21341 valueParts[1] = fractional.substr(0, maxPlaces);
21342 }
21343 }else{
21344 if(valueParts[1]){ valueParts.pop(); }
21345 }
21346
21347 // Pad whole with leading zeros
21348 var patternDigits = patternParts[0].replace(',', '');
21349 pad = patternDigits.indexOf("0");
21350 if(pad != -1){
21351 pad = patternDigits.length - pad;
21352 if(pad > valueParts[0].length){
21353 valueParts[0] = dojo.string.pad(valueParts[0], pad);
21354 }
21355
21356 // Truncate whole
21357 if(patternDigits.indexOf("#") == -1){
21358 valueParts[0] = valueParts[0].substr(valueParts[0].length - pad);
21359 }
21360 }
21361
21362 // Add group separators
21363 var index = patternParts[0].lastIndexOf(','),
21364 groupSize, groupSize2;
21365 if(index != -1){
21366 groupSize = patternParts[0].length - index - 1;
21367 var remainder = patternParts[0].substr(0, index);
21368 index = remainder.lastIndexOf(',');
21369 if(index != -1){
21370 groupSize2 = remainder.length - index - 1;
21371 }
21372 }
21373 var pieces = [];
21374 for(var whole = valueParts[0]; whole;){
21375 var off = whole.length - groupSize;
21376 pieces.push((off > 0) ? whole.substr(off) : whole);
21377 whole = (off > 0) ? whole.slice(0, off) : "";
21378 if(groupSize2){
21379 groupSize = groupSize2;
21380 delete groupSize2;
21381 }
21382 }
21383 valueParts[0] = pieces.reverse().join(options.group || ",");
21384
21385 return valueParts.join(options.decimal || ".");
21386};
21387
21388/*=====
21389dojo.number.__RegexpOptions = function(){
21390 // pattern: String?
21391 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21392 // with this string. Default value is based on locale. Overriding this property will defeat
21393 // localization.
21394 // type: String?
21395 // choose a format type based on the locale from the following:
21396 // decimal, scientific (not yet supported), percent, currency. decimal by default.
21397 // locale: String?
21398 // override the locale used to determine formatting rules
21399 // strict: Boolean?
21400 // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
21401 // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
21402 // places: Number|String?
21403 // number of decimal places to accept: Infinity, a positive number, or
21404 // a range "n,m". Defined by pattern or Infinity if pattern not provided.
21405 this.pattern = pattern;
21406 this.type = type;
21407 this.locale = locale;
21408 this.strict = strict;
21409 this.places = places;
21410}
21411=====*/
21412dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){
21413 // summary:
21414 // Builds the regular needed to parse a number
21415 // description:
21416 // Returns regular expression with positive and negative match, group
21417 // and decimal separators
21418 return dojo.number._parseInfo(options).regexp; // String
81bea17a 21419};
a089699c
AD
21420
21421dojo.number._parseInfo = function(/*Object?*/options){
21422 options = options || {};
21423 var locale = dojo.i18n.normalizeLocale(options.locale),
21424 bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale),
21425 pattern = options.pattern || bundle[(options.type || "decimal") + "Format"],
21426//TODO: memoize?
21427 group = bundle.group,
21428 decimal = bundle.decimal,
21429 factor = 1;
21430
21431 if(pattern.indexOf('%') != -1){
21432 factor /= 100;
21433 }else if(pattern.indexOf('\u2030') != -1){
21434 factor /= 1000; // per mille
21435 }else{
21436 var isCurrency = pattern.indexOf('\u00a4') != -1;
21437 if(isCurrency){
21438 group = bundle.currencyGroup || group;
21439 decimal = bundle.currencyDecimal || decimal;
21440 }
21441 }
21442
21443 //TODO: handle quoted escapes
21444 var patternList = pattern.split(';');
21445 if(patternList.length == 1){
21446 patternList.push("-" + patternList[0]);
21447 }
21448
21449 var re = dojo.regexp.buildGroupRE(patternList, function(pattern){
21450 pattern = "(?:"+dojo.regexp.escapeString(pattern, '.')+")";
21451 return pattern.replace(dojo.number._numberPatternRE, function(format){
21452 var flags = {
21453 signed: false,
21454 separator: options.strict ? group : [group,""],
21455 fractional: options.fractional,
21456 decimal: decimal,
21457 exponent: false
21458 },
21459
21460 parts = format.split('.'),
21461 places = options.places;
21462
21463 // special condition for percent (factor != 1)
21464 // allow decimal places even if not specified in pattern
21465 if(parts.length == 1 && factor != 1){
21466 parts[1] = "###";
21467 }
21468 if(parts.length == 1 || places === 0){
21469 flags.fractional = false;
21470 }else{
21471 if(places === undefined){ places = options.pattern ? parts[1].lastIndexOf('0') + 1 : Infinity; }
21472 if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified
21473 if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; }
21474 flags.places = places;
21475 }
21476 var groups = parts[0].split(',');
21477 if(groups.length > 1){
21478 flags.groupSize = groups.pop().length;
21479 if(groups.length > 1){
21480 flags.groupSize2 = groups.pop().length;
21481 }
21482 }
21483 return "("+dojo.number._realNumberRegexp(flags)+")";
21484 });
21485 }, true);
21486
21487 if(isCurrency){
21488 // substitute the currency symbol for the placeholder in the pattern
21489 re = re.replace(/([\s\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match, before, target, after){
21490 var prop = ["symbol", "currency", "displayName"][target.length-1],
21491 symbol = dojo.regexp.escapeString(options[prop] || options.currency || "");
21492 before = before ? "[\\s\\xa0]" : "";
21493 after = after ? "[\\s\\xa0]" : "";
21494 if(!options.strict){
21495 if(before){before += "*";}
21496 if(after){after += "*";}
21497 return "(?:"+before+symbol+after+")?";
21498 }
21499 return before+symbol+after;
21500 });
21501 }
21502
21503//TODO: substitute localized sign/percent/permille/etc.?
21504
21505 // normalize whitespace and return
21506 return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object
81bea17a 21507};
a089699c
AD
21508
21509/*=====
21510dojo.number.__ParseOptions = function(){
21511 // pattern: String?
21512 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21513 // with this string. Default value is based on locale. Overriding this property will defeat
21514 // localization. Literal characters in patterns are not supported.
21515 // type: String?
21516 // choose a format type based on the locale from the following:
21517 // decimal, scientific (not yet supported), percent, currency. decimal by default.
21518 // locale: String?
21519 // override the locale used to determine formatting rules
21520 // strict: Boolean?
21521 // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
21522 // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
21523 // fractional: Boolean?|Array?
21524 // Whether to include the fractional portion, where the number of decimal places are implied by pattern
21525 // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional.
21526 this.pattern = pattern;
21527 this.type = type;
21528 this.locale = locale;
21529 this.strict = strict;
21530 this.fractional = fractional;
21531}
21532=====*/
21533dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){
21534 // summary:
21535 // Convert a properly formatted string to a primitive Number, using
21536 // locale-specific settings.
21537 // description:
21538 // Create a Number from a string using a known localized pattern.
21539 // Formatting patterns are chosen appropriate to the locale
21540 // and follow the syntax described by
21541 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21542 // Note that literal characters in patterns are not supported.
21543 // expression:
21544 // A string representation of a Number
21545 var info = dojo.number._parseInfo(options),
21546 results = (new RegExp("^"+info.regexp+"$")).exec(expression);
21547 if(!results){
21548 return NaN; //NaN
21549 }
21550 var absoluteMatch = results[1]; // match for the positive expression
21551 if(!results[1]){
21552 if(!results[2]){
21553 return NaN; //NaN
21554 }
21555 // matched the negative pattern
21556 absoluteMatch =results[2];
21557 info.factor *= -1;
21558 }
21559
21560 // Transform it to something Javascript can parse as a number. Normalize
21561 // decimal point and strip out group separators or alternate forms of whitespace
21562 absoluteMatch = absoluteMatch.
21563 replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), "").
21564 replace(info.decimal, ".");
21565 // Adjust for negative sign, percent, etc. as necessary
21566 return absoluteMatch * info.factor; //Number
21567};
21568
21569/*=====
21570dojo.number.__RealNumberRegexpFlags = function(){
21571 // places: Number?
21572 // The integer number of decimal places or a range given as "n,m". If
21573 // not given, the decimal part is optional and the number of places is
21574 // unlimited.
21575 // decimal: String?
21576 // A string for the character used as the decimal point. Default
21577 // is ".".
21578 // fractional: Boolean?|Array?
21579 // Whether decimal places are used. Can be true, false, or [true,
21580 // false]. Default is [true, false] which means optional.
21581 // exponent: Boolean?|Array?
21582 // Express in exponential notation. Can be true, false, or [true,
21583 // false]. Default is [true, false], (i.e. will match if the
21584 // exponential part is present are not).
21585 // eSigned: Boolean?|Array?
21586 // The leading plus-or-minus sign on the exponent. Can be true,
21587 // false, or [true, false]. Default is [true, false], (i.e. will
21588 // match if it is signed or unsigned). flags in regexp.integer can be
21589 // applied.
21590 this.places = places;
21591 this.decimal = decimal;
21592 this.fractional = fractional;
21593 this.exponent = exponent;
21594 this.eSigned = eSigned;
21595}
21596=====*/
21597
21598dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){
21599 // summary:
21600 // Builds a regular expression to match a real number in exponential
21601 // notation
21602
21603 // assign default values to missing parameters
21604 flags = flags || {};
21605 //TODO: use mixin instead?
21606 if(!("places" in flags)){ flags.places = Infinity; }
21607 if(typeof flags.decimal != "string"){ flags.decimal = "."; }
21608 if(!("fractional" in flags) || /^0/.test(flags.places)){ flags.fractional = [true, false]; }
21609 if(!("exponent" in flags)){ flags.exponent = [true, false]; }
21610 if(!("eSigned" in flags)){ flags.eSigned = [true, false]; }
21611
21612 var integerRE = dojo.number._integerRegexp(flags),
21613 decimalRE = dojo.regexp.buildGroupRE(flags.fractional,
21614 function(q){
21615 var re = "";
21616 if(q && (flags.places!==0)){
21617 re = "\\" + flags.decimal;
81bea17a
AD
21618 if(flags.places == Infinity){
21619 re = "(?:" + re + "\\d+)?";
a089699c 21620 }else{
81bea17a 21621 re += "\\d{" + flags.places + "}";
a089699c
AD
21622 }
21623 }
21624 return re;
21625 },
21626 true
21627 );
21628
21629 var exponentRE = dojo.regexp.buildGroupRE(flags.exponent,
81bea17a 21630 function(q){
a089699c 21631 if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; }
81bea17a 21632 return "";
a089699c
AD
21633 }
21634 );
21635
81bea17a
AD
21636 var realRE = integerRE + decimalRE;
21637 // allow for decimals without integers, e.g. .25
21638 if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";}
21639 return realRE + exponentRE; // String
21640};
21641
21642/*=====
21643dojo.number.__IntegerRegexpFlags = function(){
21644 // signed: Boolean?
21645 // The leading plus-or-minus sign. Can be true, false, or `[true,false]`.
21646 // Default is `[true, false]`, (i.e. will match if it is signed
21647 // or unsigned).
21648 // separator: String?
21649 // The character used as the thousands separator. Default is no
21650 // separator. For more than one symbol use an array, e.g. `[",", ""]`,
21651 // makes ',' optional.
21652 // groupSize: Number?
21653 // group size between separators
21654 // groupSize2: Number?
21655 // second grouping, where separators 2..n have a different interval than the first separator (for India)
21656 this.signed = signed;
21657 this.separator = separator;
21658 this.groupSize = groupSize;
21659 this.groupSize2 = groupSize2;
21660}
21661=====*/
21662
21663dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){
21664 // summary:
21665 // Builds a regular expression that matches an integer
21666
21667 // assign default values to missing parameters
21668 flags = flags || {};
21669 if(!("signed" in flags)){ flags.signed = [true, false]; }
21670 if(!("separator" in flags)){
21671 flags.separator = "";
21672 }else if(!("groupSize" in flags)){
21673 flags.groupSize = 3;
21674 }
21675
21676 var signRE = dojo.regexp.buildGroupRE(flags.signed,
21677 function(q){ return q ? "[-+]" : ""; },
21678 true
21679 );
21680
21681 var numberRE = dojo.regexp.buildGroupRE(flags.separator,
21682 function(sep){
21683 if(!sep){
21684 return "(?:\\d+)";
21685 }
21686
21687 sep = dojo.regexp.escapeString(sep);
21688 if(sep == " "){ sep = "\\s"; }
21689 else if(sep == "\xa0"){ sep = "\\s\\xa0"; }
21690
21691 var grp = flags.groupSize, grp2 = flags.groupSize2;
21692 //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933
21693 if(grp2){
21694 var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})";
21695 return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE;
21696 }
21697 return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)";
21698 },
21699 true
21700 );
21701
21702 return signRE + numberRE; // String
21703};
21704
21705}
21706
21707if(!dojo._hasResource["dijit.ProgressBar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21708dojo._hasResource["dijit.ProgressBar"] = true;
21709dojo.provide("dijit.ProgressBar");
21710
21711
21712
21713
21714
21715
21716dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], {
21717 // summary:
21718 // A progress indication widget, showing the amount completed
21719 // (often the percentage completed) of a task.
21720 //
21721 // example:
21722 // | <div dojoType="ProgressBar"
21723 // | places="0"
21724 // | value="..." maximum="...">
21725 // | </div>
21726
21727 // progress: [const] String (Percentage or Number)
21728 // Number or percentage indicating amount of task completed.
21729 // Deprecated. Use "value" instead.
21730 progress: "0",
21731
21732 // value: String (Percentage or Number)
21733 // Number or percentage indicating amount of task completed.
21734 // With "%": percentage value, 0% <= progress <= 100%, or
21735 // without "%": absolute value, 0 <= progress <= maximum.
21736 // Infinity means that the progress bar is indeterminate.
21737 value: "",
21738
21739 // maximum: [const] Float
21740 // Max sample number
21741 maximum: 100,
21742
21743 // places: [const] Number
21744 // Number of places to show in values; 0 by default
21745 places: 0,
21746
21747 // indeterminate: [const] Boolean
21748 // If false: show progress value (number or percentage).
21749 // If true: show that a process is underway but that the amount completed is unknown.
21750 // Deprecated. Use "value" instead.
21751 indeterminate: false,
21752
21753 // label: String?
21754 // Label on progress bar. Defaults to percentage for determinate progress bar and
21755 // blank for indeterminate progress bar.
21756 label:"",
21757
21758 // name: String
21759 // this is the field name (for a form) if set. This needs to be set if you want to use
21760 // this widget in a dijit.form.Form widget (such as dijit.Dialog)
21761 name: '',
21762
21763 templateString: dojo.cache("dijit", "templates/ProgressBar.html", "<div class=\"dijitProgressBar dijitProgressBarEmpty\" role=\"progressbar\"\n\t><div dojoAttachPoint=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\" role=\"presentation\"></div\n\t\t><span style=\"visibility:hidden\">&nbsp;</span\n\t></div\n\t><div dojoAttachPoint=\"labelNode\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"></div\n\t><img dojoAttachPoint=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n"),
21764
21765 // _indeterminateHighContrastImagePath: [private] dojo._URL
21766 // URL to image to use for indeterminate progress bar when display is in high contrast mode
21767 _indeterminateHighContrastImagePath:
21768 dojo.moduleUrl("dijit", "themes/a11y/indeterminate_progress.gif"),
21769
21770 postMixInProperties: function(){
21771 this.inherited(arguments);
21772 if(!("value" in this.params)){
21773 this.value = this.indeterminate ? Infinity : this.progress;
21774 }
21775 },
21776
21777 buildRendering: function(){
21778 this.inherited(arguments);
21779 this.indeterminateHighContrastImage.setAttribute("src",
21780 this._indeterminateHighContrastImagePath.toString());
21781 this.update();
21782 },
21783
21784 update: function(/*Object?*/attributes){
21785 // summary:
21786 // Internal method to change attributes of ProgressBar, similar to set(hash). Users should call
21787 // set("value", ...) rather than calling this method directly.
21788 // attributes:
21789 // May provide progress and/or maximum properties on this parameter;
21790 // see attribute specs for details.
21791 // example:
21792 // | myProgressBar.update({'indeterminate': true});
21793 // | myProgressBar.update({'progress': 80});
21794 // | myProgressBar.update({'indeterminate': true, label:"Loading ..." })
21795 // tags:
21796 // private
21797
21798 // TODO: deprecate this method and use set() instead
21799
21800 dojo.mixin(this, attributes || {});
21801 var tip = this.internalProgress, ap = this.domNode;
21802 var percent = 1;
21803 if(this.indeterminate){
21804 dijit.removeWaiState(ap, "valuenow");
21805 dijit.removeWaiState(ap, "valuemin");
21806 dijit.removeWaiState(ap, "valuemax");
21807 }else{
21808 if(String(this.progress).indexOf("%") != -1){
21809 percent = Math.min(parseFloat(this.progress)/100, 1);
21810 this.progress = percent * this.maximum;
21811 }else{
21812 this.progress = Math.min(this.progress, this.maximum);
21813 percent = this.progress / this.maximum;
21814 }
21815
21816 dijit.setWaiState(ap, "describedby", this.labelNode.id);
21817 dijit.setWaiState(ap, "valuenow", this.progress);
21818 dijit.setWaiState(ap, "valuemin", 0);
21819 dijit.setWaiState(ap, "valuemax", this.maximum);
21820 }
21821 this.labelNode.innerHTML = this.report(percent);
21822
21823 dojo.toggleClass(this.domNode, "dijitProgressBarIndeterminate", this.indeterminate);
21824 tip.style.width = (percent * 100) + "%";
21825 this.onChange();
21826 },
21827
21828 _setValueAttr: function(v){
21829 this._set("value", v);
21830 if(v == Infinity){
21831 this.update({indeterminate:true});
21832 }else{
21833 this.update({indeterminate:false, progress:v});
21834 }
21835 },
21836
21837 _setLabelAttr: function(label){
21838 this._set("label", label);
21839 this.update();
21840 },
21841
21842 _setIndeterminateAttr: function(indeterminate){
21843 // Deprecated, use set("value", ...) instead
21844 this.indeterminate = indeterminate;
21845 this.update();
21846 },
21847
21848 report: function(/*float*/percent){
21849 // summary:
21850 // Generates message to show inside progress bar (normally indicating amount of task completed).
21851 // May be overridden.
21852 // tags:
21853 // extension
21854
21855 return this.label ? this.label :
21856 (this.indeterminate ? "&nbsp;" : dojo.number.format(percent, { type: "percent", places: this.places, locale: this.lang }));
21857 },
21858
21859 onChange: function(){
21860 // summary:
21861 // Callback fired when progress updates.
21862 // tags:
21863 // extension
21864 }
21865});
21866
21867}
21868
21869if(!dojo._hasResource["dijit.ToolbarSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21870dojo._hasResource["dijit.ToolbarSeparator"] = true;
21871dojo.provide("dijit.ToolbarSeparator");
21872
21873
21874
21875
21876dojo.declare("dijit.ToolbarSeparator",
21877 [ dijit._Widget, dijit._Templated ],
21878 {
21879 // summary:
21880 // A spacer between two `dijit.Toolbar` items
21881 templateString: '<div class="dijitToolbarSeparator dijitInline" role="presentation"></div>',
21882 buildRendering: function(){
21883 this.inherited(arguments);
21884 dojo.setSelectable(this.domNode, false);
21885 },
21886 isFocusable: function(){
21887 // summary:
21888 // This widget isn't focusable, so pass along that fact.
21889 // tags:
21890 // protected
21891 return false;
21892 }
21893
21894 });
21895
21896}
21897
21898if(!dojo._hasResource["dijit.Toolbar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21899dojo._hasResource["dijit.Toolbar"] = true;
21900dojo.provide("dijit.Toolbar");
21901
21902
21903
21904
21905
21906
21907// Note: require of ToolbarSeparator is for back-compat, remove for 2.0
21908
21909dojo.declare("dijit.Toolbar",
21910 [dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
21911 {
21912 // summary:
21913 // A Toolbar widget, used to hold things like `dijit.Editor` buttons
21914
21915 templateString:
21916 '<div class="dijit" role="toolbar" tabIndex="${tabIndex}" dojoAttachPoint="containerNode">' +
21917 // '<table style="table-layout: fixed" class="dijitReset dijitToolbarTable">' + // factor out style
21918 // '<tr class="dijitReset" dojoAttachPoint="containerNode"></tr>'+
21919 // '</table>' +
21920 '</div>',
21921
21922 baseClass: "dijitToolbar",
a089699c 21923
81bea17a
AD
21924 postCreate: function(){
21925 this.inherited(arguments);
a089699c 21926
81bea17a
AD
21927 this.connectKeyNavHandlers(
21928 this.isLeftToRight() ? [dojo.keys.LEFT_ARROW] : [dojo.keys.RIGHT_ARROW],
21929 this.isLeftToRight() ? [dojo.keys.RIGHT_ARROW] : [dojo.keys.LEFT_ARROW]
21930 );
21931 },
a089699c 21932
81bea17a
AD
21933 startup: function(){
21934 if(this._started){ return; }
21935
21936 this.startupKeyNavChildren();
21937
21938 this.inherited(arguments);
a089699c 21939 }
81bea17a
AD
21940}
21941);
a089699c 21942
81bea17a 21943}
a089699c 21944
81bea17a
AD
21945if(!dojo._hasResource["dojo.DeferredList"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
21946dojo._hasResource["dojo.DeferredList"] = true;
21947dojo.provide("dojo.DeferredList");
a089699c 21948
a089699c 21949
81bea17a
AD
21950dojo.DeferredList = function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/ consumeErrors, /*Function?*/ canceller){
21951 // summary:
21952 // Provides event handling for a group of Deferred objects.
21953 // description:
21954 // DeferredList takes an array of existing deferreds and returns a new deferred of its own
21955 // this new deferred will typically have its callback fired when all of the deferreds in
21956 // the given list have fired their own deferreds. The parameters `fireOnOneCallback` and
21957 // fireOnOneErrback, will fire before all the deferreds as appropriate
21958 //
21959 // list:
21960 // The list of deferreds to be synchronizied with this DeferredList
21961 // fireOnOneCallback:
21962 // Will cause the DeferredLists callback to be fired as soon as any
21963 // of the deferreds in its list have been fired instead of waiting until
21964 // the entire list has finished
21965 // fireonOneErrback:
21966 // Will cause the errback to fire upon any of the deferreds errback
21967 // canceller:
21968 // A deferred canceller function, see dojo.Deferred
21969 var resultList = [];
21970 dojo.Deferred.call(this);
21971 var self = this;
21972 if(list.length === 0 && !fireOnOneCallback){
21973 this.resolve([0, []]);
21974 }
21975 var finished = 0;
21976 dojo.forEach(list, function(item, i){
21977 item.then(function(result){
21978 if(fireOnOneCallback){
21979 self.resolve([i, result]);
21980 }else{
21981 addResult(true, result);
a089699c 21982 }
81bea17a
AD
21983 },function(error){
21984 if(fireOnOneErrback){
21985 self.reject(error);
21986 }else{
21987 addResult(false, error);
21988 }
21989 if(consumeErrors){
21990 return null;
21991 }
21992 throw error;
21993 });
21994 function addResult(succeeded, result){
21995 resultList[i] = [succeeded, result];
21996 finished++;
21997 if(finished === list.length){
21998 self.resolve(resultList);
21999 }
22000
22001 }
22002 });
22003};
22004dojo.DeferredList.prototype = new dojo.Deferred();
a089699c 22005
81bea17a
AD
22006dojo.DeferredList.prototype.gatherResults= function(deferredList){
22007 // summary:
22008 // Gathers the results of the deferreds for packaging
22009 // as the parameters to the Deferred Lists' callback
a089699c 22010
81bea17a
AD
22011 var d = new dojo.DeferredList(deferredList, false, true, false);
22012 d.addCallback(function(results){
22013 var ret = [];
22014 dojo.forEach(results, function(result){
22015 ret.push(result[1]);
22016 });
22017 return ret;
22018 });
22019 return d;
22020};
a089699c 22021
81bea17a 22022}
a089699c 22023
81bea17a
AD
22024if(!dojo._hasResource["dijit.tree.TreeStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
22025dojo._hasResource["dijit.tree.TreeStoreModel"] = true;
22026dojo.provide("dijit.tree.TreeStoreModel");
a089699c
AD
22027
22028
81bea17a
AD
22029dojo.declare(
22030 "dijit.tree.TreeStoreModel",
22031 null,
22032 {
22033 // summary:
22034 // Implements dijit.Tree.model connecting to a store with a single
22035 // root item. Any methods passed into the constructor will override
22036 // the ones defined here.
a089699c 22037
81bea17a
AD
22038 // store: dojo.data.Store
22039 // Underlying store
22040 store: null,
a089699c 22041
81bea17a
AD
22042 // childrenAttrs: String[]
22043 // One or more attribute names (attributes in the dojo.data item) that specify that item's children
22044 childrenAttrs: ["children"],
a089699c 22045
81bea17a
AD
22046 // newItemIdAttr: String
22047 // Name of attribute in the Object passed to newItem() that specifies the id.
22048 //
22049 // If newItemIdAttr is set then it's used when newItem() is called to see if an
22050 // item with the same id already exists, and if so just links to the old item
22051 // (so that the old item ends up with two parents).
22052 //
22053 // Setting this to null or "" will make every drop create a new item.
22054 newItemIdAttr: "id",
a089699c 22055
81bea17a
AD
22056 // labelAttr: String
22057 // If specified, get label for tree node from this attribute, rather
22058 // than by calling store.getLabel()
22059 labelAttr: "",
a089699c 22060
81bea17a
AD
22061 // root: [readonly] dojo.data.Item
22062 // Pointer to the root item (read only, not a parameter)
22063 root: null,
a089699c 22064
81bea17a
AD
22065 // query: anything
22066 // Specifies datastore query to return the root item for the tree.
22067 // Must only return a single item. Alternately can just pass in pointer
22068 // to root item.
22069 // example:
22070 // | {id:'ROOT'}
22071 query: null,
a089699c 22072
81bea17a
AD
22073 // deferItemLoadingUntilExpand: Boolean
22074 // Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes
22075 // until they are expanded. This allows for lazying loading where only one
22076 // loadItem (and generally one network call, consequently) per expansion
22077 // (rather than one for each child).
22078 // This relies on partial loading of the children items; each children item of a
22079 // fully loaded item should contain the label and info about having children.
22080 deferItemLoadingUntilExpand: false,
a089699c 22081
81bea17a
AD
22082 constructor: function(/* Object */ args){
22083 // summary:
22084 // Passed the arguments listed above (store, etc)
22085 // tags:
22086 // private
a089699c 22087
81bea17a 22088 dojo.mixin(this, args);
a089699c 22089
81bea17a 22090 this.connects = [];
a089699c 22091
81bea17a
AD
22092 var store = this.store;
22093 if(!store.getFeatures()['dojo.data.api.Identity']){
22094 throw new Error("dijit.Tree: store must support dojo.data.Identity");
22095 }
a089699c 22096
81bea17a
AD
22097 // if the store supports Notification, subscribe to the notification events
22098 if(store.getFeatures()['dojo.data.api.Notification']){
22099 this.connects = this.connects.concat([
22100 dojo.connect(store, "onNew", this, "onNewItem"),
22101 dojo.connect(store, "onDelete", this, "onDeleteItem"),
22102 dojo.connect(store, "onSet", this, "onSetItem")
22103 ]);
22104 }
22105 },
a089699c 22106
81bea17a
AD
22107 destroy: function(){
22108 dojo.forEach(this.connects, dojo.disconnect);
22109 // TODO: should cancel any in-progress processing of getRoot(), getChildren()
22110 },
a089699c 22111
81bea17a
AD
22112 // =======================================================================
22113 // Methods for traversing hierarchy
a089699c 22114
81bea17a
AD
22115 getRoot: function(onItem, onError){
22116 // summary:
22117 // Calls onItem with the root item for the tree, possibly a fabricated item.
22118 // Calls onError on error.
22119 if(this.root){
22120 onItem(this.root);
a089699c 22121 }else{
81bea17a
AD
22122 this.store.fetch({
22123 query: this.query,
22124 onComplete: dojo.hitch(this, function(items){
22125 if(items.length != 1){
22126 throw new Error(this.declaredClass + ": query " + dojo.toJson(this.query) + " returned " + items.length +
22127 " items, but must return exactly one item");
22128 }
22129 this.root = items[0];
22130 onItem(this.root);
22131 }),
22132 onError: onError
22133 });
a089699c 22134 }
81bea17a 22135 },
a089699c 22136
81bea17a
AD
22137 mayHaveChildren: function(/*dojo.data.Item*/ item){
22138 // summary:
22139 // Tells if an item has or may have children. Implementing logic here
22140 // avoids showing +/- expando icon for nodes that we know don't have children.
22141 // (For efficiency reasons we may not want to check if an element actually
22142 // has children until user clicks the expando node)
22143 return dojo.some(this.childrenAttrs, function(attr){
22144 return this.store.hasAttribute(item, attr);
22145 }, this);
22146 },
a089699c 22147
81bea17a
AD
22148 getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){
22149 // summary:
22150 // Calls onComplete() with array of child items of given parent item, all loaded.
a089699c 22151
81bea17a
AD
22152 var store = this.store;
22153 if(!store.isItemLoaded(parentItem)){
22154 // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand
22155 // mode, so we will load it and just return the children (without loading each
22156 // child item)
22157 var getChildren = dojo.hitch(this, arguments.callee);
22158 store.loadItem({
22159 item: parentItem,
22160 onItem: function(parentItem){
22161 getChildren(parentItem, onComplete, onError);
22162 },
22163 onError: onError
22164 });
22165 return;
22166 }
22167 // get children of specified item
22168 var childItems = [];
22169 for(var i=0; i<this.childrenAttrs.length; i++){
22170 var vals = store.getValues(parentItem, this.childrenAttrs[i]);
22171 childItems = childItems.concat(vals);
22172 }
a089699c 22173
81bea17a
AD
22174 // count how many items need to be loaded
22175 var _waitCount = 0;
22176 if(!this.deferItemLoadingUntilExpand){
22177 dojo.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } });
22178 }
a089699c 22179
81bea17a
AD
22180 if(_waitCount == 0){
22181 // all items are already loaded (or we aren't loading them). proceed...
22182 onComplete(childItems);
22183 }else{
22184 // still waiting for some or all of the items to load
22185 dojo.forEach(childItems, function(item, idx){
22186 if(!store.isItemLoaded(item)){
22187 store.loadItem({
22188 item: item,
22189 onItem: function(item){
22190 childItems[idx] = item;
22191 if(--_waitCount == 0){
22192 // all nodes have been loaded, send them to the tree
22193 onComplete(childItems);
22194 }
22195 },
22196 onError: onError
22197 });
22198 }
22199 });
22200 }
22201 },
a089699c 22202
81bea17a
AD
22203 // =======================================================================
22204 // Inspecting items
a089699c 22205
81bea17a
AD
22206 isItem: function(/* anything */ something){
22207 return this.store.isItem(something); // Boolean
22208 },
a089699c 22209
81bea17a
AD
22210 fetchItemByIdentity: function(/* object */ keywordArgs){
22211 this.store.fetchItemByIdentity(keywordArgs);
22212 },
a089699c 22213
81bea17a
AD
22214 getIdentity: function(/* item */ item){
22215 return this.store.getIdentity(item); // Object
22216 },
a089699c 22217
81bea17a 22218 getLabel: function(/*dojo.data.Item*/ item){
a089699c 22219 // summary:
81bea17a
AD
22220 // Get the label for an item
22221 if(this.labelAttr){
22222 return this.store.getValue(item,this.labelAttr); // String
22223 }else{
22224 return this.store.getLabel(item); // String
22225 }
22226 },
a089699c 22227
81bea17a
AD
22228 // =======================================================================
22229 // Write interface
a089699c 22230
81bea17a
AD
22231 newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){
22232 // summary:
22233 // Creates a new item. See `dojo.data.api.Write` for details on args.
22234 // Used in drag & drop when item from external source dropped onto tree.
22235 // description:
22236 // Developers will need to override this method if new items get added
22237 // to parents with multiple children attributes, in order to define which
22238 // children attribute points to the new item.
a089699c 22239
81bea17a 22240 var pInfo = {parent: parent, attribute: this.childrenAttrs[0]}, LnewItem;
a089699c 22241
81bea17a
AD
22242 if(this.newItemIdAttr && args[this.newItemIdAttr]){
22243 // Maybe there's already a corresponding item in the store; if so, reuse it.
22244 this.fetchItemByIdentity({identity: args[this.newItemIdAttr], scope: this, onItem: function(item){
22245 if(item){
22246 // There's already a matching item in store, use it
22247 this.pasteItem(item, null, parent, true, insertIndex);
22248 }else{
22249 // Create new item in the tree, based on the drag source.
22250 LnewItem=this.store.newItem(args, pInfo);
22251 if (LnewItem && (insertIndex!=undefined)){
22252 // Move new item to desired position
22253 this.pasteItem(LnewItem, parent, parent, false, insertIndex);
22254 }
22255 }
22256 }});
22257 }else{
22258 // [as far as we know] there is no id so we must assume this is a new item
22259 LnewItem=this.store.newItem(args, pInfo);
22260 if (LnewItem && (insertIndex!=undefined)){
22261 // Move new item to desired position
22262 this.pasteItem(LnewItem, parent, parent, false, insertIndex);
22263 }
22264 }
22265 },
a089699c 22266
81bea17a
AD
22267 pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){
22268 // summary:
22269 // Move or copy an item from one parent item to another.
22270 // Used in drag & drop
22271 var store = this.store,
22272 parentAttr = this.childrenAttrs[0]; // name of "children" attr in parent item
a089699c 22273
81bea17a
AD
22274 // remove child from source item, and record the attribute that child occurred in
22275 if(oldParentItem){
22276 dojo.forEach(this.childrenAttrs, function(attr){
22277 if(store.containsValue(oldParentItem, attr, childItem)){
22278 if(!bCopy){
22279 var values = dojo.filter(store.getValues(oldParentItem, attr), function(x){
22280 return x != childItem;
22281 });
22282 store.setValues(oldParentItem, attr, values);
22283 }
22284 parentAttr = attr;
22285 }
22286 });
22287 }
a089699c 22288
81bea17a
AD
22289 // modify target item's children attribute to include this item
22290 if(newParentItem){
22291 if(typeof insertIndex == "number"){
22292 // call slice() to avoid modifying the original array, confusing the data store
22293 var childItems = store.getValues(newParentItem, parentAttr).slice();
22294 childItems.splice(insertIndex, 0, childItem);
22295 store.setValues(newParentItem, parentAttr, childItems);
22296 }else{
22297 store.setValues(newParentItem, parentAttr,
22298 store.getValues(newParentItem, parentAttr).concat(childItem));
22299 }
22300 }
22301 },
a089699c 22302
81bea17a
AD
22303 // =======================================================================
22304 // Callbacks
a089699c 22305
81bea17a
AD
22306 onChange: function(/*dojo.data.Item*/ item){
22307 // summary:
22308 // Callback whenever an item has changed, so that Tree
22309 // can update the label, icon, etc. Note that changes
22310 // to an item's children or parent(s) will trigger an
22311 // onChildrenChange() so you can ignore those changes here.
22312 // tags:
22313 // callback
22314 },
a089699c 22315
81bea17a
AD
22316 onChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
22317 // summary:
22318 // Callback to do notifications about new, updated, or deleted items.
22319 // tags:
22320 // callback
22321 },
a089699c 22322
81bea17a
AD
22323 onDelete: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
22324 // summary:
22325 // Callback when an item has been deleted.
22326 // description:
22327 // Note that there will also be an onChildrenChange() callback for the parent
22328 // of this item.
22329 // tags:
22330 // callback
22331 },
a089699c 22332
81bea17a
AD
22333 // =======================================================================
22334 // Events from data store
a089699c 22335
81bea17a
AD
22336 onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){
22337 // summary:
22338 // Handler for when new items appear in the store, either from a drop operation
22339 // or some other way. Updates the tree view (if necessary).
22340 // description:
22341 // If the new item is a child of an existing item,
22342 // calls onChildrenChange() with the new list of children
22343 // for that existing item.
22344 //
22345 // tags:
22346 // extension
a089699c 22347
81bea17a
AD
22348 // We only care about the new item if it has a parent that corresponds to a TreeNode
22349 // we are currently displaying
22350 if(!parentInfo){
22351 return;
22352 }
a089699c 22353
81bea17a
AD
22354 // Call onChildrenChange() on parent (ie, existing) item with new list of children
22355 // In the common case, the new list of children is simply parentInfo.newValue or
22356 // [ parentInfo.newValue ], although if items in the store has multiple
22357 // child attributes (see `childrenAttr`), then it's a superset of parentInfo.newValue,
22358 // so call getChildren() to be sure to get right answer.
22359 this.getChildren(parentInfo.item, dojo.hitch(this, function(children){
22360 this.onChildrenChange(parentInfo.item, children);
22361 }));
22362 },
a089699c 22363
81bea17a
AD
22364 onDeleteItem: function(/*Object*/ item){
22365 // summary:
22366 // Handler for delete notifications from underlying store
22367 this.onDelete(item);
22368 },
a089699c 22369
81bea17a
AD
22370 onSetItem: function(/* item */ item,
22371 /* attribute-name-string */ attribute,
22372 /* object | array */ oldValue,
22373 /* object | array */ newValue){
22374 // summary:
22375 // Updates the tree view according to changes in the data store.
22376 // description:
22377 // Handles updates to an item's children by calling onChildrenChange(), and
22378 // other updates to an item by calling onChange().
22379 //
22380 // See `onNewItem` for more details on handling updates to an item's children.
22381 // tags:
22382 // extension
a089699c 22383
81bea17a
AD
22384 if(dojo.indexOf(this.childrenAttrs, attribute) != -1){
22385 // item's children list changed
22386 this.getChildren(item, dojo.hitch(this, function(children){
22387 // See comments in onNewItem() about calling getChildren()
22388 this.onChildrenChange(item, children);
22389 }));
a089699c 22390 }else{
81bea17a
AD
22391 // item's label/icon/etc. changed.
22392 this.onChange(item);
a089699c 22393 }
a089699c
AD
22394 }
22395 });
a089699c
AD
22396
22397}
22398
81bea17a
AD
22399if(!dojo._hasResource["dijit.tree.ForestStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
22400dojo._hasResource["dijit.tree.ForestStoreModel"] = true;
22401dojo.provide("dijit.tree.ForestStoreModel");
a089699c 22402
a089699c 22403
a089699c 22404
81bea17a
AD
22405dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.TreeStoreModel, {
22406 // summary:
22407 // Interface between a dijit.Tree and a dojo.data store that doesn't have a root item,
22408 // a.k.a. a store that has multiple "top level" items.
22409 //
22410 // description
22411 // Use this class to wrap a dojo.data store, making all the items matching the specified query
22412 // appear as children of a fabricated "root item". If no query is specified then all the
22413 // items returned by fetch() on the underlying store become children of the root item.
22414 // This class allows dijit.Tree to assume a single root item, even if the store doesn't have one.
22415 //
22416 // When using this class the developer must override a number of methods according to their app and
22417 // data, including:
22418 // - onNewRootItem
22419 // - onAddToRoot
22420 // - onLeaveRoot
22421 // - onNewItem
22422 // - onSetItem
a089699c 22423
81bea17a 22424 // Parameters to constructor
a089699c 22425
81bea17a
AD
22426 // rootId: String
22427 // ID of fabricated root item
22428 rootId: "$root$",
a089699c 22429
81bea17a
AD
22430 // rootLabel: String
22431 // Label of fabricated root item
22432 rootLabel: "ROOT",
a089699c 22433
81bea17a
AD
22434 // query: String
22435 // Specifies the set of children of the root item.
22436 // example:
22437 // | {type:'continent'}
22438 query: null,
a089699c 22439
81bea17a 22440 // End of parameters to constructor
a089699c 22441
81bea17a
AD
22442 constructor: function(params){
22443 // summary:
22444 // Sets up variables, etc.
22445 // tags:
22446 // private
a089699c 22447
81bea17a
AD
22448 // Make dummy root item
22449 this.root = {
22450 store: this,
22451 root: true,
22452 id: params.rootId,
22453 label: params.rootLabel,
22454 children: params.rootChildren // optional param
22455 };
22456 },
a089699c 22457
81bea17a
AD
22458 // =======================================================================
22459 // Methods for traversing hierarchy
a089699c 22460
81bea17a
AD
22461 mayHaveChildren: function(/*dojo.data.Item*/ item){
22462 // summary:
22463 // Tells if an item has or may have children. Implementing logic here
22464 // avoids showing +/- expando icon for nodes that we know don't have children.
22465 // (For efficiency reasons we may not want to check if an element actually
22466 // has children until user clicks the expando node)
22467 // tags:
22468 // extension
22469 return item === this.root || this.inherited(arguments);
22470 },
a089699c 22471
81bea17a
AD
22472 getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ callback, /*function*/ onError){
22473 // summary:
22474 // Calls onComplete() with array of child items of given parent item, all loaded.
22475 if(parentItem === this.root){
22476 if(this.root.children){
22477 // already loaded, just return
22478 callback(this.root.children);
a089699c
AD
22479 }else{
22480 this.store.fetch({
22481 query: this.query,
22482 onComplete: dojo.hitch(this, function(items){
81bea17a
AD
22483 this.root.children = items;
22484 callback(items);
a089699c
AD
22485 }),
22486 onError: onError
22487 });
22488 }
81bea17a
AD
22489 }else{
22490 this.inherited(arguments);
22491 }
22492 },
a089699c 22493
81bea17a
AD
22494 // =======================================================================
22495 // Inspecting items
a089699c 22496
81bea17a
AD
22497 isItem: function(/* anything */ something){
22498 return (something === this.root) ? true : this.inherited(arguments);
22499 },
a089699c 22500
81bea17a
AD
22501 fetchItemByIdentity: function(/* object */ keywordArgs){
22502 if(keywordArgs.identity == this.root.id){
22503 var scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
22504 if(keywordArgs.onItem){
22505 keywordArgs.onItem.call(scope, this.root);
a089699c 22506 }
81bea17a
AD
22507 }else{
22508 this.inherited(arguments);
22509 }
22510 },
a089699c 22511
81bea17a
AD
22512 getIdentity: function(/* item */ item){
22513 return (item === this.root) ? this.root.id : this.inherited(arguments);
22514 },
a089699c 22515
81bea17a
AD
22516 getLabel: function(/* item */ item){
22517 return (item === this.root) ? this.root.label : this.inherited(arguments);
22518 },
a089699c 22519
81bea17a
AD
22520 // =======================================================================
22521 // Write interface
a089699c 22522
81bea17a
AD
22523 newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){
22524 // summary:
22525 // Creates a new item. See dojo.data.api.Write for details on args.
22526 // Used in drag & drop when item from external source dropped onto tree.
22527 if(parent === this.root){
22528 this.onNewRootItem(args);
22529 return this.store.newItem(args);
22530 }else{
22531 return this.inherited(arguments);
22532 }
22533 },
a089699c 22534
81bea17a
AD
22535 onNewRootItem: function(args){
22536 // summary:
22537 // User can override this method to modify a new element that's being
22538 // added to the root of the tree, for example to add a flag like root=true
22539 },
a089699c 22540
81bea17a
AD
22541 pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){
22542 // summary:
22543 // Move or copy an item from one parent item to another.
22544 // Used in drag & drop
22545 if(oldParentItem === this.root){
22546 if(!bCopy){
22547 // It's onLeaveRoot()'s responsibility to modify the item so it no longer matches
22548 // this.query... thus triggering an onChildrenChange() event to notify the Tree
22549 // that this element is no longer a child of the root node
22550 this.onLeaveRoot(childItem);
a089699c 22551 }
81bea17a
AD
22552 }
22553 dijit.tree.TreeStoreModel.prototype.pasteItem.call(this, childItem,
22554 oldParentItem === this.root ? null : oldParentItem,
22555 newParentItem === this.root ? null : newParentItem,
22556 bCopy,
22557 insertIndex
22558 );
22559 if(newParentItem === this.root){
22560 // It's onAddToRoot()'s responsibility to modify the item so it matches
22561 // this.query... thus triggering an onChildrenChange() event to notify the Tree
22562 // that this element is now a child of the root node
22563 this.onAddToRoot(childItem);
22564 }
22565 },
a089699c 22566
81bea17a
AD
22567 // =======================================================================
22568 // Handling for top level children
a089699c 22569
81bea17a
AD
22570 onAddToRoot: function(/* item */ item){
22571 // summary:
22572 // Called when item added to root of tree; user must override this method
22573 // to modify the item so that it matches the query for top level items
22574 // example:
22575 // | store.setValue(item, "root", true);
22576 // tags:
22577 // extension
22578 console.log(this, ": item ", item, " added to root");
22579 },
a089699c 22580
81bea17a
AD
22581 onLeaveRoot: function(/* item */ item){
22582 // summary:
22583 // Called when item removed from root of tree; user must override this method
22584 // to modify the item so it doesn't match the query for top level items
22585 // example:
22586 // | store.unsetAttribute(item, "root");
22587 // tags:
22588 // extension
22589 console.log(this, ": item ", item, " removed from root");
22590 },
a089699c 22591
81bea17a
AD
22592 // =======================================================================
22593 // Events from data store
a089699c 22594
81bea17a
AD
22595 _requeryTop: function(){
22596 // reruns the query for the children of the root node,
22597 // sending out an onSet notification if those children have changed
22598 var oldChildren = this.root.children || [];
22599 this.store.fetch({
22600 query: this.query,
22601 onComplete: dojo.hitch(this, function(newChildren){
22602 this.root.children = newChildren;
a089699c 22603
81bea17a
AD
22604 // If the list of children or the order of children has changed...
22605 if(oldChildren.length != newChildren.length ||
22606 dojo.some(oldChildren, function(item, idx){ return newChildren[idx] != item;})){
22607 this.onChildrenChange(this.root, newChildren);
22608 }
22609 })
22610 });
22611 },
a089699c 22612
81bea17a
AD
22613 onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){
22614 // summary:
22615 // Handler for when new items appear in the store. Developers should override this
22616 // method to be more efficient based on their app/data.
22617 // description:
22618 // Note that the default implementation requeries the top level items every time
22619 // a new item is created, since any new item could be a top level item (even in
22620 // addition to being a child of another item, since items can have multiple parents).
22621 //
22622 // If developers can detect which items are possible top level items (based on the item and the
22623 // parentInfo parameters), they should override this method to only call _requeryTop() for top
22624 // level items. Often all top level items have parentInfo==null, but
22625 // that will depend on which store you use and what your data is like.
22626 // tags:
22627 // extension
22628 this._requeryTop();
a089699c 22629
81bea17a
AD
22630 this.inherited(arguments);
22631 },
a089699c 22632
81bea17a
AD
22633 onDeleteItem: function(/*Object*/ item){
22634 // summary:
22635 // Handler for delete notifications from underlying store
a089699c 22636
81bea17a
AD
22637 // check if this was a child of root, and if so send notification that root's children
22638 // have changed
22639 if(dojo.indexOf(this.root.children, item) != -1){
22640 this._requeryTop();
22641 }
a089699c 22642
81bea17a
AD
22643 this.inherited(arguments);
22644 },
a089699c 22645
81bea17a
AD
22646 onSetItem: function(/* item */ item,
22647 /* attribute-name-string */ attribute,
22648 /* object | array */ oldValue,
22649 /* object | array */ newValue){
22650 // summary:
22651 // Updates the tree view according to changes to an item in the data store.
22652 // Developers should override this method to be more efficient based on their app/data.
22653 // description:
22654 // Handles updates to an item's children by calling onChildrenChange(), and
22655 // other updates to an item by calling onChange().
22656 //
22657 // Also, any change to any item re-executes the query for the tree's top-level items,
22658 // since this modified item may have started/stopped matching the query for top level items.
22659 //
22660 // If possible, developers should override this function to only call _requeryTop() when
22661 // the change to the item has caused it to stop/start being a top level item in the tree.
22662 // tags:
22663 // extension
a089699c 22664
81bea17a
AD
22665 this._requeryTop();
22666 this.inherited(arguments);
22667 }
a089699c 22668
81bea17a 22669});
a089699c 22670
81bea17a 22671}
a089699c 22672
81bea17a
AD
22673if(!dojo._hasResource["dojo.dnd.Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
22674dojo._hasResource["dojo.dnd.Container"] = true;
22675dojo.provide("dojo.dnd.Container");
a089699c
AD
22676
22677
a089699c 22678
a089699c 22679
81bea17a
AD
22680/*
22681 Container states:
22682 "" - normal state
22683 "Over" - mouse over a container
22684 Container item states:
22685 "" - normal state
22686 "Over" - mouse over a container item
22687*/
a089699c 22688
81bea17a
AD
22689/*=====
22690dojo.declare("dojo.dnd.__ContainerArgs", [], {
22691 creator: function(){
22692 // summary:
22693 // a creator function, which takes a data item, and returns an object like that:
22694 // {node: newNode, data: usedData, type: arrayOfStrings}
22695 },
a089699c 22696
81bea17a
AD
22697 // skipForm: Boolean
22698 // don't start the drag operation, if clicked on form elements
22699 skipForm: false,
a089699c 22700
81bea17a
AD
22701 // dropParent: Node||String
22702 // node or node's id to use as the parent node for dropped items
22703 // (must be underneath the 'node' parameter in the DOM)
22704 dropParent: null,
a089699c 22705
81bea17a
AD
22706 // _skipStartup: Boolean
22707 // skip startup(), which collects children, for deferred initialization
22708 // (this is used in the markup mode)
22709 _skipStartup: false
22710});
a089699c 22711
81bea17a
AD
22712dojo.dnd.Item = function(){
22713 // summary:
22714 // Represents (one of) the source node(s) being dragged.
22715 // Contains (at least) the "type" and "data" attributes.
22716 // type: String[]
22717 // Type(s) of this item, by default this is ["text"]
22718 // data: Object
22719 // Logical representation of the object being dragged.
22720 // If the drag object's type is "text" then data is a String,
22721 // if it's another type then data could be a different Object,
22722 // perhaps a name/value hash.
22723
22724 this.type = type;
22725 this.data = data;
22726}
22727=====*/
a089699c 22728
81bea17a
AD
22729dojo.declare("dojo.dnd.Container", null, {
22730 // summary:
22731 // a Container object, which knows when mouse hovers over it,
22732 // and over which element it hovers
22733
22734 // object attributes (for markup)
22735 skipForm: false,
22736
22737 /*=====
22738 // current: DomNode
22739 // The DOM node the mouse is currently hovered over
22740 current: null,
22741
22742 // map: Hash<String, dojo.dnd.Item>
22743 // Map from an item's id (which is also the DOMNode's id) to
22744 // the dojo.dnd.Item itself.
22745 map: {},
22746 =====*/
22747
22748 constructor: function(node, params){
22749 // summary:
22750 // a constructor of the Container
22751 // node: Node
22752 // node or node's id to build the container on
22753 // params: dojo.dnd.__ContainerArgs
22754 // a dictionary of parameters
22755 this.node = dojo.byId(node);
22756 if(!params){ params = {}; }
22757 this.creator = params.creator || null;
22758 this.skipForm = params.skipForm;
22759 this.parent = params.dropParent && dojo.byId(params.dropParent);
22760
22761 // class-specific variables
22762 this.map = {};
22763 this.current = null;
a089699c 22764
81bea17a
AD
22765 // states
22766 this.containerState = "";
22767 dojo.addClass(this.node, "dojoDndContainer");
22768
22769 // mark up children
22770 if(!(params && params._skipStartup)){
22771 this.startup();
22772 }
a089699c 22773
81bea17a
AD
22774 // set up events
22775 this.events = [
22776 dojo.connect(this.node, "onmouseover", this, "onMouseOver"),
22777 dojo.connect(this.node, "onmouseout", this, "onMouseOut"),
22778 // cancel text selection and text dragging
22779 dojo.connect(this.node, "ondragstart", this, "onSelectStart"),
22780 dojo.connect(this.node, "onselectstart", this, "onSelectStart")
22781 ];
22782 },
22783
22784 // object attributes (for markup)
22785 creator: function(){
a089699c 22786 // summary:
81bea17a 22787 // creator function, dummy at the moment
a089699c 22788 },
81bea17a
AD
22789
22790 // abstract access to the map
22791 getItem: function(/*String*/ key){
a089699c 22792 // summary:
81bea17a
AD
22793 // returns a data item by its key (id)
22794 return this.map[key]; // dojo.dnd.Item
a089699c 22795 },
81bea17a 22796 setItem: function(/*String*/ key, /*dojo.dnd.Item*/ data){
a089699c 22797 // summary:
81bea17a
AD
22798 // associates a data item with its key (id)
22799 this.map[key] = data;
a089699c 22800 },
81bea17a
AD
22801 delItem: function(/*String*/ key){
22802 // summary:
22803 // removes a data item from the map by its key (id)
22804 delete this.map[key];
a089699c 22805 },
81bea17a
AD
22806 forInItems: function(/*Function*/ f, /*Object?*/ o){
22807 // summary:
22808 // iterates over a data map skipping members that
22809 // are present in the empty object (IE and/or 3rd-party libraries).
22810 o = o || dojo.global;
22811 var m = this.map, e = dojo.dnd._empty;
22812 for(var i in m){
22813 if(i in e){ continue; }
22814 f.call(o, m[i], i, this);
a089699c 22815 }
81bea17a 22816 return o; // Object
a089699c 22817 },
81bea17a
AD
22818 clearItems: function(){
22819 // summary:
22820 // removes all data items from the map
22821 this.map = {};
a089699c 22822 },
81bea17a
AD
22823
22824 // methods
22825 getAllNodes: function(){
a089699c 22826 // summary:
81bea17a
AD
22827 // returns a list (an array) of all valid child nodes
22828 return dojo.query("> .dojoDndItem", this.parent); // NodeList
a089699c 22829 },
81bea17a 22830 sync: function(){
a089699c 22831 // summary:
81bea17a
AD
22832 // sync up the node list with the data map
22833 var map = {};
22834 this.getAllNodes().forEach(function(node){
22835 if(node.id){
22836 var item = this.getItem(node.id);
22837 if(item){
22838 map[node.id] = item;
22839 return;
22840 }
22841 }else{
22842 node.id = dojo.dnd.getUniqueId();
22843 }
22844 var type = node.getAttribute("dndType"),
22845 data = node.getAttribute("dndData");
22846 map[node.id] = {
22847 data: data || node.innerHTML,
22848 type: type ? type.split(/\s*,\s*/) : ["text"]
22849 };
22850 }, this);
22851 this.map = map;
22852 return this; // self
a089699c 22853 },
81bea17a 22854 insertNodes: function(data, before, anchor){
a089699c 22855 // summary:
81bea17a
AD
22856 // inserts an array of new nodes before/after an anchor node
22857 // data: Array
22858 // a list of data items, which should be processed by the creator function
22859 // before: Boolean
22860 // insert before the anchor, if true, and after the anchor otherwise
22861 // anchor: Node
22862 // the anchor node to be used as a point of insertion
22863 if(!this.parent.firstChild){
22864 anchor = null;
22865 }else if(before){
22866 if(!anchor){
22867 anchor = this.parent.firstChild;
22868 }
22869 }else{
22870 if(anchor){
22871 anchor = anchor.nextSibling;
a089699c
AD
22872 }
22873 }
81bea17a
AD
22874 if(anchor){
22875 for(var i = 0; i < data.length; ++i){
22876 var t = this._normalizedCreator(data[i]);
22877 this.setItem(t.node.id, {data: t.data, type: t.type});
22878 this.parent.insertBefore(t.node, anchor);
22879 }
22880 }else{
22881 for(var i = 0; i < data.length; ++i){
22882 var t = this._normalizedCreator(data[i]);
22883 this.setItem(t.node.id, {data: t.data, type: t.type});
22884 this.parent.appendChild(t.node);
22885 }
a089699c 22886 }
81bea17a 22887 return this; // self
a089699c 22888 },
81bea17a 22889 destroy: function(){
a089699c 22890 // summary:
81bea17a
AD
22891 // prepares this object to be garbage-collected
22892 dojo.forEach(this.events, dojo.disconnect);
22893 this.clearItems();
22894 this.node = this.parent = this.current = null;
a089699c
AD
22895 },
22896
81bea17a
AD
22897 // markup methods
22898 markupFactory: function(params, node){
22899 params._skipStartup = true;
22900 return new dojo.dnd.Container(node, params);
a089699c 22901 },
81bea17a 22902 startup: function(){
a089699c 22903 // summary:
81bea17a
AD
22904 // collects valid child items and populate the map
22905
22906 // set up the real parent node
22907 if(!this.parent){
22908 // use the standard algorithm, if not assigned
22909 this.parent = this.node;
22910 if(this.parent.tagName.toLowerCase() == "table"){
22911 var c = this.parent.getElementsByTagName("tbody");
22912 if(c && c.length){ this.parent = c[0]; }
22913 }
a089699c 22914 }
81bea17a 22915 this.defaultCreator = dojo.dnd._defaultCreator(this.parent);
a089699c 22916
81bea17a
AD
22917 // process specially marked children
22918 this.sync();
a089699c
AD
22919 },
22920
81bea17a
AD
22921 // mouse events
22922 onMouseOver: function(e){
22923 // summary:
22924 // event processor for onmouseover
22925 // e: Event
22926 // mouse event
22927 var n = e.relatedTarget;
22928 while(n){
22929 if(n == this.node){ break; }
22930 try{
22931 n = n.parentNode;
22932 }catch(x){
22933 n = null;
22934 }
a089699c 22935 }
81bea17a
AD
22936 if(!n){
22937 this._changeState("Container", "Over");
22938 this.onOverEvent();
22939 }
22940 n = this._getChildByEvent(e);
22941 if(this.current == n){ return; }
22942 if(this.current){ this._removeItemClass(this.current, "Over"); }
22943 if(n){ this._addItemClass(n, "Over"); }
22944 this.current = n;
a089699c 22945 },
81bea17a 22946 onMouseOut: function(e){
a089699c 22947 // summary:
81bea17a
AD
22948 // event processor for onmouseout
22949 // e: Event
22950 // mouse event
22951 for(var n = e.relatedTarget; n;){
22952 if(n == this.node){ return; }
22953 try{
22954 n = n.parentNode;
22955 }catch(x){
22956 n = null;
22957 }
22958 }
22959 if(this.current){
22960 this._removeItemClass(this.current, "Over");
22961 this.current = null;
22962 }
22963 this._changeState("Container", "");
22964 this.onOutEvent();
a089699c 22965 },
81bea17a 22966 onSelectStart: function(e){
a089699c 22967 // summary:
81bea17a
AD
22968 // event processor for onselectevent and ondragevent
22969 // e: Event
22970 // mouse event
22971 if(!this.skipForm || !dojo.dnd.isFormElement(e)){
22972 dojo.stopEvent(e);
22973 }
a089699c 22974 },
81bea17a
AD
22975
22976 // utilities
22977 onOverEvent: function(){
a089699c 22978 // summary:
81bea17a 22979 // this function is called once, when mouse is over our container
a089699c 22980 },
81bea17a 22981 onOutEvent: function(){
a089699c 22982 // summary:
81bea17a 22983 // this function is called once, when mouse is out of our container
a089699c 22984 },
81bea17a 22985 _changeState: function(type, newState){
a089699c 22986 // summary:
81bea17a
AD
22987 // changes a named state to new state value
22988 // type: String
22989 // a name of the state to change
22990 // newState: String
22991 // new state
22992 var prefix = "dojoDnd" + type;
22993 var state = type.toLowerCase() + "State";
22994 //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
22995 dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
22996 this[state] = newState;
a089699c 22997 },
81bea17a 22998 _addItemClass: function(node, type){
a089699c 22999 // summary:
81bea17a
AD
23000 // adds a class with prefix "dojoDndItem"
23001 // node: Node
23002 // a node
23003 // type: String
23004 // a variable suffix for a class name
23005 dojo.addClass(node, "dojoDndItem" + type);
a089699c 23006 },
81bea17a 23007 _removeItemClass: function(node, type){
a089699c 23008 // summary:
81bea17a
AD
23009 // removes a class with prefix "dojoDndItem"
23010 // node: Node
23011 // a node
23012 // type: String
23013 // a variable suffix for a class name
23014 dojo.removeClass(node, "dojoDndItem" + type);
a089699c 23015 },
81bea17a 23016 _getChildByEvent: function(e){
a089699c 23017 // summary:
81bea17a
AD
23018 // gets a child, which is under the mouse at the moment, or null
23019 // e: Event
23020 // a mouse event
23021 var node = e.target;
23022 if(node){
23023 for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){
23024 if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; }
23025 }
a089699c 23026 }
81bea17a 23027 return null;
a089699c 23028 },
81bea17a 23029 _normalizedCreator: function(/*dojo.dnd.Item*/ item, /*String*/ hint){
a089699c 23030 // summary:
81bea17a
AD
23031 // adds all necessary data to the output of the user-supplied creator function
23032 var t = (this.creator || this.defaultCreator).call(this, item, hint);
23033 if(!dojo.isArray(t.type)){ t.type = ["text"]; }
23034 if(!t.node.id){ t.node.id = dojo.dnd.getUniqueId(); }
23035 dojo.addClass(t.node, "dojoDndItem");
23036 return t;
23037 }
23038});
a089699c 23039
81bea17a
AD
23040dojo.dnd._createNode = function(tag){
23041 // summary:
23042 // returns a function, which creates an element of given tag
23043 // (SPAN by default) and sets its innerHTML to given text
23044 // tag: String
23045 // a tag name or empty for SPAN
23046 if(!tag){ return dojo.dnd._createSpan; }
23047 return function(text){ // Function
23048 return dojo.create(tag, {innerHTML: text}); // Node
23049 };
23050};
a089699c 23051
81bea17a
AD
23052dojo.dnd._createTrTd = function(text){
23053 // summary:
23054 // creates a TR/TD structure with given text as an innerHTML of TD
23055 // text: String
23056 // a text for TD
23057 var tr = dojo.create("tr");
23058 dojo.create("td", {innerHTML: text}, tr);
23059 return tr; // Node
23060};
a089699c 23061
81bea17a
AD
23062dojo.dnd._createSpan = function(text){
23063 // summary:
23064 // creates a SPAN element with given text as its innerHTML
23065 // text: String
23066 // a text for SPAN
23067 return dojo.create("span", {innerHTML: text}); // Node
23068};
a089699c 23069
81bea17a
AD
23070// dojo.dnd._defaultCreatorNodes: Object
23071// a dictionary that maps container tag names to child tag names
23072dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"};
a089699c 23073
81bea17a
AD
23074dojo.dnd._defaultCreator = function(node){
23075 // summary:
23076 // takes a parent node, and returns an appropriate creator function
23077 // node: Node
23078 // a container node
23079 var tag = node.tagName.toLowerCase();
23080 var c = tag == "tbody" || tag == "thead" ? dojo.dnd._createTrTd :
23081 dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]);
23082 return function(item, hint){ // Function
23083 var isObj = item && dojo.isObject(item), data, type, n;
23084 if(isObj && item.tagName && item.nodeType && item.getAttribute){
23085 // process a DOM node
23086 data = item.getAttribute("dndData") || item.innerHTML;
23087 type = item.getAttribute("dndType");
23088 type = type ? type.split(/\s*,\s*/) : ["text"];
23089 n = item; // this node is going to be moved rather than copied
a089699c 23090 }else{
81bea17a
AD
23091 // process a DnD item object or a string
23092 data = (isObj && item.data) ? item.data : item;
23093 type = (isObj && item.type) ? item.type : ["text"];
23094 n = (hint == "avatar" ? dojo.dnd._createSpan : c)(String(data));
a089699c 23095 }
81bea17a
AD
23096 if(!n.id){
23097 n.id = dojo.dnd.getUniqueId();
23098 }
23099 return {node: n, data: data, type: type};
23100 };
23101};
23102
23103}
a089699c 23104
81bea17a
AD
23105if(!dojo._hasResource["dijit.tree._dndContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
23106dojo._hasResource["dijit.tree._dndContainer"] = true;
23107dojo.provide("dijit.tree._dndContainer");
a089699c 23108
a089699c 23109
a089699c 23110
a089699c 23111
81bea17a 23112dojo.getObject("tree", true, dojo);
a089699c 23113
81bea17a
AD
23114dijit.tree._compareNodes = function(n1, n2){
23115 if(n1 === n2){
23116 return 0;
23117 }
23118
23119 if('sourceIndex' in document.documentElement){ //IE
23120 //TODO: does not yet work if n1 and/or n2 is a text node
23121 return n1.sourceIndex - n2.sourceIndex;
23122 }else if('compareDocumentPosition' in document.documentElement){ //FF, Opera
23123 return n1.compareDocumentPosition(n2) & 2 ? 1: -1;
23124 }else if(document.createRange){ //Webkit
23125 var r1 = doc.createRange();
23126 r1.setStartBefore(n1);
23127
23128 var r2 = doc.createRange();
23129 r2.setStartBefore(n2);
23130
23131 return r1.compareBoundaryPoints(r1.END_TO_END, r2);
23132 }else{
23133 throw Error("dijit.tree._compareNodes don't know how to compare two different nodes in this browser");
23134 }
23135};
a089699c 23136
81bea17a
AD
23137dojo.declare("dijit.tree._dndContainer",
23138 null,
23139 {
a089699c 23140
a089699c 23141 // summary:
81bea17a
AD
23142 // This is a base class for `dijit.tree._dndSelector`, and isn't meant to be used directly.
23143 // It's modeled after `dojo.dnd.Container`.
23144 // tags:
23145 // protected
a089699c 23146
81bea17a
AD
23147 /*=====
23148 // current: DomNode
23149 // The currently hovered TreeNode.rowNode (which is the DOM node
23150 // associated w/a given node in the tree, excluding it's descendants)
23151 current: null,
23152 =====*/
a089699c 23153
81bea17a
AD
23154 constructor: function(tree, params){
23155 // summary:
23156 // A constructor of the Container
23157 // tree: Node
23158 // Node or node's id to build the container on
23159 // params: dijit.tree.__SourceArgs
23160 // A dict of parameters, which gets mixed into the object
23161 // tags:
23162 // private
23163 this.tree = tree;
23164 this.node = tree.domNode; // TODO: rename; it's not a TreeNode but the whole Tree
23165 dojo.mixin(this, params);
a089699c 23166
81bea17a
AD
23167 // class-specific variables
23168 this.map = {};
23169 this.current = null; // current TreeNode's DOM node
a089699c 23170
81bea17a
AD
23171 // states
23172 this.containerState = "";
23173 dojo.addClass(this.node, "dojoDndContainer");
a089699c 23174
81bea17a
AD
23175 // set up events
23176 this.events = [
23177 // container level events
23178 dojo.connect(this.node, "onmouseenter", this, "onOverEvent"),
23179 dojo.connect(this.node, "onmouseleave", this, "onOutEvent"),
a089699c 23180
81bea17a
AD
23181 // switching between TreeNodes
23182 dojo.connect(this.tree, "_onNodeMouseEnter", this, "onMouseOver"),
23183 dojo.connect(this.tree, "_onNodeMouseLeave", this, "onMouseOut"),
a089699c 23184
81bea17a
AD
23185 // cancel text selection and text dragging
23186 dojo.connect(this.node, "ondragstart", dojo, "stopEvent"),
23187 dojo.connect(this.node, "onselectstart", dojo, "stopEvent")
23188 ];
23189 },
a089699c 23190
81bea17a
AD
23191 getItem: function(/*String*/ key){
23192 // summary:
23193 // Returns the dojo.dnd.Item (representing a dragged node) by it's key (id).
23194 // Called by dojo.dnd.Source.checkAcceptance().
23195 // tags:
23196 // protected
a089699c 23197
81bea17a
AD
23198 var widget = this.selection[key],
23199 ret = {
23200 data: widget,
23201 type: ["treeNode"]
23202 };
a089699c 23203
81bea17a
AD
23204 return ret; // dojo.dnd.Item
23205 },
a089699c 23206
81bea17a
AD
23207 destroy: function(){
23208 // summary:
23209 // Prepares this object to be garbage-collected
a089699c 23210
81bea17a
AD
23211 dojo.forEach(this.events, dojo.disconnect);
23212 // this.clearItems();
23213 this.node = this.parent = null;
23214 },
a089699c 23215
81bea17a
AD
23216 // mouse events
23217 onMouseOver: function(/*TreeNode*/ widget, /*Event*/ evt){
23218 // summary:
23219 // Called when mouse is moved over a TreeNode
23220 // tags:
23221 // protected
23222 this.current = widget;
23223 },
a089699c 23224
81bea17a
AD
23225 onMouseOut: function(/*TreeNode*/ widget, /*Event*/ evt){
23226 // summary:
23227 // Called when mouse is moved away from a TreeNode
23228 // tags:
23229 // protected
23230 this.current = null;
23231 },
a089699c 23232
81bea17a
AD
23233 _changeState: function(type, newState){
23234 // summary:
23235 // Changes a named state to new state value
23236 // type: String
23237 // A name of the state to change
23238 // newState: String
23239 // new state
23240 var prefix = "dojoDnd" + type;
23241 var state = type.toLowerCase() + "State";
23242 //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
23243 dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
23244 this[state] = newState;
23245 },
a089699c 23246
81bea17a
AD
23247 _addItemClass: function(node, type){
23248 // summary:
23249 // Adds a class with prefix "dojoDndItem"
23250 // node: Node
23251 // A node
23252 // type: String
23253 // A variable suffix for a class name
23254 dojo.addClass(node, "dojoDndItem" + type);
23255 },
a089699c 23256
81bea17a
AD
23257 _removeItemClass: function(node, type){
23258 // summary:
23259 // Removes a class with prefix "dojoDndItem"
23260 // node: Node
23261 // A node
23262 // type: String
23263 // A variable suffix for a class name
23264 dojo.removeClass(node, "dojoDndItem" + type);
23265 },
a089699c 23266
81bea17a
AD
23267 onOverEvent: function(){
23268 // summary:
23269 // This function is called once, when mouse is over our container
23270 // tags:
23271 // protected
23272 this._changeState("Container", "Over");
23273 },
23274
23275 onOutEvent: function(){
23276 // summary:
23277 // This function is called once, when mouse is out of our container
23278 // tags:
23279 // protected
23280 this._changeState("Container", "");
23281 }
23282});
a089699c 23283
81bea17a 23284}
a089699c 23285
81bea17a
AD
23286if(!dojo._hasResource["dijit.tree._dndSelector"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
23287dojo._hasResource["dijit.tree._dndSelector"] = true;
23288dojo.provide("dijit.tree._dndSelector");
a089699c 23289
a089699c 23290
a089699c 23291
a089699c 23292
81bea17a
AD
23293dojo.declare("dijit.tree._dndSelector",
23294 dijit.tree._dndContainer,
23295 {
23296 // summary:
23297 // This is a base class for `dijit.tree.dndSource` , and isn't meant to be used directly.
23298 // It's based on `dojo.dnd.Selector`.
23299 // tags:
23300 // protected
a089699c 23301
81bea17a
AD
23302 /*=====
23303 // selection: Hash<String, DomNode>
23304 // (id, DomNode) map for every TreeNode that's currently selected.
23305 // The DOMNode is the TreeNode.rowNode.
23306 selection: {},
23307 =====*/
a089699c 23308
81bea17a
AD
23309 constructor: function(tree, params){
23310 // summary:
23311 // Initialization
23312 // tags:
23313 // private
a089699c 23314
81bea17a
AD
23315 this.selection={};
23316 this.anchor = null;
a089699c 23317
81bea17a 23318 dijit.setWaiState(this.tree.domNode, "multiselect", !this.singular);
a089699c 23319
81bea17a
AD
23320 this.events.push(
23321 dojo.connect(this.tree.domNode, "onmousedown", this,"onMouseDown"),
23322 dojo.connect(this.tree.domNode, "onmouseup", this,"onMouseUp"),
23323 dojo.connect(this.tree.domNode, "onmousemove", this,"onMouseMove")
23324 );
23325 },
a089699c 23326
81bea17a
AD
23327 // singular: Boolean
23328 // Allows selection of only one element, if true.
23329 // Tree hasn't been tested in singular=true mode, unclear if it works.
23330 singular: false,
a089699c 23331
81bea17a
AD
23332 // methods
23333 getSelectedTreeNodes: function(){
23334 // summary:
23335 // Returns a list of selected node(s).
23336 // Used by dndSource on the start of a drag.
23337 // tags:
23338 // protected
23339 var nodes=[], sel = this.selection;
23340 for(var i in sel){
23341 nodes.push(sel[i]);
23342 }
23343 return nodes;
23344 },
a089699c 23345
81bea17a
AD
23346 selectNone: function(){
23347 // summary:
23348 // Unselects all items
23349 // tags:
23350 // private
a089699c 23351
81bea17a
AD
23352 this.setSelection([]);
23353 return this; // self
23354 },
a089699c 23355
81bea17a
AD
23356 destroy: function(){
23357 // summary:
23358 // Prepares the object to be garbage-collected
23359 this.inherited(arguments);
23360 this.selection = this.anchor = null;
23361 },
23362 addTreeNode: function(/*dijit._TreeNode*/node, /*Boolean?*/isAnchor){
23363 // summary
23364 // add node to current selection
23365 // node: Node
23366 // node to add
23367 // isAnchor: Boolean
23368 // Whether the node should become anchor.
a089699c 23369
81bea17a
AD
23370 this.setSelection(this.getSelectedTreeNodes().concat( [node] ));
23371 if(isAnchor){ this.anchor = node; }
23372 return node;
23373 },
23374 removeTreeNode: function(/*dijit._TreeNode*/node){
23375 // summary
23376 // remove node from current selection
23377 // node: Node
23378 // node to remove
23379 this.setSelection(this._setDifference(this.getSelectedTreeNodes(), [node]))
23380 return node;
23381 },
23382 isTreeNodeSelected: function(/*dijit._TreeNode*/node){
23383 // summary
23384 // return true if node is currently selected
23385 // node: Node
23386 // the node to check whether it's in the current selection
a089699c 23387
81bea17a
AD
23388 return node.id && !!this.selection[node.id];
23389 },
23390 setSelection: function(/*dijit._treeNode[]*/ newSelection){
23391 // summary
23392 // set the list of selected nodes to be exactly newSelection. All changes to the
23393 // selection should be passed through this function, which ensures that derived
23394 // attributes are kept up to date. Anchor will be deleted if it has been removed
23395 // from the selection, but no new anchor will be added by this function.
23396 // newSelection: Node[]
23397 // list of tree nodes to make selected
23398 var oldSelection = this.getSelectedTreeNodes();
23399 dojo.forEach(this._setDifference(oldSelection, newSelection), dojo.hitch(this, function(node){
23400 node.setSelected(false);
23401 if(this.anchor == node){
23402 delete this.anchor;
23403 }
23404 delete this.selection[node.id];
23405 }));
23406 dojo.forEach(this._setDifference(newSelection, oldSelection), dojo.hitch(this, function(node){
23407 node.setSelected(true);
23408 this.selection[node.id] = node;
23409 }));
23410 this._updateSelectionProperties();
23411 },
23412 _setDifference: function(xs,ys){
23413 // summary
23414 // Returns a copy of xs which lacks any objects
23415 // occurring in ys. Checks for membership by
23416 // modifying and then reading the object, so it will
23417 // not properly handle sets of numbers or strings.
23418
23419 dojo.forEach(ys, function(y){ y.__exclude__ = true; });
23420 var ret = dojo.filter(xs, function(x){ return !x.__exclude__; });
a089699c 23421
81bea17a
AD
23422 // clean up after ourselves.
23423 dojo.forEach(ys, function(y){ delete y['__exclude__'] });
23424 return ret;
23425 },
23426 _updateSelectionProperties: function() {
23427 // summary
23428 // Update the following tree properties from the current selection:
23429 // path[s], selectedItem[s], selectedNode[s]
23430
23431 var selected = this.getSelectedTreeNodes();
23432 var paths = [], nodes = [];
23433 dojo.forEach(selected, function(node) {
23434 nodes.push(node);
23435 paths.push(node.getTreePath());
23436 });
23437 var items = dojo.map(nodes,function(node) { return node.item; });
23438 this.tree._set("paths", paths);
23439 this.tree._set("path", paths[0] || []);
23440 this.tree._set("selectedNodes", nodes);
23441 this.tree._set("selectedNode", nodes[0] || null);
23442 this.tree._set("selectedItems", items);
23443 this.tree._set("selectedItem", items[0] || null);
23444 },
23445 // mouse events
23446 onMouseDown: function(e){
23447 // summary:
23448 // Event processor for onmousedown
23449 // e: Event
23450 // mouse event
23451 // tags:
23452 // protected
a089699c 23453
81bea17a
AD
23454 // ignore click on expando node
23455 if(!this.current || this.tree.isExpandoNode( e.target, this.current)){ return; }
a089699c 23456
81bea17a 23457 if(e.button == dojo.mouseButtons.RIGHT){ return; } // ignore right-click
a089699c 23458
81bea17a 23459 dojo.stopEvent(e);
a089699c 23460
81bea17a
AD
23461 var treeNode = this.current,
23462 copy = dojo.isCopyKey(e), id = treeNode.id;
a089699c 23463
81bea17a
AD
23464 // if shift key is not pressed, and the node is already in the selection,
23465 // delay deselection until onmouseup so in the case of DND, deselection
23466 // will be canceled by onmousemove.
23467 if(!this.singular && !e.shiftKey && this.selection[id]){
23468 this._doDeselect = true;
23469 return;
23470 }else{
23471 this._doDeselect = false;
23472 }
23473 this.userSelect(treeNode, copy, e.shiftKey);
23474 },
a089699c 23475
81bea17a
AD
23476 onMouseUp: function(e){
23477 // summary:
23478 // Event processor for onmouseup
23479 // e: Event
23480 // mouse event
23481 // tags:
23482 // protected
a089699c 23483
81bea17a
AD
23484 // _doDeselect is the flag to indicate that the user wants to either ctrl+click on
23485 // a already selected item (to deselect the item), or click on a not-yet selected item
23486 // (which should remove all current selection, and add the clicked item). This can not
23487 // be done in onMouseDown, because the user may start a drag after mousedown. By moving
23488 // the deselection logic here, the user can drags an already selected item.
23489 if(!this._doDeselect){ return; }
23490 this._doDeselect = false;
23491 this.userSelect(this.current, dojo.isCopyKey( e ), e.shiftKey);
23492 },
23493 onMouseMove: function(e){
23494 // summary
23495 // event processor for onmousemove
23496 // e: Event
23497 // mouse event
23498 this._doDeselect = false;
23499 },
a089699c 23500
81bea17a
AD
23501 userSelect: function(node, multi, range){
23502 // summary:
23503 // Add or remove the given node from selection, responding
23504 // to a user action such as a click or keypress.
23505 // multi: Boolean
23506 // Indicates whether this is meant to be a multi-select action (e.g. ctrl-click)
23507 // range: Boolean
23508 // Indicates whether this is meant to be a ranged action (e.g. shift-click)
23509 // tags:
23510 // protected
a089699c 23511
81bea17a
AD
23512 if(this.singular){
23513 if(this.anchor == node && multi){
23514 this.selectNone();
23515 }else{
23516 this.setSelection([node]);
23517 this.anchor = node;
23518 }
23519 }else{
23520 if(range && this.anchor){
23521 var cr = dijit.tree._compareNodes(this.anchor.rowNode, node.rowNode),
23522 begin, end, anchor = this.anchor;
23523
23524 if(cr < 0){ //current is after anchor
23525 begin = anchor;
23526 end = node;
23527 }else{ //current is before anchor
23528 begin = node;
23529 end = anchor;
23530 }
23531 nodes = [];
23532 //add everything betweeen begin and end inclusively
23533 while(begin != end) {
23534 nodes.push(begin)
23535 begin = this.tree._getNextNode(begin);
23536 }
23537 nodes.push(end)
a089699c 23538
81bea17a
AD
23539 this.setSelection(nodes);
23540 }else{
23541 if( this.selection[ node.id ] && multi ) {
23542 this.removeTreeNode( node );
23543 } else if(multi) {
23544 this.addTreeNode(node, true);
23545 } else {
23546 this.setSelection([node]);
23547 this.anchor = node;
23548 }
a089699c
AD
23549 }
23550 }
81bea17a
AD
23551 },
23552
23553 forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){
23554 // summary:
23555 // Iterates over selected items;
23556 // see `dojo.dnd.Container.forInItems()` for details
23557 o = o || dojo.global;
23558 for(var id in this.selection){
23559 // console.log("selected item id: " + id);
23560 f.call(o, this.getItem(id), id, this);
23561 }
a089699c 23562 }
81bea17a 23563});
a089699c 23564
81bea17a 23565}
a089699c 23566
81bea17a
AD
23567if(!dojo._hasResource["dijit.Tree"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
23568dojo._hasResource["dijit.Tree"] = true;
23569dojo.provide("dijit.Tree");
a089699c 23570
a089699c 23571
a089699c 23572
a089699c 23573
a089699c 23574
a089699c 23575
a089699c 23576
a089699c 23577
a089699c 23578
a089699c 23579
a089699c 23580
a089699c 23581
a089699c 23582
81bea17a
AD
23583dojo.declare(
23584 "dijit._TreeNode",
23585 [dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained, dijit._CssStateMixin],
23586{
23587 // summary:
23588 // Single node within a tree. This class is used internally
23589 // by Tree and should not be accessed directly.
23590 // tags:
23591 // private
a089699c 23592
81bea17a
AD
23593 // item: [const] dojo.data.Item
23594 // the dojo.data entry this tree represents
23595 item: null,
a089699c 23596
81bea17a
AD
23597 // isTreeNode: [protected] Boolean
23598 // Indicates that this is a TreeNode. Used by `dijit.Tree` only,
23599 // should not be accessed directly.
23600 isTreeNode: true,
a089699c 23601
81bea17a
AD
23602 // label: String
23603 // Text of this tree node
23604 label: "",
a089699c 23605
81bea17a
AD
23606 // isExpandable: [private] Boolean
23607 // This node has children, so show the expando node (+ sign)
23608 isExpandable: null,
a089699c 23609
81bea17a
AD
23610 // isExpanded: [readonly] Boolean
23611 // This node is currently expanded (ie, opened)
23612 isExpanded: false,
a089699c 23613
81bea17a
AD
23614 // state: [private] String
23615 // Dynamic loading-related stuff.
23616 // When an empty folder node appears, it is "UNCHECKED" first,
23617 // then after dojo.data query it becomes "LOADING" and, finally "LOADED"
23618 state: "UNCHECKED",
a089699c 23619
81bea17a 23620 templateString: dojo.cache("dijit", "templates/TreeNode.html", "<div class=\"dijitTreeNode\" role=\"presentation\"\n\t><div dojoAttachPoint=\"rowNode\" class=\"dijitTreeRow\" role=\"presentation\" dojoAttachEvent=\"onmouseenter:_onMouseEnter, onmouseleave:_onMouseLeave, onclick:_onClick, ondblclick:_onDblClick\"\n\t\t><img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" role=\"presentation\"\n\t\t/><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" role=\"presentation\"\n\t\t></span\n\t\t><span dojoAttachPoint=\"contentNode\"\n\t\t\tclass=\"dijitTreeContent\" role=\"presentation\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"iconNode\" class=\"dijitIcon dijitTreeIcon\" role=\"presentation\"\n\t\t\t/><span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" role=\"treeitem\" tabindex=\"-1\" aria-selected=\"false\" dojoAttachEvent=\"onfocus:_onLabelFocus\"></span>\n\t\t</span\n\t></div>\n\t<div dojoAttachPoint=\"containerNode\" class=\"dijitTreeContainer\" role=\"presentation\" style=\"display: none;\"></div>\n</div>\n"),
a089699c 23621
81bea17a
AD
23622 baseClass: "dijitTreeNode",
23623
23624 // For hover effect for tree node, and focus effect for label
23625 cssStateNodes: {
23626 rowNode: "dijitTreeRow",
23627 labelNode: "dijitTreeLabel"
a089699c
AD
23628 },
23629
81bea17a
AD
23630 attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, {
23631 label: {node: "labelNode", type: "innerText"},
23632 tooltip: {node: "rowNode", type: "attribute", attribute: "title"}
23633 }),
a089699c 23634
81bea17a
AD
23635 buildRendering: function(){
23636 this.inherited(arguments);
a089699c 23637
81bea17a
AD
23638 // set expand icon for leaf
23639 this._setExpando();
a089699c 23640
81bea17a
AD
23641 // set icon and label class based on item
23642 this._updateItemClasses(this.item);
a089699c 23643
81bea17a
AD
23644 if(this.isExpandable){
23645 dijit.setWaiState(this.labelNode, "expanded", this.isExpanded);
23646 }
a089699c 23647
81bea17a
AD
23648 //aria-selected should be false on all selectable elements.
23649 this.setSelected(false);
a089699c
AD
23650 },
23651
81bea17a 23652 _setIndentAttr: function(indent){
a089699c 23653 // summary:
81bea17a
AD
23654 // Tell this node how many levels it should be indented
23655 // description:
23656 // 0 for top level nodes, 1 for their children, 2 for their
23657 // grandchildren, etc.
a089699c 23658
81bea17a
AD
23659 // Math.max() is to prevent negative padding on hidden root node (when indent == -1)
23660 var pixels = (Math.max(indent, 0) * this.tree._nodePixelIndent) + "px";
23661
23662 dojo.style(this.domNode, "backgroundPosition", pixels + " 0px");
23663 dojo.style(this.rowNode, this.isLeftToRight() ? "paddingLeft" : "paddingRight", pixels);
23664
23665 dojo.forEach(this.getChildren(), function(child){
23666 child.set("indent", indent+1);
23667 });
23668
23669 this._set("indent", indent);
a089699c
AD
23670 },
23671
81bea17a 23672 markProcessing: function(){
a089699c 23673 // summary:
81bea17a 23674 // Visually denote that tree is loading data, etc.
a089699c 23675 // tags:
81bea17a
AD
23676 // private
23677 this.state = "LOADING";
23678 this._setExpando(true);
a089699c
AD
23679 },
23680
81bea17a 23681 unmarkProcessing: function(){
a089699c 23682 // summary:
81bea17a 23683 // Clear markup from markProcessing() call
a089699c 23684 // tags:
81bea17a
AD
23685 // private
23686 this._setExpando(false);
a089699c
AD
23687 },
23688
81bea17a 23689 _updateItemClasses: function(item){
a089699c 23690 // summary:
81bea17a
AD
23691 // Set appropriate CSS classes for icon and label dom node
23692 // (used to allow for item updates to change respective CSS)
a089699c 23693 // tags:
81bea17a
AD
23694 // private
23695 var tree = this.tree, model = tree.model;
23696 if(tree._v10Compat && item === model.root){
23697 // For back-compat with 1.0, need to use null to specify root item (TODO: remove in 2.0)
23698 item = null;
23699 }
23700 this._applyClassAndStyle(item, "icon", "Icon");
23701 this._applyClassAndStyle(item, "label", "Label");
23702 this._applyClassAndStyle(item, "row", "Row");
a089699c
AD
23703 },
23704
81bea17a 23705 _applyClassAndStyle: function(item, lower, upper){
a089699c 23706 // summary:
81bea17a
AD
23707 // Set the appropriate CSS classes and styles for labels, icons and rows.
23708 //
23709 // item:
23710 // The data item.
23711 //
23712 // lower:
23713 // The lower case attribute to use, e.g. 'icon', 'label' or 'row'.
23714 //
23715 // upper:
23716 // The upper case attribute to use, e.g. 'Icon', 'Label' or 'Row'.
23717 //
23718 // tags:
23719 // private
a089699c 23720
81bea17a
AD
23721 var clsName = "_" + lower + "Class";
23722 var nodeName = lower + "Node";
23723 var oldCls = this[clsName];
a089699c 23724
81bea17a
AD
23725 this[clsName] = this.tree["get" + upper + "Class"](item, this.isExpanded);
23726 dojo.replaceClass(this[nodeName], this[clsName] || "", oldCls || "");
23727
23728 dojo.style(this[nodeName], this.tree["get" + upper + "Style"](item, this.isExpanded) || {});
23729 },
a089699c 23730
81bea17a 23731 _updateLayout: function(){
a089699c 23732 // summary:
81bea17a
AD
23733 // Set appropriate CSS classes for this.domNode
23734 // tags:
23735 // private
23736 var parent = this.getParent();
23737 if(!parent || parent.rowNode.style.display == "none"){
23738 /* if we are hiding the root node then make every first level child look like a root node */
23739 dojo.addClass(this.domNode, "dijitTreeIsRoot");
23740 }else{
23741 dojo.toggleClass(this.domNode, "dijitTreeIsLast", !this.getNextSibling());
a089699c
AD
23742 }
23743 },
23744
81bea17a 23745 _setExpando: function(/*Boolean*/ processing){
a089699c 23746 // summary:
81bea17a
AD
23747 // Set the right image for the expando node
23748 // tags:
23749 // private
a089699c 23750
81bea17a
AD
23751 var styles = ["dijitTreeExpandoLoading", "dijitTreeExpandoOpened",
23752 "dijitTreeExpandoClosed", "dijitTreeExpandoLeaf"],
23753 _a11yStates = ["*","-","+","*"],
23754 idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3);
a089699c 23755
81bea17a
AD
23756 // apply the appropriate class to the expando node
23757 dojo.replaceClass(this.expandoNode, styles[idx], styles);
23758
23759 // provide a non-image based indicator for images-off mode
23760 this.expandoNodeText.innerHTML = _a11yStates[idx];
a089699c 23761
a089699c
AD
23762 },
23763
81bea17a 23764 expand: function(){
a089699c 23765 // summary:
81bea17a
AD
23766 // Show my children
23767 // returns:
23768 // Deferred that fires when expansion is complete
a089699c 23769
81bea17a
AD
23770 // If there's already an expand in progress or we are already expanded, just return
23771 if(this._expandDeferred){
23772 return this._expandDeferred; // dojo.Deferred
23773 }
23774
23775 // cancel in progress collapse operation
23776 this._wipeOut && this._wipeOut.stop();
23777
23778 // All the state information for when a node is expanded, maybe this should be
23779 // set when the animation completes instead
23780 this.isExpanded = true;
23781 dijit.setWaiState(this.labelNode, "expanded", "true");
23782 if(this.tree.showRoot || this !== this.tree.rootNode){
23783 dijit.setWaiRole(this.containerNode, "group");
23784 }
23785 dojo.addClass(this.contentNode,'dijitTreeContentExpanded');
23786 this._setExpando();
23787 this._updateItemClasses(this.item);
23788 if(this == this.tree.rootNode){
23789 dijit.setWaiState(this.tree.domNode, "expanded", "true");
a089699c 23790 }
81bea17a
AD
23791
23792 var def,
23793 wipeIn = dojo.fx.wipeIn({
23794 node: this.containerNode, duration: dijit.defaultDuration,
23795 onEnd: function(){
23796 def.callback(true);
23797 }
23798 });
23799
23800 // Deferred that fires when expand is complete
23801 def = (this._expandDeferred = new dojo.Deferred(function(){
23802 // Canceller
23803 wipeIn.stop();
23804 }));
23805
23806 wipeIn.play();
23807
23808 return def; // dojo.Deferred
a089699c
AD
23809 },
23810
81bea17a 23811 collapse: function(){
a089699c 23812 // summary:
81bea17a 23813 // Collapse this node (if it's expanded)
a089699c 23814
81bea17a 23815 if(!this.isExpanded){ return; }
a089699c 23816
81bea17a
AD
23817 // cancel in progress expand operation
23818 if(this._expandDeferred){
23819 this._expandDeferred.cancel();
23820 delete this._expandDeferred;
a089699c 23821 }
a089699c 23822
81bea17a
AD
23823 this.isExpanded = false;
23824 dijit.setWaiState(this.labelNode, "expanded", "false");
23825 if(this == this.tree.rootNode){
23826 dijit.setWaiState(this.tree.domNode, "expanded", "false");
23827 }
23828 dojo.removeClass(this.contentNode,'dijitTreeContentExpanded');
23829 this._setExpando();
23830 this._updateItemClasses(this.item);
23831
23832 if(!this._wipeOut){
23833 this._wipeOut = dojo.fx.wipeOut({
23834 node: this.containerNode, duration: dijit.defaultDuration
23835 });
a089699c 23836 }
81bea17a 23837 this._wipeOut.play();
a089699c
AD
23838 },
23839
81bea17a
AD
23840 // indent: Integer
23841 // Levels from this node to the root node
23842 indent: 0,
23843
23844 setChildItems: function(/* Object[] */ items){
23845 // summary:
23846 // Sets the child items of this node, removing/adding nodes
23847 // from current children to match specified items[] array.
23848 // Also, if this.persist == true, expands any children that were previously
23849 // opened.
23850 // returns:
23851 // Deferred object that fires after all previously opened children
23852 // have been expanded again (or fires instantly if there are no such children).
23853
23854 var tree = this.tree,
23855 model = tree.model,
23856 defs = []; // list of deferreds that need to fire before I am complete
23857
a089699c 23858
81bea17a
AD
23859 // Orphan all my existing children.
23860 // If items contains some of the same items as before then we will reattach them.
23861 // Don't call this.removeChild() because that will collapse the tree etc.
23862 dojo.forEach(this.getChildren(), function(child){
23863 dijit._Container.prototype.removeChild.call(this, child);
23864 }, this);
a089699c 23865
81bea17a 23866 this.state = "LOADED";
a089699c 23867
81bea17a
AD
23868 if(items && items.length > 0){
23869 this.isExpandable = true;
a089699c 23870
81bea17a
AD
23871 // Create _TreeNode widget for each specified tree node, unless one already
23872 // exists and isn't being used (presumably it's from a DnD move and was recently
23873 // released
23874 dojo.forEach(items, function(item){
23875 var id = model.getIdentity(item),
23876 existingNodes = tree._itemNodesMap[id],
23877 node;
23878 if(existingNodes){
23879 for(var i=0;i<existingNodes.length;i++){
23880 if(existingNodes[i] && !existingNodes[i].getParent()){
23881 node = existingNodes[i];
23882 node.set('indent', this.indent+1);
23883 break;
23884 }
23885 }
23886 }
23887 if(!node){
23888 node = this.tree._createTreeNode({
23889 item: item,
23890 tree: tree,
23891 isExpandable: model.mayHaveChildren(item),
23892 label: tree.getLabel(item),
23893 tooltip: tree.getTooltip(item),
23894 dir: tree.dir,
23895 lang: tree.lang,
23896 indent: this.indent + 1
23897 });
23898 if(existingNodes){
23899 existingNodes.push(node);
23900 }else{
23901 tree._itemNodesMap[id] = [node];
23902 }
23903 }
23904 this.addChild(node);
a089699c 23905
81bea17a
AD
23906 // If node was previously opened then open it again now (this may trigger
23907 // more data store accesses, recursively)
23908 if(this.tree.autoExpand || this.tree._state(item)){
23909 defs.push(tree._expandNode(node));
23910 }
23911 }, this);
23912
23913 // note that updateLayout() needs to be called on each child after
23914 // _all_ the children exist
23915 dojo.forEach(this.getChildren(), function(child, idx){
23916 child._updateLayout();
23917 });
a089699c 23918 }else{
81bea17a 23919 this.isExpandable=false;
a089699c
AD
23920 }
23921
81bea17a
AD
23922 if(this._setExpando){
23923 // change expando to/from dot or + icon, as appropriate
23924 this._setExpando(false);
23925 }
a089699c 23926
81bea17a
AD
23927 // Set leaf icon or folder icon, as appropriate
23928 this._updateItemClasses(this.item);
23929
23930 // On initial tree show, make the selected TreeNode as either the root node of the tree,
23931 // or the first child, if the root node is hidden
23932 if(this == tree.rootNode){
23933 var fc = this.tree.showRoot ? this : this.getChildren()[0];
23934 if(fc){
23935 fc.setFocusable(true);
23936 tree.lastFocused = fc;
23937 }else{
23938 // fallback: no nodes in tree so focus on Tree <div> itself
23939 tree.domNode.setAttribute("tabIndex", "0");
a089699c
AD
23940 }
23941 }
81bea17a
AD
23942
23943 return new dojo.DeferredList(defs); // dojo.Deferred
a089699c
AD
23944 },
23945
81bea17a
AD
23946 getTreePath: function(){
23947 var node = this;
23948 var path = [];
23949 while(node && node !== this.tree.rootNode){
23950 path.unshift(node.item);
23951 node = node.getParent();
23952 }
23953 path.unshift(this.tree.rootNode.item);
a089699c 23954
81bea17a
AD
23955 return path;
23956 },
a089699c 23957
81bea17a
AD
23958 getIdentity: function() {
23959 return this.tree.model.getIdentity(this.item);
a089699c 23960 },
a089699c 23961
81bea17a
AD
23962 removeChild: function(/* treeNode */ node){
23963 this.inherited(arguments);
a089699c 23964
81bea17a
AD
23965 var children = this.getChildren();
23966 if(children.length == 0){
23967 this.isExpandable = false;
23968 this.collapse();
a089699c 23969 }
81bea17a
AD
23970
23971 dojo.forEach(children, function(child){
23972 child._updateLayout();
23973 });
a089699c
AD
23974 },
23975
81bea17a 23976 makeExpandable: function(){
a089699c 23977 // summary:
81bea17a
AD
23978 // if this node wasn't already showing the expando node,
23979 // turn it into one and call _setExpando()
a089699c 23980
81bea17a 23981 // TODO: hmm this isn't called from anywhere, maybe should remove it for 2.0
a089699c 23982
81bea17a
AD
23983 this.isExpandable = true;
23984 this._setExpando(false);
a089699c
AD
23985 },
23986
81bea17a 23987 _onLabelFocus: function(evt){
a089699c 23988 // summary:
81bea17a
AD
23989 // Called when this row is focused (possibly programatically)
23990 // Note that we aren't using _onFocus() builtin to dijit
23991 // because it's called when focus is moved to a descendant TreeNode.
a089699c 23992 // tags:
81bea17a
AD
23993 // private
23994 this.tree._onNodeFocus(this);
a089699c 23995 },
81bea17a
AD
23996
23997 setSelected: function(/*Boolean*/ selected){
a089699c 23998 // summary:
81bea17a
AD
23999 // A Tree has a (single) currently selected node.
24000 // Mark that this node is/isn't that currently selected node.
24001 // description:
24002 // In particular, setting a node as selected involves setting tabIndex
24003 // so that when user tabs to the tree, focus will go to that node (only).
24004 dijit.setWaiState(this.labelNode, "selected", selected);
24005 dojo.toggleClass(this.rowNode, "dijitTreeRowSelected", selected);
a089699c
AD
24006 },
24007
81bea17a 24008 setFocusable: function(/*Boolean*/ selected){
a089699c 24009 // summary:
81bea17a
AD
24010 // A Tree has a (single) node that's focusable.
24011 // Mark that this node is/isn't that currently focsuable node.
24012 // description:
24013 // In particular, setting a node as selected involves setting tabIndex
24014 // so that when user tabs to the tree, focus will go to that node (only).
a089699c 24015
81bea17a
AD
24016 this.labelNode.setAttribute("tabIndex", selected ? "0" : "-1");
24017 },
24018
24019 _onClick: function(evt){
24020 // summary:
24021 // Handler for onclick event on a node
24022 // tags:
24023 // private
24024 this.tree._onClick(this, evt);
24025 },
24026 _onDblClick: function(evt){
24027 // summary:
24028 // Handler for ondblclick event on a node
24029 // tags:
24030 // private
24031 this.tree._onDblClick(this, evt);
a089699c
AD
24032 },
24033
81bea17a 24034 _onMouseEnter: function(evt){
a089699c 24035 // summary:
81bea17a
AD
24036 // Handler for onmouseenter event on a node
24037 // tags:
24038 // private
24039 this.tree._onNodeMouseEnter(this, evt);
a089699c
AD
24040 },
24041
81bea17a 24042 _onMouseLeave: function(evt){
a089699c 24043 // summary:
81bea17a
AD
24044 // Handler for onmouseenter event on a node
24045 // tags:
24046 // private
24047 this.tree._onNodeMouseLeave(this, evt);
24048 }
24049});
a089699c 24050
81bea17a
AD
24051dojo.declare(
24052 "dijit.Tree",
24053 [dijit._Widget, dijit._Templated],
24054{
24055 // summary:
24056 // This widget displays hierarchical data from a store.
a089699c 24057
81bea17a
AD
24058 // store: [deprecated] String||dojo.data.Store
24059 // Deprecated. Use "model" parameter instead.
24060 // The store to get data to display in the tree.
24061 store: null,
a089699c 24062
81bea17a
AD
24063 // model: dijit.Tree.model
24064 // Interface to read tree data, get notifications of changes to tree data,
24065 // and for handling drop operations (i.e drag and drop onto the tree)
24066 model: null,
a089699c 24067
81bea17a
AD
24068 // query: [deprecated] anything
24069 // Deprecated. User should specify query to the model directly instead.
24070 // Specifies datastore query to return the root item or top items for the tree.
24071 query: null,
a089699c 24072
81bea17a
AD
24073 // label: [deprecated] String
24074 // Deprecated. Use dijit.tree.ForestStoreModel directly instead.
24075 // Used in conjunction with query parameter.
24076 // If a query is specified (rather than a root node id), and a label is also specified,
24077 // then a fake root node is created and displayed, with this label.
24078 label: "",
a089699c 24079
81bea17a
AD
24080 // showRoot: [const] Boolean
24081 // Should the root node be displayed, or hidden?
24082 showRoot: true,
a089699c 24083
81bea17a
AD
24084 // childrenAttr: [deprecated] String[]
24085 // Deprecated. This information should be specified in the model.
24086 // One ore more attributes that holds children of a tree node
24087 childrenAttr: ["children"],
a089699c 24088
81bea17a
AD
24089 // paths: String[][] or Item[][]
24090 // Full paths from rootNode to selected nodes expressed as array of items or array of ids.
24091 // Since setting the paths may be asynchronous (because ofwaiting on dojo.data), set("paths", ...)
24092 // returns a Deferred to indicate when the set is complete.
24093 paths: [],
24094
24095 // path: String[] or Item[]
24096 // Backward compatible singular variant of paths.
24097 path: [],
a089699c 24098
81bea17a
AD
24099 // selectedItems: [readonly] Item[]
24100 // The currently selected items in this tree.
24101 // This property can only be set (via set('selectedItems', ...)) when that item is already
24102 // visible in the tree. (I.e. the tree has already been expanded to show that node.)
24103 // Should generally use `paths` attribute to set the selected items instead.
24104 selectedItems: null,
a089699c 24105
81bea17a
AD
24106 // selectedItem: [readonly] Item
24107 // Backward compatible singular variant of selectedItems.
24108 selectedItem: null,
a089699c 24109
81bea17a
AD
24110 // openOnClick: Boolean
24111 // If true, clicking a folder node's label will open it, rather than calling onClick()
24112 openOnClick: false,
a089699c 24113
81bea17a
AD
24114 // openOnDblClick: Boolean
24115 // If true, double-clicking a folder node's label will open it, rather than calling onDblClick()
24116 openOnDblClick: false,
a089699c 24117
81bea17a 24118 templateString: dojo.cache("dijit", "templates/Tree.html", "<div class=\"dijitTree dijitTreeContainer\" role=\"tree\"\n\tdojoAttachEvent=\"onkeypress:_onKeyPress\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" dojoAttachPoint=\"indentDetector\"></div>\n</div>\n"),
a089699c 24119
81bea17a
AD
24120 // persist: Boolean
24121 // Enables/disables use of cookies for state saving.
24122 persist: true,
a089699c 24123
81bea17a
AD
24124 // autoExpand: Boolean
24125 // Fully expand the tree on load. Overrides `persist`.
24126 autoExpand: false,
a089699c 24127
81bea17a
AD
24128 // dndController: [protected] String
24129 // Class name to use as as the dnd controller. Specifying this class enables DnD.
24130 // Generally you should specify this as "dijit.tree.dndSource".
24131 // Default of "dijit.tree._dndSelector" handles selection only (no actual DnD).
24132 dndController: "dijit.tree._dndSelector",
a089699c 24133
81bea17a
AD
24134 // parameters to pull off of the tree and pass on to the dndController as its params
24135 dndParams: ["onDndDrop","itemCreator","onDndCancel","checkAcceptance", "checkItemAcceptance", "dragThreshold", "betweenThreshold"],
24136
24137 //declare the above items so they can be pulled from the tree's markup
24138
24139 // onDndDrop: [protected] Function
24140 // Parameter to dndController, see `dijit.tree.dndSource.onDndDrop`.
24141 // Generally this doesn't need to be set.
24142 onDndDrop: null,
24143
24144 /*=====
24145 itemCreator: function(nodes, target, source){
24146 // summary:
24147 // Returns objects passed to `Tree.model.newItem()` based on DnD nodes
24148 // dropped onto the tree. Developer must override this method to enable
24149 // dropping from external sources onto this Tree, unless the Tree.model's items
24150 // happen to look like {id: 123, name: "Apple" } with no other attributes.
24151 // description:
24152 // For each node in nodes[], which came from source, create a hash of name/value
24153 // pairs to be passed to Tree.model.newItem(). Returns array of those hashes.
24154 // nodes: DomNode[]
24155 // The DOMNodes dragged from the source container
24156 // target: DomNode
24157 // The target TreeNode.rowNode
24158 // source: dojo.dnd.Source
24159 // The source container the nodes were dragged from, perhaps another Tree or a plain dojo.dnd.Source
24160 // returns: Object[]
24161 // Array of name/value hashes for each new item to be added to the Tree, like:
24162 // | [
24163 // | { id: 123, label: "apple", foo: "bar" },
24164 // | { id: 456, label: "pear", zaz: "bam" }
24165 // | ]
24166 // tags:
24167 // extension
24168 return [{}];
a089699c 24169 },
81bea17a
AD
24170 =====*/
24171 itemCreator: null,
a089699c 24172
81bea17a
AD
24173 // onDndCancel: [protected] Function
24174 // Parameter to dndController, see `dijit.tree.dndSource.onDndCancel`.
24175 // Generally this doesn't need to be set.
24176 onDndCancel: null,
a089699c 24177
81bea17a
AD
24178/*=====
24179 checkAcceptance: function(source, nodes){
a089699c 24180 // summary:
81bea17a
AD
24181 // Checks if the Tree itself can accept nodes from this source
24182 // source: dijit.tree._dndSource
24183 // The source which provides items
24184 // nodes: DOMNode[]
24185 // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if
24186 // source is a dijit.Tree.
a089699c 24187 // tags:
81bea17a
AD
24188 // extension
24189 return true; // Boolean
24190 },
24191=====*/
24192 checkAcceptance: null,
24193
24194/*=====
24195 checkItemAcceptance: function(target, source, position){
24196 // summary:
24197 // Stub function to be overridden if one wants to check for the ability to drop at the node/item level
24198 // description:
24199 // In the base case, this is called to check if target can become a child of source.
24200 // When betweenThreshold is set, position="before" or "after" means that we
24201 // are asking if the source node can be dropped before/after the target node.
24202 // target: DOMNode
24203 // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to
24204 // Use dijit.getEnclosingWidget(target) to get the TreeNode.
24205 // source: dijit.tree.dndSource
24206 // The (set of) nodes we are dropping
24207 // position: String
24208 // "over", "before", or "after"
24209 // tags:
24210 // extension
24211 return true; // Boolean
24212 },
24213=====*/
24214 checkItemAcceptance: null,
24215
24216 // dragThreshold: Integer
24217 // Number of pixels mouse moves before it's considered the start of a drag operation
24218 dragThreshold: 5,
24219
24220 // betweenThreshold: Integer
24221 // Set to a positive value to allow drag and drop "between" nodes.
24222 //
24223 // If during DnD mouse is over a (target) node but less than betweenThreshold
24224 // pixels from the bottom edge, dropping the the dragged node will make it
24225 // the next sibling of the target node, rather than the child.
24226 //
24227 // Similarly, if mouse is over a target node but less that betweenThreshold
24228 // pixels from the top edge, dropping the dragged node will make it
24229 // the target node's previous sibling rather than the target node's child.
24230 betweenThreshold: 0,
24231
24232 // _nodePixelIndent: Integer
24233 // Number of pixels to indent tree nodes (relative to parent node).
24234 // Default is 19 but can be overridden by setting CSS class dijitTreeIndent
24235 // and calling resize() or startup() on tree after it's in the DOM.
24236 _nodePixelIndent: 19,
a089699c 24237
81bea17a
AD
24238 _publish: function(/*String*/ topicName, /*Object*/ message){
24239 // summary:
24240 // Publish a message for this widget/topic
24241 dojo.publish(this.id, [dojo.mixin({tree: this, event: topicName}, message || {})]);
a089699c
AD
24242 },
24243
81bea17a
AD
24244 postMixInProperties: function(){
24245 this.tree = this;
a089699c 24246
81bea17a
AD
24247 if(this.autoExpand){
24248 // There's little point in saving opened/closed state of nodes for a Tree
24249 // that initially opens all it's nodes.
24250 this.persist = false;
a089699c 24251 }
a089699c 24252
81bea17a 24253 this._itemNodesMap={};
a089699c 24254
81bea17a
AD
24255 if(!this.cookieName){
24256 this.cookieName = this.id + "SaveStateCookie";
a089699c 24257 }
a089699c 24258
81bea17a 24259 this._loadDeferred = new dojo.Deferred();
a089699c 24260
81bea17a 24261 this.inherited(arguments);
a089699c
AD
24262 },
24263
81bea17a
AD
24264 postCreate: function(){
24265 this._initState();
a089699c 24266
81bea17a
AD
24267 // Create glue between store and Tree, if not specified directly by user
24268 if(!this.model){
24269 this._store2model();
a089699c 24270 }
a089699c 24271
81bea17a
AD
24272 // monitor changes to items
24273 this.connect(this.model, "onChange", "_onItemChange");
24274 this.connect(this.model, "onChildrenChange", "_onItemChildrenChange");
24275 this.connect(this.model, "onDelete", "_onItemDelete");
a089699c 24276
81bea17a 24277 this._load();
a089699c 24278
81bea17a 24279 this.inherited(arguments);
a089699c 24280
81bea17a
AD
24281 if(this.dndController){
24282 if(dojo.isString(this.dndController)){
24283 this.dndController = dojo.getObject(this.dndController);
24284 }
24285 var params={};
24286 for(var i=0; i<this.dndParams.length;i++){
24287 if(this[this.dndParams[i]]){
24288 params[this.dndParams[i]] = this[this.dndParams[i]];
a089699c 24289 }
a089699c 24290 }
81bea17a 24291 this.dndController = new this.dndController(this, params);
a089699c
AD
24292 }
24293 },
a089699c 24294
81bea17a
AD
24295 _store2model: function(){
24296 // summary:
24297 // User specified a store&query rather than model, so create model from store/query
24298 this._v10Compat = true;
24299 dojo.deprecated("Tree: from version 2.0, should specify a model object rather than a store/query");
a089699c 24300
81bea17a
AD
24301 var modelParams = {
24302 id: this.id + "_ForestStoreModel",
24303 store: this.store,
24304 query: this.query,
24305 childrenAttrs: this.childrenAttr
24306 };
a089699c 24307
81bea17a
AD
24308 // Only override the model's mayHaveChildren() method if the user has specified an override
24309 if(this.params.mayHaveChildren){
24310 modelParams.mayHaveChildren = dojo.hitch(this, "mayHaveChildren");
a089699c
AD
24311 }
24312
81bea17a
AD
24313 if(this.params.getItemChildren){
24314 modelParams.getChildren = dojo.hitch(this, function(item, onComplete, onError){
24315 this.getItemChildren((this._v10Compat && item === this.model.root) ? null : item, onComplete, onError);
24316 });
a089699c 24317 }
81bea17a
AD
24318 this.model = new dijit.tree.ForestStoreModel(modelParams);
24319
24320 // For backwards compatibility, the visibility of the root node is controlled by
24321 // whether or not the user has specified a label
24322 this.showRoot = Boolean(this.label);
a089699c
AD
24323 },
24324
81bea17a 24325 onLoad: function(){
a089699c 24326 // summary:
81bea17a 24327 // Called when tree finishes loading and expanding.
a089699c 24328 // description:
81bea17a
AD
24329 // If persist == true the loading may encompass many levels of fetches
24330 // from the data store, each asynchronous. Waits for all to finish.
24331 // tags:
24332 // callback
24333 },
a089699c 24334
81bea17a
AD
24335 _load: function(){
24336 // summary:
24337 // Initial load of the tree.
24338 // Load root node (possibly hidden) and it's children.
24339 this.model.getRoot(
24340 dojo.hitch(this, function(item){
24341 var rn = (this.rootNode = this.tree._createTreeNode({
24342 item: item,
24343 tree: this,
24344 isExpandable: true,
24345 label: this.label || this.getLabel(item),
24346 indent: this.showRoot ? 0 : -1
24347 }));
24348 if(!this.showRoot){
24349 rn.rowNode.style.display="none";
24350 // if root is not visible, move tree role to the invisible
24351 // root node's containerNode, see #12135
24352 dijit.setWaiRole(this.domNode, 'presentation');
24353
24354 dijit.setWaiRole(rn.labelNode, 'presentation');
24355 dijit.setWaiRole(rn.containerNode, 'tree');
24356 }
24357 this.domNode.appendChild(rn.domNode);
24358 var identity = this.model.getIdentity(item);
24359 if(this._itemNodesMap[identity]){
24360 this._itemNodesMap[identity].push(rn);
24361 }else{
24362 this._itemNodesMap[identity] = [rn];
24363 }
a089699c 24364
81bea17a 24365 rn._updateLayout(); // sets "dijitTreeIsRoot" CSS classname
a089699c 24366
81bea17a
AD
24367 // load top level children and then fire onLoad() event
24368 this._expandNode(rn).addCallback(dojo.hitch(this, function(){
24369 this._loadDeferred.callback(true);
24370 this.onLoad();
24371 }));
24372 }),
24373 function(err){
24374 console.error(this, ": error loading root: ", err);
24375 }
24376 );
24377 },
a089699c 24378
81bea17a
AD
24379 getNodesByItem: function(/*dojo.data.Item or id*/ item){
24380 // summary:
24381 // Returns all tree nodes that refer to an item
24382 // returns:
24383 // Array of tree nodes that refer to passed item
a089699c 24384
81bea17a
AD
24385 if(!item){ return []; }
24386 var identity = dojo.isString(item) ? item : this.model.getIdentity(item);
24387 // return a copy so widget don't get messed up by changes to returned array
24388 return [].concat(this._itemNodesMap[identity]);
24389 },
a089699c 24390
81bea17a
AD
24391 _setSelectedItemAttr: function(/*dojo.data.Item or id*/ item){
24392 this.set('selectedItems', [item]);
24393 },
a089699c 24394
81bea17a 24395 _setSelectedItemsAttr: function(/*dojo.data.Items or ids*/ items){
a089699c 24396 // summary:
81bea17a
AD
24397 // Select tree nodes related to passed items.
24398 // WARNING: if model use multi-parented items or desired tree node isn't already loaded
24399 // behavior is undefined. Use set('paths', ...) instead.
24400 var tree = this;
24401 this._loadDeferred.addCallback( dojo.hitch(this, function(){
24402 var identities = dojo.map(items, function(item){
24403 return (!item || dojo.isString(item)) ? item : tree.model.getIdentity(item);
24404 });
24405 var nodes = [];
24406 dojo.forEach(identities, function(id){
24407 nodes = nodes.concat(tree._itemNodesMap[id] || []);
24408 });
24409 this.set('selectedNodes', nodes);
24410 }));
a089699c
AD
24411 },
24412
81bea17a
AD
24413 _setPathAttr: function(/*Item[] || String[]*/ path){
24414 // summary:
24415 // Singular variant of _setPathsAttr
24416 if(path.length) {
24417 return this.set("paths", [path]);
24418 } else {
24419 //Empty list is interpreted as "select nothing"
24420 return this.set("paths", []);
24421 }
24422 },
a089699c 24423
81bea17a 24424 _setPathsAttr: function(/*Item[][] || String[][]*/ paths){
a089699c 24425 // summary:
81bea17a
AD
24426 // Select the tree nodes identified by passed paths.
24427 // paths:
24428 // Array of arrays of items or item id's
24429 // returns:
24430 // Deferred to indicate when the set is complete
24431 var tree = this;
a089699c 24432
81bea17a
AD
24433 // We may need to wait for some nodes to expand, so setting
24434 // each path will involve a Deferred. We bring those deferreds
24435 // together witha DeferredList.
24436 return new dojo.DeferredList(dojo.map(paths, function(path){
24437 var d = new dojo.Deferred();
24438
24439 // normalize path to use identity
24440 path = dojo.map(path, function(item){
24441 return dojo.isString(item) ? item : tree.model.getIdentity(item);
24442 });
24443
24444 if(path.length){
24445 // Wait for the tree to load, if it hasn't already.
24446 tree._loadDeferred.addCallback(function(){ selectPath(path, [tree.rootNode], d); });
24447 }else{
24448 d.errback("Empty path");
24449 }
24450 return d;
24451 })).addCallback(setNodes);
24452
24453 function selectPath(path, nodes, def){
24454 // Traverse path; the next path component should be among "nodes".
24455 var nextPath = path.shift();
24456 var nextNode = dojo.filter(nodes, function(node){
24457 return node.getIdentity() == nextPath;
24458 })[0];
24459 if(!!nextNode){
24460 if(path.length){
24461 tree._expandNode(nextNode).addCallback(function(){ selectPath(path, nextNode.getChildren(), def); });
24462 }else{
24463 //Successfully reached the end of this path
24464 def.callback(nextNode);
24465 }
24466 } else {
24467 def.errback("Could not expand path at " + nextPath);
24468 }
24469 }
a089699c 24470
81bea17a
AD
24471 function setNodes(newNodes){
24472 //After all expansion is finished, set the selection to
24473 //the set of nodes successfully found.
24474 tree.set("selectedNodes", dojo.map(
24475 dojo.filter(newNodes,function(x){return x[0];}),
24476 function(x){return x[1];}));
a089699c 24477 }
81bea17a 24478 },
a089699c 24479
81bea17a
AD
24480 _setSelectedNodeAttr: function(node){
24481 this.set('selectedNodes', [node]);
a089699c 24482 },
81bea17a
AD
24483 _setSelectedNodesAttr: function(nodes){
24484 this._loadDeferred.addCallback( dojo.hitch(this, function(){
24485 this.dndController.setSelection(nodes);
24486 }));
a089699c 24487 },
81bea17a
AD
24488
24489
24490 ////////////// Data store related functions //////////////////////
24491 // These just get passed to the model; they are here for back-compat
24492
24493 mayHaveChildren: function(/*dojo.data.Item*/ item){
a089699c 24494 // summary:
81bea17a
AD
24495 // Deprecated. This should be specified on the model itself.
24496 //
24497 // Overridable function to tell if an item has or may have children.
24498 // Controls whether or not +/- expando icon is shown.
24499 // (For efficiency reasons we may not want to check if an element actually
24500 // has children until user clicks the expando node)
24501 // tags:
24502 // deprecated
a089699c 24503 },
81bea17a
AD
24504
24505 getItemChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete){
a089699c 24506 // summary:
81bea17a
AD
24507 // Deprecated. This should be specified on the model itself.
24508 //
24509 // Overridable function that return array of child items of given parent item,
24510 // or if parentItem==null then return top items in tree
24511 // tags:
24512 // deprecated
a089699c 24513 },
81bea17a
AD
24514
24515 ///////////////////////////////////////////////////////
24516 // Functions for converting an item to a TreeNode
24517 getLabel: function(/*dojo.data.Item*/ item){
a089699c 24518 // summary:
81bea17a
AD
24519 // Overridable function to get the label for a tree node (given the item)
24520 // tags:
24521 // extension
24522 return this.model.getLabel(item); // String
a089699c 24523 },
81bea17a
AD
24524
24525 getIconClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
a089699c 24526 // summary:
81bea17a
AD
24527 // Overridable function to return CSS class name to display icon
24528 // tags:
24529 // extension
24530 return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf"
a089699c 24531 },
81bea17a
AD
24532
24533 getLabelClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
a089699c 24534 // summary:
81bea17a
AD
24535 // Overridable function to return CSS class name to display label
24536 // tags:
24537 // extension
a089699c 24538 },
81bea17a
AD
24539
24540 getRowClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
a089699c 24541 // summary:
81bea17a
AD
24542 // Overridable function to return CSS class name to display row
24543 // tags:
24544 // extension
a089699c 24545 },
81bea17a
AD
24546
24547 getIconStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
a089699c 24548 // summary:
81bea17a
AD
24549 // Overridable function to return CSS styles to display icon
24550 // returns:
24551 // Object suitable for input to dojo.style() like {backgroundImage: "url(...)"}
24552 // tags:
24553 // extension
24554 },
24555
24556 getLabelStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
24557 // summary:
24558 // Overridable function to return CSS styles to display label
24559 // returns:
24560 // Object suitable for input to dojo.style() like {color: "red", background: "green"}
24561 // tags:
24562 // extension
a089699c 24563 },
81bea17a
AD
24564
24565 getRowStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){
a089699c 24566 // summary:
81bea17a
AD
24567 // Overridable function to return CSS styles to display row
24568 // returns:
24569 // Object suitable for input to dojo.style() like {background-color: "#bbb"}
24570 // tags:
24571 // extension
24572 },
24573
24574 getTooltip: function(/*dojo.data.Item*/ item){
24575 // summary:
24576 // Overridable function to get the tooltip for a tree node (given the item)
24577 // tags:
24578 // extension
24579 return ""; // String
24580 },
24581
24582 /////////// Keyboard and Mouse handlers ////////////////////
24583
24584 _onKeyPress: function(/*Event*/ e){
24585 // summary:
24586 // Translates keypress events into commands for the controller
24587 if(e.altKey){ return; }
24588 var dk = dojo.keys;
24589 var treeNode = dijit.getEnclosingWidget(e.target);
24590 if(!treeNode){ return; }
24591
24592 var key = e.charOrCode;
24593 if(typeof key == "string" && key != " "){ // handle printables (letter navigation)
24594 // Check for key navigation.
24595 if(!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey){
24596 this._onLetterKeyNav( { node: treeNode, key: key.toLowerCase() } );
24597 dojo.stopEvent(e);
a089699c 24598 }
81bea17a
AD
24599 }else{ // handle non-printables (arrow keys)
24600 // clear record of recent printables (being saved for multi-char letter navigation),
24601 // because "a", down-arrow, "b" shouldn't search for "ab"
24602 if(this._curSearch){
24603 clearTimeout(this._curSearch.timer);
24604 delete this._curSearch;
a089699c 24605 }
81bea17a
AD
24606
24607 var map = this._keyHandlerMap;
24608 if(!map){
24609 // setup table mapping keys to events
24610 map = {};
24611 map[dk.ENTER]="_onEnterKey";
24612 //On WebKit based browsers, the combination ctrl-enter
24613 //does not get passed through. To allow accessible
24614 //multi-select on those browsers, the space key is
24615 //also used for selection.
24616 map[dk.SPACE]= map[" "] = "_onEnterKey";
24617 map[this.isLeftToRight() ? dk.LEFT_ARROW : dk.RIGHT_ARROW]="_onLeftArrow";
24618 map[this.isLeftToRight() ? dk.RIGHT_ARROW : dk.LEFT_ARROW]="_onRightArrow";
24619 map[dk.UP_ARROW]="_onUpArrow";
24620 map[dk.DOWN_ARROW]="_onDownArrow";
24621 map[dk.HOME]="_onHomeKey";
24622 map[dk.END]="_onEndKey";
24623 this._keyHandlerMap = map;
a089699c 24624 }
81bea17a
AD
24625 if(this._keyHandlerMap[key]){
24626 this[this._keyHandlerMap[key]]( { node: treeNode, item: treeNode.item, evt: e } );
24627 dojo.stopEvent(e);
a089699c
AD
24628 }
24629 }
a089699c
AD
24630 },
24631
81bea17a
AD
24632 _onEnterKey: function(/*Object*/ message){
24633 this._publish("execute", { item: message.item, node: message.node } );
24634 this.dndController.userSelect(message.node, dojo.isCopyKey( message.evt ), message.evt.shiftKey);
24635 this.onClick(message.item, message.node, message.evt);
a089699c 24636 },
81bea17a
AD
24637
24638 _onDownArrow: function(/*Object*/ message){
a089699c 24639 // summary:
81bea17a
AD
24640 // down arrow pressed; get next visible node, set focus there
24641 var node = this._getNextNode(message.node);
24642 if(node && node.isTreeNode){
24643 this.focusNode(node);
a089699c 24644 }
a089699c
AD
24645 },
24646
81bea17a 24647 _onUpArrow: function(/*Object*/ message){
a089699c 24648 // summary:
81bea17a
AD
24649 // Up arrow pressed; move to previous visible node
24650
24651 var node = message.node;
24652
24653 // if younger siblings
24654 var previousSibling = node.getPreviousSibling();
24655 if(previousSibling){
24656 node = previousSibling;
24657 // if the previous node is expanded, dive in deep
24658 while(node.isExpandable && node.isExpanded && node.hasChildren()){
24659 // move to the last child
24660 var children = node.getChildren();
24661 node = children[children.length-1];
24662 }
24663 }else{
24664 // if this is the first child, return the parent
24665 // unless the parent is the root of a tree with a hidden root
24666 var parent = node.getParent();
24667 if(!(!this.showRoot && parent === this.rootNode)){
24668 node = parent;
a089699c
AD
24669 }
24670 }
81bea17a
AD
24671
24672 if(node && node.isTreeNode){
24673 this.focusNode(node);
a089699c 24674 }
a089699c 24675 },
81bea17a
AD
24676
24677 _onRightArrow: function(/*Object*/ message){
a089699c 24678 // summary:
81bea17a
AD
24679 // Right arrow pressed; go to child node
24680 var node = message.node;
24681
24682 // if not expanded, expand, else move to 1st child
24683 if(node.isExpandable && !node.isExpanded){
24684 this._expandNode(node);
24685 }else if(node.hasChildren()){
24686 node = node.getChildren()[0];
24687 if(node && node.isTreeNode){
24688 this.focusNode(node);
a089699c
AD
24689 }
24690 }
a089699c 24691 },
81bea17a
AD
24692
24693 _onLeftArrow: function(/*Object*/ message){
a089699c 24694 // summary:
81bea17a
AD
24695 // Left arrow pressed.
24696 // If not collapsed, collapse, else move to parent.
24697
24698 var node = message.node;
24699
24700 if(node.isExpandable && node.isExpanded){
24701 this._collapseNode(node);
24702 }else{
24703 var parent = node.getParent();
24704 if(parent && parent.isTreeNode && !(!this.showRoot && parent === this.rootNode)){
24705 this.focusNode(parent);
24706 }
a089699c
AD
24707 }
24708 },
81bea17a
AD
24709
24710 _onHomeKey: function(){
a089699c 24711 // summary:
81bea17a
AD
24712 // Home key pressed; get first visible node, and set focus there
24713 var node = this._getRootOrFirstNode();
a089699c 24714 if(node){
81bea17a 24715 this.focusNode(node);
a089699c 24716 }
a089699c 24717 },
81bea17a
AD
24718
24719 _onEndKey: function(/*Object*/ message){
a089699c 24720 // summary:
81bea17a 24721 // End key pressed; go to last visible node.
a089699c 24722
81bea17a
AD
24723 var node = this.rootNode;
24724 while(node.isExpanded){
24725 var c = node.getChildren();
24726 node = c[c.length - 1];
24727 }
a089699c 24728
81bea17a
AD
24729 if(node && node.isTreeNode){
24730 this.focusNode(node);
24731 }
24732 },
a089699c 24733
81bea17a
AD
24734 // multiCharSearchDuration: Number
24735 // If multiple characters are typed where each keystroke happens within
24736 // multiCharSearchDuration of the previous keystroke,
24737 // search for nodes matching all the keystrokes.
24738 //
24739 // For example, typing "ab" will search for entries starting with
24740 // "ab" unless the delay between "a" and "b" is greater than multiCharSearchDuration.
24741 multiCharSearchDuration: 250,
a089699c 24742
81bea17a
AD
24743 _onLetterKeyNav: function(message){
24744 // summary:
24745 // Called when user presses a prinatable key; search for node starting with recently typed letters.
24746 // message: Object
24747 // Like { node: TreeNode, key: 'a' } where key is the key the user pressed.
a089699c 24748
81bea17a
AD
24749 // Branch depending on whether this key starts a new search, or modifies an existing search
24750 var cs = this._curSearch;
24751 if(cs){
24752 // We are continuing a search. Ex: user has pressed 'a', and now has pressed
24753 // 'b', so we want to search for nodes starting w/"ab".
24754 cs.pattern = cs.pattern + message.key;
24755 clearTimeout(cs.timer);
a089699c 24756 }else{
81bea17a
AD
24757 // We are starting a new search
24758 cs = this._curSearch = {
24759 pattern: message.key,
24760 startNode: message.node
24761 };
a089699c 24762 }
81bea17a
AD
24763
24764 // set/reset timer to forget recent keystrokes
24765 var self = this;
24766 cs.timer = setTimeout(function(){
24767 delete self._curSearch;
24768 }, this.multiCharSearchDuration);
24769
24770 // Navigate to TreeNode matching keystrokes [entered so far].
24771 var node = cs.startNode;
24772 do{
24773 node = this._getNextNode(node);
24774 //check for last node, jump to first node if necessary
24775 if(!node){
24776 node = this._getRootOrFirstNode();
24777 }
24778 }while(node !== cs.startNode && (node.label.toLowerCase().substr(0, cs.pattern.length) != cs.pattern));
24779 if(node && node.isTreeNode){
24780 // no need to set focus if back where we started
24781 if(node !== cs.startNode){
24782 this.focusNode(node);
24783 }
a089699c 24784 }
81bea17a 24785 },
a089699c 24786
81bea17a
AD
24787 isExpandoNode: function(node, widget){
24788 // summary:
24789 // check whether a dom node is the expandoNode for a particular TreeNode widget
24790 return dojo.isDescendant(node, widget.expandoNode);
24791 },
24792 _onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){
24793 // summary:
24794 // Translates click events into commands for the controller to process
a089699c 24795
81bea17a
AD
24796 var domElement = e.target,
24797 isExpandoClick = this.isExpandoNode(domElement, nodeWidget);
a089699c 24798
81bea17a
AD
24799 if( (this.openOnClick && nodeWidget.isExpandable) || isExpandoClick ){
24800 // expando node was clicked, or label of a folder node was clicked; open it
24801 if(nodeWidget.isExpandable){
24802 this._onExpandoClick({node:nodeWidget});
24803 }
24804 }else{
24805 this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } );
24806 this.onClick(nodeWidget.item, nodeWidget, e);
24807 this.focusNode(nodeWidget);
24808 }
24809 dojo.stopEvent(e);
24810 },
24811 _onDblClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){
24812 // summary:
24813 // Translates double-click events into commands for the controller to process
a089699c 24814
81bea17a
AD
24815 var domElement = e.target,
24816 isExpandoClick = (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText);
a089699c 24817
81bea17a
AD
24818 if( (this.openOnDblClick && nodeWidget.isExpandable) ||isExpandoClick ){
24819 // expando node was clicked, or label of a folder node was clicked; open it
24820 if(nodeWidget.isExpandable){
24821 this._onExpandoClick({node:nodeWidget});
24822 }
24823 }else{
24824 this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } );
24825 this.onDblClick(nodeWidget.item, nodeWidget, e);
24826 this.focusNode(nodeWidget);
24827 }
24828 dojo.stopEvent(e);
24829 },
a089699c 24830
81bea17a 24831 _onExpandoClick: function(/*Object*/ message){
a089699c 24832 // summary:
81bea17a
AD
24833 // User clicked the +/- icon; expand or collapse my children.
24834 var node = message.node;
a089699c 24835
81bea17a
AD
24836 // If we are collapsing, we might be hiding the currently focused node.
24837 // Also, clicking the expando node might have erased focus from the current node.
24838 // For simplicity's sake just focus on the node with the expando.
24839 this.focusNode(node);
a089699c 24840
81bea17a
AD
24841 if(node.isExpanded){
24842 this._collapseNode(node);
24843 }else{
24844 this._expandNode(node);
24845 }
24846 },
a089699c 24847
81bea17a
AD
24848 onClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){
24849 // summary:
24850 // Callback when a tree node is clicked
24851 // tags:
24852 // callback
24853 },
24854 onDblClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){
24855 // summary:
24856 // Callback when a tree node is double-clicked
24857 // tags:
24858 // callback
24859 },
24860 onOpen: function(/* dojo.data */ item, /*TreeNode*/ node){
24861 // summary:
24862 // Callback when a node is opened
24863 // tags:
24864 // callback
24865 },
24866 onClose: function(/* dojo.data */ item, /*TreeNode*/ node){
24867 // summary:
24868 // Callback when a node is closed
24869 // tags:
24870 // callback
24871 },
24872
24873 _getNextNode: function(node){
24874 // summary:
24875 // Get next visible node
24876
24877 if(node.isExpandable && node.isExpanded && node.hasChildren()){
24878 // if this is an expanded node, get the first child
24879 return node.getChildren()[0]; // _TreeNode
24880 }else{
24881 // find a parent node with a sibling
24882 while(node && node.isTreeNode){
24883 var returnNode = node.getNextSibling();
24884 if(returnNode){
24885 return returnNode; // _TreeNode
24886 }
24887 node = node.getParent();
24888 }
24889 return null;
24890 }
24891 },
a089699c 24892
81bea17a
AD
24893 _getRootOrFirstNode: function(){
24894 // summary:
24895 // Get first visible node
24896 return this.showRoot ? this.rootNode : this.rootNode.getChildren()[0];
24897 },
a089699c 24898
81bea17a
AD
24899 _collapseNode: function(/*_TreeNode*/ node){
24900 // summary:
24901 // Called when the user has requested to collapse the node
a089699c 24902
81bea17a
AD
24903 if(node._expandNodeDeferred){
24904 delete node._expandNodeDeferred;
24905 }
a089699c 24906
81bea17a
AD
24907 if(node.isExpandable){
24908 if(node.state == "LOADING"){
24909 // ignore clicks while we are in the process of loading data
24910 return;
24911 }
a089699c 24912
81bea17a
AD
24913 node.collapse();
24914 this.onClose(node.item, node);
a089699c 24915
81bea17a
AD
24916 if(node.item){
24917 this._state(node.item,false);
24918 this._saveState();
24919 }
24920 }
24921 },
a089699c 24922
81bea17a
AD
24923 _expandNode: function(/*_TreeNode*/ node, /*Boolean?*/ recursive){
24924 // summary:
24925 // Called when the user has requested to expand the node
24926 // recursive:
24927 // Internal flag used when _expandNode() calls itself, don't set.
24928 // returns:
24929 // Deferred that fires when the node is loaded and opened and (if persist=true) all it's descendants
24930 // that were previously opened too
a089699c 24931
81bea17a
AD
24932 if(node._expandNodeDeferred && !recursive){
24933 // there's already an expand in progress (or completed), so just return
24934 return node._expandNodeDeferred; // dojo.Deferred
24935 }
a089699c 24936
81bea17a
AD
24937 var model = this.model,
24938 item = node.item,
24939 _this = this;
a089699c 24940
81bea17a
AD
24941 switch(node.state){
24942 case "UNCHECKED":
24943 // need to load all the children, and then expand
24944 node.markProcessing();
a089699c 24945
81bea17a
AD
24946 // Setup deferred to signal when the load and expand are finished.
24947 // Save that deferred in this._expandDeferred as a flag that operation is in progress.
24948 var def = (node._expandNodeDeferred = new dojo.Deferred());
a089699c 24949
81bea17a
AD
24950 // Get the children
24951 model.getChildren(
24952 item,
24953 function(items){
24954 node.unmarkProcessing();
a089699c 24955
81bea17a
AD
24956 // Display the children and also start expanding any children that were previously expanded
24957 // (if this.persist == true). The returned Deferred will fire when those expansions finish.
24958 var scid = node.setChildItems(items);
a089699c 24959
81bea17a
AD
24960 // Call _expandNode() again but this time it will just to do the animation (default branch).
24961 // The returned Deferred will fire when the animation completes.
24962 // TODO: seems like I can avoid recursion and just use a deferred to sequence the events?
24963 var ed = _this._expandNode(node, true);
a089699c 24964
81bea17a
AD
24965 // After the above two tasks (setChildItems() and recursive _expandNode()) finish,
24966 // signal that I am done.
24967 scid.addCallback(function(){
24968 ed.addCallback(function(){
24969 def.callback();
24970 })
24971 });
24972 },
24973 function(err){
24974 console.error(_this, ": error loading root children: ", err);
24975 }
24976 );
24977 break;
a089699c 24978
81bea17a
AD
24979 default: // "LOADED"
24980 // data is already loaded; just expand node
24981 def = (node._expandNodeDeferred = node.expand());
a089699c 24982
81bea17a 24983 this.onOpen(node.item, node);
a089699c 24984
81bea17a
AD
24985 if(item){
24986 this._state(item, true);
24987 this._saveState();
24988 }
24989 }
a089699c 24990
81bea17a
AD
24991 return def; // dojo.Deferred
24992 },
a089699c 24993
81bea17a 24994 ////////////////// Miscellaneous functions ////////////////
a089699c 24995
81bea17a 24996 focusNode: function(/* _tree.Node */ node){
a089699c 24997 // summary:
81bea17a 24998 // Focus on the specified node (which must be visible)
a089699c
AD
24999 // tags:
25000 // protected
25001
81bea17a
AD
25002 // set focus so that the label will be voiced using screen readers
25003 dijit.focus(node.labelNode);
25004 },
a089699c 25005
81bea17a
AD
25006 _onNodeFocus: function(/*dijit._Widget*/ node){
25007 // summary:
25008 // Called when a TreeNode gets focus, either by user clicking
25009 // it, or programatically by arrow key handling code.
25010 // description:
25011 // It marks that the current node is the selected one, and the previously
25012 // selected node no longer is.
a089699c 25013
81bea17a
AD
25014 if(node && node != this.lastFocused){
25015 if(this.lastFocused && !this.lastFocused._destroyed){
25016 // mark that the previously focsable node is no longer focusable
25017 this.lastFocused.setFocusable(false);
25018 }
a089699c 25019
81bea17a
AD
25020 // mark that the new node is the currently selected one
25021 node.setFocusable(true);
25022 this.lastFocused = node;
25023 }
25024 },
a089699c 25025
81bea17a
AD
25026 _onNodeMouseEnter: function(/*dijit._Widget*/ node){
25027 // summary:
25028 // Called when mouse is over a node (onmouseenter event),
25029 // this is monitored by the DND code
25030 },
a089699c 25031
81bea17a
AD
25032 _onNodeMouseLeave: function(/*dijit._Widget*/ node){
25033 // summary:
25034 // Called when mouse leaves a node (onmouseleave event),
25035 // this is monitored by the DND code
25036 },
a089699c 25037
81bea17a 25038 //////////////// Events from the model //////////////////////////
a089699c 25039
81bea17a
AD
25040 _onItemChange: function(/*Item*/ item){
25041 // summary:
25042 // Processes notification of a change to an item's scalar values like label
25043 var model = this.model,
25044 identity = model.getIdentity(item),
25045 nodes = this._itemNodesMap[identity];
a089699c 25046
81bea17a
AD
25047 if(nodes){
25048 var label = this.getLabel(item),
25049 tooltip = this.getTooltip(item);
25050 dojo.forEach(nodes, function(node){
25051 node.set({
25052 item: item, // theoretically could be new JS Object representing same item
25053 label: label,
25054 tooltip: tooltip
25055 });
25056 node._updateItemClasses(item);
25057 });
25058 }
25059 },
a089699c 25060
81bea17a
AD
25061 _onItemChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
25062 // summary:
25063 // Processes notification of a change to an item's children
25064 var model = this.model,
25065 identity = model.getIdentity(parent),
25066 parentNodes = this._itemNodesMap[identity];
a089699c 25067
81bea17a
AD
25068 if(parentNodes){
25069 dojo.forEach(parentNodes,function(parentNode){
25070 parentNode.setChildItems(newChildrenList);
25071 });
25072 }
25073 },
a089699c 25074
81bea17a
AD
25075 _onItemDelete: function(/*Item*/ item){
25076 // summary:
25077 // Processes notification of a deletion of an item
25078 var model = this.model,
25079 identity = model.getIdentity(item),
25080 nodes = this._itemNodesMap[identity];
a089699c 25081
81bea17a
AD
25082 if(nodes){
25083 dojo.forEach(nodes,function(node){
25084 // Remove node from set of selected nodes (if it's selected)
25085 this.dndController.removeTreeNode(node);
a089699c 25086
81bea17a
AD
25087 var parent = node.getParent();
25088 if(parent){
25089 // if node has not already been orphaned from a _onSetItem(parent, "children", ..) call...
25090 parent.removeChild(node);
a089699c 25091 }
81bea17a
AD
25092 node.destroyRecursive();
25093 }, this);
25094 delete this._itemNodesMap[identity];
25095 }
25096 },
a089699c 25097
81bea17a 25098 /////////////// Miscellaneous funcs
a089699c 25099
81bea17a
AD
25100 _initState: function(){
25101 // summary:
25102 // Load in which nodes should be opened automatically
25103 if(this.persist){
25104 var cookie = dojo.cookie(this.cookieName);
25105 this._openedItemIds = {};
25106 if(cookie){
25107 dojo.forEach(cookie.split(','), function(item){
25108 this._openedItemIds[item] = true;
25109 }, this);
25110 }
25111 }
25112 },
25113 _state: function(item,expanded){
25114 // summary:
25115 // Query or set expanded state for an item,
25116 if(!this.persist){
25117 return false;
25118 }
25119 var id=this.model.getIdentity(item);
25120 if(arguments.length === 1){
25121 return this._openedItemIds[id];
25122 }
25123 if(expanded){
25124 this._openedItemIds[id] = true;
25125 }else{
25126 delete this._openedItemIds[id];
25127 }
25128 },
25129 _saveState: function(){
25130 // summary:
25131 // Create and save a cookie with the currently expanded nodes identifiers
25132 if(!this.persist){
25133 return;
25134 }
25135 var ary = [];
25136 for(var id in this._openedItemIds){
25137 ary.push(id);
25138 }
25139 dojo.cookie(this.cookieName, ary.join(","), {expires:365});
25140 },
a089699c 25141
81bea17a
AD
25142 destroy: function(){
25143 if(this._curSearch){
25144 clearTimeout(this._curSearch.timer);
25145 delete this._curSearch;
25146 }
25147 if(this.rootNode){
25148 this.rootNode.destroyRecursive();
25149 }
25150 if(this.dndController && !dojo.isString(this.dndController)){
25151 this.dndController.destroy();
25152 }
25153 this.rootNode = null;
25154 this.inherited(arguments);
25155 },
a089699c 25156
81bea17a
AD
25157 destroyRecursive: function(){
25158 // A tree is treated as a leaf, not as a node with children (like a grid),
25159 // but defining destroyRecursive for back-compat.
25160 this.destroy();
25161 },
a089699c 25162
81bea17a
AD
25163 resize: function(changeSize){
25164 if(changeSize){
25165 dojo.marginBox(this.domNode, changeSize);
25166 }
a089699c 25167
81bea17a
AD
25168 // The only JS sizing involved w/tree is the indentation, which is specified
25169 // in CSS and read in through this dummy indentDetector node (tree must be
25170 // visible and attached to the DOM to read this)
25171 this._nodePixelIndent = dojo._getMarginSize(this.tree.indentDetector).w;
a089699c 25172
81bea17a
AD
25173 if(this.tree.rootNode){
25174 // If tree has already loaded, then reset indent for all the nodes
25175 this.tree.rootNode.set('indent', this.showRoot ? 0 : -1);
a089699c 25176 }
81bea17a
AD
25177 },
25178
25179 _createTreeNode: function(/*Object*/ args){
25180 // summary:
25181 // creates a TreeNode
25182 // description:
25183 // Developers can override this method to define their own TreeNode class;
25184 // However it will probably be removed in a future release in favor of a way
25185 // of just specifying a widget for the label, rather than one that contains
25186 // the children too.
25187 return new dijit._TreeNode(args);
25188 }
a089699c
AD
25189});
25190
81bea17a
AD
25191// For back-compat. TODO: remove in 2.0
25192
a089699c
AD
25193}
25194
25195if(!dojo._hasResource["dojo.dnd.Avatar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
25196dojo._hasResource["dojo.dnd.Avatar"] = true;
25197dojo.provide("dojo.dnd.Avatar");
25198
25199
25200
25201dojo.declare("dojo.dnd.Avatar", null, {
25202 // summary:
25203 // Object that represents transferred DnD items visually
25204 // manager: Object
25205 // a DnD manager object
25206
25207 constructor: function(manager){
25208 this.manager = manager;
25209 this.construct();
25210 },
25211
25212 // methods
25213 construct: function(){
25214 // summary:
25215 // constructor function;
25216 // it is separate so it can be (dynamically) overwritten in case of need
25217 this.isA11y = dojo.hasClass(dojo.body(),"dijit_a11y");
25218 var a = dojo.create("table", {
25219 "class": "dojoDndAvatar",
25220 style: {
25221 position: "absolute",
25222 zIndex: "1999",
25223 margin: "0px"
25224 }
25225 }),
25226 source = this.manager.source, node,
25227 b = dojo.create("tbody", null, a),
25228 tr = dojo.create("tr", null, b),
25229 td = dojo.create("td", null, tr),
25230 icon = this.isA11y ? dojo.create("span", {
25231 id : "a11yIcon",
25232 innerHTML : this.manager.copy ? '+' : "<"
25233 }, td) : null,
25234 span = dojo.create("span", {
25235 innerHTML: source.generateText ? this._generateText() : ""
25236 }, td),
25237 k = Math.min(5, this.manager.nodes.length), i = 0;
25238 // we have to set the opacity on IE only after the node is live
25239 dojo.attr(tr, {
25240 "class": "dojoDndAvatarHeader",
25241 style: {opacity: 0.9}
25242 });
25243 for(; i < k; ++i){
25244 if(source.creator){
25245 // create an avatar representation of the node
25246 node = source._normalizedCreator(source.getItem(this.manager.nodes[i].id).data, "avatar").node;
25247 }else{
25248 // or just clone the node and hope it works
25249 node = this.manager.nodes[i].cloneNode(true);
25250 if(node.tagName.toLowerCase() == "tr"){
25251 // insert extra table nodes
25252 var table = dojo.create("table"),
25253 tbody = dojo.create("tbody", null, table);
25254 tbody.appendChild(node);
25255 node = table;
25256 }
25257 }
25258 node.id = "";
25259 tr = dojo.create("tr", null, b);
25260 td = dojo.create("td", null, tr);
25261 td.appendChild(node);
25262 dojo.attr(tr, {
25263 "class": "dojoDndAvatarItem",
25264 style: {opacity: (9 - i) / 10}
25265 });
25266 }
25267 this.node = a;
25268 },
25269 destroy: function(){
25270 // summary:
25271 // destructor for the avatar; called to remove all references so it can be garbage-collected
25272 dojo.destroy(this.node);
25273 this.node = false;
25274 },
25275 update: function(){
25276 // summary:
25277 // updates the avatar to reflect the current DnD state
25278 dojo[(this.manager.canDropFlag ? "add" : "remove") + "Class"](this.node, "dojoDndAvatarCanDrop");
25279 if (this.isA11y){
25280 var icon = dojo.byId("a11yIcon");
25281 var text = '+'; // assume canDrop && copy
25282 if (this.manager.canDropFlag && !this.manager.copy) {
81bea17a 25283 text = '< '; // canDrop && move
a089699c
AD
25284 }else if (!this.manager.canDropFlag && !this.manager.copy) {
25285 text = "o"; //!canDrop && move
25286 }else if(!this.manager.canDropFlag){
25287 text = 'x'; // !canDrop && copy
25288 }
25289 icon.innerHTML=text;
25290 }
25291 // replace text
25292 dojo.query(("tr.dojoDndAvatarHeader td span" +(this.isA11y ? " span" : "")), this.node).forEach(
25293 function(node){
25294 node.innerHTML = this._generateText();
25295 }, this);
25296 },
25297 _generateText: function(){
25298 // summary: generates a proper text to reflect copying or moving of items
25299 return this.manager.nodes.length.toString();
25300 }
25301});
25302
25303}
25304
25305if(!dojo._hasResource["dojo.dnd.Manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
25306dojo._hasResource["dojo.dnd.Manager"] = true;
25307dojo.provide("dojo.dnd.Manager");
25308
25309
25310
25311
25312
25313dojo.declare("dojo.dnd.Manager", null, {
25314 // summary:
25315 // the manager of DnD operations (usually a singleton)
25316 constructor: function(){
25317 this.avatar = null;
25318 this.source = null;
25319 this.nodes = [];
25320 this.copy = true;
25321 this.target = null;
25322 this.canDropFlag = false;
25323 this.events = [];
25324 },
25325
25326 // avatar's offset from the mouse
25327 OFFSET_X: 16,
25328 OFFSET_Y: 16,
25329
25330 // methods
25331 overSource: function(source){
25332 // summary:
25333 // called when a source detected a mouse-over condition
25334 // source: Object
25335 // the reporter
25336 if(this.avatar){
25337 this.target = (source && source.targetState != "Disabled") ? source : null;
25338 this.canDropFlag = Boolean(this.target);
25339 this.avatar.update();
25340 }
25341 dojo.publish("/dnd/source/over", [source]);
25342 },
25343 outSource: function(source){
25344 // summary:
25345 // called when a source detected a mouse-out condition
25346 // source: Object
25347 // the reporter
25348 if(this.avatar){
25349 if(this.target == source){
25350 this.target = null;
25351 this.canDropFlag = false;
25352 this.avatar.update();
25353 dojo.publish("/dnd/source/over", [null]);
25354 }
25355 }else{
25356 dojo.publish("/dnd/source/over", [null]);
25357 }
25358 },
25359 startDrag: function(source, nodes, copy){
25360 // summary:
25361 // called to initiate the DnD operation
25362 // source: Object
25363 // the source which provides items
25364 // nodes: Array
25365 // the list of transferred items
25366 // copy: Boolean
25367 // copy items, if true, move items otherwise
25368 this.source = source;
25369 this.nodes = nodes;
25370 this.copy = Boolean(copy); // normalizing to true boolean
25371 this.avatar = this.makeAvatar();
25372 dojo.body().appendChild(this.avatar.node);
25373 dojo.publish("/dnd/start", [source, nodes, this.copy]);
25374 this.events = [
25375 dojo.connect(dojo.doc, "onmousemove", this, "onMouseMove"),
25376 dojo.connect(dojo.doc, "onmouseup", this, "onMouseUp"),
25377 dojo.connect(dojo.doc, "onkeydown", this, "onKeyDown"),
25378 dojo.connect(dojo.doc, "onkeyup", this, "onKeyUp"),
25379 // cancel text selection and text dragging
25380 dojo.connect(dojo.doc, "ondragstart", dojo.stopEvent),
25381 dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent)
25382 ];
25383 var c = "dojoDnd" + (copy ? "Copy" : "Move");
81bea17a 25384 dojo.addClass(dojo.body(), c);
a089699c
AD
25385 },
25386 canDrop: function(flag){
25387 // summary:
25388 // called to notify if the current target can accept items
25389 var canDropFlag = Boolean(this.target && flag);
25390 if(this.canDropFlag != canDropFlag){
25391 this.canDropFlag = canDropFlag;
25392 this.avatar.update();
25393 }
25394 },
25395 stopDrag: function(){
25396 // summary:
25397 // stop the DnD in progress
81bea17a 25398 dojo.removeClass(dojo.body(), ["dojoDndCopy", "dojoDndMove"]);
a089699c
AD
25399 dojo.forEach(this.events, dojo.disconnect);
25400 this.events = [];
25401 this.avatar.destroy();
25402 this.avatar = null;
25403 this.source = this.target = null;
25404 this.nodes = [];
25405 },
25406 makeAvatar: function(){
25407 // summary:
25408 // makes the avatar; it is separate to be overwritten dynamically, if needed
25409 return new dojo.dnd.Avatar(this);
25410 },
25411 updateAvatar: function(){
25412 // summary:
25413 // updates the avatar; it is separate to be overwritten dynamically, if needed
25414 this.avatar.update();
25415 },
25416
25417 // mouse event processors
25418 onMouseMove: function(e){
25419 // summary:
25420 // event processor for onmousemove
25421 // e: Event
25422 // mouse event
25423 var a = this.avatar;
25424 if(a){
25425 dojo.dnd.autoScrollNodes(e);
25426 //dojo.dnd.autoScroll(e);
25427 var s = a.node.style;
25428 s.left = (e.pageX + this.OFFSET_X) + "px";
25429 s.top = (e.pageY + this.OFFSET_Y) + "px";
25430 var copy = Boolean(this.source.copyState(dojo.isCopyKey(e)));
81bea17a 25431 if(this.copy != copy){
a089699c
AD
25432 this._setCopyStatus(copy);
25433 }
25434 }
25435 },
25436 onMouseUp: function(e){
25437 // summary:
25438 // event processor for onmouseup
25439 // e: Event
25440 // mouse event
25441 if(this.avatar){
25442 if(this.target && this.canDropFlag){
25443 var copy = Boolean(this.source.copyState(dojo.isCopyKey(e))),
25444 params = [this.source, this.nodes, copy, this.target, e];
25445 dojo.publish("/dnd/drop/before", params);
25446 dojo.publish("/dnd/drop", params);
25447 }else{
25448 dojo.publish("/dnd/cancel");
25449 }
25450 this.stopDrag();
25451 }
25452 },
25453
25454 // keyboard event processors
25455 onKeyDown: function(e){
25456 // summary:
25457 // event processor for onkeydown:
25458 // watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag
25459 // e: Event
25460 // keyboard event
25461 if(this.avatar){
25462 switch(e.keyCode){
25463 case dojo.keys.CTRL:
25464 var copy = Boolean(this.source.copyState(true));
81bea17a 25465 if(this.copy != copy){
a089699c
AD
25466 this._setCopyStatus(copy);
25467 }
25468 break;
25469 case dojo.keys.ESCAPE:
25470 dojo.publish("/dnd/cancel");
25471 this.stopDrag();
25472 break;
25473 }
25474 }
25475 },
25476 onKeyUp: function(e){
25477 // summary:
25478 // event processor for onkeyup, watching for CTRL for copy/move status
25479 // e: Event
25480 // keyboard event
25481 if(this.avatar && e.keyCode == dojo.keys.CTRL){
25482 var copy = Boolean(this.source.copyState(false));
81bea17a 25483 if(this.copy != copy){
a089699c
AD
25484 this._setCopyStatus(copy);
25485 }
25486 }
25487 },
25488
25489 // utilities
25490 _setCopyStatus: function(copy){
25491 // summary:
25492 // changes the copy status
25493 // copy: Boolean
25494 // the copy status
25495 this.copy = copy;
25496 this.source._markDndStatus(this.copy);
25497 this.updateAvatar();
81bea17a
AD
25498 dojo.replaceClass(dojo.body(),
25499 "dojoDnd" + (this.copy ? "Copy" : "Move"),
25500 "dojoDnd" + (this.copy ? "Move" : "Copy"));
a089699c
AD
25501 }
25502});
25503
25504// dojo.dnd._manager:
25505// The manager singleton variable. Can be overwritten if needed.
25506dojo.dnd._manager = null;
25507
25508dojo.dnd.manager = function(){
25509 // summary:
25510 // Returns the current DnD manager. Creates one if it is not created yet.
25511 if(!dojo.dnd._manager){
25512 dojo.dnd._manager = new dojo.dnd.Manager();
25513 }
25514 return dojo.dnd._manager; // Object
25515};
25516
25517}
25518
25519if(!dojo._hasResource["dijit.tree.dndSource"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
25520dojo._hasResource["dijit.tree.dndSource"] = true;
25521dojo.provide("dijit.tree.dndSource");
25522
25523
25524
25525
25526/*=====
25527dijit.tree.__SourceArgs = function(){
25528 // summary:
25529 // A dict of parameters for Tree source configuration.
25530 // isSource: Boolean?
25531 // Can be used as a DnD source. Defaults to true.
25532 // accept: String[]
25533 // List of accepted types (text strings) for a target; defaults to
25534 // ["text", "treeNode"]
25535 // copyOnly: Boolean?
25536 // Copy items, if true, use a state of Ctrl key otherwise,
25537 // dragThreshold: Number
25538 // The move delay in pixels before detecting a drag; 0 by default
25539 // betweenThreshold: Integer
25540 // Distance from upper/lower edge of node to allow drop to reorder nodes
25541 this.isSource = isSource;
25542 this.accept = accept;
25543 this.autoSync = autoSync;
25544 this.copyOnly = copyOnly;
25545 this.dragThreshold = dragThreshold;
25546 this.betweenThreshold = betweenThreshold;
25547}
25548=====*/
25549
25550dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, {
25551 // summary:
25552 // Handles drag and drop operations (as a source or a target) for `dijit.Tree`
25553
25554 // isSource: [private] Boolean
25555 // Can be used as a DnD source.
25556 isSource: true,
25557
25558 // accept: String[]
25559 // List of accepted types (text strings) for the Tree; defaults to
25560 // ["text"]
25561 accept: ["text", "treeNode"],
25562
25563 // copyOnly: [private] Boolean
25564 // Copy items, if true, use a state of Ctrl key otherwise
25565 copyOnly: false,
25566
25567 // dragThreshold: Number
25568 // The move delay in pixels before detecting a drag; 5 by default
25569 dragThreshold: 5,
25570
25571 // betweenThreshold: Integer
25572 // Distance from upper/lower edge of node to allow drop to reorder nodes
25573 betweenThreshold: 0,
25574
25575 constructor: function(/*dijit.Tree*/ tree, /*dijit.tree.__SourceArgs*/ params){
25576 // summary:
25577 // a constructor of the Tree DnD Source
25578 // tags:
25579 // private
25580 if(!params){ params = {}; }
25581 dojo.mixin(this, params);
25582 this.isSource = typeof params.isSource == "undefined" ? true : params.isSource;
25583 var type = params.accept instanceof Array ? params.accept : ["text", "treeNode"];
25584 this.accept = null;
25585 if(type.length){
25586 this.accept = {};
25587 for(var i = 0; i < type.length; ++i){
25588 this.accept[type[i]] = 1;
25589 }
25590 }
25591
25592 // class-specific variables
25593 this.isDragging = false;
25594 this.mouseDown = false;
25595 this.targetAnchor = null; // DOMNode corresponding to the currently moused over TreeNode
25596 this.targetBox = null; // coordinates of this.targetAnchor
25597 this.dropPosition = ""; // whether mouse is over/after/before this.targetAnchor
25598 this._lastX = 0;
25599 this._lastY = 0;
25600
25601 // states
25602 this.sourceState = "";
25603 if(this.isSource){
25604 dojo.addClass(this.node, "dojoDndSource");
25605 }
25606 this.targetState = "";
25607 if(this.accept){
25608 dojo.addClass(this.node, "dojoDndTarget");
25609 }
25610
25611 // set up events
25612 this.topics = [
25613 dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"),
25614 dojo.subscribe("/dnd/start", this, "onDndStart"),
25615 dojo.subscribe("/dnd/drop", this, "onDndDrop"),
25616 dojo.subscribe("/dnd/cancel", this, "onDndCancel")
25617 ];
25618 },
25619
25620 // methods
25621 checkAcceptance: function(source, nodes){
25622 // summary:
25623 // Checks if the target can accept nodes from this source
25624 // source: dijit.tree.dndSource
25625 // The source which provides items
25626 // nodes: DOMNode[]
25627 // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if
25628 // source is a dijit.Tree.
25629 // tags:
25630 // extension
25631 return true; // Boolean
25632 },
25633
25634 copyState: function(keyPressed){
25635 // summary:
25636 // Returns true, if we need to copy items, false to move.
25637 // It is separated to be overwritten dynamically, if needed.
25638 // keyPressed: Boolean
25639 // The "copy" control key was pressed
25640 // tags:
25641 // protected
25642 return this.copyOnly || keyPressed; // Boolean
25643 },
25644 destroy: function(){
25645 // summary:
25646 // Prepares the object to be garbage-collected.
25647 this.inherited("destroy",arguments);
25648 dojo.forEach(this.topics, dojo.unsubscribe);
25649 this.targetAnchor = null;
25650 },
25651
25652 _onDragMouse: function(e){
25653 // summary:
25654 // Helper method for processing onmousemove/onmouseover events while drag is in progress.
25655 // Keeps track of current drop target.
25656
25657 var m = dojo.dnd.manager(),
81bea17a
AD
25658 oldTarget = this.targetAnchor, // the TreeNode corresponding to TreeNode mouse was previously over
25659 newTarget = this.current, // TreeNode corresponding to TreeNode mouse is currently over
a089699c
AD
25660 oldDropPosition = this.dropPosition; // the previous drop position (over/before/after)
25661
25662 // calculate if user is indicating to drop the dragged node before, after, or over
25663 // (i.e., to become a child of) the target node
25664 var newDropPosition = "Over";
25665 if(newTarget && this.betweenThreshold > 0){
25666 // If mouse is over a new TreeNode, then get new TreeNode's position and size
25667 if(!this.targetBox || oldTarget != newTarget){
81bea17a 25668 this.targetBox = dojo.position(newTarget.rowNode, true);
a089699c
AD
25669 }
25670 if((e.pageY - this.targetBox.y) <= this.betweenThreshold){
25671 newDropPosition = "Before";
25672 }else if((e.pageY - this.targetBox.y) >= (this.targetBox.h - this.betweenThreshold)){
25673 newDropPosition = "After";
25674 }
25675 }
25676
25677 if(newTarget != oldTarget || newDropPosition != oldDropPosition){
25678 if(oldTarget){
81bea17a 25679 this._removeItemClass(oldTarget.rowNode, oldDropPosition);
a089699c
AD
25680 }
25681 if(newTarget){
81bea17a 25682 this._addItemClass(newTarget.rowNode, newDropPosition);
a089699c
AD
25683 }
25684
25685 // Check if it's ok to drop the dragged node on/before/after the target node.
25686 if(!newTarget){
25687 m.canDrop(false);
81bea17a 25688 }else if(newTarget == this.tree.rootNode && newDropPosition != "Over"){
a089699c
AD
25689 // Can't drop before or after tree's root node; the dropped node would just disappear (at least visually)
25690 m.canDrop(false);
25691 }else if(m.source == this && (newTarget.id in this.selection)){
25692 // Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140)
25693 m.canDrop(false);
81bea17a
AD
25694 }else if(this.checkItemAcceptance(newTarget.rowNode, m.source, newDropPosition.toLowerCase())
25695 && !this._isParentChildDrop(m.source, newTarget.rowNode)){
a089699c
AD
25696 m.canDrop(true);
25697 }else{
25698 m.canDrop(false);
25699 }
25700
25701 this.targetAnchor = newTarget;
25702 this.dropPosition = newDropPosition;
25703 }
25704 },
25705
25706 onMouseMove: function(e){
25707 // summary:
25708 // Called for any onmousemove events over the Tree
25709 // e: Event
25710 // onmousemouse event
25711 // tags:
25712 // private
25713 if(this.isDragging && this.targetState == "Disabled"){ return; }
25714 this.inherited(arguments);
25715 var m = dojo.dnd.manager();
25716 if(this.isDragging){
25717 this._onDragMouse(e);
25718 }else{
25719 if(this.mouseDown && this.isSource &&
25720 (Math.abs(e.pageX-this._lastX)>=this.dragThreshold || Math.abs(e.pageY-this._lastY)>=this.dragThreshold)){
81bea17a 25721 var nodes = this.getSelectedTreeNodes();
a089699c 25722 if(nodes.length){
81bea17a
AD
25723 if(nodes.length > 1){
25724 //filter out all selected items which has one of their ancestor selected as well
25725 var seen = this.selection, i = 0, r = [], n, p;
25726 nextitem: while((n = nodes[i++])){
25727 for(p = n.getParent(); p && p !== this.tree; p = p.getParent()){
25728 if(seen[p.id]){ //parent is already selected, skip this node
25729 continue nextitem;
25730 }
25731 }
25732 //this node does not have any ancestors selected, add it
25733 r.push(n);
25734 }
25735 nodes = r;
25736 }
25737 nodes = dojo.map(nodes, function(n){return n.domNode});
a089699c
AD
25738 m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e)));
25739 }
25740 }
25741 }
25742 },
25743
25744 onMouseDown: function(e){
25745 // summary:
25746 // Event processor for onmousedown
25747 // e: Event
25748 // onmousedown event
25749 // tags:
25750 // private
25751 this.mouseDown = true;
25752 this.mouseButton = e.button;
25753 this._lastX = e.pageX;
25754 this._lastY = e.pageY;
81bea17a 25755 this.inherited(arguments);
a089699c
AD
25756 },
25757
25758 onMouseUp: function(e){
25759 // summary:
25760 // Event processor for onmouseup
25761 // e: Event
25762 // onmouseup event
25763 // tags:
25764 // private
25765 if(this.mouseDown){
25766 this.mouseDown = false;
81bea17a 25767 this.inherited(arguments);
a089699c
AD
25768 }
25769 },
25770
25771 onMouseOut: function(){
25772 // summary:
25773 // Event processor for when mouse is moved away from a TreeNode
25774 // tags:
25775 // private
25776 this.inherited(arguments);
25777 this._unmarkTargetAnchor();
25778 },
25779
25780 checkItemAcceptance: function(target, source, position){
25781 // summary:
25782 // Stub function to be overridden if one wants to check for the ability to drop at the node/item level
25783 // description:
25784 // In the base case, this is called to check if target can become a child of source.
25785 // When betweenThreshold is set, position="before" or "after" means that we
25786 // are asking if the source node can be dropped before/after the target node.
25787 // target: DOMNode
25788 // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to
25789 // Use dijit.getEnclosingWidget(target) to get the TreeNode.
25790 // source: dijit.tree.dndSource
25791 // The (set of) nodes we are dropping
25792 // position: String
25793 // "over", "before", or "after"
25794 // tags:
25795 // extension
25796 return true;
25797 },
25798
25799 // topic event processors
25800 onDndSourceOver: function(source){
25801 // summary:
25802 // Topic event processor for /dnd/source/over, called when detected a current source.
25803 // source: Object
25804 // The dijit.tree.dndSource / dojo.dnd.Source which has the mouse over it
25805 // tags:
25806 // private
25807 if(this != source){
25808 this.mouseDown = false;
25809 this._unmarkTargetAnchor();
25810 }else if(this.isDragging){
25811 var m = dojo.dnd.manager();
25812 m.canDrop(false);
25813 }
25814 },
25815 onDndStart: function(source, nodes, copy){
25816 // summary:
25817 // Topic event processor for /dnd/start, called to initiate the DnD operation
25818 // source: Object
25819 // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
25820 // nodes: DomNode[]
25821 // The list of transferred items, dndTreeNode nodes if dragging from a Tree
25822 // copy: Boolean
25823 // Copy items, if true, move items otherwise
25824 // tags:
25825 // private
25826
25827 if(this.isSource){
25828 this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
25829 }
25830 var accepted = this.checkAcceptance(source, nodes);
25831
25832 this._changeState("Target", accepted ? "" : "Disabled");
25833
25834 if(this == source){
25835 dojo.dnd.manager().overSource(this);
25836 }
25837
25838 this.isDragging = true;
25839 },
25840
25841 itemCreator: function(/*DomNode[]*/ nodes, target, /*dojo.dnd.Source*/ source){
25842 // summary:
25843 // Returns objects passed to `Tree.model.newItem()` based on DnD nodes
25844 // dropped onto the tree. Developer must override this method to enable
25845 // dropping from external sources onto this Tree, unless the Tree.model's items
25846 // happen to look like {id: 123, name: "Apple" } with no other attributes.
25847 // description:
25848 // For each node in nodes[], which came from source, create a hash of name/value
25849 // pairs to be passed to Tree.model.newItem(). Returns array of those hashes.
25850 // returns: Object[]
25851 // Array of name/value hashes for each new item to be added to the Tree, like:
25852 // | [
25853 // | { id: 123, label: "apple", foo: "bar" },
25854 // | { id: 456, label: "pear", zaz: "bam" }
25855 // | ]
25856 // tags:
25857 // extension
25858
25859 // TODO: for 2.0 refactor so itemCreator() is called once per drag node, and
25860 // make signature itemCreator(sourceItem, node, target) (or similar).
25861
25862 return dojo.map(nodes, function(node){
25863 return {
25864 "id": node.id,
25865 "name": node.textContent || node.innerText || ""
25866 };
25867 }); // Object[]
25868 },
25869
25870 onDndDrop: function(source, nodes, copy){
25871 // summary:
25872 // Topic event processor for /dnd/drop, called to finish the DnD operation.
25873 // description:
25874 // Updates data store items according to where node was dragged from and dropped
25875 // to. The tree will then respond to those data store updates and redraw itself.
25876 // source: Object
25877 // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items
25878 // nodes: DomNode[]
25879 // The list of transferred items, dndTreeNode nodes if dragging from a Tree
25880 // copy: Boolean
25881 // Copy items, if true, move items otherwise
25882 // tags:
25883 // protected
25884 if(this.containerState == "Over"){
25885 var tree = this.tree,
25886 model = tree.model,
25887 target = this.targetAnchor,
25888 requeryRoot = false; // set to true iff top level items change
25889
25890 this.isDragging = false;
25891
25892 // Compute the new parent item
81bea17a 25893 var targetWidget = target;
a089699c
AD
25894 var newParentItem;
25895 var insertIndex;
25896 newParentItem = (targetWidget && targetWidget.item) || tree.item;
25897 if(this.dropPosition == "Before" || this.dropPosition == "After"){
25898 // TODO: if there is no parent item then disallow the drop.
25899 // Actually this should be checked during onMouseMove too, to make the drag icon red.
25900 newParentItem = (targetWidget.getParent() && targetWidget.getParent().item) || tree.item;
25901 // Compute the insert index for reordering
25902 insertIndex = targetWidget.getIndexInParent();
25903 if(this.dropPosition == "After"){
25904 insertIndex = targetWidget.getIndexInParent() + 1;
25905 }
25906 }else{
25907 newParentItem = (targetWidget && targetWidget.item) || tree.item;
25908 }
25909
25910 // If necessary, use this variable to hold array of hashes to pass to model.newItem()
25911 // (one entry in the array for each dragged node).
25912 var newItemsParams;
25913
25914 dojo.forEach(nodes, function(node, idx){
25915 // dojo.dnd.Item representing the thing being dropped.
25916 // Don't confuse the use of item here (meaning a DnD item) with the
25917 // uses below where item means dojo.data item.
25918 var sourceItem = source.getItem(node.id);
25919
25920 // Information that's available if the source is another Tree
25921 // (possibly but not necessarily this tree, possibly but not
25922 // necessarily the same model as this Tree)
25923 if(dojo.indexOf(sourceItem.type, "treeNode") != -1){
25924 var childTreeNode = sourceItem.data,
25925 childItem = childTreeNode.item,
25926 oldParentItem = childTreeNode.getParent().item;
25927 }
25928
25929 if(source == this){
25930 // This is a node from my own tree, and we are moving it, not copying.
25931 // Remove item from old parent's children attribute.
25932 // TODO: dijit.tree.dndSelector should implement deleteSelectedNodes()
25933 // and this code should go there.
25934
25935 if(typeof insertIndex == "number"){
25936 if(newParentItem == oldParentItem && childTreeNode.getIndexInParent() < insertIndex){
25937 insertIndex -= 1;
25938 }
25939 }
25940 model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex);
25941 }else if(model.isItem(childItem)){
25942 // Item from same model
25943 // (maybe we should only do this branch if the source is a tree?)
25944 model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex);
25945 }else{
25946 // Get the hash to pass to model.newItem(). A single call to
25947 // itemCreator() returns an array of hashes, one for each drag source node.
25948 if(!newItemsParams){
81bea17a 25949 newItemsParams = this.itemCreator(nodes, target.rowNode, source);
a089699c
AD
25950 }
25951
25952 // Create new item in the tree, based on the drag source.
25953 model.newItem(newItemsParams[idx], newParentItem, insertIndex);
25954 }
25955 }, this);
25956
25957 // Expand the target node (if it's currently collapsed) so the user can see
25958 // where their node was dropped. In particular since that node is still selected.
25959 this.tree._expandNode(targetWidget);
25960 }
25961 this.onDndCancel();
25962 },
25963
25964 onDndCancel: function(){
25965 // summary:
25966 // Topic event processor for /dnd/cancel, called to cancel the DnD operation
25967 // tags:
25968 // private
25969 this._unmarkTargetAnchor();
25970 this.isDragging = false;
25971 this.mouseDown = false;
25972 delete this.mouseButton;
25973 this._changeState("Source", "");
25974 this._changeState("Target", "");
25975 },
25976
25977 // When focus moves in/out of the entire Tree
25978 onOverEvent: function(){
25979 // summary:
25980 // This method is called when mouse is moved over our container (like onmouseenter)
25981 // tags:
25982 // private
25983 this.inherited(arguments);
25984 dojo.dnd.manager().overSource(this);
25985 },
25986 onOutEvent: function(){
25987 // summary:
25988 // This method is called when mouse is moved out of our container (like onmouseleave)
25989 // tags:
25990 // private
25991 this._unmarkTargetAnchor();
25992 var m = dojo.dnd.manager();
25993 if(this.isDragging){
25994 m.canDrop(false);
25995 }
25996 m.outSource(this);
25997
25998 this.inherited(arguments);
25999 },
26000
26001 _isParentChildDrop: function(source, targetRow){
26002 // summary:
26003 // Checks whether the dragged items are parent rows in the tree which are being
26004 // dragged into their own children.
26005 //
26006 // source:
26007 // The DragSource object.
26008 //
26009 // targetRow:
26010 // The tree row onto which the dragged nodes are being dropped.
26011 //
26012 // tags:
26013 // private
26014
26015 // If the dragged object is not coming from the tree this widget belongs to,
26016 // it cannot be invalid.
26017 if(!source.tree || source.tree != this.tree){
26018 return false;
26019 }
26020
26021
26022 var root = source.tree.domNode;
81bea17a 26023 var ids = source.selection;
a089699c
AD
26024
26025 var node = targetRow.parentNode;
26026
26027 // Iterate up the DOM hierarchy from the target drop row,
26028 // checking of any of the dragged nodes have the same ID.
81bea17a 26029 while(node != root && !ids[node.id]){
a089699c
AD
26030 node = node.parentNode;
26031 }
26032
26033 return node.id && ids[node.id];
26034 },
26035
26036 _unmarkTargetAnchor: function(){
26037 // summary:
26038 // Removes hover class of the current target anchor
26039 // tags:
26040 // private
26041 if(!this.targetAnchor){ return; }
81bea17a 26042 this._removeItemClass(this.targetAnchor.rowNode, this.dropPosition);
a089699c
AD
26043 this.targetAnchor = null;
26044 this.targetBox = null;
26045 this.dropPosition = null;
26046 },
26047
26048 _markDndStatus: function(copy){
26049 // summary:
26050 // Changes source's state based on "copy" status
26051 this._changeState("Source", copy ? "Copied" : "Moved");
26052 }
26053});
26054
26055}
26056
26057if(!dojo._hasResource["dojo.data.ItemFileReadStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
26058dojo._hasResource["dojo.data.ItemFileReadStore"] = true;
26059dojo.provide("dojo.data.ItemFileReadStore");
26060
26061
26062
26063
26064
26065dojo.declare("dojo.data.ItemFileReadStore", null,{
26066 // summary:
26067 // The ItemFileReadStore implements the dojo.data.api.Read API and reads
26068 // data from JSON files that have contents in this format --
26069 // { items: [
26070 // { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]},
26071 // { name:'Fozzie Bear', wears:['hat', 'tie']},
26072 // { name:'Miss Piggy', pets:'Foo-Foo'}
26073 // ]}
81bea17a 26074 // Note that it can also contain an 'identifer' property that specified which attribute on the items
a089699c
AD
26075 // in the array of items that acts as the unique identifier for that item.
26076 //
26077 constructor: function(/* Object */ keywordParameters){
26078 // summary: constructor
26079 // keywordParameters: {url: String}
26080 // keywordParameters: {data: jsonObject}
26081 // keywordParameters: {typeMap: object)
26082 // The structure of the typeMap object is as follows:
26083 // {
26084 // type0: function || object,
26085 // type1: function || object,
26086 // ...
26087 // typeN: function || object
26088 // }
81bea17a 26089 // Where if it is a function, it is assumed to be an object constructor that takes the
a089699c
AD
26090 // value of _value as the initialization parameters. If it is an object, then it is assumed
26091 // to be an object of general form:
26092 // {
26093 // type: function, //constructor.
26094 // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately.
26095 // }
26096
26097 this._arrayOfAllItems = [];
26098 this._arrayOfTopLevelItems = [];
26099 this._loadFinished = false;
26100 this._jsonFileUrl = keywordParameters.url;
26101 this._ccUrl = keywordParameters.url;
26102 this.url = keywordParameters.url;
26103 this._jsonData = keywordParameters.data;
26104 this.data = null;
26105 this._datatypeMap = keywordParameters.typeMap || {};
26106 if(!this._datatypeMap['Date']){
26107 //If no default mapping for dates, then set this as default.
26108 //We use the dojo.date.stamp here because the ISO format is the 'dojo way'
26109 //of generically representing dates.
26110 this._datatypeMap['Date'] = {
26111 type: Date,
26112 deserialize: function(value){
26113 return dojo.date.stamp.fromISOString(value);
26114 }
26115 };
26116 }
26117 this._features = {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true};
26118 this._itemsByIdentity = null;
26119 this._storeRefPropName = "_S"; // Default name for the store reference to attach to every item.
26120 this._itemNumPropName = "_0"; // Default Item Id for isItem to attach to every item.
26121 this._rootItemPropName = "_RI"; // Default Item Id for isItem to attach to every item.
26122 this._reverseRefMap = "_RRM"; // Default attribute for constructing a reverse reference map for use with reference integrity
26123 this._loadInProgress = false; //Got to track the initial load to prevent duelling loads of the dataset.
26124 this._queuedFetches = [];
26125 if(keywordParameters.urlPreventCache !== undefined){
26126 this.urlPreventCache = keywordParameters.urlPreventCache?true:false;
26127 }
26128 if(keywordParameters.hierarchical !== undefined){
26129 this.hierarchical = keywordParameters.hierarchical?true:false;
26130 }
26131 if(keywordParameters.clearOnClose){
26132 this.clearOnClose = true;
26133 }
26134 if("failOk" in keywordParameters){
26135 this.failOk = keywordParameters.failOk?true:false;
26136 }
26137 },
26138
26139 url: "", // use "" rather than undefined for the benefit of the parser (#3539)
26140
26141 //Internal var, crossCheckUrl. Used so that setting either url or _jsonFileUrl, can still trigger a reload
26142 //when clearOnClose and close is used.
26143 _ccUrl: "",
26144
26145 data: null, // define this so that the parser can populate it
26146
26147 typeMap: null, //Define so parser can populate.
26148
26149 //Parameter to allow users to specify if a close call should force a reload or not.
26150 //By default, it retains the old behavior of not clearing if close is called. But
26151 //if set true, the store will be reset to default state. Note that by doing this,
26152 //all item handles will become invalid and a new fetch must be issued.
26153 clearOnClose: false,
26154
81bea17a 26155 //Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url.
a089699c
AD
26156 //Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option.
26157 //Added for tracker: #6072
26158 urlPreventCache: false,
26159
26160 //Parameter for specifying that it is OK for the xhrGet call to fail silently.
26161 failOk: false,
26162
81bea17a
AD
26163 //Parameter to indicate to process data from the url as hierarchical
26164 //(data items can contain other data items in js form). Default is true
26165 //for backwards compatibility. False means only root items are processed
26166 //as items, all child objects outside of type-mapped objects and those in
a089699c
AD
26167 //specific reference format, are left straight JS data objects.
26168 hierarchical: true,
26169
26170 _assertIsItem: function(/* item */ item){
26171 // summary:
26172 // This function tests whether the item passed in is indeed an item in the store.
81bea17a 26173 // item:
a089699c 26174 // The item to test for being contained by the store.
81bea17a 26175 if(!this.isItem(item)){
a089699c
AD
26176 throw new Error("dojo.data.ItemFileReadStore: Invalid item argument.");
26177 }
26178 },
26179
26180 _assertIsAttribute: function(/* attribute-name-string */ attribute){
26181 // summary:
26182 // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
81bea17a 26183 // attribute:
a089699c 26184 // The attribute to test for being contained by the store.
81bea17a 26185 if(typeof attribute !== "string"){
a089699c
AD
26186 throw new Error("dojo.data.ItemFileReadStore: Invalid attribute argument.");
26187 }
26188 },
26189
81bea17a
AD
26190 getValue: function( /* item */ item,
26191 /* attribute-name-string */ attribute,
a089699c 26192 /* value? */ defaultValue){
81bea17a 26193 // summary:
a089699c
AD
26194 // See dojo.data.api.Read.getValue()
26195 var values = this.getValues(item, attribute);
26196 return (values.length > 0)?values[0]:defaultValue; // mixed
26197 },
26198
81bea17a 26199 getValues: function(/* item */ item,
a089699c 26200 /* attribute-name-string */ attribute){
81bea17a 26201 // summary:
a089699c
AD
26202 // See dojo.data.api.Read.getValues()
26203
26204 this._assertIsItem(item);
26205 this._assertIsAttribute(attribute);
26206 // Clone it before returning. refs: #10474
26207 return (item[attribute] || []).slice(0); // Array
26208 },
26209
26210 getAttributes: function(/* item */ item){
81bea17a 26211 // summary:
a089699c
AD
26212 // See dojo.data.api.Read.getAttributes()
26213 this._assertIsItem(item);
26214 var attributes = [];
26215 for(var key in item){
26216 // Save off only the real item attributes, not the special id marks for O(1) isItem.
26217 if((key !== this._storeRefPropName) && (key !== this._itemNumPropName) && (key !== this._rootItemPropName) && (key !== this._reverseRefMap)){
26218 attributes.push(key);
26219 }
26220 }
26221 return attributes; // Array
26222 },
26223
26224 hasAttribute: function( /* item */ item,
26225 /* attribute-name-string */ attribute){
81bea17a 26226 // summary:
a089699c
AD
26227 // See dojo.data.api.Read.hasAttribute()
26228 this._assertIsItem(item);
26229 this._assertIsAttribute(attribute);
26230 return (attribute in item);
26231 },
26232
81bea17a
AD
26233 containsValue: function(/* item */ item,
26234 /* attribute-name-string */ attribute,
a089699c 26235 /* anything */ value){
81bea17a 26236 // summary:
a089699c
AD
26237 // See dojo.data.api.Read.containsValue()
26238 var regexp = undefined;
26239 if(typeof value === "string"){
26240 regexp = dojo.data.util.filter.patternToRegExp(value, false);
26241 }
26242 return this._containsValue(item, attribute, value, regexp); //boolean.
26243 },
26244
81bea17a
AD
26245 _containsValue: function( /* item */ item,
26246 /* attribute-name-string */ attribute,
a089699c
AD
26247 /* anything */ value,
26248 /* RegExp?*/ regexp){
81bea17a 26249 // summary:
a089699c 26250 // Internal function for looking at the values contained by the item.
81bea17a
AD
26251 // description:
26252 // Internal function for looking at the values contained by the item. This
a089699c
AD
26253 // function allows for denoting if the comparison should be case sensitive for
26254 // strings or not (for handling filtering cases where string case should not matter)
81bea17a 26255 //
a089699c
AD
26256 // item:
26257 // The data item to examine for attribute values.
26258 // attribute:
26259 // The attribute to inspect.
81bea17a 26260 // value:
a089699c
AD
26261 // The value to match.
26262 // regexp:
26263 // Optional regular expression generated off value if value was of string type to handle wildcarding.
26264 // If present and attribute values are string, then it can be used for comparison instead of 'value'
26265 return dojo.some(this.getValues(item, attribute), function(possibleValue){
26266 if(possibleValue !== null && !dojo.isObject(possibleValue) && regexp){
26267 if(possibleValue.toString().match(regexp)){
26268 return true; // Boolean
26269 }
26270 }else if(value === possibleValue){
26271 return true; // Boolean
26272 }
26273 });
26274 },
26275
26276 isItem: function(/* anything */ something){
81bea17a 26277 // summary:
a089699c
AD
26278 // See dojo.data.api.Read.isItem()
26279 if(something && something[this._storeRefPropName] === this){
26280 if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){
26281 return true;
26282 }
26283 }
26284 return false; // Boolean
26285 },
26286
26287 isItemLoaded: function(/* anything */ something){
81bea17a 26288 // summary:
a089699c
AD
26289 // See dojo.data.api.Read.isItemLoaded()
26290 return this.isItem(something); //boolean
26291 },
26292
26293 loadItem: function(/* object */ keywordArgs){
81bea17a 26294 // summary:
a089699c
AD
26295 // See dojo.data.api.Read.loadItem()
26296 this._assertIsItem(keywordArgs.item);
26297 },
26298
26299 getFeatures: function(){
81bea17a 26300 // summary:
a089699c
AD
26301 // See dojo.data.api.Read.getFeatures()
26302 return this._features; //Object
26303 },
26304
26305 getLabel: function(/* item */ item){
81bea17a 26306 // summary:
a089699c
AD
26307 // See dojo.data.api.Read.getLabel()
26308 if(this._labelAttr && this.isItem(item)){
26309 return this.getValue(item,this._labelAttr); //String
26310 }
26311 return undefined; //undefined
26312 },
26313
26314 getLabelAttributes: function(/* item */ item){
81bea17a 26315 // summary:
a089699c
AD
26316 // See dojo.data.api.Read.getLabelAttributes()
26317 if(this._labelAttr){
26318 return [this._labelAttr]; //array
26319 }
26320 return null; //null
26321 },
26322
81bea17a
AD
26323 _fetchItems: function( /* Object */ keywordArgs,
26324 /* Function */ findCallback,
a089699c 26325 /* Function */ errorCallback){
81bea17a 26326 // summary:
a089699c
AD
26327 // See dojo.data.util.simpleFetch.fetch()
26328 var self = this,
26329 filter = function(requestArgs, arrayOfItems){
26330 var items = [],
26331 i, key;
26332 if(requestArgs.query){
26333 var value,
26334 ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false;
26335
26336 //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
26337 //same value for each item examined. Much more efficient.
26338 var regexpList = {};
26339 for(key in requestArgs.query){
26340 value = requestArgs.query[key];
26341 if(typeof value === "string"){
26342 regexpList[key] = dojo.data.util.filter.patternToRegExp(value, ignoreCase);
26343 }else if(value instanceof RegExp){
26344 regexpList[key] = value;
26345 }
26346 }
26347 for(i = 0; i < arrayOfItems.length; ++i){
26348 var match = true;
26349 var candidateItem = arrayOfItems[i];
26350 if(candidateItem === null){
26351 match = false;
26352 }else{
26353 for(key in requestArgs.query){
26354 value = requestArgs.query[key];
26355 if(!self._containsValue(candidateItem, key, value, regexpList[key])){
26356 match = false;
26357 }
26358 }
26359 }
26360 if(match){
26361 items.push(candidateItem);
26362 }
26363 }
26364 findCallback(items, requestArgs);
26365 }else{
81bea17a
AD
26366 // We want a copy to pass back in case the parent wishes to sort the array.
26367 // We shouldn't allow resort of the internal list, so that multiple callers
a089699c
AD
26368 // can get lists and sort without affecting each other. We also need to
26369 // filter out any null values that have been left as a result of deleteItem()
26370 // calls in ItemFileWriteStore.
26371 for(i = 0; i < arrayOfItems.length; ++i){
26372 var item = arrayOfItems[i];
26373 if(item !== null){
26374 items.push(item);
26375 }
26376 }
26377 findCallback(items, requestArgs);
26378 }
26379 };
26380
26381 if(this._loadFinished){
26382 filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions));
26383 }else{
26384 //Do a check on the JsonFileUrl and crosscheck it.
26385 //If it doesn't match the cross-check, it needs to be updated
26386 //This allows for either url or _jsonFileUrl to he changed to
81bea17a 26387 //reset the store load location. Done this way for backwards
a089699c
AD
26388 //compatibility. People use _jsonFileUrl (even though officially
26389 //private.
26390 if(this._jsonFileUrl !== this._ccUrl){
81bea17a 26391 dojo.deprecated("dojo.data.ItemFileReadStore: ",
a089699c
AD
26392 "To change the url, set the url property of the store," +
26393 " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
26394 this._ccUrl = this._jsonFileUrl;
26395 this.url = this._jsonFileUrl;
26396 }else if(this.url !== this._ccUrl){
26397 this._jsonFileUrl = this.url;
26398 this._ccUrl = this.url;
26399 }
26400
26401 //See if there was any forced reset of data.
81bea17a 26402 if(this.data != null){
a089699c
AD
26403 this._jsonData = this.data;
26404 this.data = null;
26405 }
26406
26407 if(this._jsonFileUrl){
26408 //If fetches come in before the loading has finished, but while
81bea17a 26409 //a load is in progress, we have to defer the fetching to be
a089699c
AD
26410 //invoked in the callback.
26411 if(this._loadInProgress){
26412 this._queuedFetches.push({args: keywordArgs, filter: filter});
26413 }else{
26414 this._loadInProgress = true;
26415 var getArgs = {
81bea17a 26416 url: self._jsonFileUrl,
a089699c
AD
26417 handleAs: "json-comment-optional",
26418 preventCache: this.urlPreventCache,
26419 failOk: this.failOk
26420 };
26421 var getHandler = dojo.xhrGet(getArgs);
26422 getHandler.addCallback(function(data){
26423 try{
26424 self._getItemsFromLoadedData(data);
26425 self._loadFinished = true;
26426 self._loadInProgress = false;
26427
26428 filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions));
26429 self._handleQueuedFetches();
26430 }catch(e){
26431 self._loadFinished = true;
26432 self._loadInProgress = false;
26433 errorCallback(e, keywordArgs);
26434 }
26435 });
26436 getHandler.addErrback(function(error){
26437 self._loadInProgress = false;
26438 errorCallback(error, keywordArgs);
26439 });
26440
26441 //Wire up the cancel to abort of the request
26442 //This call cancel on the deferred if it hasn't been called
26443 //yet and then will chain to the simple abort of the
26444 //simpleFetch keywordArgs
26445 var oldAbort = null;
26446 if(keywordArgs.abort){
26447 oldAbort = keywordArgs.abort;
26448 }
26449 keywordArgs.abort = function(){
26450 var df = getHandler;
26451 if(df && df.fired === -1){
26452 df.cancel();
26453 df = null;
26454 }
26455 if(oldAbort){
26456 oldAbort.call(keywordArgs);
26457 }
26458 };
26459 }
26460 }else if(this._jsonData){
26461 try{
26462 this._loadFinished = true;
26463 this._getItemsFromLoadedData(this._jsonData);
26464 this._jsonData = null;
26465 filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions));
26466 }catch(e){
26467 errorCallback(e, keywordArgs);
26468 }
26469 }else{
26470 errorCallback(new Error("dojo.data.ItemFileReadStore: No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs);
26471 }
26472 }
26473 },
26474
26475 _handleQueuedFetches: function(){
81bea17a 26476 // summary:
a089699c
AD
26477 // Internal function to execute delayed request in the store.
26478 //Execute any deferred fetches now.
26479 if(this._queuedFetches.length > 0){
26480 for(var i = 0; i < this._queuedFetches.length; i++){
26481 var fData = this._queuedFetches[i],
26482 delayedQuery = fData.args,
26483 delayedFilter = fData.filter;
26484 if(delayedFilter){
81bea17a 26485 delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions));
a089699c
AD
26486 }else{
26487 this.fetchItemByIdentity(delayedQuery);
26488 }
26489 }
26490 this._queuedFetches = [];
26491 }
26492 },
26493
26494 _getItemsArray: function(/*object?*/queryOptions){
81bea17a 26495 // summary:
a089699c
AD
26496 // Internal function to determine which list of items to search over.
26497 // queryOptions: The query options parameter, if any.
26498 if(queryOptions && queryOptions.deep){
81bea17a 26499 return this._arrayOfAllItems;
a089699c
AD
26500 }
26501 return this._arrayOfTopLevelItems;
26502 },
26503
26504 close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
81bea17a 26505 // summary:
a089699c 26506 // See dojo.data.api.Read.close()
81bea17a
AD
26507 if(this.clearOnClose &&
26508 this._loadFinished &&
a089699c
AD
26509 !this._loadInProgress){
26510 //Reset all internalsback to default state. This will force a reload
81bea17a 26511 //on next fetch. This also checks that the data or url param was set
a089699c
AD
26512 //so that the store knows it can get data. Without one of those being set,
26513 //the next fetch will trigger an error.
26514
81bea17a 26515 if(((this._jsonFileUrl == "" || this._jsonFileUrl == null) &&
a089699c
AD
26516 (this.url == "" || this.url == null)
26517 ) && this.data == null){
26518 console.debug("dojo.data.ItemFileReadStore: WARNING! Data reload " +
81bea17a 26519 " information has not been provided." +
a089699c
AD
26520 " Please set 'url' or 'data' to the appropriate value before" +
26521 " the next fetch");
26522 }
26523 this._arrayOfAllItems = [];
26524 this._arrayOfTopLevelItems = [];
26525 this._loadFinished = false;
26526 this._itemsByIdentity = null;
26527 this._loadInProgress = false;
26528 this._queuedFetches = [];
26529 }
26530 },
26531
26532 _getItemsFromLoadedData: function(/* Object */ dataObject){
26533 // summary:
26534 // Function to parse the loaded data into item format and build the internal items array.
26535 // description:
26536 // Function to parse the loaded data into item format and build the internal items array.
26537 //
26538 // dataObject:
26539 // The JS data object containing the raw data to convery into item format.
26540 //
26541 // returns: array
26542 // Array of items in store item format.
26543
26544 // First, we define a couple little utility functions...
26545 var addingArrays = false,
26546 self = this;
26547
26548 function valueIsAnItem(/* anything */ aValue){
26549 // summary:
26550 // Given any sort of value that could be in the raw json data,
26551 // return true if we should interpret the value as being an
26552 // item itself, rather than a literal value or a reference.
26553 // example:
26554 // | false == valueIsAnItem("Kermit");
26555 // | false == valueIsAnItem(42);
26556 // | false == valueIsAnItem(new Date());
81bea17a 26557 // | false == valueIsAnItem({_type:'Date', _value:'1802-05-14'});
a089699c
AD
26558 // | false == valueIsAnItem({_reference:'Kermit'});
26559 // | true == valueIsAnItem({name:'Kermit', color:'green'});
26560 // | true == valueIsAnItem({iggy:'pop'});
26561 // | true == valueIsAnItem({foo:42});
26562 var isItem = (
26563 (aValue !== null) &&
26564 (typeof aValue === "object") &&
26565 (!dojo.isArray(aValue) || addingArrays) &&
26566 (!dojo.isFunction(aValue)) &&
26567 (aValue.constructor == Object || dojo.isArray(aValue)) &&
81bea17a
AD
26568 (typeof aValue._reference === "undefined") &&
26569 (typeof aValue._type === "undefined") &&
a089699c
AD
26570 (typeof aValue._value === "undefined") &&
26571 self.hierarchical
26572 );
26573 return isItem;
26574 }
26575
26576 function addItemAndSubItemsToArrayOfAllItems(/* Item */ anItem){
26577 self._arrayOfAllItems.push(anItem);
26578 for(var attribute in anItem){
26579 var valueForAttribute = anItem[attribute];
26580 if(valueForAttribute){
26581 if(dojo.isArray(valueForAttribute)){
26582 var valueArray = valueForAttribute;
26583 for(var k = 0; k < valueArray.length; ++k){
26584 var singleValue = valueArray[k];
26585 if(valueIsAnItem(singleValue)){
26586 addItemAndSubItemsToArrayOfAllItems(singleValue);
26587 }
26588 }
26589 }else{
26590 if(valueIsAnItem(valueForAttribute)){
26591 addItemAndSubItemsToArrayOfAllItems(valueForAttribute);
26592 }
26593 }
26594 }
26595 }
26596 }
26597
26598 this._labelAttr = dataObject.label;
26599
26600 // We need to do some transformations to convert the data structure
26601 // that we read from the file into a format that will be convenient
26602 // to work with in memory.
26603
26604 // Step 1: Walk through the object hierarchy and build a list of all items
26605 var i,
26606 item;
26607 this._arrayOfAllItems = [];
26608 this._arrayOfTopLevelItems = dataObject.items;
26609
26610 for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){
26611 item = this._arrayOfTopLevelItems[i];
26612 if(dojo.isArray(item)){
26613 addingArrays = true;
26614 }
26615 addItemAndSubItemsToArrayOfAllItems(item);
26616 item[this._rootItemPropName]=true;
26617 }
26618
81bea17a 26619 // Step 2: Walk through all the attribute values of all the items,
a089699c
AD
26620 // and replace single values with arrays. For example, we change this:
26621 // { name:'Miss Piggy', pets:'Foo-Foo'}
26622 // into this:
26623 // { name:['Miss Piggy'], pets:['Foo-Foo']}
81bea17a
AD
26624 //
26625 // We also store the attribute names so we can validate our store
a089699c
AD
26626 // reference and item id special properties for the O(1) isItem
26627 var allAttributeNames = {},
26628 key;
26629
26630 for(i = 0; i < this._arrayOfAllItems.length; ++i){
26631 item = this._arrayOfAllItems[i];
26632 for(key in item){
26633 if(key !== this._rootItemPropName){
26634 var value = item[key];
26635 if(value !== null){
26636 if(!dojo.isArray(value)){
26637 item[key] = [value];
26638 }
26639 }else{
26640 item[key] = [null];
26641 }
26642 }
26643 allAttributeNames[key]=key;
26644 }
26645 }
26646
26647 // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName
26648 // This should go really fast, it will generally never even run the loop.
26649 while(allAttributeNames[this._storeRefPropName]){
26650 this._storeRefPropName += "_";
26651 }
26652 while(allAttributeNames[this._itemNumPropName]){
26653 this._itemNumPropName += "_";
26654 }
26655 while(allAttributeNames[this._reverseRefMap]){
26656 this._reverseRefMap += "_";
26657 }
26658
81bea17a
AD
26659 // Step 4: Some data files specify an optional 'identifier', which is
26660 // the name of an attribute that holds the identity of each item.
26661 // If this data file specified an identifier attribute, then build a
a089699c
AD
26662 // hash table of items keyed by the identity of the items.
26663 var arrayOfValues;
26664
26665 var identifier = dataObject.identifier;
26666 if(identifier){
26667 this._itemsByIdentity = {};
26668 this._features['dojo.data.api.Identity'] = identifier;
26669 for(i = 0; i < this._arrayOfAllItems.length; ++i){
26670 item = this._arrayOfAllItems[i];
26671 arrayOfValues = item[identifier];
26672 var identity = arrayOfValues[0];
81bea17a 26673 if(!Object.hasOwnProperty.call(this._itemsByIdentity, identity)){
a089699c
AD
26674 this._itemsByIdentity[identity] = item;
26675 }else{
26676 if(this._jsonFileUrl){
26677 throw new Error("dojo.data.ItemFileReadStore: The json data as specified by: [" + this._jsonFileUrl + "] is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]");
26678 }else if(this._jsonData){
26679 throw new Error("dojo.data.ItemFileReadStore: The json data provided by the creation arguments is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]");
26680 }
26681 }
26682 }
26683 }else{
26684 this._features['dojo.data.api.Identity'] = Number;
26685 }
26686
81bea17a 26687 // Step 5: Walk through all the items, and set each item's properties
a089699c
AD
26688 // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true.
26689 for(i = 0; i < this._arrayOfAllItems.length; ++i){
26690 item = this._arrayOfAllItems[i];
26691 item[this._storeRefPropName] = this;
26692 item[this._itemNumPropName] = i;
26693 }
26694
26695 // Step 6: We walk through all the attribute values of all the items,
26696 // looking for type/value literals and item-references.
26697 //
26698 // We replace item-references with pointers to items. For example, we change:
26699 // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
26700 // into this:
81bea17a 26701 // { name:['Kermit'], friends:[miss_piggy] }
a089699c
AD
26702 // (where miss_piggy is the object representing the 'Miss Piggy' item).
26703 //
26704 // We replace type/value pairs with typed-literals. For example, we change:
81bea17a 26705 // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'1918-07-18'}] }
a089699c 26706 // into this:
81bea17a 26707 // { name:['Kermit'], born:(new Date(1918, 6, 18)) }
a089699c
AD
26708 //
26709 // We also generate the associate map for all items for the O(1) isItem function.
26710 for(i = 0; i < this._arrayOfAllItems.length; ++i){
26711 item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
26712 for(key in item){
26713 arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}]
26714 for(var j = 0; j < arrayOfValues.length; ++j){
26715 value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}}
26716 if(value !== null && typeof value == "object"){
26717 if(("_type" in value) && ("_value" in value)){
26718 var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber'
26719 var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}}
81bea17a 26720 if(!mappingObj){
a089699c
AD
26721 throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'");
26722 }else if(dojo.isFunction(mappingObj)){
26723 arrayOfValues[j] = new mappingObj(value._value);
26724 }else if(dojo.isFunction(mappingObj.deserialize)){
26725 arrayOfValues[j] = mappingObj.deserialize(value._value);
26726 }else{
26727 throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function");
26728 }
26729 }
26730 if(value._reference){
26731 var referenceDescription = value._reference; // example: {name:'Miss Piggy'}
26732 if(!dojo.isObject(referenceDescription)){
26733 // example: 'Miss Piggy'
26734 // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]}
26735 arrayOfValues[j] = this._getItemByIdentity(referenceDescription);
26736 }else{
26737 // example: {name:'Miss Piggy'}
26738 // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] }
26739 for(var k = 0; k < this._arrayOfAllItems.length; ++k){
26740 var candidateItem = this._arrayOfAllItems[k],
26741 found = true;
26742 for(var refKey in referenceDescription){
81bea17a
AD
26743 if(candidateItem[refKey] != referenceDescription[refKey]){
26744 found = false;
a089699c
AD
26745 }
26746 }
81bea17a
AD
26747 if(found){
26748 arrayOfValues[j] = candidateItem;
a089699c
AD
26749 }
26750 }
26751 }
26752 if(this.referenceIntegrity){
26753 var refItem = arrayOfValues[j];
26754 if(this.isItem(refItem)){
26755 this._addReferenceToMap(refItem, item, key);
26756 }
26757 }
26758 }else if(this.isItem(value)){
81bea17a 26759 //It's a child item (not one referenced through _reference).
a089699c
AD
26760 //We need to treat this as a referenced item, so it can be cleaned up
26761 //in a write store easily.
26762 if(this.referenceIntegrity){
26763 this._addReferenceToMap(value, item, key);
26764 }
26765 }
26766 }
26767 }
26768 }
26769 }
26770 },
26771
26772 _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){
26773 // summary:
26774 // Method to add an reference map entry for an item and attribute.
26775 // description:
26776 // Method to add an reference map entry for an item and attribute. //
26777 // refItem:
26778 // The item that is referenced.
26779 // parentItem:
26780 // The item that holds the new reference to refItem.
26781 // attribute:
26782 // The attribute on parentItem that contains the new reference.
26783
26784 //Stub function, does nothing. Real processing is in ItemFileWriteStore.
26785 },
26786
26787 getIdentity: function(/* item */ item){
81bea17a 26788 // summary:
a089699c
AD
26789 // See dojo.data.api.Identity.getIdentity()
26790 var identifier = this._features['dojo.data.api.Identity'];
26791 if(identifier === Number){
26792 return item[this._itemNumPropName]; // Number
26793 }else{
26794 var arrayOfValues = item[identifier];
26795 if(arrayOfValues){
26796 return arrayOfValues[0]; // Object || String
26797 }
26798 }
26799 return null; // null
26800 },
26801
26802 fetchItemByIdentity: function(/* Object */ keywordArgs){
81bea17a 26803 // summary:
a089699c
AD
26804 // See dojo.data.api.Identity.fetchItemByIdentity()
26805
26806 // Hasn't loaded yet, we have to trigger the load.
26807 var item,
26808 scope;
26809 if(!this._loadFinished){
26810 var self = this;
26811 //Do a check on the JsonFileUrl and crosscheck it.
26812 //If it doesn't match the cross-check, it needs to be updated
26813 //This allows for either url or _jsonFileUrl to he changed to
81bea17a 26814 //reset the store load location. Done this way for backwards
a089699c
AD
26815 //compatibility. People use _jsonFileUrl (even though officially
26816 //private.
26817 if(this._jsonFileUrl !== this._ccUrl){
81bea17a 26818 dojo.deprecated("dojo.data.ItemFileReadStore: ",
a089699c
AD
26819 "To change the url, set the url property of the store," +
26820 " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
26821 this._ccUrl = this._jsonFileUrl;
26822 this.url = this._jsonFileUrl;
26823 }else if(this.url !== this._ccUrl){
26824 this._jsonFileUrl = this.url;
26825 this._ccUrl = this.url;
26826 }
26827
26828 //See if there was any forced reset of data.
26829 if(this.data != null && this._jsonData == null){
26830 this._jsonData = this.data;
26831 this.data = null;
26832 }
26833
26834 if(this._jsonFileUrl){
26835
26836 if(this._loadInProgress){
26837 this._queuedFetches.push({args: keywordArgs});
26838 }else{
26839 this._loadInProgress = true;
26840 var getArgs = {
81bea17a 26841 url: self._jsonFileUrl,
a089699c
AD
26842 handleAs: "json-comment-optional",
26843 preventCache: this.urlPreventCache,
26844 failOk: this.failOk
26845 };
26846 var getHandler = dojo.xhrGet(getArgs);
26847 getHandler.addCallback(function(data){
26848 var scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
26849 try{
26850 self._getItemsFromLoadedData(data);
26851 self._loadFinished = true;
26852 self._loadInProgress = false;
26853 item = self._getItemByIdentity(keywordArgs.identity);
26854 if(keywordArgs.onItem){
26855 keywordArgs.onItem.call(scope, item);
26856 }
26857 self._handleQueuedFetches();
26858 }catch(error){
26859 self._loadInProgress = false;
26860 if(keywordArgs.onError){
26861 keywordArgs.onError.call(scope, error);
26862 }
26863 }
26864 });
26865 getHandler.addErrback(function(error){
26866 self._loadInProgress = false;
26867 if(keywordArgs.onError){
26868 var scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
26869 keywordArgs.onError.call(scope, error);
26870 }
26871 });
26872 }
26873
26874 }else if(this._jsonData){
26875 // Passed in data, no need to xhr.
26876 self._getItemsFromLoadedData(self._jsonData);
26877 self._jsonData = null;
26878 self._loadFinished = true;
26879 item = self._getItemByIdentity(keywordArgs.identity);
26880 if(keywordArgs.onItem){
26881 scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
26882 keywordArgs.onItem.call(scope, item);
26883 }
81bea17a 26884 }
a089699c
AD
26885 }else{
26886 // Already loaded. We can just look it up and call back.
26887 item = this._getItemByIdentity(keywordArgs.identity);
26888 if(keywordArgs.onItem){
26889 scope = keywordArgs.scope?keywordArgs.scope:dojo.global;
26890 keywordArgs.onItem.call(scope, item);
26891 }
26892 }
26893 },
26894
26895 _getItemByIdentity: function(/* Object */ identity){
26896 // summary:
26897 // Internal function to look an item up by its identity map.
26898 var item = null;
81bea17a
AD
26899 if(this._itemsByIdentity &&
26900 Object.hasOwnProperty.call(this._itemsByIdentity, identity)){
a089699c 26901 item = this._itemsByIdentity[identity];
81bea17a 26902 }else if (Object.hasOwnProperty.call(this._arrayOfAllItems, identity)){
a089699c
AD
26903 item = this._arrayOfAllItems[identity];
26904 }
26905 if(item === undefined){
26906 item = null;
26907 }
26908 return item; // Object
26909 },
26910
26911 getIdentityAttributes: function(/* item */ item){
81bea17a
AD
26912 // summary:
26913 // See dojo.data.api.Identity.getIdentityAttributes()
a089699c
AD
26914
26915 var identifier = this._features['dojo.data.api.Identity'];
26916 if(identifier === Number){
26917 // If (identifier === Number) it means getIdentity() just returns
26918 // an integer item-number for each item. The dojo.data.api.Identity
81bea17a
AD
26919 // spec says we need to return null if the identity is not composed
26920 // of attributes
a089699c
AD
26921 return null; // null
26922 }else{
26923 return [identifier]; // Array
26924 }
26925 },
26926
26927 _forceLoad: function(){
81bea17a 26928 // summary:
a089699c 26929 // Internal function to force a load of the store if it hasn't occurred yet. This is required
81bea17a 26930 // for specific functions to work properly.
a089699c
AD
26931 var self = this;
26932 //Do a check on the JsonFileUrl and crosscheck it.
26933 //If it doesn't match the cross-check, it needs to be updated
26934 //This allows for either url or _jsonFileUrl to he changed to
81bea17a 26935 //reset the store load location. Done this way for backwards
a089699c
AD
26936 //compatibility. People use _jsonFileUrl (even though officially
26937 //private.
26938 if(this._jsonFileUrl !== this._ccUrl){
81bea17a 26939 dojo.deprecated("dojo.data.ItemFileReadStore: ",
a089699c
AD
26940 "To change the url, set the url property of the store," +
26941 " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0");
26942 this._ccUrl = this._jsonFileUrl;
26943 this.url = this._jsonFileUrl;
26944 }else if(this.url !== this._ccUrl){
26945 this._jsonFileUrl = this.url;
26946 this._ccUrl = this.url;
26947 }
26948
26949 //See if there was any forced reset of data.
81bea17a 26950 if(this.data != null){
a089699c
AD
26951 this._jsonData = this.data;
26952 this.data = null;
26953 }
26954
26955 if(this._jsonFileUrl){
26956 var getArgs = {
81bea17a 26957 url: this._jsonFileUrl,
a089699c
AD
26958 handleAs: "json-comment-optional",
26959 preventCache: this.urlPreventCache,
26960 failOk: this.failOk,
26961 sync: true
26962 };
26963 var getHandler = dojo.xhrGet(getArgs);
26964 getHandler.addCallback(function(data){
26965 try{
81bea17a 26966 //Check to be sure there wasn't another load going on concurrently
a089699c
AD
26967 //So we don't clobber data that comes in on it. If there is a load going on
26968 //then do not save this data. It will potentially clobber current data.
26969 //We mainly wanted to sync/wait here.
26970 //TODO: Revisit the loading scheme of this store to improve multi-initial
26971 //request handling.
26972 if(self._loadInProgress !== true && !self._loadFinished){
26973 self._getItemsFromLoadedData(data);
26974 self._loadFinished = true;
26975 }else if(self._loadInProgress){
26976 //Okay, we hit an error state we can't recover from. A forced load occurred
26977 //while an async load was occurring. Since we cannot block at this point, the best
26978 //that can be managed is to throw an error.
81bea17a 26979 throw new Error("dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress.");
a089699c
AD
26980 }
26981 }catch(e){
26982 console.log(e);
26983 throw e;
26984 }
26985 });
26986 getHandler.addErrback(function(error){
26987 throw error;
26988 });
26989 }else if(this._jsonData){
26990 self._getItemsFromLoadedData(self._jsonData);
26991 self._jsonData = null;
26992 self._loadFinished = true;
81bea17a 26993 }
a089699c
AD
26994 }
26995});
26996//Mix in the simple fetch implementation to this class.
26997dojo.extend(dojo.data.ItemFileReadStore,dojo.data.util.simpleFetch);
26998
26999}
27000
27001if(!dojo._hasResource["dojo.data.ItemFileWriteStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
27002dojo._hasResource["dojo.data.ItemFileWriteStore"] = true;
27003dojo.provide("dojo.data.ItemFileWriteStore");
27004
27005
81bea17a 27006
a089699c
AD
27007dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, {
27008 constructor: function(/* object */ keywordParameters){
27009 // keywordParameters: {typeMap: object)
27010 // The structure of the typeMap object is as follows:
27011 // {
27012 // type0: function || object,
27013 // type1: function || object,
27014 // ...
27015 // typeN: function || object
27016 // }
81bea17a 27017 // Where if it is a function, it is assumed to be an object constructor that takes the
a089699c
AD
27018 // value of _value as the initialization parameters. It is serialized assuming object.toString()
27019 // serialization. If it is an object, then it is assumed
27020 // to be an object of general form:
27021 // {
27022 // type: function, //constructor.
27023 // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately.
27024 // serialize: function(object) //The function that converts the object back into the proper file format form.
27025 // }
27026
27027 // ItemFileWriteStore extends ItemFileReadStore to implement these additional dojo.data APIs
27028 this._features['dojo.data.api.Write'] = true;
27029 this._features['dojo.data.api.Notification'] = true;
27030
27031 // For keeping track of changes so that we can implement isDirty and revert
27032 this._pending = {
81bea17a
AD
27033 _newItems:{},
27034 _modifiedItems:{},
a089699c
AD
27035 _deletedItems:{}
27036 };
27037
27038 if(!this._datatypeMap['Date'].serialize){
27039 this._datatypeMap['Date'].serialize = function(obj){
27040 return dojo.date.stamp.toISOString(obj, {zulu:true});
27041 };
27042 }
27043 //Disable only if explicitly set to false.
27044 if(keywordParameters && (keywordParameters.referenceIntegrity === false)){
27045 this.referenceIntegrity = false;
27046 }
27047
27048 // this._saveInProgress is set to true, briefly, from when save() is first called to when it completes
27049 this._saveInProgress = false;
27050 },
27051
27052 referenceIntegrity: true, //Flag that defaultly enabled reference integrity tracking. This way it can also be disabled pogrammatially or declaratively.
27053
27054 _assert: function(/* boolean */ condition){
27055 if(!condition){
27056 throw new Error("assertion failed in ItemFileWriteStore");
27057 }
27058 },
27059
27060 _getIdentifierAttribute: function(){
27061 var identifierAttribute = this.getFeatures()['dojo.data.api.Identity'];
27062 // this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute)));
27063 return identifierAttribute;
27064 },
27065
27066
27067/* dojo.data.api.Write */
27068
27069 newItem: function(/* Object? */ keywordArgs, /* Object? */ parentInfo){
27070 // summary: See dojo.data.api.Write.newItem()
27071
27072 this._assert(!this._saveInProgress);
27073
27074 if(!this._loadFinished){
27075 // We need to do this here so that we'll be able to find out what
27076 // identifierAttribute was specified in the data file.
27077 this._forceLoad();
27078 }
27079
27080 if(typeof keywordArgs != "object" && typeof keywordArgs != "undefined"){
27081 throw new Error("newItem() was passed something other than an object");
27082 }
27083 var newIdentity = null;
27084 var identifierAttribute = this._getIdentifierAttribute();
27085 if(identifierAttribute === Number){
27086 newIdentity = this._arrayOfAllItems.length;
27087 }else{
27088 newIdentity = keywordArgs[identifierAttribute];
27089 if(typeof newIdentity === "undefined"){
27090 throw new Error("newItem() was not passed an identity for the new item");
27091 }
27092 if(dojo.isArray(newIdentity)){
27093 throw new Error("newItem() was not passed an single-valued identity");
27094 }
27095 }
27096
81bea17a
AD
27097 // make sure this identity is not already in use by another item, if identifiers were
27098 // defined in the file. Otherwise it would be the item count,
a089699c
AD
27099 // which should always be unique in this case.
27100 if(this._itemsByIdentity){
27101 this._assert(typeof this._itemsByIdentity[newIdentity] === "undefined");
27102 }
27103 this._assert(typeof this._pending._newItems[newIdentity] === "undefined");
27104 this._assert(typeof this._pending._deletedItems[newIdentity] === "undefined");
27105
27106 var newItem = {};
81bea17a 27107 newItem[this._storeRefPropName] = this;
a089699c
AD
27108 newItem[this._itemNumPropName] = this._arrayOfAllItems.length;
27109 if(this._itemsByIdentity){
27110 this._itemsByIdentity[newIdentity] = newItem;
27111 //We have to set the identifier now, otherwise we can't look it
27112 //up at calls to setValueorValues in parentInfo handling.
27113 newItem[identifierAttribute] = [newIdentity];
27114 }
27115 this._arrayOfAllItems.push(newItem);
27116
27117 //We need to construct some data for the onNew call too...
27118 var pInfo = null;
27119
27120 // Now we need to check to see where we want to assign this thingm if any.
27121 if(parentInfo && parentInfo.parent && parentInfo.attribute){
27122 pInfo = {
27123 item: parentInfo.parent,
27124 attribute: parentInfo.attribute,
27125 oldValue: undefined
27126 };
27127
27128 //See if it is multi-valued or not and handle appropriately
27129 //Generally, all attributes are multi-valued for this store
27130 //So, we only need to append if there are already values present.
27131 var values = this.getValues(parentInfo.parent, parentInfo.attribute);
27132 if(values && values.length > 0){
27133 var tempValues = values.slice(0, values.length);
27134 if(values.length === 1){
27135 pInfo.oldValue = values[0];
27136 }else{
27137 pInfo.oldValue = values.slice(0, values.length);
27138 }
27139 tempValues.push(newItem);
27140 this._setValueOrValues(parentInfo.parent, parentInfo.attribute, tempValues, false);
27141 pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute);
27142 }else{
27143 this._setValueOrValues(parentInfo.parent, parentInfo.attribute, newItem, false);
27144 pInfo.newValue = newItem;
27145 }
27146 }else{
27147 //Toplevel item, add to both top list as well as all list.
27148 newItem[this._rootItemPropName]=true;
27149 this._arrayOfTopLevelItems.push(newItem);
27150 }
27151
27152 this._pending._newItems[newIdentity] = newItem;
27153
27154 //Clone over the properties to the new item
27155 for(var key in keywordArgs){
27156 if(key === this._storeRefPropName || key === this._itemNumPropName){
27157 // Bummer, the user is trying to do something like
27158 // newItem({_S:"foo"}). Unfortunately, our superclass,
27159 // ItemFileReadStore, is already using _S in each of our items
81bea17a
AD
27160 // to hold private info. To avoid a naming collision, we
27161 // need to move all our private info to some other property
a089699c 27162 // of all the items/objects. So, we need to iterate over all
81bea17a 27163 // the items and do something like:
a089699c
AD
27164 // item.__S = item._S;
27165 // item._S = undefined;
81bea17a
AD
27166 // But first we have to make sure the new "__S" variable is
27167 // not in use, which means we have to iterate over all the
a089699c
AD
27168 // items checking for that.
27169 throw new Error("encountered bug in ItemFileWriteStore.newItem");
27170 }
27171 var value = keywordArgs[key];
27172 if(!dojo.isArray(value)){
27173 value = [value];
27174 }
27175 newItem[key] = value;
27176 if(this.referenceIntegrity){
27177 for(var i = 0; i < value.length; i++){
27178 var val = value[i];
27179 if(this.isItem(val)){
27180 this._addReferenceToMap(val, newItem, key);
27181 }
27182 }
27183 }
27184 }
27185 this.onNew(newItem, pInfo); // dojo.data.api.Notification call
27186 return newItem; // item
27187 },
27188
27189 _removeArrayElement: function(/* Array */ array, /* anything */ element){
27190 var index = dojo.indexOf(array, element);
27191 if(index != -1){
27192 array.splice(index, 1);
27193 return true;
27194 }
27195 return false;
27196 },
27197
27198 deleteItem: function(/* item */ item){
27199 // summary: See dojo.data.api.Write.deleteItem()
27200 this._assert(!this._saveInProgress);
27201 this._assertIsItem(item);
27202
27203 // Remove this item from the _arrayOfAllItems, but leave a null value in place
81bea17a 27204 // of the item, so as not to change the length of the array, so that in newItem()
a089699c
AD
27205 // we can still safely do: newIdentity = this._arrayOfAllItems.length;
27206 var indexInArrayOfAllItems = item[this._itemNumPropName];
27207 var identity = this.getIdentity(item);
27208
27209 //If we have reference integrity on, we need to do reference cleanup for the deleted item
27210 if(this.referenceIntegrity){
81bea17a 27211 //First scan all the attributes of this items for references and clean them up in the map
a089699c
AD
27212 //As this item is going away, no need to track its references anymore.
27213
81bea17a 27214 //Get the attributes list before we generate the backup so it
a089699c
AD
27215 //doesn't pollute the attributes list.
27216 var attributes = this.getAttributes(item);
27217
27218 //Backup the map, we'll have to restore it potentially, in a revert.
27219 if(item[this._reverseRefMap]){
27220 item["backup_" + this._reverseRefMap] = dojo.clone(item[this._reverseRefMap]);
27221 }
27222
27223 //TODO: This causes a reversion problem. This list won't be restored on revert since it is
27224 //attached to the 'value'. item, not ours. Need to back tese up somehow too.
27225 //Maybe build a map of the backup of the entries and attach it to the deleted item to be restored
27226 //later. Or just record them and call _addReferenceToMap on them in revert.
27227 dojo.forEach(attributes, function(attribute){
27228 dojo.forEach(this.getValues(item, attribute), function(value){
27229 if(this.isItem(value)){
27230 //We have to back up all the references we had to others so they can be restored on a revert.
27231 if(!item["backupRefs_" + this._reverseRefMap]){
27232 item["backupRefs_" + this._reverseRefMap] = [];
27233 }
27234 item["backupRefs_" + this._reverseRefMap].push({id: this.getIdentity(value), attr: attribute});
27235 this._removeReferenceFromMap(value, item, attribute);
27236 }
27237 }, this);
27238 }, this);
27239
27240 //Next, see if we have references to this item, if we do, we have to clean them up too.
27241 var references = item[this._reverseRefMap];
27242 if(references){
27243 //Look through all the items noted as references to clean them up.
27244 for(var itemId in references){
27245 var containingItem = null;
27246 if(this._itemsByIdentity){
27247 containingItem = this._itemsByIdentity[itemId];
27248 }else{
27249 containingItem = this._arrayOfAllItems[itemId];
27250 }
27251 //We have a reference to a containing item, now we have to process the
27252 //attributes and clear all references to the item being deleted.
27253 if(containingItem){
27254 for(var attribute in references[itemId]){
27255 var oldValues = this.getValues(containingItem, attribute) || [];
27256 var newValues = dojo.filter(oldValues, function(possibleItem){
27257 return !(this.isItem(possibleItem) && this.getIdentity(possibleItem) == identity);
27258 }, this);
27259 //Remove the note of the reference to the item and set the values on the modified attribute.
81bea17a 27260 this._removeReferenceFromMap(item, containingItem, attribute);
a089699c
AD
27261 if(newValues.length < oldValues.length){
27262 this._setValueOrValues(containingItem, attribute, newValues, true);
27263 }
27264 }
27265 }
27266 }
27267 }
27268 }
27269
27270 this._arrayOfAllItems[indexInArrayOfAllItems] = null;
27271
27272 item[this._storeRefPropName] = null;
27273 if(this._itemsByIdentity){
27274 delete this._itemsByIdentity[identity];
27275 }
27276 this._pending._deletedItems[identity] = item;
27277
27278 //Remove from the toplevel items, if necessary...
27279 if(item[this._rootItemPropName]){
27280 this._removeArrayElement(this._arrayOfTopLevelItems, item);
27281 }
27282 this.onDelete(item); // dojo.data.api.Notification call
27283 return true;
27284 },
27285
27286 setValue: function(/* item */ item, /* attribute-name-string */ attribute, /* almost anything */ value){
27287 // summary: See dojo.data.api.Write.set()
27288 return this._setValueOrValues(item, attribute, value, true); // boolean
27289 },
27290
27291 setValues: function(/* item */ item, /* attribute-name-string */ attribute, /* array */ values){
27292 // summary: See dojo.data.api.Write.setValues()
27293 return this._setValueOrValues(item, attribute, values, true); // boolean
27294 },
27295
27296 unsetAttribute: function(/* item */ item, /* attribute-name-string */ attribute){
27297 // summary: See dojo.data.api.Write.unsetAttribute()
27298 return this._setValueOrValues(item, attribute, [], true);
27299 },
27300
27301 _setValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute, /* anything */ newValueOrValues, /*boolean?*/ callOnSet){
27302 this._assert(!this._saveInProgress);
27303
27304 // Check for valid arguments
27305 this._assertIsItem(item);
27306 this._assert(dojo.isString(attribute));
27307 this._assert(typeof newValueOrValues !== "undefined");
27308
27309 // Make sure the user isn't trying to change the item's identity
27310 var identifierAttribute = this._getIdentifierAttribute();
27311 if(attribute == identifierAttribute){
27312 throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier.");
27313 }
27314
27315 // To implement the Notification API, we need to make a note of what
27316 // the old attribute value was, so that we can pass that info when
27317 // we call the onSet method.
27318 var oldValueOrValues = this._getValueOrValues(item, attribute);
27319
27320 var identity = this.getIdentity(item);
27321 if(!this._pending._modifiedItems[identity]){
81bea17a
AD
27322 // Before we actually change the item, we make a copy of it to
27323 // record the original state, so that we'll be able to revert if
a089699c
AD
27324 // the revert method gets called. If the item has already been
27325 // modified then there's no need to do this now, since we already
81bea17a 27326 // have a record of the original state.
a089699c
AD
27327 var copyOfItemState = {};
27328 for(var key in item){
27329 if((key === this._storeRefPropName) || (key === this._itemNumPropName) || (key === this._rootItemPropName)){
27330 copyOfItemState[key] = item[key];
27331 }else if(key === this._reverseRefMap){
27332 copyOfItemState[key] = dojo.clone(item[key]);
27333 }else{
27334 copyOfItemState[key] = item[key].slice(0, item[key].length);
27335 }
27336 }
27337 // Now mark the item as dirty, and save the copy of the original state
27338 this._pending._modifiedItems[identity] = copyOfItemState;
27339 }
27340
27341 // Okay, now we can actually change this attribute on the item
27342 var success = false;
27343
27344 if(dojo.isArray(newValueOrValues) && newValueOrValues.length === 0){
27345
27346 // If we were passed an empty array as the value, that counts
81bea17a 27347 // as "unsetting" the attribute, so we need to remove this
a089699c
AD
27348 // attribute from the item.
27349 success = delete item[attribute];
27350 newValueOrValues = undefined; // used in the onSet Notification call below
27351
27352 if(this.referenceIntegrity && oldValueOrValues){
27353 var oldValues = oldValueOrValues;
27354 if(!dojo.isArray(oldValues)){
27355 oldValues = [oldValues];
27356 }
27357 for(var i = 0; i < oldValues.length; i++){
27358 var value = oldValues[i];
27359 if(this.isItem(value)){
27360 this._removeReferenceFromMap(value, item, attribute);
27361 }
27362 }
27363 }
27364 }else{
27365 var newValueArray;
27366 if(dojo.isArray(newValueOrValues)){
27367 var newValues = newValueOrValues;
27368 // Unfortunately, it's not safe to just do this:
27369 // newValueArray = newValues;
27370 // Instead, we need to copy the array, which slice() does very nicely.
81bea17a 27371 // This is so that our internal data structure won't
a089699c
AD
27372 // get corrupted if the user mucks with the values array *after*
27373 // calling setValues().
27374 newValueArray = newValueOrValues.slice(0, newValueOrValues.length);
27375 }else{
27376 newValueArray = [newValueOrValues];
27377 }
27378
81bea17a 27379 //We need to handle reference integrity if this is on.
a089699c
AD
27380 //In the case of set, we need to see if references were added or removed
27381 //and update the reference tracking map accordingly.
27382 if(this.referenceIntegrity){
27383 if(oldValueOrValues){
27384 var oldValues = oldValueOrValues;
27385 if(!dojo.isArray(oldValues)){
27386 oldValues = [oldValues];
27387 }
27388 //Use an associative map to determine what was added/removed from the list.
27389 //Should be O(n) performant. First look at all the old values and make a list of them
27390 //Then for any item not in the old list, we add it. If it was already present, we remove it.
27391 //Then we pass over the map and any references left it it need to be removed (IE, no match in
27392 //the new values list).
27393 var map = {};
27394 dojo.forEach(oldValues, function(possibleItem){
27395 if(this.isItem(possibleItem)){
27396 var id = this.getIdentity(possibleItem);
27397 map[id.toString()] = true;
27398 }
27399 }, this);
27400 dojo.forEach(newValueArray, function(possibleItem){
27401 if(this.isItem(possibleItem)){
27402 var id = this.getIdentity(possibleItem);
27403 if(map[id.toString()]){
27404 delete map[id.toString()];
27405 }else{
81bea17a 27406 this._addReferenceToMap(possibleItem, item, attribute);
a089699c
AD
27407 }
27408 }
27409 }, this);
27410 for(var rId in map){
27411 var removedItem;
27412 if(this._itemsByIdentity){
27413 removedItem = this._itemsByIdentity[rId];
27414 }else{
27415 removedItem = this._arrayOfAllItems[rId];
27416 }
27417 this._removeReferenceFromMap(removedItem, item, attribute);
27418 }
27419 }else{
27420 //Everything is new (no old values) so we have to just
27421 //insert all the references, if any.
27422 for(var i = 0; i < newValueArray.length; i++){
27423 var value = newValueArray[i];
27424 if(this.isItem(value)){
27425 this._addReferenceToMap(value, item, attribute);
27426 }
27427 }
27428 }
27429 }
27430 item[attribute] = newValueArray;
27431 success = true;
27432 }
27433
27434 // Now we make the dojo.data.api.Notification call
27435 if(callOnSet){
81bea17a 27436 this.onSet(item, attribute, oldValueOrValues, newValueOrValues);
a089699c
AD
27437 }
27438 return success; // boolean
27439 },
27440
27441 _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){
27442 // summary:
27443 // Method to add an reference map entry for an item and attribute.
27444 // description:
27445 // Method to add an reference map entry for an item and attribute. //
27446 // refItem:
27447 // The item that is referenced.
27448 // parentItem:
27449 // The item that holds the new reference to refItem.
27450 // attribute:
27451 // The attribute on parentItem that contains the new reference.
27452
27453 var parentId = this.getIdentity(parentItem);
27454 var references = refItem[this._reverseRefMap];
27455
27456 if(!references){
27457 references = refItem[this._reverseRefMap] = {};
27458 }
27459 var itemRef = references[parentId];
27460 if(!itemRef){
27461 itemRef = references[parentId] = {};
27462 }
27463 itemRef[attribute] = true;
27464 },
27465
27466 _removeReferenceFromMap: function(/* item */ refItem, /* item */ parentItem, /*strin*/ attribute){
27467 // summary:
27468 // Method to remove an reference map entry for an item and attribute.
27469 // description:
27470 // Method to remove an reference map entry for an item and attribute. This will
81bea17a 27471 // also perform cleanup on the map such that if there are no more references at all to
a089699c
AD
27472 // the item, its reference object and entry are removed.
27473 //
27474 // refItem:
27475 // The item that is referenced.
27476 // parentItem:
27477 // The item holding a reference to refItem.
27478 // attribute:
27479 // The attribute on parentItem that contains the reference.
27480 var identity = this.getIdentity(parentItem);
27481 var references = refItem[this._reverseRefMap];
27482 var itemId;
27483 if(references){
27484 for(itemId in references){
27485 if(itemId == identity){
27486 delete references[itemId][attribute];
27487 if(this._isEmpty(references[itemId])){
27488 delete references[itemId];
27489 }
27490 }
27491 }
27492 if(this._isEmpty(references)){
27493 delete refItem[this._reverseRefMap];
27494 }
27495 }
27496 },
27497
27498 _dumpReferenceMap: function(){
27499 // summary:
27500 // Function to dump the reverse reference map of all items in the store for debug purposes.
27501 // description:
27502 // Function to dump the reverse reference map of all items in the store for debug purposes.
27503 var i;
27504 for(i = 0; i < this._arrayOfAllItems.length; i++){
27505 var item = this._arrayOfAllItems[i];
27506 if(item && item[this._reverseRefMap]){
27507 console.log("Item: [" + this.getIdentity(item) + "] is referenced by: " + dojo.toJson(item[this._reverseRefMap]));
27508 }
27509 }
27510 },
27511
27512 _getValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute){
27513 var valueOrValues = undefined;
27514 if(this.hasAttribute(item, attribute)){
27515 var valueArray = this.getValues(item, attribute);
27516 if(valueArray.length == 1){
27517 valueOrValues = valueArray[0];
27518 }else{
27519 valueOrValues = valueArray;
27520 }
27521 }
27522 return valueOrValues;
27523 },
27524
27525 _flatten: function(/* anything */ value){
27526 if(this.isItem(value)){
27527 var item = value;
81bea17a 27528 // Given an item, return an serializable object that provides a
a089699c
AD
27529 // reference to the item.
27530 // For example, given kermit:
27531 // var kermit = store.newItem({id:2, name:"Kermit"});
27532 // we want to return
27533 // {_reference:2}
27534 var identity = this.getIdentity(item);
27535 var referenceObject = {_reference: identity};
27536 return referenceObject;
27537 }else{
27538 if(typeof value === "object"){
27539 for(var type in this._datatypeMap){
27540 var typeMap = this._datatypeMap[type];
27541 if(dojo.isObject(typeMap) && !dojo.isFunction(typeMap)){
27542 if(value instanceof typeMap.type){
27543 if(!typeMap.serialize){
27544 throw new Error("ItemFileWriteStore: No serializer defined for type mapping: [" + type + "]");
27545 }
27546 return {_type: type, _value: typeMap.serialize(value)};
27547 }
27548 } else if(value instanceof typeMap){
27549 //SImple mapping, therefore, return as a toString serialization.
27550 return {_type: type, _value: value.toString()};
27551 }
27552 }
27553 }
27554 return value;
27555 }
27556 },
27557
27558 _getNewFileContentString: function(){
81bea17a 27559 // summary:
a089699c
AD
27560 // Generate a string that can be saved to a file.
27561 // The result should look similar to:
27562 // http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json
27563 var serializableStructure = {};
27564
27565 var identifierAttribute = this._getIdentifierAttribute();
27566 if(identifierAttribute !== Number){
27567 serializableStructure.identifier = identifierAttribute;
27568 }
27569 if(this._labelAttr){
27570 serializableStructure.label = this._labelAttr;
27571 }
27572 serializableStructure.items = [];
27573 for(var i = 0; i < this._arrayOfAllItems.length; ++i){
27574 var item = this._arrayOfAllItems[i];
27575 if(item !== null){
27576 var serializableItem = {};
27577 for(var key in item){
27578 if(key !== this._storeRefPropName && key !== this._itemNumPropName && key !== this._reverseRefMap && key !== this._rootItemPropName){
27579 var attribute = key;
27580 var valueArray = this.getValues(item, attribute);
27581 if(valueArray.length == 1){
27582 serializableItem[attribute] = this._flatten(valueArray[0]);
27583 }else{
27584 var serializableArray = [];
27585 for(var j = 0; j < valueArray.length; ++j){
27586 serializableArray.push(this._flatten(valueArray[j]));
27587 serializableItem[attribute] = serializableArray;
27588 }
27589 }
27590 }
27591 }
27592 serializableStructure.items.push(serializableItem);
27593 }
27594 }
27595 var prettyPrint = true;
27596 return dojo.toJson(serializableStructure, prettyPrint);
27597 },
27598
27599 _isEmpty: function(something){
81bea17a 27600 // summary:
a089699c
AD
27601 // Function to determine if an array or object has no properties or values.
27602 // something:
27603 // The array or object to examine.
27604 var empty = true;
27605 if(dojo.isObject(something)){
27606 var i;
27607 for(i in something){
27608 empty = false;
27609 break;
27610 }
27611 }else if(dojo.isArray(something)){
27612 if(something.length > 0){
27613 empty = false;
27614 }
27615 }
27616 return empty; //boolean
27617 },
27618
27619 save: function(/* object */ keywordArgs){
27620 // summary: See dojo.data.api.Write.save()
27621 this._assert(!this._saveInProgress);
27622
27623 // this._saveInProgress is set to true, briefly, from when save is first called to when it completes
27624 this._saveInProgress = true;
27625
27626 var self = this;
27627 var saveCompleteCallback = function(){
27628 self._pending = {
81bea17a 27629 _newItems:{},
a089699c
AD
27630 _modifiedItems:{},
27631 _deletedItems:{}
27632 };
27633
27634 self._saveInProgress = false; // must come after this._pending is cleared, but before any callbacks
27635 if(keywordArgs && keywordArgs.onComplete){
27636 var scope = keywordArgs.scope || dojo.global;
27637 keywordArgs.onComplete.call(scope);
27638 }
27639 };
27640 var saveFailedCallback = function(err){
27641 self._saveInProgress = false;
27642 if(keywordArgs && keywordArgs.onError){
27643 var scope = keywordArgs.scope || dojo.global;
27644 keywordArgs.onError.call(scope, err);
27645 }
27646 };
27647
27648 if(this._saveEverything){
27649 var newFileContentString = this._getNewFileContentString();
27650 this._saveEverything(saveCompleteCallback, saveFailedCallback, newFileContentString);
27651 }
27652 if(this._saveCustom){
27653 this._saveCustom(saveCompleteCallback, saveFailedCallback);
27654 }
27655 if(!this._saveEverything && !this._saveCustom){
27656 // Looks like there is no user-defined save-handler function.
27657 // That's fine, it just means the datastore is acting as a "mock-write"
27658 // store -- changes get saved in memory but don't get saved to disk.
27659 saveCompleteCallback();
27660 }
27661 },
27662
27663 revert: function(){
27664 // summary: See dojo.data.api.Write.revert()
27665 this._assert(!this._saveInProgress);
27666
27667 var identity;
27668 for(identity in this._pending._modifiedItems){
27669 // find the original item and the modified item that replaced it
27670 var copyOfItemState = this._pending._modifiedItems[identity];
27671 var modifiedItem = null;
27672 if(this._itemsByIdentity){
27673 modifiedItem = this._itemsByIdentity[identity];
27674 }else{
27675 modifiedItem = this._arrayOfAllItems[identity];
27676 }
27677
81bea17a 27678 // Restore the original item into a full-fledged item again, we want to try to
a089699c
AD
27679 // keep the same object instance as if we don't it, causes bugs like #9022.
27680 copyOfItemState[this._storeRefPropName] = this;
27681 for(key in modifiedItem){
27682 delete modifiedItem[key];
27683 }
27684 dojo.mixin(modifiedItem, copyOfItemState);
27685 }
27686 var deletedItem;
27687 for(identity in this._pending._deletedItems){
27688 deletedItem = this._pending._deletedItems[identity];
27689 deletedItem[this._storeRefPropName] = this;
27690 var index = deletedItem[this._itemNumPropName];
27691
27692 //Restore the reverse refererence map, if any.
27693 if(deletedItem["backup_" + this._reverseRefMap]){
27694 deletedItem[this._reverseRefMap] = deletedItem["backup_" + this._reverseRefMap];
27695 delete deletedItem["backup_" + this._reverseRefMap];
27696 }
27697 this._arrayOfAllItems[index] = deletedItem;
27698 if(this._itemsByIdentity){
27699 this._itemsByIdentity[identity] = deletedItem;
27700 }
27701 if(deletedItem[this._rootItemPropName]){
27702 this._arrayOfTopLevelItems.push(deletedItem);
27703 }
27704 }
27705 //We have to pass through it again and restore the reference maps after all the
27706 //undeletes have occurred.
27707 for(identity in this._pending._deletedItems){
27708 deletedItem = this._pending._deletedItems[identity];
27709 if(deletedItem["backupRefs_" + this._reverseRefMap]){
27710 dojo.forEach(deletedItem["backupRefs_" + this._reverseRefMap], function(reference){
27711 var refItem;
27712 if(this._itemsByIdentity){
27713 refItem = this._itemsByIdentity[reference.id];
27714 }else{
27715 refItem = this._arrayOfAllItems[reference.id];
27716 }
27717 this._addReferenceToMap(refItem, deletedItem, reference.attr);
27718 }, this);
81bea17a 27719 delete deletedItem["backupRefs_" + this._reverseRefMap];
a089699c
AD
27720 }
27721 }
27722
27723 for(identity in this._pending._newItems){
27724 var newItem = this._pending._newItems[identity];
27725 newItem[this._storeRefPropName] = null;
27726 // null out the new item, but don't change the array index so
27727 // so we can keep using _arrayOfAllItems.length.
27728 this._arrayOfAllItems[newItem[this._itemNumPropName]] = null;
27729 if(newItem[this._rootItemPropName]){
27730 this._removeArrayElement(this._arrayOfTopLevelItems, newItem);
27731 }
27732 if(this._itemsByIdentity){
27733 delete this._itemsByIdentity[identity];
27734 }
27735 }
27736
27737 this._pending = {
81bea17a
AD
27738 _newItems:{},
27739 _modifiedItems:{},
a089699c
AD
27740 _deletedItems:{}
27741 };
27742 return true; // boolean
27743 },
27744
27745 isDirty: function(/* item? */ item){
27746 // summary: See dojo.data.api.Write.isDirty()
27747 if(item){
27748 // return true if the item is dirty
27749 var identity = this.getIdentity(item);
81bea17a 27750 return new Boolean(this._pending._newItems[identity] ||
a089699c
AD
27751 this._pending._modifiedItems[identity] ||
27752 this._pending._deletedItems[identity]).valueOf(); // boolean
27753 }else{
27754 // return true if the store is dirty -- which means return true
27755 // if there are any new items, dirty items, or modified items
81bea17a 27756 if(!this._isEmpty(this._pending._newItems) ||
a089699c
AD
27757 !this._isEmpty(this._pending._modifiedItems) ||
27758 !this._isEmpty(this._pending._deletedItems)){
27759 return true;
27760 }
27761 return false; // boolean
27762 }
27763 },
27764
27765/* dojo.data.api.Notification */
27766
81bea17a
AD
27767 onSet: function(/* item */ item,
27768 /*attribute-name-string*/ attribute,
a089699c
AD
27769 /*object | array*/ oldValue,
27770 /*object | array*/ newValue){
27771 // summary: See dojo.data.api.Notification.onSet()
27772
81bea17a 27773 // No need to do anything. This method is here just so that the
a089699c
AD
27774 // client code can connect observers to it.
27775 },
27776
27777 onNew: function(/* item */ newItem, /*object?*/ parentInfo){
27778 // summary: See dojo.data.api.Notification.onNew()
27779
81bea17a
AD
27780 // No need to do anything. This method is here just so that the
27781 // client code can connect observers to it.
a089699c
AD
27782 },
27783
27784 onDelete: function(/* item */ deletedItem){
27785 // summary: See dojo.data.api.Notification.onDelete()
27786
81bea17a
AD
27787 // No need to do anything. This method is here just so that the
27788 // client code can connect observers to it.
a089699c
AD
27789 },
27790
27791 close: function(/* object? */ request){
27792 // summary:
27793 // Over-ride of base close function of ItemFileReadStore to add in check for store state.
27794 // description:
27795 // Over-ride of base close function of ItemFileReadStore to add in check for store state.
27796 // If the store is still dirty (unsaved changes), then an error will be thrown instead of
27797 // clearing the internal state for reload from the url.
27798
27799 //Clear if not dirty ... or throw an error
27800 if(this.clearOnClose){
27801 if(!this.isDirty()){
27802 this.inherited(arguments);
27803 }else{
27804 //Only throw an error if the store was dirty and we were loading from a url (cannot reload from url until state is saved).
27805 throw new Error("dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close.");
27806 }
27807 }
27808 }
27809});
27810
27811}
27812
27813
27814dojo.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"]);