diff options
Diffstat (limited to 'lib/dojo/tt-rss-layer.js.uncompressed.js')
-rw-r--r-- | lib/dojo/tt-rss-layer.js.uncompressed.js | 46469 |
1 files changed, 25451 insertions, 21018 deletions
diff --git a/lib/dojo/tt-rss-layer.js.uncompressed.js b/lib/dojo/tt-rss-layer.js.uncompressed.js index 81d4302df..cc43d8726 100644 --- a/lib/dojo/tt-rss-layer.js.uncompressed.js +++ b/lib/dojo/tt-rss-layer.js.uncompressed.js @@ -1,3197 +1,5521 @@ -/* - Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. - Available via Academic Free License >= 2.1 OR the modified BSD license. - see: http://dojotoolkit.org/license for details -*/ +require({cache:{ +'dijit/form/TextBox':function(){ +require({cache:{ +'url: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\" data-dojo-attach-point='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"}}); +define("dijit/form/TextBox", [ + "dojo/_base/declare", // declare + "dojo/dom-construct", // domConstruct.create + "dojo/dom-style", // domStyle.getComputedStyle + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/lang", // lang.hitch + "dojo/_base/sniff", // has("ie") has("mozilla") + "dojo/_base/window", // win.doc.selection.createRange + "./_FormValueWidget", + "./_TextBoxMixin", + "dojo/text!./templates/TextBox.html", + ".." // to export dijit._setSelectionRange, remove in 2.0 +], function(declare, domConstruct, domStyle, kernel, lang, has, win, + _FormValueWidget, _TextBoxMixin, template, dijit){ -/* - This is an optimized version of Dojo, built for deployment and not for - development. To get sources and documentation, please visit: +/*===== + var _FormValueWidget = dijit.form._FormValueWidget; + var _TextBoxMixin = dijit.form._TextBoxMixin; +=====*/ - http://dojotoolkit.org -*/ + // module: + // dijit/form/TextBox + // summary: + // A base class for textbox form inputs -dojo.provide("tt-rss-layer"); -if(!dojo._hasResource["dojo.date.stamp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.date.stamp"] = true; -dojo.provide("dojo.date.stamp"); + var TextBox = declare(/*====="dijit.form.TextBox", =====*/ [_FormValueWidget, _TextBoxMixin], { + // summary: + // A base class for textbox form inputs -dojo.getObject("date.stamp", true, dojo); + templateString: template, + _singleNodeTemplate: '<input class="dijit dijitReset dijitLeft dijitInputField" data-dojo-attach-point="textbox,focusNode" autocomplete="off" type="${type}" ${!nameAttrSetting} />', -// Methods to convert dates to or from a wire (string) format using well-known conventions + _buttonInputDisabled: has("ie") ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events -dojo.date.stamp.fromISOString = function(/*String*/formattedString, /*Number?*/defaultTime){ - // summary: - // Returns a Date object given a string formatted according to a subset of the ISO-8601 standard. - // - // description: - // Accepts a string formatted according to a profile of ISO8601 as defined by - // [RFC3339](http://www.ietf.org/rfc/rfc3339.txt), except that partial input is allowed. - // Can also process dates as specified [by the W3C](http://www.w3.org/TR/NOTE-datetime) - // The following combinations are valid: - // - // * dates only - // | * yyyy - // | * yyyy-MM - // | * yyyy-MM-dd - // * times only, with an optional time zone appended - // | * THH:mm - // | * THH:mm:ss - // | * THH:mm:ss.SSS - // * and "datetimes" which could be any combination of the above - // - // timezones may be specified as Z (for UTC) or +/- followed by a time expression HH:mm - // Assumes the local time zone if not specified. Does not validate. Improperly formatted - // input may return null. Arguments which are out of bounds will be handled - // by the Date constructor (e.g. January 32nd typically gets resolved to February 1st) - // Only years between 100 and 9999 are supported. - // - // formattedString: - // A string such as 2005-06-30T08:05:00-07:00 or 2005-06-30 or T08:05:00 - // - // defaultTime: - // Used for defaults for fields omitted in the formattedString. - // Uses 1970-01-01T00:00:00.0Z by default. + baseClass: "dijitTextBox", - if(!dojo.date.stamp._isoRegExp){ - dojo.date.stamp._isoRegExp = -//TODO: could be more restrictive and check for 00-59, etc. - /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/; - } + postMixInProperties: function(){ + var type = this.type.toLowerCase(); + if(this.templateString && this.templateString.toLowerCase() == "input" || ((type == "hidden" || type == "file") && this.templateString == this.constructor.prototype.templateString)){ + this.templateString = this._singleNodeTemplate; + } + this.inherited(arguments); + }, - var match = dojo.date.stamp._isoRegExp.exec(formattedString), - result = null; + _onInput: function(e){ + this.inherited(arguments); + if(this.intermediateChanges){ // _TextBoxMixin uses onInput + var _this = this; + // the setTimeout allows the key to post to the widget input box + setTimeout(function(){ _this._handleOnChange(_this.get('value'), false); }, 0); + } + }, - if(match){ - match.shift(); - if(match[1]){match[1]--;} // Javascript Date months are 0-based - if(match[6]){match[6] *= 1000;} // Javascript Date expects fractional seconds as milliseconds + _setPlaceHolderAttr: function(v){ + this._set("placeHolder", v); + if(!this._phspan){ + this._attachPoints.push('_phspan'); + // dijitInputField class gives placeHolder same padding as the input field + // parent node already has dijitInputField class but it doesn't affect this <span> + // since it's position: absolute. + this._phspan = domConstruct.create('span',{className:'dijitPlaceHolder dijitInputField'},this.textbox,'after'); + } + this._phspan.innerHTML=""; + this._phspan.appendChild(document.createTextNode(v)); + this._updatePlaceHolder(); + }, - if(defaultTime){ - // mix in defaultTime. Relatively expensive, so use || operators for the fast path of defaultTime === 0 - defaultTime = new Date(defaultTime); - dojo.forEach(dojo.map(["FullYear", "Month", "Date", "Hours", "Minutes", "Seconds", "Milliseconds"], function(prop){ - return defaultTime["get" + prop](); - }), function(value, index){ - match[index] = match[index] || value; - }); - } - 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 - if(match[0] < 100){ - result.setFullYear(match[0] || 1970); - } + _updatePlaceHolder: function(){ + if(this._phspan){ + this._phspan.style.display=(this.placeHolder&&!this.focused&&!this.textbox.value)?"":"none"; + } + }, - var offset = 0, - zoneSign = match[7] && match[7].charAt(0); - if(zoneSign != 'Z'){ - offset = ((match[8] || 0) * 60) + (Number(match[9]) || 0); - if(zoneSign != '-'){ offset *= -1; } - } - if(zoneSign){ - offset -= result.getTimezoneOffset(); - } - if(offset){ - result.setTime(result.getTime() + offset * 60000); - } - } + _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){ + this.inherited(arguments); + this._updatePlaceHolder(); + }, - return result; // Date or null -}; + getDisplayedValue: function(){ + // summary: + // Deprecated. Use get('displayedValue') instead. + // tags: + // deprecated + kernel.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use set('displayedValue') instead.", "", "2.0"); + return this.get('displayedValue'); + }, -/*===== - dojo.date.stamp.__Options = function(){ - // selector: String - // "date" or "time" for partial formatting of the Date object. - // Both date and time will be formatted by default. - // zulu: Boolean - // if true, UTC/GMT is used for a timezone - // milliseconds: Boolean - // if true, output milliseconds - this.selector = selector; - this.zulu = zulu; - this.milliseconds = milliseconds; - } -=====*/ + setDisplayedValue: function(/*String*/ value){ + // summary: + // Deprecated. Use set('displayedValue', ...) instead. + // tags: + // deprecated + kernel.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use set('displayedValue', ...) instead.", "", "2.0"); + this.set('displayedValue', value); + }, -dojo.date.stamp.toISOString = function(/*Date*/dateObject, /*dojo.date.stamp.__Options?*/options){ - // summary: - // Format a Date object as a string according a subset of the ISO-8601 standard - // - // description: - // When options.selector is omitted, output follows [RFC3339](http://www.ietf.org/rfc/rfc3339.txt) - // The local time zone is included as an offset from GMT, except when selector=='time' (time without a date) - // Does not check bounds. Only years between 100 and 9999 are supported. - // - // dateObject: - // A Date object + _onBlur: function(e){ + if(this.disabled){ return; } + this.inherited(arguments); + this._updatePlaceHolder(); + }, - var _ = function(n){ return (n < 10) ? "0" + n : n; }; - options = options || {}; - var formattedDate = [], - getter = options.zulu ? "getUTC" : "get", - date = ""; - if(options.selector != "time"){ - var year = dateObject[getter+"FullYear"](); - date = ["0000".substr((year+"").length)+year, _(dateObject[getter+"Month"]()+1), _(dateObject[getter+"Date"]())].join('-'); - } - formattedDate.push(date); - if(options.selector != "date"){ - var time = [_(dateObject[getter+"Hours"]()), _(dateObject[getter+"Minutes"]()), _(dateObject[getter+"Seconds"]())].join(':'); - var millis = dateObject[getter+"Milliseconds"](); - if(options.milliseconds){ - time += "."+ (millis < 100 ? "0" : "") + _(millis); + _onFocus: function(/*String*/ by){ + if(this.disabled || this.readOnly){ return; } + this.inherited(arguments); + this._updatePlaceHolder(); } - if(options.zulu){ - time += "Z"; - }else if(options.selector != "time"){ - var timezoneOffset = dateObject.getTimezoneOffset(); - var absOffset = Math.abs(timezoneOffset); - time += (timezoneOffset > 0 ? "-" : "+") + - _(Math.floor(absOffset/60)) + ":" + _(absOffset%60); + }); + + if(has("ie")){ + TextBox = declare(/*===== "dijit.form.TextBox.IEMixin", =====*/ TextBox, { + declaredClass: "dijit.form.TextBox", // for user code referencing declaredClass + + _isTextSelected: function(){ + var range = win.doc.selection.createRange(); + var parent = range.parentElement(); + return parent == this.textbox && range.text.length == 0; + }, + + postCreate: function(){ + this.inherited(arguments); + // IE INPUT tag fontFamily has to be set directly using STYLE + // the setTimeout gives IE a chance to render the TextBox and to deal with font inheritance + setTimeout(lang.hitch(this, function(){ + try{ + var s = domStyle.getComputedStyle(this.domNode); // can throw an exception if widget is immediately destroyed + if(s){ + var ff = s.fontFamily; + if(ff){ + var inputs = this.domNode.getElementsByTagName("INPUT"); + if(inputs){ + for(var i=0; i < inputs.length; i++){ + inputs[i].style.fontFamily = ff; + } + } + } + } + }catch(e){/*when used in a Dialog, and this is called before the dialog is + shown, s.fontFamily would trigger "Invalid Argument" error.*/} + }), 0); + } + }); + + // Overrides definition of _setSelectionRange from _TextBoxMixin (TODO: move to _TextBoxMixin.js?) + dijit._setSelectionRange = _TextBoxMixin._setSelectionRange = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){ + if(element.createTextRange){ + var r = element.createTextRange(); + r.collapse(true); + r.moveStart("character", -99999); // move to 0 + r.moveStart("character", start); // delta from 0 is the correct position + r.moveEnd("character", stop-start); + r.select(); + } } - formattedDate.push(time); + }else if(has("mozilla")){ + TextBox = declare(/*===== "dijit.form.TextBox.MozMixin", =====*/TextBox, { + declaredClass: "dijit.form.TextBox", // for user code referencing declaredClass + + _onBlur: function(e){ + this.inherited(arguments); + if(this.selectOnClick){ + // clear selection so that the next mouse click doesn't reselect + this.textbox.selectionStart = this.textbox.selectionEnd = undefined; + } + } + }); + }else{ + TextBox.prototype.declaredClass = "dijit.form.TextBox"; } - return formattedDate.join('T'); // String -}; + lang.setObject("dijit.form.TextBox", TextBox); // don't do direct assignment, it confuses API doc parser -} + return TextBox; +}); -if(!dojo._hasResource["dojo.parser"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.parser"] = true; -dojo.provide("dojo.parser"); +}, +'dijit/_base/scroll':function(){ +define("dijit/_base/scroll", [ + "dojo/window", // windowUtils.scrollIntoView + ".." // export symbol to dijit +], function(windowUtils, dijit){ + // module: + // dijit/_base/scroll + // summary: + // Back compatibility module, new code should use windowUtils directly instead of using this module. + dijit.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){ + // summary: + // Scroll the passed node into view, if it is not already. + // Deprecated, use `windowUtils.scrollIntoView` instead. + windowUtils.scrollIntoView(node, pos); + }; +}); -new Date("X"); // workaround for #11279, new Date("") == NaN +}, +'dijit/_TemplatedMixin':function(){ +define("dijit/_TemplatedMixin", [ + "dojo/_base/lang", // lang.getObject + "dojo/touch", + "./_WidgetBase", + "dojo/string", // string.substitute string.trim + "dojo/cache", // dojo.cache + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dom-construct", // domConstruct.destroy, domConstruct.toDom + "dojo/_base/sniff", // has("ie") + "dojo/_base/unload", // unload.addOnWindowUnload + "dojo/_base/window" // win.doc +], function(lang, touch, _WidgetBase, string, cache, array, declare, domConstruct, has, unload, win) { -dojo.parser = new function(){ - // summary: - // The Dom/Widget parsing package +/*===== + var _WidgetBase = dijit._WidgetBase; +=====*/ - var d = dojo; + // module: + // dijit/_TemplatedMixin + // summary: + // Mixin for widgets that are instantiated from a template - function val2type(/*Object*/ value){ + var _TemplatedMixin = declare("dijit._TemplatedMixin", null, { // summary: - // Returns name of type of given value. + // Mixin for widgets that are instantiated from a template - if(d.isString(value)){ return "string"; } - if(typeof value == "number"){ return "number"; } - if(typeof value == "boolean"){ return "boolean"; } - if(d.isFunction(value)){ return "function"; } - if(d.isArray(value)){ return "array"; } // typeof [] == "object" - if(value instanceof Date) { return "date"; } // assume timestamp - if(value instanceof d._Url){ return "url"; } - return "object"; - } + // templateString: [protected] String + // A string that represents the widget template. + // Use in conjunction with dojo.cache() to load from a file. + templateString: null, - function str2obj(/*String*/ value, /*String*/ type){ - // summary: - // Convert given string value to given type - switch(type){ - case "string": - return value; - case "number": - return value.length ? Number(value) : NaN; - case "boolean": - // for checked/disabled value might be "" or "checked". interpret as true. - return typeof value == "boolean" ? value : !(value.toLowerCase()=="false"); - case "function": - if(d.isFunction(value)){ - // IE gives us a function, even when we say something like onClick="foo" - // (in which case it gives us an invalid function "function(){ foo }"). - // Therefore, convert to string - value=value.toString(); - value=d.trim(value.substring(value.indexOf('{')+1, value.length-1)); - } - try{ - if(value === "" || value.search(/[^\w\.]+/i) != -1){ - // The user has specified some text for a function like "return x+5" - return new Function(value); - }else{ - // The user has specified the name of a function like "myOnClick" - // or a single word function "return" - return d.getObject(value, false) || new Function(value); - } - }catch(e){ return new Function(); } - case "array": - return value ? value.split(/\s*,\s*/) : []; - case "date": - switch(value){ - case "": return new Date(""); // the NaN of dates - case "now": return new Date(); // current date - default: return d.date.stamp.fromISOString(value); - } - case "url": - return d.baseUrl + value; - default: - return d.fromJson(value); - } - } + // templatePath: [protected deprecated] String + // Path to template (HTML file) for this widget relative to dojo.baseUrl. + // Deprecated: use templateString with require([... "dojo/text!..."], ...) instead + templatePath: null, - var dummyClass = {}, instanceClasses = { - // map from fully qualified name (like "dijit.Button") to structure like - // { cls: dijit.Button, params: {label: "string", disabled: "boolean"} } - }; + // skipNodeCache: [protected] Boolean + // If using a cached widget template nodes poses issues for a + // particular widget class, it can set this property to ensure + // that its template is always re-built from a string + _skipNodeCache: false, - // Widgets like BorderContainer add properties to _Widget via dojo.extend(). - // If BorderContainer is loaded after _Widget's parameter list has been cached, - // we need to refresh that parameter list (for _Widget and all widgets that extend _Widget). - // TODO: remove this in 2.0, when we stop caching parameters. - d.connect(d, "extend", function(){ - instanceClasses = {}; - }); + // _earlyTemplatedStartup: Boolean + // A fallback to preserve the 1.0 - 1.3 behavior of children in + // templates having their startup called before the parent widget + // fires postCreate. Defaults to 'false', causing child widgets to + // have their .startup() called immediately before a parent widget + // .startup(), but always after the parent .postCreate(). Set to + // 'true' to re-enable to previous, arguably broken, behavior. + _earlyTemplatedStartup: false, - function getProtoInfo(cls, params){ - // cls: A prototype - // The prototype of the class to check props on - // params: Object - // The parameters object to mix found parameters onto. - for(var name in cls){ - if(name.charAt(0)=="_"){ continue; } // skip internal properties - if(name in dummyClass){ continue; } // skip "constructor" and "toString" - params[name] = val2type(cls[name]); - } - return params; - } +/*===== + // _attachPoints: [private] String[] + // List of widget attribute names associated with data-dojo-attach-point=... in the + // template, ex: ["containerNode", "labelNode"] + _attachPoints: [], + =====*/ - function getClassInfo(/*String*/ className, /*Boolean*/ skipParamsLookup){ - // summary: - // Maps a widget name string like "dijit.form.Button" to the widget constructor itself, - // and a list of that widget's parameters and their types - // className: - // fully qualified name (like "dijit.form.Button") - // returns: - // structure like - // { - // cls: dijit.Button, - // params: { label: "string", disabled: "boolean"} - // } - - var c = instanceClasses[className]; - if(!c){ - // get pointer to widget class - var cls = d.getObject(className), params = null; - if(!cls){ return null; } // class not defined [yet] - if(!skipParamsLookup){ // from fastpath, we don't need to lookup the attrs on the proto because they are explicit - params = getProtoInfo(cls.prototype, {}) - } - c = { cls: cls, params: params }; - - }else if(!skipParamsLookup && !c.params){ - // if we're calling getClassInfo and have a cls proto, but no params info, scan that cls for params now - // and update the pointer in instanceClasses[className]. This happens when a widget appears in another - // widget's template which still uses dojoType, but an instance of the widget appears prior with a data-dojo-type, - // skipping this lookup the first time. - c.params = getProtoInfo(c.cls.prototype, {}); - } - - return c; - } +/*===== + // _attachEvents: [private] Handle[] + // List of connections associated with data-dojo-attach-event=... in the + // template + _attachEvents: [], + =====*/ - this._functionFromScript = function(script, attrData){ - // summary: - // Convert a <script type="dojo/method" args="a, b, c"> ... </script> - // into a function - // script: DOMNode - // The <script> DOMNode - // attrData: String - // For HTML5 compliance, searches for attrData + "args" (typically - // "data-dojo-args") instead of "args" - var preamble = ""; - var suffix = ""; - var argsStr = (script.getAttribute(attrData + "args") || script.getAttribute("args")); - if(argsStr){ - d.forEach(argsStr.split(/\s*,\s*/), function(part, idx){ - preamble += "var "+part+" = arguments["+idx+"]; "; - }); - } - var withStr = script.getAttribute("with"); - if(withStr && withStr.length){ - d.forEach(withStr.split(/\s*,\s*/), function(part){ - preamble += "with("+part+"){"; - suffix += "}"; - }); - } - return new Function(preamble+script.innerHTML+suffix); - }; + constructor: function(){ + this._attachPoints = []; + this._attachEvents = []; + }, - this.instantiate = function(/* Array */nodes, /* Object? */mixin, /* Object? */args){ - // summary: - // Takes array of nodes, and turns them into class instances and - // potentially calls a startup method to allow them to connect with - // any children. - // nodes: Array - // Array of nodes or objects like - // | { - // | type: "dijit.form.Button", - // | node: DOMNode, - // | scripts: [ ... ], // array of <script type="dojo/..."> children of node - // | inherited: { ... } // settings inherited from ancestors like dir, theme, etc. - // | } - // mixin: Object? - // An object that will be mixed in with each node in the array. - // Values in the mixin will override values in the node, if they - // exist. - // args: Object? - // An object used to hold kwArgs for instantiation. - // See parse.args argument for details. + _stringRepl: function(tmpl){ + // summary: + // Does substitution of ${foo} type properties in template string + // tags: + // private + var className = this.declaredClass, _this = this; + // Cache contains a string because we need to do property replacement + // do the property replacement + return string.substitute(tmpl, this, function(value, key){ + if(key.charAt(0) == '!'){ value = lang.getObject(key.substr(1), false, _this); } + if(typeof value == "undefined"){ throw new Error(className+" template:"+key); } // a debugging aide + if(value == null){ return ""; } - var thelist = [], - mixin = mixin||{}; - args = args||{}; + // Substitution keys beginning with ! will skip the transform step, + // in case a user wishes to insert unescaped markup, e.g. ${!foo} + return key.charAt(0) == "!" ? value : + // Safer substitution, see heading "Attribute values" in + // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 + value.toString().replace(/"/g,"""); //TODO: add &? use encodeXML method? + }, this); + }, - // TODO: for 2.0 default to data-dojo- regardless of scopeName (or maybe scopeName won't exist in 2.0) - var attrName = (args.scope || d._scopeName) + "Type", // typically "dojoType" - attrData = "data-" + (args.scope || d._scopeName) + "-"; // typically "data-dojo-" + buildRendering: function(){ + // summary: + // Construct the UI for this widget from a template, setting this.domNode. + // tags: + // protected - d.forEach(nodes, function(obj){ - if(!obj){ return; } + if(!this.templateString){ + this.templateString = cache(this.templatePath, {sanitize: true}); + } + + // Lookup cached version of template, and download to cache if it + // isn't there already. Returns either a DomNode or a string, depending on + // whether or not the template contains ${foo} replacement parameters. + var cached = _TemplatedMixin.getCachedTemplate(this.templateString, this._skipNodeCache); - // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc. - var node, type, clsInfo, clazz, scripts, fastpath; - if(obj.node){ - // new format of nodes[] array, object w/lots of properties pre-computed for me - node = obj.node; - type = obj.type; - fastpath = obj.fastpath; - clsInfo = obj.clsInfo || (type && getClassInfo(type, fastpath)); - clazz = clsInfo && clsInfo.cls; - scripts = obj.scripts; + var node; + if(lang.isString(cached)){ + node = domConstruct.toDom(this._stringRepl(cached)); + if(node.nodeType != 1){ + // Flag common problems such as templates with multiple top level nodes (nodeType == 11) + throw new Error("Invalid template: " + cached); + } }else{ - // old (backwards compatible) format of nodes[] array, simple array of DOMNodes. no fastpath/data-dojo-type support here. - node = obj; - type = attrName in mixin ? mixin[attrName] : node.getAttribute(attrName); - clsInfo = type && getClassInfo(type); - clazz = clsInfo && clsInfo.cls; - scripts = (clazz && (clazz._noScript || clazz.prototype._noScript) ? [] : - d.query("> script[type^='dojo/']", node)); - } - if(!clsInfo){ - throw new Error("Could not load class '" + type); + // if it's a node, all we have to do is clone it + node = cached.cloneNode(true); } - // Setup hash to hold parameter settings for this widget. Start with the parameter - // settings inherited from ancestors ("dir" and "lang"). - // Inherited setting may later be overridden by explicit settings on node itself. - var params = {}; - - if(args.defaults){ - // settings for the document itself (or whatever subtree is being parsed) - d._mixin(params, args.defaults); - } - if(obj.inherited){ - // settings from dir=rtl or lang=... on a node above this node - d._mixin(params, obj.inherited); - } - - // mix things found in data-dojo-props into the params - if(fastpath){ - var extra = node.getAttribute(attrData + "props"); - if(extra && extra.length){ - try{ - extra = d.fromJson.call(args.propsThis, "{" + extra + "}"); - d._mixin(params, extra); - }catch(e){ - // give the user a pointer to their invalid parameters. FIXME: can we kill this in production? - throw new Error(e.toString() + " in data-dojo-props='" + extra + "'"); - } + this.domNode = node; + + // Call down to _Widget.buildRendering() to get base classes assigned + // TODO: change the baseClass assignment to _setBaseClassAttr + this.inherited(arguments); + + // recurse through the node, looking for, and attaching to, our + // attachment points and events, which should be defined on the template node. + this._attachTemplateNodes(node, function(n,p){ return n.getAttribute(p); }); + + this._beforeFillContent(); // hook for _WidgetsInTemplateMixin + + this._fillContent(this.srcNodeRef); + }, + + _beforeFillContent: function(){ + }, + + _fillContent: function(/*DomNode*/ source){ + // summary: + // Relocate source contents to templated container node. + // this.containerNode must be able to receive children, or exceptions will be thrown. + // tags: + // protected + var dest = this.containerNode; + if(source && dest){ + while(source.hasChildNodes()){ + dest.appendChild(source.firstChild); } + } + }, + + _attachTemplateNodes: function(rootNode, getAttrFunc){ + // summary: + // Iterate through the template and attach functions and nodes accordingly. + // Alternately, if rootNode is an array of widgets, then will process data-dojo-attach-point + // etc. for those widgets. + // description: + // Map widget properties and functions to the handlers specified in + // the dom node and it's descendants. This function iterates over all + // nodes and looks for these properties: + // * dojoAttachPoint/data-dojo-attach-point + // * dojoAttachEvent/data-dojo-attach-event + // rootNode: DomNode|Widget[] + // the node to search for properties. All children will be searched. + // getAttrFunc: Function + // a function which will be used to obtain property for a given + // DomNode/Widget + // tags: + // private - // For the benefit of _Templated, check if node has data-dojo-attach-point/data-dojo-attach-event - // and mix those in as though they were parameters - var attachPoint = node.getAttribute(attrData + "attach-point"); + var nodes = lang.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*")); + var x = lang.isArray(rootNode) ? 0 : -1; + for(; x<nodes.length; x++){ + var baseNode = (x == -1) ? rootNode : nodes[x]; + if(this.widgetsInTemplate && (getAttrFunc(baseNode, "dojoType") || getAttrFunc(baseNode, "data-dojo-type"))){ + continue; + } + // Process data-dojo-attach-point + var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point"); if(attachPoint){ - params.dojoAttachPoint = attachPoint; + var point, points = attachPoint.split(/\s*,\s*/); + while((point = points.shift())){ + if(lang.isArray(this[point])){ + this[point].push(baseNode); + }else{ + this[point]=baseNode; + } + this._attachPoints.push(point); + } } - var attachEvent = node.getAttribute(attrData + "attach-event"); + + // Process data-dojo-attach-event + var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event"); if(attachEvent){ - params.dojoAttachEvent = attachEvent; - } - dojo.mixin(params, mixin); - }else{ - // FIXME: we need something like "deprecateOnce()" to throw dojo.deprecation for something. - // remove this logic in 2.0 - // read parameters (ie, attributes) specified on DOMNode - - var attributes = node.attributes; - - // clsInfo.params lists expected params like {"checked": "boolean", "n": "number"} - for(var name in clsInfo.params){ - var item = name in mixin ? { value:mixin[name], specified:true } : attributes.getNamedItem(name); - if(!item || (!item.specified && (!dojo.isIE || name.toLowerCase()!="value"))){ continue; } - var value = item.value; - // Deal with IE quirks for 'class' and 'style' - switch(name){ - case "class": - value = "className" in mixin ? mixin.className : node.className; - break; - case "style": - value = "style" in mixin ? mixin.style : (node.style && node.style.cssText); // FIXME: Opera? - } - var _type = clsInfo.params[name]; - if(typeof value == "string"){ - params[name] = str2obj(value, _type); - }else{ - params[name] = value; + // NOTE: we want to support attributes that have the form + // "domEvent: nativeEvent; ..." + var event, events = attachEvent.split(/\s*,\s*/); + var trim = lang.trim; + while((event = events.shift())){ + if(event){ + var thisFunc = null; + if(event.indexOf(":") != -1){ + // oh, if only JS had tuple assignment + var funcNameArr = event.split(":"); + event = trim(funcNameArr[0]); + thisFunc = trim(funcNameArr[1]); + }else{ + event = trim(event); + } + if(!thisFunc){ + thisFunc = event; + } + // Map "press", "move" and "release" to keys.touch, keys.move, keys.release + this._attachEvents.push(this.connect(baseNode, touch[event] || event, thisFunc)); + } } } } + }, - // Process <script type="dojo/*"> script tags - // <script type="dojo/method" event="foo"> tags are added to params, and passed to - // the widget on instantiation. - // <script type="dojo/method"> tags (with no event) are executed after instantiation - // <script type="dojo/connect" event="foo"> tags are dojo.connected after instantiation - // note: dojo/* script tags cannot exist in self closing widgets, like <input /> - var connects = [], // functions to connect after instantiation - calls = []; // functions to call after instantiation - - d.forEach(scripts, function(script){ - node.removeChild(script); - // FIXME: drop event="" support in 2.0. use data-dojo-event="" instead - var event = (script.getAttribute(attrData + "event") || script.getAttribute("event")), - type = script.getAttribute("type"), - nf = d.parser._functionFromScript(script, attrData); - if(event){ - if(type == "dojo/connect"){ - connects.push({event: event, func: nf}); - }else{ - params[event] = nf; - } - }else{ - calls.push(nf); + destroyRendering: function(){ + // Delete all attach points to prevent IE6 memory leaks. + array.forEach(this._attachPoints, function(point){ + delete this[point]; + }, this); + this._attachPoints = []; + + // And same for event handlers + array.forEach(this._attachEvents, this.disconnect, this); + this._attachEvents = []; + + this.inherited(arguments); + } + }); + + // key is templateString; object is either string or DOM tree + _TemplatedMixin._templateCache = {}; + + _TemplatedMixin.getCachedTemplate = function(templateString, alwaysUseString){ + // summary: + // Static method to get a template based on the templatePath or + // templateString key + // templateString: String + // The template + // alwaysUseString: Boolean + // Don't cache the DOM tree for this template, even if it doesn't have any variables + // returns: Mixed + // Either string (if there are ${} variables that need to be replaced) or just + // a DOM tree (if the node can be cloned directly) + + // is it already cached? + var tmplts = _TemplatedMixin._templateCache; + var key = templateString; + var cached = tmplts[key]; + if(cached){ + try{ + // 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 + if(!cached.ownerDocument || cached.ownerDocument == win.doc){ + // string or node of the same document + return cached; } - }); + }catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded + domConstruct.destroy(cached); + } - var markupFactory = clazz.markupFactory || clazz.prototype && clazz.prototype.markupFactory; - // create the instance - var instance = markupFactory ? markupFactory(params, node, clazz) : new clazz(params, node); - thelist.push(instance); + templateString = string.trim(templateString); - // map it to the JS namespace if that makes sense - // FIXME: in 2.0, drop jsId support. use data-dojo-id instead - var jsname = (node.getAttribute(attrData + "id") || node.getAttribute("jsId")); - if(jsname){ - d.setObject(jsname, instance); + if(alwaysUseString || templateString.match(/\$\{([^\}]+)\}/g)){ + // there are variables in the template so all we can do is cache the string + return (tmplts[key] = templateString); //String + }else{ + // there are no variables in the template so we can cache the DOM tree + var node = domConstruct.toDom(templateString); + if(node.nodeType != 1){ + throw new Error("Invalid template: " + templateString); } + return (tmplts[key] = node); //Node + } + }; - // process connections and startup functions - d.forEach(connects, function(connect){ - d.connect(instance, connect.event, null, connect.func); - }); - d.forEach(calls, function(func){ - func.call(instance); - }); + if(has("ie")){ + unload.addOnWindowUnload(function(){ + var cache = _TemplatedMixin._templateCache; + for(var key in cache){ + var value = cache[key]; + if(typeof value == "object"){ // value is either a string or a DOM node template + domConstruct.destroy(value); + } + delete cache[key]; + } }); + } - // Call startup on each top level instance if it makes sense (as for - // widgets). Parent widgets will recursively call startup on their - // (non-top level) children - if(!mixin._started){ - // TODO: for 2.0, when old instantiate() API is desupported, store parent-child - // relationships in the nodes[] array so that no getParent() call is needed. - // Note that will require a parse() call from ContentPane setting a param that the - // ContentPane is the parent widget (so that the parse doesn't call startup() on the - // ContentPane's children) - d.forEach(thelist, function(instance){ - if( !args.noStart && instance && - dojo.isFunction(instance.startup) && - !instance._started && - (!instance.getParent || !instance.getParent()) - ){ - instance.startup(); - } - }); + // These arguments can be specified for widgets which are used in templates. + // Since any widget can be specified as sub widgets in template, mix it + // into the base widget class. (This is a hack, but it's effective.) + lang.extend(_WidgetBase,{ + dojoAttachEvent: "", + dojoAttachPoint: "" + }); + + return _TemplatedMixin; +}); + +}, +'dijit/_CssStateMixin':function(){ +define("dijit/_CssStateMixin", [ + "dojo/touch", + "dojo/_base/array", // array.forEach array.map + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.toggle + "dojo/_base/lang", // lang.hitch + "dojo/_base/window" // win.body +], function(touch, array, declare, domClass, lang, win){ + +// module: +// dijit/_CssStateMixin +// summary: +// Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus +// state changes, and also higher-level state changes such becoming disabled or selected. + +return declare("dijit._CssStateMixin", [], { + // summary: + // Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus + // state changes, and also higher-level state changes such becoming disabled or selected. + // + // description: + // By mixing this class into your widget, and setting the this.baseClass attribute, it will automatically + // maintain CSS classes on the widget root node (this.domNode) depending on hover, + // active, focus, etc. state. Ex: with a baseClass of dijitButton, it will apply the classes + // dijitButtonHovered and dijitButtonActive, as the user moves the mouse over the widget and clicks it. + // + // It also sets CSS like dijitButtonDisabled based on widget semantic state. + // + // By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons + // within the widget). + + // cssStateNodes: [protected] Object + // List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus + //. + // Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names + // (like "dijitUpArrowButton"). Example: + // | { + // | "upArrowButton": "dijitUpArrowButton", + // | "downArrowButton": "dijitDownArrowButton" + // | } + // The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it + // is hovered, etc. + cssStateNodes: {}, + + // hovering: [readonly] Boolean + // True if cursor is over this widget + hovering: false, + + // active: [readonly] Boolean + // True if mouse was pressed while over this widget, and hasn't been released yet + active: false, + + _applyAttributes: function(){ + // This code would typically be in postCreate(), but putting in _applyAttributes() for + // performance: so the class changes happen before DOM is inserted into the document. + // Change back to postCreate() in 2.0. See #11635. + + this.inherited(arguments); + + // Automatically monitor mouse events (essentially :hover and :active) on this.domNode + array.forEach(["onmouseenter", "onmouseleave", touch.press], function(e){ + this.connect(this.domNode, e, "_cssMouseEvent"); + }, this); + + // Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node + array.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active"], function(attr){ + this.watch(attr, lang.hitch(this, "_setStateClass")); + }, this); + + // Events on sub nodes within the widget + for(var ap in this.cssStateNodes){ + this._trackMouseState(this[ap], this.cssStateNodes[ap]); } - return thelist; - }; + // Set state initially; there's probably no hover/active/focus state but widget might be + // disabled/readonly/checked/selected so we want to set CSS classes for those conditions. + this._setStateClass(); + }, - this.parse = function(rootNode, args){ + _cssMouseEvent: function(/*Event*/ event){ // summary: - // Scan the DOM for class instances, and instantiate them. + // Sets hovering and active properties depending on mouse state, + // which triggers _setStateClass() to set appropriate CSS classes for this.domNode. + + if(!this.disabled){ + switch(event.type){ + case "mouseenter": + case "mouseover": // generated on non-IE browsers even though we connected to mouseenter + this._set("hovering", true); + this._set("active", this._mouseDown); + break; + + case "mouseleave": + case "mouseout": // generated on non-IE browsers even though we connected to mouseleave + this._set("hovering", false); + this._set("active", false); + break; + + case "mousedown": + case "touchpress": + this._set("active", true); + this._mouseDown = true; + // Set a global event to handle mouseup, so it fires properly + // even if the cursor leaves this.domNode before the mouse up event. + // Alternately could set active=false on mouseout. + var mouseUpConnector = this.connect(win.body(), touch.release, function(){ + this._mouseDown = false; + this._set("active", false); + this.disconnect(mouseUpConnector); + }); + break; + } + } + }, + + _setStateClass: function(){ + // summary: + // Update the visual state of the widget by setting the css classes on this.domNode + // (or this.stateNode if defined) by combining this.baseClass with + // various suffixes that represent the current widget state(s). // // description: - // Search specified node (or root node) recursively for class instances, - // and instantiate them. Searches for either data-dojo-type="Class" or - // dojoType="Class" where "Class" is a a fully qualified class name, - // like `dijit.form.Button` - // - // Using `data-dojo-type`: - // Attributes using can be mixed into the parameters used to instantitate the - // Class by using a `data-dojo-props` attribute on the node being converted. - // `data-dojo-props` should be a string attribute to be converted from JSON. - // - // Using `dojoType`: - // Attributes are read from the original domNode and converted to appropriate - // types by looking up the Class prototype values. This is the default behavior - // from Dojo 1.0 to Dojo 1.5. `dojoType` support is deprecated, and will - // go away in Dojo 2.0. - // - // rootNode: DomNode? - // A default starting root node from which to start the parsing. Can be - // omitted, defaulting to the entire document. If omitted, the `args` - // object can be passed in this place. If the `args` object has a - // `rootNode` member, that is used. - // - // args: Object - // a kwArgs object passed along to instantiate() - // - // * noStart: Boolean? - // when set will prevent the parser from calling .startup() - // when locating the nodes. - // * rootNode: DomNode? - // identical to the function's `rootNode` argument, though - // allowed to be passed in via this `args object. - // * template: Boolean - // If true, ignores ContentPane's stopParser flag and parses contents inside of - // a ContentPane inside of a template. This allows dojoAttachPoint on widgets/nodes - // nested inside the ContentPane to work. - // * inherited: Object - // Hash possibly containing dir and lang settings to be applied to - // parsed widgets, unless there's another setting on a sub-node that overrides - // * scope: String - // Root for attribute names to search for. If scopeName is dojo, - // will search for data-dojo-type (or dojoType). For backwards compatibility - // reasons defaults to dojo._scopeName (which is "dojo" except when - // multi-version support is used, when it will be something like dojo16, dojo20, etc.) - // * propsThis: Object - // If specified, "this" referenced from data-dojo-props will refer to propsThis. - // Intended for use from the widgets-in-template feature of `dijit._Templated` - // - // example: - // Parse all widgets on a page: - // | dojo.parser.parse(); - // - // example: - // Parse all classes within the node with id="foo" - // | dojo.parser.parse(dojo.byId('foo')); + // In the case where a widget has multiple + // states, it sets the class based on all possible + // combinations. For example, an invalid form widget that is being hovered + // will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover". // - // example: - // Parse all classes in a page, but do not call .startup() on any - // child - // | dojo.parser.parse({ noStart: true }) + // The widget may have one or more of the following states, determined + // by this.state, this.checked, this.valid, and this.selected: + // - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid + // - Incomplete - ValidationTextBox sets this.state to "Incomplete" if the current input value is not finished yet + // - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true + // - Selected - ex: currently selected tab will have this.selected==true // - // example: - // Parse all classes in a node, but do not call .startup() - // | dojo.parser.parse(someNode, { noStart:true }); - // | // or - // | dojo.parser.parse({ noStart:true, rootNode: someNode }); + // In addition, it may have one or more of the following states, + // based on this.disabled and flags set in _onMouse (this.active, this.hovering) and from focus manager (this.focused): + // - Disabled - if the widget is disabled + // - Active - if the mouse (or space/enter key?) is being pressed down + // - Focused - if the widget has focus + // - Hover - if the mouse is over the widget - // determine the root node based on the passed arguments. - var root; - if(!args && rootNode && rootNode.rootNode){ - args = rootNode; - root = args.rootNode; + // Compute new set of classes + var newStateClasses = this.baseClass.split(" "); + + function multiply(modifier){ + newStateClasses = newStateClasses.concat(array.map(newStateClasses, function(c){ return c+modifier; }), "dijit"+modifier); + } + + if(!this.isLeftToRight()){ + // For RTL mode we need to set an addition class like dijitTextBoxRtl. + multiply("Rtl"); + } + + var checkedState = this.checked == "mixed" ? "Mixed" : (this.checked ? "Checked" : ""); + if(this.checked){ + multiply(checkedState); + } + if(this.state){ + multiply(this.state); + } + if(this.selected){ + multiply("Selected"); + } + + if(this.disabled){ + multiply("Disabled"); + }else if(this.readOnly){ + multiply("ReadOnly"); }else{ - root = rootNode; + if(this.active){ + multiply("Active"); + }else if(this.hovering){ + multiply("Hover"); + } } - root = root ? dojo.byId(root) : dojo.body(); - args = args || {}; - var attrName = (args.scope || d._scopeName) + "Type", // typically "dojoType" - attrData = "data-" + (args.scope || d._scopeName) + "-"; // typically "data-dojo-" - - function scan(parent, list){ - // summary: - // Parent is an Object representing a DOMNode, with or without a dojoType specified. - // Scan parent's children looking for nodes with dojoType specified, storing in list[]. - // If parent has a dojoType, also collects <script type=dojo/*> children and stores in parent.scripts[]. - // parent: Object - // Object representing the parent node, like - // | { - // | node: DomNode, // scan children of this node - // | inherited: {dir: "rtl"}, // dir/lang setting inherited from above node - // | - // | // attributes only set if node has dojoType specified - // | scripts: [], // empty array, put <script type=dojo/*> in here - // | clsInfo: { cls: dijit.form.Button, ...} - // | } - // list: DomNode[] - // Output array of objects (same format as parent) representing nodes to be turned into widgets - - // Effective dir and lang settings on parent node, either set directly or inherited from grandparent - var inherited = dojo.clone(parent.inherited); - dojo.forEach(["dir", "lang"], function(name){ - // TODO: what if this is a widget and dir/lang are declared in data-dojo-props? - var val = parent.node.getAttribute(name); - if(val){ - inherited[name] = val; - } - }); + if(this.focused){ + multiply("Focused"); + } - // if parent is a widget, then search for <script type=dojo/*> tags and put them in scripts[]. - var scripts = parent.clsInfo && !parent.clsInfo.cls.prototype._noScript ? parent.scripts : null; + // Remove old state classes and add new ones. + // For performance concerns we only write into domNode.className once. + var tn = this.stateNode || this.domNode, + classHash = {}; // set of all classes (state and otherwise) for node - // unless parent is a widget with the stopParser flag set, continue search for dojoType, recursively - var recurse = (!parent.clsInfo || !parent.clsInfo.cls.prototype.stopParser) || (args && args.template); + array.forEach(tn.className.split(" "), function(c){ classHash[c] = true; }); - // scan parent's children looking for dojoType and <script type=dojo/*> - for(var child = parent.node.firstChild; child; child = child.nextSibling){ - if(child.nodeType == 1){ - // FIXME: desupport dojoType in 2.0. use data-dojo-type instead - var type, html5 = recurse && child.getAttribute(attrData + "type"); - if(html5){ - type = html5; - }else{ - // fallback to backward compatible mode, using dojoType. remove in 2.0 - type = recurse && child.getAttribute(attrName); - } - - var fastpath = html5 == type; - - if(type){ - // if dojoType/data-dojo-type specified, add to output array of nodes to instantiate - var params = { - "type": type, - fastpath: fastpath, - clsInfo: getClassInfo(type, fastpath), // note: won't find classes declared via dojo.Declaration - node: child, - scripts: [], // <script> nodes that are parent's children - inherited: inherited // dir & lang attributes inherited from parent - }; - list.push(params); - - // Recurse, collecting <script type="dojo/..."> children, and also looking for - // descendant nodes with dojoType specified (unless the widget has the stopParser flag), - scan(params, list); - }else if(scripts && child.nodeName.toLowerCase() == "script"){ - // if <script type="dojo/...">, save in scripts[] - type = child.getAttribute("type"); - if (type && /^dojo\/\w/i.test(type)) { - scripts.push(child); - } - }else if(recurse){ - // Recurse, looking for grandchild nodes with dojoType specified - scan({ - node: child, - inherited: inherited - }, list); - } - } - } + if("_stateClasses" in this){ + array.forEach(this._stateClasses, function(c){ delete classHash[c]; }); } - // Ignore bogus entries in inherited hash like {dir: ""} - var inherited = {}; - if(args && args.inherited){ - for(var key in args.inherited){ - if(args.inherited[key]){ inherited[key] = args.inherited[key]; } - } + array.forEach(newStateClasses, function(c){ classHash[c] = true; }); + + var newClasses = []; + for(var c in classHash){ + newClasses.push(c); } + tn.className = newClasses.join(" "); - // Make list of all nodes on page w/dojoType specified - var list = []; - scan({ - node: root, - inherited: inherited - }, list); + this._stateClasses = newStateClasses; + }, - // go build the object instances - var mixin = args && args.template ? {template: true} : null; - return this.instantiate(list, mixin, args); // Array - }; -}(); + _trackMouseState: function(/*DomNode*/ node, /*String*/ clazz){ + // summary: + // Track mouse/focus events on specified node and set CSS class on that node to indicate + // current state. Usually not called directly, but via cssStateNodes attribute. + // description: + // Given class=foo, will set the following CSS class on the node + // - fooActive: if the user is currently pressing down the mouse button while over the node + // - fooHover: if the user is hovering the mouse over the node, but not pressing down a button + // - fooFocus: if the node is focused + // + // Note that it won't set any classes if the widget is disabled. + // node: DomNode + // Should be a sub-node of the widget, not the top node (this.domNode), since the top node + // is handled specially and automatically just by mixing in this class. + // clazz: String + // CSS class name (ex: dijitSliderUpArrow). -//Register the parser callback. It should be the first callback -//after the a11y test. + // Current state of node (initially false) + // NB: setting specifically to false because domClass.toggle() needs true boolean as third arg + var hovering=false, active=false, focused=false; + + var self = this, + cn = lang.hitch(this, "connect", node); -(function(){ - var parseRunner = function(){ - if(dojo.config.parseOnLoad){ - dojo.parser.parse(); + function setClass(){ + var disabled = ("disabled" in self && self.disabled) || ("readonly" in self && self.readonly); + domClass.toggle(node, clazz+"Hover", hovering && !active && !disabled); + domClass.toggle(node, clazz+"Active", active && !disabled); + domClass.toggle(node, clazz+"Focused", focused && !disabled); } - }; - // FIXME: need to clobber cross-dependency!! - if(dojo.getObject("dijit.wai.onload") === dojo._loaders[0]){ - dojo._loaders.splice(1, 0, parseRunner); - }else{ - dojo._loaders.unshift(parseRunner); - } -})(); + // Mouse + cn("onmouseenter", function(){ + hovering = true; + setClass(); + }); + cn("onmouseleave", function(){ + hovering = false; + active = false; + setClass(); + }); + cn(touch.press, function(){ + active = true; + setClass(); + }); + cn(touch.release, function(){ + active = false; + setClass(); + }); -} + // Focus + cn("onfocus", function(){ + focused = true; + setClass(); + }); + cn("onblur", function(){ + focused = false; + setClass(); + }); -if(!dojo._hasResource["dojo.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.window"] = true; -dojo.provide("dojo.window"); + // Just in case widget is enabled/disabled while it has focus/hover/active state. + // Maybe this is overkill. + this.watch("disabled", setClass); + this.watch("readOnly", setClass); + } +}); +}); -dojo.getObject("window", true, dojo); +}, +'dijit/DialogUnderlay':function(){ +define("dijit/DialogUnderlay", [ + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/_base/window", // win.body + "dojo/window", // winUtils.getBox + "./_Widget", + "./_TemplatedMixin", + "./BackgroundIframe" +], function(declare, domAttr, win, winUtils, _Widget, _TemplatedMixin, BackgroundIframe){ -dojo.window.getBox = function(){ +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; +=====*/ + + // module: + // dijit/DialogUnderlay // summary: - // Returns the dimensions and scroll position of the viewable area of a browser window + // The component that blocks the screen behind a `dijit.Dialog` - var scrollRoot = (dojo.doc.compatMode == 'BackCompat') ? dojo.body() : dojo.doc.documentElement; + return declare("dijit.DialogUnderlay", [_Widget, _TemplatedMixin], { + // summary: + // The component that blocks the screen behind a `dijit.Dialog` + // + // description: + // A component used to block input behind a `dijit.Dialog`. Only a single + // instance of this widget is created by `dijit.Dialog`, and saved as + // a reference to be shared between all Dialogs as `dijit._underlay` + // + // The underlay itself can be styled based on and id: + // | #myDialog_underlay { background-color:red; } + // + // In the case of `dijit.Dialog`, this id is based on the id of the Dialog, + // suffixed with _underlay. - // get scroll position - var scroll = dojo._docScroll(); // scrollRoot.scrollTop/Left should work - return { w: scrollRoot.clientWidth, h: scrollRoot.clientHeight, l: scroll.x, t: scroll.y }; -}; + // Template has two divs; outer div is used for fade-in/fade-out, and also to hold background iframe. + // Inner div has opacity specified in CSS file. + templateString: "<div class='dijitDialogUnderlayWrapper'><div class='dijitDialogUnderlay' data-dojo-attach-point='node'></div></div>", -dojo.window.get = function(doc){ - // summary: - // Get window object associated with document doc + // Parameters on creation or updatable later - // In some IE versions (at least 6.0), document.parentWindow does not return a - // reference to the real window object (maybe a copy), so we must fix it as well - // We use IE specific execScript to attach the real window reference to - // document._parentWindow for later use - if(dojo.isIE && window !== document.parentWindow){ - /* - In IE 6, only the variable "window" can be used to connect events (others - may be only copies). - */ - doc.parentWindow.execScript("document._parentWindow = window;", "Javascript"); - //to prevent memory leak, unset it after use - //another possibility is to add an onUnload handler which seems overkill to me (liucougar) - var win = doc._parentWindow; - doc._parentWindow = null; - return win; // Window - } + // dialogId: String + // Id of the dialog.... DialogUnderlay's id is based on this id + dialogId: "", - return doc.parentWindow || doc.defaultView; // Window -}; + // class: String + // This class name is used on the DialogUnderlay node, in addition to dijitDialogUnderlay + "class": "", -dojo.window.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){ - // summary: - // Scroll the passed node into view, if it is not already. - - // don't rely on node.scrollIntoView working just because the function is there + _setDialogIdAttr: function(id){ + domAttr.set(this.node, "id", id + "_underlay"); + this._set("dialogId", id); + }, - try{ // catch unexpected/unrecreatable errors (#7808) since we can recover using a semi-acceptable native method - node = dojo.byId(node); - var doc = node.ownerDocument || dojo.doc, - body = doc.body || dojo.body(), - html = doc.documentElement || body.parentNode, - isIE = dojo.isIE, isWK = dojo.isWebKit; - // if an untested browser, then use the native method - if((!(dojo.isMoz || isIE || isWK || dojo.isOpera) || node == body || node == html) && (typeof node.scrollIntoView != "undefined")){ - node.scrollIntoView(false); // short-circuit to native if possible - return; - } - var backCompat = doc.compatMode == 'BackCompat', - clientAreaRoot = (isIE >= 9 && node.ownerDocument.parentWindow.frameElement) - ? ((html.clientHeight > 0 && html.clientWidth > 0 && (body.clientHeight == 0 || body.clientWidth == 0 || body.clientHeight > html.clientHeight || body.clientWidth > html.clientWidth)) ? html : body) - : (backCompat ? body : html), - scrollRoot = isWK ? body : clientAreaRoot, - rootWidth = clientAreaRoot.clientWidth, - rootHeight = clientAreaRoot.clientHeight, - rtl = !dojo._isBodyLtr(), - nodePos = pos || dojo.position(node), - el = node.parentNode, - isFixed = function(el){ - return ((isIE <= 6 || (isIE && backCompat))? false : (dojo.style(el, 'position').toLowerCase() == "fixed")); - }; - if(isFixed(node)){ return; } // nothing to do + _setClassAttr: function(clazz){ + this.node.className = "dijitDialogUnderlay " + clazz; + this._set("class", clazz); + }, - while(el){ - if(el == body){ el = scrollRoot; } - var elPos = dojo.position(el), - fixedPos = isFixed(el); - - if(el == scrollRoot){ - elPos.w = rootWidth; elPos.h = rootHeight; - if(scrollRoot == html && isIE && rtl){ elPos.x += scrollRoot.offsetWidth-elPos.w; } // IE workaround where scrollbar causes negative x - if(elPos.x < 0 || !isIE){ elPos.x = 0; } // IE can have values > 0 - if(elPos.y < 0 || !isIE){ elPos.y = 0; } - }else{ - var pb = dojo._getPadBorderExtents(el); - elPos.w -= pb.w; elPos.h -= pb.h; elPos.x += pb.l; elPos.y += pb.t; - var clientSize = el.clientWidth, - scrollBarSize = elPos.w - clientSize; - if(clientSize > 0 && scrollBarSize > 0){ - elPos.w = clientSize; - elPos.x += (rtl && (isIE || el.clientLeft > pb.l/*Chrome*/)) ? scrollBarSize : 0; - } - clientSize = el.clientHeight; - scrollBarSize = elPos.h - clientSize; - if(clientSize > 0 && scrollBarSize > 0){ - elPos.h = clientSize; - } - } - if(fixedPos){ // bounded by viewport, not parents - if(elPos.y < 0){ - elPos.h += elPos.y; elPos.y = 0; - } - if(elPos.x < 0){ - elPos.w += elPos.x; elPos.x = 0; - } - if(elPos.y + elPos.h > rootHeight){ - elPos.h = rootHeight - elPos.y; - } - if(elPos.x + elPos.w > rootWidth){ - elPos.w = rootWidth - elPos.x; - } - } - // calculate overflow in all 4 directions - var l = nodePos.x - elPos.x, // beyond left: < 0 - t = nodePos.y - Math.max(elPos.y, 0), // beyond top: < 0 - r = l + nodePos.w - elPos.w, // beyond right: > 0 - bot = t + nodePos.h - elPos.h; // beyond bottom: > 0 - if(r * l > 0){ - var s = Math[l < 0? "max" : "min"](l, r); - if(rtl && ((isIE == 8 && !backCompat) || isIE >= 9)){ s = -s; } - nodePos.x += el.scrollLeft; - el.scrollLeft += s; - nodePos.x -= el.scrollLeft; - } - if(bot * t > 0){ - nodePos.y += el.scrollTop; - el.scrollTop += Math[t < 0? "max" : "min"](t, bot); - nodePos.y -= el.scrollTop; - } - el = (el != scrollRoot) && !fixedPos && el.parentNode; + postCreate: function(){ + // summary: + // Append the underlay to the body + win.body().appendChild(this.domNode); + }, + + layout: function(){ + // summary: + // Sets the background to the size of the viewport + // + // description: + // Sets the background to the size of the viewport (rather than the size + // of the document) since we need to cover the whole browser window, even + // if the document is only a few lines long. + // tags: + // private + + var is = this.node.style, + os = this.domNode.style; + + // hide the background temporarily, so that the background itself isn't + // causing scrollbars to appear (might happen when user shrinks browser + // window and then we are called to resize) + os.display = "none"; + + // then resize and show + var viewport = winUtils.getBox(); + os.top = viewport.t + "px"; + os.left = viewport.l + "px"; + is.width = viewport.w + "px"; + is.height = viewport.h + "px"; + os.display = "block"; + }, + + show: function(){ + // summary: + // Show the dialog underlay + this.domNode.style.display = "block"; + this.layout(); + this.bgIframe = new BackgroundIframe(this.domNode); + }, + + hide: function(){ + // summary: + // Hides the dialog underlay + this.bgIframe.destroy(); + delete this.bgIframe; + this.domNode.style.display = "none"; } - }catch(error){ - console.error('scrollIntoView: ' + error); - node.scrollIntoView(false); - } -}; + }); +}); -} +}, +'dijit/layout/ScrollingTabController':function(){ +require({cache:{ +'url:dijit/layout/templates/ScrollingTabController.html':"<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div data-dojo-type=\"dijit.layout._ScrollingTabControllerMenuButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\"\n\t\t\tdata-dojo-props=\"containerId: '${containerId}', iconClass: 'dijitTabStripMenuIcon',\n\t\t\t\t\tdropDownPosition: ['below-alt', 'above-alt']\"\n\t\t\tdata-dojo-attach-point=\"_menuBtn\" showLabel=\"false\" title=\"\">▼</div>\n\t<div data-dojo-type=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\"\n\t\t\tdata-dojo-props=\"iconClass:'dijitTabStripSlideLeftIcon', showLabel:false, title:''\"\n\t\t\tdata-dojo-attach-point=\"_leftBtn\" data-dojo-attach-event=\"onClick: doSlideLeft\">◀</div>\n\t<div data-dojo-type=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\"\n\t\t\tdata-dojo-props=\"iconClass:'dijitTabStripSlideRightIcon', showLabel:false, title:''\"\n\t\t\tdata-dojo-attach-point=\"_rightBtn\" data-dojo-attach-event=\"onClick: doSlideRight\">▶</div>\n\t<div class='dijitTabListWrapper' data-dojo-attach-point='tablistWrapper'>\n\t\t<div role='tablist' data-dojo-attach-event='onkeypress:onkeypress'\n\t\t\t\tdata-dojo-attach-point='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>", +'url:dijit/layout/templates/_ScrollingTabControllerButton.html':"<div data-dojo-attach-event=\"onclick:_onClick\">\n\t<div role=\"presentation\" class=\"dijitTabInnerDiv\" data-dojo-attach-point=\"innerDiv,focusNode\">\n\t\t<div role=\"presentation\" class=\"dijitTabContent dijitButtonContents\" data-dojo-attach-point=\"tabContent\">\n\t\t\t<img role=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" data-dojo-attach-point=\"iconNode\"/>\n\t\t\t<span data-dojo-attach-point=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>"}}); +define("dijit/layout/ScrollingTabController", [ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add domClass.contains + "dojo/dom-geometry", // domGeometry.contentBox + "dojo/dom-style", // domStyle.style + "dojo/_base/fx", // Animation + "dojo/_base/lang", // lang.hitch + "dojo/query", // query + "dojo/_base/sniff", // has("ie"), has("webkit"), has("quirks") + "../registry", // registry.byId() + "dojo/text!./templates/ScrollingTabController.html", + "dojo/text!./templates/_ScrollingTabControllerButton.html", + "./TabController", + "./utils", // marginBox2contextBox, layoutChildren + "../_WidgetsInTemplateMixin", + "../Menu", + "../MenuItem", + "../form/Button", + "../_HasDropDown", + "dojo/NodeList-dom" // NodeList.style +], function(array, declare, domClass, domGeometry, domStyle, fx, lang, query, has, + registry, tabControllerTemplate, buttonTemplate, TabController, layoutUtils, _WidgetsInTemplateMixin, + Menu, MenuItem, Button, _HasDropDown){ -if(!dojo._hasResource["dijit._base.manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._base.manager"] = true; -dojo.provide("dijit._base.manager"); +/*===== +var _WidgetsInTemplateMixin = dijit._WidgetsInTemplateMixin; +var Menu = dijit.Menu; +var _HasDropDown = dijit._HasDropDown; +var TabController = dijit.layout.TabController; +=====*/ + + +// module: +// dijit/layout/ScrollingTabController +// summary: +// Set of tabs with left/right arrow keys and a menu to switch between tabs not +// all fitting on a single row. -dojo.declare("dijit.WidgetSet", null, { +var ScrollingTabController = declare("dijit.layout.ScrollingTabController", [TabController, _WidgetsInTemplateMixin], { // summary: - // A set of widgets indexed by id. A default instance of this class is - // available as `dijit.registry` - // - // example: - // Create a small list of widgets: - // | var ws = new dijit.WidgetSet(); - // | ws.add(dijit.byId("one")); - // | ws.add(dijit.byId("two")); - // | // destroy both: - // | ws.forEach(function(w){ w.destroy(); }); - // - // example: - // Using dijit.registry: - // | dijit.registry.forEach(function(w){ /* do something */ }); + // Set of tabs with left/right arrow keys and a menu to switch between tabs not + // all fitting on a single row. + // Works only for horizontal tabs (either above or below the content, not to the left + // or right). + // tags: + // private - constructor: function(){ - this._hash = {}; - this.length = 0; - }, + baseClass: "dijitTabController dijitScrollingTabController", - add: function(/*dijit._Widget*/ widget){ - // summary: - // Add a widget to this list. If a duplicate ID is detected, a error is thrown. - // - // widget: dijit._Widget - // Any dijit._Widget subclass. - if(this._hash[widget.id]){ - throw new Error("Tried to register widget with id==" + widget.id + " but that id is already registered"); + templateString: tabControllerTemplate, + + // useMenu: [const] Boolean + // True if a menu should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useMenu: true, + + // useSlider: [const] Boolean + // True if a slider should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useSlider: true, + + // tabStripClass: [const] String + // The css class to apply to the tab strip, if it is visible. + tabStripClass: "", + + widgetsInTemplate: true, + + // _minScroll: Number + // The distance in pixels from the edge of the tab strip which, + // if a scroll animation is less than, forces the scroll to + // go all the way to the left/right. + _minScroll: 5, + + // Override default behavior mapping class to DOMNode + _setClassAttr: { node: "containerNode", type: "class" }, + + buildRendering: function(){ + this.inherited(arguments); + var n = this.domNode; + + this.scrollNode = this.tablistWrapper; + this._initButtons(); + + if(!this.tabStripClass){ + this.tabStripClass = "dijitTabContainer" + + this.tabPosition.charAt(0).toUpperCase() + + this.tabPosition.substr(1).replace(/-.*/, "") + + "None"; + domClass.add(n, "tabStrip-disabled") } - this._hash[widget.id] = widget; - this.length++; + + domClass.add(this.tablistWrapper, this.tabStripClass); }, - remove: function(/*String*/ id){ - // summary: - // Remove a widget from this WidgetSet. Does not destroy the widget; simply - // removes the reference. - if(this._hash[id]){ - delete this._hash[id]; - this.length--; - } + onStartup: function(){ + this.inherited(arguments); + + // TabController is hidden until it finishes drawing, to give + // a less visually jumpy instantiation. When it's finished, set visibility to "" + // to that the tabs are hidden/shown depending on the container's visibility setting. + domStyle.set(this.domNode, "visibility", ""); + this._postStartup = true; }, - forEach: function(/*Function*/ func, /* Object? */thisObj){ - // summary: - // Call specified function for each widget in this set. - // - // func: - // A callback function to run for each item. Is passed the widget, the index - // in the iteration, and the full hash, similar to `dojo.forEach`. - // - // thisObj: - // An optional scope parameter - // - // example: - // Using the default `dijit.registry` instance: - // | dijit.registry.forEach(function(widget){ - // | console.log(widget.declaredClass); - // | }); - // - // returns: - // Returns self, in order to allow for further chaining. + onAddChild: function(page, insertIndex){ + this.inherited(arguments); + + // changes to the tab button label or iconClass will have changed the width of the + // buttons, so do a resize + array.forEach(["label", "iconClass"], function(attr){ + this.pane2watches[page.id].push( + this.pane2button[page.id].watch(attr, lang.hitch(this, function(){ + if(this._postStartup && this._dim){ + this.resize(this._dim); + } + })) + ); + }, this); + + // Increment the width of the wrapper when a tab is added + // This makes sure that the buttons never wrap. + // The value 200 is chosen as it should be bigger than most + // Tab button widths. + domStyle.set(this.containerNode, "width", + (domStyle.get(this.containerNode, "width") + 200) + "px"); + }, - thisObj = thisObj || dojo.global; - var i = 0, id; - for(id in this._hash){ - func.call(thisObj, this._hash[id], i++, this._hash); + onRemoveChild: function(page, insertIndex){ + // null out _selectedTab because we are about to delete that dom node + var button = this.pane2button[page.id]; + if(this._selectedTab === button.domNode){ + this._selectedTab = null; } - return this; // dijit.WidgetSet + + this.inherited(arguments); }, - filter: function(/*Function*/ filter, /* Object? */thisObj){ + _initButtons: function(){ // summary: - // Filter down this WidgetSet to a smaller new WidgetSet - // Works the same as `dojo.filter` and `dojo.NodeList.filter` - // - // filter: - // Callback function to test truthiness. Is passed the widget - // reference and the pseudo-index in the object. - // - // thisObj: Object? - // Option scope to use for the filter function. - // - // example: - // Arbitrary: select the odd widgets in this list - // | dijit.registry.filter(function(w, i){ - // | return i % 2 == 0; - // | }).forEach(function(w){ /* odd ones */ }); + // Creates the buttons used to scroll to view tabs that + // may not be visible if the TabContainer is too narrow. - thisObj = thisObj || dojo.global; - var res = new dijit.WidgetSet(), i = 0, id; - for(id in this._hash){ - var w = this._hash[id]; - if(filter.call(thisObj, w, i++, this._hash)){ - res.add(w); + // Make a list of the buttons to display when the tab labels become + // wider than the TabContainer, and hide the other buttons. + // Also gets the total width of the displayed buttons. + this._btnWidth = 0; + this._buttons = query("> .tabStripButton", this.domNode).filter(function(btn){ + if((this.useMenu && btn == this._menuBtn.domNode) || + (this.useSlider && (btn == this._rightBtn.domNode || btn == this._leftBtn.domNode))){ + this._btnWidth += domGeometry.getMarginSize(btn).w; + return true; + }else{ + domStyle.set(btn, "display", "none"); + return false; } + }, this); + }, + + _getTabsWidth: function(){ + var children = this.getChildren(); + if(children.length){ + var leftTab = children[this.isLeftToRight() ? 0 : children.length - 1].domNode, + rightTab = children[this.isLeftToRight() ? children.length - 1 : 0].domNode; + return rightTab.offsetLeft + domStyle.get(rightTab, "width") - leftTab.offsetLeft; + }else{ + return 0; } - return res; // dijit.WidgetSet }, - byId: function(/*String*/ id){ + _enableBtn: function(width){ // summary: - // Find a widget in this list by it's id. - // example: - // Test if an id is in a particular WidgetSet - // | var ws = new dijit.WidgetSet(); - // | ws.add(dijit.byId("bar")); - // | var t = ws.byId("bar") // returns a widget - // | var x = ws.byId("foo"); // returns undefined - - return this._hash[id]; // dijit._Widget + // Determines if the tabs are wider than the width of the TabContainer, and + // thus that we need to display left/right/menu navigation buttons. + var tabsWidth = this._getTabsWidth(); + width = width || domStyle.get(this.scrollNode, "width"); + return tabsWidth > 0 && width < tabsWidth; }, - byClass: function(/*String*/ cls){ + resize: function(dim){ // summary: - // Reduce this widgetset to a new WidgetSet of a particular `declaredClass` - // - // cls: String - // The Class to scan for. Full dot-notated string. - // - // example: - // Find all `dijit.TitlePane`s in a page: - // | dijit.registry.byClass("dijit.TitlePane").forEach(function(tp){ tp.close(); }); + // Hides or displays the buttons used to scroll the tab list and launch the menu + // that selects tabs. - var res = new dijit.WidgetSet(), id, widget; - for(id in this._hash){ - widget = this._hash[id]; - if(widget.declaredClass == cls){ - res.add(widget); - } - } - return res; // dijit.WidgetSet -}, + // Save the dimensions to be used when a child is renamed. + this._dim = dim; - toArray: function(){ - // summary: - // Convert this WidgetSet into a true Array - // - // example: - // Work with the widget .domNodes in a real Array - // | dojo.map(dijit.registry.toArray(), function(w){ return w.domNode; }); + // Set my height to be my natural height (tall enough for one row of tab labels), + // and my content-box width based on margin-box width specified in dim parameter. + // But first reset scrollNode.height in case it was set by layoutChildren() call + // in a previous run of this method. + this.scrollNode.style.height = "auto"; + var cb = this._contentBox = layoutUtils.marginBox2contentBox(this.domNode, {h: 0, w: dim.w}); + cb.h = this.scrollNode.offsetHeight; + domGeometry.setContentSize(this.domNode, cb); - var ar = []; - for(var id in this._hash){ - ar.push(this._hash[id]); + // Show/hide the left/right/menu navigation buttons depending on whether or not they + // are needed. + var enable = this._enableBtn(this._contentBox.w); + this._buttons.style("display", enable ? "" : "none"); + + // Position and size the navigation buttons and the tablist + this._leftBtn.layoutAlign = "left"; + this._rightBtn.layoutAlign = "right"; + this._menuBtn.layoutAlign = this.isLeftToRight() ? "right" : "left"; + layoutUtils.layoutChildren(this.domNode, this._contentBox, + [this._menuBtn, this._leftBtn, this._rightBtn, {domNode: this.scrollNode, layoutAlign: "client"}]); + + // set proper scroll so that selected tab is visible + if(this._selectedTab){ + if(this._anim && this._anim.status() == "playing"){ + this._anim.stop(); + } + this.scrollNode.scrollLeft = this._convertToScrollLeft(this._getScrollForSelectedTab()); } - return ar; // dijit._Widget[] -}, - map: function(/* Function */func, /* Object? */thisObj){ + // Enable/disabled left right buttons depending on whether or not user can scroll to left or right + this._setButtonClass(this._getScroll()); + + this._postResize = true; + + // Return my size so layoutChildren() can use it. + // Also avoids IE9 layout glitch on browser resize when scroll buttons present + return {h: this._contentBox.h, w: dim.w}; + }, + + _getScroll: function(){ // summary: - // Create a new Array from this WidgetSet, following the same rules as `dojo.map` - // example: - // | var nodes = dijit.registry.map(function(w){ return w.domNode; }); - // - // returns: - // A new array of the returned values. - return dojo.map(this.toArray(), func, thisObj); // Array + // Returns the current scroll of the tabs where 0 means + // "scrolled all the way to the left" and some positive number, based on # + // of pixels of possible scroll (ex: 1000) means "scrolled all the way to the right" + return (this.isLeftToRight() || has("ie") < 8 || (has("ie") && has("quirks")) || has("webkit")) ? this.scrollNode.scrollLeft : + domStyle.get(this.containerNode, "width") - domStyle.get(this.scrollNode, "width") + + (has("ie") == 8 ? -1 : 1) * this.scrollNode.scrollLeft; }, - every: function(func, thisObj){ + _convertToScrollLeft: function(val){ // summary: - // A synthetic clone of `dojo.every` acting explicitly on this WidgetSet - // - // func: Function - // A callback function run for every widget in this list. Exits loop - // when the first false return is encountered. + // Given a scroll value where 0 means "scrolled all the way to the left" + // and some positive number, based on # of pixels of possible scroll (ex: 1000) + // means "scrolled all the way to the right", return value to set this.scrollNode.scrollLeft + // to achieve that scroll. // - // thisObj: Object? - // Optional scope parameter to use for the callback + // This method is to adjust for RTL funniness in various browsers and versions. + if(this.isLeftToRight() || has("ie") < 8 || (has("ie") && has("quirks")) || has("webkit")){ + return val; + }else{ + var maxScroll = domStyle.get(this.containerNode, "width") - domStyle.get(this.scrollNode, "width"); + return (has("ie") == 8 ? -1 : 1) * (val - maxScroll); + } + }, + + onSelectChild: function(/*dijit._Widget*/ page){ + // summary: + // Smoothly scrolls to a tab when it is selected. + + var tab = this.pane2button[page.id]; + if(!tab || !page){return;} + + var node = tab.domNode; + + // Save the selection + if(node != this._selectedTab){ + this._selectedTab = node; + + // Scroll to the selected tab, except on startup, when scrolling is handled in resize() + if(this._postResize){ + var sl = this._getScroll(); - thisObj = thisObj || dojo.global; - var x = 0, i; - for(i in this._hash){ - if(!func.call(thisObj, this._hash[i], x++, this._hash)){ - return false; // Boolean + if(sl > node.offsetLeft || + sl + domStyle.get(this.scrollNode, "width") < + node.offsetLeft + domStyle.get(node, "width")){ + this.createSmoothScroll().play(); + } } } - return true; // Boolean + + this.inherited(arguments); }, - some: function(func, thisObj){ + _getScrollBounds: function(){ // summary: - // A synthetic clone of `dojo.some` acting explictly on this WidgetSet - // - // func: Function - // A callback function run for every widget in this list. Exits loop - // when the first true return is encountered. - // - // thisObj: Object? - // Optional scope parameter to use for the callback + // Returns the minimum and maximum scroll setting to show the leftmost and rightmost + // tabs (respectively) + var children = this.getChildren(), + scrollNodeWidth = domStyle.get(this.scrollNode, "width"), // about 500px + containerWidth = domStyle.get(this.containerNode, "width"), // 50,000px + maxPossibleScroll = containerWidth - scrollNodeWidth, // scrolling until right edge of containerNode visible + tabsWidth = this._getTabsWidth(); - thisObj = thisObj || dojo.global; - var x = 0, i; - for(i in this._hash){ - if(func.call(thisObj, this._hash[i], x++, this._hash)){ - return true; // Boolean - } + if(children.length && tabsWidth > scrollNodeWidth){ + // Scrolling should happen + return { + min: this.isLeftToRight() ? 0 : children[children.length-1].domNode.offsetLeft, + max: this.isLeftToRight() ? + (children[children.length-1].domNode.offsetLeft + domStyle.get(children[children.length-1].domNode, "width")) - scrollNodeWidth : + maxPossibleScroll + }; + }else{ + // No scrolling needed, all tabs visible, we stay either scrolled to far left or far right (depending on dir) + var onlyScrollPosition = this.isLeftToRight() ? 0 : maxPossibleScroll; + return { + min: onlyScrollPosition, + max: onlyScrollPosition + }; } - return false; // Boolean - } + }, -}); + _getScrollForSelectedTab: function(){ + // summary: + // Returns the scroll value setting so that the selected tab + // will appear in the center + var w = this.scrollNode, + n = this._selectedTab, + scrollNodeWidth = domStyle.get(this.scrollNode, "width"), + scrollBounds = this._getScrollBounds(); -(function(){ + // TODO: scroll minimal amount (to either right or left) so that + // selected tab is fully visible, and just return if it's already visible? + var pos = (n.offsetLeft + domStyle.get(n, "width")/2) - scrollNodeWidth/2; + pos = Math.min(Math.max(pos, scrollBounds.min), scrollBounds.max); - /*===== - dijit.registry = { + // TODO: + // If scrolling close to the left side or right side, scroll + // all the way to the left or right. See this._minScroll. + // (But need to make sure that doesn't scroll the tab out of view...) + return pos; + }, + + createSmoothScroll: function(x){ // summary: - // A list of widgets on a page. + // Creates a dojo._Animation object that smoothly scrolls the tab list + // either to a fixed horizontal pixel value, or to the selected tab. // description: - // Is an instance of `dijit.WidgetSet` - }; - =====*/ - dijit.registry = new dijit.WidgetSet(); + // If an number argument is passed to the function, that horizontal + // pixel position is scrolled to. Otherwise the currently selected + // tab is scrolled to. + // x: Integer? + // An optional pixel value to scroll to, indicating distance from left. - var hash = dijit.registry._hash, - attr = dojo.attr, - hasAttr = dojo.hasAttr, - style = dojo.style; + // Calculate position to scroll to + if(arguments.length > 0){ + // position specified by caller, just make sure it's within bounds + var scrollBounds = this._getScrollBounds(); + x = Math.min(Math.max(x, scrollBounds.min), scrollBounds.max); + }else{ + // scroll to center the current tab + x = this._getScrollForSelectedTab(); + } + + if(this._anim && this._anim.status() == "playing"){ + this._anim.stop(); + } - dijit.byId = function(/*String|dijit._Widget*/ id){ + var self = this, + w = this.scrollNode, + anim = new fx.Animation({ + beforeBegin: function(){ + if(this.curve){ delete this.curve; } + var oldS = w.scrollLeft, + newS = self._convertToScrollLeft(x); + anim.curve = new fx._Line(oldS, newS); + }, + onAnimate: function(val){ + w.scrollLeft = val; + } + }); + this._anim = anim; + + // Disable/enable left/right buttons according to new scroll position + this._setButtonClass(x); + + return anim; // dojo._Animation + }, + + _getBtnNode: function(/*Event*/ e){ // summary: - // Returns a widget by it's id, or if passed a widget, no-op (like dojo.byId()) - return typeof id == "string" ? hash[id] : id; // dijit._Widget - }; + // Gets a button DOM node from a mouse click event. + // e: + // The mouse click event. + var n = e.target; + while(n && !domClass.contains(n, "tabStripButton")){ + n = n.parentNode; + } + return n; + }, - var _widgetTypeCtr = {}; - dijit.getUniqueId = function(/*String*/widgetType){ + doSlideRight: function(/*Event*/ e){ // summary: - // Generates a unique id for a given widgetType - - var id; - do{ - id = widgetType + "_" + - (widgetType in _widgetTypeCtr ? - ++_widgetTypeCtr[widgetType] : _widgetTypeCtr[widgetType] = 0); - }while(hash[id]); - return dijit._scopeName == "dijit" ? id : dijit._scopeName + "_" + id; // String - }; - - dijit.findWidgets = function(/*DomNode*/ root){ + // Scrolls the menu to the right. + // e: + // The mouse click event. + this.doSlide(1, this._getBtnNode(e)); + }, + + doSlideLeft: function(/*Event*/ e){ // summary: - // Search subtree under root returning widgets found. - // Doesn't search for nested widgets (ie, widgets inside other widgets). - - var outAry = []; - - function getChildrenHelper(root){ - for(var node = root.firstChild; node; node = node.nextSibling){ - if(node.nodeType == 1){ - var widgetId = node.getAttribute("widgetId"); - if(widgetId){ - var widget = hash[widgetId]; - if(widget){ // may be null on page w/multiple dojo's loaded - outAry.push(widget); - } - }else{ - getChildrenHelper(node); - } - } - } - } - - getChildrenHelper(root); - return outAry; - }; - - dijit._destroyAll = function(){ + // Scrolls the menu to the left. + // e: + // The mouse click event. + this.doSlide(-1,this._getBtnNode(e)); + }, + + doSlide: function(/*Number*/ direction, /*DomNode*/ node){ // summary: - // Code to destroy all widgets and do other cleanup on page unload - - // Clean up focus manager lingering references to widgets and nodes - dijit._curFocus = null; - dijit._prevFocus = null; - dijit._activeStack = []; - - // Destroy all the widgets, top down - dojo.forEach(dijit.findWidgets(dojo.body()), function(widget){ - // Avoid double destroy of widgets like Menu that are attached to <body> - // even though they are logically children of other widgets. - if(!widget._destroyed){ - if(widget.destroyRecursive){ - widget.destroyRecursive(); - }else if(widget.destroy){ - widget.destroy(); + // Scrolls the tab list to the left or right by 75% of the widget width. + // direction: + // If the direction is 1, the widget scrolls to the right, if it is + // -1, it scrolls to the left. + + if(node && domClass.contains(node, "dijitTabDisabled")){return;} + + var sWidth = domStyle.get(this.scrollNode, "width"); + var d = (sWidth * 0.75) * direction; + + var to = this._getScroll() + d; + + this._setButtonClass(to); + + this.createSmoothScroll(to).play(); + }, + + _setButtonClass: function(/*Number*/ scroll){ + // summary: + // Disables the left scroll button if the tabs are scrolled all the way to the left, + // or the right scroll button in the opposite case. + // scroll: Integer + // amount of horizontal scroll + + var scrollBounds = this._getScrollBounds(); + this._leftBtn.set("disabled", scroll <= scrollBounds.min); + this._rightBtn.set("disabled", scroll >= scrollBounds.max); + } +}); + + +var ScrollingTabControllerButtonMixin = declare("dijit.layout._ScrollingTabControllerButtonMixin", null, { + baseClass: "dijitTab tabStripButton", + + templateString: buttonTemplate, + + // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be + // able to tab to the left/right/menu buttons + tabIndex: "", + + // Similarly, override FormWidget.isFocusable() because clicking a button shouldn't focus it + // either (this override avoids focus() call in FormWidget.js) + isFocusable: function(){ return false; } +}); +/*===== +ScrollingTabControllerButtonMixin = dijit.layout._ScrollingTabControllerButtonMixin; +=====*/ + +// Class used in template +declare("dijit.layout._ScrollingTabControllerButton", + [Button, ScrollingTabControllerButtonMixin]); + +// Class used in template +declare( + "dijit.layout._ScrollingTabControllerMenuButton", + [Button, _HasDropDown, ScrollingTabControllerButtonMixin], +{ + // id of the TabContainer itself + containerId: "", + + // -1 so user can't tab into the button, but so that button can still be focused programatically. + // Because need to move focus to the button (or somewhere) before the menu is hidden or IE6 will crash. + tabIndex: "-1", + + isLoaded: function(){ + // recreate menu every time, in case the TabContainer's list of children (or their icons/labels) have changed + return false; + }, + + loadDropDown: function(callback){ + this.dropDown = new Menu({ + id: this.containerId + "_menu", + dir: this.dir, + lang: this.lang, + textDir: this.textDir + }); + var container = registry.byId(this.containerId); + array.forEach(container.getChildren(), function(page){ + var menuItem = new MenuItem({ + id: page.id + "_stcMi", + label: page.title, + iconClass: page.iconClass, + dir: page.dir, + lang: page.lang, + textDir: page.textDir, + onClick: function(){ + container.selectChild(page); } + }); + this.dropDown.addChild(menuItem); + }, this); + callback(); + }, + + closeDropDown: function(/*Boolean*/ focus){ + this.inherited(arguments); + if(this.dropDown){ + this.dropDown.destroyRecursive(); + delete this.dropDown; + } + } +}); + +return ScrollingTabController; +}); + +}, +'dijit/place':function(){ +define("dijit/place", [ + "dojo/_base/array", // array.forEach array.map array.some + "dojo/dom-geometry", // domGeometry.getMarginBox domGeometry.position + "dojo/dom-style", // domStyle.getComputedStyle + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/window", // win.body + "dojo/window", // winUtils.getBox + "." // dijit (defining dijit.place to match API doc) +], function(array, domGeometry, domStyle, kernel, win, winUtils, dijit){ + + // module: + // dijit/place + // summary: + // Code to place a popup relative to another node + + + function _place(/*DomNode*/ node, choices, layoutNode, aroundNodeCoords){ + // summary: + // Given a list of spots to put node, put it at the first spot where it fits, + // of if it doesn't fit anywhere then the place with the least overflow + // choices: Array + // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} } + // Above example says to put the top-left corner of the node at (10,20) + // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size) + // for things like tooltip, they are displayed differently (and have different dimensions) + // based on their orientation relative to the parent. This adjusts the popup based on orientation. + // It also passes in the available size for the popup, which is useful for tooltips to + // tell them that their width is limited to a certain amount. layoutNode() may return a value expressing + // how much the popup had to be modified to fit into the available space. This is used to determine + // what the best placement is. + // aroundNodeCoords: Object + // Size of aroundNode, ex: {w: 200, h: 50} + + // get {x: 10, y: 10, w: 100, h:100} type obj representing position of + // viewport over document + var view = winUtils.getBox(); + + // This won't work if the node is inside a <div style="position: relative">, + // so reattach it to win.doc.body. (Otherwise, the positioning will be wrong + // and also it might get cutoff) + if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){ + win.body().appendChild(node); + } + + var best = null; + array.some(choices, function(choice){ + var corner = choice.corner; + var pos = choice.pos; + var overflow = 0; + + // calculate amount of space available given specified position of node + var spaceAvailable = { + w: { + 'L': view.l + view.w - pos.x, + 'R': pos.x - view.l, + 'M': view.w + }[corner.charAt(1)], + h: { + 'T': view.t + view.h - pos.y, + 'B': pos.y - view.t, + 'M': view.h + }[corner.charAt(0)] + }; + + // configure node to be displayed in given position relative to button + // (need to do this in order to get an accurate size for the node, because + // a tooltip's size changes based on position, due to triangle) + if(layoutNode){ + var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords); + overflow = typeof res == "undefined" ? 0 : res; } + + // get node's size + var style = node.style; + var oldDisplay = style.display; + var oldVis = style.visibility; + if(style.display == "none"){ + style.visibility = "hidden"; + style.display = ""; + } + var mb = domGeometry. getMarginBox(node); + style.display = oldDisplay; + style.visibility = oldVis; + + // coordinates and size of node with specified corner placed at pos, + // and clipped by viewport + var + startXpos = { + 'L': pos.x, + 'R': pos.x - mb.w, + 'M': Math.max(view.l, Math.min(view.l + view.w, pos.x + (mb.w >> 1)) - mb.w) // M orientation is more flexible + }[corner.charAt(1)], + startYpos = { + 'T': pos.y, + 'B': pos.y - mb.h, + 'M': Math.max(view.t, Math.min(view.t + view.h, pos.y + (mb.h >> 1)) - mb.h) + }[corner.charAt(0)], + startX = Math.max(view.l, startXpos), + startY = Math.max(view.t, startYpos), + endX = Math.min(view.l + view.w, startXpos + mb.w), + endY = Math.min(view.t + view.h, startYpos + mb.h), + width = endX - startX, + height = endY - startY; + + overflow += (mb.w - width) + (mb.h - height); + + if(best == null || overflow < best.overflow){ + best = { + corner: corner, + aroundCorner: choice.aroundCorner, + x: startX, + y: startY, + w: width, + h: height, + overflow: overflow, + spaceAvailable: spaceAvailable + }; + } + + return !overflow; }); - }; - - if(dojo.isIE){ - // Only run _destroyAll() for IE because we think it's only necessary in that case, - // and because it causes problems on FF. See bug #3531 for details. - dojo.addOnWindowUnload(function(){ - dijit._destroyAll(); - }); + + // In case the best position is not the last one we checked, need to call + // layoutNode() again. + if(best.overflow && layoutNode){ + layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords); + } + + // And then position the node. Do this last, after the layoutNode() above + // has sized the node, due to browser quirks when the viewport is scrolled + // (specifically that a Tooltip will shrink to fit as though the window was + // scrolled to the left). + // + // In RTL mode, set style.right rather than style.left so in the common case, + // window resizes move the popup along with the aroundNode. + var l = domGeometry.isBodyLtr(), + s = node.style; + s.top = best.y + "px"; + s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px"; + s[l ? "right" : "left"] = "auto"; // needed for FF or else tooltip goes to far left + + return best; } - - dijit.byNode = function(/*DOMNode*/ node){ - // summary: - // Returns the widget corresponding to the given DOMNode - return hash[node.getAttribute("widgetId")]; // dijit._Widget + + /*===== + dijit.place.__Position = function(){ + // x: Integer + // horizontal coordinate in pixels, relative to document body + // y: Integer + // vertical coordinate in pixels, relative to document body + + this.x = x; + this.y = y; }; - - dijit.getEnclosingWidget = function(/*DOMNode*/ node){ - // summary: - // Returns the widget whose DOM tree contains the specified DOMNode, or null if - // the node is not contained within the DOM tree of any widget - while(node){ - var id = node.getAttribute && node.getAttribute("widgetId"); - if(id){ - return hash[id]; - } - node = node.parentNode; - } - return null; + =====*/ + + /*===== + dijit.place.__Rectangle = function(){ + // x: Integer + // horizontal offset in pixels, relative to document body + // y: Integer + // vertical offset in pixels, relative to document body + // w: Integer + // width in pixels. Can also be specified as "width" for backwards-compatibility. + // h: Integer + // height in pixels. Can also be specified as "height" from backwards-compatibility. + + this.x = x; + this.y = y; + this.w = w; + this.h = h; }; + =====*/ - var shown = (dijit._isElementShown = function(/*Element*/ elem){ - var s = style(elem); - return (s.visibility != "hidden") - && (s.visibility != "collapsed") - && (s.display != "none") - && (attr(elem, "type") != "hidden"); - }); - - dijit.hasDefaultTabStop = function(/*Element*/ elem){ + return (dijit.place = { // summary: - // Tests if element is tab-navigable even without an explicit tabIndex setting - - // No explicit tabIndex setting, need to investigate node type - switch(elem.nodeName.toLowerCase()){ - case "a": - // An <a> w/out a tabindex is only navigable if it has an href - return hasAttr(elem, "href"); - case "area": - case "button": - case "input": - case "object": - case "select": - case "textarea": - // These are navigable by default - return true; - case "iframe": - // If it's an editor <iframe> then it's tab navigable. - var body; - try{ - // non-IE - var contentDocument = elem.contentDocument; - if("designMode" in contentDocument && contentDocument.designMode == "on"){ - return true; - } - body = contentDocument.body; - }catch(e1){ - // contentWindow.document isn't accessible within IE7/8 - // if the iframe.src points to a foreign url and this - // page contains an element, that could get focus - try{ - body = elem.contentWindow.document.body; - }catch(e2){ - return false; + // Code to place a DOMNode relative to another DOMNode. + // Load using require(["dijit/place"], function(place){ ... }). + + at: function(node, pos, corners, padding){ + // summary: + // Positions one of the node's corners at specified position + // such that node is fully visible in viewport. + // description: + // NOTE: node is assumed to be absolutely or relatively positioned. + // node: DOMNode + // The node to position + // pos: dijit.place.__Position + // Object like {x: 10, y: 20} + // corners: String[] + // Array of Strings representing order to try corners in, like ["TR", "BL"]. + // Possible values are: + // * "BL" - bottom left + // * "BR" - bottom right + // * "TL" - top left + // * "TR" - top right + // padding: dijit.place.__Position? + // optional param to set padding, to put some buffer around the element you want to position. + // example: + // Try to place node's top right corner at (10,20). + // If that makes node go (partially) off screen, then try placing + // bottom left corner at (10,20). + // | place(node, {x: 10, y: 20}, ["TR", "BL"]) + var choices = array.map(corners, function(corner){ + var c = { corner: corner, pos: {x:pos.x,y:pos.y} }; + if(padding){ + c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x; + c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y; + } + return c; + }); + + return _place(node, choices); + }, + + around: function( + /*DomNode*/ node, + /*DomNode || dijit.place.__Rectangle*/ anchor, + /*String[]*/ positions, + /*Boolean*/ leftToRight, + /*Function?*/ layoutNode){ + + // summary: + // Position node adjacent or kitty-corner to anchor + // such that it's fully visible in viewport. + // + // description: + // Place node such that corner of node touches a corner of + // aroundNode, and that node is fully visible. + // + // anchor: + // Either a DOMNode or a __Rectangle (object with x, y, width, height). + // + // positions: + // Ordered list of positions to try matching up. + // * before: places drop down to the left of the anchor node/widget, or to the right in the case + // of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down + // with the top of the anchor, or the bottom of the drop down with bottom of the anchor. + // * after: places drop down to the right of the anchor node/widget, or to the left in the case + // of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down + // with the top of the anchor, or the bottom of the drop down with bottom of the anchor. + // * before-centered: centers drop down to the left of the anchor node/widget, or to the right + // in the case of RTL scripts like Hebrew and Arabic + // * after-centered: centers drop down to the right of the anchor node/widget, or to the left + // in the case of RTL scripts like Hebrew and Arabic + // * above-centered: drop down is centered above anchor node + // * above: drop down goes above anchor node, left sides aligned + // * above-alt: drop down goes above anchor node, right sides aligned + // * below-centered: drop down is centered above anchor node + // * below: drop down goes below anchor node + // * below-alt: drop down goes below anchor node, right sides aligned + // + // layoutNode: Function(node, aroundNodeCorner, nodeCorner) + // For things like tooltip, they are displayed differently (and have different dimensions) + // based on their orientation relative to the parent. This adjusts the popup based on orientation. + // + // leftToRight: + // True if widget is LTR, false if widget is RTL. Affects the behavior of "above" and "below" + // positions slightly. + // + // example: + // | placeAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'}); + // This will try to position node such that node's top-left corner is at the same position + // as the bottom left corner of the aroundNode (ie, put node below + // aroundNode, with left edges aligned). If that fails it will try to put + // the bottom-right corner of node where the top right corner of aroundNode is + // (ie, put node above aroundNode, with right edges aligned) + // + + // if around is a DOMNode (or DOMNode id), convert to coordinates + var aroundNodePos = (typeof anchor == "string" || "offsetWidth" in anchor) + ? domGeometry.position(anchor, true) + : anchor; + + // Adjust anchor positioning for the case that a parent node has overflw hidden, therefore cuasing the anchor not to be completely visible + if(anchor.parentNode){ + var parent = anchor.parentNode; + while(parent && parent.nodeType == 1 && parent.nodeName != "BODY"){ //ignoring the body will help performance + var parentPos = domGeometry.position(parent, true); + var parentStyleOverflow = domStyle.getComputedStyle(parent).overflow; + if(parentStyleOverflow == "hidden" || parentStyleOverflow == "auto" || parentStyleOverflow == "scroll"){ + var bottomYCoord = Math.min(aroundNodePos.y + aroundNodePos.h, parentPos.y + parentPos.h); + var rightXCoord = Math.min(aroundNodePos.x + aroundNodePos.w, parentPos.x + parentPos.w); + aroundNodePos.x = Math.max(aroundNodePos.x, parentPos.x); + aroundNodePos.y = Math.max(aroundNodePos.y, parentPos.y); + aroundNodePos.h = bottomYCoord - aroundNodePos.y; + aroundNodePos.w = rightXCoord - aroundNodePos.x; + } + parent = parent.parentNode; + } + } + + var x = aroundNodePos.x, + y = aroundNodePos.y, + width = "w" in aroundNodePos ? aroundNodePos.w : (aroundNodePos.w = aroundNodePos.width), + height = "h" in aroundNodePos ? aroundNodePos.h : (kernel.deprecated("place.around: dijit.place.__Rectangle: { x:"+x+", y:"+y+", height:"+aroundNodePos.height+", width:"+width+" } has been deprecated. Please use { x:"+x+", y:"+y+", h:"+aroundNodePos.height+", w:"+width+" }", "", "2.0"), aroundNodePos.h = aroundNodePos.height); + + // Convert positions arguments into choices argument for _place() + var choices = []; + function push(aroundCorner, corner){ + choices.push({ + aroundCorner: aroundCorner, + corner: corner, + pos: { + x: { + 'L': x, + 'R': x + width, + 'M': x + (width >> 1) + }[aroundCorner.charAt(1)], + y: { + 'T': y, + 'B': y + height, + 'M': y + (height >> 1) + }[aroundCorner.charAt(0)] } + }) + } + array.forEach(positions, function(pos){ + var ltr = leftToRight; + switch(pos){ + case "above-centered": + push("TM", "BM"); + break; + case "below-centered": + push("BM", "TM"); + break; + case "after-centered": + ltr = !ltr; + // fall through + case "before-centered": + push(ltr ? "ML" : "MR", ltr ? "MR" : "ML"); + break; + case "after": + ltr = !ltr; + // fall through + case "before": + push(ltr ? "TL" : "TR", ltr ? "TR" : "TL"); + push(ltr ? "BL" : "BR", ltr ? "BR" : "BL"); + break; + case "below-alt": + ltr = !ltr; + // fall through + case "below": + // first try to align left borders, next try to align right borders (or reverse for RTL mode) + push(ltr ? "BL" : "BR", ltr ? "TL" : "TR"); + push(ltr ? "BR" : "BL", ltr ? "TR" : "TL"); + break; + case "above-alt": + ltr = !ltr; + // fall through + case "above": + // first try to align left borders, next try to align right borders (or reverse for RTL mode) + push(ltr ? "TL" : "TR", ltr ? "BL" : "BR"); + push(ltr ? "TR" : "TL", ltr ? "BR" : "BL"); + break; + default: + // To assist dijit/_base/place, accept arguments of type {aroundCorner: "BL", corner: "TL"}. + // Not meant to be used directly. + push(pos.aroundCorner, pos.corner); } - return body.contentEditable == 'true' || (body.firstChild && body.firstChild.contentEditable == 'true'); - default: - return elem.contentEditable == 'true'; - } - }; - - var isTabNavigable = (dijit.isTabNavigable = function(/*Element*/ elem){ - // summary: - // Tests if an element is tab-navigable - - // TODO: convert (and rename method) to return effective tabIndex; will save time in _getTabNavigable() - if(attr(elem, "disabled")){ - return false; - }else if(hasAttr(elem, "tabIndex")){ - // Explicit tab index setting - return attr(elem, "tabIndex") >= 0; // boolean - }else{ - // No explicit tabIndex setting, so depends on node type - return dijit.hasDefaultTabStop(elem); + }); + + var position = _place(node, choices, layoutNode, {w: width, h: height}); + position.aroundNodePos = aroundNodePos; + + return position; } }); +}); - dijit._getTabNavigable = function(/*DOMNode*/ root){ +}, +'dijit/_HasDropDown':function(){ +define("dijit/_HasDropDown", [ + "dojo/_base/declare", // declare + "dojo/_base/Deferred", + "dojo/_base/event", // event.stop + "dojo/dom", // dom.isDescendant + "dojo/dom-attr", // domAttr.set + "dojo/dom-class", // domClass.add domClass.contains domClass.remove + "dojo/dom-geometry", // domGeometry.marginBox domGeometry.position + "dojo/dom-style", // domStyle.set + "dojo/has", + "dojo/keys", // keys.DOWN_ARROW keys.ENTER keys.ESCAPE + "dojo/_base/lang", // lang.hitch lang.isFunction + "dojo/touch", + "dojo/_base/window", // win.doc + "dojo/window", // winUtils.getBox + "./registry", // registry.byNode() + "./focus", + "./popup", + "./_FocusMixin" +], function(declare, Deferred, event,dom, domAttr, domClass, domGeometry, domStyle, has, keys, lang, touch, + win, winUtils, registry, focus, popup, _FocusMixin){ + +/*===== + var _FocusMixin = dijit._FocusMixin; +=====*/ + + // module: + // dijit/_HasDropDown + // summary: + // Mixin for widgets that need drop down ability. + + return declare("dijit._HasDropDown", _FocusMixin, { // summary: - // Finds descendants of the specified root node. + // Mixin for widgets that need drop down ability. + + // _buttonNode: [protected] DomNode + // The button/icon/node to click to display the drop down. + // Can be set via a data-dojo-attach-point assignment. + // If missing, then either focusNode or domNode (if focusNode is also missing) will be used. + _buttonNode: null, + + // _arrowWrapperNode: [protected] DomNode + // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending + // on where the drop down is set to be positioned. + // Can be set via a data-dojo-attach-point assignment. + // If missing, then _buttonNode will be used. + _arrowWrapperNode: null, + + // _popupStateNode: [protected] DomNode + // The node to set the popupActive class on. + // Can be set via a data-dojo-attach-point assignment. + // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used. + _popupStateNode: null, + + // _aroundNode: [protected] DomNode + // The node to display the popup around. + // Can be set via a data-dojo-attach-point assignment. + // If missing, then domNode will be used. + _aroundNode: null, + + // dropDown: [protected] Widget + // The widget to display as a popup. This widget *must* be + // defined before the startup function is called. + dropDown: null, + + // autoWidth: [protected] Boolean + // Set to true to make the drop down at least as wide as this + // widget. Set to false if the drop down should just be its + // default width + autoWidth: true, + + // forceWidth: [protected] Boolean + // Set to true to make the drop down exactly as wide as this + // widget. Overrides autoWidth. + forceWidth: false, + + // maxHeight: [protected] Integer + // The max height for our dropdown. + // Any dropdown taller than this will have scrollbars. + // Set to 0 for no max height, or -1 to limit height to available space in viewport + maxHeight: 0, + + // dropDownPosition: [const] String[] + // This variable controls the position of the drop down. + // It's an array of strings with the following values: // - // description: - // Finds the following descendants of the specified root node: - // * the first tab-navigable element in document order - // without a tabIndex or with tabIndex="0" - // * the last tab-navigable element in document order - // without a tabIndex or with tabIndex="0" - // * the first element in document order with the lowest - // positive tabIndex value - // * the last element in document order with the highest - // positive tabIndex value - var first, last, lowest, lowestTabindex, highest, highestTabindex, radioSelected = {}; - function radioName(node) { - // If this element is part of a radio button group, return the name for that group. - return node && node.tagName.toLowerCase() == "input" && - node.type && node.type.toLowerCase() == "radio" && - node.name && node.name.toLowerCase(); - } - var walkTree = function(/*DOMNode*/parent){ - dojo.query("> *", parent).forEach(function(child){ - // Skip hidden elements, and also non-HTML elements (those in custom namespaces) in IE, - // since show() invokes getAttribute("type"), which crash on VML nodes in IE. - if((dojo.isIE && child.scopeName!=="HTML") || !shown(child)){ - return; - } + // * before: places drop down to the left of the target node/widget, or to the right in + // the case of RTL scripts like Hebrew and Arabic + // * after: places drop down to the right of the target node/widget, or to the left in + // the case of RTL scripts like Hebrew and Arabic + // * above: drop down goes above target node + // * below: drop down goes below target node + // + // The list is positions is tried, in order, until a position is found where the drop down fits + // within the viewport. + // + dropDownPosition: ["below","above"], - if(isTabNavigable(child)){ - var tabindex = attr(child, "tabIndex"); - if(!hasAttr(child, "tabIndex") || tabindex == 0){ - if(!first){ first = child; } - last = child; - }else if(tabindex > 0){ - if(!lowest || tabindex < lowestTabindex){ - lowestTabindex = tabindex; - lowest = child; - } - if(!highest || tabindex >= highestTabindex){ - highestTabindex = tabindex; - highest = child; + // _stopClickEvents: Boolean + // When set to false, the click events will not be stopped, in + // case you want to use them in your subwidget + _stopClickEvents: true, + + _onDropDownMouseDown: function(/*Event*/ e){ + // summary: + // Callback when the user mousedown's on the arrow icon + if(this.disabled || this.readOnly){ return; } + + // Prevent default to stop things like text selection, but don't stop propogation, so that: + // 1. TimeTextBox etc. can focusthe <input> on mousedown + // 2. dropDownButtonActive class applied by _CssStateMixin (on button depress) + // 3. user defined onMouseDown handler fires + e.preventDefault(); + + this._docHandler = this.connect(win.doc, touch.release, "_onDropDownMouseUp"); + + this.toggleDropDown(); + }, + + _onDropDownMouseUp: function(/*Event?*/ e){ + // summary: + // Callback when the user lifts their mouse after mouse down on the arrow icon. + // If the drop down is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our + // drop down widget. If the event is missing, then we are not + // a mouseup event. + // + // This is useful for the common mouse movement pattern + // with native browser <select> nodes: + // 1. mouse down on the select node (probably on the arrow) + // 2. move mouse to a menu item while holding down the mouse button + // 3. mouse up. this selects the menu item as though the user had clicked it. + if(e && this._docHandler){ + this.disconnect(this._docHandler); + } + var dropDown = this.dropDown, overMenu = false; + + if(e && this._opened){ + // This code deals with the corner-case when the drop down covers the original widget, + // because it's so large. In that case mouse-up shouldn't select a value from the menu. + // Find out if our target is somewhere in our dropdown widget, + // but not over our _buttonNode (the clickable node) + var c = domGeometry.position(this._buttonNode, true); + if(!(e.pageX >= c.x && e.pageX <= c.x + c.w) || + !(e.pageY >= c.y && e.pageY <= c.y + c.h)){ + var t = e.target; + while(t && !overMenu){ + if(domClass.contains(t, "dijitPopup")){ + overMenu = true; + }else{ + t = t.parentNode; } } - var rn = radioName(child); - if(dojo.attr(child, "checked") && rn) { - radioSelected[rn] = child; + if(overMenu){ + t = e.target; + if(dropDown.onItemClick){ + var menuItem; + while(t && !(menuItem = registry.byNode(t))){ + t = t.parentNode; + } + if(menuItem && menuItem.onClick && menuItem.getParent){ + menuItem.getParent().onItemClick(menuItem, e); + } + } + return; } } - if(child.nodeName.toUpperCase() != 'SELECT'){ - walkTree(child); + } + if(this._opened){ + if(dropDown.focus && dropDown.autoFocus !== false){ + // Focus the dropdown widget - do it on a delay so that we + // don't steal our own focus. + window.setTimeout(lang.hitch(dropDown, "focus"), 1); } - }); - }; - if(shown(root)){ walkTree(root) } - function rs(node) { - // substitute checked radio button for unchecked one, if there is a checked one with the same name. - return radioSelected[radioName(node)] || node; - } - return { first: rs(first), last: rs(last), lowest: rs(lowest), highest: rs(highest) }; - } - dijit.getFirstInTabbingOrder = function(/*String|DOMNode*/ root){ - // summary: - // Finds the descendant of the specified root node - // that is first in the tabbing order - var elems = dijit._getTabNavigable(dojo.byId(root)); - return elems.lowest ? elems.lowest : elems.first; // DomNode - }; - - dijit.getLastInTabbingOrder = function(/*String|DOMNode*/ root){ - // summary: - // Finds the descendant of the specified root node - // that is last in the tabbing order - var elems = dijit._getTabNavigable(dojo.byId(root)); - return elems.last ? elems.last : elems.highest; // DomNode - }; - - /*===== - dojo.mixin(dijit, { - // defaultDuration: Integer - // The default animation speed (in ms) to use for all Dijit - // transitional animations, unless otherwise specified - // on a per-instance basis. Defaults to 200, overrided by - // `djConfig.defaultDuration` - defaultDuration: 200 - }); - =====*/ - - dijit.defaultDuration = dojo.config["defaultDuration"] || 200; + }else{ + // The drop down arrow icon probably can't receive focus, but widget itself should get focus. + // setTimeout() needed to make it work on IE (test DateTextBox) + setTimeout(lang.hitch(this, "focus"), 0); + } -})(); + if(has("ios")){ + this._justGotMouseUp = true; + setTimeout(lang.hitch(this, function(){ + this._justGotMouseUp = false; + }), 0); + } + }, -} + _onDropDownClick: function(/*Event*/ e){ + if(has("ios") && !this._justGotMouseUp){ + // This branch fires on iPhone for ComboBox, because the button node is an <input> and doesn't + // generate touchstart/touchend events. Pretend we just got a mouse down / mouse up. + // The if(has("ios") is necessary since IE and desktop safari get spurious onclick events + // when there are nested tables (specifically, clicking on a table that holds a dijit.form.Select, + // but not on the Select itself, causes an onclick event on the Select) + this._onDropDownMouseDown(e); + this._onDropDownMouseUp(e); + } -if(!dojo._hasResource["dijit._base.focus"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._base.focus"] = true; -dojo.provide("dijit._base.focus"); + // The drop down was already opened on mousedown/keydown; just need to call stopEvent(). + if(this._stopClickEvents){ + event.stop(e); + } + }, + buildRendering: function(){ + this.inherited(arguments); + this._buttonNode = this._buttonNode || this.focusNode || this.domNode; + this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode; + // Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow + // based on where drop down will normally appear + var defaultPos = { + "after" : this.isLeftToRight() ? "Right" : "Left", + "before" : this.isLeftToRight() ? "Left" : "Right", + "above" : "Up", + "below" : "Down", + "left" : "Left", + "right" : "Right" + }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down"; + domClass.add(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton"); + }, -// summary: -// These functions are used to query or set the focus and selection. -// -// Also, they trace when widgets become activated/deactivated, -// so that the widget can fire _onFocus/_onBlur events. -// "Active" here means something similar to "focused", but -// "focus" isn't quite the right word because we keep track of -// a whole stack of "active" widgets. Example: ComboButton --> Menu --> -// MenuItem. The onBlur event for ComboButton doesn't fire due to focusing -// on the Menu or a MenuItem, since they are considered part of the -// ComboButton widget. It only happens when focus is shifted -// somewhere completely different. - -dojo.mixin(dijit, { - // _curFocus: DomNode - // Currently focused item on screen - _curFocus: null, - - // _prevFocus: DomNode - // Previously focused item on screen - _prevFocus: null, - - isCollapsed: function(){ - // summary: - // Returns true if there is no text selected - return dijit.getBookmark().isCollapsed; - }, - - getBookmark: function(){ - // summary: - // Retrieves a bookmark that can be used with moveToBookmark to return to the same range - var bm, rg, tg, sel = dojo.doc.selection, cf = dijit._curFocus; - - if(dojo.global.getSelection){ - //W3C Range API for selections. - sel = dojo.global.getSelection(); - if(sel){ - if(sel.isCollapsed){ - tg = cf? cf.tagName : ""; - if(tg){ - //Create a fake rangelike item to restore selections. - tg = tg.toLowerCase(); - if(tg == "textarea" || - (tg == "input" && (!cf.type || cf.type.toLowerCase() == "text"))){ - sel = { - start: cf.selectionStart, - end: cf.selectionEnd, - node: cf, - pRange: true - }; - return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object. - } - } - bm = {isCollapsed:true}; - if(sel.rangeCount){ - bm.mark = sel.getRangeAt(0).cloneRange(); - } - }else{ - rg = sel.getRangeAt(0); - bm = {isCollapsed: false, mark: rg.cloneRange()}; - } - } - }else if(sel){ - // If the current focus was a input of some sort and no selection, don't bother saving - // a native bookmark. This is because it causes issues with dialog/page selection restore. - // So, we need to create psuedo bookmarks to work with. - tg = cf ? cf.tagName : ""; - tg = tg.toLowerCase(); - if(cf && tg && (tg == "button" || tg == "textarea" || tg == "input")){ - if(sel.type && sel.type.toLowerCase() == "none"){ - return { - isCollapsed: true, - mark: null - } - }else{ - rg = sel.createRange(); - return { - isCollapsed: rg.text && rg.text.length?false:true, - mark: { - range: rg, - pRange: true - } - }; + postCreate: function(){ + // summary: + // set up nodes and connect our mouse and keypress events + + this.inherited(arguments); + + this.connect(this._buttonNode, touch.press, "_onDropDownMouseDown"); + this.connect(this._buttonNode, "onclick", "_onDropDownClick"); + this.connect(this.focusNode, "onkeypress", "_onKey"); + this.connect(this.focusNode, "onkeyup", "_onKeyUp"); + }, + + destroy: function(){ + if(this.dropDown){ + // Destroy the drop down, unless it's already been destroyed. This can happen because + // the drop down is a direct child of <body> even though it's logically my child. + if(!this.dropDown._destroyed){ + this.dropDown.destroyRecursive(); } + delete this.dropDown; } - bm = {}; + this.inherited(arguments); + }, - //'IE' way for selections. - try{ - // createRange() throws exception when dojo in iframe - //and nothing selected, see #9632 - rg = sel.createRange(); - bm.isCollapsed = !(sel.type == 'Text' ? rg.htmlText.length : rg.length); - }catch(e){ - bm.isCollapsed = true; - return bm; - } - if(sel.type.toUpperCase() == 'CONTROL'){ - if(rg.length){ - bm.mark=[]; - var i=0,len=rg.length; - while(i<len){ - bm.mark.push(rg.item(i++)); - } - }else{ - bm.isCollapsed = true; - bm.mark = null; + _onKey: function(/*Event*/ e){ + // summary: + // Callback when the user presses a key while focused on the button node + + if(this.disabled || this.readOnly){ return; } + + var d = this.dropDown, target = e.target; + if(d && this._opened && d.handleKey){ + if(d.handleKey(e) === false){ + /* false return code means that the drop down handled the key */ + event.stop(e); + return; } + } + if(d && this._opened && e.charOrCode == keys.ESCAPE){ + this.closeDropDown(); + event.stop(e); + }else if(!this._opened && + (e.charOrCode == keys.DOWN_ARROW || + ( (e.charOrCode == keys.ENTER || e.charOrCode == " ") && + //ignore enter and space if the event is for a text input + ((target.tagName || "").toLowerCase() !== 'input' || + (target.type && target.type.toLowerCase() !== 'text'))))){ + // Toggle the drop down, but wait until keyup so that the drop down doesn't + // get a stray keyup event, or in the case of key-repeat (because user held + // down key for too long), stray keydown events + this._toggleOnKeyUp = true; + event.stop(e); + } + }, + + _onKeyUp: function(){ + if(this._toggleOnKeyUp){ + delete this._toggleOnKeyUp; + this.toggleDropDown(); + var d = this.dropDown; // drop down may not exist until toggleDropDown() call + if(d && d.focus){ + setTimeout(lang.hitch(d, "focus"), 1); + } + } + }, + + _onBlur: function(){ + // summary: + // Called magically when focus has shifted away from this widget and it's dropdown + + // Don't focus on button if the user has explicitly focused on something else (happens + // when user clicks another control causing the current popup to close).. + // But if focus is inside of the drop down then reset focus to me, because IE doesn't like + // it when you display:none a node with focus. + var focusMe = focus.curNode && this.dropDown && dom.isDescendant(focus.curNode, this.dropDown.domNode); + + this.closeDropDown(focusMe); + + this.inherited(arguments); + }, + + isLoaded: function(){ + // summary: + // Returns true if the dropdown exists and it's data is loaded. This can + // be overridden in order to force a call to loadDropDown(). + // tags: + // protected + + return true; + }, + + loadDropDown: function(/*Function*/ loadCallback){ + // summary: + // Creates the drop down if it doesn't exist, loads the data + // if there's an href and it hasn't been loaded yet, and then calls + // the given callback. + // tags: + // protected + + // TODO: for 2.0, change API to return a Deferred, instead of calling loadCallback? + loadCallback(); + }, + + loadAndOpenDropDown: function(){ + // summary: + // Creates the drop down if it doesn't exist, loads the data + // if there's an href and it hasn't been loaded yet, and + // then opens the drop down. This is basically a callback when the + // user presses the down arrow button to open the drop down. + // returns: Deferred + // Deferred for the drop down widget that + // fires when drop down is created and loaded + // tags: + // protected + var d = new Deferred(), + afterLoad = lang.hitch(this, function(){ + this.openDropDown(); + d.resolve(this.dropDown); + }); + if(!this.isLoaded()){ + this.loadDropDown(afterLoad); }else{ - bm.mark = rg.getBookmark(); + afterLoad(); } - }else{ - console.warn("No idea how to store the current selection for this browser!"); - } - return bm; // Object - }, + return d; + }, - moveToBookmark: function(/*Object*/bookmark){ - // summary: - // Moves current selection to a bookmark - // bookmark: - // This should be a returned object from dijit.getBookmark() + toggleDropDown: function(){ + // summary: + // Callback when the user presses the down arrow button or presses + // the down arrow key to open/close the drop down. + // Toggle the drop-down widget; if it is up, close it, if not, open it + // tags: + // protected - var _doc = dojo.doc, - mark = bookmark.mark; - if(mark){ - if(dojo.global.getSelection){ - //W3C Rangi API (FF, WebKit, Opera, etc) - var sel = dojo.global.getSelection(); - if(sel && sel.removeAllRanges){ - if(mark.pRange){ - var r = mark; - var n = r.node; - n.selectionStart = r.start; - n.selectionEnd = r.end; - }else{ - sel.removeAllRanges(); - sel.addRange(mark); + if(this.disabled || this.readOnly){ return; } + if(!this._opened){ + this.loadAndOpenDropDown(); + }else{ + this.closeDropDown(); + } + }, + + openDropDown: function(){ + // summary: + // Opens the dropdown for this widget. To be called only when this.dropDown + // has been created and is ready to display (ie, it's data is loaded). + // returns: + // return value of dijit.popup.open() + // tags: + // protected + + var dropDown = this.dropDown, + ddNode = dropDown.domNode, + aroundNode = this._aroundNode || this.domNode, + self = this; + + // Prepare our popup's height and honor maxHeight if it exists. + + // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(), + // ie, dependent on how much space is available (BK) + + if(!this._preparedNode){ + this._preparedNode = true; + // Check if we have explicitly set width and height on the dropdown widget dom node + if(ddNode.style.width){ + this._explicitDDWidth = true; + } + if(ddNode.style.height){ + this._explicitDDHeight = true; + } + } + + // Code for resizing dropdown (height limitation, or increasing width to match my width) + if(this.maxHeight || this.forceWidth || this.autoWidth){ + var myStyle = { + display: "", + visibility: "hidden" + }; + if(!this._explicitDDWidth){ + myStyle.width = ""; + } + if(!this._explicitDDHeight){ + myStyle.height = ""; + } + domStyle.set(ddNode, myStyle); + + // Figure out maximum height allowed (if there is a height restriction) + var maxHeight = this.maxHeight; + if(maxHeight == -1){ + // limit height to space available in viewport either above or below my domNode + // (whichever side has more room) + var viewport = winUtils.getBox(), + position = domGeometry.position(aroundNode, false); + maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h))); + } + + // Attach dropDown to DOM and make make visibility:hidden rather than display:none + // so we call startup() and also get the size + popup.moveOffScreen(dropDown); + + if(dropDown.startup && !dropDown._started){ + dropDown.startup(); // this has to be done after being added to the DOM + } + // Get size of drop down, and determine if vertical scroll bar needed + var mb = domGeometry.getMarginSize(ddNode); + var overHeight = (maxHeight && mb.h > maxHeight); + domStyle.set(ddNode, { + overflowX: "hidden", + overflowY: overHeight ? "auto" : "hidden" + }); + if(overHeight){ + mb.h = maxHeight; + if("w" in mb){ + mb.w += 16; // room for vertical scrollbar } }else{ - console.warn("No idea how to restore selection for this browser!"); - } - }else if(_doc.selection && mark){ - //'IE' way. - var rg; - if(mark.pRange){ - rg = mark.range; - }else if(dojo.isArray(mark)){ - rg = _doc.body.createControlRange(); - //rg.addElement does not have call/apply method, so can not call it directly - //rg is not available in "range.addElement(item)", so can't use that either - dojo.forEach(mark, function(n){ - rg.addElement(n); - }); + delete mb.h; + } + + // Adjust dropdown width to match or be larger than my width + if(this.forceWidth){ + mb.w = aroundNode.offsetWidth; + }else if(this.autoWidth){ + mb.w = Math.max(mb.w, aroundNode.offsetWidth); }else{ - rg = _doc.body.createTextRange(); - rg.moveToBookmark(mark); + delete mb.w; } - rg.select(); + + // And finally, resize the dropdown to calculated height and width + if(lang.isFunction(dropDown.resize)){ + dropDown.resize(mb); + }else{ + domGeometry.setMarginBox(ddNode, mb); + } + } + + var retVal = popup.open({ + parent: this, + popup: dropDown, + around: aroundNode, + orient: this.dropDownPosition, + onExecute: function(){ + self.closeDropDown(true); + }, + onCancel: function(){ + self.closeDropDown(true); + }, + onClose: function(){ + domAttr.set(self._popupStateNode, "popupActive", false); + domClass.remove(self._popupStateNode, "dijitHasDropDownOpen"); + self._opened = false; + } + }); + domAttr.set(this._popupStateNode, "popupActive", "true"); + domClass.add(self._popupStateNode, "dijitHasDropDownOpen"); + this._opened=true; + + // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown + return retVal; + }, + + closeDropDown: function(/*Boolean*/ focus){ + // summary: + // Closes the drop down on this widget + // focus: + // If true, refocuses the button widget + // tags: + // protected + + if(this._opened){ + if(focus){ this.focus(); } + popup.close(this.dropDown); + this._opened = false; } } - }, - getFocus: function(/*Widget?*/ menu, /*Window?*/ openedForWindow){ + }); +}); + +}, +'dijit/tree/TreeStoreModel':function(){ +define("dijit/tree/TreeStoreModel", [ + "dojo/_base/array", // array.filter array.forEach array.indexOf array.some + "dojo/aspect", // aspect.after + "dojo/_base/declare", // declare + "dojo/_base/json", // json.stringify + "dojo/_base/lang" // lang.hitch +], function(array, aspect, declare, json, lang){ + + // module: + // dijit/tree/TreeStoreModel + // summary: + // Implements dijit.Tree.model connecting to a dojo.data store with a single + // root item. + + return declare("dijit.tree.TreeStoreModel", null, { // summary: - // Called as getFocus(), this returns an Object showing the current focus - // and selected text. - // - // Called as getFocus(widget), where widget is a (widget representing) a button - // that was just pressed, it returns where focus was before that button - // was pressed. (Pressing the button may have either shifted focus to the button, - // or removed focus altogether.) In this case the selected text is not returned, - // since it can't be accurately determined. - // - // menu: dijit._Widget or {domNode: DomNode} structure - // The button that was just pressed. If focus has disappeared or moved - // to this button, returns the previous focus. In this case the bookmark - // information is already lost, and null is returned. + // Implements dijit.Tree.model connecting to a dojo.data store with a single + // root item. Any methods passed into the constructor will override + // the ones defined here. + + // store: dojo.data.Store + // Underlying store + store: null, + + // childrenAttrs: String[] + // One or more attribute names (attributes in the dojo.data item) that specify that item's children + childrenAttrs: ["children"], + + // newItemIdAttr: String + // Name of attribute in the Object passed to newItem() that specifies the id. // - // openedForWindow: - // iframe in which menu was opened + // If newItemIdAttr is set then it's used when newItem() is called to see if an + // item with the same id already exists, and if so just links to the old item + // (so that the old item ends up with two parents). // - // returns: - // A handle to restore focus/selection, to be passed to `dijit.focus` - var node = !dijit._curFocus || (menu && dojo.isDescendant(dijit._curFocus, menu.domNode)) ? dijit._prevFocus : dijit._curFocus; - return { - node: node, - bookmark: (node == dijit._curFocus) && dojo.withGlobal(openedForWindow || dojo.global, dijit.getBookmark), - openedForWindow: openedForWindow - }; // Object - }, + // Setting this to null or "" will make every drop create a new item. + newItemIdAttr: "id", - focus: function(/*Object || DomNode */ handle){ - // summary: - // Sets the focused node and the selection according to argument. - // To set focus to an iframe's content, pass in the iframe itself. - // handle: - // object returned by get(), or a DomNode + // labelAttr: String + // If specified, get label for tree node from this attribute, rather + // than by calling store.getLabel() + labelAttr: "", - if(!handle){ return; } + // root: [readonly] dojo.data.Item + // Pointer to the root item (read only, not a parameter) + root: null, - var node = "node" in handle ? handle.node : handle, // because handle is either DomNode or a composite object - bookmark = handle.bookmark, - openedForWindow = handle.openedForWindow, - collapsed = bookmark ? bookmark.isCollapsed : false; + // query: anything + // Specifies datastore query to return the root item for the tree. + // Must only return a single item. Alternately can just pass in pointer + // to root item. + // example: + // | {id:'ROOT'} + query: null, - // Set the focus - // Note that for iframe's we need to use the <iframe> to follow the parentNode chain, - // but we need to set focus to iframe.contentWindow - if(node){ - var focusNode = (node.tagName.toLowerCase() == "iframe") ? node.contentWindow : node; - if(focusNode && focusNode.focus){ - try{ - // Gecko throws sometimes if setting focus is impossible, - // node not displayed or something like that - focusNode.focus(); - }catch(e){/*quiet*/} + // deferItemLoadingUntilExpand: Boolean + // Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes + // until they are expanded. This allows for lazying loading where only one + // loadItem (and generally one network call, consequently) per expansion + // (rather than one for each child). + // This relies on partial loading of the children items; each children item of a + // fully loaded item should contain the label and info about having children. + deferItemLoadingUntilExpand: false, + + constructor: function(/* Object */ args){ + // summary: + // Passed the arguments listed above (store, etc) + // tags: + // private + + lang.mixin(this, args); + + this.connects = []; + + var store = this.store; + if(!store.getFeatures()['dojo.data.api.Identity']){ + throw new Error("dijit.Tree: store must support dojo.data.Identity"); } - dijit._onFocusNode(node); - } - // set the selection - // do not need to restore if current selection is not empty - // (use keyboard to select a menu item) or if previous selection was collapsed - // as it may cause focus shift (Esp in IE). - if(bookmark && dojo.withGlobal(openedForWindow || dojo.global, dijit.isCollapsed) && !collapsed){ - if(openedForWindow){ - openedForWindow.focus(); + // if the store supports Notification, subscribe to the notification events + if(store.getFeatures()['dojo.data.api.Notification']){ + this.connects = this.connects.concat([ + aspect.after(store, "onNew", lang.hitch(this, "onNewItem"), true), + aspect.after(store, "onDelete", lang.hitch(this, "onDeleteItem"), true), + aspect.after(store, "onSet", lang.hitch(this, "onSetItem"), true) + ]); } - try{ - dojo.withGlobal(openedForWindow || dojo.global, dijit.moveToBookmark, null, [bookmark]); - }catch(e2){ - /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */ + }, + + destroy: function(){ + var h; + while(h = this.connects.pop()){ h.remove(); } + // TODO: should cancel any in-progress processing of getRoot(), getChildren() + }, + + // ======================================================================= + // Methods for traversing hierarchy + + getRoot: function(onItem, onError){ + // summary: + // Calls onItem with the root item for the tree, possibly a fabricated item. + // Calls onError on error. + if(this.root){ + onItem(this.root); + }else{ + this.store.fetch({ + query: this.query, + onComplete: lang.hitch(this, function(items){ + if(items.length != 1){ + throw new Error(this.declaredClass + ": query " + json.stringify(this.query) + " returned " + items.length + + " items, but must return exactly one item"); + } + this.root = items[0]; + onItem(this.root); + }), + onError: onError + }); } - } - }, + }, - // _activeStack: dijit._Widget[] - // List of currently active widgets (focused widget and it's ancestors) - _activeStack: [], + mayHaveChildren: function(/*dojo.data.Item*/ item){ + // summary: + // Tells if an item has or may have children. Implementing logic here + // avoids showing +/- expando icon for nodes that we know don't have children. + // (For efficiency reasons we may not want to check if an element actually + // has children until user clicks the expando node) + return array.some(this.childrenAttrs, function(attr){ + return this.store.hasAttribute(item, attr); + }, this); + }, - registerIframe: function(/*DomNode*/ iframe){ - // summary: - // Registers listeners on the specified iframe so that any click - // or focus event on that iframe (or anything in it) is reported - // as a focus/click event on the <iframe> itself. - // description: - // Currently only used by editor. - // returns: - // Handle to pass to unregisterIframe() - return dijit.registerWin(iframe.contentWindow, iframe); - }, + getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){ + // summary: + // Calls onComplete() with array of child items of given parent item, all loaded. - unregisterIframe: function(/*Object*/ handle){ - // summary: - // Unregisters listeners on the specified iframe created by registerIframe. - // After calling be sure to delete or null out the handle itself. - // handle: - // Handle returned by registerIframe() + var store = this.store; + if(!store.isItemLoaded(parentItem)){ + // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand + // mode, so we will load it and just return the children (without loading each + // child item) + var getChildren = lang.hitch(this, arguments.callee); + store.loadItem({ + item: parentItem, + onItem: function(parentItem){ + getChildren(parentItem, onComplete, onError); + }, + onError: onError + }); + return; + } + // get children of specified item + var childItems = []; + for(var i=0; i<this.childrenAttrs.length; i++){ + var vals = store.getValues(parentItem, this.childrenAttrs[i]); + childItems = childItems.concat(vals); + } - dijit.unregisterWin(handle); - }, + // count how many items need to be loaded + var _waitCount = 0; + if(!this.deferItemLoadingUntilExpand){ + array.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } }); + } - registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){ - // summary: - // Registers listeners on the specified window (either the main - // window or an iframe's window) to detect when the user has clicked somewhere - // or focused somewhere. - // description: - // Users should call registerIframe() instead of this method. - // targetWindow: - // If specified this is the window associated with the iframe, - // i.e. iframe.contentWindow. - // effectiveNode: - // If specified, report any focus events inside targetWindow as - // an event on effectiveNode, rather than on evt.target. - // returns: - // Handle to pass to unregisterWin() + if(_waitCount == 0){ + // all items are already loaded (or we aren't loading them). proceed... + onComplete(childItems); + }else{ + // still waiting for some or all of the items to load + array.forEach(childItems, function(item, idx){ + if(!store.isItemLoaded(item)){ + store.loadItem({ + item: item, + onItem: function(item){ + childItems[idx] = item; + if(--_waitCount == 0){ + // all nodes have been loaded, send them to the tree + onComplete(childItems); + } + }, + onError: onError + }); + } + }); + } + }, - // TODO: make this function private in 2.0; Editor/users should call registerIframe(), + // ======================================================================= + // Inspecting items - var mousedownListener = function(evt){ - dijit._justMouseDowned = true; - setTimeout(function(){ dijit._justMouseDowned = false; }, 0); - - // workaround weird IE bug where the click is on an orphaned node - // (first time clicking a Select/DropDownButton inside a TooltipDialog) - if(dojo.isIE && evt && evt.srcElement && evt.srcElement.parentNode == null){ - return; + isItem: function(/* anything */ something){ + return this.store.isItem(something); // Boolean + }, + + fetchItemByIdentity: function(/* object */ keywordArgs){ + this.store.fetchItemByIdentity(keywordArgs); + }, + + getIdentity: function(/* item */ item){ + return this.store.getIdentity(item); // Object + }, + + getLabel: function(/*dojo.data.Item*/ item){ + // summary: + // Get the label for an item + if(this.labelAttr){ + return this.store.getValue(item,this.labelAttr); // String + }else{ + return this.store.getLabel(item); // String } + }, - dijit._onTouchNode(effectiveNode || evt.target || evt.srcElement, "mouse"); - }; - //dojo.connect(targetWindow, "onscroll", ???); - - // Listen for blur and focus events on targetWindow's document. - // IIRC, I'm using attachEvent() rather than dojo.connect() because focus/blur events don't bubble - // through dojo.connect(), and also maybe to catch the focus events early, before onfocus handlers - // fire. - // Connect to <html> (rather than document) on IE to avoid memory leaks, but document on other browsers because - // (at least for FF) the focus event doesn't fire on <html> or <body>. - var doc = dojo.isIE ? targetWindow.document.documentElement : targetWindow.document; - if(doc){ - if(dojo.isIE){ - targetWindow.document.body.attachEvent('onmousedown', mousedownListener); - var activateListener = function(evt){ - // IE reports that nodes like <body> have gotten focus, even though they have tabIndex=-1, - // Should consider those more like a mouse-click than a focus.... - if(evt.srcElement.tagName.toLowerCase() != "#document" && - dijit.isTabNavigable(evt.srcElement)){ - dijit._onFocusNode(effectiveNode || evt.srcElement); + // ======================================================================= + // Write interface + + newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){ + // summary: + // Creates a new item. See `dojo.data.api.Write` for details on args. + // Used in drag & drop when item from external source dropped onto tree. + // description: + // Developers will need to override this method if new items get added + // to parents with multiple children attributes, in order to define which + // children attribute points to the new item. + + var pInfo = {parent: parent, attribute: this.childrenAttrs[0]}, LnewItem; + + if(this.newItemIdAttr && args[this.newItemIdAttr]){ + // Maybe there's already a corresponding item in the store; if so, reuse it. + this.fetchItemByIdentity({identity: args[this.newItemIdAttr], scope: this, onItem: function(item){ + if(item){ + // There's already a matching item in store, use it + this.pasteItem(item, null, parent, true, insertIndex); }else{ - dijit._onTouchNode(effectiveNode || evt.srcElement); + // Create new item in the tree, based on the drag source. + LnewItem=this.store.newItem(args, pInfo); + if(LnewItem && (insertIndex!=undefined)){ + // Move new item to desired position + this.pasteItem(LnewItem, parent, parent, false, insertIndex); + } } - }; - doc.attachEvent('onactivate', activateListener); - var deactivateListener = function(evt){ - dijit._onBlurNode(effectiveNode || evt.srcElement); - }; - doc.attachEvent('ondeactivate', deactivateListener); + }}); + }else{ + // [as far as we know] there is no id so we must assume this is a new item + LnewItem=this.store.newItem(args, pInfo); + if(LnewItem && (insertIndex!=undefined)){ + // Move new item to desired position + this.pasteItem(LnewItem, parent, parent, false, insertIndex); + } + } + }, - return function(){ - targetWindow.document.detachEvent('onmousedown', mousedownListener); - doc.detachEvent('onactivate', activateListener); - doc.detachEvent('ondeactivate', deactivateListener); - doc = null; // prevent memory leak (apparent circular reference via closure) - }; + pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){ + // summary: + // Move or copy an item from one parent item to another. + // Used in drag & drop + var store = this.store, + parentAttr = this.childrenAttrs[0]; // name of "children" attr in parent item + + // remove child from source item, and record the attribute that child occurred in + if(oldParentItem){ + array.forEach(this.childrenAttrs, function(attr){ + if(store.containsValue(oldParentItem, attr, childItem)){ + if(!bCopy){ + var values = array.filter(store.getValues(oldParentItem, attr), function(x){ + return x != childItem; + }); + store.setValues(oldParentItem, attr, values); + } + parentAttr = attr; + } + }); + } + + // modify target item's children attribute to include this item + if(newParentItem){ + if(typeof insertIndex == "number"){ + // call slice() to avoid modifying the original array, confusing the data store + var childItems = store.getValues(newParentItem, parentAttr).slice(); + childItems.splice(insertIndex, 0, childItem); + store.setValues(newParentItem, parentAttr, childItems); + }else{ + store.setValues(newParentItem, parentAttr, + store.getValues(newParentItem, parentAttr).concat(childItem)); + } + } + }, + + // ======================================================================= + // Callbacks + + onChange: function(/*dojo.data.Item*/ /*===== item =====*/){ + // summary: + // Callback whenever an item has changed, so that Tree + // can update the label, icon, etc. Note that changes + // to an item's children or parent(s) will trigger an + // onChildrenChange() so you can ignore those changes here. + // tags: + // callback + }, + + onChildrenChange: function(/*===== parent, newChildrenList =====*/){ + // summary: + // Callback to do notifications about new, updated, or deleted items. + // parent: dojo.data.Item + // newChildrenList: dojo.data.Item[] + // tags: + // callback + }, + + onDelete: function(/*dojo.data.Item*/ /*===== item =====*/){ + // summary: + // Callback when an item has been deleted. + // description: + // Note that there will also be an onChildrenChange() callback for the parent + // of this item. + // tags: + // callback + }, + + // ======================================================================= + // Events from data store + + onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ + // summary: + // Handler for when new items appear in the store, either from a drop operation + // or some other way. Updates the tree view (if necessary). + // description: + // If the new item is a child of an existing item, + // calls onChildrenChange() with the new list of children + // for that existing item. + // + // tags: + // extension + + // We only care about the new item if it has a parent that corresponds to a TreeNode + // we are currently displaying + if(!parentInfo){ + return; + } + + // Call onChildrenChange() on parent (ie, existing) item with new list of children + // In the common case, the new list of children is simply parentInfo.newValue or + // [ parentInfo.newValue ], although if items in the store has multiple + // child attributes (see `childrenAttr`), then it's a superset of parentInfo.newValue, + // so call getChildren() to be sure to get right answer. + this.getChildren(parentInfo.item, lang.hitch(this, function(children){ + this.onChildrenChange(parentInfo.item, children); + })); + }, + + onDeleteItem: function(/*Object*/ item){ + // summary: + // Handler for delete notifications from underlying store + this.onDelete(item); + }, + + onSetItem: function(item, attribute /*===== , oldValue, newValue =====*/){ + // summary: + // Updates the tree view according to changes in the data store. + // description: + // Handles updates to an item's children by calling onChildrenChange(), and + // other updates to an item by calling onChange(). + // + // See `onNewItem` for more details on handling updates to an item's children. + // item: Item + // attribute: attribute-name-string + // oldValue: object | array + // newValue: object | array + // tags: + // extension + + if(array.indexOf(this.childrenAttrs, attribute) != -1){ + // item's children list changed + this.getChildren(item, lang.hitch(this, function(children){ + // See comments in onNewItem() about calling getChildren() + this.onChildrenChange(item, children); + })); }else{ - doc.body.addEventListener('mousedown', mousedownListener, true); - var focusListener = function(evt){ - dijit._onFocusNode(effectiveNode || evt.target); - }; - doc.addEventListener('focus', focusListener, true); - var blurListener = function(evt){ - dijit._onBlurNode(effectiveNode || evt.target); - }; - doc.addEventListener('blur', blurListener, true); + // item's label/icon/etc. changed. + this.onChange(item); + } + } + }); +}); - return function(){ - doc.body.removeEventListener('mousedown', mousedownListener, true); - doc.removeEventListener('focus', focusListener, true); - doc.removeEventListener('blur', blurListener, true); - doc = null; // prevent memory leak (apparent circular reference via closure) - }; +}, +'dijit/_MenuBase':function(){ +define("dijit/_MenuBase", [ + "./popup", + "dojo/window", + "./_Widget", + "./_KeyNavContainer", + "./_TemplatedMixin", + "dojo/_base/declare", // declare + "dojo/dom", // dom.isDescendant domClass.replace + "dojo/dom-attr", + "dojo/dom-class", // domClass.replace + "dojo/_base/lang", // lang.hitch + "dojo/_base/array" // array.indexOf +], function(pm, winUtils, _Widget, _KeyNavContainer, _TemplatedMixin, + declare, dom, domAttr, domClass, lang, array){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _KeyNavContainer = dijit._KeyNavContainer; +=====*/ + +// module: +// dijit/_MenuBase +// summary: +// Base class for Menu and MenuBar + +return declare("dijit._MenuBase", + [_Widget, _TemplatedMixin, _KeyNavContainer], +{ + // summary: + // Base class for Menu and MenuBar + + // parentMenu: [readonly] Widget + // pointer to menu that displayed me + parentMenu: null, + + // popupDelay: Integer + // number of milliseconds before hovering (without clicking) causes the popup to automatically open. + popupDelay: 500, + + onExecute: function(){ + // summary: + // Attach point for notification about when a menu item has been executed. + // This is an internal mechanism used for Menus to signal to their parent to + // close them, because they are about to execute the onClick handler. In + // general developers should not attach to or override this method. + // tags: + // protected + }, + + onCancel: function(/*Boolean*/ /*===== closeAll =====*/){ + // summary: + // Attach point for notification about when the user cancels the current menu + // This is an internal mechanism used for Menus to signal to their parent to + // close them. In general developers should not attach to or override this method. + // tags: + // protected + }, + + _moveToPopup: function(/*Event*/ evt){ + // summary: + // This handles the right arrow key (left arrow key on RTL systems), + // which will either open a submenu, or move to the next item in the + // ancestor MenuBar + // tags: + // private + + if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){ + this.focusedChild._onClick(evt); + }else{ + var topMenu = this._getTopMenu(); + if(topMenu && topMenu._isMenuBar){ + topMenu.focusNext(); } } }, - unregisterWin: function(/*Handle*/ handle){ + _onPopupHover: function(/*Event*/ /*===== evt =====*/){ // summary: - // Unregisters listeners on the specified window (either the main - // window or an iframe's window) according to handle returned from registerWin(). - // After calling be sure to delete or null out the handle itself. + // This handler is called when the mouse moves over the popup. + // tags: + // private - // Currently our handle is actually a function - handle && handle(); + // if the mouse hovers over a menu popup that is in pending-close state, + // then stop the close operation. + // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker) + if(this.currentPopup && this.currentPopup._pendingClose_timer){ + var parentMenu = this.currentPopup.parentMenu; + // highlight the parent menu item pointing to this popup + if(parentMenu.focusedChild){ + parentMenu.focusedChild._setSelected(false); + } + parentMenu.focusedChild = this.currentPopup.from_item; + parentMenu.focusedChild._setSelected(true); + // cancel the pending close + this._stopPendingCloseTimer(this.currentPopup); + } }, - _onBlurNode: function(/*DomNode*/ node){ + onItemHover: function(/*MenuItem*/ item){ // summary: - // Called when focus leaves a node. - // Usually ignored, _unless_ it *isn't* follwed by touching another node, - // which indicates that we tabbed off the last field on the page, - // in which case every widget is marked inactive - dijit._prevFocus = dijit._curFocus; - dijit._curFocus = null; + // Called when cursor is over a MenuItem. + // tags: + // protected - if(dijit._justMouseDowned){ - // the mouse down caused a new widget to be marked as active; this blur event - // is coming late, so ignore it. - return; + // Don't do anything unless user has "activated" the menu by: + // 1) clicking it + // 2) opening it from a parent menu (which automatically focuses it) + if(this.isActive){ + this.focusChild(item); + if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){ + this.hover_timer = setTimeout(lang.hitch(this, "_openPopup"), this.popupDelay); + } + } + // if the user is mixing mouse and keyboard navigation, + // then the menu may not be active but a menu item has focus, + // but it's not the item that the mouse just hovered over. + // To avoid both keyboard and mouse selections, use the latest. + if(this.focusedChild){ + this.focusChild(item); } + this._hoveredChild = item; + }, - // if the blur event isn't followed by a focus event then mark all widgets as inactive. - if(dijit._clearActiveWidgetsTimer){ - clearTimeout(dijit._clearActiveWidgetsTimer); + _onChildBlur: function(item){ + // summary: + // Called when a child MenuItem becomes inactive because focus + // has been removed from the MenuItem *and* it's descendant menus. + // tags: + // private + this._stopPopupTimer(); + item._setSelected(false); + // Close all popups that are open and descendants of this menu + var itemPopup = item.popup; + if(itemPopup){ + this._stopPendingCloseTimer(itemPopup); + itemPopup._pendingClose_timer = setTimeout(function(){ + itemPopup._pendingClose_timer = null; + if(itemPopup.parentMenu){ + itemPopup.parentMenu.currentPopup = null; + } + pm.close(itemPopup); // this calls onClose + }, this.popupDelay); } - dijit._clearActiveWidgetsTimer = setTimeout(function(){ - delete dijit._clearActiveWidgetsTimer; - dijit._setStack([]); - dijit._prevFocus = null; - }, 100); }, - _onTouchNode: function(/*DomNode*/ node, /*String*/ by){ + onItemUnhover: function(/*MenuItem*/ item){ // summary: - // Callback when node is focused or mouse-downed - // node: - // The node that was touched. - // by: - // "mouse" if the focus/touch was caused by a mouse down event + // Callback fires when mouse exits a MenuItem + // tags: + // protected - // ignore the recent blurNode event - if(dijit._clearActiveWidgetsTimer){ - clearTimeout(dijit._clearActiveWidgetsTimer); - delete dijit._clearActiveWidgetsTimer; + if(this.isActive){ + this._stopPopupTimer(); } + if(this._hoveredChild == item){ this._hoveredChild = null; } + }, - // compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem) - var newStack=[]; - try{ - while(node){ - var popupParent = dojo.attr(node, "dijitPopupParent"); - if(popupParent){ - node=dijit.byId(popupParent).domNode; - }else if(node.tagName && node.tagName.toLowerCase() == "body"){ - // is this the root of the document or just the root of an iframe? - if(node === dojo.body()){ - // node is the root of the main document - break; - } - // otherwise, find the iframe this node refers to (can't access it via parentNode, - // need to do this trick instead). window.frameElement is supported in IE/FF/Webkit - node=dojo.window.get(node.ownerDocument).frameElement; - }else{ - // if this node is the root node of a widget, then add widget id to stack, - // except ignore clicks on disabled widgets (actually focusing a disabled widget still works, - // to support MenuItem) - var id = node.getAttribute && node.getAttribute("widgetId"), - widget = id && dijit.byId(id); - if(widget && !(by == "mouse" && widget.get("disabled"))){ - newStack.unshift(id); - } - node=node.parentNode; - } - } - }catch(e){ /* squelch */ } + _stopPopupTimer: function(){ + // summary: + // Cancels the popup timer because the user has stop hovering + // on the MenuItem, etc. + // tags: + // private + if(this.hover_timer){ + clearTimeout(this.hover_timer); + this.hover_timer = null; + } + }, + + _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){ + // summary: + // Cancels the pending-close timer because the close has been preempted + // tags: + // private + if(popup._pendingClose_timer){ + clearTimeout(popup._pendingClose_timer); + popup._pendingClose_timer = null; + } + }, - dijit._setStack(newStack, by); + _stopFocusTimer: function(){ + // summary: + // Cancels the pending-focus timer because the menu was closed before focus occured + // tags: + // private + if(this._focus_timer){ + clearTimeout(this._focus_timer); + this._focus_timer = null; + } + }, + + _getTopMenu: function(){ + // summary: + // Returns the top menu in this chain of Menus + // tags: + // private + for(var top=this; top.parentMenu; top=top.parentMenu); + return top; }, - _onFocusNode: function(/*DomNode*/ node){ + onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){ // summary: - // Callback when node is focused + // Handle clicks on an item. + // tags: + // private - if(!node){ - return; + // this can't be done in _onFocus since the _onFocus events occurs asynchronously + if(typeof this.isShowingNow == 'undefined'){ // non-popup menu + this._markActive(); } - if(node.nodeType == 9){ - // Ignore focus events on the document itself. This is here so that - // (for example) clicking the up/down arrows of a spinner - // (which don't get focus) won't cause that widget to blur. (FF issue) - return; + this.focusChild(item); + + if(item.disabled){ return false; } + + if(item.popup){ + this._openPopup(); + }else{ + // before calling user defined handler, close hierarchy of menus + // and restore focus to place it was when menu was opened + this.onExecute(); + + // user defined handler for click + item.onClick(evt); } + }, - dijit._onTouchNode(node); + _openPopup: function(){ + // summary: + // Open the popup to the side of/underneath the current menu item + // tags: + // protected - if(node == dijit._curFocus){ return; } - if(dijit._curFocus){ - dijit._prevFocus = dijit._curFocus; + this._stopPopupTimer(); + var from_item = this.focusedChild; + if(!from_item){ return; } // the focused child lost focus since the timer was started + var popup = from_item.popup; + if(popup.isShowingNow){ return; } + if(this.currentPopup){ + this._stopPendingCloseTimer(this.currentPopup); + pm.close(this.currentPopup); + } + popup.parentMenu = this; + popup.from_item = from_item; // helps finding the parent item that should be focused for this popup + var self = this; + pm.open({ + parent: this, + popup: popup, + around: from_item.domNode, + orient: this._orient || ["after", "before"], + onCancel: function(){ // called when the child menu is canceled + // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus + // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro) + self.focusChild(from_item); // put focus back on my node + self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved) + from_item._setSelected(true); // oops, _cleanUp() deselected the item + self.focusedChild = from_item; // and unset focusedChild + }, + onExecute: lang.hitch(this, "_cleanUp") + }); + + this.currentPopup = popup; + // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items + popup.connect(popup.domNode, "onmouseenter", lang.hitch(self, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close + + if(popup.focus){ + // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar), + // if the cursor happens to collide with the popup, it will generate an onmouseover event + // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that + // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742) + popup._focus_timer = setTimeout(lang.hitch(popup, function(){ + this._focus_timer = null; + this.focus(); + }), 0); } - dijit._curFocus = node; - dojo.publish("focusNode", [node]); }, - _setStack: function(/*String[]*/ newStack, /*String*/ by){ + _markActive: function(){ // summary: - // The stack of active widgets has changed. Send out appropriate events and records new stack. - // newStack: - // array of widget id's, starting from the top (outermost) widget - // by: - // "mouse" if the focus/touch was caused by a mouse down event + // Mark this menu's state as active. + // Called when this Menu gets focus from: + // 1) clicking it (mouse or via space/arrow key) + // 2) being opened by a parent menu. + // This is not called just from mouse hover. + // Focusing a menu via TAB does NOT automatically set isActive + // since TAB is a navigation operation and not a selection one. + // For Windows apps, pressing the ALT key focuses the menubar + // menus (similar to TAB navigation) but the menu is not active + // (ie no dropdown) until an item is clicked. + this.isActive = true; + domClass.replace(this.domNode, "dijitMenuActive", "dijitMenuPassive"); + }, - var oldStack = dijit._activeStack; - dijit._activeStack = newStack; + onOpen: function(/*Event*/ /*===== e =====*/){ + // summary: + // Callback when this menu is opened. + // This is called by the popup manager as notification that the menu + // was opened. + // tags: + // private - // compare old stack to new stack to see how many elements they have in common - for(var nCommon=0; nCommon<Math.min(oldStack.length, newStack.length); nCommon++){ - if(oldStack[nCommon] != newStack[nCommon]){ - break; + this.isShowingNow = true; + this._markActive(); + }, + + _markInactive: function(){ + // summary: + // Mark this menu's state as inactive. + this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here + domClass.replace(this.domNode, "dijitMenuPassive", "dijitMenuActive"); + }, + + onClose: function(){ + // summary: + // Callback when this menu is closed. + // This is called by the popup manager as notification that the menu + // was closed. + // tags: + // private + + this._stopFocusTimer(); + this._markInactive(); + this.isShowingNow = false; + this.parentMenu = null; + }, + + _closeChild: function(){ + // summary: + // Called when submenu is clicked or focus is lost. Close hierarchy of menus. + // tags: + // private + this._stopPopupTimer(); + + if(this.currentPopup){ + // If focus is on a descendant MenuItem then move focus to me, + // because IE doesn't like it when you display:none a node with focus, + // and also so keyboard users don't lose control. + // Likely, immediately after a user defined onClick handler will move focus somewhere + // else, like a Dialog. + if(array.indexOf(this._focusManager.activeStack, this.id) >= 0){ + domAttr.set(this.focusedChild.focusNode, "tabIndex", this.tabIndex); + this.focusedChild.focusNode.focus(); } + // Close all popups that are open and descendants of this menu + pm.close(this.currentPopup); + this.currentPopup = null; } - var widget; - // for all elements that have gone out of focus, send blur event - for(var i=oldStack.length-1; i>=nCommon; i--){ - widget = dijit.byId(oldStack[i]); - if(widget){ - widget._focused = false; - widget.set("focused", false); - widget._hasBeenBlurred = true; - if(widget._onBlur){ - widget._onBlur(by); - } - dojo.publish("widgetBlur", [widget, by]); - } + if(this.focusedChild){ // unhighlight the focused item + this.focusedChild._setSelected(false); + this.focusedChild._onUnhover(); + this.focusedChild = null; } + }, - // for all element that have come into focus, send focus event - for(i=nCommon; i<newStack.length; i++){ - widget = dijit.byId(newStack[i]); - if(widget){ - widget._focused = true; - widget.set("focused", true); - if(widget._onFocus){ - widget._onFocus(by); - } - dojo.publish("widgetFocus", [widget, by]); - } + _onItemFocus: function(/*MenuItem*/ item){ + // summary: + // Called when child of this Menu gets focus from: + // 1) clicking it + // 2) tabbing into it + // 3) being opened by a parent menu. + // This is not called just from mouse hover. + if(this._hoveredChild && this._hoveredChild != item){ + this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection + } + }, + + _onBlur: function(){ + // summary: + // Called when focus is moved away from this Menu and it's submenus. + // tags: + // protected + this._cleanUp(); + this.inherited(arguments); + }, + + _cleanUp: function(){ + // summary: + // Called when the user is done with this menu. Closes hierarchy of menus. + // tags: + // private + + this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close + if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose + this._markInactive(); } } }); -// register top window and all the iframes it contains -dojo.addOnLoad(function(){ - var handle = dijit.registerWin(window); - if(dojo.isIE){ - dojo.addOnWindowUnload(function(){ - dijit.unregisterWin(handle); - handle = null; - }) - } }); -} +}, +'dijit/focus':function(){ +define("dijit/focus", [ + "dojo/aspect", + "dojo/_base/declare", // declare + "dojo/dom", // domAttr.get dom.isDescendant + "dojo/dom-attr", // domAttr.get dom.isDescendant + "dojo/dom-construct", // connect to domConstruct.empty, domConstruct.destroy + "dojo/Evented", + "dojo/_base/lang", // lang.hitch + "dojo/on", + "dojo/ready", + "dojo/_base/sniff", // has("ie") + "dojo/Stateful", + "dojo/_base/unload", // unload.addOnWindowUnload + "dojo/_base/window", // win.body + "dojo/window", // winUtils.get + "./a11y", // a11y.isTabNavigable + "./registry", // registry.byId + "." // to set dijit.focus +], function(aspect, declare, dom, domAttr, domConstruct, Evented, lang, on, ready, has, Stateful, unload, win, winUtils, + a11y, registry, dijit){ + + // module: + // dijit/focus + // summary: + // Returns a singleton that tracks the currently focused node, and which widgets are currently "active". -if(!dojo._hasResource["dojo.AdapterRegistry"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.AdapterRegistry"] = true; -dojo.provide("dojo.AdapterRegistry"); +/*===== + dijit.focus = { + // summary: + // Tracks the currently focused node, and which widgets are currently "active". + // Access via require(["dijit/focus"], function(focus){ ... }). + // + // A widget is considered active if it or a descendant widget has focus, + // or if a non-focusable node of this widget or a descendant was recently clicked. + // + // Call focus.watch("curNode", callback) to track the current focused DOMNode, + // or focus.watch("activeStack", callback) to track the currently focused stack of widgets. + // + // Call focus.on("widget-blur", func) or focus.on("widget-focus", ...) to monitor when + // when widgets become active/inactive + // + // Finally, focus(node) will focus a node, suppressing errors if the node doesn't exist. + // curNode: DomNode + // Currently focused item on screen + curNode: null, -dojo.AdapterRegistry = function(/*Boolean?*/ returnWrappers){ - // summary: - // A registry to make contextual calling/searching easier. - // description: - // Objects of this class keep list of arrays in the form [name, check, - // wrap, directReturn] that are used to determine what the contextual - // result of a set of checked arguments is. All check/wrap functions - // in this registry should be of the same arity. - // example: - // | // create a new registry - // | var reg = new dojo.AdapterRegistry(); - // | reg.register("handleString", - // | dojo.isString, - // | function(str){ - // | // do something with the string here - // | } - // | ); - // | reg.register("handleArr", - // | dojo.isArray, - // | function(arr){ - // | // do something with the array here - // | } - // | ); - // | - // | // now we can pass reg.match() *either* an array or a string and - // | // the value we pass will get handled by the right function - // | reg.match("someValue"); // will call the first function - // | reg.match(["someValue"]); // will call the second + // activeStack: dijit._Widget[] + // List of currently active widgets (focused widget and it's ancestors) + activeStack: [], - this.pairs = []; - this.returnWrappers = returnWrappers || false; // Boolean -}; + registerIframe: function(iframe){ + // summary: + // Registers listeners on the specified iframe so that any click + // or focus event on that iframe (or anything in it) is reported + // as a focus/click event on the <iframe> itself. + // description: + // Currently only used by editor. + // returns: + // Handle with remove() method to deregister. + }, -dojo.extend(dojo.AdapterRegistry, { - register: function(/*String*/ name, /*Function*/ check, /*Function*/ wrap, /*Boolean?*/ directReturn, /*Boolean?*/ override){ - // summary: - // register a check function to determine if the wrap function or - // object gets selected - // name: - // a way to identify this matcher. - // check: - // a function that arguments are passed to from the adapter's - // match() function. The check function should return true if the - // given arguments are appropriate for the wrap function. - // directReturn: - // If directReturn is true, the value passed in for wrap will be - // returned instead of being called. Alternately, the - // AdapterRegistry can be set globally to "return not call" using - // the returnWrappers property. Either way, this behavior allows - // the registry to act as a "search" function instead of a - // function interception library. - // override: - // If override is given and true, the check function will be given - // highest priority. Otherwise, it will be the lowest priority - // adapter. - this.pairs[((override) ? "unshift" : "push")]([name, check, wrap, directReturn]); - }, - - match: function(/* ... */){ - // summary: - // Find an adapter for the given arguments. If no suitable adapter - // is found, throws an exception. match() accepts any number of - // arguments, all of which are passed to all matching functions - // from the registered pairs. - for(var i = 0; i < this.pairs.length; i++){ - var pair = this.pairs[i]; - if(pair[1].apply(this, arguments)){ - if((pair[3])||(this.returnWrappers)){ - return pair[2]; + registerWin: function(targetWindow, effectiveNode){ + // summary: + // Registers listeners on the specified window (either the main + // window or an iframe's window) to detect when the user has clicked somewhere + // or focused somewhere. + // description: + // Users should call registerIframe() instead of this method. + // targetWindow: Window? + // If specified this is the window associated with the iframe, + // i.e. iframe.contentWindow. + // effectiveNode: DOMNode? + // If specified, report any focus events inside targetWindow as + // an event on effectiveNode, rather than on evt.target. + // returns: + // Handle with remove() method to deregister. + } + }; +=====*/ + + var FocusManager = declare([Stateful, Evented], { + // curNode: DomNode + // Currently focused item on screen + curNode: null, + + // activeStack: dijit._Widget[] + // List of currently active widgets (focused widget and it's ancestors) + activeStack: [], + + constructor: function(){ + // Don't leave curNode/prevNode pointing to bogus elements + var check = lang.hitch(this, function(node){ + if(dom.isDescendant(this.curNode, node)){ + this.set("curNode", null); + } + if(dom.isDescendant(this.prevNode, node)){ + this.set("prevNode", null); + } + }); + aspect.before(domConstruct, "empty", check); + aspect.before(domConstruct, "destroy", check); + }, + + registerIframe: function(/*DomNode*/ iframe){ + // summary: + // Registers listeners on the specified iframe so that any click + // or focus event on that iframe (or anything in it) is reported + // as a focus/click event on the <iframe> itself. + // description: + // Currently only used by editor. + // returns: + // Handle with remove() method to deregister. + return this.registerWin(iframe.contentWindow, iframe); + }, + + registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){ + // summary: + // Registers listeners on the specified window (either the main + // window or an iframe's window) to detect when the user has clicked somewhere + // or focused somewhere. + // description: + // Users should call registerIframe() instead of this method. + // targetWindow: + // If specified this is the window associated with the iframe, + // i.e. iframe.contentWindow. + // effectiveNode: + // If specified, report any focus events inside targetWindow as + // an event on effectiveNode, rather than on evt.target. + // returns: + // Handle with remove() method to deregister. + + // TODO: make this function private in 2.0; Editor/users should call registerIframe(), + + var _this = this; + var mousedownListener = function(evt){ + _this._justMouseDowned = true; + setTimeout(function(){ _this._justMouseDowned = false; }, 0); + + // workaround weird IE bug where the click is on an orphaned node + // (first time clicking a Select/DropDownButton inside a TooltipDialog) + if(has("ie") && evt && evt.srcElement && evt.srcElement.parentNode == null){ + return; + } + + _this._onTouchNode(effectiveNode || evt.target || evt.srcElement, "mouse"); + }; + + // Listen for blur and focus events on targetWindow's document. + // IIRC, I'm using attachEvent() rather than dojo.connect() because focus/blur events don't bubble + // through dojo.connect(), and also maybe to catch the focus events early, before onfocus handlers + // fire. + // Connect to <html> (rather than document) on IE to avoid memory leaks, but document on other browsers because + // (at least for FF) the focus event doesn't fire on <html> or <body>. + var doc = has("ie") ? targetWindow.document.documentElement : targetWindow.document; + if(doc){ + if(has("ie")){ + targetWindow.document.body.attachEvent('onmousedown', mousedownListener); + var activateListener = function(evt){ + // IE reports that nodes like <body> have gotten focus, even though they have tabIndex=-1, + // ignore those events + var tag = evt.srcElement.tagName.toLowerCase(); + if(tag == "#document" || tag == "body"){ return; } + + // Previous code called _onTouchNode() for any activate event on a non-focusable node. Can + // probably just ignore such an event as it will be handled by onmousedown handler above, but + // leaving the code for now. + if(a11y.isTabNavigable(evt.srcElement)){ + _this._onFocusNode(effectiveNode || evt.srcElement); + }else{ + _this._onTouchNode(effectiveNode || evt.srcElement); + } + }; + doc.attachEvent('onactivate', activateListener); + var deactivateListener = function(evt){ + _this._onBlurNode(effectiveNode || evt.srcElement); + }; + doc.attachEvent('ondeactivate', deactivateListener); + + return { + remove: function(){ + targetWindow.document.detachEvent('onmousedown', mousedownListener); + doc.detachEvent('onactivate', activateListener); + doc.detachEvent('ondeactivate', deactivateListener); + doc = null; // prevent memory leak (apparent circular reference via closure) + } + }; }else{ - return pair[2].apply(this, arguments); + doc.body.addEventListener('mousedown', mousedownListener, true); + doc.body.addEventListener('touchstart', mousedownListener, true); + var focusListener = function(evt){ + _this._onFocusNode(effectiveNode || evt.target); + }; + doc.addEventListener('focus', focusListener, true); + var blurListener = function(evt){ + _this._onBlurNode(effectiveNode || evt.target); + }; + doc.addEventListener('blur', blurListener, true); + + return { + remove: function(){ + doc.body.removeEventListener('mousedown', mousedownListener, true); + doc.body.removeEventListener('touchstart', mousedownListener, true); + doc.removeEventListener('focus', focusListener, true); + doc.removeEventListener('blur', blurListener, true); + doc = null; // prevent memory leak (apparent circular reference via closure) + } + }; } } - } - throw new Error("No match found"); - }, + }, - unregister: function(name){ - // summary: Remove a named adapter from the registry + _onBlurNode: function(/*DomNode*/ /*===== node =====*/){ + // summary: + // Called when focus leaves a node. + // Usually ignored, _unless_ it *isn't* followed by touching another node, + // which indicates that we tabbed off the last field on the page, + // in which case every widget is marked inactive + this.set("prevNode", this.curNode); + this.set("curNode", null); + + if(this._justMouseDowned){ + // the mouse down caused a new widget to be marked as active; this blur event + // is coming late, so ignore it. + return; + } - // FIXME: this is kind of a dumb way to handle this. On a large - // registry this will be slow-ish and we can use the name as a lookup - // should we choose to trade memory for speed. - for(var i = 0; i < this.pairs.length; i++){ - var pair = this.pairs[i]; - if(pair[0] == name){ - this.pairs.splice(i, 1); - return true; + // if the blur event isn't followed by a focus event then mark all widgets as inactive. + if(this._clearActiveWidgetsTimer){ + clearTimeout(this._clearActiveWidgetsTimer); } - } - return false; - } -}); + this._clearActiveWidgetsTimer = setTimeout(lang.hitch(this, function(){ + delete this._clearActiveWidgetsTimer; + this._setStack([]); + this.prevNode = null; + }), 100); + }, -} + _onTouchNode: function(/*DomNode*/ node, /*String*/ by){ + // summary: + // Callback when node is focused or mouse-downed + // node: + // The node that was touched. + // by: + // "mouse" if the focus/touch was caused by a mouse down event -if(!dojo._hasResource["dijit._base.place"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._base.place"] = true; -dojo.provide("dijit._base.place"); + // ignore the recent blurNode event + if(this._clearActiveWidgetsTimer){ + clearTimeout(this._clearActiveWidgetsTimer); + delete this._clearActiveWidgetsTimer; + } + // compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem) + var newStack=[]; + try{ + while(node){ + var popupParent = domAttr.get(node, "dijitPopupParent"); + if(popupParent){ + node=registry.byId(popupParent).domNode; + }else if(node.tagName && node.tagName.toLowerCase() == "body"){ + // is this the root of the document or just the root of an iframe? + if(node === win.body()){ + // node is the root of the main document + break; + } + // otherwise, find the iframe this node refers to (can't access it via parentNode, + // need to do this trick instead). window.frameElement is supported in IE/FF/Webkit + node=winUtils.get(node.ownerDocument).frameElement; + }else{ + // if this node is the root node of a widget, then add widget id to stack, + // except ignore clicks on disabled widgets (actually focusing a disabled widget still works, + // to support MenuItem) + var id = node.getAttribute && node.getAttribute("widgetId"), + widget = id && registry.byId(id); + if(widget && !(by == "mouse" && widget.get("disabled"))){ + newStack.unshift(id); + } + node=node.parentNode; + } + } + }catch(e){ /* squelch */ } + this._setStack(newStack, by); + }, + _onFocusNode: function(/*DomNode*/ node){ + // summary: + // Callback when node is focused -dijit.getViewport = function(){ - // summary: - // Returns the dimensions and scroll position of the viewable area of a browser window + if(!node){ + return; + } - return dojo.window.getBox(); -}; + if(node.nodeType == 9){ + // Ignore focus events on the document itself. This is here so that + // (for example) clicking the up/down arrows of a spinner + // (which don't get focus) won't cause that widget to blur. (FF issue) + return; + } -/*===== -dijit.__Position = function(){ - // x: Integer - // horizontal coordinate in pixels, relative to document body - // y: Integer - // vertical coordinate in pixels, relative to document body - - thix.x = x; - this.y = y; -} -=====*/ + this._onTouchNode(node); + if(node == this.curNode){ return; } + this.set("curNode", node); + }, -dijit.placeOnScreen = function( - /* DomNode */ node, - /* dijit.__Position */ pos, - /* String[] */ corners, - /* dijit.__Position? */ padding){ - // summary: - // Positions one of the node's corners at specified position - // such that node is fully visible in viewport. - // description: - // NOTE: node is assumed to be absolutely or relatively positioned. - // pos: - // Object like {x: 10, y: 20} - // corners: - // Array of Strings representing order to try corners in, like ["TR", "BL"]. - // Possible values are: - // * "BL" - bottom left - // * "BR" - bottom right - // * "TL" - top left - // * "TR" - top right - // padding: - // set padding to put some buffer around the element you want to position. - // example: - // Try to place node's top right corner at (10,20). - // If that makes node go (partially) off screen, then try placing - // bottom left corner at (10,20). - // | placeOnScreen(node, {x: 10, y: 20}, ["TR", "BL"]) - - var choices = dojo.map(corners, function(corner){ - var c = { corner: corner, pos: {x:pos.x,y:pos.y} }; - if(padding){ - c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x; - c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y; - } - return c; - }); + _setStack: function(/*String[]*/ newStack, /*String*/ by){ + // summary: + // The stack of active widgets has changed. Send out appropriate events and records new stack. + // newStack: + // array of widget id's, starting from the top (outermost) widget + // by: + // "mouse" if the focus/touch was caused by a mouse down event + + var oldStack = this.activeStack; + this.set("activeStack", newStack); + + // compare old stack to new stack to see how many elements they have in common + for(var nCommon=0; nCommon<Math.min(oldStack.length, newStack.length); nCommon++){ + if(oldStack[nCommon] != newStack[nCommon]){ + break; + } + } - return dijit._place(node, choices); -} + var widget; + // for all elements that have gone out of focus, set focused=false + for(var i=oldStack.length-1; i>=nCommon; i--){ + widget = registry.byId(oldStack[i]); + if(widget){ + widget._hasBeenBlurred = true; // TODO: used by form widgets, should be moved there + widget.set("focused", false); + if(widget._focusManager == this){ + widget._onBlur(by); + } + this.emit("widget-blur", widget, by); + } + } -dijit._place = function(/*DomNode*/ node, choices, layoutNode, /*Object*/ aroundNodeCoords){ - // summary: - // Given a list of spots to put node, put it at the first spot where it fits, - // of if it doesn't fit anywhere then the place with the least overflow - // choices: Array - // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} } - // Above example says to put the top-left corner of the node at (10,20) - // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size) - // for things like tooltip, they are displayed differently (and have different dimensions) - // based on their orientation relative to the parent. This adjusts the popup based on orientation. - // It also passes in the available size for the popup, which is useful for tooltips to - // tell them that their width is limited to a certain amount. layoutNode() may return a value expressing - // how much the popup had to be modified to fit into the available space. This is used to determine - // what the best placement is. - // aroundNodeCoords: Object - // Size of aroundNode, ex: {w: 200, h: 50} - - // get {x: 10, y: 10, w: 100, h:100} type obj representing position of - // viewport over document - var view = dojo.window.getBox(); - - // This won't work if the node is inside a <div style="position: relative">, - // so reattach it to dojo.doc.body. (Otherwise, the positioning will be wrong - // and also it might get cutoff) - if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){ - dojo.body().appendChild(node); - } + // for all element that have come into focus, set focused=true + for(i=nCommon; i<newStack.length; i++){ + widget = registry.byId(newStack[i]); + if(widget){ + widget.set("focused", true); + if(widget._focusManager == this){ + widget._onFocus(by); + } + this.emit("widget-focus", widget, by); + } + } + }, - var best = null; - dojo.some(choices, function(choice){ - var corner = choice.corner; - var pos = choice.pos; - var overflow = 0; + focus: function(node){ + // summary: + // Focus the specified node, suppressing errors if they occur + if(node){ + try{ node.focus(); }catch(e){/*quiet*/} + } + } + }); - // calculate amount of space available given specified position of node - var spaceAvailable = { - w: corner.charAt(1) == 'L' ? (view.l + view.w) - pos.x : pos.x - view.l, - h: corner.charAt(1) == 'T' ? (view.t + view.h) - pos.y : pos.y - view.t - }; + var singleton = new FocusManager(); - // configure node to be displayed in given position relative to button - // (need to do this in order to get an accurate size for the node, because - // a tooltip's size changes based on position, due to triangle) - if(layoutNode){ - var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords); - overflow = typeof res == "undefined" ? 0 : res; - } - - // get node's size - var style = node.style; - var oldDisplay = style.display; - var oldVis = style.visibility; - style.visibility = "hidden"; - style.display = ""; - var mb = dojo.marginBox(node); - style.display = oldDisplay; - style.visibility = oldVis; - - // coordinates and size of node with specified corner placed at pos, - // and clipped by viewport - var startX = Math.max(view.l, corner.charAt(1) == 'L' ? pos.x : (pos.x - mb.w)), - startY = Math.max(view.t, corner.charAt(0) == 'T' ? pos.y : (pos.y - mb.h)), - endX = Math.min(view.l + view.w, corner.charAt(1) == 'L' ? (startX + mb.w) : pos.x), - endY = Math.min(view.t + view.h, corner.charAt(0) == 'T' ? (startY + mb.h) : pos.y), - width = endX - startX, - height = endY - startY; - - overflow += (mb.w - width) + (mb.h - height); - - if(best == null || overflow < best.overflow){ - best = { - corner: corner, - aroundCorner: choice.aroundCorner, - x: startX, - y: startY, - w: width, - h: height, - overflow: overflow, - spaceAvailable: spaceAvailable - }; + // register top window and all the iframes it contains + ready(function(){ + var handle = singleton.registerWin(win.doc.parentWindow || win.doc.defaultView); + if(has("ie")){ + unload.addOnWindowUnload(function(){ + handle.remove(); + handle = null; + }) } - - return !overflow; }); - // In case the best position is not the last one we checked, need to call - // layoutNode() again. - if(best.overflow && layoutNode){ - layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords); + // Setup dijit.focus as a pointer to the singleton but also (for backwards compatibility) + // as a function to set focus. + dijit.focus = function(node){ + singleton.focus(node); // indirection here allows dijit/_base/focus.js to override behavior + }; + for(var attr in singleton){ + if(!/^_/.test(attr)){ + dijit.focus[attr] = typeof singleton[attr] == "function" ? lang.hitch(singleton, attr) : singleton[attr]; + } } + singleton.watch(function(attr, oldVal, newVal){ + dijit.focus[attr] = newVal; + }); - // And then position the node. Do this last, after the layoutNode() above - // has sized the node, due to browser quirks when the viewport is scrolled - // (specifically that a Tooltip will shrink to fit as though the window was - // scrolled to the left). - // - // In RTL mode, set style.right rather than style.left so in the common case, - // window resizes move the popup along with the aroundNode. - var l = dojo._isBodyLtr(), - s = node.style; - s.top = best.y + "px"; - s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px"; - - return best; -} - -dijit.placeOnScreenAroundNode = function( - /* DomNode */ node, - /* DomNode */ aroundNode, - /* Object */ aroundCorners, - /* Function? */ layoutNode){ + return singleton; +}); +}, +'dojo/i18n':function(){ +define("dojo/i18n", ["./_base/kernel", "require", "./has", "./_base/array", "./_base/config", "./_base/lang", "./_base/xhr", "./json"], + function(dojo, require, has, array, config, lang, xhr, json) { + // module: + // dojo/i18n // summary: - // Position node adjacent or kitty-corner to aroundNode - // such that it's fully visible in viewport. - // + // This module implements the !dojo/i18n plugin and the v1.6- i18n API // description: - // Place node such that corner of node touches a corner of - // aroundNode, and that node is fully visible. - // - // aroundCorners: - // Ordered list of pairs of corners to try matching up. - // Each pair of corners is represented as a key/value in the hash, - // where the key corresponds to the aroundNode's corner, and - // the value corresponds to the node's corner: - // - // | { aroundNodeCorner1: nodeCorner1, aroundNodeCorner2: nodeCorner2, ...} - // - // The following strings are used to represent the four corners: - // * "BL" - bottom left - // * "BR" - bottom right - // * "TL" - top left - // * "TR" - top right - // - // layoutNode: Function(node, aroundNodeCorner, nodeCorner) - // For things like tooltip, they are displayed differently (and have different dimensions) - // based on their orientation relative to the parent. This adjusts the popup based on orientation. - // - // example: - // | dijit.placeOnScreenAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'}); - // This will try to position node such that node's top-left corner is at the same position - // as the bottom left corner of the aroundNode (ie, put node below - // aroundNode, with left edges aligned). If that fails it will try to put - // the bottom-right corner of node where the top right corner of aroundNode is - // (ie, put node above aroundNode, with right edges aligned) - // + // We choose to include our own plugin to leverage functionality already contained in dojo + // and thereby reduce the size of the plugin compared to various loader implementations. Also, this + // allows foreign AMD loaders to be used without their plugins. - // get coordinates of aroundNode - aroundNode = dojo.byId(aroundNode); - var aroundNodePos = dojo.position(aroundNode, true); - // place the node around the calculated rectangle - return dijit._placeOnScreenAroundRect(node, - aroundNodePos.x, aroundNodePos.y, aroundNodePos.w, aroundNodePos.h, // rectangle - aroundCorners, layoutNode); -}; + has.add("dojo-preload-i18n-Api", + // if true, define the preload localizations machinery + 1 + ); -/*===== -dijit.__Rectangle = function(){ - // x: Integer - // horizontal offset in pixels, relative to document body - // y: Integer - // vertical offset in pixels, relative to document body - // width: Integer - // width in pixels - // height: Integer - // height in pixels - - this.x = x; - this.y = y; - this.width = width; - this.height = height; -} -=====*/ + true || has.add("dojo-v1x-i18n-Api", + // if true, define the v1.x i18n functions + 1 + ); + var + thisModule= dojo.i18n= + // the dojo.i18n module + {}, + + nlsRe= + // regexp for reconstructing the master bundle name from parts of the regexp match + // nlsRe.exec("foo/bar/baz/nls/en-ca/foo") gives: + // ["foo/bar/baz/nls/en-ca/foo", "foo/bar/baz/nls/", "/", "/", "en-ca", "foo"] + // nlsRe.exec("foo/bar/baz/nls/foo") gives: + // ["foo/bar/baz/nls/foo", "foo/bar/baz/nls/", "/", "/", "foo", ""] + // so, if match[5] is blank, it means this is the top bundle definition. + // courtesy of http://requirejs.org + /(^.*(^|\/)nls)(\/|$)([^\/]*)\/?([^\/]*)/, + + getAvailableLocales= function( + root, + locale, + bundlePath, + bundleName + ){ + // return a vector of module ids containing all available locales with respect to the target locale + // For example, assuming: + // * the root bundle indicates specific bundles for "fr" and "fr-ca", + // * bundlePath is "myPackage/nls" + // * bundleName is "myBundle" + // Then a locale argument of "fr-ca" would return + // ["myPackage/nls/myBundle", "myPackage/nls/fr/myBundle", "myPackage/nls/fr-ca/myBundle"] + // Notice that bundles are returned least-specific to most-specific, starting with the root. + // + // If root===false indicates we're working with a pre-AMD i18n bundle that doesn't tell about the available locales; + // therefore, assume everything is available and get 404 errors that indicate a particular localization is not available + // -dijit.placeOnScreenAroundRectangle = function( - /* DomNode */ node, - /* dijit.__Rectangle */ aroundRect, - /* Object */ aroundCorners, - /* Function */ layoutNode){ + for(var result= [bundlePath + bundleName], localeParts= locale.split("-"), current= "", i= 0; i<localeParts.length; i++){ + current+= (current ? "-" : "") + localeParts[i]; + if(!root || root[current]){ + result.push(bundlePath + current + "/" + bundleName); + } + } + return result; + }, - // summary: - // Like dijit.placeOnScreenAroundNode(), except that the "around" - // parameter is an arbitrary rectangle on the screen (x, y, width, height) - // instead of a dom node. + cache= {}, - return dijit._placeOnScreenAroundRect(node, - aroundRect.x, aroundRect.y, aroundRect.width, aroundRect.height, // rectangle - aroundCorners, layoutNode); -}; + getL10nName= dojo.getL10nName = function(moduleName, bundleName, locale){ + locale = locale ? locale.toLowerCase() : dojo.locale; + moduleName = "dojo/i18n!" + moduleName.replace(/\./g, "/"); + bundleName = bundleName.replace(/\./g, "/"); + return (/root/i.test(locale)) ? + (moduleName + "/nls/" + bundleName) : + (moduleName + "/nls/" + locale + "/" + bundleName); + }, -dijit._placeOnScreenAroundRect = function( - /* DomNode */ node, - /* Number */ x, - /* Number */ y, - /* Number */ width, - /* Number */ height, - /* Object */ aroundCorners, - /* Function */ layoutNode){ + doLoad = function(require, bundlePathAndName, bundlePath, bundleName, locale, load){ + // get the root bundle which instructs which other bundles are required to construct the localized bundle + require([bundlePathAndName], function(root){ + var current= lang.clone(root.root), + availableLocales= getAvailableLocales(!root._v1x && root, locale, bundlePath, bundleName); + require(availableLocales, function(){ + for (var i= 1; i<availableLocales.length; i++){ + current= lang.mixin(lang.clone(current), arguments[i]); + } + // target may not have been resolve (e.g., maybe only "fr" exists when "fr-ca" was requested) + var target= bundlePathAndName + "/" + locale; + cache[target]= current; + load(); + }); + }); + }, - // summary: - // Like dijit.placeOnScreenAroundNode(), except it accepts coordinates - // of a rectangle to place node adjacent to. + normalize = function(id, toAbsMid){ + // id may be relative + // preload has form *preload*<path>/nls/<module>*<flattened locales> and + // therefore never looks like a relative + return /^\./.test(id) ? toAbsMid(id) : id; + }, - // TODO: combine with placeOnScreenAroundRectangle() + getLocalesToLoad = function(targetLocale){ + var list = config.extraLocale || []; + list = lang.isArray(list) ? list : [list]; + list.push(targetLocale); + return list; + }, - // Generate list of possible positions for node - var choices = []; - for(var nodeCorner in aroundCorners){ - choices.push( { - aroundCorner: nodeCorner, - corner: aroundCorners[nodeCorner], - pos: { - x: x + (nodeCorner.charAt(1) == 'L' ? 0 : width), - y: y + (nodeCorner.charAt(0) == 'T' ? 0 : height) + load = function(id, require, load){ + // + // id is in one of the following formats + // + // 1. <path>/nls/<bundle> + // => load the bundle, localized to config.locale; load all bundles localized to + // config.extraLocale (if any); return the loaded bundle localized to config.locale. + // + // 2. <path>/nls/<locale>/<bundle> + // => load then return the bundle localized to <locale> + // + // 3. *preload*<path>/nls/<module>*<JSON array of available locales> + // => for config.locale and all config.extraLocale, load all bundles found + // in the best-matching bundle rollup. A value of 1 is returned, which + // is meaningless other than to say the plugin is executing the requested + // preloads + // + // In cases 1 and 2, <path> is always normalized to an absolute module id upon entry; see + // normalize. In case 3, it <path> is assumed to be absolue; this is arranged by the builder. + // + // To load a bundle means to insert the bundle into the plugin's cache and publish the bundle + // value to the loader. Given <path>, <bundle>, and a particular <locale>, the cache key + // + // <path>/nls/<bundle>/<locale> + // + // will hold the value. Similarly, then plugin will publish this value to the loader by + // + // define("<path>/nls/<bundle>/<locale>", <bundle-value>); + // + // Given this algorithm, other machinery can provide fast load paths be preplacing + // values in the plugin's cache, which is public. When a load is demanded the + // cache is inspected before starting any loading. Explicitly placing values in the plugin + // cache is an advanced/experimental feature that should not be needed; use at your own risk. + // + // For the normal AMD algorithm, the root bundle is loaded first, which instructs the + // plugin what additional localized bundles are required for a particular locale. These + // additional locales are loaded and a mix of the root and each progressively-specific + // locale is returned. For example: + // + // 1. The client demands "dojo/i18n!some/path/nls/someBundle + // + // 2. The loader demands load(some/path/nls/someBundle) + // + // 3. This plugin require's "some/path/nls/someBundle", which is the root bundle. + // + // 4. Assuming config.locale is "ab-cd-ef" and the root bundle indicates that localizations + // are available for "ab" and "ab-cd-ef" (note the missing "ab-cd", then the plugin + // requires "some/path/nls/ab/someBundle" and "some/path/nls/ab-cd-ef/someBundle" + // + // 5. Upon receiving all required bundles, the plugin constructs the value of the bundle + // ab-cd-ef as... + // + // mixin(mixin(mixin({}, require("some/path/nls/someBundle"), + // require("some/path/nls/ab/someBundle")), + // require("some/path/nls/ab-cd-ef/someBundle")); + // + // This value is inserted into the cache and published to the loader at the + // key/module-id some/path/nls/someBundle/ab-cd-ef. + // + // The special preload signature (case 3) instructs the plugin to stop servicing all normal requests + // (further preload requests will be serviced) until all ongoing preloading has completed. + // + // The preload signature instructs the plugin that a special rollup module is available that contains + // one or more flattened, localized bundles. The JSON array of available locales indicates which locales + // are available. Here is an example: + // + // *preload*some/path/nls/someModule*["root", "ab", "ab-cd-ef"] + // + // This indicates the following rollup modules are available: + // + // some/path/nls/someModule_ROOT + // some/path/nls/someModule_ab + // some/path/nls/someModule_ab-cd-ef + // + // Each of these modules is a normal AMD module that contains one or more flattened bundles in a hash. + // For example, assume someModule contained the bundles some/bundle/path/someBundle and + // some/bundle/path/someOtherBundle, then some/path/nls/someModule_ab would be expressed as folllows: + // + // define({ + // some/bundle/path/someBundle:<value of someBundle, flattened with respect to locale ab>, + // some/bundle/path/someOtherBundle:<value of someOtherBundle, flattened with respect to locale ab>, + // }); + // + // E.g., given this design, preloading for locale=="ab" can execute the following algorithm: + // + // require(["some/path/nls/someModule_ab"], function(rollup){ + // for(var p in rollup){ + // var id = p + "/ab", + // cache[id] = rollup[p]; + // define(id, rollup[p]); + // } + // }); + // + // Similarly, if "ab-cd" is requested, the algorithm can determine that "ab" is the best available and + // load accordingly. + // + // The builder will write such rollups for every layer if a non-empty localeList profile property is + // provided. Further, the builder will include the following cache entry in the cache associated with + // any layer. + // + // "*now":function(r){r(['dojo/i18n!*preload*<path>/nls/<module>*<JSON array of available locales>']);} + // + // The *now special cache module instructs the loader to apply the provided function to context-require + // with respect to the particular layer being defined. This causes the plugin to hold all normal service + // requests until all preloading is complete. + // + // Notice that this algorithm is rarely better than the standard AMD load algorithm. Consider the normal case + // where the target locale has a single segment and a layer depends on a single bundle: + // + // Without Preloads: + // + // 1. Layer loads root bundle. + // 2. bundle is demanded; plugin loads single localized bundle. + // + // With Preloads: + // + // 1. Layer causes preloading of target bundle. + // 2. bundle is demanded; service is delayed until preloading complete; bundle is returned. + // + // In each case a single transaction is required to load the target bundle. In cases where multiple bundles + // are required and/or the locale has multiple segments, preloads still requires a single transaction whereas + // the normal path requires an additional transaction for each additional bundle/locale-segment. However all + // of these additional transactions can be done concurrently. Owing to this analysis, the entire preloading + // algorithm can be discard during a build by setting the has feature dojo-preload-i18n-Api to false. + // + if(has("dojo-preload-i18n-Api")){ + var split = id.split("*"), + preloadDemand = split[1]=="preload"; + if(preloadDemand){ + if(!cache[id]){ + // use cache[id] to prevent multiple preloads of the same preload; this shouldn't happen, but + // who knows what over-aggressive human optimizers may attempt + cache[id] = 1; + preloadL10n(split[2], json.parse(split[3]), 1); + } + // don't stall the loader! + load(1); + } + if(preloadDemand || waitForPreloads(id, require, load)){ + return; + } } - }); + + var match= nlsRe.exec(id), + bundlePath= match[1] + "/", + bundleName= match[5] || match[4], + bundlePathAndName= bundlePath + bundleName, + localeSpecified = (match[5] && match[4]), + targetLocale= localeSpecified || dojo.locale, + loadTarget= bundlePathAndName + "/" + targetLocale, + loadList = localeSpecified ? [targetLocale] : getLocalesToLoad(targetLocale), + remaining = loadList.length, + finish = function(){ + if(!--remaining){ + load(lang.delegate(cache[loadTarget])); + } + }; + array.forEach(loadList, function(locale){ + var target = bundlePathAndName + "/" + locale; + if(has("dojo-preload-i18n-Api")){ + checkForLegacyModules(target); + } + if(!cache[target]){ + doLoad(require, bundlePathAndName, bundlePath, bundleName, locale, finish); + }else{ + finish(); + } + }); + }; + + if(has("dojo-unit-tests")){ + var unitTests = thisModule.unitTests = []; } - return dijit._place(node, choices, layoutNode, {w: width, h: height}); -}; + if(has("dojo-preload-i18n-Api") || 1){ + var normalizeLocale = thisModule.normalizeLocale= function(locale){ + var result = locale ? locale.toLowerCase() : dojo.locale; + return result == "root" ? "ROOT" : result; + }, -dijit.placementRegistry= new dojo.AdapterRegistry(); -dijit.placementRegistry.register("node", - function(n, x){ - return typeof x == "object" && - typeof x.offsetWidth != "undefined" && typeof x.offsetHeight != "undefined"; - }, - dijit.placeOnScreenAroundNode); -dijit.placementRegistry.register("rect", - function(n, x){ - return typeof x == "object" && - "x" in x && "y" in x && "width" in x && "height" in x; - }, - dijit.placeOnScreenAroundRectangle); + isXd = function(mid){ + return (1 && 1) ? + require.isXdUrl(require.toUrl(mid + ".js")) : + true; + }, -dijit.placeOnScreenAroundElement = function( - /* DomNode */ node, - /* Object */ aroundElement, - /* Object */ aroundCorners, - /* Function */ layoutNode){ + preloading = 0, - // summary: - // Like dijit.placeOnScreenAroundNode(), except it accepts an arbitrary object - // for the "around" argument and finds a proper processor to place a node. + preloadWaitQueue = [], - return dijit.placementRegistry.match.apply(dijit.placementRegistry, arguments); -}; + preloadL10n = thisModule._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated, /*boolean*/ guaranteedAmdFormat){ + // summary: + // Load available flattened resource bundles associated with a particular module for dojo.locale and all dojo.config.extraLocale (if any) + // + // descirption: + // Only called by built layer files. The entire locale hierarchy is loaded. For example, + // if locale=="ab-cd", then ROOT, "ab", and "ab-cd" are loaded. This is different than v1.6- + // in that the v1.6- would lonly load ab-cd...which was *always* flattened. + // + // If guaranteedAmdFormat is true, then the module can be loaded with require thereby circumventing the detection algorithm + // and the extra possible extra transaction. + // + + function forEachLocale(locale, func){ + // given locale= "ab-cd-ef", calls func on "ab-cd-ef", "ab-cd", "ab", "ROOT"; stops calling the first time func returns truthy + var parts = locale.split("-"); + while(parts.length){ + if(func(parts.join("-"))){ + return true; + } + parts.pop(); + } + return func("ROOT"); + } -dijit.getPopupAroundAlignment = function(/*Array*/ position, /*Boolean*/ leftToRight){ - // summary: - // Transforms the passed array of preferred positions into a format suitable for passing as the aroundCorners argument to dijit.placeOnScreenAroundElement. - // - // position: String[] - // This variable controls the position of the drop down. - // It's an array of strings with the following values: - // - // * before: places drop down to the left of the target node/widget, or to the right in - // the case of RTL scripts like Hebrew and Arabic - // * after: places drop down to the right of the target node/widget, or to the left in - // the case of RTL scripts like Hebrew and Arabic - // * above: drop down goes above target node - // * below: drop down goes below target node - // - // The list is positions is tried, in order, until a position is found where the drop down fits - // within the viewport. - // - // leftToRight: Boolean - // Whether the popup will be displaying in leftToRight mode. - // - var align = {}; - dojo.forEach(position, function(pos){ - switch(pos){ - case "after": - align[leftToRight ? "BR" : "BL"] = leftToRight ? "BL" : "BR"; - break; - case "before": - align[leftToRight ? "BL" : "BR"] = leftToRight ? "BR" : "BL"; - break; - case "below-alt": - leftToRight = !leftToRight; - // fall through - case "below": - // first try to align left borders, next try to align right borders (or reverse for RTL mode) - align[leftToRight ? "BL" : "BR"] = leftToRight ? "TL" : "TR"; - align[leftToRight ? "BR" : "BL"] = leftToRight ? "TR" : "TL"; - break; - case "above-alt": - leftToRight = !leftToRight; - // fall through - case "above": - default: - // first try to align left borders, next try to align right borders (or reverse for RTL mode) - align[leftToRight ? "TL" : "TR"] = leftToRight ? "BL" : "BR"; - align[leftToRight ? "TR" : "TL"] = leftToRight ? "BR" : "BL"; - break; - } - }); - return align; -}; + function preload(locale){ + locale = normalizeLocale(locale); + forEachLocale(locale, function(loc){ + if(array.indexOf(localesGenerated, loc)>=0){ + var mid = bundlePrefix.replace(/\./g, "/")+"_"+loc; + preloading++; + (isXd(mid) || guaranteedAmdFormat ? require : syncRequire)([mid], function(rollup){ + for(var p in rollup){ + cache[p + "/" + locale] = rollup[p]; + } + --preloading; + while(!preloading && preloadWaitQueue.length){ + load.apply(null, preloadWaitQueue.shift()); + } + }); + return true; + } + return false; + }); + } -} + preload(); + array.forEach(dojo.config.extraLocale, preload); + }, -if(!dojo._hasResource["dijit._base.window"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._base.window"] = true; -dojo.provide("dijit._base.window"); + waitForPreloads = function(id, require, load){ + if(preloading){ + preloadWaitQueue.push([id, require, load]); + } + return preloading; + }; + } + if(1){ + // this code path assumes the dojo loader and won't work with a standard AMD loader + var evalBundle= + // use the function ctor to keep the minifiers away (also come close to global scope, but this is secondary) + new Function( + "__bundle", // the bundle to evalutate + "__checkForLegacyModules", // a function that checks if __bundle defined __mid in the global space + "__mid", // the mid that __bundle is intended to define + + // returns one of: + // 1 => the bundle was an AMD bundle + // a legacy bundle object that is the value of __mid + // instance of Error => could not figure out how to evaluate bundle + + // used to detect when __bundle calls define + "var define = function(){define.called = 1;}," + + " require = function(){define.called = 1;};" + + + "try{" + + "define.called = 0;" + + "eval(__bundle);" + + "if(define.called==1)" + // bundle called define; therefore signal it's an AMD bundle + + "return 1;" + + + "if((__checkForLegacyModules = __checkForLegacyModules(__mid)))" + // bundle was probably a v1.6- built NLS flattened NLS bundle that defined __mid in the global space + + "return __checkForLegacyModules;" + + + "}catch(e){}" + // evaulating the bundle was *neither* an AMD *nor* a legacy flattened bundle + // either way, re-eval *after* surrounding with parentheses + + + "try{" + + "return eval('('+__bundle+')');" + + "}catch(e){" + + "return e;" + + "}" + ), + + syncRequire= function(deps, callback){ + var results= []; + array.forEach(deps, function(mid){ + var url= require.toUrl(mid + ".js"); + + function load(text){ + var result = evalBundle(text, checkForLegacyModules, mid); + if(result===1){ + // the bundle was an AMD module; re-inject it through the normal AMD path + // we gotta do this since it could be an anonymous module and simply evaluating + // the text here won't provide the loader with the context to know what + // module is being defined()'d. With browser caching, this should be free; further + // this entire code path can be circumvented by using the AMD format to begin with + require([mid], function(bundle){ + results.push(cache[url]= bundle); + }); + }else{ + if(result instanceof Error){ + console.error("failed to evaluate i18n bundle; url=" + url, result); + result = {}; + } + // nls/<locale>/<bundle-name> indicates not the root. + results.push(cache[url] = (/nls\/[^\/]+\/[^\/]+$/.test(url) ? result : {root:result, _v1x:1})); + } + } + if(cache[url]){ + results.push(cache[url]); + }else{ + var bundle= require.syncLoadNls(mid); + // don't need to check for legacy since syncLoadNls returns a module if the module + // (1) was already loaded, or (2) was in the cache. In case 1, if syncRequire is called + // from getLocalization --> load, then load will have called checkForLegacyModules() before + // calling syncRequire; if syncRequire is called from preloadLocalizations, then we + // don't care about checkForLegacyModules() because that will be done when a particular + // bundle is actually demanded. In case 2, checkForLegacyModules() is never relevant + // because cached modules are always v1.7+ built modules. + if(bundle){ + results.push(bundle); + }else{ + if(!xhr){ + try{ + require.getText(url, true, load); + }catch(e){ + results.push(cache[url]= {}); + } + }else{ + xhr.get({ + url:url, + sync:true, + load:load, + error:function(){ + results.push(cache[url]= {}); + } + }); + } + } + } + }); + callback && callback.apply(null, results); + }, -dijit.getDocumentWindow = function(doc){ - return dojo.window.get(doc); -}; + checkForLegacyModules = function(target){ + // legacy code may have already loaded [e.g] the raw bundle x/y/z at x.y.z; when true, push into the cache + for(var result, names = target.split("/"), object = dojo.global[names[0]], i = 1; object && i<names.length-1; object = object[names[i++]]){} + if(object){ + result = object[names[i]]; + if(!result){ + // fallback for incorrect bundle build of 1.6 + result = object[names[i].replace(/-/g,"_")]; + } + if(result){ + cache[target] = result; + } + } + return result; + }; -} + thisModule.getLocalization= function(moduleName, bundleName, locale){ + var result, + l10nName= getL10nName(moduleName, bundleName, locale).substring(10); + load(l10nName, (!isXd(l10nName) ? syncRequire : require), function(result_){ result= result_; }); + return result; + }; + + if(has("dojo-unit-tests")){ + unitTests.push(function(doh){ + doh.register("tests.i18n.unit", function(t){ + var check; + + check = evalBundle("{prop:1}"); + t.is({prop:1}, check); t.is(undefined, check[1]); -if(!dojo._hasResource["dijit._base.popup"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._base.popup"] = true; -dojo.provide("dijit._base.popup"); + check = evalBundle("({prop:1})"); + t.is({prop:1}, check); t.is(undefined, check[1]); + + check = evalBundle("{'prop-x':1}"); + t.is({'prop-x':1}, check); t.is(undefined, check[1]); + + check = evalBundle("({'prop-x':1})"); + t.is({'prop-x':1}, check); t.is(undefined, check[1]); + + check = evalBundle("define({'prop-x':1})"); + t.is(1, check); + + check = evalBundle("this is total nonsense and should throw an error"); + t.is(check instanceof Error, true); + }); + }); + } + } + return lang.mixin(thisModule, { + dynamic:true, + normalize:normalize, + load:load, + cache:cache + }); +}); +}, +'dijit/hccss':function(){ +define("dijit/hccss", [ + "require", // require.toUrl + "dojo/_base/config", // config.blankGif + "dojo/dom-class", // domClass.add domConstruct.create domStyle.getComputedStyle + "dojo/dom-construct", // domClass.add domConstruct.create domStyle.getComputedStyle + "dojo/dom-style", // domClass.add domConstruct.create domStyle.getComputedStyle + "dojo/ready", // ready + "dojo/_base/sniff", // has("ie") has("mozilla") + "dojo/_base/window" // win.body +], function(require, config, domClass, domConstruct, domStyle, ready, has, win){ + + // module: + // dijit/hccss + // summary: + // Test if computer is in high contrast mode, and sets dijit_a11y flag on <body> if it is. + if(has("ie") || has("mozilla")){ // NOTE: checking in Safari messes things up + // priority is 90 to run ahead of parser priority of 100 + ready(90, function(){ + // summary: + // Detects if we are in high-contrast mode or not + + // create div for testing if high contrast mode is on or images are turned off + var div = domConstruct.create("div",{ + id: "a11yTestNode", + style:{ + cssText:'border: 1px solid;' + + 'border-color:red green;' + + 'position: absolute;' + + 'height: 5px;' + + 'top: -999px;' + + 'background-image: url("' + (config.blankGif || require.toUrl("dojo/resources/blank.gif")) + '");' + } + }, win.body()); + + // test it + var cs = domStyle.getComputedStyle(div); + if(cs){ + var bkImg = cs.backgroundImage; + var needsA11y = (cs.borderTopColor == cs.borderRightColor) || (bkImg != null && (bkImg == "none" || bkImg == "url(invalid-url:)" )); + if(needsA11y){ + domClass.add(win.body(), "dijit_a11y"); + } + if(has("ie")){ + div.outerHTML = ""; // prevent mixed-content warning, see http://support.microsoft.com/kb/925014 + }else{ + win.body().removeChild(div); + } + } + }); + } +}); +}, +'dijit/tree/ForestStoreModel':function(){ +define("dijit/tree/ForestStoreModel", [ + "dojo/_base/array", // array.indexOf array.some + "dojo/_base/declare", // declare + "dojo/_base/lang", // lang.hitch + "dojo/_base/window", // win.global + "./TreeStoreModel" +], function(array, declare, lang, win, TreeStoreModel){ /*===== -dijit.popup.__OpenArgs = function(){ - // popup: Widget - // widget to display - // parent: Widget - // the button etc. that is displaying this popup - // around: DomNode - // DOM node (typically a button); place popup relative to this node. (Specify this *or* "x" and "y" parameters.) - // x: Integer - // Absolute horizontal position (in pixels) to place node at. (Specify this *or* "around" parameter.) - // y: Integer - // Absolute vertical position (in pixels) to place node at. (Specify this *or* "around" parameter.) - // orient: Object|String - // When the around parameter is specified, orient should be an - // ordered list of tuples of the form (around-node-corner, popup-node-corner). - // dijit.popup.open() tries to position the popup according to each tuple in the list, in order, - // until the popup appears fully within the viewport. - // - // The default value is {BL:'TL', TL:'BL'}, which represents a list of two tuples: - // 1. (BL, TL) - // 2. (TL, BL) - // where BL means "bottom left" and "TL" means "top left". - // So by default, it first tries putting the popup below the around node, left-aligning them, - // and then tries to put it above the around node, still left-aligning them. Note that the - // default is horizontally reversed when in RTL mode. - // - // When an (x,y) position is specified rather than an around node, orient is either - // "R" or "L". R (for right) means that it tries to put the popup to the right of the mouse, - // specifically positioning the popup's top-right corner at the mouse position, and if that doesn't - // fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner, - // and the top-right corner. - // onCancel: Function - // callback when user has canceled the popup by - // 1. hitting ESC or - // 2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog); - // i.e. whenever popupWidget.onCancel() is called, args.onCancel is called - // onClose: Function - // callback whenever this popup is closed - // onExecute: Function - // callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only) - // padding: dijit.__Position - // adding a buffer around the opening position. This is only useful when around is not set. - this.popup = popup; - this.parent = parent; - this.around = around; - this.x = x; - this.y = y; - this.orient = orient; - this.onCancel = onCancel; - this.onClose = onClose; - this.onExecute = onExecute; - this.padding = padding; -} +var TreeStoreModel = dijit.tree.TreeStoreModel; =====*/ -dijit.popup = { +// module: +// dijit/tree/ForestStoreModel +// summary: +// Interface between a dijit.Tree and a dojo.data store that doesn't have a root item, +// a.k.a. a store that has multiple "top level" items. + +return declare("dijit.tree.ForestStoreModel", TreeStoreModel, { // summary: - // This singleton is used to show/hide widgets as popups. + // Interface between a dijit.Tree and a dojo.data store that doesn't have a root item, + // a.k.a. a store that has multiple "top level" items. + // + // description + // Use this class to wrap a dojo.data store, making all the items matching the specified query + // appear as children of a fabricated "root item". If no query is specified then all the + // items returned by fetch() on the underlying store become children of the root item. + // This class allows dijit.Tree to assume a single root item, even if the store doesn't have one. + // + // When using this class the developer must override a number of methods according to their app and + // data, including: + // - onNewRootItem + // - onAddToRoot + // - onLeaveRoot + // - onNewItem + // - onSetItem - // _stack: dijit._Widget[] - // Stack of currently popped up widgets. - // (someone opened _stack[0], and then it opened _stack[1], etc.) - _stack: [], - - // _beginZIndex: Number - // Z-index of the first popup. (If first popup opens other - // popups they get a higher z-index.) - _beginZIndex: 1000, - - _idGen: 1, - - _createWrapper: function(/*Widget || DomNode*/ widget){ - // summary: - // Initialization for widgets that will be used as popups. - // Puts widget inside a wrapper DIV (if not already in one), - // and returns pointer to that wrapper DIV. - - var wrapper = widget.declaredClass ? widget._popupWrapper : (widget.parentNode && dojo.hasClass(widget.parentNode, "dijitPopup")), - node = widget.domNode || widget; - - if(!wrapper){ - // Create wrapper <div> for when this widget [in the future] will be used as a popup. - // This is done early because of IE bugs where creating/moving DOM nodes causes focus - // to go wonky, see tests/robot/Toolbar.html to reproduce - wrapper = dojo.create("div",{ - "class":"dijitPopup", - style:{ display: "none"}, - role: "presentation" - }, dojo.body()); - wrapper.appendChild(node); - - var s = node.style; - s.display = ""; - s.visibility = ""; - s.position = ""; - s.top = "0px"; + // Parameters to constructor - if(widget.declaredClass){ // TODO: in 2.0 change signature to always take widget, then remove if() - widget._popupWrapper = wrapper; - dojo.connect(widget, "destroy", function(){ - dojo.destroy(wrapper); - delete widget._popupWrapper; + // rootId: String + // ID of fabricated root item + rootId: "$root$", + + // rootLabel: String + // Label of fabricated root item + rootLabel: "ROOT", + + // query: String + // Specifies the set of children of the root item. + // example: + // | {type:'continent'} + query: null, + + // End of parameters to constructor + + constructor: function(params){ + // summary: + // Sets up variables, etc. + // tags: + // private + + // Make dummy root item + this.root = { + store: this, + root: true, + id: params.rootId, + label: params.rootLabel, + children: params.rootChildren // optional param + }; + }, + + // ======================================================================= + // Methods for traversing hierarchy + + mayHaveChildren: function(/*dojo.data.Item*/ item){ + // summary: + // Tells if an item has or may have children. Implementing logic here + // avoids showing +/- expando icon for nodes that we know don't have children. + // (For efficiency reasons we may not want to check if an element actually + // has children until user clicks the expando node) + // tags: + // extension + return item === this.root || this.inherited(arguments); + }, + + getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ callback, /*function*/ onError){ + // summary: + // Calls onComplete() with array of child items of given parent item, all loaded. + if(parentItem === this.root){ + if(this.root.children){ + // already loaded, just return + callback(this.root.children); + }else{ + this.store.fetch({ + query: this.query, + onComplete: lang.hitch(this, function(items){ + this.root.children = items; + callback(items); + }), + onError: onError }); } + }else{ + this.inherited(arguments); } - - return wrapper; }, - moveOffScreen: function(/*Widget || DomNode*/ widget){ - // summary: - // Moves the popup widget off-screen. - // Do not use this method to hide popups when not in use, because - // that will create an accessibility issue: the offscreen popup is - // still in the tabbing order. + // ======================================================================= + // Inspecting items - // Create wrapper if not already there - var wrapper = this._createWrapper(widget); + isItem: function(/* anything */ something){ + return (something === this.root) ? true : this.inherited(arguments); + }, - dojo.style(wrapper, { - visibility: "hidden", - top: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111) - display: "" - }); + fetchItemByIdentity: function(/* object */ keywordArgs){ + if(keywordArgs.identity == this.root.id){ + var scope = keywordArgs.scope?keywordArgs.scope:win.global; + if(keywordArgs.onItem){ + keywordArgs.onItem.call(scope, this.root); + } + }else{ + this.inherited(arguments); + } }, - hide: function(/*dijit._Widget*/ widget){ - // summary: - // Hide this popup widget (until it is ready to be shown). - // Initialization for widgets that will be used as popups - // - // Also puts widget inside a wrapper DIV (if not already in one) - // - // If popup widget needs to layout it should - // do so when it is made visible, and popup._onShow() is called. + getIdentity: function(/* item */ item){ + return (item === this.root) ? this.root.id : this.inherited(arguments); + }, - // Create wrapper if not already there - var wrapper = this._createWrapper(widget); + getLabel: function(/* item */ item){ + return (item === this.root) ? this.root.label : this.inherited(arguments); + }, - dojo.style(wrapper, "display", "none"); + // ======================================================================= + // Write interface + + newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){ + // summary: + // Creates a new item. See dojo.data.api.Write for details on args. + // Used in drag & drop when item from external source dropped onto tree. + if(parent === this.root){ + this.onNewRootItem(args); + return this.store.newItem(args); + }else{ + return this.inherited(arguments); + } }, - - getTopPopup: function(){ + + onNewRootItem: function(/* dojo.dnd.Item */ /*===== args =====*/){ // summary: - // Compute the closest ancestor popup that's *not* a child of another popup. - // Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button. - var stack = this._stack; - for(var pi=stack.length-1; pi > 0 && stack[pi].parent === stack[pi-1].widget; pi--){ - /* do nothing, just trying to get right value for pi */ + // User can override this method to modify a new element that's being + // added to the root of the tree, for example to add a flag like root=true + }, + + pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){ + // summary: + // Move or copy an item from one parent item to another. + // Used in drag & drop + if(oldParentItem === this.root){ + if(!bCopy){ + // It's onLeaveRoot()'s responsibility to modify the item so it no longer matches + // this.query... thus triggering an onChildrenChange() event to notify the Tree + // that this element is no longer a child of the root node + this.onLeaveRoot(childItem); + } + } + this.inherited(arguments, [childItem, + oldParentItem === this.root ? null : oldParentItem, + newParentItem === this.root ? null : newParentItem, + bCopy, + insertIndex + ]); + if(newParentItem === this.root){ + // It's onAddToRoot()'s responsibility to modify the item so it matches + // this.query... thus triggering an onChildrenChange() event to notify the Tree + // that this element is now a child of the root node + this.onAddToRoot(childItem); } - return stack[pi]; }, - open: function(/*dijit.popup.__OpenArgs*/ args){ + // ======================================================================= + // Handling for top level children + + onAddToRoot: function(/* item */ item){ // summary: - // Popup the widget at the specified position - // + // Called when item added to root of tree; user must override this method + // to modify the item so that it matches the query for top level items // example: - // opening at the mouse position - // | dijit.popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY}); - // + // | store.setValue(item, "root", true); + // tags: + // extension + console.log(this, ": item ", item, " added to root"); + }, + + onLeaveRoot: function(/* item */ item){ + // summary: + // Called when item removed from root of tree; user must override this method + // to modify the item so it doesn't match the query for top level items // example: - // opening the widget as a dropdown - // | dijit.popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}}); + // | store.unsetAttribute(item, "root"); + // tags: + // extension + console.log(this, ": item ", item, " removed from root"); + }, + + // ======================================================================= + // Events from data store + + _requeryTop: function(){ + // reruns the query for the children of the root node, + // sending out an onSet notification if those children have changed + var oldChildren = this.root.children || []; + this.store.fetch({ + query: this.query, + onComplete: lang.hitch(this, function(newChildren){ + this.root.children = newChildren; + + // If the list of children or the order of children has changed... + if(oldChildren.length != newChildren.length || + array.some(oldChildren, function(item, idx){ return newChildren[idx] != item;})){ + this.onChildrenChange(this.root, newChildren); + } + }) + }); + }, + + onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ + // summary: + // Handler for when new items appear in the store. Developers should override this + // method to be more efficient based on their app/data. + // description: + // Note that the default implementation requeries the top level items every time + // a new item is created, since any new item could be a top level item (even in + // addition to being a child of another item, since items can have multiple parents). // - // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback - // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed. - - var stack = this._stack, - widget = args.popup, - orient = args.orient || ( - (args.parent ? args.parent.isLeftToRight() : dojo._isBodyLtr()) ? - {'BL':'TL', 'BR':'TR', 'TL':'BL', 'TR':'BR'} : - {'BR':'TR', 'BL':'TL', 'TR':'BR', 'TL':'BL'} - ), - around = args.around, - id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++); + // If developers can detect which items are possible top level items (based on the item and the + // parentInfo parameters), they should override this method to only call _requeryTop() for top + // level items. Often all top level items have parentInfo==null, but + // that will depend on which store you use and what your data is like. + // tags: + // extension + this._requeryTop(); + + this.inherited(arguments); + }, - // If we are opening a new popup that isn't a child of a currently opened popup, then - // close currently opened popup(s). This should happen automatically when the old popups - // gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198]. - while(stack.length && (!args.parent || !dojo.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){ - dijit.popup.close(stack[stack.length-1].widget); + onDeleteItem: function(/*Object*/ item){ + // summary: + // Handler for delete notifications from underlying store + + // check if this was a child of root, and if so send notification that root's children + // have changed + if(array.indexOf(this.root.children, item) != -1){ + this._requeryTop(); } - // Get pointer to popup wrapper, and create wrapper if it doesn't exist - var wrapper = this._createWrapper(widget); + this.inherited(arguments); + }, + onSetItem: function(/* item */ item, + /* attribute-name-string */ attribute, + /* object | array */ oldValue, + /* object | array */ newValue){ + // summary: + // Updates the tree view according to changes to an item in the data store. + // Developers should override this method to be more efficient based on their app/data. + // description: + // Handles updates to an item's children by calling onChildrenChange(), and + // other updates to an item by calling onChange(). + // + // Also, any change to any item re-executes the query for the tree's top-level items, + // since this modified item may have started/stopped matching the query for top level items. + // + // If possible, developers should override this function to only call _requeryTop() when + // the change to the item has caused it to stop/start being a top level item in the tree. + // tags: + // extension - dojo.attr(wrapper, { - id: id, - style: { - zIndex: this._beginZIndex + stack.length - }, - "class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup", - dijitPopupParent: args.parent ? args.parent.id : "" - }); + this._requeryTop(); + this.inherited(arguments); + } - if(dojo.isIE || dojo.isMoz){ - if(!widget.bgIframe){ - // setting widget.bgIframe triggers cleanup in _Widget.destroy() - widget.bgIframe = new dijit.BackgroundIframe(wrapper); - } - } +}); - // position the wrapper node and make it visible - var best = around ? - dijit.placeOnScreenAroundElement(wrapper, around, orient, widget.orient ? dojo.hitch(widget, "orient") : null) : - dijit.placeOnScreen(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding); +}); - wrapper.style.display = ""; - wrapper.style.visibility = "visible"; - widget.domNode.style.visibility = "visible"; // counteract effects from _HasDropDown +}, +'url:dijit/layout/templates/AccordionButton.html':"<div data-dojo-attach-event='onclick:_onTitleClick' class='dijitAccordionTitle' role=\"presentation\">\n\t<div data-dojo-attach-point='titleNode,focusNode' data-dojo-attach-event='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\" data-dojo-attach-point='iconNode' style=\"vertical-align: middle\" role=\"presentation\"/>\n\t\t<span role=\"presentation\" data-dojo-attach-point='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n", +'dijit/form/_ComboBoxMenuMixin':function(){ +define("dijit/form/_ComboBoxMenuMixin", [ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/i18n", // i18n.getLocalization + "dojo/_base/window", // win.doc.createTextNode + "dojo/i18n!./nls/ComboBox" +], function(array, declare, domAttr, i18n, win){ + +// module: +// dijit/form/_ComboBoxMenuMixin +// summary: +// Focus-less menu for internal use in `dijit.form.ComboBox` - var handlers = []; +return declare( "dijit.form._ComboBoxMenuMixin", null, { + // summary: + // Focus-less menu for internal use in `dijit.form.ComboBox` + // tags: + // private - // provide default escape and tab key handling - // (this will work for any widget, not just menu) - handlers.push(dojo.connect(wrapper, "onkeypress", this, function(evt){ - if(evt.charOrCode == dojo.keys.ESCAPE && args.onCancel){ - dojo.stopEvent(evt); - args.onCancel(); - }else if(evt.charOrCode === dojo.keys.TAB){ - dojo.stopEvent(evt); - var topPopup = this.getTopPopup(); - if(topPopup && topPopup.onCancel){ - topPopup.onCancel(); - } - } - })); + // _messages: Object + // Holds "next" and "previous" text for paging buttons on drop down + _messages: null, - // watch for cancel/execute events on the popup and notify the caller - // (for a menu, "execute" means clicking an item) - if(widget.onCancel){ - handlers.push(dojo.connect(widget, "onCancel", args.onCancel)); - } + postMixInProperties: function(){ + this.inherited(arguments); + this._messages = i18n.getLocalization("dijit.form", "ComboBox", this.lang); + }, - handlers.push(dojo.connect(widget, widget.onExecute ? "onExecute" : "onChange", this, function(){ - var topPopup = this.getTopPopup(); - if(topPopup && topPopup.onExecute){ - topPopup.onExecute(); - } - })); + buildRendering: function(){ + this.inherited(arguments); - stack.push({ - widget: widget, - parent: args.parent, - onExecute: args.onExecute, - onCancel: args.onCancel, - onClose: args.onClose, - handlers: handlers - }); + // fill in template with i18n messages + this.previousButton.innerHTML = this._messages["previousMessage"]; + this.nextButton.innerHTML = this._messages["nextMessage"]; + }, - if(widget.onOpen){ - // TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here) - widget.onOpen(best); - } + _setValueAttr: function(/*Object*/ value){ + this.value = value; + this.onChange(value); + }, - return best; + onClick: function(/*DomNode*/ node){ + if(node == this.previousButton){ + this._setSelectedAttr(null); + this.onPage(-1); + }else if(node == this.nextButton){ + this._setSelectedAttr(null); + this.onPage(1); + }else{ + this.onChange(node); + } }, - close: function(/*dijit._Widget?*/ popup){ + // stubs + onChange: function(/*Number*/ /*===== direction =====*/){ // summary: - // Close specified popup and any popups that it parented. - // If no popup is specified, closes all popups. + // Notifies ComboBox/FilteringSelect that user selected an option. + // tags: + // callback + }, - var stack = this._stack; + onPage: function(/*Number*/ /*===== direction =====*/){ + // summary: + // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page. + // tags: + // callback + }, - // Basically work backwards from the top of the stack closing popups - // until we hit the specified popup, but IIRC there was some issue where closing - // a popup would cause others to close too. Thus if we are trying to close B in [A,B,C] - // closing C might close B indirectly and then the while() condition will run where stack==[A]... - // so the while condition is constructed defensively. - while((popup && dojo.some(stack, function(elem){return elem.widget == popup;})) || - (!popup && stack.length)){ - var top = stack.pop(), - widget = top.widget, - onClose = top.onClose; + onClose: function(){ + // summary: + // Callback from dijit.popup code to this widget, notifying it that it closed + // tags: + // private + this._setSelectedAttr(null); + }, - if(widget.onClose){ - // TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here) - widget.onClose(); - } - dojo.forEach(top.handlers, dojo.disconnect); + _createOption: function(/*Object*/ item, labelFunc){ + // summary: + // Creates an option to appear on the popup menu subclassed by + // `dijit.form.FilteringSelect`. - // Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc. - if(widget && widget.domNode){ - this.hide(widget); - } - - if(onClose){ - onClose(); - } + var menuitem = this._createMenuItem(); + var labelObject = labelFunc(item); + if(labelObject.html){ + menuitem.innerHTML = labelObject.label; + }else{ + menuitem.appendChild( + win.doc.createTextNode(labelObject.label) + ); + } + // #3250: in blank options, assign a normal height + if(menuitem.innerHTML == ""){ + menuitem.innerHTML = " "; // } - } -}; -// TODO: remove dijit._frames, it isn't being used much, since popups never release their -// iframes (see [22236]) -dijit._frames = new function(){ - // summary: - // cache of iframes + // update menuitem.dir if BidiSupport was required + this.applyTextDir(menuitem, (menuitem.innerText || menuitem.textContent || "")); - var queue = []; + menuitem.item=item; + return menuitem; + }, - this.pop = function(){ - var iframe; - if(queue.length){ - iframe = queue.pop(); - iframe.style.display=""; - }else{ - if(dojo.isIE < 9){ - var burl = dojo.config["dojoBlankHtmlUrl"] || (dojo.moduleUrl("dojo", "resources/blank.html")+"") || "javascript:\"\""; - var html="<iframe src='" + burl + "'" - + " style='position: absolute; left: 0px; top: 0px;" - + "z-index: -1; filter:Alpha(Opacity=\"0\");'>"; - iframe = dojo.doc.createElement(html); - }else{ - iframe = dojo.create("iframe"); - iframe.src = 'javascript:""'; - iframe.className = "dijitBackgroundIframe"; - dojo.style(iframe, "opacity", 0.1); + createOptions: function(results, options, labelFunc){ + // summary: + // Fills in the items in the drop down list + // results: + // Array of items + // options: + // The options to the query function of the store + // + // labelFunc: + // Function to produce a label in the drop down list from a dojo.data item + + // display "Previous . . ." button + this.previousButton.style.display = (options.start == 0) ? "none" : ""; + domAttr.set(this.previousButton, "id", this.id + "_prev"); + // create options using _createOption function defined by parent + // ComboBox (or FilteringSelect) class + // #2309: + // iterate over cache nondestructively + array.forEach(results, function(item, i){ + var menuitem = this._createOption(item, labelFunc); + domAttr.set(menuitem, "id", this.id + i); + this.nextButton.parentNode.insertBefore(menuitem, this.nextButton); + }, this); + // display "Next . . ." button + var displayMore = false; + // Try to determine if we should show 'more'... + if(results.total && !results.total.then && results.total != -1){ + if((options.start + options.count) < results.total){ + displayMore = true; + }else if((options.start + options.count) > results.total && options.count == results.length){ + // Weird return from a data store, where a start + count > maxOptions + // implies maxOptions isn't really valid and we have to go into faking it. + // And more or less assume more if count == results.length + displayMore = true; } - iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didn't work. - dijit.setWaiRole(iframe,"presentation"); + }else if(options.count == results.length){ + //Don't know the size, so we do the best we can based off count alone. + //So, if we have an exact match to count, assume more. + displayMore = true; } - return iframe; - }; - - this.push = function(iframe){ - iframe.style.display="none"; - queue.push(iframe); - } -}(); + this.nextButton.style.display = displayMore ? "" : "none"; + domAttr.set(this.nextButton,"id", this.id + "_next"); + return this.containerNode.childNodes; + }, -dijit.BackgroundIframe = function(/*DomNode*/ node){ - // summary: - // For IE/FF z-index schenanigans. id attribute is required. - // - // description: - // new dijit.BackgroundIframe(node) - // Makes a background iframe as a child of node, that fills - // area (and position) of node - - if(!node.id){ throw new Error("no id"); } - if(dojo.isIE || dojo.isMoz){ - var iframe = (this.iframe = dijit._frames.pop()); - node.appendChild(iframe); - if(dojo.isIE<7 || dojo.isQuirks){ - this.resize(node); - this._conn = dojo.connect(node, 'onresize', this, function(){ - this.resize(node); - }); - }else{ - dojo.style(iframe, { - width: '100%', - height: '100%' - }); + clearResultList: function(){ + // summary: + // Clears the entries in the drop down list, but of course keeps the previous and next buttons. + var container = this.containerNode; + while(container.childNodes.length > 2){ + container.removeChild(container.childNodes[container.childNodes.length-2]); } - } -}; + this._setSelectedAttr(null); + }, -dojo.extend(dijit.BackgroundIframe, { - resize: function(node){ + highlightFirstOption: function(){ // summary: - // Resize the iframe so it's the same size as node. - // Needed on IE6 and IE/quirks because height:100% doesn't work right. - if(this.iframe){ - dojo.style(this.iframe, { - width: node.offsetWidth + 'px', - height: node.offsetHeight + 'px' - }); - } + // Highlight the first real item in the list (not Previous Choices). + this.selectFirstNode(); }, - destroy: function(){ + + highlightLastOption: function(){ // summary: - // destroy the iframe - if(this._conn){ - dojo.disconnect(this._conn); - this._conn = null; + // Highlight the last real item in the list (not More Choices). + this.selectLastNode(); + }, + + selectFirstNode: function(){ + this.inherited(arguments); + if(this.getHighlightedOption() == this.previousButton){ + this.selectNextNode(); } - if(this.iframe){ - dijit._frames.push(this.iframe); - delete this.iframe; + }, + + selectLastNode: function(){ + this.inherited(arguments); + if(this.getHighlightedOption() == this.nextButton){ + this.selectPreviousNode(); } + }, + + getHighlightedOption: function(){ + return this._getSelectedAttr(); } }); -} +}); + +}, +'dojo/parser':function(){ +define( + "dojo/parser", ["./_base/kernel", "./_base/lang", "./_base/array", "./_base/html", "./_base/window", "./_base/url", + "./_base/json", "./aspect", "./date/stamp", "./query", "./on", "./ready"], + function(dojo, dlang, darray, dhtml, dwindow, _Url, djson, aspect, dates, query, don){ + +// module: +// dojo/parser +// summary: +// The Dom/Widget parsing package -if(!dojo._hasResource["dijit._base.scroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._base.scroll"] = true; -dojo.provide("dijit._base.scroll"); +new Date("X"); // workaround for #11279, new Date("") == NaN +var features = { + // Feature detection for when node.attributes only lists the attributes specified in the markup + // rather than old IE/quirks behavior where it lists every default value too + "dom-attributes-explicit": document.createElement("div").attributes.length < 40 +}; +function has(feature){ + return features[feature]; +} -dijit.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){ +dojo.parser = new function(){ // summary: - // Scroll the passed node into view, if it is not already. - // Deprecated, use `dojo.window.scrollIntoView` instead. - - dojo.window.scrollIntoView(node, pos); -}; + // The Dom/Widget parsing package -} + var _nameMap = { + // Map from widget name (ex: "dijit.form.Button") to structure mapping + // lowercase version of attribute names to the version in the widget ex: + // { + // label: "label", + // onclick: "onClick" + // } + }; + function getNameMap(proto){ + // summary: + // Returns map from lowercase name to attribute name in class, ex: {onclick: "onClick"} + var map = {}; + for(var name in proto){ + if(name.charAt(0)=="_"){ continue; } // skip internal properties + map[name.toLowerCase()] = name; + } + return map; + } + // Widgets like BorderContainer add properties to _Widget via dojo.extend(). + // If BorderContainer is loaded after _Widget's parameter list has been cached, + // we need to refresh that parameter list (for _Widget and all widgets that extend _Widget). + aspect.after(dlang, "extend", function(){ + _nameMap = {}; + }, true); -if(!dojo._hasResource["dojo.uacss"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.uacss"] = true; -dojo.provide("dojo.uacss"); + // Map from widget name (ex: "dijit.form.Button") to constructor + var _ctorMap = {}; + this._functionFromScript = function(script, attrData){ + // summary: + // Convert a <script type="dojo/method" args="a, b, c"> ... </script> + // into a function + // script: DOMNode + // The <script> DOMNode + // attrData: String + // For HTML5 compliance, searches for attrData + "args" (typically + // "data-dojo-args") instead of "args" + var preamble = ""; + var suffix = ""; + var argsStr = (script.getAttribute(attrData + "args") || script.getAttribute("args")); + if(argsStr){ + darray.forEach(argsStr.split(/\s*,\s*/), function(part, idx){ + preamble += "var "+part+" = arguments["+idx+"]; "; + }); + } + var withStr = script.getAttribute("with"); + if(withStr && withStr.length){ + darray.forEach(withStr.split(/\s*,\s*/), function(part){ + preamble += "with("+part+"){"; + suffix += "}"; + }); + } + return new Function(preamble+script.innerHTML+suffix); + }; -(function(){ - // summary: - // Applies pre-set CSS classes to the top-level HTML node, based on: - // - browser (ex: dj_ie) - // - browser version (ex: dj_ie6) - // - box model (ex: dj_contentBox) - // - text direction (ex: dijitRtl) - // - // In addition, browser, browser version, and box model are - // combined with an RTL flag when browser text is RTL. ex: dj_ie-rtl. + this.instantiate = /*====== dojo.parser.instantiate= ======*/function(nodes, mixin, args){ + // summary: + // Takes array of nodes, and turns them into class instances and + // potentially calls a startup method to allow them to connect with + // any children. + // nodes: Array + // Array of nodes or objects like + // | { + // | type: "dijit.form.Button", + // | node: DOMNode, + // | scripts: [ ... ], // array of <script type="dojo/..."> children of node + // | inherited: { ... } // settings inherited from ancestors like dir, theme, etc. + // | } + // mixin: Object? + // An object that will be mixed in with each node in the array. + // Values in the mixin will override values in the node, if they + // exist. + // args: Object? + // An object used to hold kwArgs for instantiation. + // See parse.args argument for details. - var d = dojo, - html = d.doc.documentElement, - ie = d.isIE, - opera = d.isOpera, - maj = Math.floor, - ff = d.isFF, - boxModel = d.boxModel.replace(/-/,''), + var thelist = [], + mixin = mixin||{}; + args = args||{}; - classes = { - dj_ie: ie, - dj_ie6: maj(ie) == 6, - dj_ie7: maj(ie) == 7, - dj_ie8: maj(ie) == 8, - dj_ie9: maj(ie) == 9, - dj_quirks: d.isQuirks, - dj_iequirks: ie && d.isQuirks, + // Precompute names of special attributes we are looking for + // TODO: for 2.0 default to data-dojo- regardless of scopeName (or maybe scopeName won't exist in 2.0) + var dojoType = (args.scope || dojo._scopeName) + "Type", // typically "dojoType" + attrData = "data-" + (args.scope || dojo._scopeName) + "-",// typically "data-dojo-" + dataDojoType = attrData + "type", // typically "data-dojo-type" + dataDojoProps = attrData + "props", // typically "data-dojo-props" + dataDojoAttachPoint = attrData + "attach-point", + dataDojoAttachEvent = attrData + "attach-event", + dataDojoId = attrData + "id"; + + // And make hash to quickly check if a given attribute is special, and to map the name to something friendly + var specialAttrs = {}; + darray.forEach([dataDojoProps, dataDojoType, dojoType, dataDojoId, "jsId", dataDojoAttachPoint, + dataDojoAttachEvent, "dojoAttachPoint", "dojoAttachEvent", "class", "style"], function(name){ + specialAttrs[name.toLowerCase()] = name.replace(args.scope, "dojo"); + }); - // NOTE: Opera not supported by dijit - dj_opera: opera, + darray.forEach(nodes, function(obj){ + if(!obj){ return; } - dj_khtml: d.isKhtml, + var node = obj.node || obj, + type = dojoType in mixin ? mixin[dojoType] : obj.node ? obj.type : (node.getAttribute(dataDojoType) || node.getAttribute(dojoType)), + ctor = _ctorMap[type] || (_ctorMap[type] = dlang.getObject(type)), + proto = ctor && ctor.prototype; + if(!ctor){ + throw new Error("Could not load class '" + type); + } - dj_webkit: d.isWebKit, - dj_safari: d.isSafari, - dj_chrome: d.isChrome, + // Setup hash to hold parameter settings for this widget. Start with the parameter + // settings inherited from ancestors ("dir" and "lang"). + // Inherited setting may later be overridden by explicit settings on node itself. + var params = {}; - dj_gecko: d.isMozilla, - dj_ff3: maj(ff) == 3 - }; // no dojo unsupported browsers + if(args.defaults){ + // settings for the document itself (or whatever subtree is being parsed) + dlang.mixin(params, args.defaults); + } + if(obj.inherited){ + // settings from dir=rtl or lang=... on a node above this node + dlang.mixin(params, obj.inherited); + } - classes["dj_" + boxModel] = true; + // Get list of attributes explicitly listed in the markup + var attributes; + if(has("dom-attributes-explicit")){ + // Standard path to get list of user specified attributes + attributes = node.attributes; + }else{ + // Special path for IE, avoid (sometimes >100) bogus entries in node.attributes + var clone = /^input$|^img$/i.test(node.nodeName) ? node : node.cloneNode(false), + attrs = clone.outerHTML.replace(/=[^\s"']+|="[^"]*"|='[^']*'/g, "").replace(/^\s*<[a-zA-Z0-9]*/, "").replace(/>.*$/, ""); - // apply browser, browser version, and box model class names - var classStr = ""; - for(var clz in classes){ - if(classes[clz]){ - classStr += clz + " "; - } - } - html.className = d.trim(html.className + " " + classStr); + attributes = darray.map(attrs.split(/\s+/), function(name){ + var lcName = name.toLowerCase(); + return { + name: name, + // getAttribute() doesn't work for button.value, returns innerHTML of button. + // but getAttributeNode().value doesn't work for the form.encType or li.value + value: (node.nodeName == "LI" && name == "value") || lcName == "enctype" ? + node.getAttribute(lcName) : node.getAttributeNode(lcName).value, + specified: true + }; + }); + } - // If RTL mode, then add dj_rtl flag plus repeat existing classes with -rtl extension. - // We can't run the code below until the <body> tag has loaded (so we can check for dir=rtl). - // Unshift() is to run sniff code before the parser. - dojo._loaders.unshift(function(){ - if(!dojo._isBodyLtr()){ - var rtlClassStr = "dj_rtl dijitRtl " + classStr.replace(/ /g, "-rtl ") - html.className = d.trim(html.className + " " + rtlClassStr); - } - }); -})(); + // Read in attributes and process them, including data-dojo-props, data-dojo-type, + // dojoAttachPoint, etc., as well as normal foo=bar attributes. + var i=0, item; + while(item = attributes[i++]){ + if(!item || !item.specified){ + continue; + } -} + var name = item.name, + lcName = name.toLowerCase(), + value = item.value; -if(!dojo._hasResource["dijit._base.sniff"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._base.sniff"] = true; -dojo.provide("dijit._base.sniff"); + if(lcName in specialAttrs){ + switch(specialAttrs[lcName]){ + // Data-dojo-props. Save for later to make sure it overrides direct foo=bar settings + case "data-dojo-props": + var extra = value; + break; + // data-dojo-id or jsId. TODO: drop jsId in 2.0 + case "data-dojo-id": + case "jsId": + var jsname = value; + break; -// summary: -// Applies pre-set CSS classes to the top-level HTML node, see -// `dojo.uacss` for details. -// -// Simply doing a require on this module will -// establish this CSS. Modified version of Morris' CSS hack. + // For the benefit of _Templated + case "data-dojo-attach-point": + case "dojoAttachPoint": + params.dojoAttachPoint = value; + break; + case "data-dojo-attach-event": + case "dojoAttachEvent": + params.dojoAttachEvent = value; + break; -} + // Special parameter handling needed for IE + case "class": + params["class"] = node.className; + break; + case "style": + params["style"] = node.style && node.style.cssText; + break; + } + }else{ + // Normal attribute, ex: value="123" -if(!dojo._hasResource["dijit._base.typematic"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._base.typematic"] = true; -dojo.provide("dijit._base.typematic"); + // Find attribute in widget corresponding to specified name. + // May involve case conversion, ex: onclick --> onClick + if(!(name in proto)){ + var map = (_nameMap[type] || (_nameMap[type] = getNameMap(proto))); + name = map[lcName] || name; + } + // Set params[name] to value, doing type conversion + if(name in proto){ + switch(typeof proto[name]){ + case "string": + params[name] = value; + break; + case "number": + params[name] = value.length ? Number(value) : NaN; + break; + case "boolean": + // for checked/disabled value might be "" or "checked". interpret as true. + params[name] = value.toLowerCase() != "false"; + break; + case "function": + if(value === "" || value.search(/[^\w\.]+/i) != -1){ + // The user has specified some text for a function like "return x+5" + params[name] = new Function(value); + }else{ + // The user has specified the name of a function like "myOnClick" + // or a single word function "return" + params[name] = dlang.getObject(value, false) || new Function(value); + } + break; + default: + var pVal = proto[name]; + params[name] = + (pVal && "length" in pVal) ? (value ? value.split(/\s*,\s*/) : []) : // array + (pVal instanceof Date) ? + (value == "" ? new Date("") : // the NaN of dates + value == "now" ? new Date() : // current date + dates.fromISOString(value)) : + (pVal instanceof dojo._Url) ? (dojo.baseUrl + value) : + djson.fromJson(value); + } + }else{ + params[name] = value; + } + } + } -dijit.typematic = { - // summary: - // These functions are used to repetitively call a user specified callback - // method when a specific key or mouse click over a specific DOM node is - // held down for a specific amount of time. - // Only 1 such event is allowed to occur on the browser page at 1 time. + // Mix things found in data-dojo-props into the params, overriding any direct settings + if(extra){ + try{ + extra = djson.fromJson.call(args.propsThis, "{" + extra + "}"); + dlang.mixin(params, extra); + }catch(e){ + // give the user a pointer to their invalid parameters. FIXME: can we kill this in production? + throw new Error(e.toString() + " in data-dojo-props='" + extra + "'"); + } + } - _fireEventAndReload: function(){ - this._timer = null; - this._callback(++this._count, this._node, this._evt); - - // Schedule next event, timer is at most minDelay (default 10ms) to avoid - // browser overload (particularly avoiding starving DOH robot so it never gets to send a mouseup) - this._currentTimeout = Math.max( - this._currentTimeout < 0 ? this._initialDelay : - (this._subsequentDelay > 1 ? this._subsequentDelay : Math.round(this._currentTimeout * this._subsequentDelay)), - this._minDelay); - this._timer = setTimeout(dojo.hitch(this, "_fireEventAndReload"), this._currentTimeout); - }, + // Any parameters specified in "mixin" override everything else. + dlang.mixin(params, mixin); - trigger: function(/*Event*/ evt, /*Object*/ _this, /*DOMNode*/ node, /*Function*/ callback, /*Object*/ obj, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ - // summary: - // Start a timed, repeating callback sequence. - // If already started, the function call is ignored. - // This method is not normally called by the user but can be - // when the normal listener code is insufficient. - // evt: - // key or mouse event object to pass to the user callback - // _this: - // pointer to the user's widget space. - // node: - // the DOM node object to pass the the callback function - // callback: - // function to call until the sequence is stopped called with 3 parameters: - // count: - // integer representing number of repeated calls (0..n) with -1 indicating the iteration has stopped - // node: - // the DOM node object passed in - // evt: - // key or mouse event object - // obj: - // user space object used to uniquely identify each typematic sequence - // subsequentDelay (optional): - // if > 1, the number of milliseconds until the 3->n events occur - // or else the fractional time multiplier for the next event's delay, default=0.9 - // initialDelay (optional): - // the number of milliseconds until the 2nd event occurs, default=500ms - // minDelay (optional): - // the maximum delay in milliseconds for event to fire, default=10ms - if(obj != this._obj){ - this.stop(); - this._initialDelay = initialDelay || 500; - this._subsequentDelay = subsequentDelay || 0.90; - this._minDelay = minDelay || 10; - this._obj = obj; - this._evt = evt; - this._node = node; - this._currentTimeout = -1; - this._count = -1; - this._callback = dojo.hitch(_this, callback); - this._fireEventAndReload(); - this._evt = dojo.mixin({faux: true}, evt); + var scripts = obj.node ? obj.scripts : (ctor && (ctor._noScript || proto._noScript) ? [] : + query("> script[type^='dojo/']", node)); + + // Process <script type="dojo/*"> script tags + // <script type="dojo/method" event="foo"> tags are added to params, and passed to + // the widget on instantiation. + // <script type="dojo/method"> tags (with no event) are executed after instantiation + // <script type="dojo/connect" data-dojo-event="foo"> tags are dojo.connected after instantiation + // <script type="dojo/watch" data-dojo-prop="foo"> tags are dojo.watch after instantiation + // <script type="dojo/on" data-dojo-event="foo"> tags are dojo.on after instantiation + // note: dojo/* script tags cannot exist in self closing widgets, like <input /> + var connects = [], // functions to connect after instantiation + calls = [], // functions to call after instantiation + watch = [], //functions to watch after instantiation + on = []; //functions to on after instantiation + + if(scripts){ + for(i=0; i<scripts.length; i++){ + var script = scripts[i]; + node.removeChild(script); + // FIXME: drop event="" support in 2.0. use data-dojo-event="" instead + var event = (script.getAttribute(attrData + "event") || script.getAttribute("event")), + prop = script.getAttribute(attrData + "prop"), + type = script.getAttribute("type"), + nf = this._functionFromScript(script, attrData); + if(event){ + if(type == "dojo/connect"){ + connects.push({event: event, func: nf}); + }else if(type == "dojo/on"){ + on.push({event: event, func: nf}); + }else{ + params[event] = nf; + } + }else if(type == "dojo/watch"){ + watch.push({prop: prop, func: nf}); + }else{ + calls.push(nf); + } + } + } + + // create the instance + var markupFactory = ctor.markupFactory || proto.markupFactory; + var instance = markupFactory ? markupFactory(params, node, ctor) : new ctor(params, node); + thelist.push(instance); + + // map it to the JS namespace if that makes sense + if(jsname){ + dlang.setObject(jsname, instance); + } + + // process connections and startup functions + for(i=0; i<connects.length; i++){ + aspect.after(instance, connects[i].event, dojo.hitch(instance, connects[i].func), true); + } + for(i=0; i<calls.length; i++){ + calls[i].call(instance); + } + for(i=0; i<watch.length; i++){ + instance.watch(watch[i].prop, watch[i].func); + } + for(i=0; i<on.length; i++){ + don(instance, on[i].event, on[i].func); + } + }, this); + + // Call startup on each top level instance if it makes sense (as for + // widgets). Parent widgets will recursively call startup on their + // (non-top level) children + if(!mixin._started){ + darray.forEach(thelist, function(instance){ + if( !args.noStart && instance && + dlang.isFunction(instance.startup) && + !instance._started + ){ + instance.startup(); + } + }); } - }, + return thelist; + }; - stop: function(){ + this.parse = /*====== dojo.parser.parse= ======*/ function(rootNode, args){ // summary: - // Stop an ongoing timed, repeating callback sequence. - if(this._timer){ - clearTimeout(this._timer); - this._timer = null; - } - if(this._obj){ - this._callback(-1, this._node, this._evt); - this._obj = null; + // Scan the DOM for class instances, and instantiate them. + // + // description: + // Search specified node (or root node) recursively for class instances, + // and instantiate them. Searches for either data-dojo-type="Class" or + // dojoType="Class" where "Class" is a a fully qualified class name, + // like `dijit.form.Button` + // + // Using `data-dojo-type`: + // Attributes using can be mixed into the parameters used to instantiate the + // Class by using a `data-dojo-props` attribute on the node being converted. + // `data-dojo-props` should be a string attribute to be converted from JSON. + // + // Using `dojoType`: + // Attributes are read from the original domNode and converted to appropriate + // types by looking up the Class prototype values. This is the default behavior + // from Dojo 1.0 to Dojo 1.5. `dojoType` support is deprecated, and will + // go away in Dojo 2.0. + // + // rootNode: DomNode? + // A default starting root node from which to start the parsing. Can be + // omitted, defaulting to the entire document. If omitted, the `args` + // object can be passed in this place. If the `args` object has a + // `rootNode` member, that is used. + // + // args: Object + // a kwArgs object passed along to instantiate() + // + // * noStart: Boolean? + // when set will prevent the parser from calling .startup() + // when locating the nodes. + // * rootNode: DomNode? + // identical to the function's `rootNode` argument, though + // allowed to be passed in via this `args object. + // * template: Boolean + // If true, ignores ContentPane's stopParser flag and parses contents inside of + // a ContentPane inside of a template. This allows dojoAttachPoint on widgets/nodes + // nested inside the ContentPane to work. + // * inherited: Object + // Hash possibly containing dir and lang settings to be applied to + // parsed widgets, unless there's another setting on a sub-node that overrides + // * scope: String + // Root for attribute names to search for. If scopeName is dojo, + // will search for data-dojo-type (or dojoType). For backwards compatibility + // reasons defaults to dojo._scopeName (which is "dojo" except when + // multi-version support is used, when it will be something like dojo16, dojo20, etc.) + // * propsThis: Object + // If specified, "this" referenced from data-dojo-props will refer to propsThis. + // Intended for use from the widgets-in-template feature of `dijit._WidgetsInTemplateMixin` + // + // example: + // Parse all widgets on a page: + // | dojo.parser.parse(); + // + // example: + // Parse all classes within the node with id="foo" + // | dojo.parser.parse(dojo.byId('foo')); + // + // example: + // Parse all classes in a page, but do not call .startup() on any + // child + // | dojo.parser.parse({ noStart: true }) + // + // example: + // Parse all classes in a node, but do not call .startup() + // | dojo.parser.parse(someNode, { noStart:true }); + // | // or + // | dojo.parser.parse({ noStart:true, rootNode: someNode }); + + // determine the root node based on the passed arguments. + var root; + if(!args && rootNode && rootNode.rootNode){ + args = rootNode; + root = args.rootNode; + }else{ + root = rootNode; } - }, + root = root ? dhtml.byId(root) : dwindow.body(); + args = args || {}; - addKeyListener: function(/*DOMNode*/ node, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ - // summary: - // Start listening for a specific typematic key. - // See also the trigger method for other parameters. - // keyObject: - // an object defining the key to listen for: - // charOrCode: - // the printable character (string) or keyCode (number) to listen for. - // keyCode: - // (deprecated - use charOrCode) the keyCode (number) to listen for (implies charCode = 0). - // charCode: - // (deprecated - use charOrCode) the charCode (number) to listen for. - // ctrlKey: - // desired ctrl key state to initiate the callback sequence: - // - pressed (true) - // - released (false) - // - either (unspecified) - // altKey: - // same as ctrlKey but for the alt key - // shiftKey: - // same as ctrlKey but for the shift key - // returns: - // an array of dojo.connect handles - if(keyObject.keyCode){ - keyObject.charOrCode = keyObject.keyCode; - dojo.deprecated("keyCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0"); - }else if(keyObject.charCode){ - keyObject.charOrCode = String.fromCharCode(keyObject.charCode); - dojo.deprecated("charCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0"); + var dojoType = (args.scope || dojo._scopeName) + "Type", // typically "dojoType" + attrData = "data-" + (args.scope || dojo._scopeName) + "-", // typically "data-dojo-" + dataDojoType = attrData + "type", // typically "data-dojo-type" + dataDojoTextDir = attrData + "textdir"; // typically "data-dojo-textdir" + + // List of all nodes on page w/dojoType specified + var list = []; + + // Info on DOMNode currently being processed + var node = root.firstChild; + + // Info on parent of DOMNode currently being processed + // - inherited: dir, lang, and textDir setting of parent, or inherited by parent + // - parent: pointer to identical structure for my parent (or null if no parent) + // - scripts: if specified, collects <script type="dojo/..."> type nodes from children + var inherited = args && args.inherited; + if(!inherited){ + function findAncestorAttr(node, attr){ + return (node.getAttribute && node.getAttribute(attr)) || + (node !== dwindow.doc && node !== dwindow.doc.documentElement && node.parentNode ? findAncestorAttr(node.parentNode, attr) : null); + } + inherited = { + dir: findAncestorAttr(root, "dir"), + lang: findAncestorAttr(root, "lang"), + textDir: findAncestorAttr(root, dataDojoTextDir) + }; + for(var key in inherited){ + if(!inherited[key]){ delete inherited[key]; } + } } - return [ - dojo.connect(node, "onkeypress", this, function(evt){ - if(evt.charOrCode == keyObject.charOrCode && - (keyObject.ctrlKey === undefined || keyObject.ctrlKey == evt.ctrlKey) && - (keyObject.altKey === undefined || keyObject.altKey == evt.altKey) && - (keyObject.metaKey === undefined || keyObject.metaKey == (evt.metaKey || false)) && // IE doesn't even set metaKey - (keyObject.shiftKey === undefined || keyObject.shiftKey == evt.shiftKey)){ - dojo.stopEvent(evt); - dijit.typematic.trigger(evt, _this, node, callback, keyObject, subsequentDelay, initialDelay, minDelay); - }else if(dijit.typematic._obj == keyObject){ - dijit.typematic.stop(); - } - }), - dojo.connect(node, "onkeyup", this, function(evt){ - if(dijit.typematic._obj == keyObject){ - dijit.typematic.stop(); - } - }) - ]; - }, + var parent = { + inherited: inherited + }; - addMouseListener: function(/*DOMNode*/ node, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ - // summary: - // Start listening for a typematic mouse click. - // See the trigger method for other parameters. - // returns: - // an array of dojo.connect handles - var dc = dojo.connect; - return [ - dc(node, "mousedown", this, function(evt){ - dojo.stopEvent(evt); - dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay); - }), - dc(node, "mouseup", this, function(evt){ - dojo.stopEvent(evt); - dijit.typematic.stop(); - }), - dc(node, "mouseout", this, function(evt){ - dojo.stopEvent(evt); - dijit.typematic.stop(); - }), - dc(node, "mousemove", this, function(evt){ - evt.preventDefault(); - }), - dc(node, "dblclick", this, function(evt){ - dojo.stopEvent(evt); - if(dojo.isIE){ - dijit.typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay); - setTimeout(dojo.hitch(this, dijit.typematic.stop), 50); - } - }) - ]; - }, + // For collecting <script type="dojo/..."> type nodes (when null, we don't need to collect) + var scripts; - addListener: function(/*Node*/ mouseNode, /*Node*/ keyNode, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ - // summary: - // Start listening for a specific typematic key and mouseclick. - // This is a thin wrapper to addKeyListener and addMouseListener. - // See the addMouseListener and addKeyListener methods for other parameters. - // mouseNode: - // the DOM node object to listen on for mouse events. - // keyNode: - // the DOM node object to listen on for key events. - // returns: - // an array of dojo.connect handles - return this.addKeyListener(keyNode, keyObject, _this, callback, subsequentDelay, initialDelay, minDelay).concat( - this.addMouseListener(mouseNode, _this, callback, subsequentDelay, initialDelay, minDelay)); - } -}; + // when true, only look for <script type="dojo/..."> tags, and don't recurse to children + var scriptsOnly; -} + function getEffective(parent){ + // summary: + // Get effective dir, lang, textDir settings for specified obj + // (matching "parent" object structure above), and do caching. + // Take care not to return null entries. + if(!parent.inherited){ + parent.inherited = {}; + var node = parent.node, + grandparent = getEffective(parent.parent); + var inherited = { + dir: node.getAttribute("dir") || grandparent.dir, + lang: node.getAttribute("lang") || grandparent.lang, + textDir: node.getAttribute(dataDojoTextDir) || grandparent.textDir + }; + for(var key in inherited){ + if(inherited[key]){ + parent.inherited[key] = inherited[key]; + } + } + } + return parent.inherited; + } -if(!dojo._hasResource["dijit._base.wai"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._base.wai"] = true; -dojo.provide("dijit._base.wai"); + // DFS on DOM tree, collecting nodes with data-dojo-type specified. + while(true){ + if(!node){ + // Finished this level, continue to parent's next sibling + if(!parent || !parent.node){ + break; + } + node = parent.node.nextSibling; + scripts = parent.scripts; + scriptsOnly = false; + parent = parent.parent; + continue; + } + if(node.nodeType != 1){ + // Text or comment node, skip to next sibling + node = node.nextSibling; + continue; + } -dijit.wai = { - onload: function(){ - // summary: - // Detects if we are in high-contrast mode or not + if(scripts && node.nodeName.toLowerCase() == "script"){ + // Save <script type="dojo/..."> for parent, then continue to next sibling + type = node.getAttribute("type"); + if(type && /^dojo\/\w/i.test(type)){ + scripts.push(node); + } + node = node.nextSibling; + continue; + } + if(scriptsOnly){ + node = node.nextSibling; + continue; + } - // This must be a named function and not an anonymous - // function, so that the widget parsing code can make sure it - // registers its onload function after this function. - // DO NOT USE "this" within this function. + // Check for data-dojo-type attribute, fallback to backward compatible dojoType + var type = node.getAttribute(dataDojoType) || node.getAttribute(dojoType); - // create div for testing if high contrast mode is on or images are turned off - var div = dojo.create("div",{ - id: "a11yTestNode", - style:{ - cssText:'border: 1px solid;' - + 'border-color:red green;' - + 'position: absolute;' - + 'height: 5px;' - + 'top: -999px;' - + 'background-image: url("' + (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")) + '");' + // Short circuit for leaf nodes containing nothing [but text] + var firstChild = node.firstChild; + if(!type && (!firstChild || (firstChild.nodeType == 3 && !firstChild.nextSibling))){ + node = node.nextSibling; + continue; } - }, dojo.body()); - // test it - var cs = dojo.getComputedStyle(div); - if(cs){ - var bkImg = cs.backgroundImage; - var needsA11y = (cs.borderTopColor == cs.borderRightColor) || (bkImg != null && (bkImg == "none" || bkImg == "url(invalid-url:)" )); - dojo[needsA11y ? "addClass" : "removeClass"](dojo.body(), "dijit_a11y"); - if(dojo.isIE){ - div.outerHTML = ""; // prevent mixed-content warning, see http://support.microsoft.com/kb/925014 - }else{ - dojo.body().removeChild(div); + // Setup data structure to save info on current node for when we return from processing descendant nodes + var current = { + node: node, + scripts: scripts, + parent: parent + }; + + // If dojoType/data-dojo-type specified, add to output array of nodes to instantiate + var ctor = type && (_ctorMap[type] || (_ctorMap[type] = dlang.getObject(type))), // note: won't find classes declared via dojo.Declaration + childScripts = ctor && !ctor.prototype._noScript ? [] : null; // <script> nodes that are parent's children + if(type){ + list.push({ + "type": type, + node: node, + scripts: childScripts, + inherited: getEffective(current) // dir & lang settings for current node, explicit or inherited + }); } + + // Recurse, collecting <script type="dojo/..."> children, and also looking for + // descendant nodes with dojoType specified (unless the widget has the stopParser flag). + // When finished with children, go to my next sibling. + node = firstChild; + scripts = childScripts; + scriptsOnly = ctor && ctor.prototype.stopParser && !(args && args.template); + parent = current; + } - } -}; -// Test if computer is in high contrast mode. -// Make sure the a11y test runs first, before widgets are instantiated. -if(dojo.isIE || dojo.isMoz){ // NOTE: checking in Safari messes things up - dojo._loaders.unshift(dijit.wai.onload); + // go build the object instances + var mixin = args && args.template ? {template: true} : null; + return this.instantiate(list, mixin, args); // Array + }; +}(); + + +//Register the parser callback. It should be the first callback +//after the a11y test. +if(dojo.config.parseOnLoad){ + dojo.ready(100, dojo.parser, "parse"); } -dojo.mixin(dijit, { - hasWaiRole: function(/*Element*/ elem, /*String?*/ role){ - // summary: - // Determines if an element has a particular role. - // returns: - // True if elem has the specific role attribute and false if not. - // For backwards compatibility if role parameter not provided, - // returns true if has a role - var waiRole = this.getWaiRole(elem); - return role ? (waiRole.indexOf(role) > -1) : (waiRole.length > 0); - }, +return dojo.parser; +}); - getWaiRole: function(/*Element*/ elem){ - // summary: - // Gets the role for an element (which should be a wai role). - // returns: - // The role of elem or an empty string if elem - // does not have a role. - return dojo.trim((dojo.attr(elem, "role") || "").replace("wairole:","")); - }, +}, +'url:dijit/form/templates/DropDownButton.html':"<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdata-dojo-attach-event=\"ondijitclick:_onClick\" data-dojo-attach-point=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdata-dojo-attach-point=\"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\tdata-dojo-attach-point=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdata-dojo-attach-point=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">▼</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdata-dojo-attach-point=\"valueNode\"\n/></span>\n", +'dojo/dnd/Manager':function(){ +define("dojo/dnd/Manager", ["../main", "../Evented", "./common", "./autoscroll", "./Avatar"], function(dojo, Evented) { + // module: + // dojo/dnd/Manager + // summary: + // TODOC - setWaiRole: function(/*Element*/ elem, /*String*/ role){ - // summary: - // Sets the role on an element. - // description: - // Replace existing role attribute with new role. - dojo.attr(elem, "role", role); +var Manager = dojo.declare("dojo.dnd.Manager", [Evented], { + // summary: + // the manager of DnD operations (usually a singleton) + constructor: function(){ + this.avatar = null; + this.source = null; + this.nodes = []; + this.copy = true; + this.target = null; + this.canDropFlag = false; + this.events = []; }, - removeWaiRole: function(/*Element*/ elem, /*String*/ role){ - // summary: - // Removes the specified role from an element. - // Removes role attribute if no specific role provided (for backwards compat.) + // avatar's offset from the mouse + OFFSET_X: 16, + OFFSET_Y: 16, - var roleValue = dojo.attr(elem, "role"); - if(!roleValue){ return; } - if(role){ - var t = dojo.trim((" " + roleValue + " ").replace(" " + role + " ", " ")); - dojo.attr(elem, "role", t); + // methods + overSource: function(source){ + // summary: + // called when a source detected a mouse-over condition + // source: Object + // the reporter + if(this.avatar){ + this.target = (source && source.targetState != "Disabled") ? source : null; + this.canDropFlag = Boolean(this.target); + this.avatar.update(); + } + dojo.publish("/dnd/source/over", [source]); + }, + outSource: function(source){ + // summary: + // called when a source detected a mouse-out condition + // source: Object + // the reporter + if(this.avatar){ + if(this.target == source){ + this.target = null; + this.canDropFlag = false; + this.avatar.update(); + dojo.publish("/dnd/source/over", [null]); + } }else{ - elem.removeAttribute("role"); + dojo.publish("/dnd/source/over", [null]); } }, - - hasWaiState: function(/*Element*/ elem, /*String*/ state){ + startDrag: function(source, nodes, copy){ // summary: - // Determines if an element has a given state. - // description: - // Checks for an attribute called "aria-"+state. - // returns: - // true if elem has a value for the given state and - // false if it does not. - - return elem.hasAttribute ? elem.hasAttribute("aria-"+state) : !!elem.getAttribute("aria-"+state); + // called to initiate the DnD operation + // source: Object + // the source which provides items + // nodes: Array + // the list of transferred items + // copy: Boolean + // copy items, if true, move items otherwise + this.source = source; + this.nodes = nodes; + this.copy = Boolean(copy); // normalizing to true boolean + this.avatar = this.makeAvatar(); + dojo.body().appendChild(this.avatar.node); + dojo.publish("/dnd/start", [source, nodes, this.copy]); + this.events = [ + dojo.connect(dojo.doc, "onmousemove", this, "onMouseMove"), + dojo.connect(dojo.doc, "onmouseup", this, "onMouseUp"), + dojo.connect(dojo.doc, "onkeydown", this, "onKeyDown"), + dojo.connect(dojo.doc, "onkeyup", this, "onKeyUp"), + // cancel text selection and text dragging + dojo.connect(dojo.doc, "ondragstart", dojo.stopEvent), + dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent) + ]; + var c = "dojoDnd" + (copy ? "Copy" : "Move"); + dojo.addClass(dojo.body(), c); }, - - getWaiState: function(/*Element*/ elem, /*String*/ state){ + canDrop: function(flag){ // summary: - // Gets the value of a state on an element. - // description: - // Checks for an attribute called "aria-"+state. - // returns: - // The value of the requested state on elem - // or an empty string if elem has no value for state. - - return elem.getAttribute("aria-"+state) || ""; + // called to notify if the current target can accept items + var canDropFlag = Boolean(this.target && flag); + if(this.canDropFlag != canDropFlag){ + this.canDropFlag = canDropFlag; + this.avatar.update(); + } + }, + stopDrag: function(){ + // summary: + // stop the DnD in progress + dojo.removeClass(dojo.body(), ["dojoDndCopy", "dojoDndMove"]); + dojo.forEach(this.events, dojo.disconnect); + this.events = []; + this.avatar.destroy(); + this.avatar = null; + this.source = this.target = null; + this.nodes = []; + }, + makeAvatar: function(){ + // summary: + // makes the avatar; it is separate to be overwritten dynamically, if needed + return new dojo.dnd.Avatar(this); + }, + updateAvatar: function(){ + // summary: + // updates the avatar; it is separate to be overwritten dynamically, if needed + this.avatar.update(); }, - setWaiState: function(/*Element*/ elem, /*String*/ state, /*String*/ value){ + // mouse event processors + onMouseMove: function(e){ // summary: - // Sets a state on an element. - // description: - // Sets an attribute called "aria-"+state. + // event processor for onmousemove + // e: Event + // mouse event + var a = this.avatar; + if(a){ + dojo.dnd.autoScrollNodes(e); + //dojo.dnd.autoScroll(e); + var s = a.node.style; + s.left = (e.pageX + this.OFFSET_X) + "px"; + s.top = (e.pageY + this.OFFSET_Y) + "px"; + var copy = Boolean(this.source.copyState(dojo.isCopyKey(e))); + if(this.copy != copy){ + this._setCopyStatus(copy); + } + } + }, + onMouseUp: function(e){ + // summary: + // event processor for onmouseup + // e: Event + // mouse event + if(this.avatar){ + if(this.target && this.canDropFlag){ + var copy = Boolean(this.source.copyState(dojo.isCopyKey(e))), + params = [this.source, this.nodes, copy, this.target, e]; + dojo.publish("/dnd/drop/before", params); + dojo.publish("/dnd/drop", params); + }else{ + dojo.publish("/dnd/cancel"); + } + this.stopDrag(); + } + }, - elem.setAttribute("aria-"+state, value); + // keyboard event processors + onKeyDown: function(e){ + // summary: + // event processor for onkeydown: + // watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag + // e: Event + // keyboard event + if(this.avatar){ + switch(e.keyCode){ + case dojo.keys.CTRL: + var copy = Boolean(this.source.copyState(true)); + if(this.copy != copy){ + this._setCopyStatus(copy); + } + break; + case dojo.keys.ESCAPE: + dojo.publish("/dnd/cancel"); + this.stopDrag(); + break; + } + } + }, + onKeyUp: function(e){ + // summary: + // event processor for onkeyup, watching for CTRL for copy/move status + // e: Event + // keyboard event + if(this.avatar && e.keyCode == dojo.keys.CTRL){ + var copy = Boolean(this.source.copyState(false)); + if(this.copy != copy){ + this._setCopyStatus(copy); + } + } }, - removeWaiState: function(/*Element*/ elem, /*String*/ state){ + // utilities + _setCopyStatus: function(copy){ // summary: - // Removes a state from an element. - // description: - // Sets an attribute called "aria-"+state. + // changes the copy status + // copy: Boolean + // the copy status + this.copy = copy; + this.source._markDndStatus(this.copy); + this.updateAvatar(); + dojo.replaceClass(dojo.body(), + "dojoDnd" + (this.copy ? "Copy" : "Move"), + "dojoDnd" + (this.copy ? "Move" : "Copy")); + } +}); + +// dojo.dnd._manager: +// The manager singleton variable. Can be overwritten if needed. +dojo.dnd._manager = null; - elem.removeAttribute("aria-"+state); +Manager.manager = dojo.dnd.manager = function(){ + // summary: + // Returns the current DnD manager. Creates one if it is not created yet. + if(!dojo.dnd._manager){ + dojo.dnd._manager = new dojo.dnd.Manager(); } + return dojo.dnd._manager; // Object +}; + +return Manager; }); -} +}, +'dijit/form/ToggleButton':function(){ +define("dijit/form/ToggleButton", [ + "dojo/_base/declare", // declare + "dojo/_base/kernel", // kernel.deprecated + "./Button", + "./_ToggleButtonMixin" +], function(declare, kernel, Button, _ToggleButtonMixin){ -if(!dojo._hasResource["dijit._base"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._base"] = true; -dojo.provide("dijit._base"); +/*===== + var Button = dijit.form.Button; + var _ToggleButtonMixin = dijit.form._ToggleButtonMixin; +=====*/ + // module: + // dijit/form/ToggleButton + // summary: + // A templated button widget that can be in two states (checked or not). + return declare("dijit.form.ToggleButton", [Button, _ToggleButtonMixin], { + // summary: + // A templated button widget that can be in two states (checked or not). + // Can be base class for things like tabs or checkbox or radio buttons + baseClass: "dijitToggleButton", + setChecked: function(/*Boolean*/ checked){ + // summary: + // Deprecated. Use set('checked', true/false) instead. + kernel.deprecated("setChecked("+checked+") is deprecated. Use set('checked',"+checked+") instead.", "", "2.0"); + this.set('checked', checked); + } + }); +}); +}, +'dojo/date/stamp':function(){ +define("dojo/date/stamp", ["../_base/kernel", "../_base/lang", "../_base/array"], function(dojo, lang, array) { + // module: + // dojo/date/stamp + // summary: + // TODOC +lang.getObject("date.stamp", true, dojo); +// Methods to convert dates to or from a wire (string) format using well-known conventions +dojo.date.stamp.fromISOString = function(/*String*/formattedString, /*Number?*/defaultTime){ + // summary: + // Returns a Date object given a string formatted according to a subset of the ISO-8601 standard. + // + // description: + // Accepts a string formatted according to a profile of ISO8601 as defined by + // [RFC3339](http://www.ietf.org/rfc/rfc3339.txt), except that partial input is allowed. + // Can also process dates as specified [by the W3C](http://www.w3.org/TR/NOTE-datetime) + // The following combinations are valid: + // + // * dates only + // | * yyyy + // | * yyyy-MM + // | * yyyy-MM-dd + // * times only, with an optional time zone appended + // | * THH:mm + // | * THH:mm:ss + // | * THH:mm:ss.SSS + // * and "datetimes" which could be any combination of the above + // + // timezones may be specified as Z (for UTC) or +/- followed by a time expression HH:mm + // Assumes the local time zone if not specified. Does not validate. Improperly formatted + // input may return null. Arguments which are out of bounds will be handled + // by the Date constructor (e.g. January 32nd typically gets resolved to February 1st) + // Only years between 100 and 9999 are supported. + // + // formattedString: + // A string such as 2005-06-30T08:05:00-07:00 or 2005-06-30 or T08:05:00 + // + // defaultTime: + // Used for defaults for fields omitted in the formattedString. + // Uses 1970-01-01T00:00:00.0Z by default. + if(!dojo.date.stamp._isoRegExp){ + dojo.date.stamp._isoRegExp = +//TODO: could be more restrictive and check for 00-59, etc. + /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/; + } + var match = dojo.date.stamp._isoRegExp.exec(formattedString), + result = null; -} + if(match){ + match.shift(); + if(match[1]){match[1]--;} // Javascript Date months are 0-based + if(match[6]){match[6] *= 1000;} // Javascript Date expects fractional seconds as milliseconds + + if(defaultTime){ + // mix in defaultTime. Relatively expensive, so use || operators for the fast path of defaultTime === 0 + defaultTime = new Date(defaultTime); + array.forEach(array.map(["FullYear", "Month", "Date", "Hours", "Minutes", "Seconds", "Milliseconds"], function(prop){ + return defaultTime["get" + prop](); + }), function(value, index){ + match[index] = match[index] || value; + }); + } + 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 + if(match[0] < 100){ + result.setFullYear(match[0] || 1970); + } + + var offset = 0, + zoneSign = match[7] && match[7].charAt(0); + if(zoneSign != 'Z'){ + offset = ((match[8] || 0) * 60) + (Number(match[9]) || 0); + if(zoneSign != '-'){ offset *= -1; } + } + if(zoneSign){ + offset -= result.getTimezoneOffset(); + } + if(offset){ + result.setTime(result.getTime() + offset * 60000); + } + } -if(!dojo._hasResource["dojo.Stateful"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.Stateful"] = true; -dojo.provide("dojo.Stateful"); + return result; // Date or null +}; +/*===== + dojo.date.stamp.__Options = function(){ + // selector: String + // "date" or "time" for partial formatting of the Date object. + // Both date and time will be formatted by default. + // zulu: Boolean + // if true, UTC/GMT is used for a timezone + // milliseconds: Boolean + // if true, output milliseconds + this.selector = selector; + this.zulu = zulu; + this.milliseconds = milliseconds; + } +=====*/ -dojo.declare("dojo.Stateful", null, { +dojo.date.stamp.toISOString = function(/*Date*/dateObject, /*dojo.date.stamp.__Options?*/options){ + // summary: + // Format a Date object as a string according a subset of the ISO-8601 standard + // + // description: + // When options.selector is omitted, output follows [RFC3339](http://www.ietf.org/rfc/rfc3339.txt) + // The local time zone is included as an offset from GMT, except when selector=='time' (time without a date) + // Does not check bounds. Only years between 100 and 9999 are supported. + // + // dateObject: + // A Date object + + var _ = function(n){ return (n < 10) ? "0" + n : n; }; + options = options || {}; + var formattedDate = [], + getter = options.zulu ? "getUTC" : "get", + date = ""; + if(options.selector != "time"){ + var year = dateObject[getter+"FullYear"](); + date = ["0000".substr((year+"").length)+year, _(dateObject[getter+"Month"]()+1), _(dateObject[getter+"Date"]())].join('-'); + } + formattedDate.push(date); + if(options.selector != "date"){ + var time = [_(dateObject[getter+"Hours"]()), _(dateObject[getter+"Minutes"]()), _(dateObject[getter+"Seconds"]())].join(':'); + var millis = dateObject[getter+"Milliseconds"](); + if(options.milliseconds){ + time += "."+ (millis < 100 ? "0" : "") + _(millis); + } + if(options.zulu){ + time += "Z"; + }else if(options.selector != "time"){ + var timezoneOffset = dateObject.getTimezoneOffset(); + var absOffset = Math.abs(timezoneOffset); + time += (timezoneOffset > 0 ? "-" : "+") + + _(Math.floor(absOffset/60)) + ":" + _(absOffset%60); + } + formattedDate.push(time); + } + return formattedDate.join('T'); // String +}; + +return dojo.date.stamp; +}); + +}, +'dojo/Stateful':function(){ +define("dojo/Stateful", ["./_base/kernel", "./_base/declare", "./_base/lang", "./_base/array"], function(dojo, declare, lang, array) { + // module: + // dojo/Stateful + // summary: + // TODOC + +return dojo.declare("dojo.Stateful", null, { // summary: // Base class for objects that provide named properties with optional getter/setter // control and the ability to watch for property changes @@ -3203,15 +5527,17 @@ dojo.declare("dojo.Stateful", null, { // | obj.set("foo","bar"); postscript: function(mixin){ if(mixin){ - dojo.mixin(this, mixin); + lang.mixin(this, mixin); } }, - + get: function(/*String*/name){ // summary: // Get a property on a Stateful instance. // name: // The property to get. + // returns: + // The property value on this Stateful instance. // description: // Get a named property on a Stateful object. The property may // potentially be retrieved via a getter method in subclasses. In the base class @@ -3220,8 +5546,8 @@ dojo.declare("dojo.Stateful", null, { // | stateful = new dojo.Stateful({foo: 3}); // | stateful.get("foo") // returns 3 // | stateful.foo // returns 3 - - return this[name]; + + return this[name]; //Any }, set: function(/*String*/name, /*Object*/value){ // summary: @@ -3230,6 +5556,8 @@ dojo.declare("dojo.Stateful", null, { // The property to set. // value: // The value to set in the property. + // returns: + // The function returns this dojo.Stateful instance. // description: // Sets named properties on a stateful object and notifies any watchers of // the property. A programmatic setter may be defined in subclasses. @@ -3257,7 +5585,7 @@ dojo.declare("dojo.Stateful", null, { if(this._watchCallbacks){ this._watchCallbacks(name, oldValue, value); } - return this; + return this; //dojo.Stateful }, watch: function(/*String?*/name, /*Function*/callback){ // summary: @@ -3275,7 +5603,7 @@ dojo.declare("dojo.Stateful", null, { // the property has been changed. The callback will be called with the |this| // set to the instance, the first argument as the name of the property, the // second argument as the old value and the third argument as the new value. - + var callbacks = this._watchCallbacks; if(!callbacks){ var self = this; @@ -3312,3643 +5640,5497 @@ dojo.declare("dojo.Stateful", null, { propertyCallbacks.push(callback); return { unwatch: function(){ - propertyCallbacks.splice(dojo.indexOf(propertyCallbacks, callback), 1); + propertyCallbacks.splice(array.indexOf(propertyCallbacks, callback), 1); } - }; + }; //Object } - + }); -} +}); -if(!dojo._hasResource["dijit._WidgetBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._WidgetBase"] = true; -dojo.provide("dijit._WidgetBase"); +}, +'dijit/layout/AccordionContainer':function(){ +require({cache:{ +'url:dijit/layout/templates/AccordionButton.html':"<div data-dojo-attach-event='onclick:_onTitleClick' class='dijitAccordionTitle' role=\"presentation\">\n\t<div data-dojo-attach-point='titleNode,focusNode' data-dojo-attach-event='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\" data-dojo-attach-point='iconNode' style=\"vertical-align: middle\" role=\"presentation\"/>\n\t\t<span role=\"presentation\" data-dojo-attach-point='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n"}}); +define("dijit/layout/AccordionContainer", [ + "require", + "dojo/_base/array", // array.forEach array.map + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/_base/fx", // fx.Animation + "dojo/dom", // dom.setSelectable + "dojo/dom-attr", // domAttr.attr + "dojo/dom-class", // domClass.remove + "dojo/dom-construct", // domConstruct.place + "dojo/dom-geometry", + "dojo/_base/kernel", + "dojo/keys", // keys + "dojo/_base/lang", // lang.getObject lang.hitch + "dojo/_base/sniff", // has("ie") + "dojo/topic", // publish + "../focus", // focus.focus() + "../_base/manager", // manager.defaultDuration + "dojo/ready", + "../_Widget", + "../_Container", + "../_TemplatedMixin", + "../_CssStateMixin", + "./StackContainer", + "./ContentPane", + "dojo/text!./templates/AccordionButton.html" +], function(require, array, declare, event, fx, dom, domAttr, domClass, domConstruct, domGeometry, + kernel, keys, lang, has, topic, focus, manager, ready, + _Widget, _Container, _TemplatedMixin, _CssStateMixin, StackContainer, ContentPane, template){ +/*===== + var _Widget = dijit._Widget; + var _Container = dijit._Container; + var _TemplatedMixin = dijit._TemplatedMixin; + var _CssStateMixin = dijit._CssStateMixin; + var StackContainer = dijit.layout.StackContainer; + var ContentPane = dijit.layout.ContentPane; +=====*/ + // module: + // dijit/layout/AccordionContainer + // summary: + // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time, + // and switching between panes is visualized by sliding the other panes up/down. -(function(){ + // Design notes: + // + // An AccordionContainer is a StackContainer, but each child (typically ContentPane) + // is wrapped in a _AccordionInnerContainer. This is hidden from the caller. + // + // The resulting markup will look like: + // + // <div class=dijitAccordionContainer> + // <div class=dijitAccordionInnerContainer> (one pane) + // <div class=dijitAccordionTitle> (title bar) ... </div> + // <div class=dijtAccordionChildWrapper> (content pane) </div> + // </div> + // </div> + // + // Normally the dijtAccordionChildWrapper is hidden for all but one child (the shown + // child), so the space for the content pane is all the title bars + the one dijtAccordionChildWrapper, + // which on claro has a 1px border plus a 2px bottom margin. + // + // During animation there are two dijtAccordionChildWrapper's shown, so we need + // to compensate for that. -dojo.declare("dijit._WidgetBase", dojo.Stateful, { - // summary: - // Future base class for all Dijit widgets. - // _Widget extends this class adding support for various features needed by desktop. - // id: [const] String - // A unique, opaque ID string that can be assigned by users or by the - // system. If the developer passes an ID which is known not to be - // unique, the specified ID is ignored and the system-generated ID is - // used instead. - id: "", + var AccordionButton = declare("dijit.layout._AccordionButton", [_Widget, _TemplatedMixin, _CssStateMixin], { + // summary: + // The title bar to click to open up an accordion pane. + // Internal widget used by AccordionContainer. + // tags: + // private - // lang: [const] String - // Rarely used. Overrides the default Dojo locale used to render this widget, - // as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute. - // Value must be among the list of locales specified during by the Dojo bootstrap, - // formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us). - lang: "", + templateString: template, - // dir: [const] String - // Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir) - // attribute. Either left-to-right "ltr" or right-to-left "rtl". If undefined, widgets renders in page's - // default direction. - dir: "", + // label: String + // Title of the pane + label: "", + _setLabelAttr: {node: "titleTextNode", type: "innerHTML" }, - // class: String - // HTML class attribute - "class": "", + // title: String + // Tooltip that appears on hover + title: "", + _setTitleAttr: {node: "titleTextNode", type: "attribute", attribute: "title"}, - // style: String||Object - // HTML style attributes as cssText string or name/value hash - style: "", + // iconClassAttr: String + // CSS class for icon to left of label + iconClassAttr: "", + _setIconClassAttr: { node: "iconNode", type: "class" }, - // title: String - // HTML title attribute. - // - // For form widgets this specifies a tooltip to display when hovering over - // the widget (just like the native HTML title attribute). - // - // For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer, - // etc., it's used to specify the tab label, accordion pane title, etc. - title: "", + baseClass: "dijitAccordionTitle", - // tooltip: String - // When this widget's title attribute is used to for a tab label, accordion pane title, etc., - // this specifies the tooltip to appear when the mouse is hovered over that text. - tooltip: "", + getParent: function(){ + // summary: + // Returns the AccordionContainer parent. + // tags: + // private + return this.parent; + }, - // baseClass: [protected] String - // Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate - // widget state. - baseClass: "", + buildRendering: function(){ + this.inherited(arguments); + var titleTextNodeId = this.id.replace(' ','_'); + domAttr.set(this.titleTextNode, "id", titleTextNodeId+"_title"); + this.focusNode.setAttribute("aria-labelledby", domAttr.get(this.titleTextNode, "id")); + dom.setSelectable(this.domNode, false); + }, - // srcNodeRef: [readonly] DomNode - // pointer to original DOM node - srcNodeRef: null, + getTitleHeight: function(){ + // summary: + // Returns the height of the title dom node. + return domGeometry.getMarginSize(this.domNode).h; // Integer + }, - // domNode: [readonly] DomNode - // This is our visible representation of the widget! Other DOM - // Nodes may by assigned to other properties, usually through the - // template system's dojoAttachPoint syntax, but the domNode - // property is the canonical "top level" node in widget UI. - domNode: null, + // TODO: maybe the parent should set these methods directly rather than forcing the code + // into the button widget? + _onTitleClick: function(){ + // summary: + // Callback when someone clicks my title. + var parent = this.getParent(); + parent.selectChild(this.contentWidget, true); + focus.focus(this.focusNode); + }, - // containerNode: [readonly] DomNode - // Designates where children of the source DOM node will be placed. - // "Children" in this case refers to both DOM nodes and widgets. - // For example, for myWidget: - // - // | <div dojoType=myWidget> - // | <b> here's a plain DOM node - // | <span dojoType=subWidget>and a widget</span> - // | <i> and another plain DOM node </i> - // | </div> - // - // containerNode would point to: - // - // | <b> here's a plain DOM node - // | <span dojoType=subWidget>and a widget</span> - // | <i> and another plain DOM node </i> - // - // In templated widgets, "containerNode" is set via a - // dojoAttachPoint assignment. - // - // containerNode must be defined for any widget that accepts innerHTML - // (like ContentPane or BorderContainer or even Button), and conversely - // is null for widgets that don't, like TextBox. - containerNode: null, + _onTitleKeyPress: function(/*Event*/ evt){ + return this.getParent()._onKeyPress(evt, this.contentWidget); + }, + + _setSelectedAttr: function(/*Boolean*/ isSelected){ + this._set("selected", isSelected); + this.focusNode.setAttribute("aria-expanded", isSelected); + this.focusNode.setAttribute("aria-selected", isSelected); + this.focusNode.setAttribute("tabIndex", isSelected ? "0" : "-1"); + } + }); + + var AccordionInnerContainer = declare("dijit.layout._AccordionInnerContainer", [_Widget, _CssStateMixin], { + // summary: + // Internal widget placed as direct child of AccordionContainer.containerNode. + // When other widgets are added as children to an AccordionContainer they are wrapped in + // this widget. /*===== - // _started: Boolean - // startup() has completed. - _started: false, + // buttonWidget: Function || String + // Class to use to instantiate title + // (Wish we didn't have a separate widget for just the title but maintaining it + // for backwards compatibility, is it worth it?) + buttonWidget: null, =====*/ - // attributeMap: [protected] Object - // attributeMap sets up a "binding" between attributes (aka properties) - // of the widget and the widget's DOM. - // Changes to widget attributes listed in attributeMap will be - // reflected into the DOM. - // - // For example, calling set('title', 'hello') - // on a TitlePane will automatically cause the TitlePane's DOM to update - // with the new title. - // - // attributeMap is a hash where the key is an attribute of the widget, - // and the value reflects a binding to a: - // - // - DOM node attribute - // | focus: {node: "focusNode", type: "attribute"} - // Maps this.focus to this.focusNode.focus - // - // - DOM node innerHTML - // | title: { node: "titleNode", type: "innerHTML" } - // Maps this.title to this.titleNode.innerHTML - // - // - DOM node innerText - // | title: { node: "titleNode", type: "innerText" } - // Maps this.title to this.titleNode.innerText - // - // - DOM node CSS class - // | myClass: { node: "domNode", type: "class" } - // Maps this.myClass to this.domNode.className - // - // If the value is an array, then each element in the array matches one of the - // formats of the above list. - // - // There are also some shorthands for backwards compatibility: - // - string --> { node: string, type: "attribute" }, for example: - // | "focusNode" ---> { node: "focusNode", type: "attribute" } - // - "" --> { node: "domNode", type: "attribute" } - attributeMap: {id:"", dir:"", lang:"", "class":"", style:"", title:""}, +/*===== + // contentWidget: dijit._Widget + // Pointer to the real child widget + contentWidget: null, +=====*/ - // _blankGif: [protected] String - // Path to a blank 1x1 image. - // Used by <img> nodes in templates that really get their image via CSS background-image. - _blankGif: (dojo.config.blankGif || dojo.moduleUrl("dojo", "resources/blank.gif")).toString(), + baseClass: "dijitAccordionInnerContainer", - //////////// INITIALIZATION METHODS /////////////////////////////////////// + // tell nested layout widget that we will take care of sizing + isLayoutContainer: true, - postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){ - // summary: - // Kicks off widget instantiation. See create() for details. - // tags: - // private - this.create(params, srcNodeRef); - }, + buildRendering: function(){ + // Builds a template like: + // <div class=dijitAccordionInnerContainer> + // Button + // <div class=dijitAccordionChildWrapper> + // ContentPane + // </div> + // </div> - create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){ - // summary: - // Kick off the life-cycle of a widget - // params: - // Hash of initialization parameters for widget, including - // scalar values (like title, duration etc.) and functions, - // typically callbacks like onClick. - // srcNodeRef: - // If a srcNodeRef (DOM node) is specified: - // - use srcNodeRef.innerHTML as my contents - // - if this is a behavioral widget then apply behavior - // to that srcNodeRef - // - otherwise, replace srcNodeRef with my generated DOM - // tree - // description: - // Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate, - // etc.), some of which of you'll want to override. See http://docs.dojocampus.org/dijit/_Widget - // for a discussion of the widget creation lifecycle. - // - // Of course, adventurous developers could override create entirely, but this should - // only be done as a last resort. - // tags: - // private + // Create wrapper div, placed where the child is now + this.domNode = domConstruct.place("<div class='" + this.baseClass + + "' role='presentation'>", this.contentWidget.domNode, "after"); - // store pointer to original DOM tree - this.srcNodeRef = dojo.byId(srcNodeRef); + // wrapper div's first child is the button widget (ie, the title bar) + var child = this.contentWidget, + cls = lang.isString(this.buttonWidget) ? lang.getObject(this.buttonWidget) : this.buttonWidget; + this.button = child._buttonWidget = (new cls({ + contentWidget: child, + label: child.title, + title: child.tooltip, + dir: child.dir, + lang: child.lang, + textDir: child.textDir, + iconClass: child.iconClass, + id: child.id + "_button", + parent: this.parent + })).placeAt(this.domNode); - // For garbage collection. An array of handles returned by Widget.connect() - // Each handle returned from Widget.connect() is an array of handles from dojo.connect() - this._connects = []; + // and then the actual content widget (changing it from prior-sibling to last-child), + // wrapped by a <div class=dijitAccordionChildWrapper> + this.containerNode = domConstruct.place("<div class='dijitAccordionChildWrapper' style='display:none'>", this.domNode); + domConstruct.place(this.contentWidget.domNode, this.containerNode); + }, - // For garbage collection. An array of handles returned by Widget.subscribe() - // The handle returned from Widget.subscribe() is the handle returned from dojo.subscribe() - this._subscribes = []; + postCreate: function(){ + this.inherited(arguments); - // mix in our passed parameters - if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; } - if(params){ - this.params = params; - dojo._mixin(this, params); - } - this.postMixInProperties(); + // Map changes in content widget's title etc. to changes in the button + var button = this.button; + this._contentWidgetWatches = [ + this.contentWidget.watch('title', lang.hitch(this, function(name, oldValue, newValue){ + button.set("label", newValue); + })), + this.contentWidget.watch('tooltip', lang.hitch(this, function(name, oldValue, newValue){ + button.set("title", newValue); + })), + this.contentWidget.watch('iconClass', lang.hitch(this, function(name, oldValue, newValue){ + button.set("iconClass", newValue); + })) + ]; + }, - // generate an id for the widget if one wasn't specified - // (be sure to do this before buildRendering() because that function might - // expect the id to be there.) - if(!this.id){ - this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_")); - } - dijit.registry.add(this); + _setSelectedAttr: function(/*Boolean*/ isSelected){ + this._set("selected", isSelected); + this.button.set("selected", isSelected); + if(isSelected){ + var cw = this.contentWidget; + if(cw.onSelected){ cw.onSelected(); } + } + }, - this.buildRendering(); + startup: function(){ + // Called by _Container.addChild() + this.contentWidget.startup(); + }, - if(this.domNode){ - // Copy attributes listed in attributeMap into the [newly created] DOM for the widget. - // Also calls custom setters for all attributes with custom setters. - this._applyAttributes(); + destroy: function(){ + this.button.destroyRecursive(); - // If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree. - // For 2.0, move this after postCreate(). postCreate() shouldn't depend on the - // widget being attached to the DOM since it isn't when a widget is created programmatically like - // new MyWidget({}). See #11635. - var source = this.srcNodeRef; - if(source && source.parentNode && this.domNode !== source){ - source.parentNode.replaceChild(this.domNode, source); - } - } + array.forEach(this._contentWidgetWatches || [], function(w){ w.unwatch(); }); - if(this.domNode){ - // Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId", - // assuming that dojo._scopeName even exists in 2.0 - this.domNode.setAttribute("widgetId", this.id); - } - this.postCreate(); + delete this.contentWidget._buttonWidget; + delete this.contentWidget._wrapperWidget; - // If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC. - if(this.srcNodeRef && !this.srcNodeRef.parentNode){ - delete this.srcNodeRef; - } + this.inherited(arguments); + }, - this._created = true; - }, + destroyDescendants: function(/*Boolean*/ preserveDom){ + // since getChildren isn't working for me, have to code this manually + this.contentWidget.destroyRecursive(preserveDom); + } + }); - _applyAttributes: function(){ + var AccordionContainer = declare("dijit.layout.AccordionContainer", StackContainer, { // summary: - // Step during widget creation to copy all widget attributes to the - // DOM as per attributeMap and _setXXXAttr functions. - // description: - // Skips over blank/false attribute values, unless they were explicitly specified - // as parameters to the widget, since those are the default anyway, - // and setting tabIndex="" is different than not setting tabIndex at all. - // - // It processes the attributes in the attribute map first, and then - // it goes through and processes the attributes for the _setXXXAttr - // functions that have been specified - // tags: - // private - var condAttrApply = function(attr, scope){ - if((scope.params && attr in scope.params) || scope[attr]){ - scope.set(attr, scope[attr]); - } - }; + // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time, + // and switching between panes is visualized by sliding the other panes up/down. + // example: + // | <div data-dojo-type="dijit.layout.AccordionContainer"> + // | <div data-dojo-type="dijit.layout.ContentPane" title="pane 1"> + // | </div> + // | <div data-dojo-type="dijit.layout.ContentPane" title="pane 2"> + // | <p>This is some text</p> + // | </div> + // | </div> - // Do the attributes in attributeMap - for(var attr in this.attributeMap){ - condAttrApply(attr, this); - } + // duration: Integer + // Amount of time (in ms) it takes to slide panes + duration: manager.defaultDuration, - // And also any attributes with custom setters - dojo.forEach(this._getSetterAttributes(), function(a){ - if(!(a in this.attributeMap)){ - condAttrApply(a, this); + // buttonWidget: [const] String + // The name of the widget used to display the title of each pane + buttonWidget: AccordionButton, + +/*===== + // _verticalSpace: Number + // Pixels of space available for the open pane + // (my content box size minus the cumulative size of all the title bars) + _verticalSpace: 0, +=====*/ + baseClass: "dijitAccordionContainer", + + buildRendering: function(){ + this.inherited(arguments); + this.domNode.style.overflow = "hidden"; // TODO: put this in dijit.css + this.domNode.setAttribute("role", "tablist"); // TODO: put this in template + }, + + startup: function(){ + if(this._started){ return; } + this.inherited(arguments); + if(this.selectedChildWidget){ + var style = this.selectedChildWidget.containerNode.style; + style.display = ""; + style.overflow = "auto"; + this.selectedChildWidget._wrapperWidget.set("selected", true); } - }, this); - }, + }, - _getSetterAttributes: function(){ - // summary: - // Returns list of attributes with custom setters for this widget - var ctor = this.constructor; - if(!ctor._setterAttrs){ - var r = (ctor._setterAttrs = []), - attrs, - proto = ctor.prototype; - for(var fxName in proto){ - if(dojo.isFunction(proto[fxName]) && (attrs = fxName.match(/^_set([a-zA-Z]*)Attr$/)) && attrs[1]){ - r.push(attrs[1].charAt(0).toLowerCase() + attrs[1].substr(1)); + layout: function(){ + // Implement _LayoutWidget.layout() virtual method. + // Set the height of the open pane based on what room remains. + + var openPane = this.selectedChildWidget; + + if(!openPane){ return;} + + // space taken up by title, plus wrapper div (with border/margin) for open pane + var wrapperDomNode = openPane._wrapperWidget.domNode, + wrapperDomNodeMargin = domGeometry.getMarginExtents(wrapperDomNode), + wrapperDomNodePadBorder = domGeometry.getPadBorderExtents(wrapperDomNode), + wrapperContainerNode = openPane._wrapperWidget.containerNode, + wrapperContainerNodeMargin = domGeometry.getMarginExtents(wrapperContainerNode), + wrapperContainerNodePadBorder = domGeometry.getPadBorderExtents(wrapperContainerNode), + mySize = this._contentBox; + + // get cumulative height of all the unselected title bars + var totalCollapsedHeight = 0; + array.forEach(this.getChildren(), function(child){ + if(child != openPane){ + // Using domGeometry.getMarginSize() rather than domGeometry.position() since claro has 1px bottom margin + // to separate accordion panes. Not sure that works perfectly, it's probably putting a 1px + // margin below the bottom pane (even though we don't want one). + totalCollapsedHeight += domGeometry.getMarginSize(child._wrapperWidget.domNode).h; } + }); + this._verticalSpace = mySize.h - totalCollapsedHeight - wrapperDomNodeMargin.h + - wrapperDomNodePadBorder.h - wrapperContainerNodeMargin.h - wrapperContainerNodePadBorder.h + - openPane._buttonWidget.getTitleHeight(); + + // Memo size to make displayed child + this._containerContentBox = { + h: this._verticalSpace, + w: this._contentBox.w - wrapperDomNodeMargin.w - wrapperDomNodePadBorder.w + - wrapperContainerNodeMargin.w - wrapperContainerNodePadBorder.w + }; + + if(openPane){ + openPane.resize(this._containerContentBox); } - } - return ctor._setterAttrs; // String[] - }, + }, - postMixInProperties: function(){ - // summary: - // Called after the parameters to the widget have been read-in, - // but before the widget template is instantiated. Especially - // useful to set properties that are referenced in the widget - // template. - // tags: - // protected - }, + _setupChild: function(child){ + // Overrides _LayoutWidget._setupChild(). + // Put wrapper widget around the child widget, showing title - buildRendering: function(){ - // summary: - // Construct the UI for this widget, setting this.domNode - // description: - // Most widgets will mixin `dijit._Templated`, which implements this - // method. - // tags: - // protected + child._wrapperWidget = AccordionInnerContainer({ + contentWidget: child, + buttonWidget: this.buttonWidget, + id: child.id + "_wrapper", + dir: child.dir, + lang: child.lang, + textDir: child.textDir, + parent: this + }); - if(!this.domNode){ - // Create root node if it wasn't created by _Templated - this.domNode = this.srcNodeRef || dojo.create('div'); - } + this.inherited(arguments); + }, - // baseClass is a single class name or occasionally a space-separated list of names. - // Add those classes to the DOMNode. If RTL mode then also add with Rtl suffix. - // TODO: make baseClass custom setter - if(this.baseClass){ - var classes = this.baseClass.split(" "); - if(!this.isLeftToRight()){ - classes = classes.concat( dojo.map(classes, function(name){ return name+"Rtl"; })); + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + if(this._started){ + // Adding a child to a started Accordion is complicated because children have + // wrapper widgets. Default code path (calling this.inherited()) would add + // the new child inside another child's wrapper. + + // First add in child as a direct child of this AccordionContainer + var refNode = this.containerNode; + if(insertIndex && typeof insertIndex == "number"){ + var children = _Widget.prototype.getChildren.call(this); // get wrapper panes + if(children && children.length >= insertIndex){ + refNode = children[insertIndex-1].domNode; + insertIndex = "after"; + } + } + domConstruct.place(child.domNode, refNode, insertIndex); + + if(!child._started){ + child.startup(); + } + + // Then stick the wrapper widget around the child widget + this._setupChild(child); + + // Code below copied from StackContainer + topic.publish(this.id+"-addChild", child, insertIndex); // publish + this.layout(); + if(!this.selectedChildWidget){ + this.selectChild(child); + } + }else{ + // We haven't been started yet so just add in the child widget directly, + // and the wrapper will be created on startup() + this.inherited(arguments); } - dojo.addClass(this.domNode, classes); - } - }, + }, - postCreate: function(){ - // summary: - // Processing after the DOM fragment is created - // description: - // Called after the DOM fragment has been created, but not necessarily - // added to the document. Do not include any operations which rely on - // node dimensions or placement. - // tags: - // protected - }, + removeChild: function(child){ + // Overrides _LayoutWidget.removeChild(). - startup: function(){ - // summary: - // Processing after the DOM fragment is added to the document - // description: - // Called after a widget and its children have been created and added to the page, - // and all related widgets have finished their create() cycle, up through postCreate(). - // This is useful for composite widgets that need to control or layout sub-widgets. - // Many layout widgets can use this as a wiring phase. - this._started = true; - }, + // Destroy wrapper widget first, before StackContainer.getChildren() call. + // Replace wrapper widget with true child widget (ContentPane etc.). + // This step only happens if the AccordionContainer has been started; otherwise there's no wrapper. + if(child._wrapperWidget){ + domConstruct.place(child.domNode, child._wrapperWidget.domNode, "after"); + child._wrapperWidget.destroy(); + delete child._wrapperWidget; + } - //////////// DESTROY FUNCTIONS //////////////////////////////// + domClass.remove(child.domNode, "dijitHidden"); - destroyRecursive: function(/*Boolean?*/ preserveDom){ - // summary: - // Destroy this widget and its descendants - // description: - // This is the generic "destructor" function that all widget users - // should call to cleanly discard with a widget. Once a widget is - // destroyed, it is removed from the manager object. - // preserveDom: - // If true, this method will leave the original DOM structure - // alone of descendant Widgets. Note: This will NOT work with - // dijit._Templated widgets. + this.inherited(arguments); + }, - this._beingDestroyed = true; - this.destroyDescendants(preserveDom); - this.destroy(preserveDom); - }, + getChildren: function(){ + // Overrides _Container.getChildren() to return content panes rather than internal AccordionInnerContainer panes + return array.map(this.inherited(arguments), function(child){ + return child.declaredClass == "dijit.layout._AccordionInnerContainer" ? child.contentWidget : child; + }, this); + }, - destroy: function(/*Boolean*/ preserveDom){ - // summary: - // Destroy this widget, but not its descendants. - // This method will, however, destroy internal widgets such as those used within a template. - // preserveDom: Boolean - // If true, this method will leave the original DOM structure alone. - // Note: This will not yet work with _Templated widgets + destroy: function(){ + if(this._animation){ + this._animation.stop(); + } + array.forEach(this.getChildren(), function(child){ + // If AccordionContainer has been started, then each child has a wrapper widget which + // also needs to be destroyed. + if(child._wrapperWidget){ + child._wrapperWidget.destroy(); + }else{ + child.destroyRecursive(); + } + }); + this.inherited(arguments); + }, - this._beingDestroyed = true; - this.uninitialize(); - var d = dojo, - dfe = d.forEach, - dun = d.unsubscribe; - dfe(this._connects, function(array){ - dfe(array, d.disconnect); - }); - dfe(this._subscribes, function(handle){ - dun(handle); - }); + _showChild: function(child){ + // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode + child._wrapperWidget.containerNode.style.display="block"; + return this.inherited(arguments); + }, - // destroy widgets created as part of template, etc. - dfe(this._supportingWidgets || [], function(w){ - if(w.destroyRecursive){ - w.destroyRecursive(); - }else if(w.destroy){ - w.destroy(); + _hideChild: function(child){ + // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode + child._wrapperWidget.containerNode.style.display="none"; + this.inherited(arguments); + }, + + _transition: function(/*dijit._Widget?*/ newWidget, /*dijit._Widget?*/ oldWidget, /*Boolean*/ animate){ + // Overrides StackContainer._transition() to provide sliding of title bars etc. + + if(has("ie") < 8){ + // workaround animation bugs by not animating; not worth supporting animation for IE6 & 7 + animate = false; } - }); - this.destroyRendering(preserveDom); - dijit.registry.remove(this.id); - this._destroyed = true; - }, + if(this._animation){ + // there's an in-progress animation. speedily end it so we can do the newly requested one + this._animation.stop(true); + delete this._animation; + } - destroyRendering: function(/*Boolean?*/ preserveDom){ - // summary: - // Destroys the DOM nodes associated with this widget - // preserveDom: - // If true, this method will leave the original DOM structure alone - // during tear-down. Note: this will not work with _Templated - // widgets yet. - // tags: - // protected + var self = this; - if(this.bgIframe){ - this.bgIframe.destroy(preserveDom); - delete this.bgIframe; - } + if(newWidget){ + newWidget._wrapperWidget.set("selected", true); - if(this.domNode){ - if(preserveDom){ - dojo.removeAttr(this.domNode, "widgetId"); - }else{ - dojo.destroy(this.domNode); + var d = this._showChild(newWidget); // prepare widget to be slid in + + // Size the new widget, in case this is the first time it's being shown, + // or I have been resized since the last time it was shown. + // Note that page must be visible for resizing to work. + if(this.doLayout && newWidget.resize){ + newWidget.resize(this._containerContentBox); + } } - delete this.domNode; - } - if(this.srcNodeRef){ - if(!preserveDom){ - dojo.destroy(this.srcNodeRef); + if(oldWidget){ + oldWidget._wrapperWidget.set("selected", false); + if(!animate){ + this._hideChild(oldWidget); + } } - delete this.srcNodeRef; - } - }, - destroyDescendants: function(/*Boolean?*/ preserveDom){ - // summary: - // Recursively destroy the children of this widget and their - // descendants. - // preserveDom: - // If true, the preserveDom attribute is passed to all descendant - // widget's .destroy() method. Not for use with _Templated - // widgets. + if(animate){ + var newContents = newWidget._wrapperWidget.containerNode, + oldContents = oldWidget._wrapperWidget.containerNode; - // get all direct descendants and destroy them recursively - dojo.forEach(this.getChildren(), function(widget){ - if(widget.destroyRecursive){ - widget.destroyRecursive(preserveDom); + // During the animation we will be showing two dijitAccordionChildWrapper nodes at once, + // which on claro takes up 4px extra space (compared to stable AccordionContainer). + // Have to compensate for that by immediately shrinking the pane being closed. + var wrapperContainerNode = newWidget._wrapperWidget.containerNode, + wrapperContainerNodeMargin = domGeometry.getMarginExtents(wrapperContainerNode), + wrapperContainerNodePadBorder = domGeometry.getPadBorderExtents(wrapperContainerNode), + animationHeightOverhead = wrapperContainerNodeMargin.h + wrapperContainerNodePadBorder.h; + + oldContents.style.height = (self._verticalSpace - animationHeightOverhead) + "px"; + + this._animation = new fx.Animation({ + node: newContents, + duration: this.duration, + curve: [1, this._verticalSpace - animationHeightOverhead - 1], + onAnimate: function(value){ + value = Math.floor(value); // avoid fractional values + newContents.style.height = value + "px"; + oldContents.style.height = (self._verticalSpace - animationHeightOverhead - value) + "px"; + }, + onEnd: function(){ + delete self._animation; + newContents.style.height = "auto"; + oldWidget._wrapperWidget.containerNode.style.display = "none"; + oldContents.style.height = "auto"; + self._hideChild(oldWidget); + } + }); + this._animation.onStop = this._animation.onEnd; + this._animation.play(); + } + + return d; // If child has an href, promise that fires when the widget has finished loading + }, + + // note: we are treating the container as controller here + _onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){ + // summary: + // Handle keypress events + // description: + // This is called from a handler on AccordionContainer.domNode + // (setup in StackContainer), and is also called directly from + // the click handler for accordion labels + if(this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){ + return; + } + var c = e.charOrCode; + if((fromTitle && (c == keys.LEFT_ARROW || c == keys.UP_ARROW)) || + (e.ctrlKey && c == keys.PAGE_UP)){ + this._adjacent(false)._buttonWidget._onTitleClick(); + event.stop(e); + }else if((fromTitle && (c == keys.RIGHT_ARROW || c == keys.DOWN_ARROW)) || + (e.ctrlKey && (c == keys.PAGE_DOWN || c == keys.TAB))){ + this._adjacent(true)._buttonWidget._onTitleClick(); + event.stop(e); } + } + }); + + // Back compat w/1.6, remove for 2.0 + if(!kernel.isAsync){ + ready(0, function(){ + var requires = ["dijit/layout/AccordionPane"]; + require(requires); // use indirection so modules not rolled into a build }); - }, + } - uninitialize: function(){ - // summary: - // Stub function. Override to implement custom widget tear-down - // behavior. - // tags: - // protected - return false; - }, + // For monkey patching + AccordionContainer._InnerContainer = AccordionInnerContainer; + AccordionContainer._Button = AccordionButton; - ////////////////// GET/SET, CUSTOM SETTERS, ETC. /////////////////// + return AccordionContainer; +}); - _setClassAttr: function(/*String*/ value){ - // summary: - // Custom setter for the CSS "class" attribute - // tags: - // protected - var mapNode = this[this.attributeMap["class"] || 'domNode']; - dojo.replaceClass(mapNode, value, this["class"]); - this._set("class", value); - }, +}, +'dijit/form/_AutoCompleterMixin':function(){ +define("dijit/form/_AutoCompleterMixin", [ + "dojo/_base/connect", // keys keys.SHIFT + "dojo/data/util/filter", // patternToRegExp + "dojo/_base/declare", // declare + "dojo/_base/Deferred", // Deferred.when + "dojo/dom-attr", // domAttr.get + "dojo/_base/event", // event.stop + "dojo/keys", + "dojo/_base/lang", // lang.clone lang.hitch + "dojo/query", // query + "dojo/regexp", // regexp.escapeString + "dojo/_base/sniff", // has("ie") + "dojo/string", // string.substitute + "dojo/_base/window", // win.doc.selection.createRange + "./DataList", + "../registry", // registry.byId + "./_TextBoxMixin" // defines _TextBoxMixin.selectInputText +], function(connect, filter, declare, Deferred, domAttr, event, keys, lang, query, regexp, has, string, win, + DataList, registry, _TextBoxMixin){ + + // module: + // dijit/form/_AutoCompleterMixin + // summary: + // A mixin that implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect` - _setStyleAttr: function(/*String||Object*/ value){ + + return declare("dijit.form._AutoCompleterMixin", null, { // summary: - // Sets the style attribute of the widget according to value, - // which is either a hash like {height: "5px", width: "3px"} - // or a plain string + // A mixin that implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect` // description: - // Determines which node to set the style on based on style setting - // in attributeMap. + // All widgets that mix in dijit.form._AutoCompleterMixin must extend `dijit.form._FormValueWidget`. // tags: // protected - var mapNode = this[this.attributeMap.style || 'domNode']; + // item: Object + // This is the item returned by the dojo.data.store implementation that + // provides the data for this ComboBox, it's the currently selected item. + item: null, - // Note: technically we should revert any style setting made in a previous call - // to his method, but that's difficult to keep track of. + // pageSize: Integer + // Argument to data provider. + // Specifies number of search results per page (before hitting "next" button) + pageSize: Infinity, - if(dojo.isObject(value)){ - dojo.style(mapNode, value); - }else{ - if(mapNode.style.cssText){ - mapNode.style.cssText += "; " + value; - }else{ - mapNode.style.cssText = value; - } - } + // store: [const] dojo.store.api.Store + // Reference to data provider object used by this ComboBox + store: null, - this._set("style", value); - }, + // fetchProperties: Object + // Mixin to the store's fetch. + // For example, to set the sort order of the ComboBox menu, pass: + // | { sort: [{attribute:"name",descending: true}] } + // To override the default queryOptions so that deep=false, do: + // | { queryOptions: {ignoreCase: true, deep: false} } + fetchProperties:{}, - _attrToDom: function(/*String*/ attr, /*String*/ value){ - // summary: - // Reflect a widget attribute (title, tabIndex, duration etc.) to - // the widget DOM, as specified in attributeMap. - // Note some attributes like "type" - // cannot be processed this way as they are not mutable. + // query: Object + // A query that can be passed to 'store' to initially filter the items, + // before doing further filtering based on `searchAttr` and the key. + // Any reference to the `searchAttr` is ignored. + query: {}, + + // autoComplete: Boolean + // If user types in a partial string, and then tab out of the `<input>` box, + // automatically copy the first entry displayed in the drop down list to + // the `<input>` field + autoComplete: true, + + // highlightMatch: String + // One of: "first", "all" or "none". // - // tags: - // private + // If the ComboBox/FilteringSelect opens with the search results and the searched + // string can be found, it will be highlighted. If set to "all" + // then will probably want to change `queryExpr` parameter to '*${0}*' + // + // Highlighting is only performed when `labelType` is "text", so as to not + // interfere with any HTML markup an HTML label might contain. + highlightMatch: "first", - var commands = this.attributeMap[attr]; - dojo.forEach(dojo.isArray(commands) ? commands : [commands], function(command){ + // searchDelay: Integer + // Delay in milliseconds between when user types something and we start + // searching based on that value + searchDelay: 100, - // Get target node and what we are doing to that node - var mapNode = this[command.node || command || "domNode"]; // DOM node - var type = command.type || "attribute"; // class, innerHTML, innerText, or attribute + // searchAttr: String + // Search for items in the data store where this attribute (in the item) + // matches what the user typed + searchAttr: "name", - switch(type){ - case "attribute": - if(dojo.isFunction(value)){ // functions execute in the context of the widget - value = dojo.hitch(this, value); - } + // labelAttr: String? + // The entries in the drop down list come from this attribute in the + // dojo.data items. + // If not specified, the searchAttr attribute is used instead. + labelAttr: "", - // Get the name of the DOM node attribute; usually it's the same - // as the name of the attribute in the widget (attr), but can be overridden. - // Also maps handler names to lowercase, like onSubmit --> onsubmit - var attrName = command.attribute ? command.attribute : - (/^on[A-Z][a-zA-Z]*$/.test(attr) ? attr.toLowerCase() : attr); + // labelType: String + // Specifies how to interpret the labelAttr in the data store items. + // Can be "html" or "text". + labelType: "text", + + // queryExpr: String + // This specifies what query ComboBox/FilteringSelect sends to the data store, + // based on what the user has typed. Changing this expression will modify + // whether the drop down shows only exact matches, a "starting with" match, + // etc. Use it in conjunction with highlightMatch. + // dojo.data query expression pattern. + // `${0}` will be substituted for the user text. + // `*` is used for wildcards. + // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is" + queryExpr: "${0}*", + + // ignoreCase: Boolean + // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items + ignoreCase: true, + + // Flags to _HasDropDown to limit height of drop down to make it fit in viewport + maxHeight: -1, + + // For backwards compatibility let onClick events propagate, even clicks on the down arrow button + _stopClickEvents: false, - dojo.attr(mapNode, attrName, value); + _getCaretPos: function(/*DomNode*/ element){ + // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22 + var pos = 0; + if(typeof(element.selectionStart) == "number"){ + // FIXME: this is totally borked on Moz < 1.3. Any recourse? + pos = element.selectionStart; + }else if(has("ie")){ + // in the case of a mouse click in a popup being handled, + // then the win.doc.selection is not the textarea, but the popup + // var r = win.doc.selection.createRange(); + // hack to get IE 6 to play nice. What a POS browser. + var tr = win.doc.selection.createRange().duplicate(); + var ntr = element.createTextRange(); + tr.move("character",0); + ntr.move("character",0); + try{ + // If control doesn't have focus, you get an exception. + // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes). + // There appears to be no workaround for this - googled for quite a while. + ntr.setEndPoint("EndToEnd", tr); + pos = String(ntr.text).replace(/\r/g,"").length; + }catch(e){ + // If focus has shifted, 0 is fine for caret pos. + } + } + return pos; + }, + + _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){ + location = parseInt(location); + _TextBoxMixin.selectInputText(element, location, location); + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + // Additional code to set disabled state of ComboBox node. + // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr(). + this.inherited(arguments); + this.domNode.setAttribute("aria-disabled", value); + }, + + _abortQuery: function(){ + // stop in-progress query + if(this.searchTimer){ + clearTimeout(this.searchTimer); + this.searchTimer = null; + } + if(this._fetchHandle){ + if(this._fetchHandle.cancel){ + this._cancelingQuery = true; + this._fetchHandle.cancel(); + this._cancelingQuery = false; + } + this._fetchHandle = null; + } + }, + + _onInput: function(/*Event*/ evt){ + // summary: + // Handles paste events + this.inherited(arguments); + if(evt.charOrCode == 229){ // IME or cut/paste event + this._onKey(evt); + } + }, + + _onKey: function(/*Event*/ evt){ + // summary: + // Handles keyboard events + + if(this.disabled || this.readOnly){ return; } + var key = evt.charOrCode; + + // except for cutting/pasting case - ctrl + x/v + if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == keys.SHIFT){ + return; // throw out weird key combinations and spurious events + } + + var doSearch = false; + var pw = this.dropDown; + var highlighted = null; + this._prev_key_backspace = false; + this._abortQuery(); + + // _HasDropDown will do some of the work: + // 1. when drop down is not yet shown: + // - if user presses the down arrow key, call loadDropDown() + // 2. when drop down is already displayed: + // - on ESC key, call closeDropDown() + // - otherwise, call dropDown.handleKey() to process the keystroke + this.inherited(arguments); + + if(this._opened){ + highlighted = pw.getHighlightedOption(); + } + switch(key){ + case keys.PAGE_DOWN: + case keys.DOWN_ARROW: + case keys.PAGE_UP: + case keys.UP_ARROW: + // Keystroke caused ComboBox_menu to move to a different item. + // Copy new item to <input> box. + if(this._opened){ + this._announceOption(highlighted); + } + event.stop(evt); break; - case "innerText": - mapNode.innerHTML = ""; - mapNode.appendChild(dojo.doc.createTextNode(value)); + + case keys.ENTER: + // prevent submitting form if user presses enter. Also + // prevent accepting the value if either Next or Previous + // are selected + if(highlighted){ + // only stop event on prev/next + if(highlighted == pw.nextButton){ + this._nextSearch(1); + event.stop(evt); + break; + }else if(highlighted == pw.previousButton){ + this._nextSearch(-1); + event.stop(evt); + break; + } + }else{ + // Update 'value' (ex: KY) according to currently displayed text + this._setBlurValue(); // set value if needed + this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting + } + // default case: + // if enter pressed while drop down is open, or for FilteringSelect, + // if we are in the middle of a query to convert a directly typed in value to an item, + // prevent submit + if(this._opened || this._fetchHandle){ + event.stop(evt); + } + // fall through + + case keys.TAB: + var newvalue = this.get('displayedValue'); + // if the user had More Choices selected fall into the + // _onBlur handler + if(pw && ( + newvalue == pw._messages["previousMessage"] || + newvalue == pw._messages["nextMessage"]) + ){ + break; + } + if(highlighted){ + this._selectOption(highlighted); + } + // fall through + + case keys.ESCAPE: + if(this._opened){ + this._lastQuery = null; // in case results come back later + this.closeDropDown(); + } break; - case "innerHTML": - mapNode.innerHTML = value; + + case ' ': + if(highlighted){ + // user is effectively clicking a choice in the drop down menu + event.stop(evt); + this._selectOption(highlighted); + this.closeDropDown(); + }else{ + // user typed a space into the input box, treat as normal character + doSearch = true; + } break; - case "class": - dojo.replaceClass(mapNode, value, this[attr]); + + case keys.DELETE: + case keys.BACKSPACE: + this._prev_key_backspace = true; + doSearch = true; break; + + default: + // Non char keys (F1-F12 etc..) shouldn't open list. + // Ascii characters and IME input (Chinese, Japanese etc.) should. + //IME input produces keycode == 229. + doSearch = typeof key == 'string' || key == 229; } - }, this); - }, + if(doSearch){ + // need to wait a tad before start search so that the event + // bubbles through DOM and we have value visible + this.item = undefined; // undefined means item needs to be set + this.searchTimer = setTimeout(lang.hitch(this, "_startSearchFromInput"),1); + } + }, - get: function(name){ - // summary: - // Get a property from a widget. - // name: - // The property to get. - // description: - // Get a named property from a widget. The property may - // potentially be retrieved via a getter method. If no getter is defined, this - // just retrieves the object's property. - // For example, if the widget has a properties "foo" - // and "bar" and a method named "_getFooAttr", calling: - // | myWidget.get("foo"); - // would be equivalent to writing: - // | widget._getFooAttr(); - // and: - // | myWidget.get("bar"); - // would be equivalent to writing: - // | widget.bar; - var names = this._getAttrNames(name); - return this[names.g] ? this[names.g]() : this[name]; - }, - - set: function(name, value){ - // summary: - // Set a property on a widget - // name: - // The property to set. - // value: - // The value to set in the property. - // description: - // Sets named properties on a widget which may potentially be handled by a - // setter in the widget. - // For example, if the widget has a properties "foo" - // and "bar" and a method named "_setFooAttr", calling: - // | myWidget.set("foo", "Howdy!"); - // would be equivalent to writing: - // | widget._setFooAttr("Howdy!"); - // and: - // | myWidget.set("bar", 3); - // would be equivalent to writing: - // | widget.bar = 3; - // - // set() may also be called with a hash of name/value pairs, ex: - // | myWidget.set({ - // | foo: "Howdy", - // | bar: 3 - // | }) - // This is equivalent to calling set(foo, "Howdy") and set(bar, 3) + _autoCompleteText: function(/*String*/ text){ + // summary: + // Fill in the textbox with the first item from the drop down + // list, and highlight the characters that were + // auto-completed. For example, if user typed "CA" and the + // drop down list appeared, the textbox would be changed to + // "California" and "ifornia" would be highlighted. - if(typeof name === "object"){ - for(var x in name){ - this.set(x, name[x]); + var fn = this.focusNode; + + // IE7: clear selection so next highlight works all the time + _TextBoxMixin.selectInputText(fn, fn.value.length); + // does text autoComplete the value in the textbox? + var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr'; + if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){ + var cpos = this.autoComplete ? this._getCaretPos(fn) : fn.value.length; + // only try to extend if we added the last character at the end of the input + if((cpos+1) > fn.value.length){ + // only add to input node as we would overwrite Capitalisation of chars + // actually, that is ok + fn.value = text;//.substr(cpos); + // visually highlight the autocompleted characters + _TextBoxMixin.selectInputText(fn, cpos); + } + }else{ + // text does not autoComplete; replace the whole value and highlight + fn.value = text; + _TextBoxMixin.selectInputText(fn); } - return this; - } - var names = this._getAttrNames(name); - if(this[names.s]){ - // use the explicit setter - var result = this[names.s].apply(this, Array.prototype.slice.call(arguments, 1)); - }else{ - // if param is specified as DOM node attribute, copy it - if(name in this.attributeMap){ - this._attrToDom(name, value); + }, + + _openResultList: function(/*Object*/ results, /*Object*/ query, /*Object*/ options){ + // summary: + // Callback when a search completes. + // description: + // 1. generates drop-down list and calls _showResultList() to display it + // 2. if this result list is from user pressing "more choices"/"previous choices" + // then tell screen reader to announce new option + this._fetchHandle = null; + if( this.disabled || + this.readOnly || + (query[this.searchAttr] !== this._lastQuery) // TODO: better way to avoid getting unwanted notify + ){ + return; + } + var wasSelected = this.dropDown.getHighlightedOption(); + this.dropDown.clearResultList(); + if(!results.length && options.start == 0){ // if no results and not just the previous choices button + this.closeDropDown(); + return; } - this._set(name, value); - } - return result || this; - }, - - _attrPairNames: {}, // shared between all widgets - _getAttrNames: function(name){ - // summary: - // Helper function for get() and set(). - // Caches attribute name values so we don't do the string ops every time. - // tags: - // private - var apn = this._attrPairNames; - if(apn[name]){ return apn[name]; } - var uc = name.charAt(0).toUpperCase() + name.substr(1); - return (apn[name] = { - n: name+"Node", - s: "_set"+uc+"Attr", - g: "_get"+uc+"Attr" - }); - }, + // Fill in the textbox with the first item from the drop down list, + // and highlight the characters that were auto-completed. For + // example, if user typed "CA" and the drop down list appeared, the + // textbox would be changed to "California" and "ifornia" would be + // highlighted. - _set: function(/*String*/ name, /*anything*/ value){ - // summary: - // Helper function to set new value for specified attribute, and call handlers - // registered with watch() if the value has changed. - var oldValue = this[name]; - this[name] = value; - if(this._watchCallbacks && this._created && value !== oldValue){ - this._watchCallbacks(name, oldValue, value); - } - }, + var nodes = this.dropDown.createOptions( + results, + options, + lang.hitch(this, "_getMenuLabelFromItem") + ); - toString: function(){ - // summary: - // Returns a string that represents the widget - // description: - // When a widget is cast to a string, this method will be used to generate the - // output. Currently, it does not implement any sort of reversible - // serialization. - return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String - }, + // show our list (only if we have content, else nothing) + this._showResultList(); - getDescendants: function(){ - // summary: - // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode. - // This method should generally be avoided as it returns widgets declared in templates, which are - // supposed to be internal/hidden, but it's left here for back-compat reasons. + // #4091: + // tell the screen reader that the paging callback finished by + // shouting the next choice + if(options.direction){ + if(1 == options.direction){ + this.dropDown.highlightFirstOption(); + }else if(-1 == options.direction){ + this.dropDown.highlightLastOption(); + } + if(wasSelected){ + this._announceOption(this.dropDown.getHighlightedOption()); + } + }else if(this.autoComplete && !this._prev_key_backspace + // when the user clicks the arrow button to show the full list, + // startSearch looks for "*". + // it does not make sense to autocomplete + // if they are just previewing the options available. + && !/^[*]+$/.test(query[this.searchAttr].toString())){ + this._announceOption(nodes[1]); // 1st real item + } + }, - return this.containerNode ? dojo.query('[widgetId]', this.containerNode).map(dijit.byNode) : []; // dijit._Widget[] - }, + _showResultList: function(){ + // summary: + // Display the drop down if not already displayed, or if it is displayed, then + // reposition it if necessary (reposition may be necessary if drop down's height changed). + this.closeDropDown(true); + this.openDropDown(); + this.domNode.setAttribute("aria-expanded", "true"); + }, - getChildren: function(){ - // summary: - // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode. - // Does not return nested widgets, nor widgets that are part of this widget's template. - return this.containerNode ? dijit.findWidgets(this.containerNode) : []; // dijit._Widget[] - }, + loadDropDown: function(/*Function*/ /*===== callback =====*/){ + // Overrides _HasDropDown.loadDropDown(). + // This is called when user has pressed button icon or pressed the down arrow key + // to open the drop down. - connect: function( - /*Object|null*/ obj, - /*String|Function*/ event, - /*String|Function*/ method){ - // summary: - // Connects specified obj/event to specified method of this object - // and registers for disconnect() on widget destroy. - // description: - // Provide widget-specific analog to dojo.connect, except with the - // implicit use of this widget as the target object. - // Events connected with `this.connect` are disconnected upon - // destruction. - // returns: - // A handle that can be passed to `disconnect` in order to disconnect before - // the widget is destroyed. - // example: - // | var btn = new dijit.form.Button(); - // | // when foo.bar() is called, call the listener we're going to - // | // provide in the scope of btn - // | btn.connect(foo, "bar", function(){ - // | console.debug(this.toString()); - // | }); - // tags: - // protected + this._startSearchAll(); + }, - var handles = [dojo._connect(obj, event, this, method)]; - this._connects.push(handles); - return handles; // _Widget.Handle - }, + isLoaded: function(){ + // signal to _HasDropDown that it needs to call loadDropDown() to load the + // drop down asynchronously before displaying it + return false; + }, - disconnect: function(/* _Widget.Handle */ handles){ - // summary: - // Disconnects handle created by `connect`. - // Also removes handle from this widget's list of connects. - // tags: - // protected - for(var i=0; i<this._connects.length; i++){ - if(this._connects[i] == handles){ - dojo.forEach(handles, dojo.disconnect); - this._connects.splice(i, 1); - return; + closeDropDown: function(){ + // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open). + // This method is the callback when the user types ESC or clicking + // the button icon while the drop down is open. It's also called by other code. + this._abortQuery(); + if(this._opened){ + this.inherited(arguments); + this.domNode.setAttribute("aria-expanded", "false"); + this.focusNode.removeAttribute("aria-activedescendant"); } - } - }, + }, - subscribe: function( - /*String*/ topic, - /*String|Function*/ method){ - // summary: - // Subscribes to the specified topic and calls the specified method - // of this object and registers for unsubscribe() on widget destroy. - // description: - // Provide widget-specific analog to dojo.subscribe, except with the - // implicit use of this widget as the target object. - // example: - // | var btn = new dijit.form.Button(); - // | // when /my/topic is published, this button changes its label to - // | // be the parameter of the topic. - // | btn.subscribe("/my/topic", function(v){ - // | this.set("label", v); - // | }); - var handle = dojo.subscribe(topic, this, method); + _setBlurValue: function(){ + // if the user clicks away from the textbox OR tabs away, set the + // value to the textbox value + // #4617: + // if value is now more choices or previous choices, revert + // the value + var newvalue = this.get('displayedValue'); + var pw = this.dropDown; + if(pw && ( + newvalue == pw._messages["previousMessage"] || + newvalue == pw._messages["nextMessage"] + ) + ){ + this._setValueAttr(this._lastValueReported, true); + }else if(typeof this.item == "undefined"){ + // Update 'value' (ex: KY) according to currently displayed text + this.item = null; + this.set('displayedValue', newvalue); + }else{ + if(this.value != this._lastValueReported){ + this._handleOnChange(this.value, true); + } + this._refreshState(); + } + }, - // return handles for Any widget that may need them - this._subscribes.push(handle); - return handle; - }, + _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ + // summary: + // Set the displayed valued in the input box, and the hidden value + // that gets submitted, based on a dojo.data store item. + // description: + // Users shouldn't call this function; they should be calling + // set('item', value) + // tags: + // private + var value = ''; + if(item){ + if(!displayedValue){ + displayedValue = this.store._oldAPI ? // remove getValue() for 2.0 (old dojo.data API) + this.store.getValue(item, this.searchAttr) : item[this.searchAttr]; + } + value = this._getValueField() != this.searchAttr ? this.store.getIdentity(item) : displayedValue; + } + this.set('value', value, priorityChange, displayedValue, item); + }, - unsubscribe: function(/*Object*/ handle){ - // summary: - // Unsubscribes handle created by this.subscribe. - // Also removes handle from this widget's list of subscriptions - for(var i=0; i<this._subscribes.length; i++){ - if(this._subscribes[i] == handle){ - dojo.unsubscribe(handle); - this._subscribes.splice(i, 1); + _announceOption: function(/*Node*/ node){ + // summary: + // a11y code that puts the highlighted option in the textbox. + // This way screen readers will know what is happening in the + // menu. + + if(!node){ return; } + // pull the text value from the item attached to the DOM node + var newValue; + if(node == this.dropDown.nextButton || + node == this.dropDown.previousButton){ + newValue = node.innerHTML; + this.item = undefined; + this.value = ''; + }else{ + newValue = (this.store._oldAPI ? // remove getValue() for 2.0 (old dojo.data API) + this.store.getValue(node.item, this.searchAttr) : node.item[this.searchAttr]).toString(); + this.set('item', node.item, false, newValue); + } + // get the text that the user manually entered (cut off autocompleted text) + this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length); + // set up ARIA activedescendant + this.focusNode.setAttribute("aria-activedescendant", domAttr.get(node, "id")); + // autocomplete the rest of the option to announce change + this._autoCompleteText(newValue); + }, + + _selectOption: function(/*DomNode*/ target){ + // summary: + // Menu callback function, called when an item in the menu is selected. + this.closeDropDown(); + if(target){ + this._announceOption(target); + } + this._setCaretPos(this.focusNode, this.focusNode.value.length); + this._handleOnChange(this.value, true); + }, + + _startSearchAll: function(){ + this._startSearch(''); + }, + + _startSearchFromInput: function(){ + this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1")); + }, + + _getQueryString: function(/*String*/ text){ + return string.substitute(this.queryExpr, [text]); + }, + + _startSearch: function(/*String*/ key){ + // summary: + // Starts a search for elements matching key (key=="" means to return all items), + // and calls _openResultList() when the search completes, to display the results. + if(!this.dropDown){ + var popupId = this.id + "_popup", + dropDownConstructor = lang.isString(this.dropDownClass) ? + lang.getObject(this.dropDownClass, false) : this.dropDownClass; + this.dropDown = new dropDownConstructor({ + onChange: lang.hitch(this, this._selectOption), + id: popupId, + dir: this.dir, + textDir: this.textDir + }); + this.focusNode.removeAttribute("aria-activedescendant"); + this.textbox.setAttribute("aria-owns",popupId); // associate popup with textbox + } + this._lastInput = key; // Store exactly what was entered by the user. + + // Setup parameters to be passed to store.query(). + // Create a new query to prevent accidentally querying for a hidden + // value from FilteringSelect's keyField + var query = lang.clone(this.query); // #5970 + var options = { + start: 0, + count: this.pageSize, + queryOptions: { // remove for 2.0 + ignoreCase: this.ignoreCase, + deep: true + } + }; + lang.mixin(options, this.fetchProperties); + + // Generate query + var qs = this._getQueryString(key), q; + if(this.store._oldAPI){ + // remove this branch for 2.0 + q = qs; + }else{ + // Query on searchAttr is a regex for benefit of dojo.store.Memory, + // but with a toString() method to help dojo.store.JsonRest. + // Search string like "Co*" converted to regex like /^Co.*$/i. + q = filter.patternToRegExp(qs, this.ignoreCase); + q.toString = function(){ return qs; }; + } + this._lastQuery = query[this.searchAttr] = q; + + // Function to run the query, wait for the results, and then call _openResultList() + var _this = this, + startQuery = function(){ + var resPromise = _this._fetchHandle = _this.store.query(query, options); + Deferred.when(resPromise, function(res){ + _this._fetchHandle = null; + res.total = resPromise.total; + _this._openResultList(res, query, options); + }, function(err){ + _this._fetchHandle = null; + if(!_this._cancelingQuery){ // don't treat canceled query as an error + console.error(_this.declaredClass + ' ' + err.toString()); + _this.closeDropDown(); + } + }); + }; + + // #5970: set _lastQuery, *then* start the timeout + // otherwise, if the user types and the last query returns before the timeout, + // _lastQuery won't be set and their input gets rewritten + + this.searchTimer = setTimeout(lang.hitch(this, function(query, _this){ + this.searchTimer = null; + + startQuery(); + + // Setup method to handle clicking next/previous buttons to page through results + this._nextSearch = this.dropDown.onPage = function(direction){ + options.start += options.count * direction; + // tell callback the direction of the paging so the screen + // reader knows which menu option to shout + options.direction = direction; + startQuery(); + _this.focus(); + }; + }, query, this), this.searchDelay); + }, + + _getValueField: function(){ + // summary: + // Helper for postMixInProperties() to set this.value based on data inlined into the markup. + // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value. + return this.searchAttr; + }, + + //////////// INITIALIZATION METHODS /////////////////////////////////////// + + constructor: function(){ + this.query={}; + this.fetchProperties={}; + }, + + postMixInProperties: function(){ + if(!this.store){ + var srcNodeRef = this.srcNodeRef; + var list = this.list; + if(list){ + this.store = registry.byId(list); + }else{ + // if user didn't specify store, then assume there are option tags + this.store = new DataList({}, srcNodeRef); + } + + // if there is no value set and there is an option list, set + // the value to the first value to be consistent with native Select + // Firefox and Safari set value + // IE6 and Opera set selectedIndex, which is automatically set + // by the selected attribute of an option tag + // IE6 does not set value, Opera sets value = selectedIndex + if(!("value" in this.params)){ + var item = (this.item = this.store.fetchSelectedItem()); + if(item){ + var valueField = this._getValueField(); + // remove getValue() for 2.0 (old dojo.data API) + this.value = this.store._oldAPI ? this.store.getValue(item, valueField) : item[valueField]; + } + } + } + + this.inherited(arguments); + }, + + postCreate: function(){ + // summary: + // Subclasses must call this method from their postCreate() methods + // tags: + // protected + + // find any associated label element and add to ComboBox node. + var label=query('label[for="'+this.id+'"]'); + if(label.length){ + label[0].id = (this.id+"_label"); + this.domNode.setAttribute("aria-labelledby", label[0].id); + + } + this.inherited(arguments); + }, + + _getMenuLabelFromItem: function(/*Item*/ item){ + var label = this.labelFunc(item, this.store), + labelType = this.labelType; + // If labelType is not "text" we don't want to screw any markup ot whatever. + if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){ + label = this.doHighlight(label, this._escapeHtml(this._lastInput)); + labelType = "html"; + } + return {html: labelType == "html", label: label}; + }, + + doHighlight: function(/*String*/ label, /*String*/ find){ + // summary: + // Highlights the string entered by the user in the menu. By default this + // highlights the first occurrence found. Override this method + // to implement your custom highlighting. + // tags: + // protected + + var + // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true + modifiers = (this.ignoreCase ? "i" : "") + (this.highlightMatch == "all" ? "g" : ""), + i = this.queryExpr.indexOf("${0}"); + find = regexp.escapeString(find); // escape regexp special chars + return this._escapeHtml(label).replace( + // prepend ^ when this.queryExpr == "${0}*" and append $ when this.queryExpr == "*${0}" + new RegExp((i == 0 ? "^" : "") + "("+ find +")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers), + '<span class="dijitComboBoxHighlightMatch">$1</span>' + ); // returns String, (almost) valid HTML (entities encoded) + }, + + _escapeHtml: function(/*String*/ str){ + // TODO Should become dojo.html.entities(), when exists use instead + // summary: + // Adds escape sequences for special characters in XML: &<>"' + str = String(str).replace(/&/gm, "&").replace(/</gm, "<") + .replace(/>/gm, ">").replace(/"/gm, """); //balance" + return str; // string + }, + + reset: function(){ + // Overrides the _FormWidget.reset(). + // Additionally reset the .item (to clean up). + this.item = null; + this.inherited(arguments); + }, + + labelFunc: function(/*item*/ item, /*dojo.store.api.Store*/ store){ + // summary: + // Computes the label to display based on the dojo.data store item. + // returns: + // The label that the ComboBox should display + // tags: + // private + + // Use toString() because XMLStore returns an XMLItem whereas this + // method is expected to return a String (#9354). + // Remove getValue() for 2.0 (old dojo.data API) + return (store._oldAPI ? store.getValue(item, this.labelAttr || this.searchAttr) : + item[this.labelAttr || this.searchAttr]).toString(); // String + }, + + _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue, /*item?*/ item){ + // summary: + // Hook so set('value', value) works. + // description: + // Sets the value of the select. + this._set("item", item||null); // value not looked up in store + if(!value){ value = ''; } // null translates to blank + this.inherited(arguments); + }, + _setTextDirAttr: function(/*String*/ textDir){ + // summary: + // Setter for textDir, needed for the dropDown's textDir update. + // description: + // Users shouldn't call this function; they should be calling + // set('textDir', value) + // tags: + // private + this.inherited(arguments); + // update the drop down also (_ComboBoxMenuMixin) + if(this.dropDown){ + this.dropDown._set("textDir", textDir); + } } - }, + }); +}); - isLeftToRight: function(){ +}, +'url:dijit/templates/ColorPalette.html':"<div class=\"dijitInline dijitColorPalette\">\n\t<table dojoAttachPoint=\"paletteTableNode\" class=\"dijitPaletteTable\" cellSpacing=\"0\" cellPadding=\"0\" role=\"grid\">\n\t\t<tbody data-dojo-attach-point=\"gridNode\"></tbody>\n\t</table>\n</div>\n", +'url:dijit/layout/templates/_ScrollingTabControllerButton.html':"<div data-dojo-attach-event=\"onclick:_onClick\">\n\t<div role=\"presentation\" class=\"dijitTabInnerDiv\" data-dojo-attach-point=\"innerDiv,focusNode\">\n\t\t<div role=\"presentation\" class=\"dijitTabContent dijitButtonContents\" data-dojo-attach-point=\"tabContent\">\n\t\t\t<img role=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" data-dojo-attach-point=\"iconNode\"/>\n\t\t\t<span data-dojo-attach-point=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>", +'dijit/form/MappedTextBox':function(){ +define("dijit/form/MappedTextBox", [ + "dojo/_base/declare", // declare + "dojo/dom-construct", // domConstruct.place + "./ValidationTextBox" +], function(declare, domConstruct, ValidationTextBox){ + +/*===== + var ValidationTextBox = dijit.form.ValidationTextBox; +=====*/ + + // module: + // dijit/form/MappedTextBox + // summary: + // A dijit.form.ValidationTextBox subclass which provides a base class for widgets that have + // a visible formatted display value, and a serializable + // value in a hidden input field which is actually sent to the server. + + return declare("dijit.form.MappedTextBox", ValidationTextBox, { // summary: - // Return this widget's explicit or implicit orientation (true for LTR, false for RTL) + // A dijit.form.ValidationTextBox subclass which provides a base class for widgets that have + // a visible formatted display value, and a serializable + // value in a hidden input field which is actually sent to the server. + // description: + // The visible display may + // be locale-dependent and interactive. The value sent to the server is stored in a hidden + // input field which uses the `name` attribute declared by the original widget. That value sent + // to the server is defined by the dijit.form.MappedTextBox.serialize method and is typically + // locale-neutral. // tags: // protected - return this.dir ? (this.dir == "ltr") : dojo._isBodyLtr(); //Boolean - }, - placeAt: function(/* String|DomNode|_Widget */reference, /* String?|Int? */position){ - // summary: - // Place this widget's domNode reference somewhere in the DOM based - // on standard dojo.place conventions, or passing a Widget reference that - // contains and addChild member. - // - // description: - // A convenience function provided in all _Widgets, providing a simple - // shorthand mechanism to put an existing (or newly created) Widget - // somewhere in the dom, and allow chaining. - // - // reference: - // The String id of a domNode, a domNode reference, or a reference to a Widget posessing - // an addChild method. - // - // position: - // If passed a string or domNode reference, the position argument - // accepts a string just as dojo.place does, one of: "first", "last", - // "before", or "after". - // - // If passed a _Widget reference, and that widget reference has an ".addChild" method, - // it will be called passing this widget instance into that method, supplying the optional - // position index passed. - // - // returns: - // dijit._Widget - // Provides a useful return of the newly created dijit._Widget instance so you - // can "chain" this function by instantiating, placing, then saving the return value - // to a variable. - // - // example: - // | // create a Button with no srcNodeRef, and place it in the body: - // | var button = new dijit.form.Button({ label:"click" }).placeAt(dojo.body()); - // | // now, 'button' is still the widget reference to the newly created button - // | dojo.connect(button, "onClick", function(e){ console.log('click'); }); - // - // example: - // | // create a button out of a node with id="src" and append it to id="wrapper": - // | var button = new dijit.form.Button({},"src").placeAt("wrapper"); - // - // example: - // | // place a new button as the first element of some div - // | var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first"); - // - // example: - // | // create a contentpane and add it to a TabContainer - // | var tc = dijit.byId("myTabs"); - // | new dijit.layout.ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc) + postMixInProperties: function(){ + this.inherited(arguments); - if(reference.declaredClass && reference.addChild){ - reference.addChild(this, position); - }else{ - dojo.place(this.domNode, reference, position); + // we want the name attribute to go to the hidden <input>, not the displayed <input>, + // so override _FormWidget.postMixInProperties() setting of nameAttrSetting + this.nameAttrSetting = ""; + }, + + // Override default behavior to assign name to focusNode + _setNameAttr: null, + + serialize: function(val /*=====, options =====*/){ + // summary: + // Overridable function used to convert the get('value') result to a canonical + // (non-localized) string. For example, will print dates in ISO format, and + // numbers the same way as they are represented in javascript. + // val: anything + // options: Object? + // tags: + // protected extension + return val.toString ? val.toString() : ""; // String + }, + + toString: function(){ + // summary: + // Returns widget as a printable string using the widget's value + // tags: + // protected + var val = this.filter(this.get('value')); // call filter in case value is nonstring and filter has been customized + return val != null ? (typeof val == "string" ? val : this.serialize(val, this.constraints)) : ""; // String + }, + + validate: function(){ + // Overrides `dijit.form.TextBox.validate` + this.valueNode.value = this.toString(); + return this.inherited(arguments); + }, + + buildRendering: function(){ + // Overrides `dijit._TemplatedMixin.buildRendering` + + this.inherited(arguments); + + // Create a hidden <input> node with the serialized value used for submit + // (as opposed to the displayed value). + // Passing in name as markup rather than calling domConstruct.create() with an attrs argument + // to make query(input[name=...]) work on IE. (see #8660) + this.valueNode = domConstruct.place("<input type='hidden'" + (this.name ? " name='" + this.name.replace(/'/g, """) + "'" : "") + "/>", this.textbox, "after"); + }, + + reset: function(){ + // Overrides `dijit.form.ValidationTextBox.reset` to + // reset the hidden textbox value to '' + this.valueNode.value = ''; + this.inherited(arguments); } - return this; - } + }); }); -})(); +}, +'dijit/form/ComboBoxMixin':function(){ +require({cache:{ +'url:dijit/form/templates/DropDownBox.html':"<div class=\"dijit dijitReset dijitInline dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdata-dojo-attach-point=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdata-dojo-attach-point=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n"}}); +define("dijit/form/ComboBoxMixin", [ + "dojo/_base/declare", // declare + "dojo/_base/Deferred", + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/lang", // lang.mixin + "dojo/store/util/QueryResults", // dojo.store.util.QueryResults + "./_AutoCompleterMixin", + "./_ComboBoxMenu", + "../_HasDropDown", + "dojo/text!./templates/DropDownBox.html" +], function(declare, Deferred, kernel, lang, QueryResults, _AutoCompleterMixin, _ComboBoxMenu, _HasDropDown, template){ -} +/*===== + var _AutoCompleterMixin = dijit.form._AutoCompleterMixin; + var _ComboBoxMenu = dijit.form._ComboBoxMenu; + var _HasDropDown = dijit._HasDropDown; +=====*/ -if(!dojo._hasResource["dijit._Widget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._Widget"] = true; -dojo.provide("dijit._Widget"); + // module: + // dijit/form/ComboBoxMixin + // summary: + // Provides main functionality of ComboBox widget + + return declare("dijit.form.ComboBoxMixin", [_HasDropDown, _AutoCompleterMixin], { + // summary: + // Provides main functionality of ComboBox widget + + // dropDownClass: [protected extension] Function String + // Dropdown widget class used to select a date/time. + // Subclasses should specify this. + dropDownClass: _ComboBoxMenu, + + // hasDownArrow: Boolean + // Set this textbox to have a down arrow button, to display the drop down list. + // Defaults to true. + hasDownArrow: true, + templateString: template, + baseClass: "dijitTextBox dijitComboBox", + /*===== + // store: [const] dojo.store.api.Store || dojo.data.api.Read + // Reference to data provider object used by this ComboBox. + // + // Should be dojo.store.api.Store, but dojo.data.api.Read supported + // for backwards compatibility. + store: null, + =====*/ + // Set classes like dijitDownArrowButtonHover depending on + // mouse action over button node + cssStateNodes: { + "_buttonNode": "dijitDownArrowButton" + }, -////////////////// DEFERRED CONNECTS /////////////////// + _setHasDownArrowAttr: function(/*Boolean*/ val){ + this._set("hasDownArrow", val); + this._buttonNode.style.display = val ? "" : "none"; + }, -// This code is to assist deferring dojo.connect() calls in widgets (connecting to events on the widgets' -// DOM nodes) until someone actually needs to monitor that event. -dojo.connect(dojo, "_connect", - function(/*dijit._Widget*/ widget, /*String*/ event){ - if(widget && dojo.isFunction(widget._onConnect)){ - widget._onConnect(event); + _showResultList: function(){ + // hide the tooltip + this.displayMessage(""); + this.inherited(arguments); + }, + + _setStoreAttr: function(store){ + // For backwards-compatibility, accept dojo.data store in addition to dojo.store.store. Remove in 2.0. + if(!store.get){ + lang.mixin(store, { + _oldAPI: true, + get: function(id){ + // summary: + // Retrieves an object by it's identity. This will trigger a fetchItemByIdentity. + // Like dojo.store.DataStore.get() except returns native item. + var deferred = new Deferred(); + this.fetchItemByIdentity({ + identity: id, + onItem: function(object){ + deferred.resolve(object); + }, + onError: function(error){ + deferred.reject(error); + } + }); + return deferred.promise; + }, + query: function(query, options){ + // summary: + // Queries the store for objects. Like dojo.store.DataStore.query() + // except returned Deferred contains array of native items. + var deferred = new Deferred(function(){ fetchHandle.abort && fetchHandle.abort(); }); + var fetchHandle = this.fetch(lang.mixin({ + query: query, + onBegin: function(count){ + deferred.total = count; + }, + onComplete: function(results){ + deferred.resolve(results); + }, + onError: function(error){ + deferred.reject(error); + } + }, options)); + return QueryResults(deferred); + } + }); + } + this._set("store", store); + }, + + postMixInProperties: function(){ + // Since _setValueAttr() depends on this.store, _setStoreAttr() needs to execute first. + // Unfortunately, without special code, it ends up executing second. + if(this.params.store){ + this._setStoreAttr(this.params.store); + } + + this.inherited(arguments); + + // User may try to access this.store.getValue() etc. in a custom labelFunc() function. + // It's not available with the new data store for handling inline <option> tags, so add it. + if(!this.params.store){ + var clazz = this.declaredClass; + lang.mixin(this.store, { + getValue: function(item, attr){ + kernel.deprecated(clazz + ".store.getValue(item, attr) is deprecated for builtin store. Use item.attr directly", "", "2.0"); + return item[attr]; + }, + getLabel: function(item){ + kernel.deprecated(clazz + ".store.getLabel(item) is deprecated for builtin store. Use item.label directly", "", "2.0"); + return item.name; + }, + fetch: function(args){ + kernel.deprecated(clazz + ".store.fetch() is deprecated for builtin store.", "Use store.query()", "2.0"); + var shim = ["dojo/data/ObjectStore"]; // indirection so it doesn't get rolled into a build + require(shim, lang.hitch(this, function(ObjectStore){ + new ObjectStore({objectStore: this}).fetch(args); + })); + } + }); + } } }); +}); -dijit._connectOnUseEventHandler = function(/*Event*/ event){}; - -////////////////// ONDIJITCLICK SUPPORT /////////////////// - -// Keep track of where the last keydown event was, to help avoid generating -// spurious ondijitclick events when: -// 1. focus is on a <button> or <a> -// 2. user presses then releases the ENTER key -// 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler -// 4. onkeyup event fires, causing the ondijitclick handler to fire -dijit._lastKeyDownNode = null; -if(dojo.isIE){ - (function(){ - var keydownCallback = function(evt){ - dijit._lastKeyDownNode = evt.srcElement; - }; - dojo.doc.attachEvent('onkeydown', keydownCallback); - dojo.addOnWindowUnload(function(){ - dojo.doc.detachEvent('onkeydown', keydownCallback); - }); - })(); -}else{ - dojo.doc.addEventListener('keydown', function(evt){ - dijit._lastKeyDownNode = evt.target; - }, true); -} - -(function(){ +}, +'dijit/form/_TextBoxMixin':function(){ +define("dijit/form/_TextBoxMixin", [ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dom", // dom.byId + "dojo/_base/event", // event.stop + "dojo/keys", // keys.ALT keys.CAPS_LOCK keys.CTRL keys.META keys.SHIFT + "dojo/_base/lang", // lang.mixin + ".." // for exporting dijit._setSelectionRange, dijit.selectInputText +], function(array, declare, dom, event, keys, lang, dijit){ + +// module: +// dijit/form/_TextBoxMixin +// summary: +// A mixin for textbox form input widgets -dojo.declare("dijit._Widget", dijit._WidgetBase, { +var _TextBoxMixin = declare("dijit.form._TextBoxMixin", null, { // summary: - // Base class for all Dijit widgets. - // - // Extends _WidgetBase, adding support for: - // - deferred connections - // A call like dojo.connect(myWidget, "onMouseMove", func) - // will essentially do a dojo.connect(myWidget.domNode, "onMouseMove", func) - // - ondijitclick - // Support new dojoAttachEvent="ondijitclick: ..." that is triggered by a mouse click or a SPACE/ENTER keypress - // - focus related functions - // In particular, the onFocus()/onBlur() callbacks. Driven internally by - // dijit/_base/focus.js. - // - deprecated methods - // - onShow(), onHide(), onClose() - // - // Also, by loading code in dijit/_base, turns on: - // - browser sniffing (putting browser id like .dj_ie on <html> node) - // - high contrast mode sniffing (add .dijit_a11y class to <body> if machine is in high contrast mode) - + // A mixin for textbox form input widgets - ////////////////// DEFERRED CONNECTS /////////////////// + // trim: Boolean + // Removes leading and trailing whitespace if true. Default is false. + trim: false, - // _deferredConnects: [protected] Object - // attributeMap addendum for event handlers that should be connected only on first use - _deferredConnects: { - onClick: "", - onDblClick: "", - onKeyDown: "", - onKeyPress: "", - onKeyUp: "", - onMouseMove: "", - onMouseDown: "", - onMouseOut: "", - onMouseOver: "", - onMouseLeave: "", - onMouseEnter: "", - onMouseUp: "" - }, - - onClick: dijit._connectOnUseEventHandler, - /*===== - onClick: function(event){ - // summary: - // Connect to this function to receive notifications of mouse click events. - // event: - // mouse Event - // tags: - // callback - }, - =====*/ - onDblClick: dijit._connectOnUseEventHandler, - /*===== - onDblClick: function(event){ - // summary: - // Connect to this function to receive notifications of mouse double click events. - // event: - // mouse Event - // tags: - // callback - }, - =====*/ - onKeyDown: dijit._connectOnUseEventHandler, - /*===== - onKeyDown: function(event){ - // summary: - // Connect to this function to receive notifications of keys being pressed down. - // event: - // key Event - // tags: - // callback - }, - =====*/ - onKeyPress: dijit._connectOnUseEventHandler, - /*===== - onKeyPress: function(event){ + // uppercase: Boolean + // Converts all characters to uppercase if true. Default is false. + uppercase: false, + + // lowercase: Boolean + // Converts all characters to lowercase if true. Default is false. + lowercase: false, + + // propercase: Boolean + // Converts the first character of each word to uppercase if true. + propercase: false, + + // maxLength: String + // HTML INPUT tag maxLength declaration. + maxLength: "", + + // selectOnClick: [const] Boolean + // If true, all text will be selected when focused with mouse + selectOnClick: false, + + // placeHolder: String + // Defines a hint to help users fill out the input field (as defined in HTML 5). + // This should only contain plain text (no html markup). + placeHolder: "", + + _getValueAttr: function(){ // summary: - // Connect to this function to receive notifications of printable keys being typed. - // event: - // key Event - // tags: - // callback + // Hook so get('value') works as we like. + // description: + // For `dijit.form.TextBox` this basically returns the value of the <input>. + // + // For `dijit.form.MappedTextBox` subclasses, which have both + // a "displayed value" and a separate "submit value", + // This treats the "displayed value" as the master value, computing the + // submit value from it via this.parse(). + return this.parse(this.get('displayedValue'), this.constraints); }, - =====*/ - onKeyUp: dijit._connectOnUseEventHandler, - /*===== - onKeyUp: function(event){ + + _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){ // summary: - // Connect to this function to receive notifications of keys being released. - // event: - // key Event - // tags: - // callback + // Hook so set('value', ...) works. + // + // description: + // Sets the value of the widget to "value" which can be of + // any type as determined by the widget. + // + // value: + // The visual element value is also set to a corresponding, + // but not necessarily the same, value. + // + // formattedValue: + // If specified, used to set the visual element value, + // otherwise a computed visual value is used. + // + // priorityChange: + // If true, an onChange event is fired immediately instead of + // waiting for the next blur event. + + var filteredValue; + if(value !== undefined){ + // TODO: this is calling filter() on both the display value and the actual value. + // I added a comment to the filter() definition about this, but it should be changed. + filteredValue = this.filter(value); + if(typeof formattedValue != "string"){ + if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){ + formattedValue = this.filter(this.format(filteredValue, this.constraints)); + }else{ formattedValue = ''; } + } + } + if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){ + this.textbox.value = formattedValue; + this._set("displayedValue", this.get("displayedValue")); + } + + if(this.textDir == "auto"){ + this.applyTextDir(this.focusNode, formattedValue); + } + + this.inherited(arguments, [filteredValue, priorityChange]); }, - =====*/ - onMouseDown: dijit._connectOnUseEventHandler, - /*===== - onMouseDown: function(event){ + + // displayedValue: String + // For subclasses like ComboBox where the displayed value + // (ex: Kentucky) and the serialized value (ex: KY) are different, + // this represents the displayed value. + // + // Setting 'displayedValue' through set('displayedValue', ...) + // updates 'value', and vice-versa. Otherwise 'value' is updated + // from 'displayedValue' periodically, like onBlur etc. + // + // TODO: move declaration to MappedTextBox? + // Problem is that ComboBox references displayedValue, + // for benefit of FilteringSelect. + displayedValue: "", + + _getDisplayedValueAttr: function(){ // summary: - // Connect to this function to receive notifications of when the mouse button is pressed down. - // event: - // mouse Event - // tags: - // callback + // Hook so get('displayedValue') works. + // description: + // Returns the displayed value (what the user sees on the screen), + // after filtering (ie, trimming spaces etc.). + // + // For some subclasses of TextBox (like ComboBox), the displayed value + // is different from the serialized value that's actually + // sent to the server (see dijit.form.ValidationTextBox.serialize) + + // TODO: maybe we should update this.displayedValue on every keystroke so that we don't need + // this method + // TODO: this isn't really the displayed value when the user is typing + return this.filter(this.textbox.value); }, - =====*/ - onMouseMove: dijit._connectOnUseEventHandler, - /*===== - onMouseMove: function(event){ + + _setDisplayedValueAttr: function(/*String*/ value){ // summary: - // Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget. - // event: - // mouse Event - // tags: - // callback + // Hook so set('displayedValue', ...) works. + // description: + // Sets the value of the visual element to the string "value". + // The widget value is also set to a corresponding, + // but not necessarily the same, value. + + if(value === null || value === undefined){ value = '' } + else if(typeof value != "string"){ value = String(value) } + + this.textbox.value = value; + + // sets the serialized value to something corresponding to specified displayedValue + // (if possible), and also updates the textbox.value, for example converting "123" + // to "123.00" + this._setValueAttr(this.get('value'), undefined); + + this._set("displayedValue", this.get('displayedValue')); + + // textDir support + if(this.textDir == "auto"){ + this.applyTextDir(this.focusNode, value); + } }, - =====*/ - onMouseOut: dijit._connectOnUseEventHandler, - /*===== - onMouseOut: function(event){ + + format: function(value /*=====, constraints =====*/){ // summary: - // Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget. - // event: - // mouse Event + // Replaceable function to convert a value to a properly formatted string. + // value: String + // constraints: Object // tags: - // callback + // protected extension + return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value)); }, - =====*/ - onMouseOver: dijit._connectOnUseEventHandler, - /*===== - onMouseOver: function(event){ + + parse: function(value /*=====, constraints =====*/){ // summary: - // Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget. - // event: - // mouse Event + // Replaceable function to convert a formatted string to a value + // value: String + // constraints: Object // tags: - // callback + // protected extension + + return value; // String }, - =====*/ - onMouseLeave: dijit._connectOnUseEventHandler, - /*===== - onMouseLeave: function(event){ + + _refreshState: function(){ // summary: - // Connect to this function to receive notifications of when the mouse moves off of this widget. - // event: - // mouse Event + // After the user types some characters, etc., this method is + // called to check the field for validity etc. The base method + // in `dijit.form.TextBox` does nothing, but subclasses override. // tags: - // callback + // protected }, - =====*/ - onMouseEnter: dijit._connectOnUseEventHandler, + /*===== - onMouseEnter: function(event){ + onInput: function(event){ // summary: - // Connect to this function to receive notifications of when the mouse moves onto this widget. + // Connect to this function to receive notifications of various user data-input events. + // Return false to cancel the event and prevent it from being processed. // event: - // mouse Event + // keydown | keypress | cut | paste | input // tags: // callback }, =====*/ - onMouseUp: dijit._connectOnUseEventHandler, - /*===== - onMouseUp: function(event){ + onInput: function(){}, + + __skipInputEvent: false, + _onInput: function(){ // summary: - // Connect to this function to receive notifications of when the mouse button is released. - // event: - // mouse Event - // tags: - // callback + // Called AFTER the input event has happened + // set text direction according to textDir that was defined in creation + if(this.textDir == "auto"){ + this.applyTextDir(this.focusNode, this.focusNode.value); + } + + this._refreshState(); + + // In case someone is watch()'ing for changes to displayedValue + this._set("displayedValue", this.get("displayedValue")); }, - =====*/ - create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){ - // To avoid double-connects, remove entries from _deferredConnects - // that have been setup manually by a subclass (ex, by dojoAttachEvent). - // If a subclass has redefined a callback (ex: onClick) then assume it's being - // connected to manually. - this._deferredConnects = dojo.clone(this._deferredConnects); - for(var attr in this.attributeMap){ - delete this._deferredConnects[attr]; // can't be in both attributeMap and _deferredConnects - } - for(attr in this._deferredConnects){ - if(this[attr] !== dijit._connectOnUseEventHandler){ - delete this._deferredConnects[attr]; // redefined, probably dojoAttachEvent exists - } - } + postCreate: function(){ + // setting the value here is needed since value="" in the template causes "undefined" + // and setting in the DOM (instead of the JS object) helps with form reset actions + this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values should be the same this.inherited(arguments); - if(this.domNode){ - // If the developer has specified a handler as a widget parameter - // (ex: new Button({onClick: ...}) - // then naturally need to connect from DOM node to that handler immediately, - for(attr in this.params){ - this._onConnect(attr); + // normalize input events to reduce spurious event processing + // onkeydown: do not forward modifier keys + // set charOrCode to numeric keycode + // onkeypress: do not forward numeric charOrCode keys (already sent through onkeydown) + // onpaste & oncut: set charOrCode to 229 (IME) + // oninput: if primary event not already processed, set charOrCode to 229 (IME), else do not forward + var handleEvent = function(e){ + var charCode = e.charOrCode || e.keyCode || 229; + if(e.type == "keydown"){ + switch(charCode){ // ignore "state" keys + case keys.SHIFT: + case keys.ALT: + case keys.CTRL: + case keys.META: + case keys.CAPS_LOCK: + return; + default: + if(charCode >= 65 && charCode <= 90){ return; } // keydown for A-Z can be processed with keypress + } } - } + if(e.type == "keypress" && typeof charCode != "string"){ return; } + if(e.type == "input"){ + if(this.__skipInputEvent){ // duplicate event + this.__skipInputEvent = false; + return; + } + }else{ + this.__skipInputEvent = true; + } + // create fake event to set charOrCode and to know if preventDefault() was called + var faux = lang.mixin({}, e, { + charOrCode: charCode, + wasConsumed: false, + preventDefault: function(){ + faux.wasConsumed = true; + e.preventDefault(); + }, + stopPropagation: function(){ e.stopPropagation(); } + }); + // give web page author a chance to consume the event + if(this.onInput(faux) === false){ + event.stop(faux); // return false means stop + } + if(faux.wasConsumed){ return; } // if preventDefault was called + setTimeout(lang.hitch(this, "_onInput", faux), 0); // widget notification after key has posted + }; + array.forEach([ "onkeydown", "onkeypress", "onpaste", "oncut", "oninput", "oncompositionend" ], function(event){ + this.connect(this.textbox, event, handleEvent); + }, this); }, - _onConnect: function(/*String*/ event){ + _blankValue: '', // if the textbox is blank, what value should be reported + filter: function(val){ // summary: - // Called when someone connects to one of my handlers. - // "Turn on" that handler if it isn't active yet. + // Auto-corrections (such as trimming) that are applied to textbox + // value on blur or form submit. + // description: + // For MappedTextBox subclasses, this is called twice + // - once with the display value + // - once the value as set/returned by set('value', ...) + // and get('value'), ex: a Number for NumberTextBox. + // + // In the latter case it does corrections like converting null to NaN. In + // the former case the NumberTextBox.filter() method calls this.inherited() + // to execute standard trimming code in TextBox.filter(). + // + // TODO: break this into two methods in 2.0 // - // This is also called for every single initialization parameter - // so need to do nothing for parameters like "id". // tags: - // private - if(event in this._deferredConnects){ - var mapNode = this[this._deferredConnects[event] || 'domNode']; - this.connect(mapNode, event.toLowerCase(), event); - delete this._deferredConnects[event]; + // protected extension + if(val === null){ return this._blankValue; } + if(typeof val != "string"){ return val; } + if(this.trim){ + val = lang.trim(val); + } + if(this.uppercase){ + val = val.toUpperCase(); + } + if(this.lowercase){ + val = val.toLowerCase(); + } + if(this.propercase){ + val = val.replace(/[^\s]+/g, function(word){ + return word.substring(0,1).toUpperCase() + word.substring(1); + }); } + return val; }, - ////////////////// FOCUS RELATED /////////////////// - // _onFocus() and _onBlur() are called by the focus manager - - // focused: [readonly] Boolean - // This widget or a widget it contains has focus, or is "active" because - // it was recently clicked. - focused: false, - - isFocusable: function(){ - // summary: - // Return true if this widget can currently be focused - // and false if not - return this.focus && (dojo.style(this.domNode, "display") != "none"); + _setBlurValue: function(){ + this._setValueAttr(this.get('value'), true); }, - onFocus: function(){ - // summary: - // Called when the widget becomes "active" because - // it or a widget inside of it either has focus, or has recently - // been clicked. - // tags: - // callback - }, + _onBlur: function(e){ + if(this.disabled){ return; } + this._setBlurValue(); + this.inherited(arguments); - onBlur: function(){ - // summary: - // Called when the widget stops being "active" because - // focus moved to something outside of it, or the user - // clicked somewhere outside of it, or the widget was - // hidden. - // tags: - // callback + if(this._selectOnClickHandle){ + this.disconnect(this._selectOnClickHandle); + } }, - _onFocus: function(e){ - // summary: - // This is where widgets do processing for when they are active, - // such as changing CSS classes. See onFocus() for more details. - // tags: - // protected - this.onFocus(); + _isTextSelected: function(){ + return this.textbox.selectionStart == this.textbox.selectionEnd; }, - _onBlur: function(){ - // summary: - // This is where widgets do processing for when they stop being active, - // such as changing CSS classes. See onBlur() for more details. - // tags: - // protected - this.onBlur(); - }, + _onFocus: function(/*String*/ by){ + if(this.disabled || this.readOnly){ return; } - ////////////////// DEPRECATED METHODS /////////////////// + // Select all text on focus via click if nothing already selected. + // Since mouse-up will clear the selection need to defer selection until after mouse-up. + // Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event. + if(this.selectOnClick && by == "mouse"){ + this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){ + // Only select all text on first click; otherwise users would have no way to clear + // the selection. + this.disconnect(this._selectOnClickHandle); - setAttribute: function(/*String*/ attr, /*anything*/ value){ - // summary: - // Deprecated. Use set() instead. - // tags: - // deprecated - dojo.deprecated(this.declaredClass+"::setAttribute(attr, value) is deprecated. Use set() instead.", "", "2.0"); - this.set(attr, value); + // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up) + // and if not, then select all the text + if(this._isTextSelected()){ + _TextBoxMixin.selectInputText(this.textbox); + } + }); + } + // call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport + // (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip + this.inherited(arguments); + + this._refreshState(); }, - attr: function(/*String|Object*/name, /*Object?*/value){ + reset: function(){ + // Overrides dijit._FormWidget.reset(). + // Additionally resets the displayed textbox value to '' + this.textbox.value = ''; + this.inherited(arguments); + }, + _setTextDirAttr: function(/*String*/ textDir){ // summary: - // Set or get properties on a widget instance. - // name: - // The property to get or set. If an object is passed here and not - // a string, its keys are used as names of attributes to be set - // and the value of the object as values to set in the widget. - // value: - // Optional. If provided, attr() operates as a setter. If omitted, - // the current value of the named property is returned. + // Setter for textDir. // description: - // This method is deprecated, use get() or set() directly. + // Users shouldn't call this function; they should be calling + // set('textDir', value) + // tags: + // private - // Print deprecation warning but only once per calling function - if(dojo.config.isDebug){ - var alreadyCalledHash = arguments.callee._ach || (arguments.callee._ach = {}), - caller = (arguments.callee.caller || "unknown caller").toString(); - if(!alreadyCalledHash[caller]){ - dojo.deprecated(this.declaredClass + "::attr() is deprecated. Use get() or set() instead, called from " + - caller, "", "2.0"); - alreadyCalledHash[caller] = true; - } + // only if new textDir is different from the old one + // and on widgets creation. + if(!this._created + || this.textDir != textDir){ + this._set("textDir", textDir); + // so the change of the textDir will take place immediately. + this.applyTextDir(this.focusNode, this.focusNode.value); } + } +}); - var args = arguments.length; - if(args >= 2 || typeof name === "object"){ // setter - return this.set.apply(this, arguments); - }else{ // getter - return this.get(name); - } - }, - - ////////////////// ONDIJITCLICK SUPPORT /////////////////// - // nodesWithKeyClick: [private] String[] - // List of nodes that correctly handle click events via native browser support, - // and don't need dijit's help - nodesWithKeyClick: ["input", "button"], +_TextBoxMixin._setSelectionRange = dijit._setSelectionRange = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){ + if(element.setSelectionRange){ + element.setSelectionRange(start, stop); + } +}; - connect: function( - /*Object|null*/ obj, - /*String|Function*/ event, - /*String|Function*/ method){ - // summary: - // Connects specified obj/event to specified method of this object - // and registers for disconnect() on widget destroy. - // description: - // Provide widget-specific analog to dojo.connect, except with the - // implicit use of this widget as the target object. - // This version of connect also provides a special "ondijitclick" - // event which triggers on a click or space or enter keyup. - // Events connected with `this.connect` are disconnected upon - // destruction. - // returns: - // A handle that can be passed to `disconnect` in order to disconnect before - // the widget is destroyed. - // example: - // | var btn = new dijit.form.Button(); - // | // when foo.bar() is called, call the listener we're going to - // | // provide in the scope of btn - // | btn.connect(foo, "bar", function(){ - // | console.debug(this.toString()); - // | }); - // tags: - // protected +_TextBoxMixin.selectInputText = dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){ + // summary: + // Select text in the input element argument, from start (default 0), to stop (default end). - var d = dojo, - dc = d._connect, - handles = this.inherited(arguments, [obj, event == "ondijitclick" ? "onclick" : event, method]); - - if(event == "ondijitclick"){ - // add key based click activation for unsupported nodes. - // do all processing onkey up to prevent spurious clicks - // for details see comments at top of this file where _lastKeyDownNode is defined - if(d.indexOf(this.nodesWithKeyClick, obj.nodeName.toLowerCase()) == -1){ // is NOT input or button - var m = d.hitch(this, method); - handles.push( - dc(obj, "onkeydown", this, function(e){ - //console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode)); - if((e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) && - !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){ - // needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work - dijit._lastKeyDownNode = e.target; - - // Stop event to prevent scrolling on space key in IE. - // But don't do this for _HasDropDown because it surpresses the onkeypress - // event needed to open the drop down when the user presses the SPACE key. - if(!("openDropDown" in this && obj == this._buttonNode)){ - e.preventDefault(); - } - } - }), - dc(obj, "onkeyup", this, function(e){ - //console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", dijit._lastKeyDownNode, ", equality is ", (e.target === dijit._lastKeyDownNode)); - if( (e.keyCode == d.keys.ENTER || e.keyCode == d.keys.SPACE) && - e.target == dijit._lastKeyDownNode && // === breaks greasemonkey - !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey){ - //need reset here or have problems in FF when focus returns to trigger element after closing popup/alert - dijit._lastKeyDownNode = null; - return m(e); - } - }) - ); - } - } + // TODO: use functions in _editor/selection.js? + element = dom.byId(element); + if(isNaN(start)){ start = 0; } + if(isNaN(stop)){ stop = element.value ? element.value.length : 0; } + try{ + element.focus(); + _TextBoxMixin._setSelectionRange(element, start, stop); + }catch(e){ /* squelch random errors (esp. on IE) from unexpected focus changes or DOM nodes being hidden */ } +}; - return handles; // _Widget.Handle - }, +return _TextBoxMixin; +}); - ////////////////// MISCELLANEOUS METHODS /////////////////// +}, +'dijit/form/SimpleTextarea':function(){ +define("dijit/form/SimpleTextarea", [ + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add + "dojo/_base/sniff", // has("ie") has("opera") + "dojo/_base/window", // win.doc.selection win.doc.selection.createRange + "./TextBox" +], function(declare, domClass, has, win, TextBox){ - _onShow: function(){ - // summary: - // Internal method called when this widget is made visible. - // See `onShow` for details. - this.onShow(); - }, +/*===== + var TextBox = dijit.form.TextBox; +=====*/ - onShow: function(){ - // summary: - // Called when this widget becomes the selected pane in a - // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`, - // `dijit.layout.AccordionContainer`, etc. - // - // Also called to indicate display of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`. - // tags: - // callback +// module: +// dijit/form/SimpleTextarea +// summary: +// A simple textarea that degrades, and responds to +// minimal LayoutContainer usage, and works with dijit.form.Form. +// Doesn't automatically size according to input, like Textarea. + +return declare("dijit.form.SimpleTextarea", TextBox, { + // summary: + // A simple textarea that degrades, and responds to + // minimal LayoutContainer usage, and works with dijit.form.Form. + // Doesn't automatically size according to input, like Textarea. + // + // example: + // | <textarea data-dojo-type="dijit.form.SimpleTextarea" name="foo" value="bar" rows=30 cols=40></textarea> + // + // example: + // | new dijit.form.SimpleTextarea({ rows:20, cols:30 }, "foo"); + + baseClass: "dijitTextBox dijitTextArea", + + // rows: Number + // The number of rows of text. + rows: "3", + + // rows: Number + // The number of characters per line. + cols: "20", + + templateString: "<textarea ${!nameAttrSetting} data-dojo-attach-point='focusNode,containerNode,textbox' autocomplete='off'></textarea>", + + postMixInProperties: function(){ + // Copy value from srcNodeRef, unless user specified a value explicitly (or there is no srcNodeRef) + // TODO: parser will handle this in 2.0 + if(!this.value && this.srcNodeRef){ + this.value = this.srcNodeRef.value; + } + this.inherited(arguments); }, - onHide: function(){ - // summary: - // Called when another widget becomes the selected pane in a - // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`, - // `dijit.layout.AccordionContainer`, etc. - // - // Also called to indicate hide of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`. - // tags: - // callback + buildRendering: function(){ + this.inherited(arguments); + if(has("ie") && this.cols){ // attribute selectors is not supported in IE6 + domClass.add(this.textbox, "dijitTextAreaCols"); + } }, - onClose: function(){ - // summary: - // Called when this widget is being displayed as a popup (ex: a Calendar popped - // up from a DateTextBox), and it is hidden. - // This is called from the dijit.popup code, and should not be called directly. - // - // Also used as a parameter for children of `dijit.layout.StackContainer` or subclasses. - // Callback if a user tries to close the child. Child will be closed if this function returns true. - // tags: - // extension + filter: function(/*String*/ value){ + // Override TextBox.filter to deal with newlines... specifically (IIRC) this is for IE which writes newlines + // as \r\n instead of just \n + if(value){ + value = value.replace(/\r/g,""); + } + return this.inherited(arguments); + }, - return true; // Boolean + _onInput: function(/*Event?*/ e){ + // Override TextBox._onInput() to enforce maxLength restriction + if(this.maxLength){ + var maxLength = parseInt(this.maxLength); + var value = this.textbox.value.replace(/\r/g,''); + var overflow = value.length - maxLength; + if(overflow > 0){ + var textarea = this.textbox; + if(textarea.selectionStart){ + var pos = textarea.selectionStart; + var cr = 0; + if(has("opera")){ + cr = (this.textbox.value.substring(0,pos).match(/\r/g) || []).length; + } + this.textbox.value = value.substring(0,pos-overflow-cr)+value.substring(pos-cr); + textarea.setSelectionRange(pos-overflow, pos-overflow); + }else if(win.doc.selection){ //IE + textarea.focus(); + var range = win.doc.selection.createRange(); + // delete overflow characters + range.moveStart("character", -overflow); + range.text = ''; + // show cursor + range.select(); + } + } + } + this.inherited(arguments); } }); -})(); +}); -} +}, +'url:dijit/layout/templates/_TabButton.html':"<div role=\"presentation\" data-dojo-attach-point=\"titleNode\" data-dojo-attach-event='onclick:onClick'>\n <div role=\"presentation\" class='dijitTabInnerDiv' data-dojo-attach-point='innerDiv'>\n <div role=\"presentation\" class='dijitTabContent' data-dojo-attach-point='tabContent'>\n \t<div role=\"presentation\" data-dojo-attach-point='focusNode'>\n\t\t <img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitTabButtonIcon\" data-dojo-attach-point='iconNode' />\n\t\t <span data-dojo-attach-point='containerNode' class='tabLabel'></span>\n\t\t <span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" data-dojo-attach-point='closeNode'\n\t\t \t\tdata-dojo-attach-event='onclick: onClickCloseButton' role=\"presentation\">\n\t\t <span data-dojo-attach-point='closeText' class='dijitTabCloseText'>[x]</span\n\t\t ></span>\n\t\t\t</div>\n </div>\n </div>\n</div>\n", +'dijit/_base/window':function(){ +define("dijit/_base/window", [ + "dojo/window", // windowUtils.get + ".." // export symbol to dijit +], function(windowUtils, dijit){ + // module: + // dijit/_base/window + // summary: + // Back compatibility module, new code should use windowUtils directly instead of using this module. -if(!dojo._hasResource["dojo.string"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.string"] = true; -dojo.provide("dojo.string"); + dijit.getDocumentWindow = function(doc){ + return windowUtils.get(doc); + }; +}); -dojo.getObject("string", true, dojo); +}, +'dijit/form/RadioButton':function(){ +define("dijit/form/RadioButton", [ + "dojo/_base/declare", // declare + "./CheckBox", + "./_RadioButtonMixin" +], function(declare, CheckBox, _RadioButtonMixin){ /*===== -dojo.string = { - // summary: String utilities for Dojo -}; + var CheckBox = dijit.form.CheckBox; + var _RadioButtonMixin = dijit.form._RadioButtonMixin; =====*/ -dojo.string.rep = function(/*String*/str, /*Integer*/num){ - // summary: - // Efficiently replicate a string `n` times. - // str: - // the string to replicate - // num: - // number of times to replicate the string - - if(num <= 0 || !str){ return ""; } - - var buf = []; - for(;;){ - if(num & 1){ - buf.push(str); - } - if(!(num >>= 1)){ break; } - str += str; - } - return buf.join(""); // String -}; + // module: + // dijit/form/RadioButton + // summary: + // Radio button widget -dojo.string.pad = function(/*String*/text, /*Integer*/size, /*String?*/ch, /*Boolean?*/end){ - // summary: - // Pad a string to guarantee that it is at least `size` length by - // filling with the character `ch` at either the start or end of the - // string. Pads at the start, by default. - // text: - // the string to pad - // size: - // length to provide padding - // ch: - // character to pad, defaults to '0' - // end: - // adds padding at the end if true, otherwise pads at start - // example: - // | // Fill the string to length 10 with "+" characters on the right. Yields "Dojo++++++". - // | dojo.string.pad("Dojo", 10, "+", true); + return declare("dijit.form.RadioButton", [CheckBox, _RadioButtonMixin], { + // summary: + // Same as an HTML radio, but with fancy styling. - if(!ch){ - ch = '0'; - } - var out = String(text), - pad = dojo.string.rep(ch, Math.ceil((size - out.length) / ch.length)); - return end ? out + pad : pad + out; // String -}; + baseClass: "dijitRadio" + }); +}); -dojo.string.substitute = function( /*String*/ template, - /*Object|Array*/map, - /*Function?*/ transform, - /*Object?*/ thisObject){ - // summary: - // Performs parameterized substitutions on a string. Throws an - // exception if any parameter is unmatched. - // template: - // a string with expressions in the form `${key}` to be replaced or - // `${key:format}` which specifies a format function. keys are case-sensitive. - // map: - // hash to search for substitutions - // transform: - // a function to process all parameters before substitution takes - // place, e.g. mylib.encodeXML - // thisObject: - // where to look for optional format function; default to the global - // namespace - // example: - // Substitutes two expressions in a string from an Array or Object - // | // returns "File 'foo.html' is not found in directory '/temp'." - // | // by providing substitution data in an Array - // | dojo.string.substitute( - // | "File '${0}' is not found in directory '${1}'.", - // | ["foo.html","/temp"] - // | ); - // | - // | // also returns "File 'foo.html' is not found in directory '/temp'." - // | // but provides substitution data in an Object structure. Dotted - // | // notation may be used to traverse the structure. - // | dojo.string.substitute( - // | "File '${name}' is not found in directory '${info.dir}'.", - // | { name: "foo.html", info: { dir: "/temp" } } - // | ); - // example: - // Use a transform function to modify the values: - // | // returns "file 'foo.html' is not found in directory '/temp'." - // | dojo.string.substitute( - // | "${0} is not found in ${1}.", - // | ["foo.html","/temp"], - // | function(str){ - // | // try to figure out the type - // | var prefix = (str.charAt(0) == "/") ? "directory": "file"; - // | return prefix + " '" + str + "'"; - // | } - // | ); - // example: - // Use a formatter - // | // returns "thinger -- howdy" - // | dojo.string.substitute( - // | "${0:postfix}", ["thinger"], null, { - // | postfix: function(value, key){ - // | return value + " -- howdy"; - // | } - // | } - // | ); +}, +'dijit/main':function(){ +define("dijit/main", [ + "dojo/_base/kernel" +], function(dojo){ + // module: + // dijit + // summary: + // The dijit package main module - thisObject = thisObject || dojo.global; - transform = transform ? - dojo.hitch(thisObject, transform) : function(v){ return v; }; + return dojo.dijit; +}); - return template.replace(/\$\{([^\s\:\}]+)(?:\:([^\s\:\}]+))?\}/g, - function(match, key, format){ - var value = dojo.getObject(key, false, map); - if(format){ - value = dojo.getObject(format, false, thisObject).call(thisObject, value, key); - } - return transform(value, key).toString(); - }); // String -}; +}, +'dijit/_OnDijitClickMixin':function(){ +define("dijit/_OnDijitClickMixin", [ + "dojo/on", + "dojo/_base/array", // array.forEach + "dojo/keys", // keys.ENTER keys.SPACE + "dojo/_base/declare", // declare + "dojo/_base/sniff", // has("ie") + "dojo/_base/unload", // unload.addOnWindowUnload + "dojo/_base/window" // win.doc.addEventListener win.doc.attachEvent win.doc.detachEvent +], function(on, array, keys, declare, has, unload, win){ + + // module: + // dijit/_OnDijitClickMixin + // summary: + // Mixin so you can pass "ondijitclick" to this.connect() method, + // as a way to handle clicks by mouse, or by keyboard (SPACE/ENTER key) + + + // Keep track of where the last keydown event was, to help avoid generating + // spurious ondijitclick events when: + // 1. focus is on a <button> or <a> + // 2. user presses then releases the ENTER key + // 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler + // 4. onkeyup event fires, causing the ondijitclick handler to fire + var lastKeyDownNode = null; + if(has("ie")){ + (function(){ + var keydownCallback = function(evt){ + lastKeyDownNode = evt.srcElement; + }; + win.doc.attachEvent('onkeydown', keydownCallback); + unload.addOnWindowUnload(function(){ + win.doc.detachEvent('onkeydown', keydownCallback); + }); + })(); + }else{ + win.doc.addEventListener('keydown', function(evt){ + lastKeyDownNode = evt.target; + }, true); + } -/*===== -dojo.string.trim = function(str){ - // summary: - // Trims whitespace from both sides of the string - // str: String - // String to be trimmed - // returns: String - // Returns the trimmed string - // description: - // This version of trim() was taken from [Steven Levithan's blog](http://blog.stevenlevithan.com/archives/faster-trim-javascript). - // The short yet performant version of this function is dojo.trim(), - // which is part of Dojo base. Uses String.prototype.trim instead, if available. - return ""; // String -} -=====*/ + // Custom a11yclick (a.k.a. ondijitclick) event + var a11yclick = function(node, listener){ + if(/input|button/i.test(node.nodeName)){ + // pass through, the browser already generates click event on SPACE/ENTER key + return on(node, "click", listener); + }else{ + // Don't fire the click event unless both the keydown and keyup occur on this node. + // Avoids problems where focus shifted to this node or away from the node on keydown, + // either causing this node to process a stray keyup event, or causing another node + // to get a stray keyup event. + + function clickKey(/*Event*/ e){ + return (e.keyCode == keys.ENTER || e.keyCode == keys.SPACE) && + !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey; + } + var handles = [ + on(node, "keypress", function(e){ + //console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode)); + if(clickKey(e)){ + // needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work + lastKeyDownNode = e.target; + + // Prevent viewport scrolling on space key in IE<9. + // (Reproducible on test_Button.html on any of the first dijit.form.Button examples) + // Do this onkeypress rather than onkeydown because onkeydown.preventDefault() will + // suppress the onkeypress event, breaking _HasDropDown + e.preventDefault(); + } + }), -dojo.string.trim = String.prototype.trim ? - dojo.trim : // aliasing to the native function - function(str){ - str = str.replace(/^\s+/, ''); - for(var i = str.length - 1; i >= 0; i--){ - if(/\S/.test(str.charAt(i))){ - str = str.substring(0, i + 1); - break; - } + on(node, "keyup", function(e){ + //console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode)); + if(clickKey(e) && e.target == lastKeyDownNode){ // === breaks greasemonkey + //need reset here or have problems in FF when focus returns to trigger element after closing popup/alert + lastKeyDownNode = null; + listener.call(this, e); + } + }), + + on(node, "click", function(e){ + // and connect for mouse clicks too (or touch-clicks on mobile) + listener.call(this, e); + }) + ]; + + return { + remove: function(){ + array.forEach(handles, function(h){ h.remove(); }); + } + }; } - return str; }; -} + return declare("dijit._OnDijitClickMixin", null, { + connect: function( + /*Object|null*/ obj, + /*String|Function*/ event, + /*String|Function*/ method){ + // summary: + // Connects specified obj/event to specified method of this object + // and registers for disconnect() on widget destroy. + // description: + // Provide widget-specific analog to connect.connect, except with the + // implicit use of this widget as the target object. + // This version of connect also provides a special "ondijitclick" + // event which triggers on a click or space or enter keyup. + // Events connected with `this.connect` are disconnected upon + // destruction. + // returns: + // A handle that can be passed to `disconnect` in order to disconnect before + // the widget is destroyed. + // example: + // | var btn = new dijit.form.Button(); + // | // when foo.bar() is called, call the listener we're going to + // | // provide in the scope of btn + // | btn.connect(foo, "bar", function(){ + // | console.debug(this.toString()); + // | }); + // tags: + // protected -if(!dojo._hasResource["dojo.cache"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.cache"] = true; -dojo.provide("dojo.cache"); + return this.inherited(arguments, [obj, event == "ondijitclick" ? a11yclick : event, method]); + } + }); +}); +}, +'dijit/InlineEditBox':function(){ +require({cache:{ +'url: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"}}); +define("dijit/InlineEditBox", [ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set domAttr.get + "dojo/dom-class", // domClass.add domClass.remove domClass.toggle + "dojo/dom-construct", // domConstruct.create domConstruct.destroy + "dojo/dom-style", // domStyle.getComputedStyle domStyle.set domStyle.get + "dojo/_base/event", // event.stop + "dojo/i18n", // i18n.getLocalization + "dojo/_base/kernel", // kernel.deprecated + "dojo/keys", // keys.ENTER keys.ESCAPE + "dojo/_base/lang", // lang.getObject + "dojo/_base/sniff", // has("ie") + "./focus", + "./_Widget", + "./_TemplatedMixin", + "./_WidgetsInTemplateMixin", + "./_Container", + "./form/Button", + "./form/_TextBoxMixin", + "./form/TextBox", + "dojo/text!./templates/InlineEditBox.html", + "dojo/i18n!./nls/common" +], function(array, declare, domAttr, domClass, domConstruct, domStyle, event, i18n, kernel, keys, lang, has, + fm, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, _Container, Button, _TextBoxMixin, TextBox, template){ /*===== -dojo.cache = { - // summary: - // A way to cache string content that is fetchable via `dojo.moduleUrl`. -}; + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _WidgetsInTemplateMixin = dijit._WidgetsInTemplateMixin; + var _Container = dijit._Container; + var Button = dijit.form.Button; + var TextBox = dijit.form.TextBox; =====*/ - var cache = {}; - dojo.cache = function(/*String||Object*/module, /*String*/url, /*String||Object?*/value){ - // summary: - // A getter and setter for storing the string content associated with the - // module and url arguments. - // description: - // module and url are used to call `dojo.moduleUrl()` to generate a module URL. - // If value is specified, the cache value for the moduleUrl will be set to - // that value. Otherwise, dojo.cache will fetch the moduleUrl and store it - // in its internal cache and return that cached value for the URL. To clear - // a cache value pass null for value. Since XMLHttpRequest (XHR) is used to fetch the - // the URL contents, only modules on the same domain of the page can use this capability. - // The build system can inline the cache values though, to allow for xdomain hosting. - // module: String||Object - // If a String, the module name to use for the base part of the URL, similar to module argument - // to `dojo.moduleUrl`. If an Object, something that has a .toString() method that - // generates a valid path for the cache item. For example, a dojo._Url object. - // url: String - // The rest of the path to append to the path derived from the module argument. If - // module is an object, then this second argument should be the "value" argument instead. - // value: String||Object? - // If a String, the value to use in the cache for the module/url combination. - // If an Object, it can have two properties: value and sanitize. The value property - // should be the value to use in the cache, and sanitize can be set to true or false, - // to indicate if XML declarations should be removed from the value and if the HTML - // inside a body tag in the value should be extracted as the real value. The value argument - // or the value property on the value argument are usually only used by the build system - // as it inlines cache content. - // example: - // To ask dojo.cache to fetch content and store it in the cache (the dojo["cache"] style - // of call is used to avoid an issue with the build system erroneously trying to intern - // this example. To get the build system to intern your dojo.cache calls, use the - // "dojo.cache" style of call): - // | //If template.html contains "<h1>Hello</h1>" that will be - // | //the value for the text variable. - // | var text = dojo["cache"]("my.module", "template.html"); - // example: - // To ask dojo.cache to fetch content and store it in the cache, and sanitize the input - // (the dojo["cache"] style of call is used to avoid an issue with the build system - // erroneously trying to intern this example. To get the build system to intern your - // dojo.cache calls, use the "dojo.cache" style of call): - // | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the - // | //text variable will contain just "<h1>Hello</h1>". - // | var text = dojo["cache"]("my.module", "template.html", {sanitize: true}); - // example: - // Same example as previous, but demostrates how an object can be passed in as - // the first argument, then the value argument can then be the second argument. - // | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the - // | //text variable will contain just "<h1>Hello</h1>". - // | var text = dojo["cache"](new dojo._Url("my/module/template.html"), {sanitize: true}); - - //Module could be a string, or an object that has a toString() method - //that will return a useful path. If it is an object, then the "url" argument - //will actually be the value argument. - if(typeof module == "string"){ - var pathObj = dojo.moduleUrl(module, url); - }else{ - pathObj = module; - value = url; - } - var key = pathObj.toString(); +// module: +// dijit/InlineEditBox +// summary: +// An element with in-line edit capabilities - var val = value; - if(value != undefined && !dojo.isString(value)){ - val = ("value" in value ? value.value : undefined); - } +var InlineEditor = declare("dijit._InlineEditor", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], { + // summary: + // Internal widget used by InlineEditBox, displayed when in editing mode + // to display the editor and maybe save/cancel buttons. Calling code should + // connect to save/cancel methods to detect when editing is finished + // + // Has mainly the same parameters as InlineEditBox, plus these values: + // + // style: Object + // Set of CSS attributes of display node, to replicate in editor + // + // value: String + // Value as an HTML string or plain text string, depending on renderAsHTML flag - var sanitize = value && value.sanitize ? true : false; + templateString: template, - if(typeof val == "string"){ - //We have a string, set cache value - val = cache[key] = sanitize ? dojo.cache._sanitize(val) : val; - }else if(val === null){ - //Remove cached value - delete cache[key]; - }else{ - //Allow cache values to be empty strings. If key property does - //not exist, fetch it. - if(!(key in cache)){ - val = dojo._getText(key); - cache[key] = sanitize ? dojo.cache._sanitize(val) : val; - } - val = cache[key]; - } - return val; //String - }; + postMixInProperties: function(){ + this.inherited(arguments); + this.messages = i18n.getLocalization("dijit", "common", this.lang); + array.forEach(["buttonSave", "buttonCancel"], function(prop){ + if(!this[prop]){ this[prop] = this.messages[prop]; } + }, this); + }, - dojo.cache._sanitize = function(/*String*/val){ - // summary: - // Strips <?xml ...?> declarations so that external SVG and XML - // documents can be added to a document without worry. Also, if the string - // is an HTML document, only the part inside the body tag is returned. - // description: - // Copied from dijit._Templated._sanitizeTemplateString. - if(val){ - val = val.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, ""); - var matches = val.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); - if(matches){ - val = matches[1]; + buildRendering: function(){ + this.inherited(arguments); + + // Create edit widget in place in the template + var cls = typeof this.editor == "string" ? lang.getObject(this.editor) : this.editor; + + // Copy the style from the source + // Don't copy ALL properties though, just the necessary/applicable ones. + // wrapperStyle/destStyle code is to workaround IE bug where getComputedStyle().fontSize + // is a relative value like 200%, rather than an absolute value like 24px, and + // the 200% can refer *either* to a setting on the node or it's ancestor (see #11175) + var srcStyle = this.sourceStyle, + editStyle = "line-height:" + srcStyle.lineHeight + ";", + destStyle = domStyle.getComputedStyle(this.domNode); + array.forEach(["Weight","Family","Size","Style"], function(prop){ + var textStyle = srcStyle["font"+prop], + wrapperStyle = destStyle["font"+prop]; + if(wrapperStyle != textStyle){ + editStyle += "font-"+prop+":"+srcStyle["font"+prop]+";"; } + }, this); + array.forEach(["marginTop","marginBottom","marginLeft", "marginRight"], function(prop){ + this.domNode.style[prop] = srcStyle[prop]; + }, this); + var width = this.inlineEditBox.width; + if(width == "100%"){ + // block mode + editStyle += "width:100%;"; + this.domNode.style.display = "block"; }else{ - val = ""; + // inline-block mode + editStyle += "width:" + (width + (Number(width) == width ? "px" : "")) + ";"; } - return val; //String - }; - -} - -if(!dojo._hasResource["dijit._Templated"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._Templated"] = true; -dojo.provide("dijit._Templated"); + var editorParams = lang.delegate(this.inlineEditBox.editorParams, { + style: editStyle, + dir: this.dir, + lang: this.lang, + textDir: this.textDir + }); + editorParams[ "displayedValue" in cls.prototype ? "displayedValue" : "value"] = this.value; + this.editWidget = new cls(editorParams, this.editorPlaceholder); + if(this.inlineEditBox.autoSave){ + // Remove the save/cancel buttons since saving is done by simply tabbing away or + // selecting a value from the drop down list + domConstruct.destroy(this.buttonContainer); + } + }, + postCreate: function(){ + this.inherited(arguments); + var ew = this.editWidget; + if(this.inlineEditBox.autoSave){ + // Selecting a value from a drop down list causes an onChange event and then we save + this.connect(ew, "onChange", "_onChange"); + // ESC and TAB should cancel and save. Note that edit widgets do a stopEvent() on ESC key (to + // prevent Dialog from closing when the user just wants to revert the value in the edit widget), + // so this is the only way we can see the key press event. + this.connect(ew, "onKeyPress", "_onKeyPress"); + }else{ + // If possible, enable/disable save button based on whether the user has changed the value + if("intermediateChanges" in ew){ + ew.set("intermediateChanges", true); + this.connect(ew, "onChange", "_onIntermediateChange"); + this.saveButton.set("disabled", true); + } + } + }, -dojo.declare("dijit._Templated", - null, - { + _onIntermediateChange: function(/*===== val =====*/){ // summary: - // Mixin for widgets that are instantiated from a template - - // templateString: [protected] String - // A string that represents the widget template. Pre-empts the - // templatePath. In builds that have their strings "interned", the - // templatePath is converted to an inline templateString, thereby - // preventing a synchronous network call. - // - // Use in conjunction with dojo.cache() to load from a file. - templateString: null, + // Called for editor widgets that support the intermediateChanges=true flag as a way + // to detect when to enable/disabled the save button + this.saveButton.set("disabled", (this.getValue() == this._resetValue) || !this.enableSave()); + }, - // templatePath: [protected deprecated] String - // Path to template (HTML file) for this widget relative to dojo.baseUrl. - // Deprecated: use templateString with dojo.cache() instead. - templatePath: null, + destroy: function(){ + this.editWidget.destroy(true); // let the parent wrapper widget clean up the DOM + this.inherited(arguments); + }, - // widgetsInTemplate: [protected] Boolean - // Should we parse the template to find widgets that might be - // declared in markup inside it? False by default. - widgetsInTemplate: false, + getValue: function(){ + // summary: + // Return the [display] value of the edit widget + var ew = this.editWidget; + return String(ew.get("displayedValue" in ew ? "displayedValue" : "value")); + }, - // skipNodeCache: [protected] Boolean - // If using a cached widget template node poses issues for a - // particular widget class, it can set this property to ensure - // that its template is always re-built from a string - _skipNodeCache: false, + _onKeyPress: function(e){ + // summary: + // Handler for keypress in the edit box in autoSave mode. + // description: + // For autoSave widgets, if Esc/Enter, call cancel/save. + // tags: + // private - // _earlyTemplatedStartup: Boolean - // A fallback to preserve the 1.0 - 1.3 behavior of children in - // templates having their startup called before the parent widget - // fires postCreate. Defaults to 'false', causing child widgets to - // have their .startup() called immediately before a parent widget - // .startup(), but always after the parent .postCreate(). Set to - // 'true' to re-enable to previous, arguably broken, behavior. - _earlyTemplatedStartup: false, + if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){ + if(e.altKey || e.ctrlKey){ return; } + // If Enter/Esc pressed, treat as save/cancel. + if(e.charOrCode == keys.ESCAPE){ + event.stop(e); + this.cancel(true); // sets editing=false which short-circuits _onBlur processing + }else if(e.charOrCode == keys.ENTER && e.target.tagName == "INPUT"){ + event.stop(e); + this._onChange(); // fire _onBlur and then save + } -/*===== - // _attachPoints: [private] String[] - // List of widget attribute names associated with dojoAttachPoint=... in the - // template, ex: ["containerNode", "labelNode"] - _attachPoints: [], - =====*/ + // _onBlur will handle TAB automatically by allowing + // the TAB to change focus before we mess with the DOM: #6227 + // Expounding by request: + // The current focus is on the edit widget input field. + // save() will hide and destroy this widget. + // We want the focus to jump from the currently hidden + // displayNode, but since it's hidden, it's impossible to + // unhide it, focus it, and then have the browser focus + // away from it to the next focusable element since each + // of these events is asynchronous and the focus-to-next-element + // is already queued. + // So we allow the browser time to unqueue the move-focus event + // before we do all the hide/show stuff. + } + }, -/*===== - // _attachEvents: [private] Handle[] - // List of connections associated with dojoAttachEvent=... in the - // template - _attachEvents: [], - =====*/ + _onBlur: function(){ + // summary: + // Called when focus moves outside the editor + // tags: + // private - constructor: function(){ - this._attachPoints = []; - this._attachEvents = []; - }, + this.inherited(arguments); + if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){ + if(this.getValue() == this._resetValue){ + this.cancel(false); + }else if(this.enableSave()){ + this.save(false); + } + } + }, - _stringRepl: function(tmpl){ - // summary: - // Does substitution of ${foo} type properties in template string - // tags: - // private - var className = this.declaredClass, _this = this; - // Cache contains a string because we need to do property replacement - // do the property replacement - return dojo.string.substitute(tmpl, this, function(value, key){ - if(key.charAt(0) == '!'){ value = dojo.getObject(key.substr(1), false, _this); } - if(typeof value == "undefined"){ throw new Error(className+" template:"+key); } // a debugging aide - if(value == null){ return ""; } + _onChange: function(){ + // summary: + // Called when the underlying widget fires an onChange event, + // such as when the user selects a value from the drop down list of a ComboBox, + // which means that the user has finished entering the value and we should save. + // tags: + // private - // Substitution keys beginning with ! will skip the transform step, - // in case a user wishes to insert unescaped markup, e.g. ${!foo} - return key.charAt(0) == "!" ? value : - // Safer substitution, see heading "Attribute values" in - // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 - value.toString().replace(/"/g,"""); //TODO: add &? use encodeXML method? - }, this); - }, + if(this.inlineEditBox.autoSave && this.inlineEditBox.editing && this.enableSave()){ + fm.focus(this.inlineEditBox.displayNode); // fires _onBlur which will save the formatted value + } + }, - buildRendering: function(){ - // summary: - // Construct the UI for this widget from a template, setting this.domNode. - // tags: - // protected + enableSave: function(){ + // summary: + // User overridable function returning a Boolean to indicate + // if the Save button should be enabled or not - usually due to invalid conditions + // tags: + // extension + return ( + this.editWidget.isValid + ? this.editWidget.isValid() + : true + ); + }, - // Lookup cached version of template, and download to cache if it - // isn't there already. Returns either a DomNode or a string, depending on - // whether or not the template contains ${foo} replacement parameters. - var cached = dijit._Templated.getCachedTemplate(this.templatePath, this.templateString, this._skipNodeCache); + focus: function(){ + // summary: + // Focus the edit widget. + // tags: + // protected - var node; - if(dojo.isString(cached)){ - node = dojo._toDom(this._stringRepl(cached)); - if(node.nodeType != 1){ - // Flag common problems such as templates with multiple top level nodes (nodeType == 11) - throw new Error("Invalid template: " + cached); - } - }else{ - // if it's a node, all we have to do is clone it - node = cached.cloneNode(true); + this.editWidget.focus(); + setTimeout(lang.hitch(this, function(){ + if(this.editWidget.focusNode && this.editWidget.focusNode.tagName == "INPUT"){ + _TextBoxMixin.selectInputText(this.editWidget.focusNode); } + }), 0); + } +}); - this.domNode = node; - // Call down to _Widget.buildRendering() to get base classes assigned - // TODO: change the baseClass assignment to attributeMap - this.inherited(arguments); +var InlineEditBox = declare("dijit.InlineEditBox", _Widget, { + // summary: + // An element with in-line edit capabilities + // + // description: + // Behavior for an existing node (`<p>`, `<div>`, `<span>`, etc.) so that + // when you click it, an editor shows up in place of the original + // text. Optionally, Save and Cancel button are displayed below the edit widget. + // When Save is clicked, the text is pulled from the edit + // widget and redisplayed and the edit widget is again hidden. + // By default a plain Textarea widget is used as the editor (or for + // inline values a TextBox), but you can specify an editor such as + // dijit.Editor (for editing HTML) or a Slider (for adjusting a number). + // An edit widget must support the following API to be used: + // - displayedValue or value as initialization parameter, + // and available through set('displayedValue') / set('value') + // - void focus() + // - DOM-node focusNode = node containing editable text - // recurse through the node, looking for, and attaching to, our - // attachment points and events, which should be defined on the template node. - this._attachTemplateNodes(node); + // editing: [readonly] Boolean + // Is the node currently in edit mode? + editing: false, - if(this.widgetsInTemplate){ - // Store widgets that we need to start at a later point in time - var cw = (this._startupWidgets = dojo.parser.parse(node, { - noStart: !this._earlyTemplatedStartup, - template: true, - inherited: {dir: this.dir, lang: this.lang}, - propsThis: this, // so data-dojo-props of widgets in the template can reference "this" to refer to me - scope: "dojo" // even in multi-version mode templates use dojoType/data-dojo-type - })); + // autoSave: Boolean + // Changing the value automatically saves it; don't have to push save button + // (and save button isn't even displayed) + autoSave: true, - this._supportingWidgets = dijit.findWidgets(node); + // buttonSave: String + // Save button label + buttonSave: "", - this._attachTemplateNodes(cw, function(n,p){ - return n[p]; - }); - } + // buttonCancel: String + // Cancel button label + buttonCancel: "", - this._fillContent(this.srcNodeRef); - }, + // renderAsHtml: Boolean + // Set this to true if the specified Editor's value should be interpreted as HTML + // rather than plain text (ex: `dijit.Editor`) + renderAsHtml: false, - _fillContent: function(/*DomNode*/ source){ - // summary: - // Relocate source contents to templated container node. - // this.containerNode must be able to receive children, or exceptions will be thrown. - // tags: - // protected - var dest = this.containerNode; - if(source && dest){ - while(source.hasChildNodes()){ - dest.appendChild(source.firstChild); - } - } - }, + // editor: String|Function + // Class name (or reference to the Class) for Editor widget + editor: TextBox, - _attachTemplateNodes: function(rootNode, getAttrFunc){ - // summary: - // Iterate through the template and attach functions and nodes accordingly. - // Alternately, if rootNode is an array of widgets, then will process dojoAttachPoint - // etc. for those widgets. - // description: - // Map widget properties and functions to the handlers specified in - // the dom node and it's descendants. This function iterates over all - // nodes and looks for these properties: - // * dojoAttachPoint - // * dojoAttachEvent - // * waiRole - // * waiState - // rootNode: DomNode|Array[Widgets] - // the node to search for properties. All children will be searched. - // getAttrFunc: Function? - // a function which will be used to obtain property for a given - // DomNode/Widget - // tags: - // private + // editorWrapper: String|Function + // Class name (or reference to the Class) for widget that wraps the editor widget, displaying save/cancel + // buttons. + editorWrapper: InlineEditor, - getAttrFunc = getAttrFunc || function(n,p){ return n.getAttribute(p); }; + // editorParams: Object + // Set of parameters for editor, like {required: true} + editorParams: {}, - var nodes = dojo.isArray(rootNode) ? rootNode : (rootNode.all || rootNode.getElementsByTagName("*")); - var x = dojo.isArray(rootNode) ? 0 : -1; - for(; x<nodes.length; x++){ - var baseNode = (x == -1) ? rootNode : nodes[x]; - if(this.widgetsInTemplate && (getAttrFunc(baseNode, "dojoType") || getAttrFunc(baseNode, "data-dojo-type"))){ - continue; - } - // Process dojoAttachPoint - var attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point"); - if(attachPoint){ - var point, points = attachPoint.split(/\s*,\s*/); - while((point = points.shift())){ - if(dojo.isArray(this[point])){ - this[point].push(baseNode); - }else{ - this[point]=baseNode; - } - this._attachPoints.push(point); - } - } + // disabled: Boolean + // If true, clicking the InlineEditBox to edit it will have no effect. + disabled: false, - // Process dojoAttachEvent - var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event");; - if(attachEvent){ - // NOTE: we want to support attributes that have the form - // "domEvent: nativeEvent; ..." - var event, events = attachEvent.split(/\s*,\s*/); - var trim = dojo.trim; - while((event = events.shift())){ - if(event){ - var thisFunc = null; - if(event.indexOf(":") != -1){ - // oh, if only JS had tuple assignment - var funcNameArr = event.split(":"); - event = trim(funcNameArr[0]); - thisFunc = trim(funcNameArr[1]); - }else{ - event = trim(event); - } - if(!thisFunc){ - thisFunc = event; - } - this._attachEvents.push(this.connect(baseNode, event, thisFunc)); - } - } - } + onChange: function(/*===== value =====*/){ + // summary: + // Set this handler to be notified of changes to value. + // tags: + // callback + }, - // waiRole, waiState - // TODO: remove this in 2.0, templates are now using role=... and aria-XXX=... attributes directicly - var role = getAttrFunc(baseNode, "waiRole"); - if(role){ - dijit.setWaiRole(baseNode, role); - } - var values = getAttrFunc(baseNode, "waiState"); - if(values){ - dojo.forEach(values.split(/\s*,\s*/), function(stateValue){ - if(stateValue.indexOf('-') != -1){ - var pair = stateValue.split('-'); - dijit.setWaiState(baseNode, pair[0], pair[1]); - } - }); - } - } - }, + onCancel: function(){ + // summary: + // Set this handler to be notified when editing is cancelled. + // tags: + // callback + }, - startup: function(){ - dojo.forEach(this._startupWidgets, function(w){ - if(w && !w._started && w.startup){ - w.startup(); - } - }); - this.inherited(arguments); - }, + // width: String + // Width of editor. By default it's width=100% (ie, block mode). + width: "100%", - destroyRendering: function(){ - // Delete all attach points to prevent IE6 memory leaks. - dojo.forEach(this._attachPoints, function(point){ - delete this[point]; - }, this); - this._attachPoints = []; + // value: String + // The display value of the widget in read-only mode + value: "", - // And same for event handlers - dojo.forEach(this._attachEvents, this.disconnect, this); - this._attachEvents = []; - - this.inherited(arguments); - } - } -); + // noValueIndicator: [const] String + // The text that gets displayed when there is no value (so that the user has a place to click to edit) + noValueIndicator: has("ie") <= 6 ? // font-family needed on IE6 but it messes up IE8 + "<span style='font-family: wingdings; text-decoration: underline;'>    ✍    </span>" : + "<span style='text-decoration: underline;'>    ✍    </span>", // //   == -// key is either templatePath or templateString; object is either string or DOM tree -dijit._Templated._templateCache = {}; + constructor: function(){ + // summary: + // Sets up private arrays etc. + // tags: + // private + this.editorParams = {}; + }, -dijit._Templated.getCachedTemplate = function(templatePath, templateString, alwaysUseString){ - // summary: - // Static method to get a template based on the templatePath or - // templateString key - // templatePath: String||dojo.uri.Uri - // The URL to get the template from. - // templateString: String? - // a string to use in lieu of fetching the template from a URL. Takes precedence - // over templatePath - // returns: Mixed - // Either string (if there are ${} variables that need to be replaced) or just - // a DOM tree (if the node can be cloned directly) - - // is it already cached? - var tmplts = dijit._Templated._templateCache; - var key = templateString || templatePath; - var cached = tmplts[key]; - if(cached){ - try{ - // 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 - if(!cached.ownerDocument || cached.ownerDocument == dojo.doc){ - // string or node of the same document - return cached; - } - }catch(e){ /* squelch */ } // IE can throw an exception if cached.ownerDocument was reloaded - dojo.destroy(cached); - } + postMixInProperties: function(){ + this.inherited(arguments); - // If necessary, load template string from template path - if(!templateString){ - templateString = dojo.cache(templatePath, {sanitize: true}); - } - templateString = dojo.string.trim(templateString); + // save pointer to original source node, since Widget nulls-out srcNodeRef + this.displayNode = this.srcNodeRef; - if(alwaysUseString || templateString.match(/\$\{([^\}]+)\}/g)){ - // there are variables in the template so all we can do is cache the string - return (tmplts[key] = templateString); //String - }else{ - // there are no variables in the template so we can cache the DOM tree - var node = dojo._toDom(templateString); - if(node.nodeType != 1){ - throw new Error("Invalid template: " + templateString); + // connect handlers to the display node + var events = { + ondijitclick: "_onClick", + onmouseover: "_onMouseOver", + onmouseout: "_onMouseOut", + onfocus: "_onMouseOver", + onblur: "_onMouseOut" + }; + for(var name in events){ + this.connect(this.displayNode, name, events[name]); } - return (tmplts[key] = node); //Node - } -}; - -if(dojo.isIE){ - dojo.addOnWindowUnload(function(){ - var cache = dijit._Templated._templateCache; - for(var key in cache){ - var value = cache[key]; - if(typeof value == "object"){ // value is either a string or a DOM node template - dojo.destroy(value); - } - delete cache[key]; + this.displayNode.setAttribute("role", "button"); + if(!this.displayNode.getAttribute("tabIndex")){ + this.displayNode.setAttribute("tabIndex", 0); } - }); -} -// These arguments can be specified for widgets which are used in templates. -// Since any widget can be specified as sub widgets in template, mix it -// into the base widget class. (This is a hack, but it's effective.) -dojo.extend(dijit._Widget,{ - dojoAttachEvent: "", - dojoAttachPoint: "", - waiRole: "", - waiState:"" -}); + if(!this.value && !("value" in this.params)){ // "" is a good value if specified directly so check params){ + this.value = lang.trim(this.renderAsHtml ? this.displayNode.innerHTML : + (this.displayNode.innerText||this.displayNode.textContent||"")); + } + if(!this.value){ + this.displayNode.innerHTML = this.noValueIndicator; + } -} + domClass.add(this.displayNode, 'dijitInlineEditBoxDisplayMode'); + }, -if(!dojo._hasResource["dijit._Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._Container"] = true; -dojo.provide("dijit._Container"); + setDisabled: function(/*Boolean*/ disabled){ + // summary: + // Deprecated. Use set('disabled', ...) instead. + // tags: + // deprecated + kernel.deprecated("dijit.InlineEditBox.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0"); + this.set('disabled', disabled); + }, + _setDisabledAttr: function(/*Boolean*/ disabled){ + // summary: + // Hook to make set("disabled", ...) work. + // Set disabled state of widget. + this.domNode.setAttribute("aria-disabled", disabled); + if(disabled){ + this.displayNode.removeAttribute("tabIndex"); + }else{ + this.displayNode.setAttribute("tabIndex", 0); + } + domClass.toggle(this.displayNode, "dijitInlineEditBoxDisplayModeDisabled", disabled); + this._set("disabled", disabled); + }, -dojo.declare("dijit._Container", - null, - { + _onMouseOver: function(){ // summary: - // Mixin for widgets that contain a set of widget children. - // description: - // Use this mixin for widgets that needs to know about and - // keep track of their widget children. Suitable for widgets like BorderContainer - // and TabContainer which contain (only) a set of child widgets. - // - // It's not suitable for widgets like ContentPane - // which contains mixed HTML (plain DOM nodes in addition to widgets), - // and where contained widgets are not necessarily directly below - // this.containerNode. In that case calls like addChild(node, position) - // wouldn't make sense. + // Handler for onmouseover and onfocus event. + // tags: + // private + if(!this.disabled){ + domClass.add(this.displayNode, "dijitInlineEditBoxDisplayModeHover"); + } + }, - // isContainer: [protected] Boolean - // Indicates that this widget acts as a "parent" to the descendant widgets. - // When the parent is started it will call startup() on the child widgets. - // See also `isLayoutContainer`. - isContainer: true, + _onMouseOut: function(){ + // summary: + // Handler for onmouseout and onblur event. + // tags: + // private + domClass.remove(this.displayNode, "dijitInlineEditBoxDisplayModeHover"); + }, - buildRendering: function(){ - this.inherited(arguments); - if(!this.containerNode){ - // all widgets with descendants must set containerNode - this.containerNode = this.domNode; - } - }, + _onClick: function(/*Event*/ e){ + // summary: + // Handler for onclick event. + // tags: + // private + if(this.disabled){ return; } + if(e){ event.stop(e); } + this._onMouseOut(); - addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){ - // summary: - // Makes the given widget a child of this widget. - // description: - // Inserts specified child widget's dom node as a child of this widget's - // container node, and possibly does other processing (such as layout). + // Since FF gets upset if you move a node while in an event handler for that node... + setTimeout(lang.hitch(this, "edit"), 0); + }, - var refNode = this.containerNode; - if(insertIndex && typeof insertIndex == "number"){ - var children = this.getChildren(); - if(children && children.length >= insertIndex){ - refNode = children[insertIndex-1].domNode; - insertIndex = "after"; - } - } - dojo.place(widget.domNode, refNode, insertIndex); + edit: function(){ + // summary: + // Display the editor widget in place of the original (read only) markup. + // tags: + // private - // If I've been started but the child widget hasn't been started, - // start it now. Make sure to do this after widget has been - // inserted into the DOM tree, so it can see that it's being controlled by me, - // so it doesn't try to size itself. - if(this._started && !widget._started){ - widget.startup(); - } - }, + if(this.disabled || this.editing){ return; } + this._set('editing', true); - removeChild: function(/*Widget or int*/ widget){ - // summary: - // Removes the passed widget instance from this widget but does - // not destroy it. You can also pass in an integer indicating - // the index within the container to remove + // save some display node values that can be restored later + this._savedPosition = domStyle.get(this.displayNode, "position") || "static"; + this._savedOpacity = domStyle.get(this.displayNode, "opacity") || "1"; + this._savedTabIndex = domAttr.get(this.displayNode, "tabIndex") || "0"; - if(typeof widget == "number"){ - widget = this.getChildren()[widget]; - } + if(this.wrapperWidget){ + var ew = this.wrapperWidget.editWidget; + ew.set("displayedValue" in ew ? "displayedValue" : "value", this.value); + }else{ + // Placeholder for edit widget + // Put place holder (and eventually editWidget) before the display node so that it's positioned correctly + // when Calendar dropdown appears, which happens automatically on focus. + var placeholder = domConstruct.create("span", null, this.domNode, "before"); - if(widget){ - var node = widget.domNode; - if(node && node.parentNode){ - node.parentNode.removeChild(node); // detach but don't destroy - } + // Create the editor wrapper (the thing that holds the editor widget and the save/cancel buttons) + var ewc = typeof this.editorWrapper == "string" ? lang.getObject(this.editorWrapper) : this.editorWrapper; + this.wrapperWidget = new ewc({ + value: this.value, + buttonSave: this.buttonSave, + buttonCancel: this.buttonCancel, + dir: this.dir, + lang: this.lang, + tabIndex: this._savedTabIndex, + editor: this.editor, + inlineEditBox: this, + sourceStyle: domStyle.getComputedStyle(this.displayNode), + save: lang.hitch(this, "save"), + cancel: lang.hitch(this, "cancel"), + textDir: this.textDir + }, placeholder); + if(!this._started){ + this.startup(); } - }, + } + var ww = this.wrapperWidget; - hasChildren: function(){ - // summary: - // Returns true if widget has children, i.e. if this.containerNode contains something. - return this.getChildren().length > 0; // Boolean - }, + // to avoid screen jitter, we first create the editor with position:absolute, visibility:hidden, + // and then when it's finished rendering, we switch from display mode to editor + // position:absolute releases screen space allocated to the display node + // opacity:0 is the same as visibility:hidden but is still focusable + // visiblity:hidden removes focus outline - destroyDescendants: function(/*Boolean*/ preserveDom){ - // summary: - // Destroys all the widgets inside this.containerNode, - // but not this widget itself - dojo.forEach(this.getChildren(), function(child){ child.destroyRecursive(preserveDom); }); - }, + domStyle.set(this.displayNode, { position: "absolute", opacity: "0" }); // makes display node invisible, display style used for focus-ability + domStyle.set(ww.domNode, { position: this._savedPosition, visibility: "visible", opacity: "1" }); + domAttr.set(this.displayNode, "tabIndex", "-1"); // needed by WebKit for TAB from editor to skip displayNode - _getSiblingOfChild: function(/*dijit._Widget*/ child, /*int*/ dir){ - // summary: - // Get the next or previous widget sibling of child - // dir: - // if 1, get the next sibling - // if -1, get the previous sibling - // tags: - // private - var node = child.domNode, - which = (dir>0 ? "nextSibling" : "previousSibling"); - do{ - node = node[which]; - }while(node && (node.nodeType != 1 || !dijit.byNode(node))); - return node && dijit.byNode(node); // dijit._Widget - }, + // Replace the display widget with edit widget, leaving them both displayed for a brief time so that + // focus can be shifted without incident. (browser may needs some time to render the editor.) + setTimeout(lang.hitch(ww, function(){ + this.focus(); // both nodes are showing, so we can switch focus safely + this._resetValue = this.getValue(); + }), 0); + }, - getIndexOfChild: function(/*dijit._Widget*/ child){ - // summary: - // Gets the index of the child in this container or -1 if not found - return dojo.indexOf(this.getChildren(), child); // int - }, + _onBlur: function(){ + // summary: + // Called when focus moves outside the InlineEditBox. + // Performs garbage collection. + // tags: + // private - startup: function(){ - // summary: - // Called after all the widgets have been instantiated and their - // dom nodes have been inserted somewhere under dojo.doc.body. - // - // Widgets should override this method to do any initialization - // dependent on other widgets existing, and then call - // this superclass method to finish things off. - // - // startup() in subclasses shouldn't do anything - // size related because the size of the widget hasn't been set yet. + this.inherited(arguments); + if(!this.editing){ + /* causes IE focus problems, see TooltipDialog_a11y.html... + setTimeout(lang.hitch(this, function(){ + if(this.wrapperWidget){ + this.wrapperWidget.destroy(); + delete this.wrapperWidget; + } + }), 0); + */ + } + }, - if(this._started){ return; } + destroy: function(){ + if(this.wrapperWidget && !this.wrapperWidget._destroyed){ + this.wrapperWidget.destroy(); + delete this.wrapperWidget; + } + this.inherited(arguments); + }, - // Startup all children of this widget - dojo.forEach(this.getChildren(), function(child){ child.startup(); }); + _showText: function(/*Boolean*/ focus){ + // summary: + // Revert to display mode, and optionally focus on display node + // tags: + // private - this.inherited(arguments); + var ww = this.wrapperWidget; + domStyle.set(ww.domNode, { position: "absolute", visibility: "hidden", opacity: "0" }); // hide the editor from mouse/keyboard events + domStyle.set(this.displayNode, { position: this._savedPosition, opacity: this._savedOpacity }); // make the original text visible + domAttr.set(this.displayNode, "tabIndex", this._savedTabIndex); + if(focus){ + fm.focus(this.displayNode); } - } -); + }, -} + save: function(/*Boolean*/ focus){ + // summary: + // Save the contents of the editor and revert to display mode. + // focus: Boolean + // Focus on the display mode text + // tags: + // private -if(!dojo._hasResource["dijit._Contained"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._Contained"] = true; -dojo.provide("dijit._Contained"); + if(this.disabled || !this.editing){ return; } + this._set('editing', false); + var ww = this.wrapperWidget; + var value = ww.getValue(); + this.set('value', value); // display changed, formatted value -dojo.declare("dijit._Contained", - null, - { - // summary: - // Mixin for widgets that are children of a container widget - // - // example: - // | // make a basic custom widget that knows about it's parents - // | dojo.declare("my.customClass",[dijit._Widget,dijit._Contained],{}); + this._showText(focus); // set focus as needed + }, - getParent: function(){ - // summary: - // Returns the parent widget of this widget, assuming the parent - // specifies isContainer - var parent = dijit.getEnclosingWidget(this.domNode.parentNode); - return parent && parent.isContainer ? parent : null; - }, + setValue: function(/*String*/ val){ + // summary: + // Deprecated. Use set('value', ...) instead. + // tags: + // deprecated + kernel.deprecated("dijit.InlineEditBox.setValue() is deprecated. Use set('value', ...) instead.", "", "2.0"); + return this.set("value", val); + }, - _getSibling: function(/*String*/ which){ - // summary: - // Returns next or previous sibling - // which: - // Either "next" or "previous" - // tags: - // private - var node = this.domNode; - do{ - node = node[which+"Sibling"]; - }while(node && node.nodeType != 1); - return node && dijit.byNode(node); // dijit._Widget - }, + _setValueAttr: function(/*String*/ val){ + // summary: + // Hook to make set("value", ...) work. + // Inserts specified HTML value into this node, or an "input needed" character if node is blank. - getPreviousSibling: function(){ - // summary: - // Returns null if this is the first child of the parent, - // otherwise returns the next element sibling to the "left". + val = lang.trim(val); + var renderVal = this.renderAsHtml ? val : val.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """).replace(/\n/g, "<br>"); + this.displayNode.innerHTML = renderVal || this.noValueIndicator; + this._set("value", val); - return this._getSibling("previous"); // dijit._Widget - }, + if(this._started){ + // tell the world that we have changed + setTimeout(lang.hitch(this, "onChange", val), 0); // setTimeout prevents browser freeze for long-running event handlers + } + // contextual (auto) text direction depends on the text value + if(this.textDir == "auto"){ + this.applyTextDir(this.displayNode, this.displayNode.innerText); + } + }, - getNextSibling: function(){ - // summary: - // Returns null if this is the last child of the parent, - // otherwise returns the next element sibling to the "right". + getValue: function(){ + // summary: + // Deprecated. Use get('value') instead. + // tags: + // deprecated + kernel.deprecated("dijit.InlineEditBox.getValue() is deprecated. Use get('value') instead.", "", "2.0"); + return this.get("value"); + }, - return this._getSibling("next"); // dijit._Widget - }, + cancel: function(/*Boolean*/ focus){ + // summary: + // Revert to display mode, discarding any changes made in the editor + // tags: + // private - getIndexInParent: function(){ - // summary: - // Returns the index of this widget within its container parent. - // It returns -1 if the parent does not exist, or if the parent - // is not a dijit._Container + if(this.disabled || !this.editing){ return; } + this._set('editing', false); - var p = this.getParent(); - if(!p || !p.getIndexOfChild){ - return -1; // int - } - return p.getIndexOfChild(this); // int - } - } - ); + // tell the world that we have no changes + setTimeout(lang.hitch(this, "onCancel"), 0); // setTimeout prevents browser freeze for long-running event handlers -} + this._showText(focus); + }, + _setTextDirAttr: function(/*String*/ textDir){ + // summary: + // Setter for textDir. + // description: + // Users shouldn't call this function; they should be calling + // set('textDir', value) + // tags: + // private + if(!this._created || this.textDir != textDir){ + this._set("textDir", textDir); + this.applyTextDir(this.displayNode, this.displayNode.innerText); + this.displayNode.align = this.dir == "rtl" ? "right" : "left"; //fix the text alignment + } + } +}); -if(!dojo._hasResource["dijit.layout._LayoutWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout._LayoutWidget"] = true; -dojo.provide("dijit.layout._LayoutWidget"); +InlineEditBox._InlineEditor = InlineEditor; // for monkey patching +return InlineEditBox; +}); +}, +'dojo/selector/acme':function(){ +define("dojo/selector/acme", ["../_base/kernel", "../has", "../dom", "../_base/sniff", "../_base/array", "../_base/lang", "../_base/window"], function(dojo, has, dom){ + // module: + // dojo/selector/acme + // summary: + // This module defines the Acme selector engine +/* + acme architectural overview: + + acme is a relatively full-featured CSS3 query library. It is + designed to take any valid CSS3 selector and return the nodes matching + the selector. To do this quickly, it processes queries in several + steps, applying caching where profitable. + + The steps (roughly in reverse order of the way they appear in the code): + 1.) check to see if we already have a "query dispatcher" + - if so, use that with the given parameterization. Skip to step 4. + 2.) attempt to determine which branch to dispatch the query to: + - JS (optimized DOM iteration) + - native (FF3.1+, Safari 3.1+, IE 8+) + 3.) tokenize and convert to executable "query dispatcher" + - this is where the lion's share of the complexity in the + system lies. In the DOM version, the query dispatcher is + assembled as a chain of "yes/no" test functions pertaining to + a section of a simple query statement (".blah:nth-child(odd)" + but not "div div", which is 2 simple statements). Individual + statement dispatchers are cached (to prevent re-definition) + as are entire dispatch chains (to make re-execution of the + same query fast) + 4.) the resulting query dispatcher is called in the passed scope + (by default the top-level document) + - for DOM queries, this results in a recursive, top-down + evaluation of nodes based on each simple query section + - for native implementations, this may mean working around spec + bugs. So be it. + 5.) matched nodes are pruned to ensure they are unique (if necessary) +*/ + //////////////////////////////////////////////////////////////////////// + // Toolkit aliases + //////////////////////////////////////////////////////////////////////// -dojo.declare("dijit.layout._LayoutWidget", - [dijit._Widget, dijit._Container, dijit._Contained], - { - // summary: - // Base class for a _Container widget which is responsible for laying out its children. - // Widgets which mixin this code must define layout() to manage placement and sizing of the children. + // if you are extracting acme for use in your own system, you will + // need to provide these methods and properties. No other porting should be + // necessary, save for configuring the system to use a class other than + // dojo.NodeList as the return instance instantiator + var trim = dojo.trim; + var each = dojo.forEach; + // d.isIE; // float + // d.isSafari; // float + // d.isOpera; // float + // d.isWebKit; // float + // d.doc ; // document element - // baseClass: [protected extension] String - // This class name is applied to the widget's domNode - // and also may be used to generate names for sub nodes, - // for example dijitTabContainer-content. - baseClass: "dijitLayoutContainer", + var getDoc = function(){ return dojo.doc; }; + // NOTE(alex): the spec is idiotic. CSS queries should ALWAYS be case-sensitive, but nooooooo + var cssCaseBug = ((dojo.isWebKit||dojo.isMozilla) && ((getDoc().compatMode) == "BackCompat")); - // isLayoutContainer: [protected] Boolean - // Indicates that this widget is going to call resize() on its - // children widgets, setting their size, when they become visible. - isLayoutContainer: true, + //////////////////////////////////////////////////////////////////////// + // Global utilities + //////////////////////////////////////////////////////////////////////// - buildRendering: function(){ - this.inherited(arguments); - dojo.addClass(this.domNode, "dijitContainer"); - }, - startup: function(){ - // summary: - // Called after all the widgets have been instantiated and their - // dom nodes have been inserted somewhere under dojo.doc.body. - // - // Widgets should override this method to do any initialization - // dependent on other widgets existing, and then call - // this superclass method to finish things off. - // - // startup() in subclasses shouldn't do anything - // size related because the size of the widget hasn't been set yet. + var specials = ">~+"; - if(this._started){ return; } + // global thunk to determine whether we should treat the current query as + // case sensitive or not. This switch is flipped by the query evaluator + // based on the document passed as the context to search. + var caseSensitive = false; - // Need to call inherited first - so that child widgets get started - // up correctly - this.inherited(arguments); + // how high? + var yesman = function(){ return true; }; - // If I am a not being controlled by a parent layout widget... - var parent = this.getParent && this.getParent() - if(!(parent && parent.isLayoutContainer)){ - // Do recursive sizing and layout of all my descendants - // (passing in no argument to resize means that it has to glean the size itself) - this.resize(); + //////////////////////////////////////////////////////////////////////// + // Tokenizer + //////////////////////////////////////////////////////////////////////// - // Since my parent isn't a layout container, and my style *may be* width=height=100% - // or something similar (either set directly or via a CSS class), - // monitor when my size changes so that I can re-layout. - // For browsers where I can't directly monitor when my size changes, - // monitor when the viewport changes size, which *may* indicate a size change for me. - this.connect(dojo.isIE ? this.domNode : dojo.global, 'onresize', function(){ - // Using function(){} closure to ensure no arguments to resize. - this.resize(); - }); - } - }, + var getQueryParts = function(query){ + // summary: + // state machine for query tokenization + // description: + // instead of using a brittle and slow regex-based CSS parser, + // acme implements an AST-style query representation. This + // representation is only generated once per query. For example, + // the same query run multiple times or under different root nodes + // does not re-parse the selector expression but instead uses the + // cached data structure. The state machine implemented here + // terminates on the last " " (space) character and returns an + // ordered array of query component structures (or "parts"). Each + // part represents an operator or a simple CSS filtering + // expression. The structure for parts is documented in the code + // below. + + + // NOTE: + // this code is designed to run fast and compress well. Sacrifices + // to readability and maintainability have been made. Your best + // bet when hacking the tokenizer is to put The Donnas on *really* + // loud (may we recommend their "Spend The Night" release?) and + // just assume you're gonna make mistakes. Keep the unit tests + // open and run them frequently. Knowing is half the battle ;-) + if(specials.indexOf(query.slice(-1)) >= 0){ + // if we end with a ">", "+", or "~", that means we're implicitly + // searching all children, so make it explicit + query += " * " + }else{ + // if you have not provided a terminator, one will be provided for + // you... + query += " "; + } - resize: function(changeSize, resultSize){ - // summary: - // Call this to resize a widget, or after its size has changed. - // description: - // Change size mode: - // When changeSize is specified, changes the marginBox of this widget - // and forces it to relayout its contents accordingly. - // changeSize may specify height, width, or both. - // - // If resultSize is specified it indicates the size the widget will - // become after changeSize has been applied. - // - // Notification mode: - // When changeSize is null, indicates that the caller has already changed - // the size of the widget, or perhaps it changed because the browser - // window was resized. Tells widget to relayout its contents accordingly. - // - // If resultSize is also specified it indicates the size the widget has - // become. - // - // In either mode, this method also: - // 1. Sets this._borderBox and this._contentBox to the new size of - // the widget. Queries the current domNode size if necessary. - // 2. Calls layout() to resize contents (and maybe adjust child widgets). - // - // changeSize: Object? - // Sets the widget to this margin-box size and position. - // May include any/all of the following properties: - // | {w: int, h: int, l: int, t: int} - // - // resultSize: Object? - // The margin-box size of this widget after applying changeSize (if - // changeSize is specified). If caller knows this size and - // passes it in, we don't need to query the browser to get the size. - // | {w: int, h: int} + var ts = function(/*Integer*/ s, /*Integer*/ e){ + // trim and slice. - var node = this.domNode; + // take an index to start a string slice from and an end position + // and return a trimmed copy of that sub-string + return trim(query.slice(s, e)); + }; - // set margin box size, unless it wasn't specified, in which case use current size - if(changeSize){ - dojo.marginBox(node, changeSize); + // the overall data graph of the full query, as represented by queryPart objects + var queryParts = []; + + + // state keeping vars + var inBrackets = -1, inParens = -1, inMatchFor = -1, + inPseudo = -1, inClass = -1, inId = -1, inTag = -1, + lc = "", cc = "", pStart; + + // iteration vars + var x = 0, // index in the query + ql = query.length, + currentPart = null, // data structure representing the entire clause + _cp = null; // the current pseudo or attr matcher + + // several temporary variables are assigned to this structure during a + // potential sub-expression match: + // attr: + // a string representing the current full attribute match in a + // bracket expression + // type: + // if there's an operator in a bracket expression, this is + // used to keep track of it + // value: + // the internals of parenthetical expression for a pseudo. for + // :nth-child(2n+1), value might be "2n+1" + + var endTag = function(){ + // called when the tokenizer hits the end of a particular tag name. + // Re-sets state variables for tag matching and sets up the matcher + // to handle the next type of token (tag or operator). + if(inTag >= 0){ + var tv = (inTag == x) ? null : ts(inTag, x); // .toLowerCase(); + currentPart[ (specials.indexOf(tv) < 0) ? "tag" : "oper" ] = tv; + inTag = -1; + } + }; - // set offset of the node - if(changeSize.t){ node.style.top = changeSize.t + "px"; } - if(changeSize.l){ node.style.left = changeSize.l + "px"; } + var endId = function(){ + // called when the tokenizer might be at the end of an ID portion of a match + if(inId >= 0){ + currentPart.id = ts(inId, x).replace(/\\/g, ""); + inId = -1; } + }; - // If either height or width wasn't specified by the user, then query node for it. - // But note that setting the margin box and then immediately querying dimensions may return - // inaccurate results, so try not to depend on it. - var mb = resultSize || {}; - dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize - if( !("h" in mb) || !("w" in mb) ){ - mb = dojo.mixin(dojo.marginBox(node), mb); // just use dojo.marginBox() to fill in missing values + var endClass = function(){ + // called when the tokenizer might be at the end of a class name + // match. CSS allows for multiple classes, so we augment the + // current item with another class in its list + if(inClass >= 0){ + currentPart.classes.push(ts(inClass + 1, x).replace(/\\/g, "")); + inClass = -1; } + }; - // Compute and save the size of my border box and content box - // (w/out calling dojo.contentBox() since that may fail if size was recently set) - var cs = dojo.getComputedStyle(node); - var me = dojo._getMarginExtents(node, cs); - var be = dojo._getBorderExtents(node, cs); - var bb = (this._borderBox = { - w: mb.w - (me.w + be.w), - h: mb.h - (me.h + be.h) - }); - var pe = dojo._getPadExtents(node, cs); - this._contentBox = { - l: dojo._toPixelValue(node, cs.paddingLeft), - t: dojo._toPixelValue(node, cs.paddingTop), - w: bb.w - pe.w, - h: bb.h - pe.h - }; + var endAll = function(){ + // at the end of a simple fragment, so wall off the matches + endId(); + endTag(); + endClass(); + }; - // Callback for widget to adjust size of its children - this.layout(); - }, + var endPart = function(){ + endAll(); + if(inPseudo >= 0){ + currentPart.pseudos.push({ name: ts(inPseudo + 1, x) }); + } + // hint to the selector engine to tell it whether or not it + // needs to do any iteration. Many simple selectors don't, and + // we can avoid significant construction-time work by advising + // the system to skip them + currentPart.loops = ( + currentPart.pseudos.length || + currentPart.attrs.length || + currentPart.classes.length ); + + currentPart.oquery = currentPart.query = ts(pStart, x); // save the full expression as a string + + + // otag/tag are hints to suggest to the system whether or not + // it's an operator or a tag. We save a copy of otag since the + // tag name is cast to upper-case in regular HTML matches. The + // system has a global switch to figure out if the current + // expression needs to be case sensitive or not and it will use + // otag or tag accordingly + currentPart.otag = currentPart.tag = (currentPart["oper"]) ? null : (currentPart.tag || "*"); + + if(currentPart.tag){ + // if we're in a case-insensitive HTML doc, we likely want + // the toUpperCase when matching on element.tagName. If we + // do it here, we can skip the string op per node + // comparison + currentPart.tag = currentPart.tag.toUpperCase(); + } + + // add the part to the list + if(queryParts.length && (queryParts[queryParts.length-1].oper)){ + // operators are always infix, so we remove them from the + // list and attach them to the next match. The evaluator is + // responsible for sorting out how to handle them. + currentPart.infixOper = queryParts.pop(); + currentPart.query = currentPart.infixOper.query + " " + currentPart.query; + /* + console.debug( "swapping out the infix", + currentPart.infixOper, + "and attaching it to", + currentPart); + */ + } + queryParts.push(currentPart); + + currentPart = null; + }; - layout: function(){ - // summary: - // Widgets override this method to size and position their contents/children. - // When this is called this._contentBox is guaranteed to be set (see resize()). - // - // This is called after startup(), and also when the widget's size has been - // changed. - // tags: - // protected extension - }, + // iterate over the query, character by character, building up a + // list of query part objects + for(; lc=cc, cc=query.charAt(x), x < ql; x++){ + // cc: the current character in the match + // lc: the last character (if any) + + // someone is trying to escape something, so don't try to match any + // fragments. We assume we're inside a literal. + if(lc == "\\"){ continue; } + if(!currentPart){ // a part was just ended or none has yet been created + // NOTE: I hate all this alloc, but it's shorter than writing tons of if's + pStart = x; + // rules describe full CSS sub-expressions, like: + // #someId + // .className:first-child + // but not: + // thinger > div.howdy[type=thinger] + // the indidual components of the previous query would be + // split into 3 parts that would be represented a structure + // like: + // [ + // { + // query: "thinger", + // tag: "thinger", + // }, + // { + // query: "div.howdy[type=thinger]", + // classes: ["howdy"], + // infixOper: { + // query: ">", + // oper: ">", + // } + // }, + // ] + currentPart = { + query: null, // the full text of the part's rule + pseudos: [], // CSS supports multiple pseud-class matches in a single rule + attrs: [], // CSS supports multi-attribute match, so we need an array + classes: [], // class matches may be additive, e.g.: .thinger.blah.howdy + tag: null, // only one tag... + oper: null, // ...or operator per component. Note that these wind up being exclusive. + id: null, // the id component of a rule + getTag: function(){ + return (caseSensitive) ? this.otag : this.tag; + } + }; - _setupChild: function(/*dijit._Widget*/child){ - // summary: - // Common setup for initial children and children which are added after startup - // tags: - // protected extension + // if we don't have a part, we assume we're going to start at + // the beginning of a match, which should be a tag name. This + // might fault a little later on, but we detect that and this + // iteration will still be fine. + inTag = x; + } + + if(inBrackets >= 0){ + // look for a the close first + if(cc == "]"){ // if we're in a [...] clause and we end, do assignment + if(!_cp.attr){ + // no attribute match was previously begun, so we + // assume this is an attribute existence match in the + // form of [someAttributeName] + _cp.attr = ts(inBrackets+1, x); + }else{ + // we had an attribute already, so we know that we're + // matching some sort of value, as in [attrName=howdy] + _cp.matchFor = ts((inMatchFor||inBrackets+1), x); + } + var cmf = _cp.matchFor; + if(cmf){ + // try to strip quotes from the matchFor value. We want + // [attrName=howdy] to match the same + // as [attrName = 'howdy' ] + if( (cmf.charAt(0) == '"') || (cmf.charAt(0) == "'") ){ + _cp.matchFor = cmf.slice(1, -1); + } + } + // end the attribute by adding it to the list of attributes. + currentPart.attrs.push(_cp); + _cp = null; // necessary? + inBrackets = inMatchFor = -1; + }else if(cc == "="){ + // if the last char was an operator prefix, make sure we + // record it along with the "=" operator. + var addToCc = ("|~^$*".indexOf(lc) >=0 ) ? lc : ""; + _cp.type = addToCc+cc; + _cp.attr = ts(inBrackets+1, x-addToCc.length); + inMatchFor = x+1; + } + // now look for other clause parts + }else if(inParens >= 0){ + // if we're in a parenthetical expression, we need to figure + // out if it's attached to a pseudo-selector rule like + // :nth-child(1) + if(cc == ")"){ + if(inPseudo >= 0){ + _cp.value = ts(inParens+1, x); + } + inPseudo = inParens = -1; + } + }else if(cc == "#"){ + // start of an ID match + endAll(); + inId = x+1; + }else if(cc == "."){ + // start of a class match + endAll(); + inClass = x; + }else if(cc == ":"){ + // start of a pseudo-selector match + endAll(); + inPseudo = x; + }else if(cc == "["){ + // start of an attribute match. + endAll(); + inBrackets = x; + // provide a new structure for the attribute match to fill-in + _cp = { + /*===== + attr: null, type: null, matchFor: null + =====*/ + }; + }else if(cc == "("){ + // we really only care if we've entered a parenthetical + // expression if we're already inside a pseudo-selector match + if(inPseudo >= 0){ + // provide a new structure for the pseudo match to fill-in + _cp = { + name: ts(inPseudo+1, x), + value: null + }; + currentPart.pseudos.push(_cp); + } + inParens = x; + }else if( + (cc == " ") && + // if it's a space char and the last char is too, consume the + // current one without doing more work + (lc != cc) + ){ + endPart(); + } + } + return queryParts; + }; - var cls = this.baseClass + "-child " - + (child.baseClass ? this.baseClass + "-" + child.baseClass : ""); - dojo.addClass(child.domNode, cls); - }, - addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ - // Overrides _Container.addChild() to call _setupChild() - this.inherited(arguments); - if(this._started){ - this._setupChild(child); - } - }, + //////////////////////////////////////////////////////////////////////// + // DOM query infrastructure + //////////////////////////////////////////////////////////////////////// - removeChild: function(/*dijit._Widget*/ child){ - // Overrides _Container.removeChild() to remove class added by _setupChild() - var cls = this.baseClass + "-child" - + (child.baseClass ? - " " + this.baseClass + "-" + child.baseClass : ""); - dojo.removeClass(child.domNode, cls); - - this.inherited(arguments); - } - } -); + var agree = function(first, second){ + // the basic building block of the yes/no chaining system. agree(f1, + // f2) generates a new function which returns the boolean results of + // both of the passed functions to a single logical-anded result. If + // either are not passed, the other is used exclusively. + if(!first){ return second; } + if(!second){ return first; } -dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){ - // summary: - // Given the margin-box size of a node, return its content box size. - // Functions like dojo.contentBox() but is more reliable since it doesn't have - // to wait for the browser to compute sizes. - var cs = dojo.getComputedStyle(node); - var me = dojo._getMarginExtents(node, cs); - var pb = dojo._getPadBorderExtents(node, cs); - return { - l: dojo._toPixelValue(node, cs.paddingLeft), - t: dojo._toPixelValue(node, cs.paddingTop), - w: mb.w - (me.w + pb.w), - h: mb.h - (me.h + pb.h) + return function(){ + return first.apply(window, arguments) && second.apply(window, arguments); + } }; -}; -(function(){ - var capitalize = function(word){ - return word.substring(0,1).toUpperCase() + word.substring(1); + var getArr = function(i, arr){ + // helps us avoid array alloc when we don't need it + var r = arr||[]; // FIXME: should this be 'new d._NodeListCtor()' ? + if(i){ r.push(i); } + return r; }; - var size = function(widget, dim){ - // size the child - var newSize = widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim); + var _isElement = function(n){ return (1 == n.nodeType); }; - // record child's size - if(newSize){ - // if the child returned it's new size then use that - dojo.mixin(widget, newSize); - }else{ - // otherwise, call marginBox(), but favor our own numbers when we have them. - // the browser lies sometimes - dojo.mixin(widget, dojo.marginBox(widget.domNode)); - dojo.mixin(widget, dim); + // FIXME: need to coalesce _getAttr with defaultGetter + var blank = ""; + var _getAttr = function(elem, attr){ + if(!elem){ return blank; } + if(attr == "class"){ + return elem.className || blank; } + if(attr == "for"){ + return elem.htmlFor || blank; + } + if(attr == "style"){ + return elem.style.cssText || blank; + } + return (caseSensitive ? elem.getAttribute(attr) : elem.getAttribute(attr, 2)) || blank; }; - dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Widget[]*/ children, - /*String?*/ changedRegionId, /*Number?*/ changedRegionSize){ - // summary - // Layout a bunch of child dom nodes within a parent dom node - // container: - // parent node - // dim: - // {l, t, w, h} object specifying dimensions of container into which to place children - // children: - // an array of Widgets or at least objects containing: - // * domNode: pointer to DOM node to position - // * region or layoutAlign: position to place DOM node - // * resize(): (optional) method to set size of node - // * id: (optional) Id of widgets, referenced from resize object, below. - // changedRegionId: - // If specified, the slider for the region with the specified id has been dragged, and thus - // the region's height or width should be adjusted according to changedRegionSize - // changedRegionSize: - // See changedRegionId. - - // copy dim because we are going to modify it - dim = dojo.mixin({}, dim); + var attrs = { + "*=": function(attr, value){ + return function(elem){ + // E[foo*="bar"] + // an E element whose "foo" attribute value contains + // the substring "bar" + return (_getAttr(elem, attr).indexOf(value)>=0); + } + }, + "^=": function(attr, value){ + // E[foo^="bar"] + // an E element whose "foo" attribute value begins exactly + // with the string "bar" + return function(elem){ + return (_getAttr(elem, attr).indexOf(value)==0); + } + }, + "$=": function(attr, value){ + // E[foo$="bar"] + // an E element whose "foo" attribute value ends exactly + // with the string "bar" + return function(elem){ + var ea = " "+_getAttr(elem, attr); + return (ea.lastIndexOf(value)==(ea.length-value.length)); + } + }, + "~=": function(attr, value){ + // E[foo~="bar"] + // an E element whose "foo" attribute value is a list of + // space-separated values, one of which is exactly equal + // to "bar" + + // return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]"; + var tval = " "+value+" "; + return function(elem){ + var ea = " "+_getAttr(elem, attr)+" "; + return (ea.indexOf(tval)>=0); + } + }, + "|=": function(attr, value){ + // E[hreflang|="en"] + // an E element whose "hreflang" attribute has a + // hyphen-separated list of values beginning (from the + // left) with "en" + var valueDash = value+"-"; + return function(elem){ + var ea = _getAttr(elem, attr); + return ( + (ea == value) || + (ea.indexOf(valueDash)==0) + ); + } + }, + "=": function(attr, value){ + return function(elem){ + return (_getAttr(elem, attr) == value); + } + } + }; - dojo.addClass(container, "dijitLayoutContainer"); + // avoid testing for node type if we can. Defining this in the negative + // here to avoid negation in the fast path. + var _noNES = (typeof getDoc().firstChild.nextElementSibling == "undefined"); + var _ns = !_noNES ? "nextElementSibling" : "nextSibling"; + var _ps = !_noNES ? "previousElementSibling" : "previousSibling"; + var _simpleNodeTest = (_noNES ? _isElement : yesman); - // Move "client" elements to the end of the array for layout. a11y dictates that the author - // needs to be able to put them in the document in tab-order, but this algorithm requires that - // client be last. TODO: move these lines to LayoutContainer? Unneeded other places I think. - children = dojo.filter(children, function(item){ return item.region != "center" && item.layoutAlign != "client"; }) - .concat(dojo.filter(children, function(item){ return item.region == "center" || item.layoutAlign == "client"; })); + var _lookLeft = function(node){ + // look left + while(node = node[_ps]){ + if(_simpleNodeTest(node)){ return false; } + } + return true; + }; - // set positions/sizes - dojo.forEach(children, function(child){ - var elm = child.domNode, - pos = (child.region || child.layoutAlign); + var _lookRight = function(node){ + // look right + while(node = node[_ns]){ + if(_simpleNodeTest(node)){ return false; } + } + return true; + }; - // set elem to upper left corner of unused space; may move it later - var elmStyle = elm.style; - elmStyle.left = dim.l+"px"; - elmStyle.top = dim.t+"px"; - elmStyle.position = "absolute"; + var getNodeIndex = function(node){ + var root = node.parentNode; + var i = 0, + tret = root.children || root.childNodes, + ci = (node["_i"]||-1), + cl = (root["_l"]||-1); + + if(!tret){ return -1; } + var l = tret.length; + + // we calculate the parent length as a cheap way to invalidate the + // cache. It's not 100% accurate, but it's much more honest than what + // other libraries do + if( cl == l && ci >= 0 && cl >= 0 ){ + // if it's legit, tag and release + return ci; + } + + // else re-key things + root["_l"] = l; + ci = -1; + for(var te = root["firstElementChild"]||root["firstChild"]; te; te = te[_ns]){ + if(_simpleNodeTest(te)){ + te["_i"] = ++i; + if(node === te){ + // NOTE: + // shortcutting the return at this step in indexing works + // very well for benchmarking but we avoid it here since + // it leads to potential O(n^2) behavior in sequential + // getNodexIndex operations on a previously un-indexed + // parent. We may revisit this at a later time, but for + // now we just want to get the right answer more often + // than not. + ci = i; + } + } + } + return ci; + }; - dojo.addClass(elm, "dijitAlign" + capitalize(pos)); + var isEven = function(elem){ + return !((getNodeIndex(elem)) % 2); + }; - // Size adjustments to make to this child widget - var sizeSetting = {}; + var isOdd = function(elem){ + return ((getNodeIndex(elem)) % 2); + }; - // Check for optional size adjustment due to splitter drag (height adjustment for top/bottom align - // panes and width adjustment for left/right align panes. - if(changedRegionId && changedRegionId == child.id){ - sizeSetting[child.region == "top" || child.region == "bottom" ? "h" : "w"] = changedRegionSize; + var pseudos = { + "checked": function(name, condition){ + return function(elem){ + return !!("checked" in elem ? elem.checked : elem.selected); } - - // set size && adjust record of remaining space. - // note that setting the width of a <div> may affect its height. - if(pos == "top" || pos == "bottom"){ - sizeSetting.w = dim.w; - size(child, sizeSetting); - dim.h -= child.h; - if(pos == "top"){ - dim.t += child.h; - }else{ - elmStyle.top = dim.t + dim.h + "px"; + }, + "first-child": function(){ return _lookLeft; }, + "last-child": function(){ return _lookRight; }, + "only-child": function(name, condition){ + return function(node){ + return _lookLeft(node) && _lookRight(node); + }; + }, + "empty": function(name, condition){ + return function(elem){ + // DomQuery and jQuery get this wrong, oddly enough. + // The CSS 3 selectors spec is pretty explicit about it, too. + var cn = elem.childNodes; + var cnl = elem.childNodes.length; + // if(!cnl){ return true; } + for(var x=cnl-1; x >= 0; x--){ + var nt = cn[x].nodeType; + if((nt === 1)||(nt == 3)){ return false; } } - }else if(pos == "left" || pos == "right"){ - sizeSetting.h = dim.h; - size(child, sizeSetting); - dim.w -= child.w; - if(pos == "left"){ - dim.l += child.w; + return true; + } + }, + "contains": function(name, condition){ + var cz = condition.charAt(0); + if( cz == '"' || cz == "'" ){ //remove quote + condition = condition.slice(1, -1); + } + return function(elem){ + return (elem.innerHTML.indexOf(condition) >= 0); + } + }, + "not": function(name, condition){ + var p = getQueryParts(condition)[0]; + var ignores = { el: 1 }; + if(p.tag != "*"){ + ignores.tag = 1; + } + if(!p.classes.length){ + ignores.classes = 1; + } + var ntf = getSimpleFilterFunc(p, ignores); + return function(elem){ + return (!ntf(elem)); + } + }, + "nth-child": function(name, condition){ + var pi = parseInt; + // avoid re-defining function objects if we can + if(condition == "odd"){ + return isOdd; + }else if(condition == "even"){ + return isEven; + } + // FIXME: can we shorten this? + if(condition.indexOf("n") != -1){ + var tparts = condition.split("n", 2); + var pred = tparts[0] ? ((tparts[0] == '-') ? -1 : pi(tparts[0])) : 1; + var idx = tparts[1] ? pi(tparts[1]) : 0; + var lb = 0, ub = -1; + if(pred > 0){ + if(idx < 0){ + idx = (idx % pred) && (pred + (idx % pred)); + }else if(idx>0){ + if(idx >= pred){ + lb = idx - idx % pred; + } + idx = idx % pred; + } + }else if(pred<0){ + pred *= -1; + // idx has to be greater than 0 when pred is negative; + // shall we throw an error here? + if(idx > 0){ + ub = idx; + idx = idx % pred; + } + } + if(pred > 0){ + return function(elem){ + var i = getNodeIndex(elem); + return (i>=lb) && (ub<0 || i<=ub) && ((i % pred) == idx); + } }else{ - elmStyle.left = dim.l + dim.w + "px"; + condition = idx; } - }else if(pos == "client" || pos == "center"){ - size(child, dim); } - }); + var ncount = pi(condition); + return function(elem){ + return (getNodeIndex(elem) == ncount); + } + } }; -})(); + var defaultGetter = (dojo.isIE && (dojo.isIE < 9 || dojo.isQuirks)) ? function(cond){ + var clc = cond.toLowerCase(); + if(clc == "class"){ cond = "className"; } + return function(elem){ + return (caseSensitive ? elem.getAttribute(cond) : elem[cond]||elem[clc]); + } + } : function(cond){ + return function(elem){ + return (elem && elem.getAttribute && elem.hasAttribute(cond)); + } + }; -} + var getSimpleFilterFunc = function(query, ignores){ + // generates a node tester function based on the passed query part. The + // query part is one of the structures generated by the query parser + // when it creates the query AST. The "ignores" object specifies which + // (if any) tests to skip, allowing the system to avoid duplicating + // work where it may have already been taken into account by other + // factors such as how the nodes to test were fetched in the first + // place + if(!query){ return yesman; } + ignores = ignores||{}; -if(!dojo._hasResource["dijit._CssStateMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._CssStateMixin"] = true; -dojo.provide("dijit._CssStateMixin"); + var ff = null; + if(!("el" in ignores)){ + ff = agree(ff, _isElement); + } -dojo.declare("dijit._CssStateMixin", [], { - // summary: - // Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus - // state changes, and also higher-level state changes such becoming disabled or selected. - // - // description: - // By mixing this class into your widget, and setting the this.baseClass attribute, it will automatically - // maintain CSS classes on the widget root node (this.domNode) depending on hover, - // active, focus, etc. state. Ex: with a baseClass of dijitButton, it will apply the classes - // dijitButtonHovered and dijitButtonActive, as the user moves the mouse over the widget and clicks it. - // - // It also sets CSS like dijitButtonDisabled based on widget semantic state. - // - // By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons - // within the widget). + if(!("tag" in ignores)){ + if(query.tag != "*"){ + ff = agree(ff, function(elem){ + return (elem && (elem.tagName == query.getTag())); + }); + } + } - // cssStateNodes: [protected] Object - // List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus - //. - // Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names - // (like "dijitUpArrowButton"). Example: - // | { - // | "upArrowButton": "dijitUpArrowButton", - // | "downArrowButton": "dijitDownArrowButton" - // | } - // The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it - // is hovered, etc. - cssStateNodes: {}, + if(!("classes" in ignores)){ + each(query.classes, function(cname, idx, arr){ + // get the class name + /* + var isWildcard = cname.charAt(cname.length-1) == "*"; + if(isWildcard){ + cname = cname.substr(0, cname.length-1); + } + // I dislike the regex thing, even if memoized in a cache, but it's VERY short + var re = new RegExp("(?:^|\\s)" + cname + (isWildcard ? ".*" : "") + "(?:\\s|$)"); + */ + var re = new RegExp("(?:^|\\s)" + cname + "(?:\\s|$)"); + ff = agree(ff, function(elem){ + return re.test(elem.className); + }); + ff.count = idx; + }); + } - // hovering: [readonly] Boolean - // True if cursor is over this widget - hovering: false, - - // active: [readonly] Boolean - // True if mouse was pressed while over this widget, and hasn't been released yet - active: false, + if(!("pseudos" in ignores)){ + each(query.pseudos, function(pseudo){ + var pn = pseudo.name; + if(pseudos[pn]){ + ff = agree(ff, pseudos[pn](pn, pseudo.value)); + } + }); + } - _applyAttributes: function(){ - // This code would typically be in postCreate(), but putting in _applyAttributes() for - // performance: so the class changes happen before DOM is inserted into the document. - // Change back to postCreate() in 2.0. See #11635. + if(!("attrs" in ignores)){ + each(query.attrs, function(attr){ + var matcher; + var a = attr.attr; + // type, attr, matchFor + if(attr.type && attrs[attr.type]){ + matcher = attrs[attr.type](a, attr.matchFor); + }else if(a.length){ + matcher = defaultGetter(a); + } + if(matcher){ + ff = agree(ff, matcher); + } + }); + } - this.inherited(arguments); + if(!("id" in ignores)){ + if(query.id){ + ff = agree(ff, function(elem){ + return (!!elem && (elem.id == query.id)); + }); + } + } - // Automatically monitor mouse events (essentially :hover and :active) on this.domNode - dojo.forEach(["onmouseenter", "onmouseleave", "onmousedown"], function(e){ - this.connect(this.domNode, e, "_cssMouseEvent"); - }, this); - - // Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node - dojo.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active"], function(attr){ - this.watch(attr, dojo.hitch(this, "_setStateClass")); - }, this); + if(!ff){ + if(!("default" in ignores)){ + ff = yesman; + } + } + return ff; + }; - // Events on sub nodes within the widget - for(var ap in this.cssStateNodes){ - this._trackMouseState(this[ap], this.cssStateNodes[ap]); + var _nextSibling = function(filterFunc){ + return function(node, ret, bag){ + while(node = node[_ns]){ + if(_noNES && (!_isElement(node))){ continue; } + if( + (!bag || _isUnique(node, bag)) && + filterFunc(node) + ){ + ret.push(node); + } + break; + } + return ret; } - // Set state initially; there's probably no hover/active/focus state but widget might be - // disabled/readonly/checked/selected so we want to set CSS classes for those conditions. - this._setStateClass(); - }, + }; - _cssMouseEvent: function(/*Event*/ event){ - // summary: - // Sets hovering and active properties depending on mouse state, - // which triggers _setStateClass() to set appropriate CSS classes for this.domNode. + var _nextSiblings = function(filterFunc){ + return function(root, ret, bag){ + var te = root[_ns]; + while(te){ + if(_simpleNodeTest(te)){ + if(bag && !_isUnique(te, bag)){ + break; + } + if(filterFunc(te)){ + ret.push(te); + } + } + te = te[_ns]; + } + return ret; + } + }; - if(!this.disabled){ - switch(event.type){ - case "mouseenter": - case "mouseover": // generated on non-IE browsers even though we connected to mouseenter - this._set("hovering", true); - this._set("active", this._mouseDown); - break; + // get an array of child *elements*, skipping text and comment nodes + var _childElements = function(filterFunc){ + filterFunc = filterFunc||yesman; + return function(root, ret, bag){ + // get an array of child elements, skipping text and comment nodes + var te, x = 0, tret = root.children || root.childNodes; + while(te = tret[x++]){ + if( + _simpleNodeTest(te) && + (!bag || _isUnique(te, bag)) && + (filterFunc(te, x)) + ){ + ret.push(te); + } + } + return ret; + }; + }; - case "mouseleave": - case "mouseout": // generated on non-IE browsers even though we connected to mouseleave - this._set("hovering", false); - this._set("active", false); - break; + /* + // thanks, Dean! + var itemIsAfterRoot = d.isIE ? function(item, root){ + return (item.sourceIndex > root.sourceIndex); + } : function(item, root){ + return (item.compareDocumentPosition(root) == 2); + }; + */ - case "mousedown" : - this._set("active", true); - this._mouseDown = true; - // Set a global event to handle mouseup, so it fires properly - // even if the cursor leaves this.domNode before the mouse up event. - // Alternately could set active=false on mouseout. - var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){ - this._mouseDown = false; - this._set("active", false); - this.disconnect(mouseUpConnector); - }); - break; + // test to see if node is below root + var _isDescendant = function(node, root){ + var pn = node.parentNode; + while(pn){ + if(pn == root){ + break; } + pn = pn.parentNode; } - }, + return !!pn; + }; - _setStateClass: function(){ - // summary: - // Update the visual state of the widget by setting the css classes on this.domNode - // (or this.stateNode if defined) by combining this.baseClass with - // various suffixes that represent the current widget state(s). + var _getElementsFuncCache = {}; + + var getElementsFunc = function(query){ + var retFunc = _getElementsFuncCache[query.query]; + // if we've got a cached dispatcher, just use that + if(retFunc){ return retFunc; } + // else, generate a new on + + // NOTE: + // this function returns a function that searches for nodes and + // filters them. The search may be specialized by infix operators + // (">", "~", or "+") else it will default to searching all + // descendants (the " " selector). Once a group of children is + // found, a test function is applied to weed out the ones we + // don't want. Many common cases can be fast-pathed. We spend a + // lot of cycles to create a dispatcher that doesn't do more work + // than necessary at any point since, unlike this function, the + // dispatchers will be called every time. The logic of generating + // efficient dispatchers looks like this in pseudo code: // - // description: - // In the case where a widget has multiple - // states, it sets the class based on all possible - // combinations. For example, an invalid form widget that is being hovered - // will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover". + // # if it's a purely descendant query (no ">", "+", or "~" modifiers) + // if infixOperator == " ": + // if only(id): + // return def(root): + // return d.byId(id, root); // - // The widget may have one or more of the following states, determined - // by this.state, this.checked, this.valid, and this.selected: - // - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid - // - Incomplete - ValidationTextBox sets this.state to "Incomplete" if the current input value is not finished yet - // - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true - // - Selected - ex: currently selected tab will have this.selected==true + // elif id: + // return def(root): + // return filter(d.byId(id, root)); // - // In addition, it may have one or more of the following states, - // based on this.disabled and flags set in _onMouse (this.active, this.hovering) and from focus manager (this.focused): - // - Disabled - if the widget is disabled - // - Active - if the mouse (or space/enter key?) is being pressed down - // - Focused - if the widget has focus - // - Hover - if the mouse is over the widget + // elif cssClass && getElementsByClassName: + // return def(root): + // return filter(root.getElementsByClassName(cssClass)); + // + // elif only(tag): + // return def(root): + // return root.getElementsByTagName(tagName); + // + // else: + // # search by tag name, then filter + // return def(root): + // return filter(root.getElementsByTagName(tagName||"*")); + // + // elif infixOperator == ">": + // # search direct children + // return def(root): + // return filter(root.children); + // + // elif infixOperator == "+": + // # search next sibling + // return def(root): + // return filter(root.nextElementSibling); + // + // elif infixOperator == "~": + // # search rightward siblings + // return def(root): + // return filter(nextSiblings(root)); + + var io = query.infixOper; + var oper = (io ? io.oper : ""); + // the default filter func which tests for all conditions in the query + // part. This is potentially inefficient, so some optimized paths may + // re-define it to test fewer things. + var filterFunc = getSimpleFilterFunc(query, { el: 1 }); + var qt = query.tag; + var wildcardTag = ("*" == qt); + var ecs = getDoc()["getElementsByClassName"]; + + if(!oper){ + // if there's no infix operator, then it's a descendant query. ID + // and "elements by class name" variants can be accelerated so we + // call them out explicitly: + if(query.id){ + // testing shows that the overhead of yesman() is acceptable + // and can save us some bytes vs. re-defining the function + // everywhere. + filterFunc = (!query.loops && wildcardTag) ? + yesman : + getSimpleFilterFunc(query, { el: 1, id: 1 }); + + retFunc = function(root, arr){ + var te = dom.byId(query.id, (root.ownerDocument||root)); + if(!te || !filterFunc(te)){ return; } + if(9 == root.nodeType){ // if root's a doc, we just return directly + return getArr(te, arr); + }else{ // otherwise check ancestry + if(_isDescendant(te, root)){ + return getArr(te, arr); + } + } + } + }else if( + ecs && + // isAlien check. Workaround for Prototype.js being totally evil/dumb. + /\{\s*\[native code\]\s*\}/.test(String(ecs)) && + query.classes.length && + !cssCaseBug + ){ + // it's a class-based query and we've got a fast way to run it. + + // ignore class and ID filters since we will have handled both + filterFunc = getSimpleFilterFunc(query, { el: 1, classes: 1, id: 1 }); + var classesString = query.classes.join(" "); + retFunc = function(root, arr, bag){ + var ret = getArr(0, arr), te, x=0; + var tret = root.getElementsByClassName(classesString); + while((te = tret[x++])){ + if(filterFunc(te, root) && _isUnique(te, bag)){ + ret.push(te); + } + } + return ret; + }; - // Compute new set of classes - var newStateClasses = this.baseClass.split(" "); + }else if(!wildcardTag && !query.loops){ + // it's tag only. Fast-path it. + retFunc = function(root, arr, bag){ + var ret = getArr(0, arr), te, x=0; + var tret = root.getElementsByTagName(query.getTag()); + while((te = tret[x++])){ + if(_isUnique(te, bag)){ + ret.push(te); + } + } + return ret; + }; + }else{ + // the common case: + // a descendant selector without a fast path. By now it's got + // to have a tag selector, even if it's just "*" so we query + // by that and filter + filterFunc = getSimpleFilterFunc(query, { el: 1, tag: 1, id: 1 }); + retFunc = function(root, arr, bag){ + var ret = getArr(0, arr), te, x=0; + // we use getTag() to avoid case sensitivity issues + var tret = root.getElementsByTagName(query.getTag()); + while((te = tret[x++])){ + if(filterFunc(te, root) && _isUnique(te, bag)){ + ret.push(te); + } + } + return ret; + }; + } + }else{ + // the query is scoped in some way. Instead of querying by tag we + // use some other collection to find candidate nodes + var skipFilters = { el: 1 }; + if(wildcardTag){ + skipFilters.tag = 1; + } + filterFunc = getSimpleFilterFunc(query, skipFilters); + if("+" == oper){ + retFunc = _nextSibling(filterFunc); + }else if("~" == oper){ + retFunc = _nextSiblings(filterFunc); + }else if(">" == oper){ + retFunc = _childElements(filterFunc); + } + } + // cache it and return + return _getElementsFuncCache[query.query] = retFunc; + }; - function multiply(modifier){ - newStateClasses = newStateClasses.concat(dojo.map(newStateClasses, function(c){ return c+modifier; }), "dijit"+modifier); + var filterDown = function(root, queryParts){ + // NOTE: + // this is the guts of the DOM query system. It takes a list of + // parsed query parts and a root and finds children which match + // the selector represented by the parts + var candidates = getArr(root), qp, x, te, qpl = queryParts.length, bag, ret; + + for(var i = 0; i < qpl; i++){ + ret = []; + qp = queryParts[i]; + x = candidates.length - 1; + if(x > 0){ + // if we have more than one root at this level, provide a new + // hash to use for checking group membership but tell the + // system not to post-filter us since we will already have been + // gauranteed to be unique + bag = {}; + ret.nozip = true; + } + var gef = getElementsFunc(qp); + for(var j = 0; (te = candidates[j]); j++){ + // for every root, get the elements that match the descendant + // selector, adding them to the "ret" array and filtering them + // via membership in this level's bag. If there are more query + // parts, then this level's return will be used as the next + // level's candidates + gef(te, ret, bag); + } + if(!ret.length){ break; } + candidates = ret; } + return ret; + }; - if(!this.isLeftToRight()){ - // For RTL mode we need to set an addition class like dijitTextBoxRtl. - multiply("Rtl"); - } + //////////////////////////////////////////////////////////////////////// + // the query runner + //////////////////////////////////////////////////////////////////////// - if(this.checked){ - multiply("Checked"); - } - if(this.state){ - multiply(this.state); - } - if(this.selected){ - multiply("Selected"); - } + // these are the primary caches for full-query results. The query + // dispatcher functions are generated then stored here for hash lookup in + // the future + var _queryFuncCacheDOM = {}, + _queryFuncCacheQSA = {}; - if(this.disabled){ - multiply("Disabled"); - }else if(this.readOnly){ - multiply("ReadOnly"); - }else{ - if(this.active){ - multiply("Active"); - }else if(this.hovering){ - multiply("Hover"); + // this is the second level of spliting, from full-length queries (e.g., + // "div.foo .bar") into simple query expressions (e.g., ["div.foo", + // ".bar"]) + var getStepQueryFunc = function(query){ + var qparts = getQueryParts(trim(query)); + + // if it's trivial, avoid iteration and zipping costs + if(qparts.length == 1){ + // we optimize this case here to prevent dispatch further down the + // chain, potentially slowing things down. We could more elegantly + // handle this in filterDown(), but it's slower for simple things + // that need to be fast (e.g., "#someId"). + var tef = getElementsFunc(qparts[0]); + return function(root){ + var r = tef(root, []); + if(r){ r.nozip = true; } + return r; } } - if(this._focused){ - multiply("Focused"); + // otherwise, break it up and return a runner that iterates over the parts recursively + return function(root){ + return filterDown(root, qparts); } + }; - // Remove old state classes and add new ones. - // For performance concerns we only write into domNode.className once. - var tn = this.stateNode || this.domNode, - classHash = {}; // set of all classes (state and otherwise) for node + // NOTES: + // * we can't trust QSA for anything but document-rooted queries, so + // caching is split into DOM query evaluators and QSA query evaluators + // * caching query results is dirty and leak-prone (or, at a minimum, + // prone to unbounded growth). Other toolkits may go this route, but + // they totally destroy their own ability to manage their memory + // footprint. If we implement it, it should only ever be with a fixed + // total element reference # limit and an LRU-style algorithm since JS + // has no weakref support. Caching compiled query evaluators is also + // potentially problematic, but even on large documents the size of the + // query evaluators is often < 100 function objects per evaluator (and + // LRU can be applied if it's ever shown to be an issue). + // * since IE's QSA support is currently only for HTML documents and even + // then only in IE 8's "standards mode", we have to detect our dispatch + // route at query time and keep 2 separate caches. Ugg. + + // we need to determine if we think we can run a given query via + // querySelectorAll or if we'll need to fall back on DOM queries to get + // there. We need a lot of information about the environment and the query + // to make the determiniation (e.g. does it support QSA, does the query in + // question work in the native QSA impl, etc.). + var nua = navigator.userAgent; + // some versions of Safari provided QSA, but it was buggy and crash-prone. + // We need te detect the right "internal" webkit version to make this work. + var wk = "WebKit/"; + var is525 = ( + dojo.isWebKit && + (nua.indexOf(wk) > 0) && + (parseFloat(nua.split(wk)[1]) > 528) + ); - dojo.forEach(tn.className.split(" "), function(c){ classHash[c] = true; }); + // IE QSA queries may incorrectly include comment nodes, so we throw the + // zipping function into "remove" comments mode instead of the normal "skip + // it" which every other QSA-clued browser enjoys + var noZip = dojo.isIE ? "commentStrip" : "nozip"; - if("_stateClasses" in this){ - dojo.forEach(this._stateClasses, function(c){ delete classHash[c]; }); - } + var qsa = "querySelectorAll"; + var qsaAvail = ( + !!getDoc()[qsa] && + // see #5832 + (!dojo.isSafari || (dojo.isSafari > 3.1) || is525 ) + ); - dojo.forEach(newStateClasses, function(c){ classHash[c] = true; }); + //Don't bother with n+3 type of matches, IE complains if we modify those. + var infixSpaceRe = /n\+\d|([^ ])?([>~+])([^ =])?/g; + var infixSpaceFunc = function(match, pre, ch, post){ + return ch ? (pre ? pre + " " : "") + ch + (post ? " " + post : "") : /*n+3*/ match; + }; - var newClasses = []; - for(var c in classHash){ - newClasses.push(c); + var getQueryFunc = function(query, forceDOM){ + //Normalize query. The CSS3 selectors spec allows for omitting spaces around + //infix operators, >, ~ and + + //Do the work here since detection for spaces is used as a simple "not use QSA" + //test below. + query = query.replace(infixSpaceRe, infixSpaceFunc); + + if(qsaAvail){ + // if we've got a cached variant and we think we can do it, run it! + var qsaCached = _queryFuncCacheQSA[query]; + if(qsaCached && !forceDOM){ return qsaCached; } } - tn.className = newClasses.join(" "); - this._stateClasses = newStateClasses; - }, + // else if we've got a DOM cached variant, assume that we already know + // all we need to and use it + var domCached = _queryFuncCacheDOM[query]; + if(domCached){ return domCached; } - _trackMouseState: function(/*DomNode*/ node, /*String*/ clazz){ - // summary: - // Track mouse/focus events on specified node and set CSS class on that node to indicate - // current state. Usually not called directly, but via cssStateNodes attribute. - // description: - // Given class=foo, will set the following CSS class on the node - // - fooActive: if the user is currently pressing down the mouse button while over the node - // - fooHover: if the user is hovering the mouse over the node, but not pressing down a button - // - fooFocus: if the node is focused - // - // Note that it won't set any classes if the widget is disabled. - // node: DomNode - // Should be a sub-node of the widget, not the top node (this.domNode), since the top node - // is handled specially and automatically just by mixing in this class. - // clazz: String - // CSS class name (ex: dijitSliderUpArrow). + // TODO: + // today we're caching DOM and QSA branches separately so we + // recalc useQSA every time. If we had a way to tag root+query + // efficiently, we'd be in good shape to do a global cache. + + var qcz = query.charAt(0); + var nospace = (-1 == query.indexOf(" ")); + + // byId searches are wicked fast compared to QSA, even when filtering + // is required + if( (query.indexOf("#") >= 0) && (nospace) ){ + forceDOM = true; + } + + var useQSA = ( + qsaAvail && (!forceDOM) && + // as per CSS 3, we can't currently start w/ combinator: + // http://www.w3.org/TR/css3-selectors/#w3cselgrammar + (specials.indexOf(qcz) == -1) && + // IE's QSA impl sucks on pseudos + (!dojo.isIE || (query.indexOf(":") == -1)) && + + (!(cssCaseBug && (query.indexOf(".") >= 0))) && + + // FIXME: + // need to tighten up browser rules on ":contains" and "|=" to + // figure out which aren't good + // Latest webkit (around 531.21.8) does not seem to do well with :checked on option + // elements, even though according to spec, selected options should + // match :checked. So go nonQSA for it: + // http://bugs.dojotoolkit.org/ticket/5179 + (query.indexOf(":contains") == -1) && (query.indexOf(":checked") == -1) && + (query.indexOf("|=") == -1) // some browsers don't grok it + ); - // Current state of node (initially false) - // NB: setting specifically to false because dojo.toggleClass() needs true boolean as third arg - var hovering=false, active=false, focused=false; + // TODO: + // if we've got a descendant query (e.g., "> .thinger" instead of + // just ".thinger") in a QSA-able doc, but are passed a child as a + // root, it should be possible to give the item a synthetic ID and + // trivially rewrite the query to the form "#synid > .thinger" to + // use the QSA branch - var self = this, - cn = dojo.hitch(this, "connect", node); - function setClass(){ - var disabled = ("disabled" in self && self.disabled) || ("readonly" in self && self.readonly); - dojo.toggleClass(node, clazz+"Hover", hovering && !active && !disabled); - dojo.toggleClass(node, clazz+"Active", active && !disabled); - dojo.toggleClass(node, clazz+"Focused", focused && !disabled); + if(useQSA){ + var tq = (specials.indexOf(query.charAt(query.length-1)) >= 0) ? + (query + " *") : query; + return _queryFuncCacheQSA[query] = function(root){ + try{ + // the QSA system contains an egregious spec bug which + // limits us, effectively, to only running QSA queries over + // entire documents. See: + // http://ejohn.org/blog/thoughts-on-queryselectorall/ + // despite this, we can also handle QSA runs on simple + // selectors, but we don't want detection to be expensive + // so we're just checking for the presence of a space char + // right now. Not elegant, but it's cheaper than running + // the query parser when we might not need to + if(!((9 == root.nodeType) || nospace)){ throw ""; } + var r = root[qsa](tq); + // skip expensive duplication checks and just wrap in a NodeList + r[noZip] = true; + return r; + }catch(e){ + // else run the DOM branch on this query, ensuring that we + // default that way in the future + return getQueryFunc(query, true)(root); + } + } + }else{ + // DOM branch + var parts = query.split(/\s*,\s*/); + return _queryFuncCacheDOM[query] = ((parts.length < 2) ? + // if not a compound query (e.g., ".foo, .bar"), cache and return a dispatcher + getStepQueryFunc(query) : + // if it *is* a complex query, break it up into its + // constituent parts and return a dispatcher that will + // merge the parts when run + function(root){ + var pindex = 0, // avoid array alloc for every invocation + ret = [], + tp; + while((tp = parts[pindex++])){ + ret = ret.concat(getStepQueryFunc(tp)(root)); + } + return ret; + } + ); } + }; - // Mouse - cn("onmouseenter", function(){ - hovering = true; - setClass(); - }); - cn("onmouseleave", function(){ - hovering = false; - active = false; - setClass(); - }); - cn("onmousedown", function(){ - active = true; - setClass(); - }); - cn("onmouseup", function(){ - active = false; - setClass(); - }); + var _zipIdx = 0; - // Focus - cn("onfocus", function(){ - focused = true; - setClass(); - }); - cn("onblur", function(){ - focused = false; - setClass(); - }); + // NOTE: + // this function is Moo inspired, but our own impl to deal correctly + // with XML in IE + var _nodeUID = dojo.isIE ? function(node){ + if(caseSensitive){ + // XML docs don't have uniqueID on their nodes + return (node.getAttribute("_uid") || node.setAttribute("_uid", ++_zipIdx) || _zipIdx); - // Just in case widget is enabled/disabled while it has focus/hover/active state. - // Maybe this is overkill. - this.watch("disabled", setClass); - this.watch("readOnly", setClass); - } -}); + }else{ + return node.uniqueID; + } + } : + function(node){ + return (node._uid || (node._uid = ++_zipIdx)); + }; -} + // determine if a node in is unique in a "bag". In this case we don't want + // to flatten a list of unique items, but rather just tell if the item in + // question is already in the bag. Normally we'd just use hash lookup to do + // this for us but IE's DOM is busted so we can't really count on that. On + // the upside, it gives us a built in unique ID function. + var _isUnique = function(node, bag){ + if(!bag){ return 1; } + var id = _nodeUID(node); + if(!bag[id]){ return bag[id] = 1; } + return 0; + }; + + // attempt to efficiently determine if an item in a list is a dupe, + // returning a list of "uniques", hopefully in doucment order + var _zipIdxName = "_zipIdx"; + var _zip = function(arr){ + if(arr && arr.nozip){ + return arr; + } + var ret = []; + if(!arr || !arr.length){ return ret; } + if(arr[0]){ + ret.push(arr[0]); + } + if(arr.length < 2){ return ret; } + + _zipIdx++; + + // we have to fork here for IE and XML docs because we can't set + // expandos on their nodes (apparently). *sigh* + if(dojo.isIE && caseSensitive){ + var szidx = _zipIdx+""; + arr[0].setAttribute(_zipIdxName, szidx); + for(var x = 1, te; te = arr[x]; x++){ + if(arr[x].getAttribute(_zipIdxName) != szidx){ + ret.push(te); + } + te.setAttribute(_zipIdxName, szidx); + } + }else if(dojo.isIE && arr.commentStrip){ + try{ + for(var x = 1, te; te = arr[x]; x++){ + if(_isElement(te)){ + ret.push(te); + } + } + }catch(e){ /* squelch */ } + }else{ + if(arr[0]){ arr[0][_zipIdxName] = _zipIdx; } + for(var x = 1, te; te = arr[x]; x++){ + if(arr[x][_zipIdxName] != _zipIdx){ + ret.push(te); + } + te[_zipIdxName] = _zipIdx; + } + } + return ret; + }; -if(!dojo._hasResource["dijit.form._FormWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form._FormWidget"] = true; -dojo.provide("dijit.form._FormWidget"); + // the main executor + var query = function(/*String*/ query, /*String|DOMNode?*/ root){ + // summary: + // Returns nodes which match the given CSS3 selector, searching the + // entire document by default but optionally taking a node to scope + // the search by. Returns an array. + // description: + // dojo.query() is the swiss army knife of DOM node manipulation in + // Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's + // "$" function, dojo.query provides robust, high-performance + // CSS-based node selector support with the option of scoping searches + // to a particular sub-tree of a document. + // + // Supported Selectors: + // -------------------- + // + // acme supports a rich set of CSS3 selectors, including: + // + // * class selectors (e.g., `.foo`) + // * node type selectors like `span` + // * ` ` descendant selectors + // * `>` child element selectors + // * `#foo` style ID selectors + // * `*` universal selector + // * `~`, the preceded-by sibling selector + // * `+`, the immediately preceded-by sibling selector + // * attribute queries: + // | * `[foo]` attribute presence selector + // | * `[foo='bar']` attribute value exact match + // | * `[foo~='bar']` attribute value list item match + // | * `[foo^='bar']` attribute start match + // | * `[foo$='bar']` attribute end match + // | * `[foo*='bar']` attribute substring match + // * `:first-child`, `:last-child`, and `:only-child` positional selectors + // * `:empty` content emtpy selector + // * `:checked` pseudo selector + // * `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations + // * `:nth-child(even)`, `:nth-child(odd)` positional selectors + // * `:not(...)` negation pseudo selectors + // + // Any legal combination of these selectors will work with + // `dojo.query()`, including compound selectors ("," delimited). + // Very complex and useful searches can be constructed with this + // palette of selectors and when combined with functions for + // manipulation presented by dojo.NodeList, many types of DOM + // manipulation operations become very straightforward. + // + // Unsupported Selectors: + // ---------------------- + // + // While dojo.query handles many CSS3 selectors, some fall outside of + // what's reasonable for a programmatic node querying engine to + // handle. Currently unsupported selectors include: + // + // * namespace-differentiated selectors of any form + // * all `::` pseduo-element selectors + // * certain pseduo-selectors which don't get a lot of day-to-day use: + // | * `:root`, `:lang()`, `:target`, `:focus` + // * all visual and state selectors: + // | * `:root`, `:active`, `:hover`, `:visisted`, `:link`, + // `:enabled`, `:disabled` + // * `:*-of-type` pseudo selectors + // + // dojo.query and XML Documents: + // ----------------------------- + // + // `dojo.query` (as of dojo 1.2) supports searching XML documents + // in a case-sensitive manner. If an HTML document is served with + // a doctype that forces case-sensitivity (e.g., XHTML 1.1 + // Strict), dojo.query() will detect this and "do the right + // thing". Case sensitivity is dependent upon the document being + // searched and not the query used. It is therefore possible to + // use case-sensitive queries on strict sub-documents (iframes, + // etc.) or XML documents while still assuming case-insensitivity + // for a host/root document. + // + // Non-selector Queries: + // --------------------- + // + // If something other than a String is passed for the query, + // `dojo.query` will return a new `dojo.NodeList` instance + // constructed from that parameter alone and all further + // processing will stop. This means that if you have a reference + // to a node or NodeList, you can quickly construct a new NodeList + // from the original by calling `dojo.query(node)` or + // `dojo.query(list)`. + // + // query: + // The CSS3 expression to match against. For details on the syntax of + // CSS3 selectors, see <http://www.w3.org/TR/css3-selectors/#selectors> + // root: + // A DOMNode (or node id) to scope the search from. Optional. + // returns: Array + // example: + // search the entire document for elements with the class "foo": + // | dojo.query(".foo"); + // these elements will match: + // | <span class="foo"></span> + // | <span class="foo bar"></span> + // | <p class="thud foo"></p> + // example: + // search the entire document for elements with the classes "foo" *and* "bar": + // | dojo.query(".foo.bar"); + // these elements will match: + // | <span class="foo bar"></span> + // while these will not: + // | <span class="foo"></span> + // | <p class="thud foo"></p> + // example: + // find `<span>` elements which are descendants of paragraphs and + // which have a "highlighted" class: + // | dojo.query("p span.highlighted"); + // the innermost span in this fragment matches: + // | <p class="foo"> + // | <span>... + // | <span class="highlighted foo bar">...</span> + // | </span> + // | </p> + // example: + // set an "odd" class on all odd table rows inside of the table + // `#tabular_data`, using the `>` (direct child) selector to avoid + // affecting any nested tables: + // | dojo.query("#tabular_data > tbody > tr:nth-child(odd)").addClass("odd"); + // example: + // remove all elements with the class "error" from the document + // and store them in a list: + // | var errors = dojo.query(".error").orphan(); + // example: + // add an onclick handler to every submit button in the document + // which causes the form to be sent via Ajax instead: + // | dojo.query("input[type='submit']").onclick(function(e){ + // | dojo.stopEvent(e); // prevent sending the form + // | var btn = e.target; + // | dojo.xhrPost({ + // | form: btn.form, + // | load: function(data){ + // | // replace the form with the response + // | var div = dojo.doc.createElement("div"); + // | dojo.place(div, btn.form, "after"); + // | div.innerHTML = data; + // | dojo.style(btn.form, "display", "none"); + // | } + // | }); + // | }); + root = root||getDoc(); + var od = root.ownerDocument||root.documentElement; + // throw the big case sensitivity switch + // NOTE: + // Opera in XHTML mode doesn't detect case-sensitivity correctly + // and it's not clear that there's any way to test for it + caseSensitive = (root.contentType && root.contentType=="application/xml") || + (dojo.isOpera && (root.doctype || od.toString() == "[object XMLDocument]")) || + (!!od) && + (dojo.isIE ? od.xml : (root.xmlVersion || od.xmlVersion)); + // NOTE: + // adding "true" as the 2nd argument to getQueryFunc is useful for + // testing the DOM branch without worrying about the + // behavior/performance of the QSA branch. + var r = getQueryFunc(query)(root); + // FIXME: + // need to investigate this branch WRT #8074 and #8075 + if(r && r.nozip){ + return r; + } + return _zip(r); // dojo.NodeList + }; + query.filter = function(/*Node[]*/ nodeList, /*String*/ filter, /*String|DOMNode?*/ root){ + // summary: + // function for filtering a NodeList based on a selector, optimized for simple selectors + var tmpNodeList = [], + parts = getQueryParts(filter), + filterFunc = + (parts.length == 1 && !/[^\w#\.]/.test(filter)) ? + getSimpleFilterFunc(parts[0]) : + function(node){ + return dojo.query(filter, root).indexOf(node) != -1; + }; + for(var x = 0, te; te = nodeList[x]; x++){ + if(filterFunc(te)){ tmpNodeList.push(te); } + } + return tmpNodeList; + }; + return query; +});//end defineQuery -dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._CssStateMixin], - { +}, +'dojo/dnd/autoscroll':function(){ +define("dojo/dnd/autoscroll", ["../main", "../window"], function(dojo) { + // module: + // dojo/dnd/autoscroll // summary: - // Base class for widgets corresponding to native HTML elements such as <checkbox> or <button>, - // which can be children of a <form> node or a `dijit.form.Form` widget. - // - // description: - // Represents a single HTML element. - // All these widgets should have these attributes just like native HTML input elements. - // You can set them during widget construction or afterwards, via `dijit._Widget.attr`. - // - // They also share some common methods. + // TODOC - // name: [const] String - // Name used when submitting form; same as "name" attribute or plain HTML elements - name: "", - - // alt: String - // Corresponds to the native HTML <input> element's attribute. - alt: "", +dojo.getObject("dnd", true, dojo); - // value: String - // Corresponds to the native HTML <input> element's attribute. - value: "", +dojo.dnd.getViewport = dojo.window.getBox; - // type: String - // Corresponds to the native HTML <input> element's attribute. - type: "text", +dojo.dnd.V_TRIGGER_AUTOSCROLL = 32; +dojo.dnd.H_TRIGGER_AUTOSCROLL = 32; - // tabIndex: Integer - // Order fields are traversed when user hits the tab key - tabIndex: "0", +dojo.dnd.V_AUTOSCROLL_VALUE = 16; +dojo.dnd.H_AUTOSCROLL_VALUE = 16; - // disabled: Boolean - // Should this widget respond to user input? - // In markup, this is specified as "disabled='disabled'", or just "disabled". - disabled: false, +dojo.dnd.autoScroll = function(e){ + // summary: + // a handler for onmousemove event, which scrolls the window, if + // necesary + // e: Event + // onmousemove event - // intermediateChanges: Boolean - // Fires onChange for each value change or only on demand - intermediateChanges: false, + // FIXME: needs more docs! + var v = dojo.window.getBox(), dx = 0, dy = 0; + if(e.clientX < dojo.dnd.H_TRIGGER_AUTOSCROLL){ + dx = -dojo.dnd.H_AUTOSCROLL_VALUE; + }else if(e.clientX > v.w - dojo.dnd.H_TRIGGER_AUTOSCROLL){ + dx = dojo.dnd.H_AUTOSCROLL_VALUE; + } + if(e.clientY < dojo.dnd.V_TRIGGER_AUTOSCROLL){ + dy = -dojo.dnd.V_AUTOSCROLL_VALUE; + }else if(e.clientY > v.h - dojo.dnd.V_TRIGGER_AUTOSCROLL){ + dy = dojo.dnd.V_AUTOSCROLL_VALUE; + } + window.scrollBy(dx, dy); +}; - // scrollOnFocus: Boolean - // On focus, should this widget scroll into view? - scrollOnFocus: true, +dojo.dnd._validNodes = {"div": 1, "p": 1, "td": 1}; +dojo.dnd._validOverflow = {"auto": 1, "scroll": 1}; - // These mixins assume that the focus node is an INPUT, as many but not all _FormWidgets are. - attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { - value: "focusNode", - id: "focusNode", - tabIndex: "focusNode", - alt: "focusNode", - title: "focusNode" - }), +dojo.dnd.autoScrollNodes = function(e){ + // summary: + // a handler for onmousemove event, which scrolls the first avaialble + // Dom element, it falls back to dojo.dnd.autoScroll() + // e: Event + // onmousemove event - postMixInProperties: function(){ - // Setup name=foo string to be referenced from the template (but only if a name has been specified) - // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660 - // Regarding escaping, see heading "Attribute values" in - // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 - this.nameAttrSetting = this.name ? ('name="' + this.name.replace(/'/g, """) + '"') : ''; - this.inherited(arguments); - }, + // FIXME: needs more docs! - postCreate: function(){ - this.inherited(arguments); - this.connect(this.domNode, "onmousedown", "_onMouseDown"); - }, + var b, t, w, h, rx, ry, dx = 0, dy = 0, oldLeft, oldTop; - _setDisabledAttr: function(/*Boolean*/ value){ - this._set("disabled", value); - dojo.attr(this.focusNode, 'disabled', value); - if(this.valueNode){ - dojo.attr(this.valueNode, 'disabled', value); + for(var n = e.target; n;){ + if(n.nodeType == 1 && (n.tagName.toLowerCase() in dojo.dnd._validNodes)){ + var s = dojo.getComputedStyle(n), + overflow = (s.overflow.toLowerCase() in dojo.dnd._validOverflow), + overflowX = (s.overflowX.toLowerCase() in dojo.dnd._validOverflow), + overflowY = (s.overflowY.toLowerCase() in dojo.dnd._validOverflow); + if(overflow || overflowX || overflowY){ + b = dojo._getContentBox(n, s); + t = dojo.position(n, true); + } + // overflow-x + if(overflow || overflowX){ + w = Math.min(dojo.dnd.H_TRIGGER_AUTOSCROLL, b.w / 2); + rx = e.pageX - t.x; + if(dojo.isWebKit || dojo.isOpera){ + // FIXME: this code should not be here, it should be taken into account + // either by the event fixing code, or the dojo.position() + // FIXME: this code doesn't work on Opera 9.5 Beta + rx += dojo.body().scrollLeft; + } + dx = 0; + if(rx > 0 && rx < b.w){ + if(rx < w){ + dx = -w; + }else if(rx > b.w - w){ + dx = w; + } + oldLeft = n.scrollLeft; + n.scrollLeft = n.scrollLeft + dx; + } + } + // overflow-y + if(overflow || overflowY){ + //console.log(b.l, b.t, t.x, t.y, n.scrollLeft, n.scrollTop); + h = Math.min(dojo.dnd.V_TRIGGER_AUTOSCROLL, b.h / 2); + ry = e.pageY - t.y; + if(dojo.isWebKit || dojo.isOpera){ + // FIXME: this code should not be here, it should be taken into account + // either by the event fixing code, or the dojo.position() + // FIXME: this code doesn't work on Opera 9.5 Beta + ry += dojo.body().scrollTop; + } + dy = 0; + if(ry > 0 && ry < b.h){ + if(ry < h){ + dy = -h; + }else if(ry > b.h - h){ + dy = h; + } + oldTop = n.scrollTop; + n.scrollTop = n.scrollTop + dy; + } + } + if(dx || dy){ return; } } - dijit.setWaiState(this.focusNode, "disabled", value); + try{ + n = n.parentNode; + }catch(x){ + n = null; + } + } + dojo.dnd.autoScroll(e); +}; - if(value){ - // reset these, because after the domNode is disabled, we can no longer receive - // mouse related events, see #4200 - this._set("hovering", false); - this._set("active", false); + return dojo.dnd; +}); - // clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes) - var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex : "focusNode"; - dojo.forEach(dojo.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){ - var node = this[attachPointName]; - // complex code because tabIndex=-1 on a <div> doesn't work on FF - if(dojo.isWebKit || dijit.hasDefaultTabStop(node)){ // see #11064 about webkit bug - node.setAttribute('tabIndex', "-1"); - }else{ - node.removeAttribute('tabIndex'); - } - }, this); - }else{ - if(this.tabIndex != ""){ - this.focusNode.setAttribute('tabIndex', this.tabIndex); - } +}, +'dojo/data/ItemFileWriteStore':function(){ +define("dojo/data/ItemFileWriteStore", ["../_base/lang", "../_base/declare", "../_base/array", "../_base/json", "../_base/window", + "./ItemFileReadStore", "../date/stamp" +], function(lang, declare, arrayUtil, jsonUtil, window, ItemFileReadStore, dateStamp) { + // module: + // dojo/data/ItemFileWriteStore + // summary: + // TODOC + +/*===== var ItemFileReadStore = dojo.data.ItemFileReadStore; =====*/ +return declare("dojo.data.ItemFileWriteStore", ItemFileReadStore, { + constructor: function(/* object */ keywordParameters){ + // keywordParameters: {typeMap: object) + // The structure of the typeMap object is as follows: + // { + // type0: function || object, + // type1: function || object, + // ... + // typeN: function || object + // } + // Where if it is a function, it is assumed to be an object constructor that takes the + // value of _value as the initialization parameters. It is serialized assuming object.toString() + // serialization. If it is an object, then it is assumed + // to be an object of general form: + // { + // type: function, //constructor. + // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately. + // serialize: function(object) //The function that converts the object back into the proper file format form. + // } + + // ItemFileWriteStore extends ItemFileReadStore to implement these additional dojo.data APIs + this._features['dojo.data.api.Write'] = true; + this._features['dojo.data.api.Notification'] = true; + + // For keeping track of changes so that we can implement isDirty and revert + this._pending = { + _newItems:{}, + _modifiedItems:{}, + _deletedItems:{} + }; + + if(!this._datatypeMap['Date'].serialize){ + this._datatypeMap['Date'].serialize = function(obj){ + return dateStamp.toISOString(obj, {zulu:true}); + }; + } + //Disable only if explicitly set to false. + if(keywordParameters && (keywordParameters.referenceIntegrity === false)){ + this.referenceIntegrity = false; } - }, - setDisabled: function(/*Boolean*/ disabled){ - // summary: - // Deprecated. Use set('disabled', ...) instead. - dojo.deprecated("setDisabled("+disabled+") is deprecated. Use set('disabled',"+disabled+") instead.", "", "2.0"); - this.set('disabled', disabled); + // this._saveInProgress is set to true, briefly, from when save() is first called to when it completes + this._saveInProgress = false; }, - _onFocus: function(e){ - if(this.scrollOnFocus){ - dojo.window.scrollIntoView(this.domNode); + referenceIntegrity: true, //Flag that defaultly enabled reference integrity tracking. This way it can also be disabled pogrammatially or declaratively. + + _assert: function(/* boolean */ condition){ + if(!condition){ + throw new Error("assertion failed in ItemFileWriteStore"); } - this.inherited(arguments); }, - isFocusable: function(){ - // summary: - // Tells if this widget is focusable or not. Used internally by dijit. - // tags: - // protected - return !this.disabled && this.focusNode && (dojo.style(this.domNode, "display") != "none"); + _getIdentifierAttribute: function(){ + // this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute))); + return this.getFeatures()['dojo.data.api.Identity']; }, - focus: function(){ - // summary: - // Put focus on this widget - if(!this.disabled){ - dijit.focus(this.focusNode); + +/* dojo.data.api.Write */ + + newItem: function(/* Object? */ keywordArgs, /* Object? */ parentInfo){ + // summary: See dojo.data.api.Write.newItem() + + this._assert(!this._saveInProgress); + + if(!this._loadFinished){ + // We need to do this here so that we'll be able to find out what + // identifierAttribute was specified in the data file. + this._forceLoad(); } - }, - compare: function(/*anything*/ val1, /*anything*/ val2){ - // summary: - // Compare 2 values (as returned by get('value') for this widget). - // tags: - // protected - if(typeof val1 == "number" && typeof val2 == "number"){ - return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2; - }else if(val1 > val2){ - return 1; - }else if(val1 < val2){ - return -1; + if(typeof keywordArgs != "object" && typeof keywordArgs != "undefined"){ + throw new Error("newItem() was passed something other than an object"); + } + var newIdentity = null; + var identifierAttribute = this._getIdentifierAttribute(); + if(identifierAttribute === Number){ + newIdentity = this._arrayOfAllItems.length; }else{ - return 0; + newIdentity = keywordArgs[identifierAttribute]; + if(typeof newIdentity === "undefined"){ + throw new Error("newItem() was not passed an identity for the new item"); + } + if(lang.isArray(newIdentity)){ + throw new Error("newItem() was not passed an single-valued identity"); + } } - }, - onChange: function(newValue){ - // summary: - // Callback when this widget's value is changed. - // tags: - // callback - }, - - // _onChangeActive: [private] Boolean - // Indicates that changes to the value should call onChange() callback. - // This is false during widget initialization, to avoid calling onChange() - // when the initial value is set. - _onChangeActive: false, + // make sure this identity is not already in use by another item, if identifiers were + // defined in the file. Otherwise it would be the item count, + // which should always be unique in this case. + if(this._itemsByIdentity){ + this._assert(typeof this._itemsByIdentity[newIdentity] === "undefined"); + } + this._assert(typeof this._pending._newItems[newIdentity] === "undefined"); + this._assert(typeof this._pending._deletedItems[newIdentity] === "undefined"); - _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ - // summary: - // Called when the value of the widget is set. Calls onChange() if appropriate - // newValue: - // the new value - // priorityChange: - // For a slider, for example, dragging the slider is priorityChange==false, - // but on mouse up, it's priorityChange==true. If intermediateChanges==false, - // onChange is only called form priorityChange=true events. - // tags: - // private - if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){ - // this block executes not for a change, but during initialization, - // and is used to store away the original value (or for ToggleButton, the original checked state) - this._resetValue = this._lastValueReported = newValue; + var newItem = {}; + newItem[this._storeRefPropName] = this; + newItem[this._itemNumPropName] = this._arrayOfAllItems.length; + if(this._itemsByIdentity){ + this._itemsByIdentity[newIdentity] = newItem; + //We have to set the identifier now, otherwise we can't look it + //up at calls to setValueorValues in parentInfo handling. + newItem[identifierAttribute] = [newIdentity]; } - this._pendingOnChange = this._pendingOnChange - || (typeof newValue != typeof this._lastValueReported) - || (this.compare(newValue, this._lastValueReported) != 0); - if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){ - this._lastValueReported = newValue; - this._pendingOnChange = false; - if(this._onChangeActive){ - if(this._onChangeHandle){ - clearTimeout(this._onChangeHandle); + this._arrayOfAllItems.push(newItem); + + //We need to construct some data for the onNew call too... + var pInfo = null; + + // Now we need to check to see where we want to assign this thingm if any. + if(parentInfo && parentInfo.parent && parentInfo.attribute){ + pInfo = { + item: parentInfo.parent, + attribute: parentInfo.attribute, + oldValue: undefined + }; + + //See if it is multi-valued or not and handle appropriately + //Generally, all attributes are multi-valued for this store + //So, we only need to append if there are already values present. + var values = this.getValues(parentInfo.parent, parentInfo.attribute); + if(values && values.length > 0){ + var tempValues = values.slice(0, values.length); + if(values.length === 1){ + pInfo.oldValue = values[0]; + }else{ + pInfo.oldValue = values.slice(0, values.length); } - // setTimout allows hidden value processing to run and - // also the onChange handler can safely adjust focus, etc - this._onChangeHandle = setTimeout(dojo.hitch(this, - function(){ - this._onChangeHandle = null; - this.onChange(newValue); - }), 0); // try to collapse multiple onChange's fired faster than can be processed + tempValues.push(newItem); + this._setValueOrValues(parentInfo.parent, parentInfo.attribute, tempValues, false); + pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute); + }else{ + this._setValueOrValues(parentInfo.parent, parentInfo.attribute, newItem, false); + pInfo.newValue = newItem; } + }else{ + //Toplevel item, add to both top list as well as all list. + newItem[this._rootItemPropName]=true; + this._arrayOfTopLevelItems.push(newItem); } - }, - create: function(){ - // Overrides _Widget.create() - this.inherited(arguments); - this._onChangeActive = true; - }, + this._pending._newItems[newIdentity] = newItem; - destroy: function(){ - if(this._onChangeHandle){ // destroy called before last onChange has fired - clearTimeout(this._onChangeHandle); - this.onChange(this._lastValueReported); + //Clone over the properties to the new item + for(var key in keywordArgs){ + if(key === this._storeRefPropName || key === this._itemNumPropName){ + // Bummer, the user is trying to do something like + // newItem({_S:"foo"}). Unfortunately, our superclass, + // ItemFileReadStore, is already using _S in each of our items + // to hold private info. To avoid a naming collision, we + // need to move all our private info to some other property + // of all the items/objects. So, we need to iterate over all + // the items and do something like: + // item.__S = item._S; + // item._S = undefined; + // But first we have to make sure the new "__S" variable is + // not in use, which means we have to iterate over all the + // items checking for that. + throw new Error("encountered bug in ItemFileWriteStore.newItem"); + } + var value = keywordArgs[key]; + if(!lang.isArray(value)){ + value = [value]; + } + newItem[key] = value; + if(this.referenceIntegrity){ + for(var i = 0; i < value.length; i++){ + var val = value[i]; + if(this.isItem(val)){ + this._addReferenceToMap(val, newItem, key); + } + } + } } - this.inherited(arguments); + this.onNew(newItem, pInfo); // dojo.data.api.Notification call + return newItem; // item }, - setValue: function(/*String*/ value){ - // summary: - // Deprecated. Use set('value', ...) instead. - dojo.deprecated("dijit.form._FormWidget:setValue("+value+") is deprecated. Use set('value',"+value+") instead.", "", "2.0"); - this.set('value', value); + _removeArrayElement: function(/* Array */ array, /* anything */ element){ + var index = arrayUtil.indexOf(array, element); + if(index != -1){ + array.splice(index, 1); + return true; + } + return false; }, - getValue: function(){ - // summary: - // Deprecated. Use get('value') instead. - dojo.deprecated(this.declaredClass+"::getValue() is deprecated. Use get('value') instead.", "", "2.0"); - return this.get('value'); - }, - - _onMouseDown: function(e){ - // If user clicks on the button, even if the mouse is released outside of it, - // this button should get focus (to mimics native browser buttons). - // This is also needed on chrome because otherwise buttons won't get focus at all, - // which leads to bizarre focus restore on Dialog close etc. - if(!e.ctrlKey && dojo.mouseButtons.isLeft(e) && this.isFocusable()){ // !e.ctrlKey to ignore right-click on mac - // Set a global event to handle mouseup, so it fires properly - // even if the cursor leaves this.domNode before the mouse up event. - var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){ - if (this.isFocusable()) { - this.focus(); - } - this.disconnect(mouseUpConnector); - }); - } - } -}); + deleteItem: function(/* item */ item){ + // summary: See dojo.data.api.Write.deleteItem() + this._assert(!this._saveInProgress); + this._assertIsItem(item); -dojo.declare("dijit.form._FormValueWidget", dijit.form._FormWidget, -{ - // summary: - // Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values. - // description: - // Each _FormValueWidget represents a single input value, and has a (possibly hidden) <input> element, - // to which it serializes it's input value, so that form submission (either normal submission or via FormBind?) - // works as expected. + // Remove this item from the _arrayOfAllItems, but leave a null value in place + // of the item, so as not to change the length of the array, so that in newItem() + // we can still safely do: newIdentity = this._arrayOfAllItems.length; + var indexInArrayOfAllItems = item[this._itemNumPropName]; + var identity = this.getIdentity(item); - // Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared - // directly in the template as read by the parser in order to function. IE is known to specifically - // require the 'name' attribute at element creation time. See #8484, #8660. - // TODO: unclear what that {value: ""} is for; FormWidget.attributeMap copies value to focusNode, - // so maybe {value: ""} is so the value *doesn't* get copied to focusNode? - // Seems like we really want value removed from attributeMap altogether - // (although there's no easy way to do that now) + //If we have reference integrity on, we need to do reference cleanup for the deleted item + if(this.referenceIntegrity){ + //First scan all the attributes of this items for references and clean them up in the map + //As this item is going away, no need to track its references anymore. - // readOnly: Boolean - // Should this widget respond to user input? - // In markup, this is specified as "readOnly". - // Similar to disabled except readOnly form values are submitted. - readOnly: false, + //Get the attributes list before we generate the backup so it + //doesn't pollute the attributes list. + var attributes = this.getAttributes(item); - attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { - value: "", - readOnly: "focusNode" - }), + //Backup the map, we'll have to restore it potentially, in a revert. + if(item[this._reverseRefMap]){ + item["backup_" + this._reverseRefMap] = lang.clone(item[this._reverseRefMap]); + } - _setReadOnlyAttr: function(/*Boolean*/ value){ - dojo.attr(this.focusNode, 'readOnly', value); - dijit.setWaiState(this.focusNode, "readonly", value); - this._set("readOnly", value); - }, + //TODO: This causes a reversion problem. This list won't be restored on revert since it is + //attached to the 'value'. item, not ours. Need to back tese up somehow too. + //Maybe build a map of the backup of the entries and attach it to the deleted item to be restored + //later. Or just record them and call _addReferenceToMap on them in revert. + arrayUtil.forEach(attributes, function(attribute){ + arrayUtil.forEach(this.getValues(item, attribute), function(value){ + if(this.isItem(value)){ + //We have to back up all the references we had to others so they can be restored on a revert. + if(!item["backupRefs_" + this._reverseRefMap]){ + item["backupRefs_" + this._reverseRefMap] = []; + } + item["backupRefs_" + this._reverseRefMap].push({id: this.getIdentity(value), attr: attribute}); + this._removeReferenceFromMap(value, item, attribute); + } + }, this); + }, this); - postCreate: function(){ - this.inherited(arguments); + //Next, see if we have references to this item, if we do, we have to clean them up too. + var references = item[this._reverseRefMap]; + if(references){ + //Look through all the items noted as references to clean them up. + for(var itemId in references){ + var containingItem = null; + if(this._itemsByIdentity){ + containingItem = this._itemsByIdentity[itemId]; + }else{ + containingItem = this._arrayOfAllItems[itemId]; + } + //We have a reference to a containing item, now we have to process the + //attributes and clear all references to the item being deleted. + if(containingItem){ + for(var attribute in references[itemId]){ + var oldValues = this.getValues(containingItem, attribute) || []; + var newValues = arrayUtil.filter(oldValues, function(possibleItem){ + return !(this.isItem(possibleItem) && this.getIdentity(possibleItem) == identity); + }, this); + //Remove the note of the reference to the item and set the values on the modified attribute. + this._removeReferenceFromMap(item, containingItem, attribute); + if(newValues.length < oldValues.length){ + this._setValueOrValues(containingItem, attribute, newValues, true); + } + } + } + } + } + } - if(dojo.isIE < 9 || (dojo.isIE && dojo.isQuirks)){ // IE won't stop the event with keypress - this.connect(this.focusNode || this.domNode, "onkeydown", this._onKeyDown); + this._arrayOfAllItems[indexInArrayOfAllItems] = null; + + item[this._storeRefPropName] = null; + if(this._itemsByIdentity){ + delete this._itemsByIdentity[identity]; } - // Update our reset value if it hasn't yet been set (because this.set() - // is only called when there *is* a value) - if(this._resetValue === undefined){ - this._lastValueReported = this._resetValue = this.value; + this._pending._deletedItems[identity] = item; + + //Remove from the toplevel items, if necessary... + if(item[this._rootItemPropName]){ + this._removeArrayElement(this._arrayOfTopLevelItems, item); } + this.onDelete(item); // dojo.data.api.Notification call + return true; }, - _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ - // summary: - // Hook so set('value', value) works. - // description: - // Sets the value of the widget. - // If the value has changed, then fire onChange event, unless priorityChange - // is specified as null (or false?) - this._handleOnChange(newValue, priorityChange); + setValue: function(/* item */ item, /* attribute-name-string */ attribute, /* almost anything */ value){ + // summary: See dojo.data.api.Write.set() + return this._setValueOrValues(item, attribute, value, true); // boolean }, - _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ - // summary: - // Called when the value of the widget has changed. Saves the new value in this.value, - // and calls onChange() if appropriate. See _FormWidget._handleOnChange() for details. - this._set("value", newValue); - this.inherited(arguments); + setValues: function(/* item */ item, /* attribute-name-string */ attribute, /* array */ values){ + // summary: See dojo.data.api.Write.setValues() + return this._setValueOrValues(item, attribute, values, true); // boolean }, - undo: function(){ - // summary: - // Restore the value to the last value passed to onChange - this._setValueAttr(this._lastValueReported, false); + unsetAttribute: function(/* item */ item, /* attribute-name-string */ attribute){ + // summary: See dojo.data.api.Write.unsetAttribute() + return this._setValueOrValues(item, attribute, [], true); }, - reset: function(){ - // summary: - // Reset the widget's value to what it was at initialization time - this._hasBeenBlurred = false; - this._setValueAttr(this._resetValue, true); - }, + _setValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute, /* anything */ newValueOrValues, /*boolean?*/ callOnSet){ + this._assert(!this._saveInProgress); + + // Check for valid arguments + this._assertIsItem(item); + this._assert(lang.isString(attribute)); + this._assert(typeof newValueOrValues !== "undefined"); + + // Make sure the user isn't trying to change the item's identity + var identifierAttribute = this._getIdentifierAttribute(); + if(attribute == identifierAttribute){ + throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier."); + } - _onKeyDown: function(e){ - if(e.keyCode == dojo.keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){ - var te; - if(dojo.isIE){ - e.preventDefault(); // default behavior needs to be stopped here since keypress is too late - te = document.createEventObject(); - te.keyCode = dojo.keys.ESCAPE; - te.shiftKey = e.shiftKey; - e.srcElement.fireEvent('onkeypress', te); + // To implement the Notification API, we need to make a note of what + // the old attribute value was, so that we can pass that info when + // we call the onSet method. + var oldValueOrValues = this._getValueOrValues(item, attribute); + + var identity = this.getIdentity(item); + if(!this._pending._modifiedItems[identity]){ + // Before we actually change the item, we make a copy of it to + // record the original state, so that we'll be able to revert if + // the revert method gets called. If the item has already been + // modified then there's no need to do this now, since we already + // have a record of the original state. + var copyOfItemState = {}; + for(var key in item){ + if((key === this._storeRefPropName) || (key === this._itemNumPropName) || (key === this._rootItemPropName)){ + copyOfItemState[key] = item[key]; + }else if(key === this._reverseRefMap){ + copyOfItemState[key] = lang.clone(item[key]); + }else{ + copyOfItemState[key] = item[key].slice(0, item[key].length); + } } + // Now mark the item as dirty, and save the copy of the original state + this._pending._modifiedItems[identity] = copyOfItemState; } - }, - _layoutHackIE7: function(){ - // summary: - // Work around table sizing bugs on IE7 by forcing redraw + // Okay, now we can actually change this attribute on the item + var success = false; - if(dojo.isIE == 7){ // fix IE7 layout bug when the widget is scrolled out of sight - var domNode = this.domNode; - var parent = domNode.parentNode; - var pingNode = domNode.firstChild || domNode; // target node most unlikely to have a custom filter - var origFilter = pingNode.style.filter; // save custom filter, most likely nothing - var _this = this; - while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet - (function ping(){ - var disconnectHandle = _this.connect(parent, "onscroll", - function(e){ - _this.disconnect(disconnectHandle); // only call once - pingNode.style.filter = (new Date()).getMilliseconds(); // set to anything that's unique - setTimeout(function(){ pingNode.style.filter = origFilter }, 0); // restore custom filter, if any + if(lang.isArray(newValueOrValues) && newValueOrValues.length === 0){ + + // If we were passed an empty array as the value, that counts + // as "unsetting" the attribute, so we need to remove this + // attribute from the item. + success = delete item[attribute]; + newValueOrValues = undefined; // used in the onSet Notification call below + + if(this.referenceIntegrity && oldValueOrValues){ + var oldValues = oldValueOrValues; + if(!lang.isArray(oldValues)){ + oldValues = [oldValues]; + } + for(var i = 0; i < oldValues.length; i++){ + var value = oldValues[i]; + if(this.isItem(value)){ + this._removeReferenceFromMap(value, item, attribute); + } + } + } + }else{ + var newValueArray; + if(lang.isArray(newValueOrValues)){ + // Unfortunately, it's not safe to just do this: + // newValueArray = newValueOrValues; + // Instead, we need to copy the array, which slice() does very nicely. + // This is so that our internal data structure won't + // get corrupted if the user mucks with the values array *after* + // calling setValues(). + newValueArray = newValueOrValues.slice(0, newValueOrValues.length); + }else{ + newValueArray = [newValueOrValues]; + } + + //We need to handle reference integrity if this is on. + //In the case of set, we need to see if references were added or removed + //and update the reference tracking map accordingly. + if(this.referenceIntegrity){ + if(oldValueOrValues){ + var oldValues = oldValueOrValues; + if(!lang.isArray(oldValues)){ + oldValues = [oldValues]; + } + //Use an associative map to determine what was added/removed from the list. + //Should be O(n) performant. First look at all the old values and make a list of them + //Then for any item not in the old list, we add it. If it was already present, we remove it. + //Then we pass over the map and any references left it it need to be removed (IE, no match in + //the new values list). + var map = {}; + arrayUtil.forEach(oldValues, function(possibleItem){ + if(this.isItem(possibleItem)){ + var id = this.getIdentity(possibleItem); + map[id.toString()] = true; } - ); - })(); - parent = parent.parentNode; + }, this); + arrayUtil.forEach(newValueArray, function(possibleItem){ + if(this.isItem(possibleItem)){ + var id = this.getIdentity(possibleItem); + if(map[id.toString()]){ + delete map[id.toString()]; + }else{ + this._addReferenceToMap(possibleItem, item, attribute); + } + } + }, this); + for(var rId in map){ + var removedItem; + if(this._itemsByIdentity){ + removedItem = this._itemsByIdentity[rId]; + }else{ + removedItem = this._arrayOfAllItems[rId]; + } + this._removeReferenceFromMap(removedItem, item, attribute); + } + }else{ + //Everything is new (no old values) so we have to just + //insert all the references, if any. + for(var i = 0; i < newValueArray.length; i++){ + var value = newValueArray[i]; + if(this.isItem(value)){ + this._addReferenceToMap(value, item, attribute); + } + } + } } + item[attribute] = newValueArray; + success = true; } - } -}); -} + // Now we make the dojo.data.api.Notification call + if(callOnSet){ + this.onSet(item, attribute, oldValueOrValues, newValueOrValues); + } + return success; // boolean + }, -if(!dojo._hasResource["dijit.dijit"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.dijit"] = true; -dojo.provide("dijit.dijit"); + _addReferenceToMap: function(/* item */ refItem, /* item */ parentItem, /* string */ attribute){ + // summary: + // Method to add an reference map entry for an item and attribute. + // description: + // Method to add an reference map entry for an item and attribute. // + // refItem: + // The item that is referenced. + // parentItem: + // The item that holds the new reference to refItem. + // attribute: + // The attribute on parentItem that contains the new reference. + var parentId = this.getIdentity(parentItem); + var references = refItem[this._reverseRefMap]; + if(!references){ + references = refItem[this._reverseRefMap] = {}; + } + var itemRef = references[parentId]; + if(!itemRef){ + itemRef = references[parentId] = {}; + } + itemRef[attribute] = true; + }, + _removeReferenceFromMap: function(/* item */ refItem, /* item */ parentItem, /* string */ attribute){ + // summary: + // Method to remove an reference map entry for an item and attribute. + // description: + // Method to remove an reference map entry for an item and attribute. This will + // also perform cleanup on the map such that if there are no more references at all to + // the item, its reference object and entry are removed. + // + // refItem: + // The item that is referenced. + // parentItem: + // The item holding a reference to refItem. + // attribute: + // The attribute on parentItem that contains the reference. + var identity = this.getIdentity(parentItem); + var references = refItem[this._reverseRefMap]; + var itemId; + if(references){ + for(itemId in references){ + if(itemId == identity){ + delete references[itemId][attribute]; + if(this._isEmpty(references[itemId])){ + delete references[itemId]; + } + } + } + if(this._isEmpty(references)){ + delete refItem[this._reverseRefMap]; + } + } + }, + _dumpReferenceMap: function(){ + // summary: + // Function to dump the reverse reference map of all items in the store for debug purposes. + // description: + // Function to dump the reverse reference map of all items in the store for debug purposes. + var i; + for(i = 0; i < this._arrayOfAllItems.length; i++){ + var item = this._arrayOfAllItems[i]; + if(item && item[this._reverseRefMap]){ + console.log("Item: [" + this.getIdentity(item) + "] is referenced by: " + jsonUtil.toJson(item[this._reverseRefMap])); + } + } + }, + _getValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute){ + var valueOrValues = undefined; + if(this.hasAttribute(item, attribute)){ + var valueArray = this.getValues(item, attribute); + if(valueArray.length == 1){ + valueOrValues = valueArray[0]; + }else{ + valueOrValues = valueArray; + } + } + return valueOrValues; + }, + _flatten: function(/* anything */ value){ + if(this.isItem(value)){ + // Given an item, return an serializable object that provides a + // reference to the item. + // For example, given kermit: + // var kermit = store.newItem({id:2, name:"Kermit"}); + // we want to return + // {_reference:2} + return {_reference: this.getIdentity(value)}; + }else{ + if(typeof value === "object"){ + for(var type in this._datatypeMap){ + var typeMap = this._datatypeMap[type]; + if(lang.isObject(typeMap) && !lang.isFunction(typeMap)){ + if(value instanceof typeMap.type){ + if(!typeMap.serialize){ + throw new Error("ItemFileWriteStore: No serializer defined for type mapping: [" + type + "]"); + } + return {_type: type, _value: typeMap.serialize(value)}; + } + } else if(value instanceof typeMap){ + //SImple mapping, therefore, return as a toString serialization. + return {_type: type, _value: value.toString()}; + } + } + } + return value; + } + }, + _getNewFileContentString: function(){ + // summary: + // Generate a string that can be saved to a file. + // The result should look similar to: + // http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json + var serializableStructure = {}; + var identifierAttribute = this._getIdentifierAttribute(); + if(identifierAttribute !== Number){ + serializableStructure.identifier = identifierAttribute; + } + if(this._labelAttr){ + serializableStructure.label = this._labelAttr; + } + serializableStructure.items = []; + for(var i = 0; i < this._arrayOfAllItems.length; ++i){ + var item = this._arrayOfAllItems[i]; + if(item !== null){ + var serializableItem = {}; + for(var key in item){ + if(key !== this._storeRefPropName && key !== this._itemNumPropName && key !== this._reverseRefMap && key !== this._rootItemPropName){ + var valueArray = this.getValues(item, key); + if(valueArray.length == 1){ + serializableItem[key] = this._flatten(valueArray[0]); + }else{ + var serializableArray = []; + for(var j = 0; j < valueArray.length; ++j){ + serializableArray.push(this._flatten(valueArray[j])); + serializableItem[key] = serializableArray; + } + } + } + } + serializableStructure.items.push(serializableItem); + } + } + var prettyPrint = true; + return jsonUtil.toJson(serializableStructure, prettyPrint); + }, -/*===== -dijit.dijit = { - // summary: - // A roll-up for common dijit methods - // description: - // A rollup file for the build system including the core and common - // dijit files. - // - // example: - // | <script type="text/javascript" src="js/dojo/dijit/dijit.js"></script> - // -}; -=====*/ + _isEmpty: function(something){ + // summary: + // Function to determine if an array or object has no properties or values. + // something: + // The array or object to examine. + var empty = true; + if(lang.isObject(something)){ + var i; + for(i in something){ + empty = false; + break; + } + }else if(lang.isArray(something)){ + if(something.length > 0){ + empty = false; + } + } + return empty; //boolean + }, -// All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require) + save: function(/* object */ keywordArgs){ + // summary: See dojo.data.api.Write.save() + this._assert(!this._saveInProgress); -// And some other stuff that we tend to pull in all the time anyway + // this._saveInProgress is set to true, briefly, from when save is first called to when it completes + this._saveInProgress = true; -} + var self = this; + var saveCompleteCallback = function(){ + self._pending = { + _newItems:{}, + _modifiedItems:{}, + _deletedItems:{} + }; -if(!dojo._hasResource["dojo.fx.Toggler"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.fx.Toggler"] = true; -dojo.provide("dojo.fx.Toggler"); + self._saveInProgress = false; // must come after this._pending is cleared, but before any callbacks + if(keywordArgs && keywordArgs.onComplete){ + var scope = keywordArgs.scope || window.global; + keywordArgs.onComplete.call(scope); + } + }; + var saveFailedCallback = function(err){ + self._saveInProgress = false; + if(keywordArgs && keywordArgs.onError){ + var scope = keywordArgs.scope || window.global; + keywordArgs.onError.call(scope, err); + } + }; + if(this._saveEverything){ + var newFileContentString = this._getNewFileContentString(); + this._saveEverything(saveCompleteCallback, saveFailedCallback, newFileContentString); + } + if(this._saveCustom){ + this._saveCustom(saveCompleteCallback, saveFailedCallback); + } + if(!this._saveEverything && !this._saveCustom){ + // Looks like there is no user-defined save-handler function. + // That's fine, it just means the datastore is acting as a "mock-write" + // store -- changes get saved in memory but don't get saved to disk. + saveCompleteCallback(); + } + }, -dojo.declare("dojo.fx.Toggler", null, { - // summary: - // A simple `dojo.Animation` toggler API. - // - // description: - // class constructor for an animation toggler. It accepts a packed - // set of arguments about what type of animation to use in each - // direction, duration, etc. All available members are mixed into - // these animations from the constructor (for example, `node`, - // `showDuration`, `hideDuration`). - // - // example: - // | var t = new dojo.fx.Toggler({ - // | node: "nodeId", - // | showDuration: 500, - // | // hideDuration will default to "200" - // | showFunc: dojo.fx.wipeIn, - // | // hideFunc will default to "fadeOut" - // | }); - // | t.show(100); // delay showing for 100ms - // | // ...time passes... - // | t.hide(); + revert: function(){ + // summary: See dojo.data.api.Write.revert() + this._assert(!this._saveInProgress); - // node: DomNode - // the node to target for the showing and hiding animations - node: null, + var identity; + for(identity in this._pending._modifiedItems){ + // find the original item and the modified item that replaced it + var copyOfItemState = this._pending._modifiedItems[identity]; + var modifiedItem = null; + if(this._itemsByIdentity){ + modifiedItem = this._itemsByIdentity[identity]; + }else{ + modifiedItem = this._arrayOfAllItems[identity]; + } - // showFunc: Function - // The function that returns the `dojo.Animation` to show the node - showFunc: dojo.fadeIn, + // Restore the original item into a full-fledged item again, we want to try to + // keep the same object instance as if we don't it, causes bugs like #9022. + copyOfItemState[this._storeRefPropName] = this; + for(var key in modifiedItem){ + delete modifiedItem[key]; + } + lang.mixin(modifiedItem, copyOfItemState); + } + var deletedItem; + for(identity in this._pending._deletedItems){ + deletedItem = this._pending._deletedItems[identity]; + deletedItem[this._storeRefPropName] = this; + var index = deletedItem[this._itemNumPropName]; - // hideFunc: Function - // The function that returns the `dojo.Animation` to hide the node - hideFunc: dojo.fadeOut, + //Restore the reverse refererence map, if any. + if(deletedItem["backup_" + this._reverseRefMap]){ + deletedItem[this._reverseRefMap] = deletedItem["backup_" + this._reverseRefMap]; + delete deletedItem["backup_" + this._reverseRefMap]; + } + this._arrayOfAllItems[index] = deletedItem; + if(this._itemsByIdentity){ + this._itemsByIdentity[identity] = deletedItem; + } + if(deletedItem[this._rootItemPropName]){ + this._arrayOfTopLevelItems.push(deletedItem); + } + } + //We have to pass through it again and restore the reference maps after all the + //undeletes have occurred. + for(identity in this._pending._deletedItems){ + deletedItem = this._pending._deletedItems[identity]; + if(deletedItem["backupRefs_" + this._reverseRefMap]){ + arrayUtil.forEach(deletedItem["backupRefs_" + this._reverseRefMap], function(reference){ + var refItem; + if(this._itemsByIdentity){ + refItem = this._itemsByIdentity[reference.id]; + }else{ + refItem = this._arrayOfAllItems[reference.id]; + } + this._addReferenceToMap(refItem, deletedItem, reference.attr); + }, this); + delete deletedItem["backupRefs_" + this._reverseRefMap]; + } + } - // showDuration: - // Time in milliseconds to run the show Animation - showDuration: 200, + for(identity in this._pending._newItems){ + var newItem = this._pending._newItems[identity]; + newItem[this._storeRefPropName] = null; + // null out the new item, but don't change the array index so + // so we can keep using _arrayOfAllItems.length. + this._arrayOfAllItems[newItem[this._itemNumPropName]] = null; + if(newItem[this._rootItemPropName]){ + this._removeArrayElement(this._arrayOfTopLevelItems, newItem); + } + if(this._itemsByIdentity){ + delete this._itemsByIdentity[identity]; + } + } - // hideDuration: - // Time in milliseconds to run the hide Animation - hideDuration: 200, + this._pending = { + _newItems:{}, + _modifiedItems:{}, + _deletedItems:{} + }; + return true; // boolean + }, - // FIXME: need a policy for where the toggler should "be" the next - // time show/hide are called if we're stopped somewhere in the - // middle. - // FIXME: also would be nice to specify individual showArgs/hideArgs mixed into - // each animation individually. - // FIXME: also would be nice to have events from the animations exposed/bridged + isDirty: function(/* item? */ item){ + // summary: See dojo.data.api.Write.isDirty() + if(item){ + // return true if the item is dirty + var identity = this.getIdentity(item); + return new Boolean(this._pending._newItems[identity] || + this._pending._modifiedItems[identity] || + this._pending._deletedItems[identity]).valueOf(); // boolean + }else{ + // return true if the store is dirty -- which means return true + // if there are any new items, dirty items, or modified items + return !this._isEmpty(this._pending._newItems) || + !this._isEmpty(this._pending._modifiedItems) || + !this._isEmpty(this._pending._deletedItems); // boolean + } + }, - /*===== - _showArgs: null, - _showAnim: null, +/* dojo.data.api.Notification */ - _hideArgs: null, - _hideAnim: null, + onSet: function(/* item */ item, + /*attribute-name-string*/ attribute, + /*object|array*/ oldValue, + /*object|array*/ newValue){ + // summary: See dojo.data.api.Notification.onSet() - _isShowing: false, - _isHiding: false, - =====*/ + // No need to do anything. This method is here just so that the + // client code can connect observers to it. + }, - constructor: function(args){ - var _t = this; + onNew: function(/* item */ newItem, /*object?*/ parentInfo){ + // summary: See dojo.data.api.Notification.onNew() - dojo.mixin(_t, args); - _t.node = args.node; - _t._showArgs = dojo.mixin({}, args); - _t._showArgs.node = _t.node; - _t._showArgs.duration = _t.showDuration; - _t.showAnim = _t.showFunc(_t._showArgs); + // No need to do anything. This method is here just so that the + // client code can connect observers to it. + }, - _t._hideArgs = dojo.mixin({}, args); - _t._hideArgs.node = _t.node; - _t._hideArgs.duration = _t.hideDuration; - _t.hideAnim = _t.hideFunc(_t._hideArgs); + onDelete: function(/* item */ deletedItem){ + // summary: See dojo.data.api.Notification.onDelete() - dojo.connect(_t.showAnim, "beforeBegin", dojo.hitch(_t.hideAnim, "stop", true)); - dojo.connect(_t.hideAnim, "beforeBegin", dojo.hitch(_t.showAnim, "stop", true)); + // No need to do anything. This method is here just so that the + // client code can connect observers to it. }, - show: function(delay){ - // summary: Toggle the node to showing - // delay: Integer? - // Ammount of time to stall playing the show animation - return this.showAnim.play(delay || 0); - }, + close: function(/* object? */ request){ + // summary: + // Over-ride of base close function of ItemFileReadStore to add in check for store state. + // description: + // Over-ride of base close function of ItemFileReadStore to add in check for store state. + // If the store is still dirty (unsaved changes), then an error will be thrown instead of + // clearing the internal state for reload from the url. - hide: function(delay){ - // summary: Toggle the node to hidden - // delay: Integer? - // Ammount of time to stall playing the hide animation - return this.hideAnim.play(delay || 0); + //Clear if not dirty ... or throw an error + if(this.clearOnClose){ + if(!this.isDirty()){ + this.inherited(arguments); + }else{ + //Only throw an error if the store was dirty and we were loading from a url (cannot reload from url until state is saved). + throw new Error("dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close."); + } + } } }); -} - -if(!dojo._hasResource["dojo.fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.fx"] = true; -dojo.provide("dojo.fx"); - +}); +}, +'dijit/form/_RadioButtonMixin':function(){ +define("dijit/form/_RadioButtonMixin", [ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/_base/event", // event.stop + "dojo/_base/lang", // lang.hitch + "dojo/query", // query + "dojo/_base/window", // win.doc + "../registry" // registry.getEnclosingWidget +], function(array, declare, domAttr, event, lang, query, win, registry){ + + // module: + // dijit/form/_RadioButtonMixin + // summary: + // Mixin to provide widget functionality for an HTML radio button -/*===== -dojo.fx = { - // summary: Effects library on top of Base animations -}; -=====*/ -(function(){ - - var d = dojo, - _baseObj = { - _fire: function(evt, args){ - if(this[evt]){ - this[evt].apply(this, args||[]); - } - return this; - } - }; + return declare("dijit.form._RadioButtonMixin", null, { + // summary: + // Mixin to provide widget functionality for an HTML radio button - var _chain = function(animations){ - this._index = -1; - this._animations = animations||[]; - this._current = this._onAnimateCtx = this._onEndCtx = null; + // type: [private] String + // type attribute on <input> node. + // Users should not change this value. + type: "radio", - this.duration = 0; - d.forEach(this._animations, function(a){ - this.duration += a.duration; - if(a.delay){ this.duration += a.delay; } - }, this); - }; - d.extend(_chain, { - _onAnimate: function(){ - this._fire("onAnimate", arguments); - }, - _onEnd: function(){ - d.disconnect(this._onAnimateCtx); - d.disconnect(this._onEndCtx); - this._onAnimateCtx = this._onEndCtx = null; - if(this._index + 1 == this._animations.length){ - this._fire("onEnd"); - }else{ - // switch animations - this._current = this._animations[++this._index]; - this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate"); - this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd"); - this._current.play(0, true); - } - }, - play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){ - if(!this._current){ this._current = this._animations[this._index = 0]; } - if(!gotoStart && this._current.status() == "playing"){ return this; } - var beforeBegin = d.connect(this._current, "beforeBegin", this, function(){ - this._fire("beforeBegin"); - }), - onBegin = d.connect(this._current, "onBegin", this, function(arg){ - this._fire("onBegin", arguments); - }), - onPlay = d.connect(this._current, "onPlay", this, function(arg){ - this._fire("onPlay", arguments); - d.disconnect(beforeBegin); - d.disconnect(onBegin); - d.disconnect(onPlay); - }); - if(this._onAnimateCtx){ - d.disconnect(this._onAnimateCtx); - } - this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate"); - if(this._onEndCtx){ - d.disconnect(this._onEndCtx); - } - this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd"); - this._current.play.apply(this._current, arguments); - return this; + _getRelatedWidgets: function(){ + // Private function needed to help iterate over all radio buttons in a group. + var ary = []; + query("input[type=radio]", this.focusNode.form || win.doc).forEach( // can't use name= since query doesn't support [] in the name + lang.hitch(this, function(inputNode){ + if(inputNode.name == this.name && inputNode.form == this.focusNode.form){ + var widget = registry.getEnclosingWidget(inputNode); + if(widget){ + ary.push(widget); + } + } + }) + ); + return ary; }, - pause: function(){ - if(this._current){ - var e = d.connect(this._current, "onPause", this, function(arg){ - this._fire("onPause", arguments); - d.disconnect(e); - }); - this._current.pause(); + + _setCheckedAttr: function(/*Boolean*/ value){ + // If I am being checked then have to deselect currently checked radio button + this.inherited(arguments); + if(!this._created){ return; } + if(value){ + array.forEach(this._getRelatedWidgets(), lang.hitch(this, function(widget){ + if(widget != this && widget.checked){ + widget.set('checked', false); + } + })); } - return this; }, - gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){ - this.pause(); - var offset = this.duration * percent; - this._current = null; - d.some(this._animations, function(a){ - if(a.duration <= offset){ - this._current = a; - return true; - } - offset -= a.duration; + + _onClick: function(/*Event*/ e){ + if(this.checked || this.disabled){ // nothing to do + event.stop(e); return false; - }); - if(this._current){ - this._current.gotoPercent(offset / this._current.duration, andPlay); } - return this; - }, - stop: function(/*boolean?*/ gotoEnd){ - if(this._current){ - if(gotoEnd){ - for(; this._index + 1 < this._animations.length; ++this._index){ - this._animations[this._index].stop(true); - } - this._current = this._animations[this._index]; - } - var e = d.connect(this._current, "onStop", this, function(arg){ - this._fire("onStop", arguments); - d.disconnect(e); - }); - this._current.stop(); + if(this.readOnly){ // ignored by some browsers so we have to resync the DOM elements with widget values + event.stop(e); + array.forEach(this._getRelatedWidgets(), lang.hitch(this, function(widget){ + domAttr.set(this.focusNode || this.domNode, 'checked', widget.checked); + })); + return false; } - return this; - }, - status: function(){ - return this._current ? this._current.status() : "stopped"; - }, - destroy: function(){ - if(this._onAnimateCtx){ d.disconnect(this._onAnimateCtx); } - if(this._onEndCtx){ d.disconnect(this._onEndCtx); } + return this.inherited(arguments); } }); - d.extend(_chain, _baseObj); - - dojo.fx.chain = function(/*dojo.Animation[]*/ animations){ - // summary: - // Chain a list of `dojo.Animation`s to run in sequence - // - // description: - // Return a `dojo.Animation` which will play all passed - // `dojo.Animation` instances in sequence, firing its own - // synthesized events simulating a single animation. (eg: - // onEnd of this animation means the end of the chain, - // not the individual animations within) - // - // example: - // Once `node` is faded out, fade in `otherNode` - // | dojo.fx.chain([ - // | dojo.fadeIn({ node:node }), - // | dojo.fadeOut({ node:otherNode }) - // | ]).play(); - // - return new _chain(animations) // dojo.Animation - }; +}); - var _combine = function(animations){ - this._animations = animations||[]; - this._connects = []; - this._finished = 0; +}, +'url:dijit/templates/TreeNode.html':"<div class=\"dijitTreeNode\" role=\"presentation\"\n\t><div data-dojo-attach-point=\"rowNode\" class=\"dijitTreeRow\" role=\"presentation\" data-dojo-attach-event=\"onmouseenter:_onMouseEnter, onmouseleave:_onMouseLeave, onclick:_onClick, ondblclick:_onDblClick\"\n\t\t><img src=\"${_blankGif}\" alt=\"\" data-dojo-attach-point=\"expandoNode\" class=\"dijitTreeExpando\" role=\"presentation\"\n\t\t/><span data-dojo-attach-point=\"expandoNodeText\" class=\"dijitExpandoText\" role=\"presentation\"\n\t\t></span\n\t\t><span data-dojo-attach-point=\"contentNode\"\n\t\t\tclass=\"dijitTreeContent\" role=\"presentation\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" data-dojo-attach-point=\"iconNode\" class=\"dijitIcon dijitTreeIcon\" role=\"presentation\"\n\t\t\t/><span data-dojo-attach-point=\"labelNode\" class=\"dijitTreeLabel\" role=\"treeitem\" tabindex=\"-1\" aria-selected=\"false\" data-dojo-attach-event=\"onfocus:_onLabelFocus\"></span>\n\t\t</span\n\t></div>\n\t<div data-dojo-attach-point=\"containerNode\" class=\"dijitTreeContainer\" role=\"presentation\" style=\"display: none;\"></div>\n</div>\n", +'dojo/dnd/TimedMoveable':function(){ +define("dojo/dnd/TimedMoveable", ["../main", "./Moveable"], function(dojo) { + // module: + // dojo/dnd/TimedMoveable + // summary: + // TODOC - this.duration = 0; - d.forEach(animations, function(a){ - var duration = a.duration; - if(a.delay){ duration += a.delay; } - if(this.duration < duration){ this.duration = duration; } - this._connects.push(d.connect(a, "onEnd", this, "_onEnd")); - }, this); - - this._pseudoAnimation = new d.Animation({curve: [0, 1], duration: this.duration}); - var self = this; - d.forEach(["beforeBegin", "onBegin", "onPlay", "onAnimate", "onPause", "onStop", "onEnd"], - function(evt){ - self._connects.push(d.connect(self._pseudoAnimation, evt, - function(){ self._fire(evt, arguments); } - )); - } - ); - }; - d.extend(_combine, { - _doAction: function(action, args){ - d.forEach(this._animations, function(a){ - a[action].apply(a, args); - }); - return this; - }, - _onEnd: function(){ - if(++this._finished > this._animations.length){ - this._fire("onEnd"); - } - }, - _call: function(action, args){ - var t = this._pseudoAnimation; - t[action].apply(t, args); - }, - play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){ - this._finished = 0; - this._doAction("play", arguments); - this._call("play", arguments); - return this; - }, - pause: function(){ - this._doAction("pause", arguments); - this._call("pause", arguments); - return this; - }, - gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){ - var ms = this.duration * percent; - d.forEach(this._animations, function(a){ - a.gotoPercent(a.duration < ms ? 1 : (ms / a.duration), andPlay); - }); - this._call("gotoPercent", arguments); - return this; - }, - stop: function(/*boolean?*/ gotoEnd){ - this._doAction("stop", arguments); - this._call("stop", arguments); - return this; - }, - status: function(){ - return this._pseudoAnimation.status(); - }, - destroy: function(){ - d.forEach(this._connects, dojo.disconnect); - } + /*===== + dojo.declare("dojo.dnd.__TimedMoveableArgs", [dojo.dnd.__MoveableArgs], { + // timeout: Number + // delay move by this number of ms, + // accumulating position changes during the timeout + timeout: 0 }); - d.extend(_combine, _baseObj); + =====*/ - dojo.fx.combine = function(/*dojo.Animation[]*/ animations){ - // summary: - // Combine a list of `dojo.Animation`s to run in parallel - // - // description: - // Combine an array of `dojo.Animation`s to run in parallel, - // providing a new `dojo.Animation` instance encompasing each - // animation, firing standard animation events. - // - // example: - // Fade out `node` while fading in `otherNode` simultaneously - // | dojo.fx.combine([ - // | dojo.fadeIn({ node:node }), - // | dojo.fadeOut({ node:otherNode }) - // | ]).play(); - // - // example: - // When the longest animation ends, execute a function: - // | var anim = dojo.fx.combine([ - // | dojo.fadeIn({ node: n, duration:700 }), - // | dojo.fadeOut({ node: otherNode, duration: 300 }) - // | ]); - // | dojo.connect(anim, "onEnd", function(){ - // | // overall animation is done. - // | }); - // | anim.play(); // play the animation - // - return new _combine(animations); // dojo.Animation - }; + // precalculate long expressions + var oldOnMove = dojo.dnd.Moveable.prototype.onMove; - dojo.fx.wipeIn = function(/*Object*/ args){ + dojo.declare("dojo.dnd.TimedMoveable", dojo.dnd.Moveable, { // summary: - // Expand a node to it's natural height. - // - // description: - // Returns an animation that will expand the - // node defined in 'args' object from it's current height to - // it's natural height (with no scrollbar). - // Node must have no margin/border/padding. - // - // args: Object - // A hash-map of standard `dojo.Animation` constructor properties - // (such as easing: node: duration: and so on) - // - // example: - // | dojo.fx.wipeIn({ - // | node:"someId" - // | }).play() - var node = args.node = d.byId(args.node), s = node.style, o; - - var anim = d.animateProperty(d.mixin({ - properties: { - height: { - // wrapped in functions so we wait till the last second to query (in case value has changed) - start: function(){ - // start at current [computed] height, but use 1px rather than 0 - // because 0 causes IE to display the whole panel - o = s.overflow; - s.overflow = "hidden"; - if(s.visibility == "hidden" || s.display == "none"){ - s.height = "1px"; - s.display = ""; - s.visibility = ""; - return 1; - }else{ - var height = d.style(node, "height"); - return Math.max(height, 1); - } - }, - end: function(){ - return node.scrollHeight; - } - } - } - }, args)); + // A specialized version of Moveable to support an FPS throttling. + // This class puts an upper restriction on FPS, which may reduce + // the CPU load. The additional parameter "timeout" regulates + // the delay before actually moving the moveable object. - d.connect(anim, "onEnd", function(){ - s.height = "auto"; - s.overflow = o; - }); + // object attributes (for markup) + timeout: 40, // in ms, 40ms corresponds to 25 fps - return anim; // dojo.Animation - }; + constructor: function(node, params){ + // summary: + // an object that makes a node moveable with a timer + // node: Node||String + // a node (or node's id) to be moved + // params: dojo.dnd.__TimedMoveableArgs + // object with additional parameters. - dojo.fx.wipeOut = function(/*Object*/ args){ - // summary: - // Shrink a node to nothing and hide it. - // - // description: - // Returns an animation that will shrink node defined in "args" - // from it's current height to 1px, and then hide it. - // - // args: Object - // A hash-map of standard `dojo.Animation` constructor properties - // (such as easing: node: duration: and so on) - // - // example: - // | dojo.fx.wipeOut({ node:"someId" }).play() - - var node = args.node = d.byId(args.node), s = node.style, o; - - var anim = d.animateProperty(d.mixin({ - properties: { - height: { - end: 1 // 0 causes IE to display the whole panel - } + // sanitize parameters + if(!params){ params = {}; } + if(params.timeout && typeof params.timeout == "number" && params.timeout >= 0){ + this.timeout = params.timeout; } - }, args)); - - d.connect(anim, "beforeBegin", function(){ - o = s.overflow; - s.overflow = "hidden"; - s.display = ""; - }); - d.connect(anim, "onEnd", function(){ - s.overflow = o; - s.height = "auto"; - s.display = "none"; - }); - - return anim; // dojo.Animation - }; - - dojo.fx.slideTo = function(/*Object*/ args){ - // summary: - // Slide a node to a new top/left position - // - // description: - // Returns an animation that will slide "node" - // defined in args Object from its current position to - // the position defined by (args.left, args.top). - // - // args: Object - // A hash-map of standard `dojo.Animation` constructor properties - // (such as easing: node: duration: and so on). Special args members - // are `top` and `left`, which indicate the new position to slide to. - // - // example: - // | dojo.fx.slideTo({ node: node, left:"40", top:"50", units:"px" }).play() - - var node = args.node = d.byId(args.node), - top = null, left = null; - - var init = (function(n){ - return function(){ - var cs = d.getComputedStyle(n); - var pos = cs.position; - top = (pos == 'absolute' ? n.offsetTop : parseInt(cs.top) || 0); - left = (pos == 'absolute' ? n.offsetLeft : parseInt(cs.left) || 0); - if(pos != 'absolute' && pos != 'relative'){ - var ret = d.position(n, true); - top = ret.y; - left = ret.x; - n.style.position="absolute"; - n.style.top=top+"px"; - n.style.left=left+"px"; - } - }; - })(node); - init(); + }, - var anim = d.animateProperty(d.mixin({ - properties: { - top: args.top || 0, - left: args.left || 0 + onMoveStop: function(/* dojo.dnd.Mover */ mover){ + if(mover._timer){ + // stop timer + clearTimeout(mover._timer); + // reflect the last received position + oldOnMove.call(this, mover, mover._leftTop) } - }, args)); - d.connect(anim, "beforeBegin", anim, init); - - return anim; // dojo.Animation - }; - -})(); - -} - -if(!dojo._hasResource["dojo.NodeList-fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.NodeList-fx"] = true; -dojo.provide("dojo.NodeList-fx"); + dojo.dnd.Moveable.prototype.onMoveStop.apply(this, arguments); + }, + onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ + mover._leftTop = leftTop; + if(!mover._timer){ + var _t = this; // to avoid using dojo.hitch() + mover._timer = setTimeout(function(){ + // we don't have any pending requests + mover._timer = null; + // reflect the last received position + oldOnMove.call(_t, mover, mover._leftTop); + }, this.timeout); + } + } + }); + return dojo.dnd.TimedMoveable; + +}); +}, +'dojo/NodeList-fx':function(){ +define("dojo/NodeList-fx", ["dojo/_base/NodeList", "./_base/lang", "./_base/connect", "./_base/fx", "./fx"], + function(NodeList, lang, connectLib, baseFx, coreFx) { + // module: + // dojo/NodeList-fx + // summary: + // TODOC /*===== dojo["NodeList-fx"] = { - // summary: Adds dojo.fx animation support to dojo.query() + // summary: Adds dojo.fx animation support to dojo.query() by extending the NodeList class + // with additional FX functions. NodeList is the array-like object used to hold query results. }; + +// doc alias helpers: +NodeList = dojo.NodeList; =====*/ -dojo.extend(dojo.NodeList, { +lang.extend(NodeList, { _anim: function(obj, method, args){ args = args||{}; - var a = dojo.fx.combine( + var a = coreFx.combine( this.map(function(item){ var tmpArgs = { node: item }; - dojo.mixin(tmpArgs, args); + lang.mixin(tmpArgs, args); return obj[method](tmpArgs); }) ); @@ -6956,129 +11138,133 @@ dojo.extend(dojo.NodeList, { }, wipeIn: function(args){ - // summary: + // summary: // wipe in all elements of this NodeList via `dojo.fx.wipeIn` // - // args: Object? + // args: Object? // Additional dojo.Animation arguments to mix into this set with the addition of // an `auto` parameter. // - // returns: dojo.Animation|dojo.NodeList + // returns: dojo.Animation|dojo.NodeList // A special args member `auto` can be passed to automatically play the animation. // If args.auto is present, the original dojo.NodeList will be returned for further // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed // - // example: + // example: // Fade in all tables with class "blah": // | dojo.query("table.blah").wipeIn().play(); // - // example: + // example: // Utilizing `auto` to get the NodeList back: // | dojo.query(".titles").wipeIn({ auto:true }).onclick(someFunction); // - return this._anim(dojo.fx, "wipeIn", args); // dojo.Animation|dojo.NodeList + return this._anim(coreFx, "wipeIn", args); // dojo.Animation|dojo.NodeList }, wipeOut: function(args){ - // summary: + // summary: // wipe out all elements of this NodeList via `dojo.fx.wipeOut` // - // args: Object? + // args: Object? // Additional dojo.Animation arguments to mix into this set with the addition of // an `auto` parameter. // - // returns: dojo.Animation|dojo.NodeList + // returns: dojo.Animation|dojo.NodeList // A special args member `auto` can be passed to automatically play the animation. // If args.auto is present, the original dojo.NodeList will be returned for further // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed // - // example: + // example: // Wipe out all tables with class "blah": // | dojo.query("table.blah").wipeOut().play(); - return this._anim(dojo.fx, "wipeOut", args); // dojo.Animation|dojo.NodeList + return this._anim(coreFx, "wipeOut", args); // dojo.Animation|dojo.NodeList }, slideTo: function(args){ - // summary: + // summary: // slide all elements of the node list to the specified place via `dojo.fx.slideTo` // - // args: Object? + // args: Object? // Additional dojo.Animation arguments to mix into this set with the addition of // an `auto` parameter. // - // returns: dojo.Animation|dojo.NodeList + // returns: dojo.Animation|dojo.NodeList // A special args member `auto` can be passed to automatically play the animation. // If args.auto is present, the original dojo.NodeList will be returned for further // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed // - // example: + // example: // | Move all tables with class "blah" to 300/300: // | dojo.query("table.blah").slideTo({ // | left: 40, // | top: 50 // | }).play(); - return this._anim(dojo.fx, "slideTo", args); // dojo.Animation|dojo.NodeList + return this._anim(coreFx, "slideTo", args); // dojo.Animation|dojo.NodeList }, fadeIn: function(args){ - // summary: + // summary: // fade in all elements of this NodeList via `dojo.fadeIn` // - // args: Object? + // args: Object? // Additional dojo.Animation arguments to mix into this set with the addition of // an `auto` parameter. // - // returns: dojo.Animation|dojo.NodeList + // returns: dojo.Animation|dojo.NodeList // A special args member `auto` can be passed to automatically play the animation. // If args.auto is present, the original dojo.NodeList will be returned for further // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed // - // example: + // example: // Fade in all tables with class "blah": // | dojo.query("table.blah").fadeIn().play(); - return this._anim(dojo, "fadeIn", args); // dojo.Animation|dojo.NodeList + return this._anim(baseFx, "fadeIn", args); // dojo.Animation|dojo.NodeList }, fadeOut: function(args){ - // summary: + // summary: // fade out all elements of this NodeList via `dojo.fadeOut` // - // args: Object? + // args: Object? // Additional dojo.Animation arguments to mix into this set with the addition of // an `auto` parameter. // - // returns: dojo.Animation|dojo.NodeList + // returns: dojo.Animation|dojo.NodeList // A special args member `auto` can be passed to automatically play the animation. // If args.auto is present, the original dojo.NodeList will be returned for further // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed // - // example: + // example: // Fade out all elements with class "zork": // | dojo.query(".zork").fadeOut().play(); - // example: + // example: // Fade them on a delay and do something at the end: // | var fo = dojo.query(".zork").fadeOut(); // | dojo.connect(fo, "onEnd", function(){ /*...*/ }); // | fo.play(); - // example: + // example: // Using `auto`: // | dojo.query("li").fadeOut({ auto:true }).filter(filterFn).forEach(doit); // - return this._anim(dojo, "fadeOut", args); // dojo.Animation|dojo.NodeList + return this._anim(baseFx, "fadeOut", args); // dojo.Animation|dojo.NodeList }, animateProperty: function(args){ - // summary: + // summary: // Animate all elements of this NodeList across the properties specified. // syntax identical to `dojo.animateProperty` // + // args: Object? + // Additional dojo.Animation arguments to mix into this set with the addition of + // an `auto` parameter. + // // returns: dojo.Animation|dojo.NodeList // A special args member `auto` can be passed to automatically play the animation. // If args.auto is present, the original dojo.NodeList will be returned for further // chaining. Otherwise the dojo.Animation instance is returned and must be .play()'ed // - // example: + // example: // | dojo.query(".zork").animateProperty({ // | duration: 500, // | properties: { @@ -7094,7 +11280,7 @@ dojo.extend(dojo.NodeList, { // | height:240 // | } // | }).onclick(handler); - return this._anim(dojo, "animateProperty", args); // dojo.Animation|dojo.NodeList + return this._anim(baseFx, "animateProperty", args); // dojo.Animation|dojo.NodeList }, anim: function( /*Object*/ properties, @@ -7102,31 +11288,31 @@ dojo.extend(dojo.NodeList, { /*Function?*/ easing, /*Function?*/ onEnd, /*Integer?*/ delay){ - // summary: + // summary: // Animate one or more CSS properties for all nodes in this list. // The returned animation object will already be playing when it // is returned. See the docs for `dojo.anim` for full details. - // properties: Object + // properties: Object // the properties to animate. does NOT support the `auto` parameter like other // NodeList-fx methods. - // duration: Integer? + // duration: Integer? // Optional. The time to run the animations for - // easing: Function? + // easing: Function? // Optional. The easing function to use. - // onEnd: Function? + // onEnd: Function? // A function to be called when the animation ends - // delay: + // delay: // how long to delay playing the returned animation - // example: + // example: // Another way to fade out: // | dojo.query(".thinger").anim({ opacity: 0 }); - // example: + // example: // animate all elements with the "thigner" class to a width of 500 // pixels over half a second // | dojo.query(".thinger").anim({ width: 500 }, 700); - var canim = dojo.fx.combine( + var canim = coreFx.combine( this.map(function(item){ - return dojo.animateProperty({ + return baseFx.animateProperty({ node: item, properties: properties, duration: duration||350, @@ -7135,837 +11321,496 @@ dojo.extend(dojo.NodeList, { }) ); if(onEnd){ - dojo.connect(canim, "onEnd", onEnd); + connectLib.connect(canim, "onEnd", onEnd); } return canim.play(delay||0); // dojo.Animation } }); -} - -if(!dojo._hasResource["dojo.colors"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.colors"] = true; -dojo.provide("dojo.colors"); - -dojo.getObject("colors", true, dojo); +return NodeList; +}); -//TODO: this module appears to break naming conventions +}, +'dijit/form/_ListMouseMixin':function(){ +define("dijit/form/_ListMouseMixin", [ + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/touch", + "./_ListBase" +], function(declare, event, touch, _ListBase){ /*===== -dojo.colors = { - // summary: Color utilities -} +var _ListBase = dijit.form._ListBase; =====*/ -(function(){ - // this is a standard conversion prescribed by the CSS3 Color Module - var hue2rgb = function(m1, m2, h){ - if(h < 0){ ++h; } - if(h > 1){ --h; } - var h6 = 6 * h; - if(h6 < 1){ return m1 + (m2 - m1) * h6; } - if(2 * h < 1){ return m2; } - if(3 * h < 2){ return m1 + (m2 - m1) * (2 / 3 - h) * 6; } - return m1; - }; - - dojo.colorFromRgb = function(/*String*/ color, /*dojo.Color?*/ obj){ - // summary: - // get rgb(a) array from css-style color declarations - // description: - // this function can handle all 4 CSS3 Color Module formats: rgb, - // rgba, hsl, hsla, including rgb(a) with percentage values. - var m = color.toLowerCase().match(/^(rgba?|hsla?)\(([\s\.\-,%0-9]+)\)/); - if(m){ - var c = m[2].split(/\s*,\s*/), l = c.length, t = m[1], a; - if((t == "rgb" && l == 3) || (t == "rgba" && l == 4)){ - var r = c[0]; - if(r.charAt(r.length - 1) == "%"){ - // 3 rgb percentage values - a = dojo.map(c, function(x){ - return parseFloat(x) * 2.56; - }); - if(l == 4){ a[3] = c[3]; } - return dojo.colorFromArray(a, obj); // dojo.Color - } - return dojo.colorFromArray(c, obj); // dojo.Color - } - if((t == "hsl" && l == 3) || (t == "hsla" && l == 4)){ - // normalize hsl values - var H = ((parseFloat(c[0]) % 360) + 360) % 360 / 360, - S = parseFloat(c[1]) / 100, - L = parseFloat(c[2]) / 100, - // calculate rgb according to the algorithm - // recommended by the CSS3 Color Module - m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S, - m1 = 2 * L - m2; - a = [ - hue2rgb(m1, m2, H + 1 / 3) * 256, - hue2rgb(m1, m2, H) * 256, - hue2rgb(m1, m2, H - 1 / 3) * 256, - 1 - ]; - if(l == 4){ a[3] = c[3]; } - return dojo.colorFromArray(a, obj); // dojo.Color - } - } - return null; // dojo.Color - }; - - var confine = function(c, low, high){ - // summary: - // sanitize a color component by making sure it is a number, - // and clamping it to valid values - c = Number(c); - return isNaN(c) ? high : c < low ? low : c > high ? high : c; // Number - }; - - dojo.Color.prototype.sanitize = function(){ - // summary: makes sure that the object has correct attributes - var t = this; - t.r = Math.round(confine(t.r, 0, 255)); - t.g = Math.round(confine(t.g, 0, 255)); - t.b = Math.round(confine(t.b, 0, 255)); - t.a = confine(t.a, 0, 1); - return this; // dojo.Color - }; -})(); - - -dojo.colors.makeGrey = function(/*Number*/ g, /*Number?*/ a){ - // summary: creates a greyscale color with an optional alpha - return dojo.colorFromArray([g, g, g, a]); -}; - -// mixin all CSS3 named colors not already in _base, along with SVG 1.0 variant spellings -dojo.mixin(dojo.Color.named, { - aliceblue: [240,248,255], - antiquewhite: [250,235,215], - aquamarine: [127,255,212], - azure: [240,255,255], - beige: [245,245,220], - bisque: [255,228,196], - blanchedalmond: [255,235,205], - blueviolet: [138,43,226], - brown: [165,42,42], - burlywood: [222,184,135], - cadetblue: [95,158,160], - chartreuse: [127,255,0], - chocolate: [210,105,30], - coral: [255,127,80], - cornflowerblue: [100,149,237], - cornsilk: [255,248,220], - crimson: [220,20,60], - cyan: [0,255,255], - darkblue: [0,0,139], - darkcyan: [0,139,139], - darkgoldenrod: [184,134,11], - darkgray: [169,169,169], - darkgreen: [0,100,0], - darkgrey: [169,169,169], - darkkhaki: [189,183,107], - darkmagenta: [139,0,139], - darkolivegreen: [85,107,47], - darkorange: [255,140,0], - darkorchid: [153,50,204], - darkred: [139,0,0], - darksalmon: [233,150,122], - darkseagreen: [143,188,143], - darkslateblue: [72,61,139], - darkslategray: [47,79,79], - darkslategrey: [47,79,79], - darkturquoise: [0,206,209], - darkviolet: [148,0,211], - deeppink: [255,20,147], - deepskyblue: [0,191,255], - dimgray: [105,105,105], - dimgrey: [105,105,105], - dodgerblue: [30,144,255], - firebrick: [178,34,34], - floralwhite: [255,250,240], - forestgreen: [34,139,34], - gainsboro: [220,220,220], - ghostwhite: [248,248,255], - gold: [255,215,0], - goldenrod: [218,165,32], - greenyellow: [173,255,47], - grey: [128,128,128], - honeydew: [240,255,240], - hotpink: [255,105,180], - indianred: [205,92,92], - indigo: [75,0,130], - ivory: [255,255,240], - khaki: [240,230,140], - lavender: [230,230,250], - lavenderblush: [255,240,245], - lawngreen: [124,252,0], - lemonchiffon: [255,250,205], - lightblue: [173,216,230], - lightcoral: [240,128,128], - lightcyan: [224,255,255], - lightgoldenrodyellow: [250,250,210], - lightgray: [211,211,211], - lightgreen: [144,238,144], - lightgrey: [211,211,211], - lightpink: [255,182,193], - lightsalmon: [255,160,122], - lightseagreen: [32,178,170], - lightskyblue: [135,206,250], - lightslategray: [119,136,153], - lightslategrey: [119,136,153], - lightsteelblue: [176,196,222], - lightyellow: [255,255,224], - limegreen: [50,205,50], - linen: [250,240,230], - magenta: [255,0,255], - mediumaquamarine: [102,205,170], - mediumblue: [0,0,205], - mediumorchid: [186,85,211], - mediumpurple: [147,112,219], - mediumseagreen: [60,179,113], - mediumslateblue: [123,104,238], - mediumspringgreen: [0,250,154], - mediumturquoise: [72,209,204], - mediumvioletred: [199,21,133], - midnightblue: [25,25,112], - mintcream: [245,255,250], - mistyrose: [255,228,225], - moccasin: [255,228,181], - navajowhite: [255,222,173], - oldlace: [253,245,230], - olivedrab: [107,142,35], - orange: [255,165,0], - orangered: [255,69,0], - orchid: [218,112,214], - palegoldenrod: [238,232,170], - palegreen: [152,251,152], - paleturquoise: [175,238,238], - palevioletred: [219,112,147], - papayawhip: [255,239,213], - peachpuff: [255,218,185], - peru: [205,133,63], - pink: [255,192,203], - plum: [221,160,221], - powderblue: [176,224,230], - rosybrown: [188,143,143], - royalblue: [65,105,225], - saddlebrown: [139,69,19], - salmon: [250,128,114], - sandybrown: [244,164,96], - seagreen: [46,139,87], - seashell: [255,245,238], - sienna: [160,82,45], - skyblue: [135,206,235], - slateblue: [106,90,205], - slategray: [112,128,144], - slategrey: [112,128,144], - snow: [255,250,250], - springgreen: [0,255,127], - steelblue: [70,130,180], - tan: [210,180,140], - thistle: [216,191,216], - tomato: [255,99,71], - transparent: [0, 0, 0, 0], - turquoise: [64,224,208], - violet: [238,130,238], - wheat: [245,222,179], - whitesmoke: [245,245,245], - yellowgreen: [154,205,50] -}); - -} - -if(!dojo._hasResource["dojo.i18n"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.i18n"] = true; -dojo.provide("dojo.i18n"); +// module: +// dijit/form/_ListMouseMixin +// summary: +// a mixin to handle mouse or touch events for a focus-less menu -dojo.getObject("i18n", true, dojo); +return declare( "dijit.form._ListMouseMixin", _ListBase, { + // summary: + // a Mixin to handle mouse or touch events for a focus-less menu + // Abstract methods that must be defined externally: + // onClick: item was chosen (mousedown somewhere on the menu and mouseup somewhere on the menu) + // tags: + // private -/*===== -dojo.i18n = { - // summary: Utility classes to enable loading of resources for internationalization (i18n) -}; -=====*/ + postCreate: function(){ + this.inherited(arguments); + this.connect(this.domNode, touch.press, "_onMouseDown"); + this.connect(this.domNode, touch.release, "_onMouseUp"); + this.connect(this.domNode, "onmouseover", "_onMouseOver"); + this.connect(this.domNode, "onmouseout", "_onMouseOut"); + }, -// when using a real AMD loader, dojo.i18n.getLocalization is already defined by dojo/lib/backCompat -dojo.i18n.getLocalization = dojo.i18n.getLocalization || function(/*String*/packageName, /*String*/bundleName, /*String?*/locale){ - // summary: - // Returns an Object containing the localization for a given resource - // bundle in a package, matching the specified locale. - // description: - // Returns a hash containing name/value pairs in its prototypesuch - // that values can be easily overridden. Throws an exception if the - // bundle is not found. Bundle must have already been loaded by - // `dojo.requireLocalization()` or by a build optimization step. NOTE: - // try not to call this method as part of an object property - // definition (`var foo = { bar: dojo.i18n.getLocalization() }`). In - // some loading situations, the bundle may not be available in time - // for the object definition. Instead, call this method inside a - // function that is run after all modules load or the page loads (like - // in `dojo.addOnLoad()`), or in a widget lifecycle method. - // packageName: - // package which is associated with this resource - // bundleName: - // the base filename of the resource bundle (without the ".js" suffix) - // locale: - // the variant to load (optional). By default, the locale defined by - // the host environment: dojo.locale - - locale = dojo.i18n.normalizeLocale(locale); - - // look for nearest locale match - var elements = locale.split('-'); - var module = [packageName,"nls",bundleName].join('.'); - var bundle = dojo._loadedModules[module]; - if(bundle){ - var localization; - for(var i = elements.length; i > 0; i--){ - var loc = elements.slice(0, i).join('_'); - if(bundle[loc]){ - localization = bundle[loc]; - break; - } - } - if(!localization){ - localization = bundle.ROOT; + _onMouseDown: function(/*Event*/ evt){ + event.stop(evt); + if(this._hoveredNode){ + this.onUnhover(this._hoveredNode); + this._hoveredNode = null; } + this._isDragging = true; + this._setSelectedAttr(this._getTarget(evt)); + }, - // make a singleton prototype so that the caller won't accidentally change the values globally - if(localization){ - var clazz = function(){}; - clazz.prototype = localization; - return new clazz(); // Object + _onMouseUp: function(/*Event*/ evt){ + event.stop(evt); + this._isDragging = false; + var selectedNode = this._getSelectedAttr(); + var target = this._getTarget(evt); + var hoveredNode = this._hoveredNode; + if(selectedNode && target == selectedNode){ + this.onClick(selectedNode); + }else if(hoveredNode && target == hoveredNode){ // drag to select + this._setSelectedAttr(hoveredNode); + this.onClick(hoveredNode); } - } - - throw new Error("Bundle not found: " + bundleName + " in " + packageName+" , locale=" + locale); -}; - -dojo.i18n.normalizeLocale = function(/*String?*/locale){ - // summary: - // Returns canonical form of locale, as used by Dojo. - // - // description: - // All variants are case-insensitive and are separated by '-' as specified in [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt). - // If no locale is specified, the dojo.locale is returned. dojo.locale is defined by - // the user agent's locale unless overridden by djConfig. - - var result = locale ? locale.toLowerCase() : dojo.locale; - if(result == "root"){ - result = "ROOT"; - } - return result; // String -}; - -dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundleName, /*String?*/locale, /*String?*/availableFlatLocales){ - // summary: - // See dojo.requireLocalization() - // description: - // Called by the bootstrap, but factored out so that it is only - // included in the build when needed. + }, - var targetLocale = dojo.i18n.normalizeLocale(locale); - var bundlePackage = [moduleName, "nls", bundleName].join("."); - // NOTE: - // When loading these resources, the packaging does not match what is - // on disk. This is an implementation detail, as this is just a - // private data structure to hold the loaded resources. e.g. - // `tests/hello/nls/en-us/salutations.js` is loaded as the object - // `tests.hello.nls.salutations.en_us={...}` The structure on disk is - // intended to be most convenient for developers and translators, but - // in memory it is more logical and efficient to store in a different - // order. Locales cannot use dashes, since the resulting path will - // not evaluate as valid JS, so we translate them to underscores. - - //Find the best-match locale to load if we have available flat locales. - var bestLocale = ""; - if(availableFlatLocales){ - var flatLocales = availableFlatLocales.split(","); - for(var i = 0; i < flatLocales.length; i++){ - //Locale must match from start of string. - //Using ["indexOf"] so customBase builds do not see - //this as a dojo._base.array dependency. - if(targetLocale["indexOf"](flatLocales[i]) == 0){ - if(flatLocales[i].length > bestLocale.length){ - bestLocale = flatLocales[i]; - } - } - } - if(!bestLocale){ - bestLocale = "ROOT"; + _onMouseOut: function(/*Event*/ /*===== evt ====*/){ + if(this._hoveredNode){ + this.onUnhover(this._hoveredNode); + if(this._getSelectedAttr() == this._hoveredNode){ + this.onSelect(this._hoveredNode); + } + this._hoveredNode = null; } - } - - //See if the desired locale is already loaded. - var tempLocale = availableFlatLocales ? bestLocale : targetLocale; - var bundle = dojo._loadedModules[bundlePackage]; - var localizedBundle = null; - if(bundle){ - if(dojo.config.localizationComplete && bundle._built){return;} - var jsLoc = tempLocale.replace(/-/g, '_'); - var translationPackage = bundlePackage+"."+jsLoc; - localizedBundle = dojo._loadedModules[translationPackage]; - } + if(this._isDragging){ + this._cancelDrag = (new Date()).getTime() + 1000; // cancel in 1 second if no _onMouseOver fires + } + }, - if(!localizedBundle){ - bundle = dojo["provide"](bundlePackage); - var syms = dojo._getModuleSymbols(moduleName); - var modpath = syms.concat("nls").join("/"); - var parent; - - dojo.i18n._searchLocalePath(tempLocale, availableFlatLocales, function(loc){ - var jsLoc = loc.replace(/-/g, '_'); - var translationPackage = bundlePackage + "." + jsLoc; - var loaded = false; - if(!dojo._loadedModules[translationPackage]){ - // Mark loaded whether it's found or not, so that further load attempts will not be made - dojo["provide"](translationPackage); - var module = [modpath]; - if(loc != "ROOT"){module.push(loc);} - module.push(bundleName); - var filespec = module.join("/") + '.js'; - loaded = dojo._loadPath(filespec, null, function(hash){ - hash = hash.root || hash; - // Use singleton with prototype to point to parent bundle, then mix-in result from loadPath - var clazz = function(){}; - clazz.prototype = parent; - bundle[jsLoc] = new clazz(); - for(var j in hash){ bundle[jsLoc][j] = hash[j]; } - }); - }else{ - loaded = true; + _onMouseOver: function(/*Event*/ evt){ + if(this._cancelDrag){ + var time = (new Date()).getTime(); + if(time > this._cancelDrag){ + this._isDragging = false; } - if(loaded && bundle[jsLoc]){ - parent = bundle[jsLoc]; - }else{ - bundle[jsLoc] = parent; + this._cancelDrag = null; + } + var node = this._getTarget(evt); + if(!node){ return; } + if(this._hoveredNode != node){ + if(this._hoveredNode){ + this._onMouseOut({ target: this._hoveredNode }); } - - if(availableFlatLocales){ - //Stop the locale path searching if we know the availableFlatLocales, since - //the first call to this function will load the only bundle that is needed. - return true; + if(node && node.parentNode == this.containerNode){ + if(this._isDragging){ + this._setSelectedAttr(node); + }else{ + this._hoveredNode = node; + this.onHover(node); + } } - }); + } } +}); - //Save the best locale bundle as the target locale bundle when we know the - //the available bundles. - if(availableFlatLocales && targetLocale != bestLocale){ - bundle[targetLocale.replace(/-/g, '_')] = bundle[bestLocale.replace(/-/g, '_')]; - } -}; +}); -(function(){ - // If other locales are used, dojo.requireLocalization should load them as - // well, by default. - // - // Override dojo.requireLocalization to do load the default bundle, then - // iterate through the extraLocale list and load those translations as - // well, unless a particular locale was requested. +}, +'url:dijit/templates/Tree.html':"<div class=\"dijitTree dijitTreeContainer\" role=\"tree\"\n\tdata-dojo-attach-event=\"onkeypress:_onKeyPress\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" data-dojo-attach-point=\"indentDetector\"></div>\n</div>\n", +'dojo/cookie':function(){ +define("dojo/cookie", ["./_base/kernel", "./regexp"], function(dojo, regexp) { + // module: + // dojo/cookie + // summary: + // TODOC - var extra = dojo.config.extraLocale; - if(extra){ - if(!extra instanceof Array){ - extra = [extra]; - } - var req = dojo.i18n._requireLocalization; - dojo.i18n._requireLocalization = function(m, b, locale, availableFlatLocales){ - req(m,b,locale, availableFlatLocales); - if(locale){return;} - for(var i=0; i<extra.length; i++){ - req(m,b,extra[i], availableFlatLocales); - } - }; - } -})(); - -dojo.i18n._searchLocalePath = function(/*String*/locale, /*Boolean*/down, /*Function*/searchFunc){ - // summary: - // A helper method to assist in searching for locale-based resources. - // Will iterate through the variants of a particular locale, either up - // or down, executing a callback function. For example, "en-us" and - // true will try "en-us" followed by "en" and finally "ROOT". +/*===== +dojo.__cookieProps = function(){ + // expires: Date|String|Number? + // If a number, the number of days from today at which the cookie + // will expire. If a date, the date past which the cookie will expire. + // If expires is in the past, the cookie will be deleted. + // If expires is omitted or is 0, the cookie will expire when the browser closes. + // path: String? + // The path to use for the cookie. + // domain: String? + // The domain to use for the cookie. + // secure: Boolean? + // Whether to only send the cookie on secure connections + this.expires = expires; + this.path = path; + this.domain = domain; + this.secure = secure; +} +=====*/ - locale = dojo.i18n.normalizeLocale(locale); - var elements = locale.split('-'); - var searchlist = []; - for(var i = elements.length; i > 0; i--){ - searchlist.push(elements.slice(0, i).join('-')); - } - searchlist.push(false); - if(down){searchlist.reverse();} +dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/props){ + // summary: + // Get or set a cookie. + // description: + // If one argument is passed, returns the value of the cookie + // For two or more arguments, acts as a setter. + // name: + // Name of the cookie + // value: + // Value for the cookie + // props: + // Properties for the cookie + // example: + // set a cookie with the JSON-serialized contents of an object which + // will expire 5 days from now: + // | dojo.cookie("configObj", dojo.toJson(config), { expires: 5 }); + // + // example: + // de-serialize a cookie back into a JavaScript object: + // | var config = dojo.fromJson(dojo.cookie("configObj")); + // + // example: + // delete a cookie: + // | dojo.cookie("configObj", null, {expires: -1}); + var c = document.cookie, ret; + if(arguments.length == 1){ + var matches = c.match(new RegExp("(?:^|; )" + regexp.escapeString(name) + "=([^;]*)")); + ret = matches ? decodeURIComponent(matches[1]) : undefined; + }else{ + props = props || {}; +// FIXME: expires=0 seems to disappear right away, not on close? (FF3) Change docs? + var exp = props.expires; + if(typeof exp == "number"){ + var d = new Date(); + d.setTime(d.getTime() + exp*24*60*60*1000); + exp = props.expires = d; + } + if(exp && exp.toUTCString){ props.expires = exp.toUTCString(); } - for(var j = searchlist.length - 1; j >= 0; j--){ - var loc = searchlist[j] || "ROOT"; - var stop = searchFunc(loc); - if(stop){ break; } + value = encodeURIComponent(value); + var updatedCookie = name + "=" + value, propName; + for(propName in props){ + updatedCookie += "; " + propName; + var propValue = props[propName]; + if(propValue !== true){ updatedCookie += "=" + propValue; } + } + document.cookie = updatedCookie; } + return ret; // String|undefined }; -dojo.i18n._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated){ +dojo.cookie.isSupported = function(){ // summary: - // Load built, flattened resource bundles, if available for all - // locales used in the page. Only called by built layer files. - - function preload(locale){ - locale = dojo.i18n.normalizeLocale(locale); - dojo.i18n._searchLocalePath(locale, true, function(loc){ - for(var i=0; i<localesGenerated.length;i++){ - if(localesGenerated[i] == loc){ - dojo["require"](bundlePrefix+"_"+loc); - return true; // Boolean - } - } - return false; // Boolean - }); - } - preload(); - var extra = dojo.config.extraLocale||[]; - for(var i=0; i<extra.length; i++){ - preload(extra[i]); + // Use to determine if the current browser supports cookies or not. + // + // Returns true if user allows cookies. + // Returns false if user doesn't allow cookies. + + if(!("cookieEnabled" in navigator)){ + this("__djCookieTest__", "CookiesAllowed"); + navigator.cookieEnabled = this("__djCookieTest__") == "CookiesAllowed"; + if(navigator.cookieEnabled){ + this("__djCookieTest__", "", {expires: -1}); + } } + return navigator.cookieEnabled; }; -} - -if(!dojo._hasResource["dijit._PaletteMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._PaletteMixin"] = true; -dojo.provide("dijit._PaletteMixin"); - - +return dojo.cookie; +}); -dojo.declare("dijit._PaletteMixin", - [dijit._CssStateMixin], - { +}, +'dojo/cache':function(){ +define("dojo/cache", ["./_base/kernel", "./text"], function(dojo, text){ + // module: + // dojo/cache // summary: - // A keyboard accessible palette, for picking a color/emoticon/etc. - // description: - // A mixin for a grid showing various entities, so the user can pick a certain entity. + // The module defines dojo.cache by loading dojo/text. - // defaultTimeout: Number - // Number of milliseconds before a held key or button becomes typematic - defaultTimeout: 500, - - // timeoutChangeRate: Number - // Fraction of time used to change the typematic timer between events - // 1.0 means that each typematic event fires at defaultTimeout intervals - // < 1.0 means that each typematic event fires at an increasing faster rate - timeoutChangeRate: 0.90, - - // value: String - // Currently selected color/emoticon/etc. - value: null, - - // _selectedCell: [private] Integer - // Index of the currently selected cell. Initially, none selected - _selectedCell: -1, - -/*===== - // _currentFocus: [private] DomNode - // The currently focused cell (if the palette itself has focus), or otherwise - // the cell to be focused when the palette itself gets focus. - // Different from value, which represents the selected (i.e. clicked) cell. - _currentFocus: null, -=====*/ + //dojo.cache is defined in dojo/text + return dojo.cache; +}); -/*===== - // _xDim: [protected] Integer - // This is the number of cells horizontally across. - _xDim: null, -=====*/ +}, +'url:dijit/form/templates/DropDownBox.html':"<div class=\"dijit dijitReset dijitInline dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdata-dojo-attach-point=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdata-dojo-attach-point=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n", +'dijit/ProgressBar':function(){ +require({cache:{ +'url:dijit/templates/ProgressBar.html':"<div class=\"dijitProgressBar dijitProgressBarEmpty\" role=\"progressbar\"\n\t><div data-dojo-attach-point=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\" role=\"presentation\"></div\n\t\t><span style=\"visibility:hidden\"> </span\n\t></div\n\t><div data-dojo-attach-point=\"labelNode\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"></div\n\t><img data-dojo-attach-point=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n"}}); +define("dijit/ProgressBar", [ + "require", // require.toUrl + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.toggle + "dojo/_base/lang", // lang.mixin + "dojo/number", // number.format + "./_Widget", + "./_TemplatedMixin", + "dojo/text!./templates/ProgressBar.html" +], function(require, declare, domClass, lang, number, _Widget, _TemplatedMixin, template){ /*===== - // _yDim: [protected] Integer - // This is the number of cells vertically down. - _yDim: null, + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; =====*/ - // tabIndex: String - // Widget tab index. - tabIndex: "0", +// module: +// dijit/ProgressBar +// summary: +// A progress indication widget, showing the amount completed +// (often the percentage completed) of a task. - // cellClass: [protected] String - // CSS class applied to each cell in the palette - cellClass: "dijitPaletteCell", - // dyeClass: [protected] String - // Name of javascript class for Object created for each cell of the palette. - // dyeClass should implements dijit.Dye interface - dyeClass: '', +return declare("dijit.ProgressBar", [_Widget, _TemplatedMixin], { + // summary: + // A progress indication widget, showing the amount completed + // (often the percentage completed) of a task. + // + // example: + // | <div data-dojo-type="ProgressBar" + // | places="0" + // | value="..." maximum="..."> + // | </div> - _preparePalette: function(choices, titles, dyeClassObj) { - // summary: - // Subclass must call _preparePalette() from postCreate(), passing in the tooltip - // for each cell - // choices: String[][] - // id's for each cell of the palette, used to create Dye JS object for each cell - // titles: String[] - // Localized tooltip for each cell - // dyeClassObj: Constructor? - // If specified, use this constructor rather than this.dyeClass + // progress: [const] String (Percentage or Number) + // Number or percentage indicating amount of task completed. + // Deprecated. Use "value" instead. + progress: "0", - this._cells = []; - var url = this._blankGif; - - dyeClassObj = dyeClassObj || dojo.getObject(this.dyeClass); + // value: String (Percentage or Number) + // Number or percentage indicating amount of task completed. + // With "%": percentage value, 0% <= progress <= 100%, or + // without "%": absolute value, 0 <= progress <= maximum. + // Infinity means that the progress bar is indeterminate. + value: "", - for(var row=0; row < choices.length; row++){ - var rowNode = dojo.create("tr", {tabIndex: "-1"}, this.gridNode); - for(var col=0; col < choices[row].length; col++){ - var value = choices[row][col]; - if(value){ - var cellObject = new dyeClassObj(value, row, col); - - var cellNode = dojo.create("td", { - "class": this.cellClass, - tabIndex: "-1", - title: titles[value] - }); + // maximum: [const] Float + // Max sample number + maximum: 100, - // prepare cell inner structure - cellObject.fillCell(cellNode, url); + // places: [const] Number + // Number of places to show in values; 0 by default + places: 0, - this.connect(cellNode, "ondijitclick", "_onCellClick"); - this._trackMouseState(cellNode, this.cellClass); + // indeterminate: [const] Boolean + // If false: show progress value (number or percentage). + // If true: show that a process is underway but that the amount completed is unknown. + // Deprecated. Use "value" instead. + indeterminate: false, - dojo.place(cellNode, rowNode); + // label: String? + // Label on progress bar. Defaults to percentage for determinate progress bar and + // blank for indeterminate progress bar. + label:"", - cellNode.index = this._cells.length; + // name: String + // this is the field name (for a form) if set. This needs to be set if you want to use + // this widget in a dijit.form.Form widget (such as dijit.Dialog) + name: '', - // save cell info into _cells - this._cells.push({node:cellNode, dye:cellObject}); - } - } - } - this._xDim = choices[0].length; - this._yDim = choices.length; + templateString: template, - // Now set all events - // The palette itself is navigated to with the tab key on the keyboard - // Keyboard navigation within the Palette is with the arrow keys - // Spacebar selects the cell. - // For the up key the index is changed by negative the x dimension. + // _indeterminateHighContrastImagePath: [private] URL + // URL to image to use for indeterminate progress bar when display is in high contrast mode + _indeterminateHighContrastImagePath: + require.toUrl("./themes/a11y/indeterminate_progress.gif"), - var keyIncrementMap = { - UP_ARROW: -this._xDim, - // The down key the index is increase by the x dimension. - DOWN_ARROW: this._xDim, - // Right and left move the index by 1. - RIGHT_ARROW: this.isLeftToRight() ? 1 : -1, - LEFT_ARROW: this.isLeftToRight() ? -1 : 1 - }; - for(var key in keyIncrementMap){ - this._connects.push( - dijit.typematic.addKeyListener( - this.domNode, - {charOrCode:dojo.keys[key], ctrlKey:false, altKey:false, shiftKey:false}, - this, - function(){ - var increment = keyIncrementMap[key]; - return function(count){ this._navigateByKey(increment, count); }; - }(), - this.timeoutChangeRate, - this.defaultTimeout - ) - ); + postMixInProperties: function(){ + this.inherited(arguments); + if(!("value" in this.params)){ + this.value = this.indeterminate ? Infinity : this.progress; } }, - postCreate: function(){ + buildRendering: function(){ this.inherited(arguments); - - // Set initial navigable node. - this._setCurrent(this._cells[0].node); - }, - - focus: function(){ - // summary: - // Focus this widget. Puts focus on the most recently focused cell. - - // The cell already has tabIndex set, just need to set CSS and focus it - dijit.focus(this._currentFocus); + this.indeterminateHighContrastImage.setAttribute("src", + this._indeterminateHighContrastImagePath.toString()); + this.update(); }, - _onCellClick: function(/*Event*/ evt){ + update: function(/*Object?*/attributes){ // summary: - // Handler for click, enter key & space key. Selects the cell. - // evt: - // The event. + // Internal method to change attributes of ProgressBar, similar to set(hash). Users should call + // set("value", ...) rather than calling this method directly. + // attributes: + // May provide progress and/or maximum properties on this parameter; + // see attribute specs for details. + // example: + // | myProgressBar.update({'indeterminate': true}); + // | myProgressBar.update({'progress': 80}); + // | myProgressBar.update({'indeterminate': true, label:"Loading ..." }) // tags: // private - var target = evt.currentTarget, - value = this._getDye(target).getValue(); - - // First focus the clicked cell, and then send onChange() notification. - // onChange() (via _setValueAttr) must be after the focus call, because - // it may trigger a refocus to somewhere else (like the Editor content area), and that - // second focus should win. - // Use setTimeout because IE doesn't like changing focus inside of an event handler. - this._setCurrent(target); - setTimeout(dojo.hitch(this, function(){ - dijit.focus(target); - this._setValueAttr(value, true); - })); - - // workaround bug where hover class is not removed on popup because the popup is - // closed and then there's no onblur event on the cell - dojo.removeClass(target, "dijitPaletteCellHover"); + // TODO: deprecate this method and use set() instead - dojo.stopEvent(evt); - }, + lang.mixin(this, attributes || {}); + var tip = this.internalProgress, ap = this.domNode; + var percent = 1; + if(this.indeterminate){ + ap.removeAttribute("aria-valuenow"); + ap.removeAttribute("aria-valuemin"); + ap.removeAttribute("aria-valuemax"); + }else{ + if(String(this.progress).indexOf("%") != -1){ + percent = Math.min(parseFloat(this.progress)/100, 1); + this.progress = percent * this.maximum; + }else{ + this.progress = Math.min(this.progress, this.maximum); + percent = this.maximum ? this.progress / this.maximum : 0; + } - _setCurrent: function(/*DomNode*/ node){ - // summary: - // Sets which node is the focused cell. - // description: - // At any point in time there's exactly one - // cell with tabIndex != -1. If focus is inside the palette then - // focus is on that cell. - // - // After calling this method, arrow key handlers and mouse click handlers - // should focus the cell in a setTimeout(). - // tags: - // protected - if("_currentFocus" in this){ - // Remove tabIndex on old cell - dojo.attr(this._currentFocus, "tabIndex", "-1"); + ap.setAttribute("aria-describedby", this.labelNode.id); + ap.setAttribute("aria-valuenow", this.progress); + ap.setAttribute("aria-valuemin", 0); + ap.setAttribute("aria-valuemax", this.maximum); } + this.labelNode.innerHTML = this.report(percent); - // Set tabIndex of new cell - this._currentFocus = node; - if(node){ - dojo.attr(node, "tabIndex", this.tabIndex); - } + domClass.toggle(this.domNode, "dijitProgressBarIndeterminate", this.indeterminate); + tip.style.width = (percent * 100) + "%"; + this.onChange(); }, - _setValueAttr: function(value, priorityChange){ - // summary: - // This selects a cell. It triggers the onChange event. - // value: String value of the cell to select - // tags: - // protected - // priorityChange: - // Optional parameter used to tell the select whether or not to fire - // onChange event. - - // clear old selected cell - if(this._selectedCell >= 0){ - dojo.removeClass(this._cells[this._selectedCell].node, "dijitPaletteCellSelected"); - } - this._selectedCell = -1; - - // search for cell matching specified value - if(value){ - for(var i = 0; i < this._cells.length; i++){ - if(value == this._cells[i].dye.getValue()){ - this._selectedCell = i; - dojo.addClass(this._cells[i].node, "dijitPaletteCellSelected"); - break; - } - } + _setValueAttr: function(v){ + this._set("value", v); + if(v == Infinity){ + this.update({indeterminate:true}); + }else{ + this.update({indeterminate:false, progress:v}); } - - // record new value, or null if no matching cell - this._set("value", this._selectedCell >= 0 ? value : null); + }, - if(priorityChange || priorityChange === undefined){ - this.onChange(value); - } + _setLabelAttr: function(label){ + this._set("label", label); + this.update(); }, - onChange: function(value){ - // summary: - // Callback when a cell is selected. - // value: String - // Value corresponding to cell. + _setIndeterminateAttr: function(indeterminate){ + // Deprecated, use set("value", ...) instead + this.indeterminate = indeterminate; + this.update(); }, - _navigateByKey: function(increment, typeCount){ + report: function(/*float*/percent){ // summary: - // This is the callback for typematic. - // It changes the focus and the highlighed cell. - // increment: - // How much the key is navigated. - // typeCount: - // How many times typematic has fired. + // Generates message to show inside progress bar (normally indicating amount of task completed). + // May be overridden. // tags: - // private - - // typecount == -1 means the key is released. - if(typeCount == -1){ return; } - - var newFocusIndex = this._currentFocus.index + increment; - if(newFocusIndex < this._cells.length && newFocusIndex > -1){ - var focusNode = this._cells[newFocusIndex].node; - this._setCurrent(focusNode); + // extension - // Actually focus the node, for the benefit of screen readers. - // Use setTimeout because IE doesn't like changing focus inside of an event handler - setTimeout(dojo.hitch(dijit, "focus", focusNode), 0); - } + return this.label ? this.label : + (this.indeterminate ? " " : number.format(percent, { type: "percent", places: this.places, locale: this.lang })); }, - _getDye: function(/*DomNode*/ cell){ + onChange: function(){ // summary: - // Get JS object for given cell DOMNode - - return this._cells[cell.index].dye; + // Callback fired when progress updates. + // tags: + // extension } }); -/*===== -dojo.declare("dijit.Dye", - null, - { - // summary: - // Interface for the JS Object associated with a palette cell (i.e. DOMNode) - - constructor: function(alias, row, col){ - // summary: - // Initialize according to value or alias like "white" - // alias: String - }, +}); - getValue: function(){ - // summary: - // Return "value" of cell; meaning of "value" varies by subclass. - // description: - // For example color hex value, emoticon ascii value etc, entity hex value. - }, +}, +'dijit/_base/popup':function(){ +define("dijit/_base/popup", [ + "dojo/dom-class", // domClass.contains + "../popup", + "../BackgroundIframe" // just loading for back-compat, in case client code is referencing it +], function(domClass, popup){ + +// module: +// dijit/_base/popup +// summary: +// Old module for popups, new code should use dijit/popup directly + + +// Hack support for old API passing in node instead of a widget (to various methods) +var origCreateWrapper = popup._createWrapper; +popup._createWrapper = function(widget){ + if(!widget.declaredClass){ + // make fake widget to pass to new API + widget = { + _popupWrapper: (widget.parentNode && domClass.contains(widget.parentNode, "dijitPopup")) ? + widget.parentNode : null, + domNode: widget, + destroy: function(){} + }; + } + return origCreateWrapper.call(this, widget); +}; - fillCell: function(cell, blankGif){ - // summary: - // Add cell DOMNode inner structure - // cell: DomNode - // The surrounding cell - // blankGif: String - // URL for blank cell image +// Support old format of orient parameter +var origOpen = popup.open; +popup.open = function(/*dijit.popup.__OpenArgs*/ args){ + // Convert old hash structure (ex: {"BL": "TL", ...}) of orient to format compatible w/new popup.open() API. + // Don't do conversion for: + // - null parameter (that means to use the default positioning) + // - "R" or "L" strings used to indicate positioning for context menus (when there is no around node) + // - new format, ex: ["below", "above"] + // - return value from deprecated dijit.getPopupAroundAlignment() method, + // ex: ["below", "above"] + if(args.orient && typeof args.orient != "string" && !("length" in args.orient)){ + var ary = []; + for(var key in args.orient){ + ary.push({aroundCorner: key, corner: args.orient[key]}); } + args.orient = ary; } -); -=====*/ - -} - -if(!dojo._hasResource["dijit.ColorPalette"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.ColorPalette"] = true; -dojo.provide("dijit.ColorPalette"); - - - + return origOpen.call(this, args); +}; +return popup; +}); +}, +'dijit/ColorPalette':function(){ +require({cache:{ +'url:dijit/templates/ColorPalette.html':"<div class=\"dijitInline dijitColorPalette\">\n\t<table dojoAttachPoint=\"paletteTableNode\" class=\"dijitPaletteTable\" cellSpacing=\"0\" cellPadding=\"0\" role=\"grid\">\n\t\t<tbody data-dojo-attach-point=\"gridNode\"></tbody>\n\t</table>\n</div>\n"}}); +define("dijit/ColorPalette", [ + "require", // require.toUrl + "dojo/text!./templates/ColorPalette.html", + "./_Widget", + "./_TemplatedMixin", + "./_PaletteMixin", + "dojo/i18n", // i18n.getLocalization + "dojo/_base/Color", // dojo.Color dojo.Color.named + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.contains + "dojo/dom-construct", // domConstruct.place + "dojo/_base/window", // win.body + "dojo/string", // string.substitute + "dojo/i18n!dojo/nls/colors", // translations + "dojo/colors" // extend dojo.Color w/names of other colors +], function(require, template, _Widget, _TemplatedMixin, _PaletteMixin, i18n, Color, + declare, domClass, domConstruct, win, string){ +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _PaletteMixin = dijit._PaletteMixin; +=====*/ +// module: +// dijit/ColorPalette +// summary: +// A keyboard accessible color-picking widget -dojo.declare("dijit.ColorPalette", - [dijit._Widget, dijit._Templated, dijit._PaletteMixin], - { +var ColorPalette = declare("dijit.ColorPalette", [_Widget, _TemplatedMixin, _PaletteMixin], { // summary: // A keyboard accessible color-picking widget // description: @@ -7973,7 +11818,7 @@ dojo.declare("dijit.ColorPalette", // Can be used standalone, or as a popup. // // example: - // | <div dojoType="dijit.ColorPalette"></div> + // | <div data-dojo-type="dijit.ColorPalette"></div> // // example: // | var picker = new dijit.ColorPalette({ },srcNode); @@ -8004,29 +11849,35 @@ dojo.declare("dijit.ColorPalette", // templateString: String // The template of this widget. - 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"), + templateString: template, baseClass: "dijitColorPalette", + _dyeFactory: function(value, row, col){ + // Overrides _PaletteMixin._dyeFactory(). + return new this._dyeClass(value, row, col); + }, + buildRendering: function(){ // Instantiate the template, which makes a skeleton into which we'll insert a bunch of // <img> nodes this.inherited(arguments); + // Creates customized constructor for dye class (color of a single cell) for + // specified palette and high-contrast vs. normal mode. Used in _getDye(). + this._dyeClass = declare(ColorPalette._Color, { + hc: domClass.contains(win.body(), "dijit_a11y"), + palette: this.palette + }); + // Creates <img> nodes in each cell of the template. - // Pass in "customized" dijit._Color constructor for specified palette and high-contrast vs. normal mode this._preparePalette( this._palettes[this.palette], - dojo.i18n.getLocalization("dojo", "colors", this.lang), - dojo.declare(dijit._Color, { - hc: dojo.hasClass(dojo.body(), "dijit_a11y"), - palette: this.palette - }) - ); + i18n.getLocalization("dojo", "colors", this.lang)); } }); -dojo.declare("dijit._Color", dojo.Color, { +ColorPalette._Color = declare("dijit._Color", Color, { // summary: // Object associated with each cell in a ColorPalette palette. // Implements dijit.Dye. @@ -8049,15 +11900,15 @@ dojo.declare("dijit._Color", dojo.Color, { // _imagePaths: [protected] Map // This is stores the path to the palette images used for high-contrast mode display _imagePaths: { - "7x10": dojo.moduleUrl("dijit.themes", "a11y/colors7x10.png"), - "3x4": dojo.moduleUrl("dijit.themes", "a11y/colors3x4.png") + "7x10": require.toUrl("./themes/a11y/colors7x10.png"), + "3x4": require.toUrl("./themes/a11y/colors3x4.png") }, constructor: function(/*String*/alias, /*Number*/ row, /*Number*/ col){ this._alias = alias; this._row = row; this._col = col; - this.setColor(dojo.Color.named[alias]); + this.setColor(Color.named[alias]); }, getValue: function(){ @@ -8068,12 +11919,12 @@ dojo.declare("dijit._Color", dojo.Color, { }, fillCell: function(/*DOMNode*/ cell, /*String*/ blankGif){ - var html = dojo.string.substitute(this.hc ? this.hcTemplate : this.template, { + var html = string.substitute(this.hc ? this.hcTemplate : this.template, { // substitution variables for normal mode color: this.toHex(), blankGif: blankGif, alt: this._alias, - + // variables used for high contrast mode image: this._imagePaths[this.palette].toString(), left: this._col * -20 - 5, @@ -8081,270 +11932,2043 @@ dojo.declare("dijit._Color", dojo.Color, { size: this.palette == "7x10" ? "height: 145px; width: 206px" : "height: 64px; width: 86px" }); - dojo.place(html, cell); + domConstruct.place(html, cell); } }); -} -if(!dojo._hasResource["dojo.dnd.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.dnd.common"] = true; -dojo.provide("dojo.dnd.common"); +return ColorPalette; +}); -dojo.getObject("dnd", true, dojo); +}, +'url:dijit/form/templates/Button.html':"<span class=\"dijit dijitReset dijitInline\" role=\"presentation\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdata-dojo-attach-event=\"ondijitclick:_onClick\" role=\"presentation\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdata-dojo-attach-point=\"titleNode,focusNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" data-dojo-attach-point=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">●</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdata-dojo-attach-point=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\ttabIndex=\"-1\" role=\"presentation\" data-dojo-attach-point=\"valueNode\"\n/></span>\n", +'dojo/_base/url':function(){ +define("dojo/_base/url", ["./kernel"], function(dojo) { + // module: + // dojo/url + // summary: + // This module contains dojo._Url + + var + ore = new RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$"), + ire = new RegExp("^((([^\\[:]+):)?([^@]+)@)?(\\[([^\\]]+)\\]|([^\\[:]*))(:([0-9]+))?$"), + _Url = function(){ + var n = null, + _a = arguments, + uri = [_a[0]]; + // resolve uri components relative to each other + for(var i = 1; i<_a.length; i++){ + if(!_a[i]){ continue; } + + // Safari doesn't support this.constructor so we have to be explicit + // FIXME: Tracked (and fixed) in Webkit bug 3537. + // http://bugs.webkit.org/show_bug.cgi?id=3537 + var relobj = new _Url(_a[i]+""), + uriobj = new _Url(uri[0]+""); + + if( + relobj.path == "" && + !relobj.scheme && + !relobj.authority && + !relobj.query + ){ + if(relobj.fragment != n){ + uriobj.fragment = relobj.fragment; + } + relobj = uriobj; + }else if(!relobj.scheme){ + relobj.scheme = uriobj.scheme; + + if(!relobj.authority){ + relobj.authority = uriobj.authority; + + if(relobj.path.charAt(0) != "/"){ + var path = uriobj.path.substring(0, + uriobj.path.lastIndexOf("/") + 1) + relobj.path; + + var segs = path.split("/"); + for(var j = 0; j < segs.length; j++){ + if(segs[j] == "."){ + // flatten "./" references + if(j == segs.length - 1){ + segs[j] = ""; + }else{ + segs.splice(j, 1); + j--; + } + }else if(j > 0 && !(j == 1 && segs[0] == "") && + segs[j] == ".." && segs[j-1] != ".."){ + // flatten "../" references + if(j == (segs.length - 1)){ + segs.splice(j, 1); + segs[j - 1] = ""; + }else{ + segs.splice(j - 1, 2); + j -= 2; + } + } + } + relobj.path = segs.join("/"); + } + } + } -dojo.dnd.getCopyKeyState = dojo.isCopyKey; + uri = []; + if(relobj.scheme){ + uri.push(relobj.scheme, ":"); + } + if(relobj.authority){ + uri.push("//", relobj.authority); + } + uri.push(relobj.path); + if(relobj.query){ + uri.push("?", relobj.query); + } + if(relobj.fragment){ + uri.push("#", relobj.fragment); + } + } -dojo.dnd._uniqueId = 0; -dojo.dnd.getUniqueId = function(){ + this.uri = uri.join(""); + + // break the uri into its main components + var r = this.uri.match(ore); + + this.scheme = r[2] || (r[1] ? "" : n); + this.authority = r[4] || (r[3] ? "" : n); + this.path = r[5]; // can never be undefined + this.query = r[7] || (r[6] ? "" : n); + this.fragment = r[9] || (r[8] ? "" : n); + + if(this.authority != n){ + // server based naming authority + r = this.authority.match(ire); + + this.user = r[3] || n; + this.password = r[4] || n; + this.host = r[6] || r[7]; // ipv6 || ipv4 + this.port = r[9] || n; + } + }; + _Url.prototype.toString = function(){ return this.uri; }; + + return dojo._Url = _Url; +}); + +}, +'dojo/text':function(){ +define("dojo/text", ["./_base/kernel", "require", "./has", "./_base/xhr"], function(dojo, require, has, xhr){ + // module: + // dojo/text // summary: - // returns a unique string for use with any DOM element - var id; - do{ - id = dojo._scopeName + "Unique" + (++dojo.dnd._uniqueId); - }while(dojo.byId(id)); - return id; + // This module implements the !dojo/text plugin and the dojo.cache API. + // description: + // We choose to include our own plugin to leverage functionality already contained in dojo + // and thereby reduce the size of the plugin compared to various foreign loader implementations. + // Also, this allows foreign AMD loaders to be used without their plugins. + // + // CAUTION: this module is designed to optionally function synchronously to support the dojo v1.x synchronous + // loader. This feature is outside the scope of the CommonJS plugins specification. + + var getText; + if(1){ + getText= function(url, sync, load){ + xhr("GET", {url:url, sync:!!sync, load:load}); + }; + }else{ + // TODOC: only works for dojo AMD loader + if(require.getText){ + getText= require.getText; + }else{ + console.error("dojo/text plugin failed to load because loader does not support getText"); + } + } + + var + theCache= {}, + + strip= function(text){ + //Strips <?xml ...?> declarations so that external SVG and XML + //documents can be added to a document without worry. Also, if the string + //is an HTML document, only the part inside the body tag is returned. + if(text){ + text= text.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, ""); + var matches= text.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); + if(matches){ + text= matches[1]; + } + }else{ + text = ""; + } + return text; + }, + + notFound = {}, + + pending = {}, + + result= { + dynamic: + // the dojo/text caches it's own resources because of dojo.cache + true, + + normalize:function(id, toAbsMid){ + // id is something like (path may be relative): + // + // "path/to/text.html" + // "path/to/text.html!strip" + var parts= id.split("!"), + url= parts[0]; + return (/^\./.test(url) ? toAbsMid(url) : url) + (parts[1] ? "!" + parts[1] : ""); + }, + + load:function(id, require, load){ + // id is something like (path is always absolute): + // + // "path/to/text.html" + // "path/to/text.html!strip" + var + parts= id.split("!"), + stripFlag= parts.length>1, + absMid= parts[0], + url = require.toUrl(parts[0]), + text = notFound, + finish = function(text){ + load(stripFlag ? strip(text) : text); + }; + if(absMid in theCache){ + text = theCache[absMid]; + }else if(url in require.cache){ + text = require.cache[url]; + }else if(url in theCache){ + text = theCache[url]; + } + if(text===notFound){ + if(pending[url]){ + pending[url].push(finish); + }else{ + var pendingList = pending[url] = [finish]; + getText(url, !require.async, function(text){ + theCache[absMid]= theCache[url]= text; + for(var i = 0; i<pendingList.length;){ + pendingList[i++](text); + } + delete pending[url]; + }); + } + }else{ + finish(text); + } + } + }; + + dojo.cache= function(/*String||Object*/module, /*String*/url, /*String||Object?*/value){ + // * (string string [value]) => (module, url, value) + // * (object [value]) => (module, value), url defaults to "" + // + // * if module is an object, then it must be convertable to a string + // * (module, url) module + (url ? ("/" + url) : "") must be a legal argument to require.toUrl + // * value may be a string or an object; if an object then may have the properties "value" and/or "sanitize" + var key; + if(typeof module=="string"){ + if(/\//.test(module)){ + // module is a version 1.7+ resolved path + key = module; + value = url; + }else{ + // module is a version 1.6- argument to dojo.moduleUrl + key = require.toUrl(module.replace(/\./g, "/") + (url ? ("/" + url) : "")); + } + }else{ + key = module + ""; + value = url; + } + var + val = (value != undefined && typeof value != "string") ? value.value : value, + sanitize = value && value.sanitize; + + if(typeof val == "string"){ + //We have a string, set cache value + theCache[key] = val; + return sanitize ? strip(val) : val; + }else if(val === null){ + //Remove cached value + delete theCache[key]; + return null; + }else{ + //Allow cache values to be empty strings. If key property does + //not exist, fetch it. + if(!(key in theCache)){ + getText(key, true, function(text){ + theCache[key]= text; + }); + } + return sanitize ? strip(theCache[key]) : theCache[key]; + } + }; + + return result; + +/*===== +dojo.cache = function(module, url, value){ + // summary: + // A getter and setter for storing the string content associated with the + // module and url arguments. + // description: + // If module is a string that contains slashes, then it is interpretted as a fully + // resolved path (typically a result returned by require.toUrl), and url should not be + // provided. This is the preferred signature. If module is a string that does not + // contain slashes, then url must also be provided and module and url are used to + // call `dojo.moduleUrl()` to generate a module URL. This signature is deprecated. + // If value is specified, the cache value for the moduleUrl will be set to + // that value. Otherwise, dojo.cache will fetch the moduleUrl and store it + // in its internal cache and return that cached value for the URL. To clear + // a cache value pass null for value. Since XMLHttpRequest (XHR) is used to fetch the + // the URL contents, only modules on the same domain of the page can use this capability. + // The build system can inline the cache values though, to allow for xdomain hosting. + // module: String||Object + // If a String with slashes, a fully resolved path; if a String without slashes, the + // module name to use for the base part of the URL, similar to module argument + // to `dojo.moduleUrl`. If an Object, something that has a .toString() method that + // generates a valid path for the cache item. For example, a dojo._Url object. + // url: String + // The rest of the path to append to the path derived from the module argument. If + // module is an object, then this second argument should be the "value" argument instead. + // value: String||Object? + // If a String, the value to use in the cache for the module/url combination. + // If an Object, it can have two properties: value and sanitize. The value property + // should be the value to use in the cache, and sanitize can be set to true or false, + // to indicate if XML declarations should be removed from the value and if the HTML + // inside a body tag in the value should be extracted as the real value. The value argument + // or the value property on the value argument are usually only used by the build system + // as it inlines cache content. + // example: + // To ask dojo.cache to fetch content and store it in the cache (the dojo["cache"] style + // of call is used to avoid an issue with the build system erroneously trying to intern + // this example. To get the build system to intern your dojo.cache calls, use the + // "dojo.cache" style of call): + // | //If template.html contains "<h1>Hello</h1>" that will be + // | //the value for the text variable. + // | var text = dojo["cache"]("my.module", "template.html"); + // example: + // To ask dojo.cache to fetch content and store it in the cache, and sanitize the input + // (the dojo["cache"] style of call is used to avoid an issue with the build system + // erroneously trying to intern this example. To get the build system to intern your + // dojo.cache calls, use the "dojo.cache" style of call): + // | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the + // | //text variable will contain just "<h1>Hello</h1>". + // | var text = dojo["cache"]("my.module", "template.html", {sanitize: true}); + // example: + // Same example as previous, but demostrates how an object can be passed in as + // the first argument, then the value argument can then be the second argument. + // | //If template.html contains "<html><body><h1>Hello</h1></body></html>", the + // | //text variable will contain just "<h1>Hello</h1>". + // | var text = dojo["cache"](new dojo._Url("my/module/template.html"), {sanitize: true}); + return val; //String }; +=====*/ +}); -dojo.dnd._empty = {}; -dojo.dnd.isFormElement = function(/*Event*/ e){ +}, +'url:dijit/templates/MenuItem.html':"<tr class=\"dijitReset dijitMenuItem\" data-dojo-attach-point=\"focusNode\" role=\"menuitem\" tabIndex=\"-1\"\n\t\tdata-dojo-attach-event=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" role=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitMenuItemIcon\" data-dojo-attach-point=\"iconNode\"/>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" data-dojo-attach-point=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" data-dojo-attach-point=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\">\n\t\t<div data-dojo-attach-point=\"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", +'url: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\tdata-dojo-attach-point=\"focusNode\"\n\t \tdata-dojo-attach-event=\"onclick:_onClick\"\n/></div>\n", +'dojo/uacss':function(){ +define("dojo/uacss", ["./dom-geometry", "./_base/lang", "./ready", "./_base/sniff", "./_base/window"], + function(geometry, lang, ready, has, baseWindow){ + // module: + // dojo/uacss // summary: - // returns true if user clicked on a form element - var t = e.target; - if(t.nodeType == 3 /*TEXT_NODE*/){ - t = t.parentNode; + // Applies pre-set CSS classes to the top-level HTML node, based on: + // - browser (ex: dj_ie) + // - browser version (ex: dj_ie6) + // - box model (ex: dj_contentBox) + // - text direction (ex: dijitRtl) + // + // In addition, browser, browser version, and box model are + // combined with an RTL flag when browser text is RTL. ex: dj_ie-rtl. + + var + html = baseWindow.doc.documentElement, + ie = has("ie"), + opera = has("opera"), + maj = Math.floor, + ff = has("ff"), + boxModel = geometry.boxModel.replace(/-/,''), + + classes = { + "dj_ie": ie, + "dj_ie6": maj(ie) == 6, + "dj_ie7": maj(ie) == 7, + "dj_ie8": maj(ie) == 8, + "dj_ie9": maj(ie) == 9, + "dj_quirks": has("quirks"), + "dj_iequirks": ie && has("quirks"), + + // NOTE: Opera not supported by dijit + "dj_opera": opera, + + "dj_khtml": has("khtml"), + + "dj_webkit": has("webkit"), + "dj_safari": has("safari"), + "dj_chrome": has("chrome"), + + "dj_gecko": has("mozilla"), + "dj_ff3": maj(ff) == 3 + }; // no dojo unsupported browsers + + classes["dj_" + boxModel] = true; + + // apply browser, browser version, and box model class names + var classStr = ""; + for(var clz in classes){ + if(classes[clz]){ + classStr += clz + " "; + } } - return " button textarea input select option ".indexOf(" " + t.tagName.toLowerCase() + " ") >= 0; // Boolean -}; + html.className = lang.trim(html.className + " " + classStr); -} + // If RTL mode, then add dj_rtl flag plus repeat existing classes with -rtl extension. + // We can't run the code below until the <body> tag has loaded (so we can check for dir=rtl). + // priority is 90 to run ahead of parser priority of 100 + ready(90, function(){ + if(!geometry.isBodyLtr()){ + var rtlClassStr = "dj_rtl dijitRtl " + classStr.replace(/ /g, "-rtl "); + html.className = lang.trim(html.className + " " + rtlClassStr + "dj_rtl dijitRtl " + classStr.replace(/ /g, "-rtl ")); + } + }); + return has; +}); -if(!dojo._hasResource["dojo.dnd.autoscroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.dnd.autoscroll"] = true; -dojo.provide("dojo.dnd.autoscroll"); +}, +'dijit/Tooltip':function(){ +require({cache:{ +'url:dijit/templates/Tooltip.html':"<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\"\n\t><div class=\"dijitTooltipContainer dijitTooltipContents\" data-dojo-attach-point=\"containerNode\" role='alert'></div\n\t><div class=\"dijitTooltipConnector\" data-dojo-attach-point=\"connectorNode\"></div\n></div>\n"}}); +define("dijit/Tooltip", [ + "dojo/_base/array", // array.forEach array.indexOf array.map + "dojo/_base/declare", // declare + "dojo/_base/fx", // fx.fadeIn fx.fadeOut + "dojo/dom", // dom.byId + "dojo/dom-class", // domClass.add + "dojo/dom-geometry", // domGeometry.getMarginBox domGeometry.position + "dojo/dom-style", // domStyle.set, domStyle.get + "dojo/_base/lang", // lang.hitch lang.isArrayLike + "dojo/_base/sniff", // has("ie") + "dojo/_base/window", // win.body + "./_base/manager", // manager.defaultDuration + "./place", + "./_Widget", + "./_TemplatedMixin", + "./BackgroundIframe", + "dojo/text!./templates/Tooltip.html", + "." // sets dijit.showTooltip etc. for back-compat +], function(array, declare, fx, dom, domClass, domGeometry, domStyle, lang, has, win, + manager, place, _Widget, _TemplatedMixin, BackgroundIframe, template, dijit){ +/*===== + var _Widget = dijit._Widget; + var BackgroundIframe = dijit.BackgroundIframe; + var _TemplatedMixin = dijit._TemplatedMixin; +=====*/ -dojo.getObject("dnd", true, dojo); + // module: + // dijit/Tooltip + // summary: + // Defines dijit.Tooltip widget (to display a tooltip), showTooltip()/hideTooltip(), and _MasterTooltip -dojo.dnd.getViewport = dojo.window.getBox; -dojo.dnd.V_TRIGGER_AUTOSCROLL = 32; -dojo.dnd.H_TRIGGER_AUTOSCROLL = 32; + var MasterTooltip = declare("dijit._MasterTooltip", [_Widget, _TemplatedMixin], { + // summary: + // Internal widget that holds the actual tooltip markup, + // which occurs once per page. + // Called by Tooltip widgets which are just containers to hold + // the markup + // tags: + // protected -dojo.dnd.V_AUTOSCROLL_VALUE = 16; -dojo.dnd.H_AUTOSCROLL_VALUE = 16; + // duration: Integer + // Milliseconds to fade in/fade out + duration: manager.defaultDuration, -dojo.dnd.autoScroll = function(e){ + templateString: template, + + postCreate: function(){ + win.body().appendChild(this.domNode); + + this.bgIframe = new BackgroundIframe(this.domNode); + + // Setup fade-in and fade-out functions. + this.fadeIn = fx.fadeIn({ node: this.domNode, duration: this.duration, onEnd: lang.hitch(this, "_onShow") }); + this.fadeOut = fx.fadeOut({ node: this.domNode, duration: this.duration, onEnd: lang.hitch(this, "_onHide") }); + }, + + show: function(innerHTML, aroundNode, position, rtl, textDir){ + // summary: + // Display tooltip w/specified contents to right of specified node + // (To left if there's no space on the right, or if rtl == true) + // innerHTML: String + // Contents of the tooltip + // aroundNode: DomNode || dijit.__Rectangle + // Specifies that tooltip should be next to this node / area + // position: String[]? + // List of positions to try to position tooltip (ex: ["right", "above"]) + // rtl: Boolean? + // Corresponds to `WidgetBase.dir` attribute, where false means "ltr" and true + // means "rtl"; specifies GUI direction, not text direction. + // textDir: String? + // Corresponds to `WidgetBase.textdir` attribute; specifies direction of text. + + + if(this.aroundNode && this.aroundNode === aroundNode && this.containerNode.innerHTML == innerHTML){ + return; + } + + // reset width; it may have been set by orient() on a previous tooltip show() + this.domNode.width = "auto"; + + if(this.fadeOut.status() == "playing"){ + // previous tooltip is being hidden; wait until the hide completes then show new one + this._onDeck=arguments; + return; + } + this.containerNode.innerHTML=innerHTML; + + this.set("textDir", textDir); + this.containerNode.align = rtl? "right" : "left"; //fix the text alignment + + var pos = place.around(this.domNode, aroundNode, + position && position.length ? position : Tooltip.defaultPosition, !rtl, lang.hitch(this, "orient")); + + // Position the tooltip connector for middle alignment. + // This could not have been done in orient() since the tooltip wasn't positioned at that time. + var aroundNodeCoords = pos.aroundNodePos; + if(pos.corner.charAt(0) == 'M' && pos.aroundCorner.charAt(0) == 'M'){ + this.connectorNode.style.top = aroundNodeCoords.y + ((aroundNodeCoords.h - this.connectorNode.offsetHeight) >> 1) - pos.y + "px"; + this.connectorNode.style.left = ""; + }else if(pos.corner.charAt(1) == 'M' && pos.aroundCorner.charAt(1) == 'M'){ + this.connectorNode.style.left = aroundNodeCoords.x + ((aroundNodeCoords.w - this.connectorNode.offsetWidth) >> 1) - pos.x + "px"; + } + + // show it + domStyle.set(this.domNode, "opacity", 0); + this.fadeIn.play(); + this.isShowingNow = true; + this.aroundNode = aroundNode; + }, + + orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ tooltipCorner, /*Object*/ spaceAvailable, /*Object*/ aroundNodeCoords){ + // summary: + // Private function to set CSS for tooltip node based on which position it's in. + // This is called by the dijit popup code. It will also reduce the tooltip's + // width to whatever width is available + // tags: + // protected + this.connectorNode.style.top = ""; //reset to default + + //Adjust the spaceAvailable width, without changing the spaceAvailable object + var tooltipSpaceAvaliableWidth = spaceAvailable.w - this.connectorNode.offsetWidth; + + node.className = "dijitTooltip " + + { + "MR-ML": "dijitTooltipRight", + "ML-MR": "dijitTooltipLeft", + "TM-BM": "dijitTooltipAbove", + "BM-TM": "dijitTooltipBelow", + "BL-TL": "dijitTooltipBelow dijitTooltipABLeft", + "TL-BL": "dijitTooltipAbove dijitTooltipABLeft", + "BR-TR": "dijitTooltipBelow dijitTooltipABRight", + "TR-BR": "dijitTooltipAbove dijitTooltipABRight", + "BR-BL": "dijitTooltipRight", + "BL-BR": "dijitTooltipLeft" + }[aroundCorner + "-" + tooltipCorner]; + + // reduce tooltip's width to the amount of width available, so that it doesn't overflow screen + this.domNode.style.width = "auto"; + var size = domGeometry.getContentBox(this.domNode); + + var width = Math.min((Math.max(tooltipSpaceAvaliableWidth,1)), size.w); + var widthWasReduced = width < size.w; + + this.domNode.style.width = width+"px"; + + //Adjust width for tooltips that have a really long word or a nowrap setting + if(widthWasReduced){ + this.containerNode.style.overflow = "auto"; //temp change to overflow to detect if our tooltip needs to be wider to support the content + var scrollWidth = this.containerNode.scrollWidth; + this.containerNode.style.overflow = "visible"; //change it back + if(scrollWidth > width){ + scrollWidth = scrollWidth + domStyle.get(this.domNode,"paddingLeft") + domStyle.get(this.domNode,"paddingRight"); + this.domNode.style.width = scrollWidth + "px"; + } + } + + // Reposition the tooltip connector. + if(tooltipCorner.charAt(0) == 'B' && aroundCorner.charAt(0) == 'B'){ + var mb = domGeometry.getMarginBox(node); + var tooltipConnectorHeight = this.connectorNode.offsetHeight; + if(mb.h > spaceAvailable.h){ + // The tooltip starts at the top of the page and will extend past the aroundNode + var aroundNodePlacement = spaceAvailable.h - ((aroundNodeCoords.h + tooltipConnectorHeight) >> 1); + this.connectorNode.style.top = aroundNodePlacement + "px"; + this.connectorNode.style.bottom = ""; + }else{ + // Align center of connector with center of aroundNode, except don't let bottom + // of connector extend below bottom of tooltip content, or top of connector + // extend past top of tooltip content + this.connectorNode.style.bottom = Math.min( + Math.max(aroundNodeCoords.h/2 - tooltipConnectorHeight/2, 0), + mb.h - tooltipConnectorHeight) + "px"; + this.connectorNode.style.top = ""; + } + }else{ + // reset the tooltip back to the defaults + this.connectorNode.style.top = ""; + this.connectorNode.style.bottom = ""; + } + + return Math.max(0, size.w - tooltipSpaceAvaliableWidth); + }, + + _onShow: function(){ + // summary: + // Called at end of fade-in operation + // tags: + // protected + if(has("ie")){ + // the arrow won't show up on a node w/an opacity filter + this.domNode.style.filter=""; + } + }, + + hide: function(aroundNode){ + // summary: + // Hide the tooltip + + if(this._onDeck && this._onDeck[1] == aroundNode){ + // this hide request is for a show() that hasn't even started yet; + // just cancel the pending show() + this._onDeck=null; + }else if(this.aroundNode === aroundNode){ + // this hide request is for the currently displayed tooltip + this.fadeIn.stop(); + this.isShowingNow = false; + this.aroundNode = null; + this.fadeOut.play(); + }else{ + // just ignore the call, it's for a tooltip that has already been erased + } + }, + + _onHide: function(){ + // summary: + // Called at end of fade-out operation + // tags: + // protected + + this.domNode.style.cssText=""; // to position offscreen again + this.containerNode.innerHTML=""; + if(this._onDeck){ + // a show request has been queued up; do it now + this.show.apply(this, this._onDeck); + this._onDeck=null; + } + }, + + _setAutoTextDir: function(/*Object*/node){ + // summary: + // Resolve "auto" text direction for children nodes + // tags: + // private + + this.applyTextDir(node, has("ie") ? node.outerText : node.textContent); + array.forEach(node.children, function(child){this._setAutoTextDir(child); }, this); + }, + + _setTextDirAttr: function(/*String*/ textDir){ + // summary: + // Setter for textDir. + // description: + // Users shouldn't call this function; they should be calling + // set('textDir', value) + // tags: + // private + + this._set("textDir", typeof textDir != 'undefined'? textDir : ""); + if (textDir == "auto"){ + this._setAutoTextDir(this.containerNode); + }else{ + this.containerNode.dir = this.textDir; + } + } + }); + + dijit.showTooltip = function(innerHTML, aroundNode, position, rtl, textDir){ + // summary: + // Static method to display tooltip w/specified contents in specified position. + // See description of dijit.Tooltip.defaultPosition for details on position parameter. + // If position is not specified then dijit.Tooltip.defaultPosition is used. + // innerHTML: String + // Contents of the tooltip + // aroundNode: dijit.__Rectangle + // Specifies that tooltip should be next to this node / area + // position: String[]? + // List of positions to try to position tooltip (ex: ["right", "above"]) + // rtl: Boolean? + // Corresponds to `WidgetBase.dir` attribute, where false means "ltr" and true + // means "rtl"; specifies GUI direction, not text direction. + // textDir: String? + // Corresponds to `WidgetBase.textdir` attribute; specifies direction of text. + + // after/before don't work, but they used to, so for back-compat convert them to after-centered, before-centered + if(position){ + position = array.map(position, function(val){ + return {after: "after-centered", before: "before-centered"}[val] || val; + }); + } + + if(!Tooltip._masterTT){ dijit._masterTT = Tooltip._masterTT = new MasterTooltip(); } + return Tooltip._masterTT.show(innerHTML, aroundNode, position, rtl, textDir); + }; + + dijit.hideTooltip = function(aroundNode){ + // summary: + // Static method to hide the tooltip displayed via showTooltip() + return Tooltip._masterTT && Tooltip._masterTT.hide(aroundNode); + }; + + var Tooltip = declare("dijit.Tooltip", _Widget, { + // summary: + // Pops up a tooltip (a help message) when you hover over a node. + + // label: String + // Text to display in the tooltip. + // Specified as innerHTML when creating the widget from markup. + label: "", + + // showDelay: Integer + // Number of milliseconds to wait after hovering over/focusing on the object, before + // the tooltip is displayed. + showDelay: 400, + + // connectId: String|String[] + // Id of domNode(s) to attach the tooltip to. + // When user hovers over specified dom node, the tooltip will appear. + connectId: [], + + // position: String[] + // See description of `dijit.Tooltip.defaultPosition` for details on position parameter. + position: [], + + _setConnectIdAttr: function(/*String|String[]*/ newId){ + // summary: + // Connect to specified node(s) + + // Remove connections to old nodes (if there are any) + array.forEach(this._connections || [], function(nested){ + array.forEach(nested, lang.hitch(this, "disconnect")); + }, this); + + // Make array of id's to connect to, excluding entries for nodes that don't exist yet, see startup() + this._connectIds = array.filter(lang.isArrayLike(newId) ? newId : (newId ? [newId] : []), + function(id){ return dom.byId(id); }); + + // Make connections + this._connections = array.map(this._connectIds, function(id){ + var node = dom.byId(id); + return [ + this.connect(node, "onmouseenter", "_onHover"), + this.connect(node, "onmouseleave", "_onUnHover"), + this.connect(node, "onfocus", "_onHover"), + this.connect(node, "onblur", "_onUnHover") + ]; + }, this); + + this._set("connectId", newId); + }, + + addTarget: function(/*DOMNODE || String*/ node){ + // summary: + // Attach tooltip to specified node if it's not already connected + + // TODO: remove in 2.0 and just use set("connectId", ...) interface + + var id = node.id || node; + if(array.indexOf(this._connectIds, id) == -1){ + this.set("connectId", this._connectIds.concat(id)); + } + }, + + removeTarget: function(/*DomNode || String*/ node){ + // summary: + // Detach tooltip from specified node + + // TODO: remove in 2.0 and just use set("connectId", ...) interface + + var id = node.id || node, // map from DOMNode back to plain id string + idx = array.indexOf(this._connectIds, id); + if(idx >= 0){ + // remove id (modifies original this._connectIds but that's OK in this case) + this._connectIds.splice(idx, 1); + this.set("connectId", this._connectIds); + } + }, + + buildRendering: function(){ + this.inherited(arguments); + domClass.add(this.domNode,"dijitTooltipData"); + }, + + startup: function(){ + this.inherited(arguments); + + // If this tooltip was created in a template, or for some other reason the specified connectId[s] + // didn't exist during the widget's initialization, then connect now. + var ids = this.connectId; + array.forEach(lang.isArrayLike(ids) ? ids : [ids], this.addTarget, this); + }, + + _onHover: function(/*Event*/ e){ + // summary: + // Despite the name of this method, it actually handles both hover and focus + // events on the target node, setting a timer to show the tooltip. + // tags: + // private + if(!this._showTimer){ + var target = e.target; + this._showTimer = setTimeout(lang.hitch(this, function(){this.open(target)}), this.showDelay); + } + }, + + _onUnHover: function(/*Event*/ /*===== e =====*/){ + // summary: + // Despite the name of this method, it actually handles both mouseleave and blur + // events on the target node, hiding the tooltip. + // tags: + // private + + // keep a tooltip open if the associated element still has focus (even though the + // mouse moved away) + if(this._focus){ return; } + + if(this._showTimer){ + clearTimeout(this._showTimer); + delete this._showTimer; + } + this.close(); + }, + + open: function(/*DomNode*/ target){ + // summary: + // Display the tooltip; usually not called directly. + // tags: + // private + + if(this._showTimer){ + clearTimeout(this._showTimer); + delete this._showTimer; + } + Tooltip.show(this.label || this.domNode.innerHTML, target, this.position, !this.isLeftToRight(), this.textDir); + + this._connectNode = target; + this.onShow(target, this.position); + }, + + close: function(){ + // summary: + // Hide the tooltip or cancel timer for show of tooltip + // tags: + // private + + if(this._connectNode){ + // if tooltip is currently shown + Tooltip.hide(this._connectNode); + delete this._connectNode; + this.onHide(); + } + if(this._showTimer){ + // if tooltip is scheduled to be shown (after a brief delay) + clearTimeout(this._showTimer); + delete this._showTimer; + } + }, + + onShow: function(/*===== target, position =====*/){ + // summary: + // Called when the tooltip is shown + // tags: + // callback + }, + + onHide: function(){ + // summary: + // Called when the tooltip is hidden + // tags: + // callback + }, + + uninitialize: function(){ + this.close(); + this.inherited(arguments); + } + }); + + Tooltip._MasterTooltip = MasterTooltip; // for monkey patching + Tooltip.show = dijit.showTooltip; // export function through module return value + Tooltip.hide = dijit.hideTooltip; // export function through module return value + + // dijit.Tooltip.defaultPosition: String[] + // This variable controls the position of tooltips, if the position is not specified to + // the Tooltip widget or *TextBox widget itself. It's an array of strings with the values + // possible for `dijit/place::around()`. The recommended values are: + // + // * before-centered: centers tooltip to the left of the anchor node/widget, or to the right + // in the case of RTL scripts like Hebrew and Arabic + // * after-centered: centers tooltip to the right of the anchor node/widget, or to the left + // in the case of RTL scripts like Hebrew and Arabic + // * above-centered: tooltip is centered above anchor node + // * below-centered: tooltip is centered above anchor node + // + // The list is positions is tried, in order, until a position is found where the tooltip fits + // within the viewport. + // + // Be careful setting this parameter. A value of "above-centered" may work fine until the user scrolls + // the screen so that there's no room above the target node. Nodes with drop downs, like + // DropDownButton or FilteringSelect, are especially problematic, in that you need to be sure + // that the drop down and tooltip don't overlap, even when the viewport is scrolled so that there + // is only room below (or above) the target node, but not both. + Tooltip.defaultPosition = ["after-centered", "before-centered"]; + + + return Tooltip; +}); + +}, +'dojo/string':function(){ +define("dojo/string", ["./_base/kernel", "./_base/lang"], function(dojo, lang) { + // module: + // dojo/string // summary: - // a handler for onmousemove event, which scrolls the window, if - // necesary - // e: Event - // onmousemove event + // TODOC - // FIXME: needs more docs! - var v = dojo.window.getBox(), dx = 0, dy = 0; - if(e.clientX < dojo.dnd.H_TRIGGER_AUTOSCROLL){ - dx = -dojo.dnd.H_AUTOSCROLL_VALUE; - }else if(e.clientX > v.w - dojo.dnd.H_TRIGGER_AUTOSCROLL){ - dx = dojo.dnd.H_AUTOSCROLL_VALUE; +lang.getObject("string", true, dojo); + +/*===== +dojo.string = { + // summary: String utilities for Dojo +}; +=====*/ + +dojo.string.rep = function(/*String*/str, /*Integer*/num){ + // summary: + // Efficiently replicate a string `n` times. + // str: + // the string to replicate + // num: + // number of times to replicate the string + + if(num <= 0 || !str){ return ""; } + + var buf = []; + for(;;){ + if(num & 1){ + buf.push(str); + } + if(!(num >>= 1)){ break; } + str += str; } - if(e.clientY < dojo.dnd.V_TRIGGER_AUTOSCROLL){ - dy = -dojo.dnd.V_AUTOSCROLL_VALUE; - }else if(e.clientY > v.h - dojo.dnd.V_TRIGGER_AUTOSCROLL){ - dy = dojo.dnd.V_AUTOSCROLL_VALUE; + return buf.join(""); // String +}; + +dojo.string.pad = function(/*String*/text, /*Integer*/size, /*String?*/ch, /*Boolean?*/end){ + // summary: + // Pad a string to guarantee that it is at least `size` length by + // filling with the character `ch` at either the start or end of the + // string. Pads at the start, by default. + // text: + // the string to pad + // size: + // length to provide padding + // ch: + // character to pad, defaults to '0' + // end: + // adds padding at the end if true, otherwise pads at start + // example: + // | // Fill the string to length 10 with "+" characters on the right. Yields "Dojo++++++". + // | dojo.string.pad("Dojo", 10, "+", true); + + if(!ch){ + ch = '0'; } - window.scrollBy(dx, dy); + var out = String(text), + pad = dojo.string.rep(ch, Math.ceil((size - out.length) / ch.length)); + return end ? out + pad : pad + out; // String }; -dojo.dnd._validNodes = {"div": 1, "p": 1, "td": 1}; -dojo.dnd._validOverflow = {"auto": 1, "scroll": 1}; +dojo.string.substitute = function( /*String*/ template, + /*Object|Array*/map, + /*Function?*/ transform, + /*Object?*/ thisObject){ + // summary: + // Performs parameterized substitutions on a string. Throws an + // exception if any parameter is unmatched. + // template: + // a string with expressions in the form `${key}` to be replaced or + // `${key:format}` which specifies a format function. keys are case-sensitive. + // map: + // hash to search for substitutions + // transform: + // a function to process all parameters before substitution takes + // place, e.g. mylib.encodeXML + // thisObject: + // where to look for optional format function; default to the global + // namespace + // example: + // Substitutes two expressions in a string from an Array or Object + // | // returns "File 'foo.html' is not found in directory '/temp'." + // | // by providing substitution data in an Array + // | dojo.string.substitute( + // | "File '${0}' is not found in directory '${1}'.", + // | ["foo.html","/temp"] + // | ); + // | + // | // also returns "File 'foo.html' is not found in directory '/temp'." + // | // but provides substitution data in an Object structure. Dotted + // | // notation may be used to traverse the structure. + // | dojo.string.substitute( + // | "File '${name}' is not found in directory '${info.dir}'.", + // | { name: "foo.html", info: { dir: "/temp" } } + // | ); + // example: + // Use a transform function to modify the values: + // | // returns "file 'foo.html' is not found in directory '/temp'." + // | dojo.string.substitute( + // | "${0} is not found in ${1}.", + // | ["foo.html","/temp"], + // | function(str){ + // | // try to figure out the type + // | var prefix = (str.charAt(0) == "/") ? "directory": "file"; + // | return prefix + " '" + str + "'"; + // | } + // | ); + // example: + // Use a formatter + // | // returns "thinger -- howdy" + // | dojo.string.substitute( + // | "${0:postfix}", ["thinger"], null, { + // | postfix: function(value, key){ + // | return value + " -- howdy"; + // | } + // | } + // | ); -dojo.dnd.autoScrollNodes = function(e){ + thisObject = thisObject || dojo.global; + transform = transform ? + lang.hitch(thisObject, transform) : function(v){ return v; }; + + return template.replace(/\$\{([^\s\:\}]+)(?:\:([^\s\:\}]+))?\}/g, + function(match, key, format){ + var value = lang.getObject(key, false, map); + if(format){ + value = lang.getObject(format, false, thisObject).call(thisObject, value, key); + } + return transform(value, key).toString(); + }); // String +}; + +/*===== +dojo.string.trim = function(str){ // summary: - // a handler for onmousemove event, which scrolls the first avaialble - // Dom element, it falls back to dojo.dnd.autoScroll() - // e: Event - // onmousemove event + // Trims whitespace from both sides of the string + // str: String + // String to be trimmed + // returns: String + // Returns the trimmed string + // description: + // This version of trim() was taken from [Steven Levithan's blog](http://blog.stevenlevithan.com/archives/faster-trim-javascript). + // The short yet performant version of this function is dojo.trim(), + // which is part of Dojo base. Uses String.prototype.trim instead, if available. + return ""; // String +} +=====*/ - // FIXME: needs more docs! - for(var n = e.target; n;){ - if(n.nodeType == 1 && (n.tagName.toLowerCase() in dojo.dnd._validNodes)){ - var s = dojo.getComputedStyle(n); - if(s.overflow.toLowerCase() in dojo.dnd._validOverflow){ - var b = dojo._getContentBox(n, s), t = dojo.position(n, true); - //console.log(b.l, b.t, t.x, t.y, n.scrollLeft, n.scrollTop); - var w = Math.min(dojo.dnd.H_TRIGGER_AUTOSCROLL, b.w / 2), - h = Math.min(dojo.dnd.V_TRIGGER_AUTOSCROLL, b.h / 2), - rx = e.pageX - t.x, ry = e.pageY - t.y, dx = 0, dy = 0; - if(dojo.isWebKit || dojo.isOpera){ - // FIXME: this code should not be here, it should be taken into account - // either by the event fixing code, or the dojo.position() - // FIXME: this code doesn't work on Opera 9.5 Beta - rx += dojo.body().scrollLeft; - ry += dojo.body().scrollTop; +dojo.string.trim = String.prototype.trim ? + lang.trim : // aliasing to the native function + function(str){ + str = str.replace(/^\s+/, ''); + for(var i = str.length - 1; i >= 0; i--){ + if(/\S/.test(str.charAt(i))){ + str = str.substring(0, i + 1); + break; + } + } + return str; + }; + +return dojo.string; +}); + +}, +'url: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>", +'dijit/dijit':function(){ +define("dijit/dijit", [ + ".", + "./_base", + "dojo/parser", + "./_Widget", + "./_TemplatedMixin", + "./_Container", + "./layout/_LayoutWidget", + "./form/_FormWidget", + "./form/_FormValueWidget" +], function(dijit){ + + // module: + // dijit/dijit + // summary: + // A roll-up for common dijit methods + // All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require) + // And some other stuff that we tend to pull in all the time anyway + + return dijit; +}); + +}, +'dijit/form/DropDownButton':function(){ +require({cache:{ +'url:dijit/form/templates/DropDownButton.html':"<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdata-dojo-attach-event=\"ondijitclick:_onClick\" data-dojo-attach-point=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdata-dojo-attach-point=\"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\tdata-dojo-attach-point=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdata-dojo-attach-point=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">▼</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdata-dojo-attach-point=\"valueNode\"\n/></span>\n"}}); +define("dijit/form/DropDownButton", [ + "dojo/_base/declare", // declare + "dojo/_base/lang", // hitch + "dojo/query", // query + "../registry", // registry.byNode + "../popup", // dijit.popup2.hide + "./Button", + "../_Container", + "../_HasDropDown", + "dojo/text!./templates/DropDownButton.html" +], function(declare, lang, query, registry, popup, Button, _Container, _HasDropDown, template){ + +/*===== + Button = dijit.form.Button; + _Container = dijit._Container; + _HasDropDown = dijit._HasDropDown; +=====*/ + +// module: +// dijit/form/DropDownButton +// summary: +// A button with a drop down + + +return declare("dijit.form.DropDownButton", [Button, _Container, _HasDropDown], { + // summary: + // A button with a drop down + // + // example: + // | <button data-dojo-type="dijit.form.DropDownButton"> + // | Hello world + // | <div data-dojo-type="dijit.Menu">...</div> + // | </button> + // + // example: + // | var button1 = new dijit.form.DropDownButton({ label: "hi", dropDown: new dijit.Menu(...) }); + // | win.body().appendChild(button1); + // + + baseClass : "dijitDropDownButton", + + templateString: template, + + _fillContent: function(){ + // Overrides Button._fillContent(). + // + // My inner HTML contains both the button contents and a drop down widget, like + // <DropDownButton> <span>push me</span> <Menu> ... </Menu> </DropDownButton> + // The first node is assumed to be the button content. The widget is the popup. + + if(this.srcNodeRef){ // programatically created buttons might not define srcNodeRef + //FIXME: figure out how to filter out the widget and use all remaining nodes as button + // content, not just nodes[0] + var nodes = query("*", this.srcNodeRef); + this.inherited(arguments, [nodes[0]]); + + // save pointer to srcNode so we can grab the drop down widget after it's instantiated + this.dropDownContainer = this.srcNodeRef; + } + }, + + startup: function(){ + if(this._started){ return; } + + // the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM, + // make it invisible, and store a reference to pass to the popup code. + if(!this.dropDown && this.dropDownContainer){ + var dropDownNode = query("[widgetId]", this.dropDownContainer)[0]; + this.dropDown = registry.byNode(dropDownNode); + delete this.dropDownContainer; + } + if(this.dropDown){ + popup.hide(this.dropDown); + } + + this.inherited(arguments); + }, + + isLoaded: function(){ + // Returns whether or not we are loaded - if our dropdown has an href, + // then we want to check that. + var dropDown = this.dropDown; + return (!!dropDown && (!dropDown.href || dropDown.isLoaded)); + }, + + loadDropDown: function(/*Function*/ callback){ + // Default implementation assumes that drop down already exists, + // but hasn't loaded it's data (ex: ContentPane w/href). + // App must override if the drop down is lazy-created. + var dropDown = this.dropDown; + var handler = dropDown.on("load", lang.hitch(this, function(){ + handler.remove(); + callback(); + })); + dropDown.refresh(); // tell it to load + }, + + isFocusable: function(){ + // Overridden so that focus is handled by the _HasDropDown mixin, not by + // the _FormWidget mixin. + return this.inherited(arguments) && !this._mouseDown; + } +}); + +}); + +}, +'dijit/form/_FormValueMixin':function(){ +define("dijit/form/_FormValueMixin", [ + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/keys", // keys.ESCAPE + "dojo/_base/sniff", // has("ie"), has("quirks") + "./_FormWidgetMixin" +], function(declare, domAttr, keys, has, _FormWidgetMixin){ + +/*===== + var _FormWidgetMixin = dijit.form._FormWidgetMixin; +=====*/ + + // module: + // dijit/form/_FormValueMixin + // summary: + // Mixin for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values. + + return declare("dijit.form._FormValueMixin", _FormWidgetMixin, { + // summary: + // Mixin for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values. + // description: + // Each _FormValueMixin represents a single input value, and has a (possibly hidden) <input> element, + // to which it serializes it's input value, so that form submission (either normal submission or via FormBind?) + // works as expected. + + // readOnly: Boolean + // Should this widget respond to user input? + // In markup, this is specified as "readOnly". + // Similar to disabled except readOnly form values are submitted. + readOnly: false, + + _setReadOnlyAttr: function(/*Boolean*/ value){ + domAttr.set(this.focusNode, 'readOnly', value); + this.focusNode.setAttribute("aria-readonly", value); + this._set("readOnly", value); + }, + + postCreate: function(){ + this.inherited(arguments); + + if(has("ie")){ // IE won't stop the event with keypress + this.connect(this.focusNode || this.domNode, "onkeydown", this._onKeyDown); + } + // Update our reset value if it hasn't yet been set (because this.set() + // is only called when there *is* a value) + if(this._resetValue === undefined){ + this._lastValueReported = this._resetValue = this.value; + } + }, + + _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ + // summary: + // Hook so set('value', value) works. + // description: + // Sets the value of the widget. + // If the value has changed, then fire onChange event, unless priorityChange + // is specified as null (or false?) + this._handleOnChange(newValue, priorityChange); + }, + + _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ + // summary: + // Called when the value of the widget has changed. Saves the new value in this.value, + // and calls onChange() if appropriate. See _FormWidget._handleOnChange() for details. + this._set("value", newValue); + this.inherited(arguments); + }, + + undo: function(){ + // summary: + // Restore the value to the last value passed to onChange + this._setValueAttr(this._lastValueReported, false); + }, + + reset: function(){ + // summary: + // Reset the widget's value to what it was at initialization time + this._hasBeenBlurred = false; + this._setValueAttr(this._resetValue, true); + }, + + _onKeyDown: function(e){ + if(e.keyCode == keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){ + var te; + if(has("ie") < 9 || (has("ie") && has("quirks"))){ + e.preventDefault(); // default behavior needs to be stopped here since keypress is too late + te = document.createEventObject(); + te.keyCode = keys.ESCAPE; + te.shiftKey = e.shiftKey; + e.srcElement.fireEvent('onkeypress', te); } - if(rx > 0 && rx < b.w){ - if(rx < w){ - dx = -w; - }else if(rx > b.w - w){ - dx = w; - } + } + } + }); +}); + +}, +'dijit/form/_FormWidgetMixin':function(){ +define("dijit/form/_FormWidgetMixin", [ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/dom-style", // domStyle.get + "dojo/_base/lang", // lang.hitch lang.isArray + "dojo/mouse", // mouse.isLeft + "dojo/_base/sniff", // has("webkit") + "dojo/_base/window", // win.body + "dojo/window", // winUtils.scrollIntoView + "../a11y" // a11y.hasDefaultTabStop +], function(array, declare, domAttr, domStyle, lang, mouse, has, win, winUtils, a11y){ + +// module: +// dijit/form/_FormWidgetMixin +// summary: +// Mixin for widgets corresponding to native HTML elements such as <checkbox> or <button>, +// which can be children of a <form> node or a `dijit.form.Form` widget. + +return declare("dijit.form._FormWidgetMixin", null, { + // summary: + // Mixin for widgets corresponding to native HTML elements such as <checkbox> or <button>, + // which can be children of a <form> node or a `dijit.form.Form` widget. + // + // description: + // Represents a single HTML element. + // All these widgets should have these attributes just like native HTML input elements. + // You can set them during widget construction or afterwards, via `dijit._Widget.attr`. + // + // They also share some common methods. + + // name: [const] String + // Name used when submitting form; same as "name" attribute or plain HTML elements + name: "", + + // alt: String + // Corresponds to the native HTML <input> element's attribute. + alt: "", + + // value: String + // Corresponds to the native HTML <input> element's attribute. + value: "", + + // type: [const] String + // Corresponds to the native HTML <input> element's attribute. + type: "text", + + // tabIndex: Integer + // Order fields are traversed when user hits the tab key + tabIndex: "0", + _setTabIndexAttr: "focusNode", // force copy even when tabIndex default value, needed since Button is <span> + + // disabled: Boolean + // Should this widget respond to user input? + // In markup, this is specified as "disabled='disabled'", or just "disabled". + disabled: false, + + // intermediateChanges: Boolean + // Fires onChange for each value change or only on demand + intermediateChanges: false, + + // scrollOnFocus: Boolean + // On focus, should this widget scroll into view? + scrollOnFocus: true, + + // Override _WidgetBase mapping id to this.domNode, needs to be on focusNode so <label> etc. + // works with screen reader + _setIdAttr: "focusNode", + + _setDisabledAttr: function(/*Boolean*/ value){ + this._set("disabled", value); + domAttr.set(this.focusNode, 'disabled', value); + if(this.valueNode){ + domAttr.set(this.valueNode, 'disabled', value); + } + this.focusNode.setAttribute("aria-disabled", value ? "true" : "false"); + + if(value){ + // reset these, because after the domNode is disabled, we can no longer receive + // mouse related events, see #4200 + this._set("hovering", false); + this._set("active", false); + + // clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes) + var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex : + ("_setTabIndexAttr" in this) ? this._setTabIndexAttr : "focusNode"; + array.forEach(lang.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){ + var node = this[attachPointName]; + // complex code because tabIndex=-1 on a <div> doesn't work on FF + if(has("webkit") || a11y.hasDefaultTabStop(node)){ // see #11064 about webkit bug + node.setAttribute('tabIndex', "-1"); + }else{ + node.removeAttribute('tabIndex'); } - //console.log("ry =", ry, "b.h =", b.h, "h =", h); - if(ry > 0 && ry < b.h){ - if(ry < h){ - dy = -h; - }else if(ry > b.h - h){ - dy = h; - } + }, this); + }else{ + if(this.tabIndex != ""){ + this.set('tabIndex', this.tabIndex); + } + } + }, + + _onFocus: function(/*String*/ by){ + // If user clicks on the widget, even if the mouse is released outside of it, + // this widget's focusNode should get focus (to mimic native browser hehavior). + // Browsers often need help to make sure the focus via mouse actually gets to the focusNode. + if(by == "mouse" && this.isFocusable()){ + // IE exhibits strange scrolling behavior when refocusing a node so only do it when !focused. + var focusConnector = this.connect(this.focusNode, "onfocus", function(){ + this.disconnect(mouseUpConnector); + this.disconnect(focusConnector); + }); + // Set a global event to handle mouseup, so it fires properly + // even if the cursor leaves this.domNode before the mouse up event. + var mouseUpConnector = this.connect(win.body(), "onmouseup", function(){ + this.disconnect(mouseUpConnector); + this.disconnect(focusConnector); + // if here, then the mousedown did not focus the focusNode as the default action + if(this.focused){ + this.focus(); + } + }); + } + if(this.scrollOnFocus){ + this.defer(function(){ winUtils.scrollIntoView(this.domNode); }); // without defer, the input caret position can change on mouse click + } + this.inherited(arguments); + }, + + isFocusable: function(){ + // summary: + // Tells if this widget is focusable or not. Used internally by dijit. + // tags: + // protected + return !this.disabled && this.focusNode && (domStyle.get(this.domNode, "display") != "none"); + }, + + focus: function(){ + // summary: + // Put focus on this widget + if(!this.disabled && this.focusNode.focus){ + try{ this.focusNode.focus(); }catch(e){}/*squelch errors from hidden nodes*/ + } + }, + + compare: function(/*anything*/ val1, /*anything*/ val2){ + // summary: + // Compare 2 values (as returned by get('value') for this widget). + // tags: + // protected + if(typeof val1 == "number" && typeof val2 == "number"){ + return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2; + }else if(val1 > val2){ + return 1; + }else if(val1 < val2){ + return -1; + }else{ + return 0; + } + }, + + onChange: function(/*===== newValue =====*/){ + // summary: + // Callback when this widget's value is changed. + // tags: + // callback + }, + + // _onChangeActive: [private] Boolean + // Indicates that changes to the value should call onChange() callback. + // This is false during widget initialization, to avoid calling onChange() + // when the initial value is set. + _onChangeActive: false, + + _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ + // summary: + // Called when the value of the widget is set. Calls onChange() if appropriate + // newValue: + // the new value + // priorityChange: + // For a slider, for example, dragging the slider is priorityChange==false, + // but on mouse up, it's priorityChange==true. If intermediateChanges==false, + // onChange is only called form priorityChange=true events. + // tags: + // private + if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){ + // this block executes not for a change, but during initialization, + // and is used to store away the original value (or for ToggleButton, the original checked state) + this._resetValue = this._lastValueReported = newValue; + } + this._pendingOnChange = this._pendingOnChange + || (typeof newValue != typeof this._lastValueReported) + || (this.compare(newValue, this._lastValueReported) != 0); + if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){ + this._lastValueReported = newValue; + this._pendingOnChange = false; + if(this._onChangeActive){ + if(this._onChangeHandle){ + this._onChangeHandle.remove(); } - var oldLeft = n.scrollLeft, oldTop = n.scrollTop; - n.scrollLeft = n.scrollLeft + dx; - n.scrollTop = n.scrollTop + dy; - if(oldLeft != n.scrollLeft || oldTop != n.scrollTop){ return; } + // defer allows hidden value processing to run and + // also the onChange handler can safely adjust focus, etc + this._onChangeHandle = this.defer( + function(){ + this._onChangeHandle = null; + this.onChange(newValue); + }); // try to collapse multiple onChange's fired faster than can be processed } } - try{ - n = n.parentNode; - }catch(x){ - n = null; + }, + + create: function(){ + // Overrides _Widget.create() + this.inherited(arguments); + this._onChangeActive = true; + }, + + destroy: function(){ + if(this._onChangeHandle){ // destroy called before last onChange has fired + this._onChangeHandle.remove(); + this.onChange(this._lastValueReported); } + this.inherited(arguments); } - dojo.dnd.autoScroll(e); -}; +}); -} +}); -if(!dojo._hasResource["dojo.dnd.Mover"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.dnd.Mover"] = true; -dojo.provide("dojo.dnd.Mover"); +}, +'url:dijit/templates/ProgressBar.html':"<div class=\"dijitProgressBar dijitProgressBarEmpty\" role=\"progressbar\"\n\t><div data-dojo-attach-point=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\" role=\"presentation\"></div\n\t\t><span style=\"visibility:hidden\"> </span\n\t></div\n\t><div data-dojo-attach-point=\"labelNode\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"></div\n\t><img data-dojo-attach-point=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n", +'dijit/layout/_ContentPaneResizeMixin':function(){ +define("dijit/layout/_ContentPaneResizeMixin", [ + "dojo/_base/array", // array.filter array.forEach + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.has + "dojo/dom-class", // domClass.contains domClass.toggle + "dojo/dom-geometry",// domGeometry.contentBox domGeometry.marginBox + "dojo/_base/lang", // lang.mixin + "dojo/query", // query + "dojo/_base/sniff", // has("ie") + "dojo/_base/window", // win.global + "../registry", // registry.byId + "./utils", // marginBox2contextBox + "../_Contained" +], function(array, declare, domAttr, domClass, domGeometry, lang, query, has, win, + registry, layoutUtils, _Contained){ +/*===== +var _Contained = dijit._Contained; +=====*/ +// module: +// dijit/layout/_ContentPaneResizeMixin +// summary: +// Resize() functionality of ContentPane. If there's a single layout widget +// child then it will call resize() with the same dimensions as the ContentPane. +// Otherwise just calls resize on each child. -dojo.declare("dojo.dnd.Mover", null, { - constructor: function(node, e, host){ +return declare("dijit.layout._ContentPaneResizeMixin", null, { + // summary: + // Resize() functionality of ContentPane. If there's a single layout widget + // child then it will call resize() with the same dimensions as the ContentPane. + // Otherwise just calls resize on each child. + // + // Also implements basic startup() functionality, where starting the parent + // will start the children + + // doLayout: Boolean + // - false - don't adjust size of children + // - true - if there is a single visible child widget, set it's size to + // however big the ContentPane is + doLayout: true, + + // isLayoutContainer: [protected] Boolean + // Indicates that this widget will call resize() on it's child widgets + // when they become visible. + isLayoutContainer: true, + + startup: function(){ // summary: - // an object which makes a node follow the mouse, or touch-drag on touch devices. - // Used as a default mover, and as a base class for custom movers. - // node: Node - // a node (or node's id) to be moved - // e: Event - // a mouse event, which started the move; - // only pageX and pageY properties are used - // host: Object? - // object which implements the functionality of the move, - // and defines proper events (onMoveStart and onMoveStop) - this.node = dojo.byId(node); - var pos = e.touches ? e.touches[0] : e; - this.marginBox = {l: pos.pageX, t: pos.pageY}; - this.mouseButton = e.button; - var h = (this.host = host), d = node.ownerDocument; - this.events = [ - // At the start of a drag, onFirstMove is called, and then the following two - // connects are disconnected - dojo.connect(d, "onmousemove", this, "onFirstMove"), - dojo.connect(d, "ontouchmove", this, "onFirstMove"), + // See `dijit.layout._LayoutWidget.startup` for description. + // Although ContentPane doesn't extend _LayoutWidget, it does implement + // the same API. - // These are called continually during the drag - dojo.connect(d, "onmousemove", this, "onMouseMove"), - dojo.connect(d, "ontouchmove", this, "onMouseMove"), + if(this._started){ return; } - // And these are called at the end of the drag - dojo.connect(d, "onmouseup", this, "onMouseUp"), - dojo.connect(d, "ontouchend", this, "onMouseUp"), + var parent = this.getParent(); + this._childOfLayoutWidget = parent && parent.isLayoutContainer; - // cancel text selection and text dragging - dojo.connect(d, "ondragstart", dojo.stopEvent), - dojo.connect(d.body, "onselectstart", dojo.stopEvent) - ]; - // notify that the move has started - if(h && h.onMoveStart){ - h.onMoveStart(this); + // I need to call resize() on my child/children (when I become visible), unless + // I'm the child of a layout widget in which case my parent will call resize() on me and I'll do it then. + this._needLayout = !this._childOfLayoutWidget; + + this.inherited(arguments); + + if(this._isShown()){ + this._onShow(); + } + + if(!this._childOfLayoutWidget){ + // If my parent isn't a layout container, since my style *may be* width=height=100% + // or something similar (either set directly or via a CSS class), + // monitor when my size changes so that I can re-layout. + // For browsers where I can't directly monitor when my size changes, + // monitor when the viewport changes size, which *may* indicate a size change for me. + this.connect(has("ie") ? this.domNode : win.global, 'onresize', function(){ + // Using function(){} closure to ensure no arguments to resize. + this._needLayout = !this._childOfLayoutWidget; + this.resize(); + }); } }, - // mouse event processors - onMouseMove: function(e){ + + _checkIfSingleChild: function(){ // summary: - // event processor for onmousemove/ontouchmove - // e: Event - // mouse/touch event - dojo.dnd.autoScroll(e); - var m = this.marginBox, - pos = e.touches ? e.touches[0] : e; - this.host.onMove(this, {l: m.l + pos.pageX, t: m.t + pos.pageY}, e); - dojo.stopEvent(e); + // Test if we have exactly one visible widget as a child, + // and if so assume that we are a container for that widget, + // and should propagate startup() and resize() calls to it. + // Skips over things like data stores since they aren't visible. + + var childNodes = query("> *", this.containerNode).filter(function(node){ + return node.tagName !== "SCRIPT"; // or a regexp for hidden elements like script|area|map|etc.. + }), + childWidgetNodes = childNodes.filter(function(node){ + return domAttr.has(node, "data-dojo-type") || domAttr.has(node, "dojoType") || domAttr.has(node, "widgetId"); + }), + candidateWidgets = array.filter(childWidgetNodes.map(registry.byNode), function(widget){ + return widget && widget.domNode && widget.resize; + }); + + if( + // all child nodes are widgets + childNodes.length == childWidgetNodes.length && + + // all but one are invisible (like dojo.data) + candidateWidgets.length == 1 + ){ + this._singleChild = candidateWidgets[0]; + }else{ + delete this._singleChild; + } + + // So we can set overflow: hidden to avoid a safari bug w/scrollbars showing up (#9449) + domClass.toggle(this.containerNode, this.baseClass + "SingleChild", !!this._singleChild); }, - onMouseUp: function(e){ - if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ? - e.button == 0 : this.mouseButton == e.button){ // TODO Should condition be met for touch devices, too? - this.destroy(); + + resize: function(changeSize, resultSize){ + // summary: + // See `dijit.layout._LayoutWidget.resize` for description. + // Although ContentPane doesn't extend _LayoutWidget, it does implement + // the same API. + + // For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is + // never called, so resize() is our trigger to do the initial href download (see [20099]). + // However, don't load href for closed TitlePanes. + if(!this._wasShown && this.open !== false){ + this._onShow(); } - dojo.stopEvent(e); + + this._resizeCalled = true; + + this._scheduleLayout(changeSize, resultSize); }, - // utilities - onFirstMove: function(e){ + + _scheduleLayout: function(changeSize, resultSize){ // summary: - // makes the node absolute; it is meant to be called only once. - // relative and absolutely positioned nodes are assumed to use pixel units - var s = this.node.style, l, t, h = this.host; - switch(s.position){ - case "relative": - case "absolute": - // assume that left and top values are in pixels already - l = Math.round(parseFloat(s.left)) || 0; - t = Math.round(parseFloat(s.top)) || 0; - break; - default: - s.position = "absolute"; // enforcing the absolute mode - var m = dojo.marginBox(this.node); - // event.pageX/pageY (which we used to generate the initial - // margin box) includes padding and margin set on the body. - // However, setting the node's position to absolute and then - // doing dojo.marginBox on it *doesn't* take that additional - // space into account - so we need to subtract the combined - // padding and margin. We use getComputedStyle and - // _getMarginBox/_getContentBox to avoid the extra lookup of - // the computed style. - var b = dojo.doc.body; - var bs = dojo.getComputedStyle(b); - var bm = dojo._getMarginBox(b, bs); - var bc = dojo._getContentBox(b, bs); - l = m.l - (bc.l - bm.l); - t = m.t - (bc.t - bm.t); - break; + // Resize myself, and call resize() on each of my child layout widgets, either now + // (if I'm currently visible) or when I become visible + if(this._isShown()){ + this._layout(changeSize, resultSize); + }else{ + this._needLayout = true; + this._changeSize = changeSize; + this._resultSize = resultSize; } - this.marginBox.l = l - this.marginBox.l; - this.marginBox.t = t - this.marginBox.t; - if(h && h.onFirstMove){ - h.onFirstMove(this, e); + }, + + _layout: function(changeSize, resultSize){ + // summary: + // Resize myself according to optional changeSize/resultSize parameters, like a layout widget. + // Also, since I am a Container widget, each of my children expects me to + // call resize() or layout() on them. + // + // Should be called on initialization and also whenever we get new content + // (from an href, or from set('content', ...))... but deferred until + // the ContentPane is visible + + // Set margin box size, unless it wasn't specified, in which case use current size. + if(changeSize){ + domGeometry.setMarginBox(this.domNode, changeSize); } - - // Disconnect onmousemove and ontouchmove events that call this function - dojo.disconnect(this.events.shift()); - dojo.disconnect(this.events.shift()); + + // Compute content box size of containerNode in case we [later] need to size our single child. + var cn = this.containerNode; + if(cn === this.domNode){ + // If changeSize or resultSize was passed to this method and this.containerNode == + // this.domNode then we can compute the content-box size without querying the node, + // which is more reliable (similar to LayoutWidget.resize) (see for example #9449). + var mb = resultSize || {}; + lang.mixin(mb, changeSize || {}); // changeSize overrides resultSize + if(!("h" in mb) || !("w" in mb)){ + mb = lang.mixin(domGeometry.getMarginBox(cn), mb); // just use domGeometry.setMarginBox() to fill in missing values + } + this._contentBox = layoutUtils.marginBox2contentBox(cn, mb); + }else{ + this._contentBox = domGeometry.getContentBox(cn); + } + + this._layoutChildren(); + + delete this._needLayout; }, - destroy: function(){ + + _layoutChildren: function(){ + // Call _checkIfSingleChild() again in case app has manually mucked w/the content + // of the ContentPane (rather than changing it through the set("content", ...) API. + if(this.doLayout){ + this._checkIfSingleChild(); + } + + if(this._singleChild && this._singleChild.resize){ + var cb = this._contentBox || domGeometry.getContentBox(this.containerNode); + + // note: if widget has padding this._contentBox will have l and t set, + // but don't pass them to resize() or it will doubly-offset the child + this._singleChild.resize({w: cb.w, h: cb.h}); + }else{ + // All my child widgets are independently sized (rather than matching my size), + // but I still need to call resize() on each child to make it layout. + array.forEach(this.getChildren(), function(widget){ + if(widget.resize){ + widget.resize(); + } + }); + } + }, + + _isShown: function(){ // summary: - // stops the move, deletes all references, so the object can be garbage-collected - dojo.forEach(this.events, dojo.disconnect); - // undo global settings - var h = this.host; - if(h && h.onMoveStop){ - h.onMoveStop(this); + // Returns true if the content is currently shown. + // description: + // If I am a child of a layout widget then it actually returns true if I've ever been visible, + // not whether I'm currently visible, since that's much faster than tracing up the DOM/widget + // tree every call, and at least solves the performance problem on page load by deferring loading + // hidden ContentPanes until they are first shown + + if(this._childOfLayoutWidget){ + // If we are TitlePane, etc - we return that only *IF* we've been resized + if(this._resizeCalled && "open" in this){ + return this.open; + } + return this._resizeCalled; + }else if("open" in this){ + return this.open; // for TitlePane, etc. + }else{ + var node = this.domNode, parent = this.domNode.parentNode; + return (node.style.display != 'none') && (node.style.visibility != 'hidden') && !domClass.contains(node, "dijitHidden") && + parent && parent.style && (parent.style.display != 'none'); } - // destroy objects - this.events = this.node = this.host = null; + }, + + _onShow: function(){ + // summary: + // Called when the ContentPane is made visible + // description: + // For a plain ContentPane, this is called on initialization, from startup(). + // If the ContentPane is a hidden pane of a TabContainer etc., then it's + // called whenever the pane is made visible. + // + // Does layout/resize of child widget(s) + + if(this._needLayout){ + // If a layout has been scheduled for when we become visible, do it now + this._layout(this._changeSize, this._resultSize); + } + + this.inherited(arguments); + + // Need to keep track of whether ContentPane has been shown (which is different than + // whether or not it's currently visible). + this._wasShown = true; } }); -} +}); + +}, +'dijit/WidgetSet':function(){ +define("dijit/WidgetSet", [ + "dojo/_base/array", // array.forEach array.map + "dojo/_base/declare", // declare + "dojo/_base/window", // win.global + "./registry" // to add functions to dijit.registry +], function(array, declare, win, registry){ + + // module: + // dijit/WidgetSet + // summary: + // Legacy registry code. New modules should just use registry. + // Will be removed in 2.0. + + var WidgetSet = declare("dijit.WidgetSet", null, { + // summary: + // A set of widgets indexed by id. A default instance of this class is + // available as `dijit.registry` + // + // example: + // Create a small list of widgets: + // | var ws = new dijit.WidgetSet(); + // | ws.add(dijit.byId("one")); + // | ws.add(dijit.byId("two")); + // | // destroy both: + // | ws.forEach(function(w){ w.destroy(); }); + // + // example: + // Using dijit.registry: + // | dijit.registry.forEach(function(w){ /* do something */ }); + + constructor: function(){ + this._hash = {}; + this.length = 0; + }, + + add: function(/*dijit._Widget*/ widget){ + // summary: + // Add a widget to this list. If a duplicate ID is detected, a error is thrown. + // + // widget: dijit._Widget + // Any dijit._Widget subclass. + if(this._hash[widget.id]){ + throw new Error("Tried to register widget with id==" + widget.id + " but that id is already registered"); + } + this._hash[widget.id] = widget; + this.length++; + }, + + remove: function(/*String*/ id){ + // summary: + // Remove a widget from this WidgetSet. Does not destroy the widget; simply + // removes the reference. + if(this._hash[id]){ + delete this._hash[id]; + this.length--; + } + }, + + forEach: function(/*Function*/ func, /* Object? */thisObj){ + // summary: + // Call specified function for each widget in this set. + // + // func: + // A callback function to run for each item. Is passed the widget, the index + // in the iteration, and the full hash, similar to `array.forEach`. + // + // thisObj: + // An optional scope parameter + // + // example: + // Using the default `dijit.registry` instance: + // | dijit.registry.forEach(function(widget){ + // | console.log(widget.declaredClass); + // | }); + // + // returns: + // Returns self, in order to allow for further chaining. + + thisObj = thisObj || win.global; + var i = 0, id; + for(id in this._hash){ + func.call(thisObj, this._hash[id], i++, this._hash); + } + return this; // dijit.WidgetSet + }, + + filter: function(/*Function*/ filter, /* Object? */thisObj){ + // summary: + // Filter down this WidgetSet to a smaller new WidgetSet + // Works the same as `array.filter` and `NodeList.filter` + // + // filter: + // Callback function to test truthiness. Is passed the widget + // reference and the pseudo-index in the object. + // + // thisObj: Object? + // Option scope to use for the filter function. + // + // example: + // Arbitrary: select the odd widgets in this list + // | dijit.registry.filter(function(w, i){ + // | return i % 2 == 0; + // | }).forEach(function(w){ /* odd ones */ }); + + thisObj = thisObj || win.global; + var res = new WidgetSet(), i = 0, id; + for(id in this._hash){ + var w = this._hash[id]; + if(filter.call(thisObj, w, i++, this._hash)){ + res.add(w); + } + } + return res; // dijit.WidgetSet + }, + + byId: function(/*String*/ id){ + // summary: + // Find a widget in this list by it's id. + // example: + // Test if an id is in a particular WidgetSet + // | var ws = new dijit.WidgetSet(); + // | ws.add(dijit.byId("bar")); + // | var t = ws.byId("bar") // returns a widget + // | var x = ws.byId("foo"); // returns undefined + + return this._hash[id]; // dijit._Widget + }, + + byClass: function(/*String*/ cls){ + // summary: + // Reduce this widgetset to a new WidgetSet of a particular `declaredClass` + // + // cls: String + // The Class to scan for. Full dot-notated string. + // + // example: + // Find all `dijit.TitlePane`s in a page: + // | dijit.registry.byClass("dijit.TitlePane").forEach(function(tp){ tp.close(); }); + + var res = new WidgetSet(), id, widget; + for(id in this._hash){ + widget = this._hash[id]; + if(widget.declaredClass == cls){ + res.add(widget); + } + } + return res; // dijit.WidgetSet + }, + + toArray: function(){ + // summary: + // Convert this WidgetSet into a true Array + // + // example: + // Work with the widget .domNodes in a real Array + // | array.map(dijit.registry.toArray(), function(w){ return w.domNode; }); + + var ar = []; + for(var id in this._hash){ + ar.push(this._hash[id]); + } + return ar; // dijit._Widget[] + }, + + map: function(/* Function */func, /* Object? */thisObj){ + // summary: + // Create a new Array from this WidgetSet, following the same rules as `array.map` + // example: + // | var nodes = dijit.registry.map(function(w){ return w.domNode; }); + // + // returns: + // A new array of the returned values. + return array.map(this.toArray(), func, thisObj); // Array + }, -if(!dojo._hasResource["dojo.dnd.Moveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.dnd.Moveable"] = true; -dojo.provide("dojo.dnd.Moveable"); + every: function(func, thisObj){ + // summary: + // A synthetic clone of `array.every` acting explicitly on this WidgetSet + // + // func: Function + // A callback function run for every widget in this list. Exits loop + // when the first false return is encountered. + // + // thisObj: Object? + // Optional scope parameter to use for the callback + + thisObj = thisObj || win.global; + var x = 0, i; + for(i in this._hash){ + if(!func.call(thisObj, this._hash[i], x++, this._hash)){ + return false; // Boolean + } + } + return true; // Boolean + }, + some: function(func, thisObj){ + // summary: + // A synthetic clone of `array.some` acting explicitly on this WidgetSet + // + // func: Function + // A callback function run for every widget in this list. Exits loop + // when the first true return is encountered. + // + // thisObj: Object? + // Optional scope parameter to use for the callback + + thisObj = thisObj || win.global; + var x = 0, i; + for(i in this._hash){ + if(func.call(thisObj, this._hash[i], x++, this._hash)){ + return true; // Boolean + } + } + return false; // Boolean + } + + }); + + // Add in 1.x compatibility methods to dijit.registry. + // These functions won't show up in the API doc but since they are deprecated anyway, + // that's probably for the best. + array.forEach(["forEach", "filter", "byClass", "map", "every", "some"], function(func){ + registry[func] = WidgetSet.prototype[func]; + }); + + + return WidgetSet; +}); + +}, +'dojo/dnd/Moveable':function(){ +define("dojo/dnd/Moveable", ["../main", "../Evented", "../touch", "./Mover"], function(dojo, Evented, touch) { + // module: + // dojo/dnd/Moveable + // summary: + // TODOC /*===== @@ -8368,12 +13992,12 @@ dojo.declare("dojo.dnd.__MoveableArgs", [], { }); =====*/ -dojo.declare("dojo.dnd.Moveable", null, { +dojo.declare("dojo.dnd.Moveable", [Evented], { // object attributes (for markup) handle: "", delay: 0, skip: false, - + constructor: function(node, params){ // summary: // an object, which makes a node moveable @@ -8389,8 +14013,7 @@ dojo.declare("dojo.dnd.Moveable", null, { this.skip = params.skip; this.mover = params.mover ? params.mover : dojo.dnd.Mover; this.events = [ - dojo.connect(this.handle, "onmousedown", this, "onMouseDown"), - dojo.connect(this.handle, "ontouchstart", this, "onMouseDown"), + dojo.connect(this.handle, touch.press, this, "onMouseDown"), // cancel text selection and text dragging dojo.connect(this.handle, "ondragstart", this, "onSelectStart"), dojo.connect(this.handle, "onselectstart", this, "onSelectStart") @@ -8398,8 +14021,8 @@ dojo.declare("dojo.dnd.Moveable", null, { }, // markup methods - markupFactory: function(params, node){ - return new dojo.dnd.Moveable(node, params); + markupFactory: function(params, node, ctor){ + return new ctor(node, params); }, // methods @@ -8409,7 +14032,7 @@ dojo.declare("dojo.dnd.Moveable", null, { dojo.forEach(this.events, dojo.disconnect); this.events = this.node = this.handle = null; }, - + // mouse event processors onMouseDown: function(e){ // summary: @@ -8419,14 +14042,11 @@ dojo.declare("dojo.dnd.Moveable", null, { if(this.skip && dojo.dnd.isFormElement(e)){ return; } if(this.delay){ this.events.push( - dojo.connect(this.handle, "onmousemove", this, "onMouseMove"), - dojo.connect(this.handle, "ontouchmove", this, "onMouseMove"), - dojo.connect(this.handle, "onmouseup", this, "onMouseUp"), - dojo.connect(this.handle, "ontouchend", this, "onMouseUp") + dojo.connect(this.handle, touch.move, this, "onMouseMove"), + dojo.connect(this.handle, touch.release, this, "onMouseUp") ); - var pos = e.touches ? e.touches[0] : e; - this._lastX = pos.pageX; - this._lastY = pos.pageY; + this._lastX = e.pageX; + this._lastY = e.pageY; }else{ this.onDragDetected(e); } @@ -8437,8 +14057,7 @@ dojo.declare("dojo.dnd.Moveable", null, { // event processor for onmousemove/ontouchmove, used only for delayed drags // e: Event // mouse/touch event - var pos = e.touches ? e.touches[0] : e; - if(Math.abs(pos.pageX - this._lastX) > this.delay || Math.abs(pos.pageY - this._lastY) > this.delay){ + if(Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay){ this.onMouseUp(e); this.onDragDetected(e); } @@ -8463,7 +14082,7 @@ dojo.declare("dojo.dnd.Moveable", null, { dojo.stopEvent(e); } }, - + // local events onDragDetected: function(/* Event */ e){ // summary: @@ -8489,7 +14108,7 @@ dojo.declare("dojo.dnd.Moveable", null, { // summary: // called during the very first move notification; // can be used to initialize coordinates, can be overwritten. - + // default implementation does nothing }, onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop, /* Event */ e){ @@ -8505,24 +14124,2321 @@ dojo.declare("dojo.dnd.Moveable", null, { onMoving: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ // summary: // called before every incremental move; can be overwritten. - + // default implementation does nothing }, onMoved: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ // summary: // called after every incremental move; can be overwritten. - + // default implementation does nothing } }); +return dojo.dnd.Moveable; +}); + +}, +'dojo/store/util/SimpleQueryEngine':function(){ +define("dojo/store/util/SimpleQueryEngine", ["../../_base/array"], function(arrayUtil) { + // module: + // dojo/store/util/SimpleQueryEngine + // summary: + // The module defines a simple filtering query engine for object stores. + +return function(query, options){ + // summary: + // Simple query engine that matches using filter functions, named filter + // functions or objects by name-value on a query object hash + // + // description: + // The SimpleQueryEngine provides a way of getting a QueryResults through + // the use of a simple object hash as a filter. The hash will be used to + // match properties on data objects with the corresponding value given. In + // other words, only exact matches will be returned. + // + // This function can be used as a template for more complex query engines; + // for example, an engine can be created that accepts an object hash that + // contains filtering functions, or a string that gets evaluated, etc. + // + // When creating a new dojo.store, simply set the store's queryEngine + // field as a reference to this function. + // + // query: Object + // An object hash with fields that may match fields of items in the store. + // Values in the hash will be compared by normal == operator, but regular expressions + // or any object that provides a test() method are also supported and can be + // used to match strings by more complex expressions + // (and then the regex's or object's test() method will be used to match values). + // + // options: dojo.store.util.SimpleQueryEngine.__queryOptions? + // An object that contains optional information such as sort, start, and count. + // + // returns: Function + // A function that caches the passed query under the field "matches". See any + // of the "query" methods on dojo.stores. + // + // example: + // Define a store with a reference to this engine, and set up a query method. + // + // | var myStore = function(options){ + // | // ...more properties here + // | this.queryEngine = dojo.store.util.SimpleQueryEngine; + // | // define our query method + // | this.query = function(query, options){ + // | return dojo.store.util.QueryResults(this.queryEngine(query, options)(this.data)); + // | }; + // | }; + + // create our matching query function + switch(typeof query){ + default: + throw new Error("Can not query with a " + typeof query); + case "object": case "undefined": + var queryObject = query; + query = function(object){ + for(var key in queryObject){ + var required = queryObject[key]; + if(required && required.test){ + if(!required.test(object[key])){ + return false; + } + }else if(required != object[key]){ + return false; + } + } + return true; + }; + break; + case "string": + // named query + if(!this[query]){ + throw new Error("No filter function " + query + " was found in store"); + } + query = this[query]; + // fall through + case "function": + // fall through + } + function execute(array){ + // execute the whole query, first we filter + var results = arrayUtil.filter(array, query); + // next we sort + if(options && options.sort){ + results.sort(function(a, b){ + for(var sort, i=0; sort = options.sort[i]; i++){ + var aValue = a[sort.attribute]; + var bValue = b[sort.attribute]; + if (aValue != bValue) { + return !!sort.descending == aValue > bValue ? -1 : 1; + } + } + return 0; + }); + } + // now we paginate + if(options && (options.start || options.count)){ + var total = results.length; + results = results.slice(options.start || 0, (options.start || 0) + (options.count || Infinity)); + results.total = total; + } + return results; + } + execute.matches = query; + return execute; +}; +}); + +}, +'dijit/typematic':function(){ +define("dijit/typematic", [ + "dojo/_base/array", // array.forEach + "dojo/_base/connect", // connect.connect + "dojo/_base/event", // event.stop + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/lang", // lang.mixin, lang.hitch + "dojo/on", + "dojo/_base/sniff", // has("ie") + "." // setting dijit.typematic global +], function(array, connect, event, kernel, lang, on, has, dijit){ + +// module: +// dijit/typematic +// summary: +// These functions are used to repetitively call a user specified callback +// method when a specific key or mouse click over a specific DOM node is +// held down for a specific amount of time. +// Only 1 such event is allowed to occur on the browser page at 1 time. + +var typematic = (dijit.typematic = { + // summary: + // These functions are used to repetitively call a user specified callback + // method when a specific key or mouse click over a specific DOM node is + // held down for a specific amount of time. + // Only 1 such event is allowed to occur on the browser page at 1 time. + + _fireEventAndReload: function(){ + this._timer = null; + this._callback(++this._count, this._node, this._evt); + + // Schedule next event, timer is at most minDelay (default 10ms) to avoid + // browser overload (particularly avoiding starving DOH robot so it never gets to send a mouseup) + this._currentTimeout = Math.max( + this._currentTimeout < 0 ? this._initialDelay : + (this._subsequentDelay > 1 ? this._subsequentDelay : Math.round(this._currentTimeout * this._subsequentDelay)), + this._minDelay); + this._timer = setTimeout(lang.hitch(this, "_fireEventAndReload"), this._currentTimeout); + }, + + trigger: function(/*Event*/ evt, /*Object*/ _this, /*DOMNode*/ node, /*Function*/ callback, /*Object*/ obj, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ + // summary: + // Start a timed, repeating callback sequence. + // If already started, the function call is ignored. + // This method is not normally called by the user but can be + // when the normal listener code is insufficient. + // evt: + // key or mouse event object to pass to the user callback + // _this: + // pointer to the user's widget space. + // node: + // the DOM node object to pass the the callback function + // callback: + // function to call until the sequence is stopped called with 3 parameters: + // count: + // integer representing number of repeated calls (0..n) with -1 indicating the iteration has stopped + // node: + // the DOM node object passed in + // evt: + // key or mouse event object + // obj: + // user space object used to uniquely identify each typematic sequence + // subsequentDelay (optional): + // if > 1, the number of milliseconds until the 3->n events occur + // or else the fractional time multiplier for the next event's delay, default=0.9 + // initialDelay (optional): + // the number of milliseconds until the 2nd event occurs, default=500ms + // minDelay (optional): + // the maximum delay in milliseconds for event to fire, default=10ms + if(obj != this._obj){ + this.stop(); + this._initialDelay = initialDelay || 500; + this._subsequentDelay = subsequentDelay || 0.90; + this._minDelay = minDelay || 10; + this._obj = obj; + this._evt = evt; + this._node = node; + this._currentTimeout = -1; + this._count = -1; + this._callback = lang.hitch(_this, callback); + this._fireEventAndReload(); + this._evt = lang.mixin({faux: true}, evt); + } + }, + + stop: function(){ + // summary: + // Stop an ongoing timed, repeating callback sequence. + if(this._timer){ + clearTimeout(this._timer); + this._timer = null; + } + if(this._obj){ + this._callback(-1, this._node, this._evt); + this._obj = null; + } + }, + + addKeyListener: function(/*DOMNode*/ node, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ + // summary: + // Start listening for a specific typematic key. + // See also the trigger method for other parameters. + // keyObject: + // an object defining the key to listen for: + // charOrCode: + // the printable character (string) or keyCode (number) to listen for. + // keyCode: + // (deprecated - use charOrCode) the keyCode (number) to listen for (implies charCode = 0). + // charCode: + // (deprecated - use charOrCode) the charCode (number) to listen for. + // ctrlKey: + // desired ctrl key state to initiate the callback sequence: + // - pressed (true) + // - released (false) + // - either (unspecified) + // altKey: + // same as ctrlKey but for the alt key + // shiftKey: + // same as ctrlKey but for the shift key + // returns: + // a connection handle + if(keyObject.keyCode){ + keyObject.charOrCode = keyObject.keyCode; + kernel.deprecated("keyCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0"); + }else if(keyObject.charCode){ + keyObject.charOrCode = String.fromCharCode(keyObject.charCode); + kernel.deprecated("charCode attribute parameter for dijit.typematic.addKeyListener is deprecated. Use charOrCode instead.", "", "2.0"); + } + var handles = [ + on(node, connect._keypress, lang.hitch(this, function(evt){ + if(evt.charOrCode == keyObject.charOrCode && + (keyObject.ctrlKey === undefined || keyObject.ctrlKey == evt.ctrlKey) && + (keyObject.altKey === undefined || keyObject.altKey == evt.altKey) && + (keyObject.metaKey === undefined || keyObject.metaKey == (evt.metaKey || false)) && // IE doesn't even set metaKey + (keyObject.shiftKey === undefined || keyObject.shiftKey == evt.shiftKey)){ + event.stop(evt); + typematic.trigger(evt, _this, node, callback, keyObject, subsequentDelay, initialDelay, minDelay); + }else if(typematic._obj == keyObject){ + typematic.stop(); + } + })), + on(node, "keyup", lang.hitch(this, function(){ + if(typematic._obj == keyObject){ + typematic.stop(); + } + })) + ]; + return { remove: function(){ array.forEach(handles, function(h){ h.remove(); }); } }; + }, + + addMouseListener: function(/*DOMNode*/ node, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ + // summary: + // Start listening for a typematic mouse click. + // See the trigger method for other parameters. + // returns: + // a connection handle + var handles = [ + on(node, "mousedown", lang.hitch(this, function(evt){ + event.stop(evt); + typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay); + })), + on(node, "mouseup", lang.hitch(this, function(evt){ + if(this._obj){ + event.stop(evt); + } + typematic.stop(); + })), + on(node, "mouseout", lang.hitch(this, function(evt){ + event.stop(evt); + typematic.stop(); + })), + on(node, "mousemove", lang.hitch(this, function(evt){ + evt.preventDefault(); + })), + on(node, "dblclick", lang.hitch(this, function(evt){ + event.stop(evt); + if(has("ie")){ + typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay); + setTimeout(lang.hitch(this, typematic.stop), 50); + } + })) + ]; + return { remove: function(){ array.forEach(handles, function(h){ h.remove(); }); } }; + }, + + addListener: function(/*Node*/ mouseNode, /*Node*/ keyNode, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){ + // summary: + // Start listening for a specific typematic key and mouseclick. + // This is a thin wrapper to addKeyListener and addMouseListener. + // See the addMouseListener and addKeyListener methods for other parameters. + // mouseNode: + // the DOM node object to listen on for mouse events. + // keyNode: + // the DOM node object to listen on for key events. + // returns: + // a connection handle + var handles = [ + this.addKeyListener(keyNode, keyObject, _this, callback, subsequentDelay, initialDelay, minDelay), + this.addMouseListener(mouseNode, _this, callback, subsequentDelay, initialDelay, minDelay) + ]; + return { remove: function(){ array.forEach(handles, function(h){ h.remove(); }); } }; + } +}); + +return typematic; + +}); + +}, +'dijit/MenuItem':function(){ +require({cache:{ +'url:dijit/templates/MenuItem.html':"<tr class=\"dijitReset dijitMenuItem\" data-dojo-attach-point=\"focusNode\" role=\"menuitem\" tabIndex=\"-1\"\n\t\tdata-dojo-attach-event=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" role=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitMenuItemIcon\" data-dojo-attach-point=\"iconNode\"/>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" data-dojo-attach-point=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" data-dojo-attach-point=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\">\n\t\t<div data-dojo-attach-point=\"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"}}); +define("dijit/MenuItem", [ + "dojo/_base/declare", // declare + "dojo/dom", // dom.setSelectable + "dojo/dom-attr", // domAttr.set + "dojo/dom-class", // domClass.toggle + "dojo/_base/event", // event.stop + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/sniff", // has("ie") + "./_Widget", + "./_TemplatedMixin", + "./_Contained", + "./_CssStateMixin", + "dojo/text!./templates/MenuItem.html" +], function(declare, dom, domAttr, domClass, event, kernel, has, + _Widget, _TemplatedMixin, _Contained, _CssStateMixin, template){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _Contained = dijit._Contained; + var _CssStateMixin = dijit._CssStateMixin; +=====*/ + + // module: + // dijit/MenuItem + // summary: + // A line item in a Menu Widget + + + return declare("dijit.MenuItem", + [_Widget, _TemplatedMixin, _Contained, _CssStateMixin], + { + // summary: + // A line item in a Menu Widget + + // Make 3 columns + // icon, label, and expand arrow (BiDi-dependent) indicating sub-menu + templateString: template, + + baseClass: "dijitMenuItem", + + // label: String + // Menu text + label: '', + _setLabelAttr: { node: "containerNode", type: "innerHTML" }, + + // iconClass: String + // Class to apply to DOMNode to make it display an icon. + iconClass: "dijitNoIcon", + _setIconClassAttr: { node: "iconNode", type: "class" }, + + // accelKey: String + // Text for the accelerator (shortcut) key combination. + // Note that although Menu can display accelerator keys there + // is no infrastructure to actually catch and execute these + // accelerators. + accelKey: "", + + // disabled: Boolean + // If true, the menu item is disabled. + // If false, the menu item is enabled. + disabled: false, + + _fillContent: function(/*DomNode*/ source){ + // If button label is specified as srcNodeRef.innerHTML rather than + // this.params.label, handle it here. + if(source && !("label" in this.params)){ + this.set('label', source.innerHTML); + } + }, + + buildRendering: function(){ + this.inherited(arguments); + var label = this.id+"_text"; + domAttr.set(this.containerNode, "id", label); + if(this.accelKeyNode){ + domAttr.set(this.accelKeyNode, "id", this.id + "_accel"); + label += " " + this.id + "_accel"; + } + this.domNode.setAttribute("aria-labelledby", label); + dom.setSelectable(this.domNode, false); + }, + + _onHover: function(){ + // summary: + // Handler when mouse is moved onto menu item + // tags: + // protected + this.getParent().onItemHover(this); + }, + + _onUnhover: function(){ + // summary: + // Handler when mouse is moved off of menu item, + // possibly to a child menu, or maybe to a sibling + // menuitem or somewhere else entirely. + // tags: + // protected + + // if we are unhovering the currently selected item + // then unselect it + this.getParent().onItemUnhover(this); + + // When menu is hidden (collapsed) due to clicking a MenuItem and having it execute, + // FF and IE don't generate an onmouseout event for the MenuItem. + // So, help out _CssStateMixin in this case. + this._set("hovering", false); + }, + + _onClick: function(evt){ + // summary: + // Internal handler for click events on MenuItem. + // tags: + // private + this.getParent().onItemClick(this, evt); + event.stop(evt); + }, + + onClick: function(/*Event*/){ + // summary: + // User defined function to handle clicks + // tags: + // callback + }, + + focus: function(){ + // summary: + // Focus on this MenuItem + try{ + if(has("ie") == 8){ + // needed for IE8 which won't scroll TR tags into view on focus yet calling scrollIntoView creates flicker (#10275) + this.containerNode.focus(); + } + this.focusNode.focus(); + }catch(e){ + // this throws on IE (at least) in some scenarios + } + }, + + _onFocus: function(){ + // summary: + // This is called by the focus manager when focus + // goes to this MenuItem or a child menu. + // tags: + // protected + this._setSelected(true); + this.getParent()._onItemFocus(this); + + this.inherited(arguments); + }, + + _setSelected: function(selected){ + // summary: + // Indicate that this node is the currently selected one + // tags: + // private + + /*** + * TODO: remove this method and calls to it, when _onBlur() is working for MenuItem. + * Currently _onBlur() gets called when focus is moved from the MenuItem to a child menu. + * That's not supposed to happen, but the problem is: + * In order to allow dijit.popup's getTopPopup() to work,a sub menu's popupParent + * points to the parent Menu, bypassing the parent MenuItem... thus the + * MenuItem is not in the chain of active widgets and gets a premature call to + * _onBlur() + */ + + domClass.toggle(this.domNode, "dijitMenuItemSelected", selected); + }, + + setLabel: function(/*String*/ content){ + // summary: + // Deprecated. Use set('label', ...) instead. + // tags: + // deprecated + kernel.deprecated("dijit.MenuItem.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0"); + this.set("label", content); + }, + + setDisabled: function(/*Boolean*/ disabled){ + // summary: + // Deprecated. Use set('disabled', bool) instead. + // tags: + // deprecated + kernel.deprecated("dijit.Menu.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0"); + this.set('disabled', disabled); + }, + _setDisabledAttr: function(/*Boolean*/ value){ + // summary: + // Hook for attr('disabled', ...) to work. + // Enable or disable this menu item. + + this.focusNode.setAttribute('aria-disabled', value ? 'true' : 'false'); + this._set("disabled", value); + }, + _setAccelKeyAttr: function(/*String*/ value){ + // summary: + // Hook for attr('accelKey', ...) to work. + // Set accelKey on this menu item. + + this.accelKeyNode.style.display=value?"":"none"; + this.accelKeyNode.innerHTML=value; + //have to use colSpan to make it work in IE + domAttr.set(this.containerNode,'colSpan',value?"1":"2"); + + this._set("accelKey", value); + } + }); +}); + +}, +'dijit/layout/TabController':function(){ +require({cache:{ +'url:dijit/layout/templates/_TabButton.html':"<div role=\"presentation\" data-dojo-attach-point=\"titleNode\" data-dojo-attach-event='onclick:onClick'>\n <div role=\"presentation\" class='dijitTabInnerDiv' data-dojo-attach-point='innerDiv'>\n <div role=\"presentation\" class='dijitTabContent' data-dojo-attach-point='tabContent'>\n \t<div role=\"presentation\" data-dojo-attach-point='focusNode'>\n\t\t <img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitTabButtonIcon\" data-dojo-attach-point='iconNode' />\n\t\t <span data-dojo-attach-point='containerNode' class='tabLabel'></span>\n\t\t <span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" data-dojo-attach-point='closeNode'\n\t\t \t\tdata-dojo-attach-event='onclick: onClickCloseButton' role=\"presentation\">\n\t\t <span data-dojo-attach-point='closeText' class='dijitTabCloseText'>[x]</span\n\t\t ></span>\n\t\t\t</div>\n </div>\n </div>\n</div>\n"}}); +define("dijit/layout/TabController", [ + "dojo/_base/declare", // declare + "dojo/dom", // dom.setSelectable + "dojo/dom-attr", // domAttr.attr + "dojo/dom-class", // domClass.toggle + "dojo/i18n", // i18n.getLocalization + "dojo/_base/lang", // lang.hitch lang.trim + "./StackController", + "../Menu", + "../MenuItem", + "dojo/text!./templates/_TabButton.html", + "dojo/i18n!../nls/common" +], function(declare, dom, domAttr, domClass, i18n, lang, StackController, Menu, MenuItem, template){ + +/*===== + var StackController = dijit.layout.StackController; + var Menu = dijit.Menu; + var MenuItem = dijit.MenuItem; +=====*/ + + // module: + // dijit/layout/TabController + // summary: + // Set of tabs (the things with titles and a close button, that you click to show a tab panel). + // Used internally by `dijit.layout.TabContainer`. + + var TabButton = declare("dijit.layout._TabButton", StackController.StackButton, { + // summary: + // A tab (the thing you click to select a pane). + // description: + // Contains the title of the pane, and optionally a close-button to destroy the pane. + // This is an internal widget and should not be instantiated directly. + // tags: + // private + + // baseClass: String + // The CSS class applied to the domNode. + baseClass: "dijitTab", + + // Apply dijitTabCloseButtonHover when close button is hovered + cssStateNodes: { + closeNode: "dijitTabCloseButton" + }, + + templateString: template, + + // Override _FormWidget.scrollOnFocus. + // Don't scroll the whole tab container into view when the button is focused. + scrollOnFocus: false, + + buildRendering: function(){ + this.inherited(arguments); + + dom.setSelectable(this.containerNode, false); + }, + + startup: function(){ + this.inherited(arguments); + var n = this.domNode; + + // Required to give IE6 a kick, as it initially hides the + // tabs until they are focused on. + setTimeout(function(){ + n.className = n.className; + }, 1); + }, + + _setCloseButtonAttr: function(/*Boolean*/ disp){ + // summary: + // Hide/show close button + this._set("closeButton", disp); + domClass.toggle(this.innerDiv, "dijitClosable", disp); + this.closeNode.style.display = disp ? "" : "none"; + if(disp){ + var _nlsResources = i18n.getLocalization("dijit", "common"); + if(this.closeNode){ + domAttr.set(this.closeNode,"title", _nlsResources.itemClose); + } + // add context menu onto title button + this._closeMenu = new Menu({ + id: this.id+"_Menu", + dir: this.dir, + lang: this.lang, + textDir: this.textDir, + targetNodeIds: [this.domNode] + }); + + this._closeMenu.addChild(new MenuItem({ + label: _nlsResources.itemClose, + dir: this.dir, + lang: this.lang, + textDir: this.textDir, + onClick: lang.hitch(this, "onClickCloseButton") + })); + }else{ + if(this._closeMenu){ + this._closeMenu.destroyRecursive(); + delete this._closeMenu; + } + } + }, + _setLabelAttr: function(/*String*/ content){ + // summary: + // Hook for set('label', ...) to work. + // description: + // takes an HTML string. + // Inherited ToggleButton implementation will Set the label (text) of the button; + // Need to set the alt attribute of icon on tab buttons if no label displayed + this.inherited(arguments); + if(!this.showLabel && !this.params.title){ + this.iconNode.alt = lang.trim(this.containerNode.innerText || this.containerNode.textContent || ''); + } + }, + + destroy: function(){ + if(this._closeMenu){ + this._closeMenu.destroyRecursive(); + delete this._closeMenu; + } + this.inherited(arguments); + } + }); + + var TabController = declare("dijit.layout.TabController", StackController, { + // summary: + // Set of tabs (the things with titles and a close button, that you click to show a tab panel). + // Used internally by `dijit.layout.TabContainer`. + // description: + // Lets the user select the currently shown pane in a TabContainer or StackContainer. + // TabController also monitors the TabContainer, and whenever a pane is + // added or deleted updates itself accordingly. + // tags: + // private + + baseClass: "dijitTabController", + + templateString: "<div role='tablist' data-dojo-attach-event='onkeypress:onkeypress'></div>", + + // tabPosition: String + // Defines where tabs go relative to the content. + // "top", "bottom", "left-h", "right-h" + tabPosition: "top", + + // buttonWidget: Constructor + // The tab widget to create to correspond to each page + buttonWidget: TabButton, + + _rectifyRtlTabList: function(){ + // summary: + // 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 + + if(0 >= this.tabPosition.indexOf('-h')){ return; } + if(!this.pane2button){ return; } + + var maxWidth = 0; + for(var pane in this.pane2button){ + var ow = this.pane2button[pane].innerDiv.scrollWidth; + maxWidth = Math.max(maxWidth, ow); + } + //unify the length of all the tabs + for(pane in this.pane2button){ + this.pane2button[pane].innerDiv.style.width = maxWidth + 'px'; + } + } + }); + + TabController.TabButton = TabButton; // for monkey patching + + return TabController; +}); + +}, +'dijit/layout/_LayoutWidget':function(){ +define("dijit/layout/_LayoutWidget", [ + "dojo/_base/lang", // lang.mixin + "../_Widget", + "../_Container", + "../_Contained", + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add domClass.remove + "dojo/dom-geometry", // domGeometry.marginBox + "dojo/dom-style", // domStyle.getComputedStyle + "dojo/_base/sniff", // has("ie") + "dojo/_base/window" // win.global +], function(lang, _Widget, _Container, _Contained, + declare, domClass, domGeometry, domStyle, has, win){ + +/*===== + var _Widget = dijit._Widget; + var _Container = dijit._Container; + var _Contained = dijit._Contained; +=====*/ + + // module: + // dijit/layout/_LayoutWidget + // summary: + // _LayoutWidget Base class for a _Container widget which is responsible for laying out its children. + // Widgets which mixin this code must define layout() to manage placement and sizing of the children. + + + return declare("dijit.layout._LayoutWidget", [_Widget, _Container, _Contained], { + // summary: + // Base class for a _Container widget which is responsible for laying out its children. + // Widgets which mixin this code must define layout() to manage placement and sizing of the children. + + // baseClass: [protected extension] String + // This class name is applied to the widget's domNode + // and also may be used to generate names for sub nodes, + // for example dijitTabContainer-content. + baseClass: "dijitLayoutContainer", + + // isLayoutContainer: [protected] Boolean + // Indicates that this widget is going to call resize() on its + // children widgets, setting their size, when they become visible. + isLayoutContainer: true, + + buildRendering: function(){ + this.inherited(arguments); + domClass.add(this.domNode, "dijitContainer"); + }, + + startup: function(){ + // summary: + // Called after all the widgets have been instantiated and their + // dom nodes have been inserted somewhere under win.doc.body. + // + // Widgets should override this method to do any initialization + // dependent on other widgets existing, and then call + // this superclass method to finish things off. + // + // startup() in subclasses shouldn't do anything + // size related because the size of the widget hasn't been set yet. + + if(this._started){ return; } + + // Need to call inherited first - so that child widgets get started + // up correctly + this.inherited(arguments); + + // If I am a not being controlled by a parent layout widget... + var parent = this.getParent && this.getParent(); + if(!(parent && parent.isLayoutContainer)){ + // Do recursive sizing and layout of all my descendants + // (passing in no argument to resize means that it has to glean the size itself) + this.resize(); + + // Since my parent isn't a layout container, and my style *may be* width=height=100% + // or something similar (either set directly or via a CSS class), + // monitor when viewport size changes so that I can re-layout. + this.connect(win.global, 'onresize', function(){ + // Using function(){} closure to ensure no arguments passed to resize(). + this.resize(); + }); + } + }, + + resize: function(changeSize, resultSize){ + // summary: + // Call this to resize a widget, or after its size has changed. + // description: + // Change size mode: + // When changeSize is specified, changes the marginBox of this widget + // and forces it to relayout its contents accordingly. + // changeSize may specify height, width, or both. + // + // If resultSize is specified it indicates the size the widget will + // become after changeSize has been applied. + // + // Notification mode: + // When changeSize is null, indicates that the caller has already changed + // the size of the widget, or perhaps it changed because the browser + // window was resized. Tells widget to relayout its contents accordingly. + // + // If resultSize is also specified it indicates the size the widget has + // become. + // + // In either mode, this method also: + // 1. Sets this._borderBox and this._contentBox to the new size of + // the widget. Queries the current domNode size if necessary. + // 2. Calls layout() to resize contents (and maybe adjust child widgets). + // + // changeSize: Object? + // Sets the widget to this margin-box size and position. + // May include any/all of the following properties: + // | {w: int, h: int, l: int, t: int} + // + // resultSize: Object? + // The margin-box size of this widget after applying changeSize (if + // changeSize is specified). If caller knows this size and + // passes it in, we don't need to query the browser to get the size. + // | {w: int, h: int} + + var node = this.domNode; + + // set margin box size, unless it wasn't specified, in which case use current size + if(changeSize){ + domGeometry.setMarginBox(node, changeSize); + } + + // If either height or width wasn't specified by the user, then query node for it. + // But note that setting the margin box and then immediately querying dimensions may return + // inaccurate results, so try not to depend on it. + var mb = resultSize || {}; + lang.mixin(mb, changeSize || {}); // changeSize overrides resultSize + if( !("h" in mb) || !("w" in mb) ){ + mb = lang.mixin(domGeometry.getMarginBox(node), mb); // just use domGeometry.marginBox() to fill in missing values + } + + // Compute and save the size of my border box and content box + // (w/out calling domGeometry.getContentBox() since that may fail if size was recently set) + var cs = domStyle.getComputedStyle(node); + var me = domGeometry.getMarginExtents(node, cs); + var be = domGeometry.getBorderExtents(node, cs); + var bb = (this._borderBox = { + w: mb.w - (me.w + be.w), + h: mb.h - (me.h + be.h) + }); + var pe = domGeometry.getPadExtents(node, cs); + this._contentBox = { + l: domStyle.toPixelValue(node, cs.paddingLeft), + t: domStyle.toPixelValue(node, cs.paddingTop), + w: bb.w - pe.w, + h: bb.h - pe.h + }; + + // Callback for widget to adjust size of its children + this.layout(); + }, + + layout: function(){ + // summary: + // Widgets override this method to size and position their contents/children. + // When this is called this._contentBox is guaranteed to be set (see resize()). + // + // This is called after startup(), and also when the widget's size has been + // changed. + // tags: + // protected extension + }, + + _setupChild: function(/*dijit._Widget*/child){ + // summary: + // Common setup for initial children and children which are added after startup + // tags: + // protected extension + + var cls = this.baseClass + "-child " + + (child.baseClass ? this.baseClass + "-" + child.baseClass : ""); + domClass.add(child.domNode, cls); + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // Overrides _Container.addChild() to call _setupChild() + this.inherited(arguments); + if(this._started){ + this._setupChild(child); + } + }, + + removeChild: function(/*dijit._Widget*/ child){ + // Overrides _Container.removeChild() to remove class added by _setupChild() + var cls = this.baseClass + "-child" + + (child.baseClass ? + " " + this.baseClass + "-" + child.baseClass : ""); + domClass.remove(child.domNode, cls); + + this.inherited(arguments); + } + }); +}); + +}, +'dijit/popup':function(){ +define("dijit/popup", [ + "dojo/_base/array", // array.forEach array.some + "dojo/aspect", + "dojo/_base/connect", // connect._keypress + "dojo/_base/declare", // declare + "dojo/dom", // dom.isDescendant + "dojo/dom-attr", // domAttr.set + "dojo/dom-construct", // domConstruct.create domConstruct.destroy + "dojo/dom-geometry", // domGeometry.isBodyLtr + "dojo/dom-style", // domStyle.set + "dojo/_base/event", // event.stop + "dojo/keys", + "dojo/_base/lang", // lang.hitch + "dojo/on", + "dojo/_base/sniff", // has("ie") has("mozilla") + "dojo/_base/window", // win.body + "./place", + "./BackgroundIframe", + "." // dijit (defining dijit.popup to match API doc) +], function(array, aspect, connect, declare, dom, domAttr, domConstruct, domGeometry, domStyle, event, keys, lang, on, has, win, + place, BackgroundIframe, dijit){ + + // module: + // dijit/popup + // summary: + // Used to show drop downs (ex: the select list of a ComboBox) + // or popups (ex: right-click context menus) + + + /*===== + dijit.popup.__OpenArgs = function(){ + // popup: Widget + // widget to display + // parent: Widget + // the button etc. that is displaying this popup + // around: DomNode + // DOM node (typically a button); place popup relative to this node. (Specify this *or* "x" and "y" parameters.) + // x: Integer + // Absolute horizontal position (in pixels) to place node at. (Specify this *or* "around" parameter.) + // y: Integer + // Absolute vertical position (in pixels) to place node at. (Specify this *or* "around" parameter.) + // orient: Object|String + // When the around parameter is specified, orient should be a list of positions to try, ex: + // | [ "below", "above" ] + // For backwards compatibility it can also be an (ordered) hash of tuples of the form + // (around-node-corner, popup-node-corner), ex: + // | { "BL": "TL", "TL": "BL" } + // where BL means "bottom left" and "TL" means "top left", etc. + // + // dijit.popup.open() tries to position the popup according to each specified position, in order, + // until the popup appears fully within the viewport. + // + // The default value is ["below", "above"] + // + // When an (x,y) position is specified rather than an around node, orient is either + // "R" or "L". R (for right) means that it tries to put the popup to the right of the mouse, + // specifically positioning the popup's top-right corner at the mouse position, and if that doesn't + // fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner, + // and the top-right corner. + // onCancel: Function + // callback when user has canceled the popup by + // 1. hitting ESC or + // 2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog); + // i.e. whenever popupWidget.onCancel() is called, args.onCancel is called + // onClose: Function + // callback whenever this popup is closed + // onExecute: Function + // callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only) + // padding: dijit.__Position + // adding a buffer around the opening position. This is only useful when around is not set. + this.popup = popup; + this.parent = parent; + this.around = around; + this.x = x; + this.y = y; + this.orient = orient; + this.onCancel = onCancel; + this.onClose = onClose; + this.onExecute = onExecute; + this.padding = padding; + } + =====*/ + + /*===== + dijit.popup = { + // summary: + // Used to show drop downs (ex: the select list of a ComboBox) + // or popups (ex: right-click context menus). + // + // Access via require(["dijit/popup"], function(popup){ ... }). + + moveOffScreen: function(widget){ + // summary: + // Moves the popup widget off-screen. + // Do not use this method to hide popups when not in use, because + // that will create an accessibility issue: the offscreen popup is + // still in the tabbing order. + // widget: dijit._WidgetBase + // The widget + }, + + hide: function(widget){ + // summary: + // Hide this popup widget (until it is ready to be shown). + // Initialization for widgets that will be used as popups + // + // Also puts widget inside a wrapper DIV (if not already in one) + // + // If popup widget needs to layout it should + // do so when it is made visible, and popup._onShow() is called. + // widget: dijit._WidgetBase + // The widget + }, + + open: function(args){ + // summary: + // Popup the widget at the specified position + // example: + // opening at the mouse position + // | popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY}); + // example: + // opening the widget as a dropdown + // | popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}}); + // + // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback + // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed. + // args: dijit.popup.__OpenArgs + // Parameters + return {}; // Object specifying which position was chosen + }, + + close: function(popup){ + // summary: + // Close specified popup and any popups that it parented. + // If no popup is specified, closes all popups. + // widget: dijit._WidgetBase? + // The widget, optional + } + }; + =====*/ + + var PopupManager = declare(null, { + // _stack: dijit._Widget[] + // Stack of currently popped up widgets. + // (someone opened _stack[0], and then it opened _stack[1], etc.) + _stack: [], + + // _beginZIndex: Number + // Z-index of the first popup. (If first popup opens other + // popups they get a higher z-index.) + _beginZIndex: 1000, + + _idGen: 1, + + _createWrapper: function(/*Widget*/ widget){ + // summary: + // Initialization for widgets that will be used as popups. + // Puts widget inside a wrapper DIV (if not already in one), + // and returns pointer to that wrapper DIV. + + var wrapper = widget._popupWrapper, + node = widget.domNode; + + if(!wrapper){ + // Create wrapper <div> for when this widget [in the future] will be used as a popup. + // This is done early because of IE bugs where creating/moving DOM nodes causes focus + // to go wonky, see tests/robot/Toolbar.html to reproduce + wrapper = domConstruct.create("div",{ + "class":"dijitPopup", + style:{ display: "none"}, + role: "presentation" + }, win.body()); + wrapper.appendChild(node); + + var s = node.style; + s.display = ""; + s.visibility = ""; + s.position = ""; + s.top = "0px"; + + widget._popupWrapper = wrapper; + aspect.after(widget, "destroy", function(){ + domConstruct.destroy(wrapper); + delete widget._popupWrapper; + }); + } + + return wrapper; + }, + + moveOffScreen: function(/*Widget*/ widget){ + // summary: + // Moves the popup widget off-screen. + // Do not use this method to hide popups when not in use, because + // that will create an accessibility issue: the offscreen popup is + // still in the tabbing order. + + // Create wrapper if not already there + var wrapper = this._createWrapper(widget); + + domStyle.set(wrapper, { + visibility: "hidden", + top: "-9999px", // prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111) + display: "" + }); + }, + + hide: function(/*Widget*/ widget){ + // summary: + // Hide this popup widget (until it is ready to be shown). + // Initialization for widgets that will be used as popups + // + // Also puts widget inside a wrapper DIV (if not already in one) + // + // If popup widget needs to layout it should + // do so when it is made visible, and popup._onShow() is called. + + // Create wrapper if not already there + var wrapper = this._createWrapper(widget); + + domStyle.set(wrapper, "display", "none"); + }, + + getTopPopup: function(){ + // summary: + // Compute the closest ancestor popup that's *not* a child of another popup. + // Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button. + var stack = this._stack; + for(var pi=stack.length-1; pi > 0 && stack[pi].parent === stack[pi-1].widget; pi--){ + /* do nothing, just trying to get right value for pi */ + } + return stack[pi]; + }, + + open: function(/*dijit.popup.__OpenArgs*/ args){ + // summary: + // Popup the widget at the specified position + // + // example: + // opening at the mouse position + // | popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY}); + // + // example: + // opening the widget as a dropdown + // | popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}}); + // + // Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback + // (fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed. + + var stack = this._stack, + widget = args.popup, + orient = args.orient || ["below", "below-alt", "above", "above-alt"], + ltr = args.parent ? args.parent.isLeftToRight() : domGeometry.isBodyLtr(), + around = args.around, + id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++); + + // If we are opening a new popup that isn't a child of a currently opened popup, then + // close currently opened popup(s). This should happen automatically when the old popups + // gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198]. + while(stack.length && (!args.parent || !dom.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){ + this.close(stack[stack.length-1].widget); + } + + // Get pointer to popup wrapper, and create wrapper if it doesn't exist + var wrapper = this._createWrapper(widget); + + + domAttr.set(wrapper, { + id: id, + style: { + zIndex: this._beginZIndex + stack.length + }, + "class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup", + dijitPopupParent: args.parent ? args.parent.id : "" + }); + + if(has("ie") || has("mozilla")){ + if(!widget.bgIframe){ + // setting widget.bgIframe triggers cleanup in _Widget.destroy() + widget.bgIframe = new BackgroundIframe(wrapper); + } + } + + // position the wrapper node and make it visible + var best = around ? + place.around(wrapper, around, orient, ltr, widget.orient ? lang.hitch(widget, "orient") : null) : + place.at(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding); + + wrapper.style.display = ""; + wrapper.style.visibility = "visible"; + widget.domNode.style.visibility = "visible"; // counteract effects from _HasDropDown + + var handlers = []; + + // provide default escape and tab key handling + // (this will work for any widget, not just menu) + handlers.push(on(wrapper, connect._keypress, lang.hitch(this, function(evt){ + if(evt.charOrCode == keys.ESCAPE && args.onCancel){ + event.stop(evt); + args.onCancel(); + }else if(evt.charOrCode === keys.TAB){ + event.stop(evt); + var topPopup = this.getTopPopup(); + if(topPopup && topPopup.onCancel){ + topPopup.onCancel(); + } + } + }))); + + // watch for cancel/execute events on the popup and notify the caller + // (for a menu, "execute" means clicking an item) + if(widget.onCancel && args.onCancel){ + handlers.push(widget.on("cancel", args.onCancel)); + } + + handlers.push(widget.on(widget.onExecute ? "execute" : "change", lang.hitch(this, function(){ + var topPopup = this.getTopPopup(); + if(topPopup && topPopup.onExecute){ + topPopup.onExecute(); + } + }))); + + stack.push({ + widget: widget, + parent: args.parent, + onExecute: args.onExecute, + onCancel: args.onCancel, + onClose: args.onClose, + handlers: handlers + }); + + if(widget.onOpen){ + // TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here) + widget.onOpen(best); + } + + return best; + }, + + close: function(/*Widget?*/ popup){ + // summary: + // Close specified popup and any popups that it parented. + // If no popup is specified, closes all popups. + + var stack = this._stack; + + // Basically work backwards from the top of the stack closing popups + // until we hit the specified popup, but IIRC there was some issue where closing + // a popup would cause others to close too. Thus if we are trying to close B in [A,B,C] + // closing C might close B indirectly and then the while() condition will run where stack==[A]... + // so the while condition is constructed defensively. + while((popup && array.some(stack, function(elem){return elem.widget == popup;})) || + (!popup && stack.length)){ + var top = stack.pop(), + widget = top.widget, + onClose = top.onClose; + + if(widget.onClose){ + // TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here) + widget.onClose(); + } + + var h; + while(h = top.handlers.pop()){ h.remove(); } + + // Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc. + if(widget && widget.domNode){ + this.hide(widget); + } + + if(onClose){ + onClose(); + } + } + } + }); + + return (dijit.popup = new PopupManager()); +}); + +}, +'dijit/_base/manager':function(){ +define("dijit/_base/manager", [ + "dojo/_base/array", + "dojo/_base/config", // defaultDuration + "../registry", + ".." // for setting exports to dijit namespace +], function(array, config, registry, dijit){ + + // module: + // dijit/_base/manager + // summary: + // Shim to methods on registry, plus a few other declarations. + // New code should access dijit/registry directly when possible. + + /*===== + dijit.byId = function(id){ + // summary: + // Returns a widget by it's id, or if passed a widget, no-op (like dom.byId()) + // id: String|dijit._Widget + return registry.byId(id); // dijit._Widget + }; + + dijit.getUniqueId = function(widgetType){ + // summary: + // Generates a unique id for a given widgetType + // widgetType: String + return registry.getUniqueId(widgetType); // String + }; + + dijit.findWidgets = function(root){ + // summary: + // Search subtree under root returning widgets found. + // Doesn't search for nested widgets (ie, widgets inside other widgets). + // root: DOMNode + return registry.findWidgets(root); + }; + + dijit._destroyAll = function(){ + // summary: + // Code to destroy all widgets and do other cleanup on page unload + + return registry._destroyAll(); + }; + + dijit.byNode = function(node){ + // summary: + // Returns the widget corresponding to the given DOMNode + // node: DOMNode + return registry.byNode(node); // dijit._Widget + }; + + dijit.getEnclosingWidget = function(node){ + // summary: + // Returns the widget whose DOM tree contains the specified DOMNode, or null if + // the node is not contained within the DOM tree of any widget + // node: DOMNode + return registry.getEnclosingWidget(node); + }; + =====*/ + array.forEach(["byId", "getUniqueId", "findWidgets", "_destroyAll", "byNode", "getEnclosingWidget"], function(name){ + dijit[name] = registry[name]; + }); + + /*===== + dojo.mixin(dijit, { + // defaultDuration: Integer + // The default fx.animation speed (in ms) to use for all Dijit + // transitional fx.animations, unless otherwise specified + // on a per-instance basis. Defaults to 200, overrided by + // `djConfig.defaultDuration` + defaultDuration: 200 + }); + =====*/ + dijit.defaultDuration = config["defaultDuration"] || 200; + + return dijit; +}); + +}, +'dijit/layout/StackController':function(){ +define("dijit/layout/StackController", [ + "dojo/_base/array", // array.forEach array.indexOf array.map + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/keys", // keys + "dojo/_base/lang", // lang.getObject + "dojo/_base/sniff", // has("ie") + "../focus", // focus.focus() + "../registry", // registry.byId + "../_Widget", + "../_TemplatedMixin", + "../_Container", + "../form/ToggleButton", + "dojo/i18n!../nls/common" +], function(array, declare, event, keys, lang, has, + focus, registry, _Widget, _TemplatedMixin, _Container, ToggleButton){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _Container = dijit._Container; + var ToggleButton = dijit.form.ToggleButton; +=====*/ + + // module: + // dijit/layout/StackController + // summary: + // Set of buttons to select a page in a `dijit.layout.StackContainer` + + var StackButton = declare("dijit.layout._StackButton", ToggleButton, { + // summary: + // Internal widget used by StackContainer. + // description: + // The button-like or tab-like object you click to select or delete a page + // tags: + // private + + // Override _FormWidget.tabIndex. + // StackContainer buttons are not in the tab order by default. + // Probably we should be calling this.startupKeyNavChildren() instead. + tabIndex: "-1", + + // closeButton: Boolean + // When true, display close button for this tab + closeButton: false, + + _setCheckedAttr: function(/*Boolean*/ value, /*Boolean?*/ priorityChange){ + this.inherited(arguments); + this.focusNode.removeAttribute("aria-pressed"); + }, + + buildRendering: function(/*Event*/ evt){ + this.inherited(arguments); + (this.focusNode || this.domNode).setAttribute("role", "tab"); + }, + + onClick: function(/*Event*/ /*===== evt =====*/){ + // summary: + // This is for TabContainer where the tabs are <span> rather than button, + // so need to set focus explicitly (on some browsers) + // Note that you shouldn't override this method, but you can connect to it. + focus.focus(this.focusNode); + + // ... now let StackController catch the event and tell me what to do + }, + + onClickCloseButton: function(/*Event*/ evt){ + // summary: + // StackContainer connects to this function; if your widget contains a close button + // then clicking it should call this function. + // Note that you shouldn't override this method, but you can connect to it. + evt.stopPropagation(); + } + }); + + + var StackController = declare("dijit.layout.StackController", [_Widget, _TemplatedMixin, _Container], { + // summary: + // Set of buttons to select a page in a `dijit.layout.StackContainer` + // description: + // Monitors the specified StackContainer, and whenever a page is + // added, deleted, or selected, updates itself accordingly. + + baseClass: "dijitStackController", + + templateString: "<span role='tablist' data-dojo-attach-event='onkeypress'></span>", + + // containerId: [const] String + // The id of the page container that I point to + containerId: "", + + // buttonWidget: [const] Constructor + // The button widget to create to correspond to each page + buttonWidget: StackButton, + + constructor: function(){ + this.pane2button = {}; // mapping from pane id to buttons + this.pane2connects = {}; // mapping from pane id to this.connect() handles + this.pane2watches = {}; // mapping from pane id to watch() handles + }, + + postCreate: function(){ + this.inherited(arguments); + + // Listen to notifications from StackContainer + this.subscribe(this.containerId+"-startup", "onStartup"); + this.subscribe(this.containerId+"-addChild", "onAddChild"); + this.subscribe(this.containerId+"-removeChild", "onRemoveChild"); + this.subscribe(this.containerId+"-selectChild", "onSelectChild"); + this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress"); + }, + + onStartup: function(/*Object*/ info){ + // summary: + // Called after StackContainer has finished initializing + // tags: + // private + array.forEach(info.children, this.onAddChild, this); + if(info.selected){ + // Show button corresponding to selected pane (unless selected + // is null because there are no panes) + this.onSelectChild(info.selected); + } + }, + + destroy: function(){ + for(var pane in this.pane2button){ + this.onRemoveChild(registry.byId(pane)); + } + this.inherited(arguments); + }, + + onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){ + // summary: + // Called whenever a page is added to the container. + // Create button corresponding to the page. + // tags: + // private + + // create an instance of the button widget + // (remove typeof buttonWidget == string support in 2.0) + var cls = lang.isString(this.buttonWidget) ? lang.getObject(this.buttonWidget) : this.buttonWidget; + var button = new cls({ + id: this.id + "_" + page.id, + label: page.title, + dir: page.dir, + lang: page.lang, + textDir: page.textDir, + showLabel: page.showTitle, + iconClass: page.iconClass, + closeButton: page.closable, + title: page.tooltip + }); + button.focusNode.setAttribute("aria-selected", "false"); + + + // map from page attribute to corresponding tab button attribute + var pageAttrList = ["title", "showTitle", "iconClass", "closable", "tooltip"], + buttonAttrList = ["label", "showLabel", "iconClass", "closeButton", "title"]; + + // watch() so events like page title changes are reflected in tab button + this.pane2watches[page.id] = array.map(pageAttrList, function(pageAttr, idx){ + return page.watch(pageAttr, function(name, oldVal, newVal){ + button.set(buttonAttrList[idx], newVal); + }); + }); + + // connections so that clicking a tab button selects the corresponding page + this.pane2connects[page.id] = [ + this.connect(button, 'onClick', lang.hitch(this,"onButtonClick", page)), + this.connect(button, 'onClickCloseButton', lang.hitch(this,"onCloseButtonClick", page)) + ]; + + this.addChild(button, insertIndex); + this.pane2button[page.id] = button; + page.controlButton = button; // this value might be overwritten if two tabs point to same container + if(!this._currentChild){ // put the first child into the tab order + button.focusNode.setAttribute("tabIndex", "0"); + button.focusNode.setAttribute("aria-selected", "true"); + this._currentChild = page; + } + // make sure all tabs have the same length + if(!this.isLeftToRight() && has("ie") && this._rectifyRtlTabList){ + this._rectifyRtlTabList(); + } + }, + + onRemoveChild: function(/*dijit._Widget*/ page){ + // summary: + // Called whenever a page is removed from the container. + // Remove the button corresponding to the page. + // tags: + // private + + if(this._currentChild === page){ this._currentChild = null; } + + // disconnect/unwatch connections/watches related to page being removed + array.forEach(this.pane2connects[page.id], lang.hitch(this, "disconnect")); + delete this.pane2connects[page.id]; + array.forEach(this.pane2watches[page.id], function(w){ w.unwatch(); }); + delete this.pane2watches[page.id]; + + var button = this.pane2button[page.id]; + if(button){ + this.removeChild(button); + delete this.pane2button[page.id]; + button.destroy(); + } + delete page.controlButton; + }, + + onSelectChild: function(/*dijit._Widget*/ page){ + // summary: + // Called when a page has been selected in the StackContainer, either by me or by another StackController + // tags: + // private + + if(!page){ return; } + + if(this._currentChild){ + var oldButton=this.pane2button[this._currentChild.id]; + oldButton.set('checked', false); + oldButton.focusNode.setAttribute("aria-selected", "false"); + oldButton.focusNode.setAttribute("tabIndex", "-1"); + } + + var newButton=this.pane2button[page.id]; + newButton.set('checked', true); + newButton.focusNode.setAttribute("aria-selected", "true"); + this._currentChild = page; + newButton.focusNode.setAttribute("tabIndex", "0"); + var container = registry.byId(this.containerId); + container.containerNode.setAttribute("aria-labelledby", newButton.id); + }, + + onButtonClick: function(/*dijit._Widget*/ page){ + // summary: + // Called whenever one of my child buttons is pressed in an attempt to select a page + // tags: + // private + + if(this._currentChild.id === page.id) { + //In case the user clicked the checked button, keep it in the checked state because it remains to be the selected stack page. + var button=this.pane2button[page.id]; + button.set('checked', true); + } + var container = registry.byId(this.containerId); + container.selectChild(page); + }, + + onCloseButtonClick: function(/*dijit._Widget*/ page){ + // summary: + // Called whenever one of my child buttons [X] is pressed in an attempt to close a page + // tags: + // private + + var container = registry.byId(this.containerId); + container.closeChild(page); + if(this._currentChild){ + var b = this.pane2button[this._currentChild.id]; + if(b){ + focus.focus(b.focusNode || b.domNode); + } + } + }, + + // TODO: this is a bit redundant with forward, back api in StackContainer + adjacent: function(/*Boolean*/ forward){ + // summary: + // Helper for onkeypress to find next/previous button + // tags: + // private + + if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; } + // find currently focused button in children array + var children = this.getChildren(); + var current = array.indexOf(children, this.pane2button[this._currentChild.id]); + // pick next button to focus on + var offset = forward ? 1 : children.length - 1; + return children[ (current + offset) % children.length ]; // dijit._Widget + }, + + onkeypress: function(/*Event*/ e){ + // summary: + // Handle keystrokes on the page list, for advancing to next/previous button + // and closing the current page if the page is closable. + // tags: + // private + + if(this.disabled || e.altKey ){ return; } + var forward = null; + if(e.ctrlKey || !e._djpage){ + switch(e.charOrCode){ + case keys.LEFT_ARROW: + case keys.UP_ARROW: + if(!e._djpage){ forward = false; } + break; + case keys.PAGE_UP: + if(e.ctrlKey){ forward = false; } + break; + case keys.RIGHT_ARROW: + case keys.DOWN_ARROW: + if(!e._djpage){ forward = true; } + break; + case keys.PAGE_DOWN: + if(e.ctrlKey){ forward = true; } + break; + case keys.HOME: + case keys.END: + var children = this.getChildren(); + if(children && children.length){ + children[e.charOrCode == keys.HOME ? 0 : children.length-1].onClick(); + } + event.stop(e); + break; + case keys.DELETE: + if(this._currentChild.closable){ + this.onCloseButtonClick(this._currentChild); + } + event.stop(e); + break; + default: + if(e.ctrlKey){ + if(e.charOrCode === keys.TAB){ + this.adjacent(!e.shiftKey).onClick(); + event.stop(e); + }else if(e.charOrCode == "w"){ + if(this._currentChild.closable){ + this.onCloseButtonClick(this._currentChild); + } + event.stop(e); // avoid browser tab closing. + } + } + } + // handle next/previous page navigation (left/right arrow, etc.) + if(forward !== null){ + this.adjacent(forward).onClick(); + event.stop(e); + } + } + }, + + onContainerKeyPress: function(/*Object*/ info){ + // summary: + // Called when there was a keypress on the container + // tags: + // private + info.e._djpage = info.page; + this.onkeypress(info.e); + } + }); + + StackController.StackButton = StackButton; // for monkey patching + + return StackController; +}); + +}, +'dojo/dnd/Mover':function(){ +define("dojo/dnd/Mover", ["../main", "../Evented", "../touch", "./common", "./autoscroll"], function(dojo, Evented, touch) { + // module: + // dojo/dnd/Mover + // summary: + // TODOC + + +dojo.declare("dojo.dnd.Mover", [Evented], { + constructor: function(node, e, host){ + // summary: + // an object which makes a node follow the mouse, or touch-drag on touch devices. + // Used as a default mover, and as a base class for custom movers. + // node: Node + // a node (or node's id) to be moved + // e: Event + // a mouse event, which started the move; + // only pageX and pageY properties are used + // host: Object? + // object which implements the functionality of the move, + // and defines proper events (onMoveStart and onMoveStop) + this.node = dojo.byId(node); + this.marginBox = {l: e.pageX, t: e.pageY}; + this.mouseButton = e.button; + var h = (this.host = host), d = node.ownerDocument; + this.events = [ + // At the start of a drag, onFirstMove is called, and then the following two + // connects are disconnected + dojo.connect(d, touch.move, this, "onFirstMove"), + + // These are called continually during the drag + dojo.connect(d, touch.move, this, "onMouseMove"), + + // And these are called at the end of the drag + dojo.connect(d, touch.release, this, "onMouseUp"), + + // cancel text selection and text dragging + dojo.connect(d, "ondragstart", dojo.stopEvent), + dojo.connect(d.body, "onselectstart", dojo.stopEvent) + ]; + // notify that the move has started + if(h && h.onMoveStart){ + h.onMoveStart(this); + } + }, + // mouse event processors + onMouseMove: function(e){ + // summary: + // event processor for onmousemove/ontouchmove + // e: Event + // mouse/touch event + dojo.dnd.autoScroll(e); + var m = this.marginBox; + this.host.onMove(this, {l: m.l + e.pageX, t: m.t + e.pageY}, e); + dojo.stopEvent(e); + }, + onMouseUp: function(e){ + if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ? + e.button == 0 : this.mouseButton == e.button){ // TODO Should condition be met for touch devices, too? + this.destroy(); + } + dojo.stopEvent(e); + }, + // utilities + onFirstMove: function(e){ + // summary: + // makes the node absolute; it is meant to be called only once. + // relative and absolutely positioned nodes are assumed to use pixel units + var s = this.node.style, l, t, h = this.host; + switch(s.position){ + case "relative": + case "absolute": + // assume that left and top values are in pixels already + l = Math.round(parseFloat(s.left)) || 0; + t = Math.round(parseFloat(s.top)) || 0; + break; + default: + s.position = "absolute"; // enforcing the absolute mode + var m = dojo.marginBox(this.node); + // event.pageX/pageY (which we used to generate the initial + // margin box) includes padding and margin set on the body. + // However, setting the node's position to absolute and then + // doing dojo.marginBox on it *doesn't* take that additional + // space into account - so we need to subtract the combined + // padding and margin. We use getComputedStyle and + // _getMarginBox/_getContentBox to avoid the extra lookup of + // the computed style. + var b = dojo.doc.body; + var bs = dojo.getComputedStyle(b); + var bm = dojo._getMarginBox(b, bs); + var bc = dojo._getContentBox(b, bs); + l = m.l - (bc.l - bm.l); + t = m.t - (bc.t - bm.t); + break; + } + this.marginBox.l = l - this.marginBox.l; + this.marginBox.t = t - this.marginBox.t; + if(h && h.onFirstMove){ + h.onFirstMove(this, e); + } + + // Disconnect onmousemove and ontouchmove events that call this function + dojo.disconnect(this.events.shift()); + }, + destroy: function(){ + // summary: + // stops the move, deletes all references, so the object can be garbage-collected + dojo.forEach(this.events, dojo.disconnect); + // undo global settings + var h = this.host; + if(h && h.onMoveStop){ + h.onMoveStop(this); + } + // destroy objects + this.events = this.node = this.host = null; + } +}); + +return dojo.dnd.Mover; +}); + +}, +'dijit/layout/TabContainer':function(){ +define("dijit/layout/TabContainer", [ + "dojo/_base/lang", // lang.getObject + "dojo/_base/declare", // declare + "./_TabContainerBase", + "./TabController", + "./ScrollingTabController" +], function(lang, declare, _TabContainerBase, TabController, ScrollingTabController){ + +/*===== + var _TabContainerBase = dijit.layout._TabContainerBase; + var TabController = dijit.layout.TabController; + var ScrollingTabController = dijit.layout.ScrollingTabController; +=====*/ + + // module: + // dijit/layout/TabContainer + // summary: + // A Container with tabs to select each child (only one of which is displayed at a time). + + + return declare("dijit.layout.TabContainer", _TabContainerBase, { + // summary: + // A Container with tabs to select each child (only one of which is displayed at a time). + // description: + // A TabContainer is a container that has multiple panes, but shows only + // one pane at a time. There are a set of tabs corresponding to each pane, + // where each tab has the name (aka title) of the pane, and optionally a close button. + + // useMenu: [const] Boolean + // True if a menu should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useMenu: true, + + // useSlider: [const] Boolean + // True if a slider should be used to select tabs when they are too + // wide to fit the TabContainer, false otherwise. + useSlider: true, + + // controllerWidget: String + // An optional parameter to override the widget used to display the tab labels + controllerWidget: "", + + _makeController: function(/*DomNode*/ srcNode){ + // summary: + // Instantiate tablist controller widget and return reference to it. + // Callback from _TabContainerBase.postCreate(). + // tags: + // protected extension + + var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"), + TabController = lang.getObject(this.controllerWidget); + + return new TabController({ + id: this.id + "_tablist", + dir: this.dir, + lang: this.lang, + textDir: this.textDir, + tabPosition: this.tabPosition, + doLayout: this.doLayout, + containerId: this.id, + "class": cls, + nested: this.nested, + useMenu: this.useMenu, + useSlider: this.useSlider, + tabStripClass: this.tabStrip ? this.baseClass + (this.tabStrip ? "":"No") + "Strip": null + }, srcNode); + }, + + postMixInProperties: function(){ + this.inherited(arguments); + + // Scrolling controller only works for horizontal non-nested tabs + if(!this.controllerWidget){ + this.controllerWidget = (this.tabPosition == "top" || this.tabPosition == "bottom") && !this.nested ? + "dijit.layout.ScrollingTabController" : "dijit.layout.TabController"; + } + } + }); +}); + +}, +'dijit/BackgroundIframe':function(){ +define("dijit/BackgroundIframe", [ + "require", // require.toUrl + ".", // to export dijit.BackgroundIframe + "dojo/_base/config", + "dojo/dom-construct", // domConstruct.create + "dojo/dom-style", // domStyle.set + "dojo/_base/lang", // lang.extend lang.hitch + "dojo/on", + "dojo/_base/sniff", // has("ie"), has("mozilla"), has("quirks") + "dojo/_base/window" // win.doc.createElement +], function(require, dijit, config, domConstruct, domStyle, lang, on, has, win){ + + // module: + // dijit/BackgroundIFrame + // summary: + // new dijit.BackgroundIframe(node) + // Makes a background iframe as a child of node, that fills + // area (and position) of node + + // TODO: remove _frames, it isn't being used much, since popups never release their + // iframes (see [22236]) + var _frames = new function(){ + // summary: + // cache of iframes + + var queue = []; + + this.pop = function(){ + var iframe; + if(queue.length){ + iframe = queue.pop(); + iframe.style.display=""; + }else{ + if(has("ie") < 9){ + var burl = config["dojoBlankHtmlUrl"] || require.toUrl("dojo/resources/blank.html") || "javascript:\"\""; + var html="<iframe src='" + burl + "' role='presentation'" + + " style='position: absolute; left: 0px; top: 0px;" + + "z-index: -1; filter:Alpha(Opacity=\"0\");'>"; + iframe = win.doc.createElement(html); + }else{ + iframe = domConstruct.create("iframe"); + iframe.src = 'javascript:""'; + iframe.className = "dijitBackgroundIframe"; + iframe.setAttribute("role", "presentation"); + domStyle.set(iframe, "opacity", 0.1); + } + iframe.tabIndex = -1; // Magic to prevent iframe from getting focus on tab keypress - as style didn't work. + } + return iframe; + }; + + this.push = function(iframe){ + iframe.style.display="none"; + queue.push(iframe); + } + }(); + + + dijit.BackgroundIframe = function(/*DomNode*/ node){ + // summary: + // For IE/FF z-index schenanigans. id attribute is required. + // + // description: + // new dijit.BackgroundIframe(node) + // Makes a background iframe as a child of node, that fills + // area (and position) of node + + if(!node.id){ throw new Error("no id"); } + if(has("ie") || has("mozilla")){ + var iframe = (this.iframe = _frames.pop()); + node.appendChild(iframe); + if(has("ie")<7 || has("quirks")){ + this.resize(node); + this._conn = on(node, 'resize', lang.hitch(this, function(){ + this.resize(node); + })); + }else{ + domStyle.set(iframe, { + width: '100%', + height: '100%' + }); + } + } + }; + + lang.extend(dijit.BackgroundIframe, { + resize: function(node){ + // summary: + // Resize the iframe so it's the same size as node. + // Needed on IE6 and IE/quirks because height:100% doesn't work right. + if(this.iframe){ + domStyle.set(this.iframe, { + width: node.offsetWidth + 'px', + height: node.offsetHeight + 'px' + }); + } + }, + destroy: function(){ + // summary: + // destroy the iframe + if(this._conn){ + this._conn.remove(); + this._conn = null; + } + if(this.iframe){ + _frames.push(this.iframe); + delete this.iframe; + } + } + }); + + return dijit.BackgroundIframe; +}); + +}, +'url:dijit/templates/Menu.html':"<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" role=\"menu\" tabIndex=\"${tabIndex}\" data-dojo-attach-event=\"onkeypress:_onKeyPress\" cellspacing=\"0\">\n\t<tbody class=\"dijitReset\" data-dojo-attach-point=\"containerNode\"></tbody>\n</table>\n", +'dojo/dnd/Avatar':function(){ +define("dojo/dnd/Avatar", ["../main", "./common"], function(dojo) { + // module: + // dojo/dnd/Avatar + // summary: + // TODOC + + +dojo.declare("dojo.dnd.Avatar", null, { + // summary: + // Object that represents transferred DnD items visually + // manager: Object + // a DnD manager object + + constructor: function(manager){ + this.manager = manager; + this.construct(); + }, + + // methods + construct: function(){ + // summary: + // constructor function; + // it is separate so it can be (dynamically) overwritten in case of need + this.isA11y = dojo.hasClass(dojo.body(),"dijit_a11y"); + var a = dojo.create("table", { + "class": "dojoDndAvatar", + style: { + position: "absolute", + zIndex: "1999", + margin: "0px" + } + }), + source = this.manager.source, node, + b = dojo.create("tbody", null, a), + tr = dojo.create("tr", null, b), + td = dojo.create("td", null, tr), + icon = this.isA11y ? dojo.create("span", { + id : "a11yIcon", + innerHTML : this.manager.copy ? '+' : "<" + }, td) : null, + span = dojo.create("span", { + innerHTML: source.generateText ? this._generateText() : "" + }, td), + k = Math.min(5, this.manager.nodes.length), i = 0; + // we have to set the opacity on IE only after the node is live + dojo.attr(tr, { + "class": "dojoDndAvatarHeader", + style: {opacity: 0.9} + }); + for(; i < k; ++i){ + if(source.creator){ + // create an avatar representation of the node + node = source._normalizedCreator(source.getItem(this.manager.nodes[i].id).data, "avatar").node; + }else{ + // or just clone the node and hope it works + node = this.manager.nodes[i].cloneNode(true); + if(node.tagName.toLowerCase() == "tr"){ + // insert extra table nodes + var table = dojo.create("table"), + tbody = dojo.create("tbody", null, table); + tbody.appendChild(node); + node = table; + } + } + node.id = ""; + tr = dojo.create("tr", null, b); + td = dojo.create("td", null, tr); + td.appendChild(node); + dojo.attr(tr, { + "class": "dojoDndAvatarItem", + style: {opacity: (9 - i) / 10} + }); + } + this.node = a; + }, + destroy: function(){ + // summary: + // destructor for the avatar; called to remove all references so it can be garbage-collected + dojo.destroy(this.node); + this.node = false; + }, + update: function(){ + // summary: + // updates the avatar to reflect the current DnD state + dojo[(this.manager.canDropFlag ? "add" : "remove") + "Class"](this.node, "dojoDndAvatarCanDrop"); + if (this.isA11y){ + var icon = dojo.byId("a11yIcon"); + var text = '+'; // assume canDrop && copy + if (this.manager.canDropFlag && !this.manager.copy) { + text = '< '; // canDrop && move + }else if (!this.manager.canDropFlag && !this.manager.copy) { + text = "o"; //!canDrop && move + }else if(!this.manager.canDropFlag){ + text = 'x'; // !canDrop && copy + } + icon.innerHTML=text; + } + // replace text + dojo.query(("tr.dojoDndAvatarHeader td span" +(this.isA11y ? " span" : "")), this.node).forEach( + function(node){ + node.innerHTML = this._generateText(); + }, this); + }, + _generateText: function(){ + // summary: generates a proper text to reflect copying or moving of items + return this.manager.nodes.length.toString(); + } +}); + +return dojo.dnd.Avatar; +}); + +}, +'dijit/form/Button':function(){ +require({cache:{ +'url:dijit/form/templates/Button.html':"<span class=\"dijit dijitReset dijitInline\" role=\"presentation\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdata-dojo-attach-event=\"ondijitclick:_onClick\" role=\"presentation\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdata-dojo-attach-point=\"titleNode,focusNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" data-dojo-attach-point=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">●</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdata-dojo-attach-point=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\ttabIndex=\"-1\" role=\"presentation\" data-dojo-attach-point=\"valueNode\"\n/></span>\n"}}); +define("dijit/form/Button", [ + "require", + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.toggle + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/lang", // lang.trim + "dojo/ready", + "./_FormWidget", + "./_ButtonMixin", + "dojo/text!./templates/Button.html" +], function(require, declare, domClass, kernel, lang, ready, _FormWidget, _ButtonMixin, template){ + +/*===== + var _FormWidget = dijit.form._FormWidget; + var _ButtonMixin = dijit.form._ButtonMixin; +=====*/ + +// module: +// dijit/form/Button +// summary: +// Button widget + +// Back compat w/1.6, remove for 2.0 +if(!kernel.isAsync){ + ready(0, function(){ + var requires = ["dijit/form/DropDownButton", "dijit/form/ComboButton", "dijit/form/ToggleButton"]; + require(requires); // use indirection so modules not rolled into a build + }); } -if(!dojo._hasResource["dojo.dnd.move"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.dnd.move"] = true; -dojo.provide("dojo.dnd.move"); +return declare("dijit.form.Button", [_FormWidget, _ButtonMixin], { + // summary: + // Basically the same thing as a normal HTML button, but with special styling. + // description: + // Buttons can display a label, an icon, or both. + // A label should always be specified (through innerHTML) or the label + // attribute. It can be hidden via showLabel=false. + // example: + // | <button data-dojo-type="dijit.form.Button" onClick="...">Hello world</button> + // + // example: + // | var button1 = new dijit.form.Button({label: "hello world", onClick: foo}); + // | dojo.body().appendChild(button1.domNode); + + // showLabel: Boolean + // Set this to true to hide the label text and display only the icon. + // (If showLabel=false then iconClass must be specified.) + // Especially useful for toolbars. + // If showLabel=true, the label will become the title (a.k.a. tooltip/hint) of the icon. + // + // The exception case is for computers in high-contrast mode, where the label + // will still be displayed, since the icon doesn't appear. + showLabel: true, + + // iconClass: String + // Class to apply to DOMNode in button to make it display an icon + iconClass: "dijitNoIcon", + _setIconClassAttr: { node: "iconNode", type: "class" }, + + baseClass: "dijitButton", + + templateString: template, + + // Map widget attributes to DOMNode attributes. + _setValueAttr: "valueNode", + _onClick: function(/*Event*/ e){ + // summary: + // Internal function to handle click actions + var ok = this.inherited(arguments); + if(ok){ + if(this.valueNode){ + this.valueNode.click(); + e.preventDefault(); // cancel BUTTON click and continue with hidden INPUT click + // leave ok = true so that subclasses can do what they need to do + } + } + return ok; + }, + + _fillContent: function(/*DomNode*/ source){ + // Overrides _Templated._fillContent(). + // If button label is specified as srcNodeRef.innerHTML rather than + // this.params.label, handle it here. + // TODO: remove the method in 2.0, parser will do it all for me + if(source && (!this.params || !("label" in this.params))){ + var sourceLabel = lang.trim(source.innerHTML); + if(sourceLabel){ + this.label = sourceLabel; // _applyAttributes will be called after buildRendering completes to update the DOM + } + } + }, + _setShowLabelAttr: function(val){ + if(this.containerNode){ + domClass.toggle(this.containerNode, "dijitDisplayNone", !val); + } + this._set("showLabel", val); + }, + + setLabel: function(/*String*/ content){ + // summary: + // Deprecated. Use set('label', ...) instead. + kernel.deprecated("dijit.form.Button.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0"); + this.set("label", content); + }, + + _setLabelAttr: function(/*String*/ content){ + // summary: + // Hook for set('label', ...) to work. + // description: + // Set the label (text) of the button; takes an HTML string. + // If the label is hidden (showLabel=false) then and no title has + // been specified, then label is also set as title attribute of icon. + this.inherited(arguments); + if(!this.showLabel && !("title" in this.params)){ + this.titleNode.title = lang.trim(this.containerNode.innerText || this.containerNode.textContent || ''); + } + } +}); + + +}); + + +}, +'url:dijit/layout/templates/TabContainer.html':"<div class=\"dijitTabContainer\">\n\t<div class=\"dijitTabListWrapper\" data-dojo-attach-point=\"tablistNode\"></div>\n\t<div data-dojo-attach-point=\"tablistSpacer\" class=\"dijitTabSpacer ${baseClass}-spacer\"></div>\n\t<div class=\"dijitTabPaneWrapper ${baseClass}-container\" data-dojo-attach-point=\"containerNode\"></div>\n</div>\n", +'dojo/dnd/move':function(){ +define("dojo/dnd/move", ["../main", "./Mover", "./Moveable"], function(dojo) { + // module: + // dojo/dnd/move + // summary: + // TODOC /*===== @@ -8542,11 +16458,6 @@ dojo.declare("dojo.dnd.move.constrainedMoveable", dojo.dnd.Moveable, { // object attributes (for markup) constraints: function(){}, within: false, - - // markup methods - markupFactory: function(params, node){ - return new dojo.dnd.move.constrainedMoveable(node, params); - }, constructor: function(node, params){ // summary: @@ -8599,11 +16510,6 @@ dojo.declare("dojo.dnd.move.boxConstrainedMoveable", dojo.dnd.move.constrainedMo // box: // object attributes (for markup) box: {}, - - // markup methods - markupFactory: function(params, node){ - return new dojo.dnd.move.boxConstrainedMoveable(node, params); - }, constructor: function(node, params){ // summary: @@ -8631,11 +16537,6 @@ dojo.declare("dojo.dnd.move.parentConstrainedMoveable", dojo.dnd.move.constraine // object attributes (for markup) area: "content", - // markup methods - markupFactory: function(params, node){ - return new dojo.dnd.move.parentConstrainedMoveable(node, params); - }, - constructor: function(node, params){ // summary: // an object, which makes a node moveable @@ -8674,129 +16575,2110 @@ dojo.dnd.constrainedMover = dojo.dnd.move.constrainedMover; dojo.dnd.boxConstrainedMover = dojo.dnd.move.boxConstrainedMover; dojo.dnd.parentConstrainedMover = dojo.dnd.move.parentConstrainedMover; +return dojo.dnd.move; +}); + +}, +'dijit/_WidgetBase':function(){ +define("dijit/_WidgetBase", [ + "require", // require.toUrl + "dojo/_base/array", // array.forEach array.map + "dojo/aspect", + "dojo/_base/config", // config.blankGif + "dojo/_base/connect", // connect.connect + "dojo/_base/declare", // declare + "dojo/dom", // dom.byId + "dojo/dom-attr", // domAttr.set domAttr.remove + "dojo/dom-class", // domClass.add domClass.replace + "dojo/dom-construct", // domConstruct.create domConstruct.destroy domConstruct.place + "dojo/dom-geometry", // isBodyLtr + "dojo/dom-style", // domStyle.set, domStyle.get + "dojo/_base/kernel", + "dojo/_base/lang", // mixin(), isArray(), etc. + "dojo/on", + "dojo/ready", + "dojo/Stateful", // Stateful + "dojo/topic", + "dojo/_base/window", // win.doc.createTextNode + "./registry" // registry.getUniqueId(), registry.findWidgets() +], function(require, array, aspect, config, connect, declare, + dom, domAttr, domClass, domConstruct, domGeometry, domStyle, kernel, + lang, on, ready, Stateful, topic, win, registry){ + +/*===== +var Stateful = dojo.Stateful; +=====*/ + +// module: +// dijit/_WidgetBase +// summary: +// Future base class for all Dijit widgets. + +// For back-compat, remove in 2.0. +if(!kernel.isAsync){ + ready(0, function(){ + var requires = ["dijit/_base/manager"]; + require(requires); // use indirection so modules not rolled into a build + }); +} + +// Nested hash listing attributes for each tag, all strings in lowercase. +// ex: {"div": {"style": true, "tabindex" true}, "form": { ... +var tagAttrs = {}; +function getAttrs(obj){ + var ret = {}; + for(var attr in obj){ + ret[attr.toLowerCase()] = true; + } + return ret; +} + +function nonEmptyAttrToDom(attr){ + // summary: + // Returns a setter function that copies the attribute to this.domNode, + // or removes the attribute from this.domNode, depending on whether the + // value is defined or not. + return function(val){ + domAttr[val ? "set" : "remove"](this.domNode, attr, val); + this._set(attr, val); + }; } -if(!dojo._hasResource["dojo.dnd.TimedMoveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.dnd.TimedMoveable"] = true; -dojo.provide("dojo.dnd.TimedMoveable"); +return declare("dijit._WidgetBase", Stateful, { + // summary: + // Future base class for all Dijit widgets. + // description: + // Future base class for all Dijit widgets. + // _Widget extends this class adding support for various features needed by desktop. + // + // Provides stubs for widget lifecycle methods for subclasses to extend, like postMixInProperties(), buildRendering(), + // postCreate(), startup(), and destroy(), and also public API methods like set(), get(), and watch(). + // + // Widgets can provide custom setters/getters for widget attributes, which are called automatically by set(name, value). + // For an attribute XXX, define methods _setXXXAttr() and/or _getXXXAttr(). + // + // _setXXXAttr can also be a string/hash/array mapping from a widget attribute XXX to the widget's DOMNodes: + // + // - DOM node attribute + // | _setFocusAttr: {node: "focusNode", type: "attribute"} + // | _setFocusAttr: "focusNode" (shorthand) + // | _setFocusAttr: "" (shorthand, maps to this.domNode) + // Maps this.focus to this.focusNode.focus, or (last example) this.domNode.focus + // + // - DOM node innerHTML + // | _setTitleAttr: { node: "titleNode", type: "innerHTML" } + // Maps this.title to this.titleNode.innerHTML + // + // - DOM node innerText + // | _setTitleAttr: { node: "titleNode", type: "innerText" } + // Maps this.title to this.titleNode.innerText + // + // - DOM node CSS class + // | _setMyClassAttr: { node: "domNode", type: "class" } + // Maps this.myClass to this.domNode.className + // + // If the value of _setXXXAttr is an array, then each element in the array matches one of the + // formats of the above list. + // + // If the custom setter is null, no action is performed other than saving the new value + // in the widget (in this). + // + // If no custom setter is defined for an attribute, then it will be copied + // to this.focusNode (if the widget defines a focusNode), or this.domNode otherwise. + // That's only done though for attributes that match DOMNode attributes (title, + // alt, aria-labelledby, etc.) + + // id: [const] String + // A unique, opaque ID string that can be assigned by users or by the + // system. If the developer passes an ID which is known not to be + // unique, the specified ID is ignored and the system-generated ID is + // used instead. + id: "", + _setIdAttr: "domNode", // to copy to this.domNode even for auto-generated id's + + // lang: [const] String + // Rarely used. Overrides the default Dojo locale used to render this widget, + // as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute. + // Value must be among the list of locales specified during by the Dojo bootstrap, + // formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us). + lang: "", + // set on domNode even when there's a focus node. but don't set lang="", since that's invalid. + _setLangAttr: nonEmptyAttrToDom("lang"), + + // dir: [const] String + // Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir) + // attribute. Either left-to-right "ltr" or right-to-left "rtl". If undefined, widgets renders in page's + // default direction. + dir: "", + // set on domNode even when there's a focus node. but don't set dir="", since that's invalid. + _setDirAttr: nonEmptyAttrToDom("dir"), // to set on domNode even when there's a focus node + + // textDir: String + // Bi-directional support, the main variable which is responsible for the direction of the text. + // The text direction can be different than the GUI direction by using this parameter in creation + // of a widget. + // Allowed values: + // 1. "ltr" + // 2. "rtl" + // 3. "auto" - contextual the direction of a text defined by first strong letter. + // By default is as the page direction. + textDir: "", + + // class: String + // HTML class attribute + "class": "", + _setClassAttr: { node: "domNode", type: "class" }, + + // style: String||Object + // HTML style attributes as cssText string or name/value hash + style: "", + + // title: String + // HTML title attribute. + // + // For form widgets this specifies a tooltip to display when hovering over + // the widget (just like the native HTML title attribute). + // + // For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer, + // etc., it's used to specify the tab label, accordion pane title, etc. + title: "", + // tooltip: String + // When this widget's title attribute is used to for a tab label, accordion pane title, etc., + // this specifies the tooltip to appear when the mouse is hovered over that text. + tooltip: "", + // baseClass: [protected] String + // Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate + // widget state. + baseClass: "", + + // srcNodeRef: [readonly] DomNode + // pointer to original DOM node + srcNodeRef: null, + + // domNode: [readonly] DomNode + // This is our visible representation of the widget! Other DOM + // Nodes may by assigned to other properties, usually through the + // template system's data-dojo-attach-point syntax, but the domNode + // property is the canonical "top level" node in widget UI. + domNode: null, + + // containerNode: [readonly] DomNode + // Designates where children of the source DOM node will be placed. + // "Children" in this case refers to both DOM nodes and widgets. + // For example, for myWidget: + // + // | <div data-dojo-type=myWidget> + // | <b> here's a plain DOM node + // | <span data-dojo-type=subWidget>and a widget</span> + // | <i> and another plain DOM node </i> + // | </div> + // + // containerNode would point to: + // + // | <b> here's a plain DOM node + // | <span data-dojo-type=subWidget>and a widget</span> + // | <i> and another plain DOM node </i> + // + // In templated widgets, "containerNode" is set via a + // data-dojo-attach-point assignment. + // + // containerNode must be defined for any widget that accepts innerHTML + // (like ContentPane or BorderContainer or even Button), and conversely + // is null for widgets that don't, like TextBox. + containerNode: null, /*===== -dojo.declare("dojo.dnd.__TimedMoveableArgs", [dojo.dnd.__MoveableArgs], { - // timeout: Number - // delay move by this number of ms, - // accumulating position changes during the timeout - timeout: 0 + // _started: Boolean + // startup() has completed. + _started: false, +=====*/ + + // attributeMap: [protected] Object + // Deprecated. Instead of attributeMap, widget should have a _setXXXAttr attribute + // for each XXX attribute to be mapped to the DOM. + // + // attributeMap sets up a "binding" between attributes (aka properties) + // of the widget and the widget's DOM. + // Changes to widget attributes listed in attributeMap will be + // reflected into the DOM. + // + // For example, calling set('title', 'hello') + // on a TitlePane will automatically cause the TitlePane's DOM to update + // with the new title. + // + // attributeMap is a hash where the key is an attribute of the widget, + // and the value reflects a binding to a: + // + // - DOM node attribute + // | focus: {node: "focusNode", type: "attribute"} + // Maps this.focus to this.focusNode.focus + // + // - DOM node innerHTML + // | title: { node: "titleNode", type: "innerHTML" } + // Maps this.title to this.titleNode.innerHTML + // + // - DOM node innerText + // | title: { node: "titleNode", type: "innerText" } + // Maps this.title to this.titleNode.innerText + // + // - DOM node CSS class + // | myClass: { node: "domNode", type: "class" } + // Maps this.myClass to this.domNode.className + // + // If the value is an array, then each element in the array matches one of the + // formats of the above list. + // + // There are also some shorthands for backwards compatibility: + // - string --> { node: string, type: "attribute" }, for example: + // | "focusNode" ---> { node: "focusNode", type: "attribute" } + // - "" --> { node: "domNode", type: "attribute" } + attributeMap: {}, + + // _blankGif: [protected] String + // Path to a blank 1x1 image. + // Used by <img> nodes in templates that really get their image via CSS background-image. + _blankGif: config.blankGif || require.toUrl("dojo/resources/blank.gif"), + + //////////// INITIALIZATION METHODS /////////////////////////////////////// + + postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){ + // summary: + // Kicks off widget instantiation. See create() for details. + // tags: + // private + this.create(params, srcNodeRef); + }, + + create: function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){ + // summary: + // Kick off the life-cycle of a widget + // params: + // Hash of initialization parameters for widget, including + // scalar values (like title, duration etc.) and functions, + // typically callbacks like onClick. + // srcNodeRef: + // If a srcNodeRef (DOM node) is specified: + // - use srcNodeRef.innerHTML as my contents + // - if this is a behavioral widget then apply behavior + // to that srcNodeRef + // - otherwise, replace srcNodeRef with my generated DOM + // tree + // description: + // Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate, + // etc.), some of which of you'll want to override. See http://dojotoolkit.org/reference-guide/dijit/_WidgetBase.html + // for a discussion of the widget creation lifecycle. + // + // Of course, adventurous developers could override create entirely, but this should + // only be done as a last resort. + // tags: + // private + + // store pointer to original DOM tree + this.srcNodeRef = dom.byId(srcNodeRef); + + // For garbage collection. An array of listener handles returned by this.connect() / this.subscribe() + this._connects = []; + + // For widgets internal to this widget, invisible to calling code + this._supportingWidgets = []; + + // this is here for back-compat, remove in 2.0 (but check NodeList-instantiate.html test) + if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; } + + // mix in our passed parameters + if(params){ + this.params = params; + lang.mixin(this, params); + } + this.postMixInProperties(); + + // generate an id for the widget if one wasn't specified + // (be sure to do this before buildRendering() because that function might + // expect the id to be there.) + if(!this.id){ + this.id = registry.getUniqueId(this.declaredClass.replace(/\./g,"_")); + } + registry.add(this); + + this.buildRendering(); + + if(this.domNode){ + // Copy attributes listed in attributeMap into the [newly created] DOM for the widget. + // Also calls custom setters for all attributes with custom setters. + this._applyAttributes(); + + // If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree. + // For 2.0, move this after postCreate(). postCreate() shouldn't depend on the + // widget being attached to the DOM since it isn't when a widget is created programmatically like + // new MyWidget({}). See #11635. + var source = this.srcNodeRef; + if(source && source.parentNode && this.domNode !== source){ + source.parentNode.replaceChild(this.domNode, source); + } + } + + if(this.domNode){ + // Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId", + // assuming that dojo._scopeName even exists in 2.0 + this.domNode.setAttribute("widgetId", this.id); + } + this.postCreate(); + + // If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC. + if(this.srcNodeRef && !this.srcNodeRef.parentNode){ + delete this.srcNodeRef; + } + + this._created = true; + }, + + _applyAttributes: function(){ + // summary: + // Step during widget creation to copy widget attributes to the + // DOM according to attributeMap and _setXXXAttr objects, and also to call + // custom _setXXXAttr() methods. + // + // Skips over blank/false attribute values, unless they were explicitly specified + // as parameters to the widget, since those are the default anyway, + // and setting tabIndex="" is different than not setting tabIndex at all. + // + // For backwards-compatibility reasons attributeMap overrides _setXXXAttr when + // _setXXXAttr is a hash/string/array, but _setXXXAttr as a functions override attributeMap. + // tags: + // private + + // Get list of attributes where this.set(name, value) will do something beyond + // setting this[name] = value. Specifically, attributes that have: + // - associated _setXXXAttr() method/hash/string/array + // - entries in attributeMap. + var ctor = this.constructor, + list = ctor._setterAttrs; + if(!list){ + list = (ctor._setterAttrs = []); + for(var attr in this.attributeMap){ + list.push(attr); + } + + var proto = ctor.prototype; + for(var fxName in proto){ + if(fxName in this.attributeMap){ continue; } + var setterName = "_set" + fxName.replace(/^[a-z]|-[a-zA-Z]/g, function(c){ return c.charAt(c.length-1).toUpperCase(); }) + "Attr"; + if(setterName in proto){ + list.push(fxName); + } + } + } + + // Call this.set() for each attribute that was either specified as parameter to constructor, + // or was found above and has a default non-null value. For correlated attributes like value and displayedValue, the one + // specified as a parameter should take precedence, so apply attributes in this.params last. + // Particularly important for new DateTextBox({displayedValue: ...}) since DateTextBox's default value is + // NaN and thus is not ignored like a default value of "". + array.forEach(list, function(attr){ + if(this.params && attr in this.params){ + // skip this one, do it below + }else if(this[attr]){ + this.set(attr, this[attr]); + } + }, this); + for(var param in this.params){ + this.set(param, this[param]); + } + }, + + postMixInProperties: function(){ + // summary: + // Called after the parameters to the widget have been read-in, + // but before the widget template is instantiated. Especially + // useful to set properties that are referenced in the widget + // template. + // tags: + // protected + }, + + buildRendering: function(){ + // summary: + // Construct the UI for this widget, setting this.domNode. + // Most widgets will mixin `dijit._TemplatedMixin`, which implements this method. + // tags: + // protected + + if(!this.domNode){ + // Create root node if it wasn't created by _Templated + this.domNode = this.srcNodeRef || domConstruct.create('div'); + } + + // baseClass is a single class name or occasionally a space-separated list of names. + // Add those classes to the DOMNode. If RTL mode then also add with Rtl suffix. + // TODO: make baseClass custom setter + if(this.baseClass){ + var classes = this.baseClass.split(" "); + if(!this.isLeftToRight()){ + classes = classes.concat( array.map(classes, function(name){ return name+"Rtl"; })); + } + domClass.add(this.domNode, classes); + } + }, + + postCreate: function(){ + // summary: + // Processing after the DOM fragment is created + // description: + // Called after the DOM fragment has been created, but not necessarily + // added to the document. Do not include any operations which rely on + // node dimensions or placement. + // tags: + // protected + }, + + startup: function(){ + // summary: + // Processing after the DOM fragment is added to the document + // description: + // Called after a widget and its children have been created and added to the page, + // and all related widgets have finished their create() cycle, up through postCreate(). + // This is useful for composite widgets that need to control or layout sub-widgets. + // Many layout widgets can use this as a wiring phase. + if(this._started){ return; } + this._started = true; + array.forEach(this.getChildren(), function(obj){ + if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){ + obj.startup(); + obj._started = true; + } + }); + }, + + //////////// DESTROY FUNCTIONS //////////////////////////////// + + destroyRecursive: function(/*Boolean?*/ preserveDom){ + // summary: + // Destroy this widget and its descendants + // description: + // This is the generic "destructor" function that all widget users + // should call to cleanly discard with a widget. Once a widget is + // destroyed, it is removed from the manager object. + // preserveDom: + // If true, this method will leave the original DOM structure + // alone of descendant Widgets. Note: This will NOT work with + // dijit._Templated widgets. + + this._beingDestroyed = true; + this.destroyDescendants(preserveDom); + this.destroy(preserveDom); + }, + + destroy: function(/*Boolean*/ preserveDom){ + // summary: + // Destroy this widget, but not its descendants. + // This method will, however, destroy internal widgets such as those used within a template. + // preserveDom: Boolean + // If true, this method will leave the original DOM structure alone. + // Note: This will not yet work with _Templated widgets + + this._beingDestroyed = true; + this.uninitialize(); + + // remove this.connect() and this.subscribe() listeners + var c; + while((c = this._connects.pop())){ + c.remove(); + } + + // destroy widgets created as part of template, etc. + var w; + while((w = this._supportingWidgets.pop())){ + if(w.destroyRecursive){ + w.destroyRecursive(); + }else if(w.destroy){ + w.destroy(); + } + } + + this.destroyRendering(preserveDom); + registry.remove(this.id); + this._destroyed = true; + }, + + destroyRendering: function(/*Boolean?*/ preserveDom){ + // summary: + // Destroys the DOM nodes associated with this widget + // preserveDom: + // If true, this method will leave the original DOM structure alone + // during tear-down. Note: this will not work with _Templated + // widgets yet. + // tags: + // protected + + if(this.bgIframe){ + this.bgIframe.destroy(preserveDom); + delete this.bgIframe; + } + + if(this.domNode){ + if(preserveDom){ + domAttr.remove(this.domNode, "widgetId"); + }else{ + domConstruct.destroy(this.domNode); + } + delete this.domNode; + } + + if(this.srcNodeRef){ + if(!preserveDom){ + domConstruct.destroy(this.srcNodeRef); + } + delete this.srcNodeRef; + } + }, + + destroyDescendants: function(/*Boolean?*/ preserveDom){ + // summary: + // Recursively destroy the children of this widget and their + // descendants. + // preserveDom: + // If true, the preserveDom attribute is passed to all descendant + // widget's .destroy() method. Not for use with _Templated + // widgets. + + // get all direct descendants and destroy them recursively + array.forEach(this.getChildren(), function(widget){ + if(widget.destroyRecursive){ + widget.destroyRecursive(preserveDom); + } + }); + }, + + uninitialize: function(){ + // summary: + // Stub function. Override to implement custom widget tear-down + // behavior. + // tags: + // protected + return false; + }, + + ////////////////// GET/SET, CUSTOM SETTERS, ETC. /////////////////// + + _setStyleAttr: function(/*String||Object*/ value){ + // summary: + // Sets the style attribute of the widget according to value, + // which is either a hash like {height: "5px", width: "3px"} + // or a plain string + // description: + // Determines which node to set the style on based on style setting + // in attributeMap. + // tags: + // protected + + var mapNode = this.domNode; + + // Note: technically we should revert any style setting made in a previous call + // to his method, but that's difficult to keep track of. + + if(lang.isObject(value)){ + domStyle.set(mapNode, value); + }else{ + if(mapNode.style.cssText){ + mapNode.style.cssText += "; " + value; + }else{ + mapNode.style.cssText = value; + } + } + + this._set("style", value); + }, + + _attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){ + // summary: + // Reflect a widget attribute (title, tabIndex, duration etc.) to + // the widget DOM, as specified by commands parameter. + // If commands isn't specified then it's looked up from attributeMap. + // Note some attributes like "type" + // cannot be processed this way as they are not mutable. + // + // tags: + // private + + commands = arguments.length >= 3 ? commands : this.attributeMap[attr]; + + array.forEach(lang.isArray(commands) ? commands : [commands], function(command){ + + // Get target node and what we are doing to that node + var mapNode = this[command.node || command || "domNode"]; // DOM node + var type = command.type || "attribute"; // class, innerHTML, innerText, or attribute + + switch(type){ + case "attribute": + if(lang.isFunction(value)){ // functions execute in the context of the widget + value = lang.hitch(this, value); + } + + // Get the name of the DOM node attribute; usually it's the same + // as the name of the attribute in the widget (attr), but can be overridden. + // Also maps handler names to lowercase, like onSubmit --> onsubmit + var attrName = command.attribute ? command.attribute : + (/^on[A-Z][a-zA-Z]*$/.test(attr) ? attr.toLowerCase() : attr); + + domAttr.set(mapNode, attrName, value); + break; + case "innerText": + mapNode.innerHTML = ""; + mapNode.appendChild(win.doc.createTextNode(value)); + break; + case "innerHTML": + mapNode.innerHTML = value; + break; + case "class": + domClass.replace(mapNode, value, this[attr]); + break; + } + }, this); + }, + + get: function(name){ + // summary: + // Get a property from a widget. + // name: + // The property to get. + // description: + // Get a named property from a widget. The property may + // potentially be retrieved via a getter method. If no getter is defined, this + // just retrieves the object's property. + // + // For example, if the widget has properties `foo` and `bar` + // and a method named `_getFooAttr()`, calling: + // `myWidget.get("foo")` would be equivalent to calling + // `widget._getFooAttr()` and `myWidget.get("bar")` + // would be equivalent to the expression + // `widget.bar2` + var names = this._getAttrNames(name); + return this[names.g] ? this[names.g]() : this[name]; + }, + + set: function(name, value){ + // summary: + // Set a property on a widget + // name: + // The property to set. + // value: + // The value to set in the property. + // description: + // Sets named properties on a widget which may potentially be handled by a + // setter in the widget. + // + // For example, if the widget has properties `foo` and `bar` + // and a method named `_setFooAttr()`, calling + // `myWidget.set("foo", "Howdy!")` would be equivalent to calling + // `widget._setFooAttr("Howdy!")` and `myWidget.set("bar", 3)` + // would be equivalent to the statement `widget.bar = 3;` + // + // set() may also be called with a hash of name/value pairs, ex: + // + // | myWidget.set({ + // | foo: "Howdy", + // | bar: 3 + // | }); + // + // This is equivalent to calling `set(foo, "Howdy")` and `set(bar, 3)` + + if(typeof name === "object"){ + for(var x in name){ + this.set(x, name[x]); + } + return this; + } + var names = this._getAttrNames(name), + setter = this[names.s]; + if(lang.isFunction(setter)){ + // use the explicit setter + var result = setter.apply(this, Array.prototype.slice.call(arguments, 1)); + }else{ + // Mapping from widget attribute to DOMNode attribute/value/etc. + // Map according to: + // 1. attributeMap setting, if one exists (TODO: attributeMap deprecated, remove in 2.0) + // 2. _setFooAttr: {...} type attribute in the widget (if one exists) + // 3. apply to focusNode or domNode if standard attribute name, excluding funcs like onClick. + // Checks if an attribute is a "standard attribute" by whether the DOMNode JS object has a similar + // attribute name (ex: accept-charset attribute matches jsObject.acceptCharset). + // Note also that Tree.focusNode() is a function not a DOMNode, so test for that. + var defaultNode = this.focusNode && !lang.isFunction(this.focusNode) ? "focusNode" : "domNode", + tag = this[defaultNode].tagName, + attrsForTag = tagAttrs[tag] || (tagAttrs[tag] = getAttrs(this[defaultNode])), + map = name in this.attributeMap ? this.attributeMap[name] : + names.s in this ? this[names.s] : + ((names.l in attrsForTag && typeof value != "function") || + /^aria-|^data-|^role$/.test(name)) ? defaultNode : null; + if(map != null){ + this._attrToDom(name, value, map); + } + this._set(name, value); + } + return result || this; + }, + + _attrPairNames: {}, // shared between all widgets + _getAttrNames: function(name){ + // summary: + // Helper function for get() and set(). + // Caches attribute name values so we don't do the string ops every time. + // tags: + // private + + var apn = this._attrPairNames; + if(apn[name]){ return apn[name]; } + var uc = name.replace(/^[a-z]|-[a-zA-Z]/g, function(c){ return c.charAt(c.length-1).toUpperCase(); }); + return (apn[name] = { + n: name+"Node", + s: "_set"+uc+"Attr", // converts dashes to camel case, ex: accept-charset --> _setAcceptCharsetAttr + g: "_get"+uc+"Attr", + l: uc.toLowerCase() // lowercase name w/out dashes, ex: acceptcharset + }); + }, + + _set: function(/*String*/ name, /*anything*/ value){ + // summary: + // Helper function to set new value for specified attribute, and call handlers + // registered with watch() if the value has changed. + var oldValue = this[name]; + this[name] = value; + if(this._watchCallbacks && this._created && value !== oldValue){ + this._watchCallbacks(name, oldValue, value); + } + }, + + on: function(/*String*/ type, /*Function*/ func){ + // summary: + // Call specified function when event occurs, ex: myWidget.on("click", function(){ ... }). + // description: + // Call specified function when event `type` occurs, ex: `myWidget.on("click", function(){ ... })`. + // Note that the function is not run in any particular scope, so if (for example) you want it to run in the + // widget's scope you must do `myWidget.on("click", lang.hitch(myWidget, func))`. + + return aspect.after(this, this._onMap(type), func, true); + }, + + _onMap: function(/*String*/ type){ + // summary: + // Maps on() type parameter (ex: "mousemove") to method name (ex: "onMouseMove") + var ctor = this.constructor, map = ctor._onMap; + if(!map){ + map = (ctor._onMap = {}); + for(var attr in ctor.prototype){ + if(/^on/.test(attr)){ + map[attr.replace(/^on/, "").toLowerCase()] = attr; + } + } + } + return map[type.toLowerCase()]; // String + }, + + toString: function(){ + // summary: + // Returns a string that represents the widget + // description: + // When a widget is cast to a string, this method will be used to generate the + // output. Currently, it does not implement any sort of reversible + // serialization. + return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String + }, + + getChildren: function(){ + // summary: + // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode. + // Does not return nested widgets, nor widgets that are part of this widget's template. + return this.containerNode ? registry.findWidgets(this.containerNode) : []; // dijit._Widget[] + }, + + getParent: function(){ + // summary: + // Returns the parent widget of this widget + return registry.getEnclosingWidget(this.domNode.parentNode); + }, + + connect: function( + /*Object|null*/ obj, + /*String|Function*/ event, + /*String|Function*/ method){ + // summary: + // Connects specified obj/event to specified method of this object + // and registers for disconnect() on widget destroy. + // description: + // Provide widget-specific analog to dojo.connect, except with the + // implicit use of this widget as the target object. + // Events connected with `this.connect` are disconnected upon + // destruction. + // returns: + // A handle that can be passed to `disconnect` in order to disconnect before + // the widget is destroyed. + // example: + // | var btn = new dijit.form.Button(); + // | // when foo.bar() is called, call the listener we're going to + // | // provide in the scope of btn + // | btn.connect(foo, "bar", function(){ + // | console.debug(this.toString()); + // | }); + // tags: + // protected + + var handle = connect.connect(obj, event, this, method); + this._connects.push(handle); + return handle; // _Widget.Handle + }, + + disconnect: function(handle){ + // summary: + // Disconnects handle created by `connect`. + // Also removes handle from this widget's list of connects. + // tags: + // protected + var i = array.indexOf(this._connects, handle); + if(i != -1){ + handle.remove(); + this._connects.splice(i, 1); + } + }, + + subscribe: function(t, method){ + // summary: + // Subscribes to the specified topic and calls the specified method + // of this object and registers for unsubscribe() on widget destroy. + // description: + // Provide widget-specific analog to dojo.subscribe, except with the + // implicit use of this widget as the target object. + // t: String + // The topic + // method: Function + // The callback + // example: + // | var btn = new dijit.form.Button(); + // | // when /my/topic is published, this button changes its label to + // | // be the parameter of the topic. + // | btn.subscribe("/my/topic", function(v){ + // | this.set("label", v); + // | }); + // tags: + // protected + var handle = topic.subscribe(t, lang.hitch(this, method)); + this._connects.push(handle); + return handle; // _Widget.Handle + }, + + unsubscribe: function(/*Object*/ handle){ + // summary: + // Unsubscribes handle created by this.subscribe. + // Also removes handle from this widget's list of subscriptions + // tags: + // protected + this.disconnect(handle); + }, + + isLeftToRight: function(){ + // summary: + // Return this widget's explicit or implicit orientation (true for LTR, false for RTL) + // tags: + // protected + return this.dir ? (this.dir == "ltr") : domGeometry.isBodyLtr(); //Boolean + }, + + isFocusable: function(){ + // summary: + // Return true if this widget can currently be focused + // and false if not + return this.focus && (domStyle.get(this.domNode, "display") != "none"); + }, + + placeAt: function(/* String|DomNode|_Widget */reference, /* String?|Int? */position){ + // summary: + // Place this widget's domNode reference somewhere in the DOM based + // on standard domConstruct.place conventions, or passing a Widget reference that + // contains and addChild member. + // + // description: + // A convenience function provided in all _Widgets, providing a simple + // shorthand mechanism to put an existing (or newly created) Widget + // somewhere in the dom, and allow chaining. + // + // reference: + // The String id of a domNode, a domNode reference, or a reference to a Widget possessing + // an addChild method. + // + // position: + // If passed a string or domNode reference, the position argument + // accepts a string just as domConstruct.place does, one of: "first", "last", + // "before", or "after". + // + // If passed a _Widget reference, and that widget reference has an ".addChild" method, + // it will be called passing this widget instance into that method, supplying the optional + // position index passed. + // + // returns: + // dijit._Widget + // Provides a useful return of the newly created dijit._Widget instance so you + // can "chain" this function by instantiating, placing, then saving the return value + // to a variable. + // + // example: + // | // create a Button with no srcNodeRef, and place it in the body: + // | var button = new dijit.form.Button({ label:"click" }).placeAt(win.body()); + // | // now, 'button' is still the widget reference to the newly created button + // | button.on("click", function(e){ console.log('click'); })); + // + // example: + // | // create a button out of a node with id="src" and append it to id="wrapper": + // | var button = new dijit.form.Button({},"src").placeAt("wrapper"); + // + // example: + // | // place a new button as the first element of some div + // | var button = new dijit.form.Button({ label:"click" }).placeAt("wrapper","first"); + // + // example: + // | // create a contentpane and add it to a TabContainer + // | var tc = dijit.byId("myTabs"); + // | new dijit.layout.ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc) + + if(reference.declaredClass && reference.addChild){ + reference.addChild(this, position); + }else{ + domConstruct.place(this.domNode, reference, position); + } + return this; + }, + + getTextDir: function(/*String*/ text,/*String*/ originalDir){ + // summary: + // Return direction of the text. + // The function overridden in the _BidiSupport module, + // its main purpose is to calculate the direction of the + // text, if was defined by the programmer through textDir. + // tags: + // protected. + return originalDir; + }, + + applyTextDir: function(/*===== element, text =====*/){ + // summary: + // The function overridden in the _BidiSupport module, + // originally used for setting element.dir according to this.textDir. + // In this case does nothing. + // element: DOMNode + // text: String + // tags: + // protected. + }, + + defer: function(fcn, delay){ + // summary: + // Wrapper to setTimeout to avoid deferred functions executing + // after the originating widget has been destroyed. + // Returns an object handle with a remove method (that returns null) (replaces clearTimeout). + // fcn: function reference + // delay: Optional number (defaults to 0) + // tags: + // protected. + var timer = setTimeout(lang.hitch(this, + function(){ + timer = null; + if(!this._destroyed){ + lang.hitch(this, fcn)(); + } + }), + delay || 0 + ); + return { + remove: function(){ + if(timer){ + clearTimeout(timer); + timer = null; + } + return null; // so this works well: handle = handle.remove(); + } + }; + } }); + +}); + +}, +'dijit/form/Form':function(){ +define("dijit/form/Form", [ + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/_base/event", // event.stop + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/sniff", // has("ie") + "../_Widget", + "../_TemplatedMixin", + "./_FormMixin", + "../layout/_ContentPaneResizeMixin" +], function(declare, domAttr, event, kernel, has, _Widget, _TemplatedMixin, _FormMixin, _ContentPaneResizeMixin){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _FormMixin = dijit.form._FormMixin; + var _ContentPaneResizeMixin = dijit.layout._ContentPaneResizeMixin; =====*/ -(function(){ - // precalculate long expressions - var oldOnMove = dojo.dnd.Moveable.prototype.onMove; - - dojo.declare("dojo.dnd.TimedMoveable", dojo.dnd.Moveable, { + // module: + // dijit/form/Form + // summary: + // Widget corresponding to HTML form tag, for validation and serialization + + + return declare("dijit.form.Form", [_Widget, _TemplatedMixin, _FormMixin, _ContentPaneResizeMixin], { // summary: - // A specialized version of Moveable to support an FPS throttling. - // This class puts an upper restriction on FPS, which may reduce - // the CPU load. The additional parameter "timeout" regulates - // the delay before actually moving the moveable object. - - // object attributes (for markup) - timeout: 40, // in ms, 40ms corresponds to 25 fps - - constructor: function(node, params){ + // Widget corresponding to HTML form tag, for validation and serialization + // + // example: + // | <form data-dojo-type="dijit.form.Form" id="myForm"> + // | Name: <input type="text" name="name" /> + // | </form> + // | myObj = {name: "John Doe"}; + // | dijit.byId('myForm').set('value', myObj); + // | + // | myObj=dijit.byId('myForm').get('value'); + + // HTML <FORM> attributes + + // name: String? + // Name of form for scripting. + name: "", + + // action: String? + // Server-side form handler. + action: "", + + // method: String? + // HTTP method used to submit the form, either "GET" or "POST". + method: "", + + // encType: String? + // Encoding type for the form, ex: application/x-www-form-urlencoded. + encType: "", + + // accept-charset: String? + // List of supported charsets. + "accept-charset": "", + + // accept: String? + // List of MIME types for file upload. + accept: "", + + // target: String? + // Target frame for the document to be opened in. + target: "", + + templateString: "<form data-dojo-attach-point='containerNode' data-dojo-attach-event='onreset:_onReset,onsubmit:_onSubmit' ${!nameAttrSetting}></form>", + + postMixInProperties: function(){ + // Setup name=foo string to be referenced from the template (but only if a name has been specified) + // Unfortunately we can't use _setNameAttr to set the name due to IE limitations, see #8660 + this.nameAttrSetting = this.name ? ("name='" + this.name + "'") : ""; + this.inherited(arguments); + }, + + execute: function(/*Object*/ /*===== formContents =====*/){ // summary: - // an object that makes a node moveable with a timer - // node: Node||String - // a node (or node's id) to be moved - // params: dojo.dnd.__TimedMoveableArgs - // object with additional parameters. - - // sanitize parameters - if(!params){ params = {}; } - if(params.timeout && typeof params.timeout == "number" && params.timeout >= 0){ - this.timeout = params.timeout; + // Deprecated: use submit() + // tags: + // deprecated + }, + + onExecute: function(){ + // summary: + // Deprecated: use onSubmit() + // tags: + // deprecated + }, + + _setEncTypeAttr: function(/*String*/ value){ + this.encType = value; + domAttr.set(this.domNode, "encType", value); + if(has("ie")){ this.domNode.encoding = value; } + }, + + reset: function(/*Event?*/ e){ + // summary: + // restores all widget values back to their init values, + // calls onReset() which can cancel the reset by returning false + + // create fake event so we can know if preventDefault() is called + var faux = { + returnValue: true, // the IE way + preventDefault: function(){ // not IE + this.returnValue = false; + }, + stopPropagation: function(){}, + currentTarget: e ? e.target : this.domNode, + target: e ? e.target : this.domNode + }; + // if return value is not exactly false, and haven't called preventDefault(), then reset + if(!(this.onReset(faux) === false) && faux.returnValue){ + this.inherited(arguments, []); } }, - - // markup methods - markupFactory: function(params, node){ - return new dojo.dnd.TimedMoveable(node, params); + + onReset: function(/*Event?*/ /*===== e =====*/){ + // summary: + // Callback when user resets the form. This method is intended + // to be over-ridden. When the `reset` method is called + // programmatically, the return value from `onReset` is used + // to compute whether or not resetting should proceed + // tags: + // callback + return true; // Boolean }, - - onMoveStop: function(/* dojo.dnd.Mover */ mover){ - if(mover._timer){ - // stop timer - clearTimeout(mover._timer) - // reflect the last received position - oldOnMove.call(this, mover, mover._leftTop) + + _onReset: function(e){ + this.reset(e); + event.stop(e); + return false; + }, + + _onSubmit: function(e){ + var fp = this.constructor.prototype; + // TODO: remove this if statement beginning with 2.0 + if(this.execute != fp.execute || this.onExecute != fp.onExecute){ + kernel.deprecated("dijit.form.Form:execute()/onExecute() are deprecated. Use onSubmit() instead.", "", "2.0"); + this.onExecute(); + this.execute(this.getValues()); + } + if(this.onSubmit(e) === false){ // only exactly false stops submit + event.stop(e); } - dojo.dnd.Moveable.prototype.onMoveStop.apply(this, arguments); }, - onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ - mover._leftTop = leftTop; - if(!mover._timer){ - var _t = this; // to avoid using dojo.hitch() - mover._timer = setTimeout(function(){ - // we don't have any pending requests - mover._timer = null; - // reflect the last received position - oldOnMove.call(_t, mover, mover._leftTop); - }, this.timeout); + + onSubmit: function(/*Event?*/ /*===== e =====*/){ + // summary: + // Callback when user submits the form. + // description: + // This method is intended to be over-ridden, but by default it checks and + // returns the validity of form elements. When the `submit` + // method is called programmatically, the return value from + // `onSubmit` is used to compute whether or not submission + // should proceed + // tags: + // extension + + return this.isValid(); // Boolean + }, + + submit: function(){ + // summary: + // programmatically submit form if and only if the `onSubmit` returns true + if(!(this.onSubmit() === false)){ + this.containerNode.submit(); } } }); -})(); +}); -} +}, +'dijit/layout/_TabContainerBase':function(){ +require({cache:{ +'url:dijit/layout/templates/TabContainer.html':"<div class=\"dijitTabContainer\">\n\t<div class=\"dijitTabListWrapper\" data-dojo-attach-point=\"tablistNode\"></div>\n\t<div data-dojo-attach-point=\"tablistSpacer\" class=\"dijitTabSpacer ${baseClass}-spacer\"></div>\n\t<div class=\"dijitTabPaneWrapper ${baseClass}-container\" data-dojo-attach-point=\"containerNode\"></div>\n</div>\n"}}); +define("dijit/layout/_TabContainerBase", [ + "dojo/text!./templates/TabContainer.html", + "./StackContainer", + "./utils", // marginBox2contextBox, layoutChildren + "../_TemplatedMixin", + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add + "dojo/dom-geometry", // domGeometry.contentBox + "dojo/dom-style" // domStyle.style +], function(template, StackContainer, layoutUtils, _TemplatedMixin, declare, domClass, domGeometry, domStyle){ -if(!dojo._hasResource["dijit.form._FormMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form._FormMixin"] = true; -dojo.provide("dijit.form._FormMixin"); +/*===== + var StackContainer = dijit.layout.StackContainer; + var _TemplatedMixin = dijit._TemplatedMixin; +=====*/ + +// module: +// dijit/layout/_TabContainerBase +// summary: +// Abstract base class for TabContainer. Must define _makeController() to instantiate +// and return the widget that displays the tab labels -dojo.declare("dijit.form._FormMixin", null, { +return declare("dijit.layout._TabContainerBase", [StackContainer, _TemplatedMixin], { // summary: - // Mixin for containers of form widgets (i.e. widgets that represent a single value - // and can be children of a <form> node or dijit.form.Form widget) + // Abstract base class for TabContainer. Must define _makeController() to instantiate + // and return the widget that displays the tab labels // description: - // Can extract all the form widgets - // values and combine them into a single javascript object, or alternately - // take such an object and set the values for all the contained - // form widgets + // A TabContainer is a container that has multiple panes, but shows only + // one pane at a time. There are a set of tabs corresponding to each pane, + // where each tab has the name (aka title) of the pane, and optionally a close button. + + // tabPosition: String + // Defines where tabs go relative to tab content. + // "top", "bottom", "left-h", "right-h" + tabPosition: "top", + + baseClass: "dijitTabContainer", + + // tabStrip: [const] Boolean + // Defines whether the tablist gets an extra class for layouting, putting a border/shading + // around the set of tabs. Not supported by claro theme. + tabStrip: false, + + // nested: [const] Boolean + // If true, use styling for a TabContainer nested inside another TabContainer. + // For tundra etc., makes tabs look like links, and hides the outer + // border since the outer TabContainer already has a border. + nested: false, + + templateString: template, + + postMixInProperties: function(){ + // set class name according to tab position, ex: dijitTabContainerTop + this.baseClass += this.tabPosition.charAt(0).toUpperCase() + this.tabPosition.substr(1).replace(/-.*/, ""); + + this.srcNodeRef && domStyle.set(this.srcNodeRef, "visibility", "hidden"); + + this.inherited(arguments); + }, + + buildRendering: function(){ + this.inherited(arguments); + + // Create the tab list that will have a tab (a.k.a. tab button) for each tab panel + this.tablist = this._makeController(this.tablistNode); + + if(!this.doLayout){ domClass.add(this.domNode, "dijitTabContainerNoLayout"); } + + if(this.nested){ + /* workaround IE's lack of support for "a > b" selectors by + * tagging each node in the template. + */ + domClass.add(this.domNode, "dijitTabContainerNested"); + domClass.add(this.tablist.containerNode, "dijitTabContainerTabListNested"); + domClass.add(this.tablistSpacer, "dijitTabContainerSpacerNested"); + domClass.add(this.containerNode, "dijitTabPaneWrapperNested"); + }else{ + domClass.add(this.domNode, "tabStrip-" + (this.tabStrip ? "enabled" : "disabled")); + } + }, + + _setupChild: function(/*dijit._Widget*/ tab){ + // Overrides StackContainer._setupChild(). + domClass.add(tab.domNode, "dijitTabPane"); + this.inherited(arguments); + }, + + startup: function(){ + if(this._started){ return; } + + // wire up the tablist and its tabs + this.tablist.startup(); + + this.inherited(arguments); + }, + + layout: function(){ + // Overrides StackContainer.layout(). + // Configure the content pane to take up all the space except for where the tabs are + + if(!this._contentBox || typeof(this._contentBox.l) == "undefined"){return;} + + var sc = this.selectedChildWidget; + + if(this.doLayout){ + // position and size the titles and the container node + var titleAlign = this.tabPosition.replace(/-h/, ""); + this.tablist.layoutAlign = titleAlign; + var children = [this.tablist, { + domNode: this.tablistSpacer, + layoutAlign: titleAlign + }, { + domNode: this.containerNode, + layoutAlign: "client" + }]; + layoutUtils.layoutChildren(this.domNode, this._contentBox, children); + + // Compute size to make each of my children. + // children[2] is the margin-box size of this.containerNode, set by layoutChildren() call above + this._containerContentBox = layoutUtils.marginBox2contentBox(this.containerNode, children[2]); + + if(sc && sc.resize){ + sc.resize(this._containerContentBox); + } + }else{ + // just layout the tab controller, so it can position left/right buttons etc. + if(this.tablist.resize){ + //make the tabs zero width so that they don't interfere with width calc, then reset + var s = this.tablist.domNode.style; + s.width="0"; + var width = domGeometry.getContentBox(this.domNode).w; + s.width=""; + this.tablist.resize({w: width}); + } + + // and call resize() on the selected pane just to tell it that it's been made visible + if(sc && sc.resize){ + sc.resize(); + } + } + }, + + destroy: function(){ + if(this.tablist){ + this.tablist.destroy(); + } + this.inherited(arguments); + } +}); + +}); + +}, +'dojo/store/Memory':function(){ +define("dojo/store/Memory", ["../_base/declare", "./util/QueryResults", "./util/SimpleQueryEngine"], function(declare, QueryResults, SimpleQueryEngine) { + // module: + // dojo/store/Memory + // summary: + // The module defines an in-memory object store. + + +return declare("dojo.store.Memory", null, { + // summary: + // This is a basic in-memory object store. It implements dojo.store.api.Store. + constructor: function(/*dojo.store.Memory*/ options){ + // summary: + // Creates a memory object store. + // options: + // This provides any configuration information that will be mixed into the store. + // This should generally include the data property to provide the starting set of data. + for(var i in options){ + this[i] = options[i]; + } + this.setData(this.data || []); + }, + // data: Array + // The array of all the objects in the memory store + data:null, + + // idProperty: String + // Indicates the property to use as the identity property. The values of this + // property should be unique. + idProperty: "id", + + // index: Object + // An index of data indices into the data array by id + index:null, + + // queryEngine: Function + // Defines the query engine to use for querying the data store + queryEngine: SimpleQueryEngine, + get: function(id){ + // summary: + // Retrieves an object by its identity + // id: Number + // The identity to use to lookup the object + // returns: Object + // The object in the store that matches the given id. + return this.data[this.index[id]]; + }, + getIdentity: function(object){ + // summary: + // Returns an object's identity + // object: Object + // The object to get the identity from + // returns: Number + return object[this.idProperty]; + }, + put: function(object, options){ + // summary: + // Stores an object + // object: Object + // The object to store. + // options: dojo.store.api.Store.PutDirectives?? + // Additional metadata for storing the data. Includes an "id" + // property if a specific id is to be used. + // returns: Number + var data = this.data, + index = this.index, + idProperty = this.idProperty; + var id = (options && "id" in options) ? options.id : idProperty in object ? object[idProperty] : Math.random(); + if(id in index){ + // object exists + if(options && options.overwrite === false){ + throw new Error("Object already exists"); + } + // replace the entry in data + data[index[id]] = object; + }else{ + // add the new object + index[id] = data.push(object) - 1; + } + return id; + }, + add: function(object, options){ + // summary: + // Creates an object, throws an error if the object already exists + // object: Object + // The object to store. + // options: dojo.store.api.Store.PutDirectives?? + // Additional metadata for storing the data. Includes an "id" + // property if a specific id is to be used. + // returns: Number + (options = options || {}).overwrite = false; + // call put with overwrite being false + return this.put(object, options); + }, + remove: function(id){ + // summary: + // Deletes an object by its identity + // id: Number + // The identity to use to delete the object + // returns: Boolean + // Returns true if an object was removed, falsy (undefined) if no object matched the id + var index = this.index; + var data = this.data; + if(id in index){ + data.splice(index[id], 1); + // now we have to reindex + this.setData(data); + return true; + } + }, + query: function(query, options){ + // summary: + // Queries the store for objects. + // query: Object + // The query to use for retrieving objects from the store. + // options: dojo.store.api.Store.QueryOptions? + // The optional arguments to apply to the resultset. + // returns: dojo.store.api.Store.QueryResults + // The results of the query, extended with iterative methods. + // + // example: + // Given the following store: + // + // | var store = new dojo.store.Memory({ + // | data: [ + // | {id: 1, name: "one", prime: false }, + // | {id: 2, name: "two", even: true, prime: true}, + // | {id: 3, name: "three", prime: true}, + // | {id: 4, name: "four", even: true, prime: false}, + // | {id: 5, name: "five", prime: true} + // | ] + // | }); + // + // ...find all items where "prime" is true: + // + // | var results = store.query({ prime: true }); + // + // ...or find all items where "even" is true: + // + // | var results = store.query({ even: true }); + return QueryResults(this.queryEngine(query, options)(this.data)); + }, + setData: function(data){ + // summary: + // Sets the given data as the source for this store, and indexes it + // data: Object[] + // An array of objects to use as the source of data. + if(data.items){ + // just for convenience with the data format IFRS expects + this.idProperty = data.identifier; + data = this.data = data.items; + }else{ + this.data = data; + } + this.index = {}; + for(var i = 0, l = data.length; i < l; i++){ + this.index[data[i][this.idProperty]] = i; + } + } +}); + +}); + +}, +'url:dijit/templates/Tooltip.html':"<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\"\n\t><div class=\"dijitTooltipContainer dijitTooltipContents\" data-dojo-attach-point=\"containerNode\" role='alert'></div\n\t><div class=\"dijitTooltipConnector\" data-dojo-attach-point=\"connectorNode\"></div\n></div>\n", +'dijit/_base/sniff':function(){ +define("dijit/_base/sniff", [ "dojo/uacss" ], function(){ + // module: + // dijit/_base/sniff + // summary: + // Back compatibility module, new code should require dojo/uacss directly instead of this module. +}); + +}, +'dijit/Toolbar':function(){ +define("dijit/Toolbar", [ + "require", + "dojo/_base/declare", // declare + "dojo/_base/kernel", + "dojo/keys", // keys.LEFT_ARROW keys.RIGHT_ARROW + "dojo/ready", + "./_Widget", + "./_KeyNavContainer", + "./_TemplatedMixin" +], function(require, declare, kernel, keys, ready, _Widget, _KeyNavContainer, _TemplatedMixin){ + +/*===== + var _Widget = dijit._Widget; + var _KeyNavContainer = dijit._KeyNavContainer; + var _TemplatedMixin = dijit._TemplatedMixin; +=====*/ + + // module: + // dijit/Toolbar + // summary: + // A Toolbar widget, used to hold things like `dijit.Editor` buttons + + + // Back compat w/1.6, remove for 2.0 + if(!kernel.isAsync){ + ready(0, function(){ + var requires = ["dijit/ToolbarSeparator"]; + require(requires); // use indirection so modules not rolled into a build + }); + } + + return declare("dijit.Toolbar", [_Widget, _TemplatedMixin, _KeyNavContainer], { + // summary: + // A Toolbar widget, used to hold things like `dijit.Editor` buttons + + templateString: + '<div class="dijit" role="toolbar" tabIndex="${tabIndex}" data-dojo-attach-point="containerNode">' + + '</div>', + + baseClass: "dijitToolbar", + + postCreate: function(){ + this.inherited(arguments); + + this.connectKeyNavHandlers( + this.isLeftToRight() ? [keys.LEFT_ARROW] : [keys.RIGHT_ARROW], + this.isLeftToRight() ? [keys.RIGHT_ARROW] : [keys.LEFT_ARROW] + ); + } + }); +}); + +}, +'dijit/layout/StackContainer':function(){ +define("dijit/layout/StackContainer", [ + "dojo/_base/array", // array.forEach array.indexOf array.some + "dojo/cookie", // cookie + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add domClass.replace + "dojo/_base/kernel", // kernel.isAsync + "dojo/_base/lang", // lang.extend + "dojo/ready", + "dojo/topic", // publish + "../registry", // registry.byId + "../_WidgetBase", + "./_LayoutWidget", + "dojo/i18n!../nls/common" +], function(array, cookie, declare, domClass, kernel, lang, ready, topic, + registry, _WidgetBase, _LayoutWidget){ /*===== - // value: Object - // Name/value hash for each child widget with a name and value. - // Child widgets without names are not part of the hash. +var _WidgetBase = dijit._WidgetBase; +var _LayoutWidget = dijit.layout._LayoutWidget; +var StackController = dijit.layout.StackController; +=====*/ + +// module: +// dijit/layout/StackContainer +// summary: +// A container that has multiple children, but shows only one child at a time. + +// Back compat w/1.6, remove for 2.0 +if(!kernel.isAsync){ + ready(0, function(){ + var requires = ["dijit/layout/StackController"]; + require(requires); // use indirection so modules not rolled into a build + }); +} + +// These arguments can be specified for the children of a StackContainer. +// Since any widget can be specified as a StackContainer child, mix them +// into the base widget class. (This is a hack, but it's effective.) +lang.extend(_WidgetBase, { + // selected: Boolean + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // Specifies that this widget should be the initially displayed pane. + // Note: to change the selected child use `dijit.layout.StackContainer.selectChild` + selected: false, + + // closable: Boolean + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // True if user can close (destroy) this child, such as (for example) clicking the X on the tab. + closable: false, + + // iconClass: String + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // CSS Class specifying icon to use in label associated with this pane. + iconClass: "dijitNoIcon", + + // showTitle: Boolean + // Parameter for children of `dijit.layout.StackContainer` or subclasses. + // When true, display title of this widget as tab label etc., rather than just using + // icon specified in iconClass + showTitle: true +}); + +return declare("dijit.layout.StackContainer", _LayoutWidget, { + // summary: + // A container that has multiple children, but shows only + // one child at a time // - // If there are multiple child widgets w/the same name, value is an array, - // unless they are radio buttons in which case value is a scalar (since only - // one radio button can be checked at a time). + // description: + // A container for widgets (ContentPanes, for example) That displays + // only one Widget at a time. // - // If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure. + // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild // - // Example: - // | { name: "John Smith", interests: ["sports", "movies"] } + // Can be base class for container, Wizard, Show, etc. + + // doLayout: Boolean + // If true, change the size of my currently displayed child to match my size + doLayout: true, + + // persist: Boolean + // Remembers the selected child across sessions + persist: false, + + baseClass: "dijitStackContainer", + +/*===== + // selectedChildWidget: [readonly] dijit._Widget + // References the currently selected child widget, if any. + // Adjust selected child with selectChild() method. + selectedChildWidget: null, =====*/ - // state: [readonly] String - // Will be "Error" if one or more of the child widgets has an invalid value, - // "Incomplete" if not all of the required child widgets are filled in. Otherwise, "", - // which indicates that the form is ready to be submitted. - state: "", + buildRendering: function(){ + this.inherited(arguments); + domClass.add(this.domNode, "dijitLayoutContainer"); + this.containerNode.setAttribute("role", "tabpanel"); + }, - // TODO: - // * Repeater - // * better handling for arrays. Often form elements have names with [] like - // * people[3].sex (for a list of people [{name: Bill, sex: M}, ...]) - // - // + postCreate: function(){ + this.inherited(arguments); + this.connect(this.domNode, "onkeypress", this._onKeyPress); + }, + + startup: function(){ + if(this._started){ return; } + + var children = this.getChildren(); + + // Setup each page panel to be initially hidden + array.forEach(children, this._setupChild, this); + + // Figure out which child to initially display, defaulting to first one + if(this.persist){ + this.selectedChildWidget = registry.byId(cookie(this.id + "_selectedChild")); + }else{ + array.some(children, function(child){ + if(child.selected){ + this.selectedChildWidget = child; + } + return child.selected; + }, this); + } + var selected = this.selectedChildWidget; + if(!selected && children[0]){ + selected = this.selectedChildWidget = children[0]; + selected.selected = true; + } + + // Publish information about myself so any StackControllers can initialize. + // This needs to happen before this.inherited(arguments) so that for + // TabContainer, this._contentBox doesn't include the space for the tab labels. + topic.publish(this.id+"-startup", {children: children, selected: selected}); + + // Startup each child widget, and do initial layout like setting this._contentBox, + // then calls this.resize() which does the initial sizing on the selected child. + this.inherited(arguments); + }, + + resize: function(){ + // Resize is called when we are first made visible (it's called from startup() + // if we are initially visible). If this is the first time we've been made + // visible then show our first child. + if(!this._hasBeenShown){ + this._hasBeenShown = true; + var selected = this.selectedChildWidget; + if(selected){ + this._showChild(selected); + } + } + this.inherited(arguments); + }, + + _setupChild: function(/*dijit._Widget*/ child){ + // Overrides _LayoutWidget._setupChild() + + this.inherited(arguments); + + domClass.replace(child.domNode, "dijitHidden", "dijitVisible"); + + // remove the title attribute so it doesn't show up when i hover + // over a node + child.domNode.title = ""; + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // Overrides _Container.addChild() to do layout and publish events + + this.inherited(arguments); + + if(this._started){ + topic.publish(this.id+"-addChild", child, insertIndex); // publish + + // in case the tab titles have overflowed from one line to two lines + // (or, if this if first child, from zero lines to one line) + // TODO: w/ScrollingTabController this is no longer necessary, although + // ScrollTabController.resize() does need to get called to show/hide + // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild(). + // If this is updated to not layout [except for initial child added / last child removed], update + // "childless startup" test in StackContainer.html to check for no resize event after second addChild() + this.layout(); + + // if this is the first child, then select it + if(!this.selectedChildWidget){ + this.selectChild(child); + } + } + }, + + removeChild: function(/*dijit._Widget*/ page){ + // Overrides _Container.removeChild() to do layout and publish events + + this.inherited(arguments); + + if(this._started){ + // this will notify any tablists to remove a button; do this first because it may affect sizing + topic.publish(this.id + "-removeChild", page); // publish + } + + // If all our children are being destroyed than don't run the code below (to select another page), + // because we are deleting every page one by one + if(this._descendantsBeingDestroyed){ return; } + + // Select new page to display, also updating TabController to show the respective tab. + // Do this before layout call because it can affect the height of the TabController. + if(this.selectedChildWidget === page){ + this.selectedChildWidget = undefined; + if(this._started){ + var children = this.getChildren(); + if(children.length){ + this.selectChild(children[0]); + } + } + } + + if(this._started){ + // In case the tab titles now take up one line instead of two lines + // (note though that ScrollingTabController never overflows to multiple lines), + // or the height has changed slightly because of addition/removal of tab which close icon + this.layout(); + } + }, + + selectChild: function(/*dijit._Widget|String*/ page, /*Boolean*/ animate){ + // summary: + // Show the given widget (which must be one of my children) + // page: + // Reference to child widget or id of child widget + + page = registry.byId(page); + + if(this.selectedChildWidget != page){ + // Deselect old page and select new one + var d = this._transition(page, this.selectedChildWidget, animate); + this._set("selectedChildWidget", page); + topic.publish(this.id+"-selectChild", page); // publish + + if(this.persist){ + cookie(this.id + "_selectedChild", this.selectedChildWidget.id); + } + } + + return d; // If child has an href, promise that fires when the child's href finishes loading + }, + + _transition: function(newWidget, oldWidget /*===== , animate =====*/){ + // summary: + // Hide the old widget and display the new widget. + // Subclasses should override this. + // newWidget: dijit._Widget + // The newly selected widget. + // oldWidget: dijit._Widget + // The previously selected widget. + // animate: Boolean + // Used by AccordionContainer to turn on/off slide effect. + // tags: + // protected extension + if(oldWidget){ + this._hideChild(oldWidget); + } + var d = this._showChild(newWidget); + + // Size the new widget, in case this is the first time it's being shown, + // or I have been resized since the last time it was shown. + // Note that page must be visible for resizing to work. + if(newWidget.resize){ + if(this.doLayout){ + newWidget.resize(this._containerContentBox || this._contentBox); + }else{ + // the child should pick it's own size but we still need to call resize() + // (with no arguments) to let the widget lay itself out + newWidget.resize(); + } + } + + return d; // If child has an href, promise that fires when the child's href finishes loading + }, + + _adjacent: function(/*Boolean*/ forward){ + // summary: + // Gets the next/previous child widget in this container from the current selection. + var children = this.getChildren(); + var index = array.indexOf(children, this.selectedChildWidget); + index += forward ? 1 : children.length - 1; + return children[ index % children.length ]; // dijit._Widget + }, + + forward: function(){ + // summary: + // Advance to next page. + return this.selectChild(this._adjacent(true), true); + }, + + back: function(){ + // summary: + // Go back to previous page. + return this.selectChild(this._adjacent(false), true); + }, + + _onKeyPress: function(e){ + topic.publish(this.id+"-containerKeyPress", { e: e, page: this}); // publish + }, + + layout: function(){ + // Implement _LayoutWidget.layout() virtual method. + var child = this.selectedChildWidget; + if(child && child.resize){ + if(this.doLayout){ + child.resize(this._containerContentBox || this._contentBox); + }else{ + child.resize(); + } + } + }, + + _showChild: function(/*dijit._Widget*/ page){ + // summary: + // Show the specified child by changing it's CSS, and call _onShow()/onShow() so + // it can do any updates it needs regarding loading href's etc. + // returns: + // Promise that fires when page has finished showing, or true if there's no href + var children = this.getChildren(); + page.isFirstChild = (page == children[0]); + page.isLastChild = (page == children[children.length-1]); + page._set("selected", true); + + domClass.replace(page.domNode, "dijitVisible", "dijitHidden"); + + return (page._onShow && page._onShow()) || true; + }, + + _hideChild: function(/*dijit._Widget*/ page){ + // summary: + // Hide the specified child by changing it's CSS, and call _onHide() so + // it's notified. + page._set("selected", false); + domClass.replace(page.domNode, "dijitHidden", "dijitVisible"); + + page.onHide && page.onHide(); + }, + + closeChild: function(/*dijit._Widget*/ page){ + // summary: + // Callback when user clicks the [X] to remove a page. + // If onClose() returns true then remove and destroy the child. + // tags: + // private + var remove = page.onClose(this, page); + if(remove){ + this.removeChild(page); + // makes sure we can clean up executeScripts in ContentPane onUnLoad + page.destroyRecursive(); + } + }, + + destroyDescendants: function(/*Boolean*/ preserveDom){ + this._descendantsBeingDestroyed = true; + this.selectedChildWidget = undefined; + array.forEach(this.getChildren(), function(child){ + if(!preserveDom){ + this.removeChild(child); + } + child.destroyRecursive(preserveDom); + }, this); + this._descendantsBeingDestroyed = false; + } +}); + +}); + +}, +'dojo/regexp':function(){ +define("dojo/regexp", ["./_base/kernel", "./_base/lang"], function(dojo, lang) { + // module: + // dojo/regexp + // summary: + // TODOC + +lang.getObject("regexp", true, dojo); + +/*===== +dojo.regexp = { + // summary: Regular expressions and Builder resources +}; +=====*/ + +dojo.regexp.escapeString = function(/*String*/str, /*String?*/except){ + // summary: + // Adds escape sequences for special characters in regular expressions + // except: + // a String with special characters to be left unescaped + + return str.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(ch){ + if(except && except.indexOf(ch) != -1){ + return ch; + } + return "\\" + ch; + }); // String +}; + +dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boolean?*/nonCapture){ + // summary: + // Builds a regular expression that groups subexpressions + // description: + // A utility function used by some of the RE generators. The + // subexpressions are constructed by the function, re, in the second + // parameter. re builds one subexpression for each elem in the array + // a, in the first parameter. Returns a string for a regular + // expression that groups all the subexpressions. + // arr: + // A single value or an array of values. + // re: + // A function. Takes one parameter and converts it to a regular + // expression. + // nonCapture: + // If true, uses non-capturing match, otherwise matches are retained + // by regular expression. Defaults to false + + // case 1: a is a single value. + if(!(arr instanceof Array)){ + return re(arr); // String + } + + // case 2: a is an array + var b = []; + for(var i = 0; i < arr.length; i++){ + // convert each elem to a RE + b.push(re(arr[i])); + } + + // join the REs as alternatives in a RE group. + return dojo.regexp.group(b.join("|"), nonCapture); // String +}; + +dojo.regexp.group = function(/*String*/expression, /*Boolean?*/nonCapture){ + // summary: + // adds group match to expression + // nonCapture: + // If true, uses non-capturing match, otherwise matches are retained + // by regular expression. + return "(" + (nonCapture ? "?:":"") + expression + ")"; // String +}; + +return dojo.regexp; +}); + +}, +'dijit/form/_FormMixin':function(){ +define("dijit/form/_FormMixin", [ + "dojo/_base/array", // array.every array.filter array.forEach array.indexOf array.map + "dojo/_base/declare", // declare + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/lang", // lang.hitch lang.isArray + "dojo/window" // winUtils.scrollIntoView +], function(array, declare, kernel, lang, winUtils){ + + // module: + // dijit/form/_FormMixin + // summary: + // Mixin for containers of form widgets (i.e. widgets that represent a single value + // and can be children of a <form> node or dijit.form.Form widget) + + return declare("dijit.form._FormMixin", null, { + // summary: + // Mixin for containers of form widgets (i.e. widgets that represent a single value + // and can be children of a <form> node or dijit.form.Form widget) + // description: + // Can extract all the form widgets + // values and combine them into a single javascript object, or alternately + // take such an object and set the values for all the contained + // form widgets + + /*===== + // value: Object + // Name/value hash for each child widget with a name and value. + // Child widgets without names are not part of the hash. + // + // If there are multiple child widgets w/the same name, value is an array, + // unless they are radio buttons in which case value is a scalar (since only + // one radio button can be checked at a time). + // + // If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure. + // + // Example: + // | { name: "John Smith", interests: ["sports", "movies"] } + =====*/ + + // state: [readonly] String + // Will be "Error" if one or more of the child widgets has an invalid value, + // "Incomplete" if not all of the required child widgets are filled in. Otherwise, "", + // which indicates that the form is ready to be submitted. + state: "", + + // TODO: + // * Repeater + // * better handling for arrays. Often form elements have names with [] like + // * people[3].sex (for a list of people [{name: Bill, sex: M}, ...]) + // + // + + _getDescendantFormWidgets: function(/*dijit._WidgetBase[]?*/ children){ + // summary: + // Returns all form widget descendants, searching through non-form child widgets like BorderContainer + var res = []; + array.forEach(children || this.getChildren(), function(child){ + if("value" in child){ + res.push(child); + }else{ + res = res.concat(this._getDescendantFormWidgets(child.getChildren())); + } + }, this); + return res; + }, reset: function(){ - dojo.forEach(this.getDescendants(), function(widget){ + array.forEach(this._getDescendantFormWidgets(), function(widget){ if(widget.reset){ widget.reset(); } @@ -8812,14 +18694,14 @@ dojo.declare("dijit.form._FormMixin", null, { // 2 - it will call focus() on the first invalid // sub-widget var didFocus = false; - return dojo.every(dojo.map(this.getDescendants(), function(widget){ + return array.every(array.map(this._getDescendantFormWidgets(), function(widget){ // Need to set this so that "required" widgets get their // state set. widget._hasBeenBlurred = true; var valid = widget.disabled || !widget.validate || widget.validate(); if(!valid && !didFocus){ // Set focus of the first non-valid widget - dojo.window.scrollIntoView(widget.containerNode || widget.domNode); + winUtils.scrollIntoView(widget.containerNode || widget.domNode); widget.focus(); didFocus = true; } @@ -8828,7 +18710,7 @@ dojo.declare("dijit.form._FormMixin", null, { }, setValues: function(val){ - dojo.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.", "", "2.0"); + kernel.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.", "", "2.0"); return this.set('value', val); }, _setValueAttr: function(/*Object*/ obj){ @@ -8837,7 +18719,7 @@ dojo.declare("dijit.form._FormMixin", null, { // generate map from name --> [list of widgets with that name] var map = { }; - dojo.forEach(this.getDescendants(), function(widget){ + array.forEach(this._getDescendantFormWidgets(), function(widget){ if(!widget.name){ return; } var entry = map[widget.name] || (map[widget.name] = [] ); entry.push(widget); @@ -8848,25 +18730,25 @@ dojo.declare("dijit.form._FormMixin", null, { continue; } var widgets = map[name], // array of widgets w/this name - values = dojo.getObject(name, false, obj); // list of values for those widgets + values = lang.getObject(name, false, obj); // list of values for those widgets if(values === undefined){ continue; } - if(!dojo.isArray(values)){ + if(!lang.isArray(values)){ values = [ values ]; } if(typeof widgets[0].checked == 'boolean'){ // for checkbox/radio, values is a list of which widgets should be checked - dojo.forEach(widgets, function(w, i){ - w.set('value', dojo.indexOf(values, w.value) != -1); + array.forEach(widgets, function(w){ + w.set('value', array.indexOf(values, w.value) != -1); }); }else if(widgets[0].multiple){ // it takes an array (e.g. multi-select) widgets[0].set('value', values); }else{ // otherwise, values is a list of values to be assigned sequentially to each widget - dojo.forEach(widgets, function(w, i){ + array.forEach(widgets, function(w, i){ w.set('value', values[i]); }); } @@ -8875,7 +18757,7 @@ dojo.declare("dijit.form._FormMixin", null, { /*** * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets) - dojo.forEach(this.containerNode.elements, function(element){ + array.forEach(this.containerNode.elements, function(element){ if(element.name == ''){return}; // like "continue" var namePath = element.name.split("."); var myObj=obj; @@ -8917,20 +18799,20 @@ dojo.declare("dijit.form._FormMixin", null, { switch(element.type){ case "checkbox": element.checked = (name in myObj) && - dojo.some(myObj[name], function(val){ return val == element.value; }); + array.some(myObj[name], function(val){ return val == element.value; }); break; case "radio": element.checked = (name in myObj) && myObj[name] == element.value; break; case "select-multiple": element.selectedIndex=-1; - dojo.forEach(element.options, function(option){ - option.selected = dojo.some(myObj[name], function(val){ return option.value == val; }); + array.forEach(element.options, function(option){ + option.selected = array.some(myObj[name], function(val){ return option.value == val; }); }); break; case "select-one": element.selectedIndex="0"; - dojo.forEach(element.options, function(option){ + array.forEach(element.options, function(option){ option.selected = option.value == myObj[name]; }); break; @@ -8943,13 +18825,13 @@ dojo.declare("dijit.form._FormMixin", null, { } }); */ - + // Note: no need to call this._set("value", ...) as the child updates will trigger onChange events // which I am monitoring. }, getValues: function(){ - dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0"); + kernel.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0"); return this.get('value'); }, _getValueAttr: function(){ @@ -8962,14 +18844,14 @@ dojo.declare("dijit.form._FormMixin", null, { // that wouldn't work when: // // 1. User presses return key to submit a form. That doesn't fire an onchange event, - // and even if it did it would come too late due to the setTimout(..., 0) in _handleOnChange() + // and even if it did it would come too late due to the setTimeout(..., 0) in _handleOnChange() // // 2. app for some reason calls this.get("value") while the user is typing into a // form field. Not sure if that case needs to be supported or not. // get widget values var obj = { }; - dojo.forEach(this.getDescendants(), function(widget){ + array.forEach(this._getDescendantFormWidgets(), function(widget){ var name = widget.name; if(!name || widget.disabled){ return; } @@ -8981,45 +18863,45 @@ dojo.declare("dijit.form._FormMixin", null, { if(/Radio/.test(widget.declaredClass)){ // radio button if(value !== false){ - dojo.setObject(name, value, obj); + lang.setObject(name, value, obj); }else{ // give radio widgets a default of null - value = dojo.getObject(name, false, obj); + value = lang.getObject(name, false, obj); if(value === undefined){ - dojo.setObject(name, null, obj); + lang.setObject(name, null, obj); } } }else{ // checkbox/toggle button - var ary=dojo.getObject(name, false, obj); + var ary=lang.getObject(name, false, obj); if(!ary){ ary=[]; - dojo.setObject(name, ary, obj); + lang.setObject(name, ary, obj); } if(value !== false){ ary.push(value); } } }else{ - var prev=dojo.getObject(name, false, obj); + var prev=lang.getObject(name, false, obj); if(typeof prev != "undefined"){ - if(dojo.isArray(prev)){ + if(lang.isArray(prev)){ prev.push(value); }else{ - dojo.setObject(name, [prev, value], obj); + lang.setObject(name, [prev, value], obj); } }else{ // unique name - dojo.setObject(name, value, obj); + lang.setObject(name, value, obj); } } }); /*** - * code for plain input boxes (see also dojo.formToObject, can we use that instead of this code? + * code for plain input boxes (see also domForm.formToObject, can we use that instead of this code? * but it doesn't understand [] notation, presumably) var obj = { }; - dojo.forEach(this.containerNode.elements, function(elm){ + array.forEach(this.containerNode.elements, function(elm){ if(!elm.name) { return; // like "continue" } @@ -9038,13 +18920,13 @@ dojo.declare("dijit.form._FormMixin", null, { if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){ myObj[nameA[0]][nameIndex] = { }; } - } else if(typeof(myObj[nameA[0]]) == "undefined"){ + }else if(typeof(myObj[nameA[0]]) == "undefined"){ myObj[nameA[0]] = { } } // if if(nameA.length == 1){ myObj=myObj[nameA[0]]; - } else{ + }else{ myObj=myObj[nameA[0]][nameIndex]; } // if } // for @@ -9052,15 +18934,15 @@ dojo.declare("dijit.form._FormMixin", null, { if((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type == "radio" && elm.checked)){ if(name == name.split("[")[0]){ myObj[name]=elm.value; - } else{ + }else{ // can not set value when there is no name } - } else if(elm.type == "checkbox" && elm.checked){ + }else if(elm.type == "checkbox" && elm.checked){ if(typeof(myObj[name]) == 'undefined'){ myObj[name]=[ ]; } myObj[name].push(elm.value); - } else if(elm.type == "select-multiple"){ + }else if(elm.type == "select-multiple"){ if(typeof(myObj[name]) == 'undefined'){ myObj[name]=[ ]; } @@ -9084,7 +18966,7 @@ dojo.declare("dijit.form._FormMixin", null, { return this.state == ""; }, - onValidStateChange: function(isValid){ + onValidStateChange: function(/*Boolean*/ /*===== isValid =====*/){ // summary: // Stub function to connect to if you want to do something // (like disable/enable a submit button) when the valid @@ -9096,20 +18978,20 @@ dojo.declare("dijit.form._FormMixin", null, { _getState: function(){ // summary: // Compute what this.state should be based on state of children - var states = dojo.map(this._descendants, function(w){ + var states = array.map(this._descendants, function(w){ return w.get("state") || ""; }); - return dojo.indexOf(states, "Error") >= 0 ? "Error" : - dojo.indexOf(states, "Incomplete") >= 0 ? "Incomplete" : ""; + return array.indexOf(states, "Error") >= 0 ? "Error" : + array.indexOf(states, "Incomplete") >= 0 ? "Incomplete" : ""; }, disconnectChildren: function(){ // summary: // Remove connections to monitor changes to children's value, error state, and disabled state, // in order to update Form.value and Form.state. - dojo.forEach(this._childConnections || [], dojo.hitch(this, "disconnect")); - dojo.forEach(this._childWatches || [], function(w){ w.unwatch(); }); + array.forEach(this._childConnections || [], lang.hitch(this, "disconnect")); + array.forEach(this._childWatches || [], function(w){ w.unwatch(); }); }, connectChildren: function(/*Boolean*/ inStartup){ @@ -9126,10 +19008,10 @@ dojo.declare("dijit.form._FormMixin", null, { // Remove old connections, if any this.disconnectChildren(); - this._descendants = this.getDescendants(); + this._descendants = this._getDescendantFormWidgets(); // (Re)set this.value and this.state. Send watch() notifications but not on startup. - var set = inStartup ? function(name, val){ _this[name] = val; } : dojo.hitch(this, "_set"); + var set = inStartup ? function(name, val){ _this[name] = val; } : lang.hitch(this, "_set"); set("value", this.get("value")); set("state", this._getState()); @@ -9137,14 +19019,14 @@ dojo.declare("dijit.form._FormMixin", null, { // Form.state var conns = (this._childConnections = []), watches = (this._childWatches = []); - dojo.forEach(dojo.filter(this._descendants, + array.forEach(array.filter(this._descendants, function(item){ return item.validate; } ), function(widget){ // We are interested in whenever the widget changes validity state - or // whenever the disabled attribute on that widget is changed. - dojo.forEach(["state", "disabled"], function(attr){ - watches.push(widget.watch(attr, function(attr, oldVal, newVal){ + array.forEach(["state", "disabled"], function(attr){ + watches.push(widget.watch(attr, function(){ _this.set("state", _this._getState()); })); }); @@ -9154,7 +19036,7 @@ dojo.declare("dijit.form._FormMixin", null, { var onChange = function(){ // summary: // Called when child's value or disabled state changes - + // Use setTimeout() to collapse value changes in multiple children into a single // update to my value. Multiple updates will occur on: // 1. Form.set() @@ -9169,8 +19051,8 @@ dojo.declare("dijit.form._FormMixin", null, { _this._set("value", _this.get("value")); }, 10); }; - dojo.forEach( - dojo.filter(this._descendants, function(item){ return item.onChange; } ), + array.forEach( + array.filter(this._descendants, function(item){ return item.onChange; } ), function(widget){ // When a child widget's value changes, // the efficient thing to do is to just update that one attribute in this.value, @@ -9203,800 +19085,611 @@ dojo.declare("dijit.form._FormMixin", null, { } }); +}); -} - -if(!dojo._hasResource["dijit._DialogMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._DialogMixin"] = true; -dojo.provide("dijit._DialogMixin"); +}, +'dijit/DropDownMenu':function(){ +require({cache:{ +'url:dijit/templates/Menu.html':"<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" role=\"menu\" tabIndex=\"${tabIndex}\" data-dojo-attach-event=\"onkeypress:_onKeyPress\" cellspacing=\"0\">\n\t<tbody class=\"dijitReset\" data-dojo-attach-point=\"containerNode\"></tbody>\n</table>\n"}}); +define("dijit/DropDownMenu", [ + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/keys", // keys + "dojo/text!./templates/Menu.html", + "./_OnDijitClickMixin", + "./_MenuBase" +], function(declare, event, keys, template, _OnDijitClickMixin, _MenuBase){ +/*===== + var _MenuBase = dijit._MenuBase; + var _OnDijitClickMixin = dijit._OnDijitClickMixin; +=====*/ + // module: + // dijit/DropDownMenu + // summary: + // dijit.DropDownMenu widget -dojo.declare("dijit._DialogMixin", null, - { + return declare("dijit.DropDownMenu", [_MenuBase, _OnDijitClickMixin], { // summary: - // This provides functions useful to Dialog and TooltipDialog + // A menu, without features for context menu (Meaning, drop down menu) - attributeMap: dijit._Widget.prototype.attributeMap, - - execute: function(/*Object*/ formContents){ - // summary: - // Callback when the user hits the submit button. - // Override this method to handle Dialog execution. - // description: - // After the user has pressed the submit button, the Dialog - // first calls onExecute() to notify the container to hide the - // dialog and restore focus to wherever it used to be. - // - // *Then* this method is called. - // type: - // callback - }, + templateString: template, - onCancel: function(){ - // summary: - // Called when user has pressed the Dialog's cancel button, to notify container. - // description: - // Developer shouldn't override or connect to this method; - // it's a private communication device between the TooltipDialog - // and the thing that opened it (ex: `dijit.form.DropDownButton`) - // type: - // protected - }, + baseClass: "dijitMenu", - onExecute: function(){ - // summary: - // Called when user has pressed the dialog's OK button, to notify container. - // description: - // Developer shouldn't override or connect to this method; - // it's a private communication device between the TooltipDialog - // and the thing that opened it (ex: `dijit.form.DropDownButton`) - // type: - // protected - }, - - _onSubmit: function(){ - // summary: - // Callback when user hits submit button - // type: - // protected - this.onExecute(); // notify container that we are about to execute - this.execute(this.get('value')); + postCreate: function(){ + var l = this.isLeftToRight(); + this._openSubMenuKey = l ? keys.RIGHT_ARROW : keys.LEFT_ARROW; + this._closeSubMenuKey = l ? keys.LEFT_ARROW : keys.RIGHT_ARROW; + this.connectKeyNavHandlers([keys.UP_ARROW], [keys.DOWN_ARROW]); }, - _getFocusItems: function(){ + _onKeyPress: function(/*Event*/ evt){ // summary: - // Finds focusable items in dialog, - // and sets this._firstFocusItem and this._lastFocusItem + // Handle keyboard based menu navigation. // tags: // protected - var elems = dijit._getTabNavigable(this.containerNode); - this._firstFocusItem = elems.lowest || elems.first || this.closeButtonNode || this.domNode; - this._lastFocusItem = elems.last || elems.highest || this._firstFocusItem; - } - } -); - -} - -if(!dojo._hasResource["dijit.DialogUnderlay"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.DialogUnderlay"] = true; -dojo.provide("dijit.DialogUnderlay"); - - - - - -dojo.declare( - "dijit.DialogUnderlay", - [dijit._Widget, dijit._Templated], - { - // summary: - // The component that blocks the screen behind a `dijit.Dialog` - // - // description: - // A component used to block input behind a `dijit.Dialog`. Only a single - // instance of this widget is created by `dijit.Dialog`, and saved as - // a reference to be shared between all Dialogs as `dijit._underlay` - // - // The underlay itself can be styled based on and id: - // | #myDialog_underlay { background-color:red; } - // - // In the case of `dijit.Dialog`, this id is based on the id of the Dialog, - // suffixed with _underlay. - - // Template has two divs; outer div is used for fade-in/fade-out, and also to hold background iframe. - // Inner div has opacity specified in CSS file. - templateString: "<div class='dijitDialogUnderlayWrapper'><div class='dijitDialogUnderlay' dojoAttachPoint='node'></div></div>", + if(evt.ctrlKey || evt.altKey){ return; } - // Parameters on creation or updatable later + switch(evt.charOrCode){ + case this._openSubMenuKey: + this._moveToPopup(evt); + event.stop(evt); + break; + case this._closeSubMenuKey: + if(this.parentMenu){ + if(this.parentMenu._isMenuBar){ + this.parentMenu.focusPrev(); + }else{ + this.onCancel(false); + } + }else{ + event.stop(evt); + } + break; + } + } + }); +}); - // dialogId: String - // Id of the dialog.... DialogUnderlay's id is based on this id - dialogId: "", +}, +'dojo/data/util/simpleFetch':function(){ +define("dojo/data/util/simpleFetch", ["dojo/_base/lang", "dojo/_base/window", "./sorter"], + function(lang, winUtil, sorter) { + // module: + // dojo/data/util/simpleFetch + // summary: + // TODOC - // class: String - // This class name is used on the DialogUnderlay node, in addition to dijitDialogUnderlay - "class": "", +var simpleFetch = lang.getObject("dojo.data.util.simpleFetch", true); - attributeMap: { id: "domNode" }, +simpleFetch.fetch = function(/* Object? */ request){ + // summary: + // The simpleFetch mixin is designed to serve as a set of function(s) that can + // be mixed into other datastore implementations to accelerate their development. + // The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems() + // call by returning an array of all the found items that matched the query. The simpleFetch mixin + // is not designed to work for datastores that respond to a fetch() call by incrementally + // loading items, or sequentially loading partial batches of the result + // set. For datastores that mixin simpleFetch, simpleFetch + // implements a fetch method that automatically handles eight of the fetch() + // arguments -- onBegin, onItem, onComplete, onError, start, count, sort and scope + // The class mixing in simpleFetch should not implement fetch(), + // but should instead implement a _fetchItems() method. The _fetchItems() + // method takes three arguments, the keywordArgs object that was passed + // to fetch(), a callback function to be called when the result array is + // available, and an error callback to be called if something goes wrong. + // The _fetchItems() method should ignore any keywordArgs parameters for + // start, count, onBegin, onItem, onComplete, onError, sort, and scope. + // The _fetchItems() method needs to correctly handle any other keywordArgs + // parameters, including the query parameter and any optional parameters + // (such as includeChildren). The _fetchItems() method should create an array of + // result items and pass it to the fetchHandler along with the original request object + // -- or, the _fetchItems() method may, if it wants to, create an new request object + // with other specifics about the request that are specific to the datastore and pass + // that as the request object to the handler. + // + // For more information on this specific function, see dojo.data.api.Read.fetch() + request = request || {}; + if(!request.store){ + request.store = this; + } + var self = this; - _setDialogIdAttr: function(id){ - dojo.attr(this.node, "id", id + "_underlay"); - this._set("dialogId", id); - }, + var _errorHandler = function(errorData, requestObject){ + if(requestObject.onError){ + var scope = requestObject.scope || winUtil.global; + requestObject.onError.call(scope, errorData, requestObject); + } + }; - _setClassAttr: function(clazz){ - this.node.className = "dijitDialogUnderlay " + clazz; - this._set("class", clazz); - }, + var _fetchHandler = function(items, requestObject){ + var oldAbortFunction = requestObject.abort || null; + var aborted = false; - postCreate: function(){ - // summary: - // Append the underlay to the body - dojo.body().appendChild(this.domNode); - }, + var startIndex = requestObject.start?requestObject.start:0; + var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length; - layout: function(){ - // summary: - // Sets the background to the size of the viewport - // - // description: - // Sets the background to the size of the viewport (rather than the size - // of the document) since we need to cover the whole browser window, even - // if the document is only a few lines long. - // tags: - // private + requestObject.abort = function(){ + aborted = true; + if(oldAbortFunction){ + oldAbortFunction.call(requestObject); + } + }; - var is = this.node.style, - os = this.domNode.style; + var scope = requestObject.scope || winUtil.global; + if(!requestObject.store){ + requestObject.store = self; + } + if(requestObject.onBegin){ + requestObject.onBegin.call(scope, items.length, requestObject); + } + if(requestObject.sort){ + items.sort(sorter.createSortFunction(requestObject.sort, self)); + } + if(requestObject.onItem){ + for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){ + var item = items[i]; + if(!aborted){ + requestObject.onItem.call(scope, item, requestObject); + } + } + } + if(requestObject.onComplete && !aborted){ + var subset = null; + if(!requestObject.onItem){ + subset = items.slice(startIndex, endIndex); + } + requestObject.onComplete.call(scope, subset, requestObject); + } + }; + this._fetchItems(request, _fetchHandler, _errorHandler); + return request; // Object +}; - // hide the background temporarily, so that the background itself isn't - // causing scrollbars to appear (might happen when user shrinks browser - // window and then we are called to resize) - os.display = "none"; +return simpleFetch; +}); - // then resize and show - var viewport = dojo.window.getBox(); - os.top = viewport.t + "px"; - os.left = viewport.l + "px"; - is.width = viewport.w + "px"; - is.height = viewport.h + "px"; - os.display = "block"; - }, +}, +'dijit/Menu':function(){ +define("dijit/Menu", [ + "require", + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/dom", // dom.byId dom.isDescendant + "dojo/dom-attr", // domAttr.get domAttr.set domAttr.has domAttr.remove + "dojo/dom-geometry", // domStyle.getComputedStyle domGeometry.position + "dojo/dom-style", // domStyle.getComputedStyle + "dojo/_base/kernel", + "dojo/keys", // keys.F10 + "dojo/_base/lang", // lang.hitch + "dojo/on", + "dojo/_base/sniff", // has("ie"), has("quirks") + "dojo/_base/window", // win.body win.doc.documentElement win.doc.frames win.withGlobal + "dojo/window", // winUtils.get + "./popup", + "./DropDownMenu", + "dojo/ready" +], function(require, array, declare, event, dom, domAttr, domGeometry, domStyle, kernel, keys, lang, on, + has, win, winUtils, pm, DropDownMenu, ready){ - show: function(){ - // summary: - // Show the dialog underlay - this.domNode.style.display = "block"; - this.layout(); - this.bgIframe = new dijit.BackgroundIframe(this.domNode); - }, +/*===== + var DropDownMenu = dijit.DropDownMenu; +=====*/ - hide: function(){ - // summary: - // Hides the dialog underlay - this.bgIframe.destroy(); - delete this.bgIframe; - this.domNode.style.display = "none"; - } - } -); +// module: +// dijit/Menu +// summary: +// Includes dijit.Menu widget and base class dijit._MenuBase +// Back compat w/1.6, remove for 2.0 +if(!kernel.isAsync){ + ready(0, function(){ + var requires = ["dijit/MenuItem", "dijit/PopupMenuItem", "dijit/CheckedMenuItem", "dijit/MenuSeparator"]; + require(requires); // use indirection so modules not rolled into a build + }); } -if(!dojo._hasResource["dijit.layout._ContentPaneResizeMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout._ContentPaneResizeMixin"] = true; -dojo.provide("dijit.layout._ContentPaneResizeMixin"); - +return declare("dijit.Menu", DropDownMenu, { + // summary: + // A context menu you can assign to multiple elements + constructor: function(){ + this._bindings = []; + }, + // targetNodeIds: [const] String[] + // Array of dom node ids of nodes to attach to. + // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes. + targetNodeIds: [], -dojo.declare("dijit.layout._ContentPaneResizeMixin", null, { - // summary: - // Resize() functionality of ContentPane. If there's a single layout widget - // child then it will call resize() with the same dimensions as the ContentPane. - // Otherwise just calls resize on each child. - // - // Also implements basic startup() functionality, where starting the parent - // will start the children + // contextMenuForWindow: [const] Boolean + // If true, right clicking anywhere on the window will cause this context menu to open. + // If false, must specify targetNodeIds. + contextMenuForWindow: false, - // doLayout: Boolean - // - false - don't adjust size of children - // - true - if there is a single visible child widget, set it's size to - // however big the ContentPane is - doLayout: true, + // leftClickToOpen: [const] Boolean + // If true, menu will open on left click instead of right click, similar to a file menu. + leftClickToOpen: false, - // isContainer: [protected] Boolean - // Indicates that this widget acts as a "parent" to the descendant widgets. - // When the parent is started it will call startup() on the child widgets. - // See also `isLayoutContainer`. - isContainer: true, + // refocus: Boolean + // When this menu closes, re-focus the element which had focus before it was opened. + refocus: true, - // isLayoutContainer: [protected] Boolean - // Indicates that this widget will call resize() on it's child widgets - // when they become visible. - isLayoutContainer: true, + postCreate: function(){ + if(this.contextMenuForWindow){ + this.bindDomNode(win.body()); + }else{ + // TODO: should have _setTargetNodeIds() method to handle initialization and a possible + // later set('targetNodeIds', ...) call. There's also a problem that targetNodeIds[] + // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610) + array.forEach(this.targetNodeIds, this.bindDomNode, this); + } + this.inherited(arguments); + }, - _startChildren: function(){ + // thanks burstlib! + _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){ // summary: - // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects - - // This starts all the widgets - dojo.forEach(this.getChildren(), function(child){ - child.startup(); - child._started = true; - }); + // Returns the window reference of the passed iframe + // tags: + // private + return winUtils.get(this._iframeContentDocument(iframe_el)) || + // Moz. TODO: is this available when defaultView isn't? + this._iframeContentDocument(iframe_el)['__parent__'] || + (iframe_el.name && win.doc.frames[iframe_el.name]) || null; // Window }, - startup: function(){ + _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){ // summary: - // See `dijit.layout._LayoutWidget.startup` for description. - // Although ContentPane doesn't extend _LayoutWidget, it does implement - // the same API. + // Returns a reference to the document object inside iframe_el + // tags: + // protected + return iframe_el.contentDocument // W3 + || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE + || (iframe_el.name && win.doc.frames[iframe_el.name] && win.doc.frames[iframe_el.name].document) + || null; // HTMLDocument + }, - if(this._started){ return; } + bindDomNode: function(/*String|DomNode*/ node){ + // summary: + // Attach menu to given node + node = dom.byId(node); - var parent = dijit._Contained.prototype.getParent.call(this); - this._childOfLayoutWidget = parent && parent.isLayoutContainer; + var cn; // Connect node - // I need to call resize() on my child/children (when I become visible), unless - // I'm the child of a layout widget in which case my parent will call resize() on me and I'll do it then. - this._needLayout = !this._childOfLayoutWidget; + // Support context menus on iframes. Rather than binding to the iframe itself we need + // to bind to the <body> node inside the iframe. + if(node.tagName.toLowerCase() == "iframe"){ + var iframe = node, + window = this._iframeContentWindow(iframe); + cn = win.withGlobal(window, win.body); + }else{ - this.inherited(arguments); + // To capture these events at the top level, attach to <html>, not <body>. + // Otherwise right-click context menu just doesn't work. + cn = (node == win.body() ? win.doc.documentElement : node); + } - this._startChildren(); - if(this._isShown()){ - this._onShow(); - } + // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode()) + var binding = { + node: node, + iframe: iframe + }; - if(!this._childOfLayoutWidget){ - // If my parent isn't a layout container, since my style *may be* width=height=100% - // or something similar (either set directly or via a CSS class), - // monitor when my size changes so that I can re-layout. - // For browsers where I can't directly monitor when my size changes, - // monitor when the viewport changes size, which *may* indicate a size change for me. - this.connect(dojo.isIE ? this.domNode : dojo.global, 'onresize', function(){ - // Using function(){} closure to ensure no arguments to resize. - this._needLayout = !this._childOfLayoutWidget; - this.resize(); - }); - } - }, + // Save info about binding in _bindings[], and make node itself record index(+1) into + // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may + // start with a number, which fails on FF/safari. + domAttr.set(node, "_dijitMenu" + this.id, this._bindings.push(binding)); - _checkIfSingleChild: function(){ - // summary: - // Test if we have exactly one visible widget as a child, - // and if so assume that we are a container for that widget, - // and should propagate startup() and resize() calls to it. - // Skips over things like data stores since they aren't visible. + // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished + // loading yet, in which case we need to wait for the onload event first, and then connect + // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so + // we need to monitor keyboard events in addition to the oncontextmenu event. + var doConnects = lang.hitch(this, function(cn){ + return [ + // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu, + // rather than shift-F10? + on(cn, this.leftClickToOpen ? "click" : "contextmenu", lang.hitch(this, function(evt){ + // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler + event.stop(evt); + this._scheduleOpen(evt.target, iframe, {x: evt.pageX, y: evt.pageY}); + })), + on(cn, "keydown", lang.hitch(this, function(evt){ + if(evt.shiftKey && evt.keyCode == keys.F10){ + event.stop(evt); + this._scheduleOpen(evt.target, iframe); // no coords - open near target node + } + })) + ]; + }); + binding.connects = cn ? doConnects(cn) : []; - var childNodes = dojo.query("> *", this.containerNode).filter(function(node){ - return node.tagName !== "SCRIPT"; // or a regexp for hidden elements like script|area|map|etc.. - }), - childWidgetNodes = childNodes.filter(function(node){ - return dojo.hasAttr(node, "data-dojo-type") || dojo.hasAttr(node, "dojoType") || dojo.hasAttr(node, "widgetId"); - }), - candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){ - return widget && widget.domNode && widget.resize; - }); + if(iframe){ + // Setup handler to [re]bind to the iframe when the contents are initially loaded, + // and every time the contents change. + // Need to do this b/c we are actually binding to the iframe's <body> node. + // Note: can't use connect.connect(), see #9609. - if( - // all child nodes are widgets - childNodes.length == childWidgetNodes.length && + binding.onloadHandler = lang.hitch(this, function(){ + // want to remove old connections, but IE throws exceptions when trying to + // access the <body> node because it's already gone, or at least in a state of limbo - // all but one are invisible (like dojo.data) - candidateWidgets.length == 1 - ){ - this._singleChild = candidateWidgets[0]; - }else{ - delete this._singleChild; + var window = this._iframeContentWindow(iframe); + cn = win.withGlobal(window, win.body); + binding.connects = doConnects(cn); + }); + if(iframe.addEventListener){ + iframe.addEventListener("load", binding.onloadHandler, false); + }else{ + iframe.attachEvent("onload", binding.onloadHandler); + } } - - // So we can set overflow: hidden to avoid a safari bug w/scrollbars showing up (#9449) - dojo.toggleClass(this.containerNode, this.baseClass + "SingleChild", !!this._singleChild); }, - resize: function(changeSize, resultSize){ + unBindDomNode: function(/*String|DomNode*/ nodeName){ // summary: - // See `dijit.layout._LayoutWidget.resize` for description. - // Although ContentPane doesn't extend _LayoutWidget, it does implement - // the same API. + // Detach menu from given node - // For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is - // never called, so resize() is our trigger to do the initial href download (see [20099]). - // However, don't load href for closed TitlePanes. - if(!this._wasShown && this.open !== false){ - this._onShow(); + var node; + try{ + node = dom.byId(nodeName); + }catch(e){ + // On IE the dom.byId() call will get an exception if the attach point was + // the <body> node of an <iframe> that has since been reloaded (and thus the + // <body> node is in a limbo state of destruction. + return; } - this._resizeCalled = true; + // node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array + var attrName = "_dijitMenu" + this.id; + if(node && domAttr.has(node, attrName)){ + var bid = domAttr.get(node, attrName)-1, b = this._bindings[bid], h; + while(h = b.connects.pop()){ + h.remove(); + } - this._scheduleLayout(changeSize, resultSize); - }, + // Remove listener for iframe onload events + var iframe = b.iframe; + if(iframe){ + if(iframe.removeEventListener){ + iframe.removeEventListener("load", b.onloadHandler, false); + }else{ + iframe.detachEvent("onload", b.onloadHandler); + } + } - _scheduleLayout: function(changeSize, resultSize){ - // summary: - // Resize myself, and call resize() on each of my child layout widgets, either now - // (if I'm currently visible) or when I become visible - if(this._isShown()){ - this._layout(changeSize, resultSize); - }else{ - this._needLayout = true; - this._changeSize = changeSize; - this._resultSize = resultSize; + domAttr.remove(node, attrName); + delete this._bindings[bid]; } }, - _layout: function(changeSize, resultSize){ + _scheduleOpen: function(/*DomNode?*/ target, /*DomNode?*/ iframe, /*Object?*/ coords){ // summary: - // Resize myself according to optional changeSize/resultSize parameters, like a layout widget. - // Also, since I am a Container widget, each of my children expects me to - // call resize() or layout() on them. + // Set timer to display myself. Using a timer rather than displaying immediately solves + // two problems: // - // Should be called on initialization and also whenever we get new content - // (from an href, or from set('content', ...))... but deferred until - // the ContentPane is visible - - // Set margin box size, unless it wasn't specified, in which case use current size. - if(changeSize){ - dojo.marginBox(this.domNode, changeSize); - } + // 1. IE: without the delay, focus work in "open" causes the system + // context menu to appear in spite of stopEvent. + // + // 2. Avoid double-shows on linux, where shift-F10 generates an oncontextmenu event + // even after a event.stop(e). (Shift-F10 on windows doesn't generate the + // oncontextmenu event.) - // Compute content box size of containerNode in case we [later] need to size our single child. - var cn = this.containerNode; - if(cn === this.domNode){ - // If changeSize or resultSize was passed to this method and this.containerNode == - // this.domNode then we can compute the content-box size without querying the node, - // which is more reliable (similar to LayoutWidget.resize) (see for example #9449). - var mb = resultSize || {}; - dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize - if(!("h" in mb) || !("w" in mb)){ - mb = dojo.mixin(dojo.marginBox(cn), mb); // just use dojo.marginBox() to fill in missing values - } - this._contentBox = dijit.layout.marginBox2contentBox(cn, mb); - }else{ - this._contentBox = dojo.contentBox(cn); + if(!this._openTimer){ + this._openTimer = setTimeout(lang.hitch(this, function(){ + delete this._openTimer; + this._openMyself({ + target: target, + iframe: iframe, + coords: coords + }); + }), 1); } - - this._layoutChildren(); - - delete this._needLayout; }, - - _layoutChildren: function(){ - // Call _checkIfSingleChild() again in case app has manually mucked w/the content - // of the ContentPane (rather than changing it through the set("content", ...) API. - if(this.doLayout){ - this._checkIfSingleChild(); - } - if(this._singleChild && this._singleChild.resize){ - var cb = this._contentBox || dojo.contentBox(this.containerNode); + _openMyself: function(args){ + // summary: + // Internal function for opening myself when the user does a right-click or something similar. + // args: + // This is an Object containing: + // * target: + // The node that is being clicked + // * iframe: + // If an <iframe> is being clicked, iframe points to that iframe + // * coords: + // Put menu at specified x/y position in viewport, or if iframe is + // specified, then relative to iframe. + // + // _openMyself() formerly took the event object, and since various code references + // evt.target (after connecting to _openMyself()), using an Object for parameters + // (so that old code still works). - // note: if widget has padding this._contentBox will have l and t set, - // but don't pass them to resize() or it will doubly-offset the child - this._singleChild.resize({w: cb.w, h: cb.h}); - }else{ - // All my child widgets are independently sized (rather than matching my size), - // but I still need to call resize() on each child to make it layout. - dojo.forEach(this.getChildren(), function(widget){ - if(widget.resize){ - widget.resize(); - } - }); - } - }, + var target = args.target, + iframe = args.iframe, + coords = args.coords; - _isShown: function(){ - // summary: - // Returns true if the content is currently shown. - // description: - // If I am a child of a layout widget then it actually returns true if I've ever been visible, - // not whether I'm currently visible, since that's much faster than tracing up the DOM/widget - // tree every call, and at least solves the performance problem on page load by deferring loading - // hidden ContentPanes until they are first shown + // Get coordinates to open menu, either at specified (mouse) position or (if triggered via keyboard) + // then near the node the menu is assigned to. + if(coords){ + if(iframe){ + // Specified coordinates are on <body> node of an <iframe>, convert to match main document + var ifc = domGeometry.position(iframe, true), + window = this._iframeContentWindow(iframe), + scroll = win.withGlobal(window, "_docScroll", dojo); - if(this._childOfLayoutWidget){ - // If we are TitlePane, etc - we return that only *IF* we've been resized - if(this._resizeCalled && "open" in this){ - return this.open; + var cs = domStyle.getComputedStyle(iframe), + tp = domStyle.toPixelValue, + left = (has("ie") && has("quirks") ? 0 : tp(iframe, cs.paddingLeft)) + (has("ie") && has("quirks") ? tp(iframe, cs.borderLeftWidth) : 0), + top = (has("ie") && has("quirks") ? 0 : tp(iframe, cs.paddingTop)) + (has("ie") && has("quirks") ? tp(iframe, cs.borderTopWidth) : 0); + + coords.x += ifc.x + left - scroll.x; + coords.y += ifc.y + top - scroll.y; } - return this._resizeCalled; - }else if("open" in this){ - return this.open; // for TitlePane, etc. }else{ - var node = this.domNode, parent = this.domNode.parentNode; - return (node.style.display != 'none') && (node.style.visibility != 'hidden') && !dojo.hasClass(node, "dijitHidden") && - parent && parent.style && (parent.style.display != 'none'); + coords = domGeometry.position(target, true); + coords.x += 10; + coords.y += 10; } - }, - _onShow: function(){ - // summary: - // Called when the ContentPane is made visible - // description: - // For a plain ContentPane, this is called on initialization, from startup(). - // If the ContentPane is a hidden pane of a TabContainer etc., then it's - // called whenever the pane is made visible. - // - // Does layout/resize of child widget(s) + var self=this; + var prevFocusNode = this._focusManager.get("prevNode"); + var curFocusNode = this._focusManager.get("curNode"); + var savedFocusNode = !curFocusNode || (dom.isDescendant(curFocusNode, this.domNode)) ? prevFocusNode : curFocusNode; - if(this._needLayout){ - // If a layout has been scheduled for when we become visible, do it now - this._layout(this._changeSize, this._resultSize); + function closeAndRestoreFocus(){ + // user has clicked on a menu or popup + if(self.refocus && savedFocusNode){ + savedFocusNode.focus(); + } + pm.close(self); } + pm.open({ + popup: this, + x: coords.x, + y: coords.y, + onExecute: closeAndRestoreFocus, + onCancel: closeAndRestoreFocus, + orient: this.isLeftToRight() ? 'L' : 'R' + }); + this.focus(); - this.inherited(arguments); + this._onBlur = function(){ + this.inherited('_onBlur', arguments); + // Usually the parent closes the child widget but if this is a context + // menu then there is no parent + pm.close(this); + // don't try to restore focus; user has clicked another part of the screen + // and set focus there + }; + }, - // Need to keep track of whether ContentPane has been shown (which is different than - // whether or not it's currently visible). - this._wasShown = true; + uninitialize: function(){ + array.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this); + this.inherited(arguments); } }); -} - -if(!dojo._hasResource["dojo.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.html"] = true; -dojo.provide("dojo.html"); - - -dojo.getObject("html", true, dojo); +}); -// the parser might be needed.. -(function(){ // private scope, sort of a namespace +}, +'dijit/form/_CheckBoxMixin':function(){ +define("dijit/form/_CheckBoxMixin", [ + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/_base/event" // event.stop +], function(declare, domAttr, event){ + + // module: + // dijit/form/_CheckBoxMixin + // summary: + // Mixin to provide widget functionality corresponding to an HTML checkbox - // idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes - var idCounter = 0, - d = dojo; - - dojo.html._secureForInnerHtml = function(/*String*/ cont){ + return declare("dijit.form._CheckBoxMixin", null, { // summary: - // removes !DOCTYPE and title elements from the html string. + // Mixin to provide widget functionality corresponding to an HTML checkbox // - // khtml is picky about dom faults, you can't attach a style or <title> node as child of body - // must go into head, so we need to cut out those tags - // cont: - // An html string for insertion into the dom + // description: + // User interacts with real html inputs. + // On onclick (which occurs by mouse click, space-bar, or + // using the arrow keys to switch the selected radio button), + // we update the state of the checkbox/radio. // - return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String - }; - -/*==== - dojo.html._emptyNode = function(node){ - // summary: - // removes all child nodes from the given node - // node: DOMNode - // the parent element - }; -=====*/ - dojo.html._emptyNode = dojo.empty; - - dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){ - // summary: - // inserts the given content into the given node - // node: - // the parent element - // content: - // the content to be set on the parent element. - // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes - - // always empty - d.empty(node); - - if(cont) { - if(typeof cont == "string") { - cont = d._toDom(cont, node.ownerDocument); - } - if(!cont.nodeType && d.isArrayLike(cont)) { - // handle as enumerable, but it may shrink as we enumerate it - for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0) { - d.place( cont[i], node, "last"); - } - } else { - // pass nodes, documentFragments and unknowns through to dojo.place - d.place(cont, node, "last"); - } - } - - // return DomNode - return node; - }; - - // we wrap up the content-setting operation in a object - dojo.declare("dojo.html._ContentSetter", null, - { - // node: DomNode|String - // An node which will be the parent element that we set content into - node: "", - - // content: String|DomNode|DomNode[] - // The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes - content: "", - - // id: String? - // Usually only used internally, and auto-generated with each instance - id: "", - - // cleanContent: Boolean - // Should the content be treated as a full html document, - // and the real content stripped of <html>, <body> wrapper before injection - cleanContent: false, - - // extractContent: Boolean - // Should the content be treated as a full html document, and the real content stripped of <html>, <body> wrapper before injection - extractContent: false, - - // parseContent: Boolean - // Should the node by passed to the parser after the new content is set - parseContent: false, - - // parserScope: String - // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo, - // will search for data-dojo-type (or dojoType). For backwards compatibility - // reasons defaults to dojo._scopeName (which is "dojo" except when - // multi-version support is used, when it will be something like dojo16, dojo20, etc.) - parserScope: dojo._scopeName, - // startup: Boolean - // Start the child widgets after parsing them. Only obeyed if parseContent is true. - startup: true, - - // lifecyle methods - constructor: function(/* Object */params, /* String|DomNode */node){ - // summary: - // Provides a configurable, extensible object to wrap the setting on content on a node - // call the set() method to actually set the content.. - - // the original params are mixed directly into the instance "this" - dojo.mixin(this, params || {}); - - // give precedence to params.node vs. the node argument - // and ensure its a node, not an id string - node = this.node = dojo.byId( this.node || node ); - - if(!this.id){ - this.id = [ - "Setter", - (node) ? node.id || node.tagName : "", - idCounter++ - ].join("_"); - } - }, - set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){ - // summary: - // front-end to the set-content sequence - // cont: - // An html string, node or enumerable list of nodes for insertion into the dom - // If not provided, the object's content property will be used - if(undefined !== cont){ - this.content = cont; - } - // in the re-use scenario, set needs to be able to mixin new configuration - if(params){ - this._mixin(params); - } - - this.onBegin(); - this.setContent(); - this.onEnd(); + // type: [private] String + // type attribute on <input> node. + // Overrides `dijit.form.Button.type`. Users should not change this value. + type: "checkbox", - return this.node; - }, - setContent: function(){ - // summary: - // sets the content on the node + // value: String + // As an initialization parameter, equivalent to value field on normal checkbox + // (if checked, the value is passed as the value when form is submitted). + value: "on", - var node = this.node; - if(!node) { - // can't proceed - throw new Error(this.declaredClass + ": setContent given no node"); - } - try{ - node = dojo.html._setNodeContent(node, this.content); - }catch(e){ - // check if a domfault occurs when we are appending this.errorMessage - // like for instance if domNode is a UL and we try append a DIV - - // FIXME: need to allow the user to provide a content error message string - var errMess = this.onContentError(e); - try{ - node.innerHTML = errMess; - }catch(e){ - console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e); - } - } - // always put back the node for the next method - this.node = node; // DomNode - }, - - empty: function() { - // summary - // cleanly empty out existing content + // readOnly: Boolean + // Should this widget respond to user input? + // In markup, this is specified as "readOnly". + // Similar to disabled except readOnly form values are submitted. + readOnly: false, + + // aria-pressed for toggle buttons, and aria-checked for checkboxes + _aria_attr: "aria-checked", - // destroy any widgets from a previous run - // NOTE: if you dont want this you'll need to empty - // the parseResults array property yourself to avoid bad things happenning - if(this.parseResults && this.parseResults.length) { - dojo.forEach(this.parseResults, function(w) { - if(w.destroy){ - w.destroy(); - } - }); - delete this.parseResults; - } - // this is fast, but if you know its already empty or safe, you could - // override empty to skip this step - dojo.html._emptyNode(this.node); - }, - - onBegin: function(){ - // summary - // Called after instantiation, but before set(); - // It allows modification of any of the object properties - // - including the node and content provided - before the set operation actually takes place - // This default implementation checks for cleanContent and extractContent flags to - // optionally pre-process html string content - var cont = this.content; - - if(dojo.isString(cont)){ - if(this.cleanContent){ - cont = dojo.html._secureForInnerHtml(cont); - } - - if(this.extractContent){ - var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); - if(match){ cont = match[1]; } - } - } + _setReadOnlyAttr: function(/*Boolean*/ value){ + this._set("readOnly", value); + domAttr.set(this.focusNode, 'readOnly', value); + this.focusNode.setAttribute("aria-readonly", value); + }, - // clean out the node and any cruft associated with it - like widgets - this.empty(); - - this.content = cont; - return this.node; /* DomNode */ - }, - - onEnd: function(){ - // summary - // Called after set(), when the new content has been pushed into the node - // It provides an opportunity for post-processing before handing back the node to the caller - // This default implementation checks a parseContent flag to optionally run the dojo parser over the new content - if(this.parseContent){ - // populates this.parseResults if you need those.. - this._parse(); - } - return this.node; /* DomNode */ - }, - - tearDown: function(){ - // summary - // manually reset the Setter instance if its being re-used for example for another set() - // description - // tearDown() is not called automatically. - // In normal use, the Setter instance properties are simply allowed to fall out of scope - // but the tearDown method can be called to explicitly reset this instance. - delete this.parseResults; - delete this.node; - delete this.content; - }, - - onContentError: function(err){ - return "Error occured setting content: " + err; - }, - - _mixin: function(params){ - // mix properties/methods into the instance - // TODO: the intention with tearDown is to put the Setter's state - // back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params) - // so we could do something here to move the original properties aside for later restoration - var empty = {}, key; - for(key in params){ - if(key in empty){ continue; } - // TODO: here's our opportunity to mask the properties we dont consider configurable/overridable - // .. but history shows we'll almost always guess wrong - this[key] = params[key]; - } - }, - _parse: function(){ - // summary: - // runs the dojo parser over the node contents, storing any results in this.parseResults - // Any errors resulting from parsing are passed to _onError for handling + // Override dijit.form.Button._setLabelAttr() since we don't even have a containerNode. + // Normally users won't try to set label, except when CheckBox or RadioButton is the child of a dojox.layout.TabContainer + _setLabelAttr: undefined, - var rootNode = this.node; - try{ - // store the results (widgets, whatever) for potential retrieval - var inherited = {}; - dojo.forEach(["dir", "lang", "textDir"], function(name){ - if(this[name]){ - inherited[name] = this[name]; - } - }, this); - this.parseResults = dojo.parser.parse({ - rootNode: rootNode, - noStart: !this.startup, - inherited: inherited, - scope: this.parserScope - }); - }catch(e){ - this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id); - } - }, - - _onError: function(type, err, consoleText){ - // summary: - // shows user the string that is returned by on[type]Error - // overide/implement on[type]Error and return your own string to customize - var errText = this['on' + type + 'Error'].call(this, err); - if(consoleText){ - console.error(consoleText, err); - }else if(errText){ // a empty string won't change current content - dojo.html._setNodeContent(this.node, errText, true); - } + postMixInProperties: function(){ + if(this.value == ""){ + this.value = "on"; } - }); // end dojo.declare() + this.inherited(arguments); + }, - dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){ + reset: function(){ + this.inherited(arguments); + // Handle unlikely event that the <input type=checkbox> value attribute has changed + this._set("value", this.params.value || "on"); + domAttr.set(this.focusNode, 'value', this.value); + }, + + _onClick: function(/*Event*/ e){ // summary: - // inserts (replaces) the given content into the given node. dojo.place(cont, node, "only") - // may be a better choice for simple HTML insertion. - // description: - // Unless you need to use the params capabilities of this method, you should use - // dojo.place(cont, node, "only"). dojo.place() has more robust support for injecting - // an HTML string into the DOM, but it only handles inserting an HTML string as DOM - // elements, or inserting a DOM node. dojo.place does not handle NodeList insertions - // or the other capabilities as defined by the params object for this method. - // node: - // the parent element that will receive the content - // cont: - // the content to be set on the parent element. - // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes - // params: - // Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter - // example: - // A safe string/node/nodelist content replacement/injection with hooks for extension - // Example Usage: - // dojo.html.set(node, "some string"); - // dojo.html.set(node, contentNode, {options}); - // dojo.html.set(node, myNode.childNodes, {options}); - if(undefined == cont){ - console.warn("dojo.html.set: no cont argument provided, using empty string"); - cont = ""; - } - if(!params){ - // simple and fast - return dojo.html._setNodeContent(node, cont, true); - }else{ - // more options but slower - // note the arguments are reversed in order, to match the convention for instantiation via the parser - var op = new dojo.html._ContentSetter(dojo.mixin( - params, - { content: cont, node: node } - )); - return op.set(); + // Internal function to handle click actions - need to check + // readOnly, since button no longer does that check. + if(this.readOnly){ + event.stop(e); + return false; + } + return this.inherited(arguments); } - }; -})(); - -} - -if(!dojo._hasResource["dijit.layout.ContentPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout.ContentPane"] = true; -dojo.provide("dijit.layout.ContentPane"); - - + }); +}); +}, +'dijit/layout/ContentPane':function(){ +define("dijit/layout/ContentPane", [ + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/lang", // lang.mixin lang.delegate lang.hitch lang.isFunction lang.isObject + "../_Widget", + "./_ContentPaneResizeMixin", + "dojo/string", // string.substitute + "dojo/html", // html._ContentSetter html._emptyNode + "dojo/i18n!../nls/loading", + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/_base/Deferred", // Deferred + "dojo/dom", // dom.byId + "dojo/dom-attr", // domAttr.attr + "dojo/_base/window", // win.body win.doc.createDocumentFragment + "dojo/_base/xhr", // xhr.get + "dojo/i18n" // i18n.getLocalization +], function(kernel, lang, _Widget, _ContentPaneResizeMixin, string, html, nlsLoading, + array, declare, Deferred, dom, domAttr, win, xhr, i18n){ +/*===== + var _Widget = dijit._Widget; + var _ContentPaneResizeMixin = dijit.layout._ContentPaneResizeMixin; +=====*/ +// module: +// dijit/layout/ContentPane +// summary: +// A widget containing an HTML fragment, specified inline +// or by uri. Fragment may include widgets. -dojo.declare( - "dijit.layout.ContentPane", [dijit._Widget, dijit.layout._ContentPaneResizeMixin], -{ +return declare("dijit.layout.ContentPane", [_Widget, _ContentPaneResizeMixin], { // summary: // A widget containing an HTML fragment, specified inline // or by uri. Fragment may include widgets. @@ -10031,13 +19724,11 @@ dojo.declare( // Changing href after creation doesn't have any effect; Use set('href', ...); href: "", -/*===== // content: String || DomNode || NodeList || dijit._Widget // The innerHTML of the ContentPane. // Note that the initialization parameter / argument to set("content", ...) // can be a String, DomNode, Nodelist, or _Widget. content: "", -=====*/ // extractContent: Boolean // Extract visible content from inside of <body> .... </body>. @@ -10053,7 +19744,7 @@ dojo.declare( // will search for data-dojo-type (or dojoType). For backwards compatibility // reasons defaults to dojo._scopeName (which is "dojo" except when // multi-version support is used, when it will be something like dojo16, dojo20, etc.) - parserScope: dojo._scopeName, + parserScope: kernel._scopeName, // preventCache: Boolean // Prevent caching of data from href's by appending a timestamp to the href. @@ -10069,11 +19760,11 @@ dojo.declare( // loadingMessage: String // Message that shows while downloading - loadingMessage: "<span class='dijitContentPaneLoading'>${loadingState}</span>", + loadingMessage: "<span class='dijitContentPaneLoading'><span class='dijitInline dijitIconLoading'></span>${loadingState}</span>", // errorMessage: String // Message that shows if an error occurs - errorMessage: "<span class='dijitContentPaneError'>${errorState}</span>", + errorMessage: "<span class='dijitContentPaneError'><span class='dijitInline dijitIconError'></span>${errorState}</span>", // isLoaded: [readonly] Boolean // True if the ContentPane has data in it, either specified @@ -10086,9 +19777,15 @@ dojo.declare( baseClass: "dijitContentPane", + /*====== + // ioMethod: dojo.xhrGet|dojo.xhrPost + // Function that should grab the content specified via href. + ioMethod: dojo.xhrGet, + ======*/ + // ioArgs: Object // Parameters to pass to xhrGet() request, for example: - // | <div dojoType="dijit.layout.ContentPane" href="./bar" ioArgs="{timeout: 500}"> + // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="href: './bar', ioArgs: {timeout: 500}"> ioArgs: {}, // onLoadDeferred: [readonly] dojo.Deferred @@ -10101,12 +19798,10 @@ dojo.declare( // or content is loaded. onLoadDeferred: null, - // Override _Widget's attributeMap because we don't want the title attribute (used to specify + // Cancel _WidgetBase's _setTitleAttr because we don't want the title attribute (used to specify // tab labels) to be copied to ContentPane.domNode... otherwise a tooltip shows up over the // entire pane. - attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { - title: [] - }), + _setTitleAttr: null, // Flag to parser that I'll parse my contents, so it shouldn't. stopParser: true, @@ -10122,21 +19817,21 @@ dojo.declare( // processed in the same way as contents set via set("content", ...), calling the parser etc. // Avoid modifying original params object since that breaks NodeList instantiation, see #11906. if((!params || !params.template) && srcNodeRef && !("href" in params) && !("content" in params)){ - var df = dojo.doc.createDocumentFragment(); - srcNodeRef = dojo.byId(srcNodeRef) + var df = win.doc.createDocumentFragment(); + srcNodeRef = dom.byId(srcNodeRef); while(srcNodeRef.firstChild){ df.appendChild(srcNodeRef.firstChild); } - params = dojo.delegate(params, {content: df}); + params = lang.delegate(params, {content: df}); } this.inherited(arguments, [params, srcNodeRef]); }, postMixInProperties: function(){ this.inherited(arguments); - var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang); - this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages); - this.errorMessage = dojo.string.substitute(this.errorMessage, messages); + var messages = i18n.getLocalization("dijit", "loading", this.lang); + this.loadingMessage = string.substitute(this.loadingMessage, messages); + this.errorMessage = string.substitute(this.errorMessage, messages); }, buildRendering: function(){ @@ -10152,12 +19847,12 @@ dojo.declare( // over a node (TODO: remove in 2.0, no longer needed after #11490) this.domNode.title = ""; - if(!dojo.attr(this.domNode,"role")){ - dijit.setWaiRole(this.domNode, "group"); + if(!domAttr.get(this.domNode,"role")){ + this.domNode.setAttribute("role", "group"); } }, - _startChildren: function(){ + startup: function(){ // summary: // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects @@ -10166,8 +19861,8 @@ dojo.declare( // And this catches stuff like dojo.dnd.Source if(this._contentSetter){ - dojo.forEach(this._contentSetter.parseResults, function(obj){ - if(!obj._started && !obj._destroyed && dojo.isFunction(obj.startup)){ + array.forEach(this._contentSetter.parseResults, function(obj){ + if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){ obj.startup(); obj._started = true; } @@ -10178,7 +19873,7 @@ dojo.declare( setHref: function(/*String|Uri*/ href){ // summary: // Deprecated. Use set('href', ...) instead. - dojo.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.", "", "2.0"); + kernel.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.", "", "2.0"); return this.set("href", href); }, _setHrefAttr: function(/*String|Uri*/ href){ @@ -10193,8 +19888,8 @@ dojo.declare( // Cancel any in-flight requests (a set('href', ...) will cancel any in-flight set('href', ...)) this.cancel(); - this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); - this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad")); + this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel")); + this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad")); this._set("href", href); @@ -10209,13 +19904,13 @@ dojo.declare( this._hrefChanged = true; } - return this.onLoadDeferred; // dojo.Deferred + return this.onLoadDeferred; // Deferred }, setContent: function(/*String|DomNode|Nodelist*/data){ // summary: // Deprecated. Use set('content', ...) instead. - dojo.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use set('content', ...) instead.", "", "2.0"); + kernel.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use set('content', ...) instead.", "", "2.0"); this.set("content", data); }, _setContentAttr: function(/*String|DomNode|Nodelist*/data){ @@ -10237,19 +19932,19 @@ dojo.declare( // Even though user is just setting content directly, still need to define an onLoadDeferred // because the _onLoadHandler() handler is still getting called from setContent() - this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); + this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel")); if(this._created){ // For back-compat reasons, call onLoad() for set('content', ...) - // calls but not for content specified in srcNodeRef (ie: <div dojoType=ContentPane>...</div>) + // calls but not for content specified in srcNodeRef (ie: <div data-dojo-type=ContentPane>...</div>) // or as initialization parameter (ie: new ContentPane({content: ...}) - this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad")); + this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad")); } this._setContent(data || ""); this._isDownloaded = false; // mark that content is from a set('content') not a set('href') - return this.onLoadDeferred; // dojo.Deferred + return this.onLoadDeferred; // Deferred }, _getContentAttr: function(){ // summary: @@ -10319,8 +20014,8 @@ dojo.declare( // Cancel possible prior in-flight request this.cancel(); - this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); - this.onLoadDeferred.addCallback(dojo.hitch(this, "onLoad")); + this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel")); + this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad")); this._load(); return this.onLoadDeferred; // If child has an href, promise that fires when refresh is complete }, @@ -10338,11 +20033,11 @@ dojo.declare( url: this.href, handleAs: "text" }; - if(dojo.isObject(this.ioArgs)){ - dojo.mixin(getArgs, this.ioArgs); + if(lang.isObject(this.ioArgs)){ + lang.mixin(getArgs, this.ioArgs); } - var hand = (this._xhrDfd = (this.ioMethod || dojo.xhrGet)(getArgs)); + var hand = (this._xhrDfd = (this.ioMethod || xhr.get)(getArgs)); hand.addCallback(function(html){ try{ @@ -10391,7 +20086,7 @@ dojo.declare( } }, - destroyDescendants: function(){ + destroyDescendants: function(/*Boolean*/ preserveDom){ // summary: // Destroy all the widgets inside the ContentPane and empty containerNode @@ -10406,24 +20101,26 @@ dojo.declare( // For historical reasons we need to delete all widgets under this.containerNode, // even ones that the user has created manually. var setter = this._contentSetter; - dojo.forEach(this.getChildren(), function(widget){ + array.forEach(this.getChildren(), function(widget){ if(widget.destroyRecursive){ - widget.destroyRecursive(); + widget.destroyRecursive(preserveDom); } }); if(setter){ // Most of the widgets in setter.parseResults have already been destroyed, but // things like Menu that have been moved to <body> haven't yet - dojo.forEach(setter.parseResults, function(widget){ - if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == dojo.body()){ - widget.destroyRecursive(); + array.forEach(setter.parseResults, function(widget){ + if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == win.body()){ + widget.destroyRecursive(preserveDom); } }); delete setter.parseResults; } // And then clear away all the DOM nodes - dojo.html._emptyNode(this.containerNode); + if(!preserveDom){ + html._emptyNode(this.containerNode); + } // Delete any state information we have about current contents delete this._singleChild; @@ -10436,17 +20133,17 @@ dojo.declare( // first get rid of child widgets this.destroyDescendants(); - // dojo.html.set will take care of the rest of the details + // html.set will take care of the rest of the details // we provide an override for the error handling to ensure the widget gets the errors // configure the setter instance with only the relevant widget instance properties // NOTE: unless we hook into attr, or provide property setters for each property, // we need to re-configure the ContentSetter with each use var setter = this._contentSetter; - if(! (setter && setter instanceof dojo.html._ContentSetter)){ - setter = this._contentSetter = new dojo.html._ContentSetter({ + if(! (setter && setter instanceof html._ContentSetter)){ + setter = this._contentSetter = new html._ContentSetter({ node: this.containerNode, - _onError: dojo.hitch(this, this._onError), - onContentError: dojo.hitch(this, function(e){ + _onError: lang.hitch(this, this._onError), + onContentError: lang.hitch(this, function(e){ // fires if a domfault occurs when we are appending this.errorMessage // like for instance if domNode is a UL and we try append a DIV var errMess = this.onContentError(e); @@ -10458,19 +20155,20 @@ dojo.declare( })/*, _onError */ }); - }; + } - var setterParams = dojo.mixin({ + var setterParams = lang.mixin({ cleanContent: this.cleanContent, extractContent: this.extractContent, - parseContent: this.parseOnLoad, + parseContent: !cont.domNode && this.parseOnLoad, parserScope: this.parserScope, startup: false, dir: this.dir, - lang: this.lang + lang: this.lang, + textDir: this.textDir }, this._contentSetterParams || {}); - setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams ); + setter.set( (lang.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams ); // setter params must be pulled afresh from the ContentPane each time delete this._contentSetterParams; @@ -10482,8 +20180,9 @@ dojo.declare( if(!isFakeContent){ if(this._started){ // Startup each top level child widget (and they will start their children, recursively) - this._startChildren(); - + delete this._started; + this.startup(); + // Call resize() on each of my child layout widgets, // or resize() on my single child layout widget... // either now (if I'm currently visible) or when I become visible @@ -10508,7 +20207,7 @@ dojo.declare( }, // EVENT's, should be overide-able - onLoad: function(data){ + onLoad: function(/*===== data =====*/){ // summary: // Event hook, is called after everything is loaded and widgetified // tags: @@ -10534,7 +20233,7 @@ dojo.declare( return this.loadingMessage; }, - onContentError: function(/*Error*/ error){ + onContentError: function(/*Error*/ /*===== error =====*/){ // summary: // Called on DOM faults, require faults etc. in content. // @@ -10547,7 +20246,7 @@ dojo.declare( // extension }, - onDownloadError: function(/*Error*/ error){ + onDownloadError: function(/*Error*/ /*===== error =====*/){ // summary: // Called when download error occurs. // @@ -10569,2979 +20268,2798 @@ dojo.declare( } }); -} - -if(!dojo._hasResource["dijit.TooltipDialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.TooltipDialog"] = true; -dojo.provide("dijit.TooltipDialog"); - - - - - - -dojo.declare( - "dijit.TooltipDialog", - [dijit.layout.ContentPane, dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin], - { - // summary: - // Pops up a dialog that appears like a Tooltip +}); - // title: String - // Description of tooltip dialog (required for a11y) - title: "", +}, +'url:dijit/form/templates/ValidationTextBox.html':"<div class=\"dijit dijitReset dijitInline dijitLeft\"\n\tid=\"widget_${id}\" role=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" data-dojo-attach-point='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n", +'url: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\" data-dojo-attach-point='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n", +'dijit/layout/utils':function(){ +define("dijit/layout/utils", [ + "dojo/_base/array", // array.filter array.forEach + "dojo/dom-class", // domClass.add domClass.remove + "dojo/dom-geometry", // domGeometry.marginBox + "dojo/dom-style", // domStyle.getComputedStyle + "dojo/_base/lang", // lang.mixin + ".." // for exporting symbols to dijit, remove in 2.0 +], function(array, domClass, domGeometry, domStyle, lang, dijit){ + + // module: + // dijit/layout/utils + // summary: + // marginBox2contentBox() and layoutChildren() - // doLayout: [protected] Boolean - // Don't change this parameter from the default value. - // This ContentPane parameter doesn't make sense for TooltipDialog, since TooltipDialog - // is never a child of a layout container, nor can you specify the size of - // TooltipDialog in order to control the size of an inner widget. - doLayout: false, + var layout = lang.getObject("layout", true, dijit); + /*===== layout = dijit.layout =====*/ - // autofocus: Boolean - // A Toggle to modify the default focus behavior of a Dialog, which - // is to focus on the first dialog element after opening the dialog. - // False will disable autofocusing. Default: true - autofocus: true, + layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){ + // summary: + // Given the margin-box size of a node, return its content box size. + // Functions like domGeometry.contentBox() but is more reliable since it doesn't have + // to wait for the browser to compute sizes. + var cs = domStyle.getComputedStyle(node); + var me = domGeometry.getMarginExtents(node, cs); + var pb = domGeometry.getPadBorderExtents(node, cs); + return { + l: domStyle.toPixelValue(node, cs.paddingLeft), + t: domStyle.toPixelValue(node, cs.paddingTop), + w: mb.w - (me.w + pb.w), + h: mb.h - (me.h + pb.h) + }; + }; - // baseClass: [protected] String - // The root className to use for the various states of this widget - baseClass: "dijitTooltipDialog", + function capitalize(word){ + return word.substring(0,1).toUpperCase() + word.substring(1); + } - // _firstFocusItem: [private] [readonly] DomNode - // The pointer to the first focusable node in the dialog. - // Set by `dijit._DialogMixin._getFocusItems`. - _firstFocusItem: null, + function size(widget, dim){ + // size the child + var newSize = widget.resize ? widget.resize(dim) : domGeometry.setMarginBox(widget.domNode, dim); - // _lastFocusItem: [private] [readonly] DomNode - // The pointer to which node has focus prior to our dialog. - // Set by `dijit._DialogMixin._getFocusItems`. - _lastFocusItem: null, + // record child's size + if(newSize){ + // if the child returned it's new size then use that + lang.mixin(widget, newSize); + }else{ + // otherwise, call getMarginBox(), but favor our own numbers when we have them. + // the browser lies sometimes + lang.mixin(widget, domGeometry.getMarginBox(widget.domNode)); + lang.mixin(widget, dim); + } + } - 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"), + layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Widget[]*/ children, + /*String?*/ changedRegionId, /*Number?*/ changedRegionSize){ + // summary: + // Layout a bunch of child dom nodes within a parent dom node + // container: + // parent node + // dim: + // {l, t, w, h} object specifying dimensions of container into which to place children + // children: + // an array of Widgets or at least objects containing: + // * domNode: pointer to DOM node to position + // * region or layoutAlign: position to place DOM node + // * resize(): (optional) method to set size of node + // * id: (optional) Id of widgets, referenced from resize object, below. + // changedRegionId: + // If specified, the slider for the region with the specified id has been dragged, and thus + // the region's height or width should be adjusted according to changedRegionSize + // changedRegionSize: + // See changedRegionId. - _setTitleAttr: function(/*String*/ title){ - this.containerNode.title = title; - this._set("title", title) - }, + // copy dim because we are going to modify it + dim = lang.mixin({}, dim); - postCreate: function(){ - this.inherited(arguments); - this.connect(this.containerNode, "onkeypress", "_onKey"); - }, + domClass.add(container, "dijitLayoutContainer"); - orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ corner){ - // summary: - // Configure widget to be displayed in given position relative to the button. - // This is called from the dijit.popup code, and should not be called - // directly. - // tags: - // protected - var newC = "dijitTooltipAB" + (corner.charAt(1) == 'L' ? "Left" : "Right") - + " dijitTooltip" - + (corner.charAt(0) == 'T' ? "Below" : "Above"); - - dojo.replaceClass(this.domNode, newC, this._currentOrientClass || ""); - this._currentOrientClass = newC; - }, + // Move "client" elements to the end of the array for layout. a11y dictates that the author + // needs to be able to put them in the document in tab-order, but this algorithm requires that + // client be last. TODO: move these lines to LayoutContainer? Unneeded other places I think. + children = array.filter(children, function(item){ return item.region != "center" && item.layoutAlign != "client"; }) + .concat(array.filter(children, function(item){ return item.region == "center" || item.layoutAlign == "client"; })); - focus: function(){ - // summary: - // Focus on first field - this._getFocusItems(this.containerNode); - dijit.focus(this._firstFocusItem); - }, + // set positions/sizes + array.forEach(children, function(child){ + var elm = child.domNode, + pos = (child.region || child.layoutAlign); + if(!pos){ + throw new Error("No region setting for " + child.id) + } - onOpen: function(/*Object*/ pos){ - // summary: - // Called when dialog is displayed. - // This is called from the dijit.popup code, and should not be called directly. - // tags: - // protected + // set elem to upper left corner of unused space; may move it later + var elmStyle = elm.style; + elmStyle.left = dim.l+"px"; + elmStyle.top = dim.t+"px"; + elmStyle.position = "absolute"; - this.orient(this.domNode,pos.aroundCorner, pos.corner); - this._onShow(); // lazy load trigger - }, + domClass.add(elm, "dijitAlign" + capitalize(pos)); - onClose: function(){ - // summary: - // Called when dialog is hidden. - // This is called from the dijit.popup code, and should not be called directly. - // tags: - // protected - this.onHide(); - }, + // Size adjustments to make to this child widget + var sizeSetting = {}; - _onKey: function(/*Event*/ evt){ - // summary: - // Handler for keyboard events - // description: - // Keep keyboard focus in dialog; close dialog on escape key - // tags: - // private + // Check for optional size adjustment due to splitter drag (height adjustment for top/bottom align + // panes and width adjustment for left/right align panes. + if(changedRegionId && changedRegionId == child.id){ + sizeSetting[child.region == "top" || child.region == "bottom" ? "h" : "w"] = changedRegionSize; + } - var node = evt.target; - var dk = dojo.keys; - if(evt.charOrCode === dk.TAB){ - this._getFocusItems(this.containerNode); + // set size && adjust record of remaining space. + // note that setting the width of a <div> may affect its height. + if(pos == "top" || pos == "bottom"){ + sizeSetting.w = dim.w; + size(child, sizeSetting); + dim.h -= child.h; + if(pos == "top"){ + dim.t += child.h; + }else{ + elmStyle.top = dim.t + dim.h + "px"; } - var singleFocusItem = (this._firstFocusItem == this._lastFocusItem); - if(evt.charOrCode == dk.ESCAPE){ - // Use setTimeout to avoid crash on IE, see #10396. - setTimeout(dojo.hitch(this, "onCancel"), 0); - dojo.stopEvent(evt); - }else if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){ - if(!singleFocusItem){ - dijit.focus(this._lastFocusItem); // send focus to last item in dialog - } - dojo.stopEvent(evt); - }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){ - if(!singleFocusItem){ - dijit.focus(this._firstFocusItem); // send focus to first item in dialog - } - dojo.stopEvent(evt); - }else if(evt.charOrCode === dk.TAB){ - // we want the browser's default tab handling to move focus - // but we don't want the tab to propagate upwards - evt.stopPropagation(); + }else if(pos == "left" || pos == "right"){ + sizeSetting.h = dim.h; + size(child, sizeSetting); + dim.w -= child.w; + if(pos == "left"){ + dim.l += child.w; + }else{ + elmStyle.left = dim.l + dim.w + "px"; } + }else if(pos == "client" || pos == "center"){ + size(child, dim); } - } - ); - -} - -if(!dojo._hasResource["dijit.Dialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.Dialog"] = true; -dojo.provide("dijit.Dialog"); - - - + }); + }; + return { + marginBox2contentBox: layout.marginBox2contentBox, + layoutChildren: layout.layoutChildren + }; +}); +}, +'dijit/_Contained':function(){ +define("dijit/_Contained", [ + "dojo/_base/declare", // declare + "./registry" // registry.getEnclosingWidget(), registry.byNode() +], function(declare, registry){ + + // module: + // dijit/_Contained + // summary: + // Mixin for widgets that are children of a container widget + return declare("dijit._Contained", null, { + // summary: + // Mixin for widgets that are children of a container widget + // + // example: + // | // make a basic custom widget that knows about it's parents + // | declare("my.customClass",[dijit._Widget,dijit._Contained],{}); + _getSibling: function(/*String*/ which){ + // summary: + // Returns next or previous sibling + // which: + // Either "next" or "previous" + // tags: + // private + var node = this.domNode; + do{ + node = node[which+"Sibling"]; + }while(node && node.nodeType != 1); + return node && registry.byNode(node); // dijit._Widget + }, + getPreviousSibling: function(){ + // summary: + // Returns null if this is the first child of the parent, + // otherwise returns the next element sibling to the "left". + return this._getSibling("previous"); // dijit._Widget + }, + getNextSibling: function(){ + // summary: + // Returns null if this is the last child of the parent, + // otherwise returns the next element sibling to the "right". + return this._getSibling("next"); // dijit._Widget + }, + getIndexInParent: function(){ + // summary: + // Returns the index of this widget within its container parent. + // It returns -1 if the parent does not exist, or if the parent + // is not a dijit._Container + var p = this.getParent(); + if(!p || !p.getIndexOfChild){ + return -1; // int + } + return p.getIndexOfChild(this); // int + } + }); +}); -// dijit/TooltipDialog required for back-compat. TODO: remove in 2.0 +}, +'dijit/_KeyNavContainer':function(){ +define("dijit/_KeyNavContainer", [ + "dojo/_base/kernel", // kernel.deprecated + "./_Container", + "./_FocusMixin", + "dojo/_base/array", // array.forEach + "dojo/keys", // keys.END keys.HOME + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/dom-attr", // domAttr.set + "dojo/_base/lang" // lang.hitch +], function(kernel, _Container, _FocusMixin, array, keys, declare, event, domAttr, lang){ /*===== -dijit._underlay = function(kwArgs){ - // summary: - // A shared instance of a `dijit.DialogUnderlay` - // - // description: - // A shared instance of a `dijit.DialogUnderlay` created and - // used by `dijit.Dialog`, though never created until some Dialog - // or subclass thereof is shown. -}; + var _FocusMixin = dijit._FocusMixin; + var _Container = dijit._Container; =====*/ -dojo.declare( - "dijit._DialogBase", - [dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin, dijit._CssStateMixin], - { - // summary: - // A modal dialog Widget - // - // description: - // Pops up a modal dialog window, blocking access to the screen - // and also graying out the screen Dialog is extended from - // ContentPane so it supports all the same parameters (href, etc.) - // - // example: - // | <div dojoType="dijit.Dialog" href="test.html"></div> - // - // example: - // | var foo = new dijit.Dialog({ title: "test dialog", content: "test content" }; - // | dojo.body().appendChild(foo.domNode); - // | foo.startup(); - templateString: dojo.cache("dijit", "templates/Dialog.html", "<div class=\"dijitDialog\" role=\"dialog\" aria-labelledby=\"${id}_title\">\n\t<div dojoAttachPoint=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t<span dojoAttachPoint=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"></span>\n\t<span dojoAttachPoint=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" dojoAttachEvent=\"ondijitclick: onCancel\" title=\"${buttonCancel}\" role=\"button\" tabIndex=\"-1\">\n\t\t<span dojoAttachPoint=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t</span>\n\t</div>\n\t\t<div dojoAttachPoint=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n"), - - baseClass: "dijitDialog", - - cssStateNodes: { - closeButtonNode: "dijitDialogCloseIcon" - }, + // module: + // dijit/_KeyNavContainer + // summary: + // A _Container with keyboard navigation of its children. - attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { - title: [ - { node: "titleNode", type: "innerHTML" }, - { node: "titleBar", type: "attribute" } - ], - "aria-describedby":"" - }), + return declare("dijit._KeyNavContainer", [_FocusMixin, _Container], { - // open: [readonly] Boolean - // True if Dialog is currently displayed on screen. - open: false, - - // duration: Integer - // The time in milliseconds it takes the dialog to fade in and out - duration: dijit.defaultDuration, + // summary: + // A _Container with keyboard navigation of its children. + // description: + // To use this mixin, call connectKeyNavHandlers() in + // postCreate(). + // It provides normalized keyboard and focusing code for Container + // widgets. - // refocus: Boolean - // A Toggle to modify the default focus behavior of a Dialog, which - // is to re-focus the element which had focus before being opened. - // False will disable refocusing. Default: true - refocus: true, +/*===== + // focusedChild: [protected] Widget + // The currently focused child widget, or null if there isn't one + focusedChild: null, +=====*/ - // autofocus: Boolean - // A Toggle to modify the default focus behavior of a Dialog, which - // is to focus on the first dialog element after opening the dialog. - // False will disable autofocusing. Default: true - autofocus: true, + // tabIndex: Integer + // Tab index of the container; same as HTML tabIndex attribute. + // Note then when user tabs into the container, focus is immediately + // moved to the first item in the container. + tabIndex: "0", - // _firstFocusItem: [private readonly] DomNode - // The pointer to the first focusable node in the dialog. - // Set by `dijit._DialogMixin._getFocusItems`. - _firstFocusItem: null, + connectKeyNavHandlers: function(/*keys[]*/ prevKeyCodes, /*keys[]*/ nextKeyCodes){ + // summary: + // Call in postCreate() to attach the keyboard handlers + // to the container. + // preKeyCodes: keys[] + // Key codes for navigating to the previous child. + // nextKeyCodes: keys[] + // Key codes for navigating to the next child. + // tags: + // protected - // _lastFocusItem: [private readonly] DomNode - // The pointer to which node has focus prior to our dialog. - // Set by `dijit._DialogMixin._getFocusItems`. - _lastFocusItem: null, + // TODO: call this automatically from my own postCreate() - // doLayout: [protected] Boolean - // Don't change this parameter from the default value. - // This ContentPane parameter doesn't make sense for Dialog, since Dialog - // is never a child of a layout container, nor can you specify the size of - // Dialog in order to control the size of an inner widget. - doLayout: false, + var keyCodes = (this._keyNavCodes = {}); + var prev = lang.hitch(this, "focusPrev"); + var next = lang.hitch(this, "focusNext"); + array.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; }); + array.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; }); + keyCodes[keys.HOME] = lang.hitch(this, "focusFirstChild"); + keyCodes[keys.END] = lang.hitch(this, "focusLastChild"); + this.connect(this.domNode, "onkeypress", "_onContainerKeypress"); + this.connect(this.domNode, "onfocus", "_onContainerFocus"); + }, - // draggable: Boolean - // Toggles the moveable aspect of the Dialog. If true, Dialog - // can be dragged by it's title. If false it will remain centered - // in the viewport. - draggable: true, + startupKeyNavChildren: function(){ + kernel.deprecated("startupKeyNavChildren() call no longer needed", "", "2.0"); + }, - //aria-describedby: String - // Allows the user to add an aria-describedby attribute onto the dialog. The value should - // be the id of the container element of text that describes the dialog purpose (usually - // the first text in the dialog). - // <div dojoType="dijit.Dialog" aria-describedby="intro" .....> - // <div id="intro">Introductory text</div> - // <div>rest of dialog contents</div> - // </div> - "aria-describedby":"", + startup: function(){ + this.inherited(arguments); + array.forEach(this.getChildren(), lang.hitch(this, "_startupChild")); + }, - postMixInProperties: function(){ - var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); - dojo.mixin(this, _nlsResources); + addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){ this.inherited(arguments); + this._startupChild(widget); }, - postCreate: function(){ - dojo.style(this.domNode, { - display: "none", - position:"absolute" - }); - dojo.body().appendChild(this.domNode); + focus: function(){ + // summary: + // Default focus() implementation: focus the first child. + this.focusFirstChild(); + }, - this.inherited(arguments); + focusFirstChild: function(){ + // summary: + // Focus the first focusable child in the container. + // tags: + // protected + this.focusChild(this._getFirstFocusableChild()); + }, - this.connect(this, "onExecute", "hide"); - this.connect(this, "onCancel", "hide"); - this._modalconnects = []; + focusLastChild: function(){ + // summary: + // Focus the last focusable child in the container. + // tags: + // protected + this.focusChild(this._getLastFocusableChild()); }, - onLoad: function(){ + focusNext: function(){ // summary: - // Called when data has been loaded from an href. - // Unlike most other callbacks, this function can be connected to (via `dojo.connect`) - // but should *not* be overridden. + // Focus the next widget // tags: - // callback + // protected + this.focusChild(this._getNextFocusableChild(this.focusedChild, 1)); + }, - // when href is specified we need to reposition the dialog after the data is loaded - // and find the focusable elements - this._position(); - if(this.autofocus && dijit._DialogLevelManager.isTop(this)){ - this._getFocusItems(this.domNode); - dijit.focus(this._firstFocusItem); - } - this.inherited(arguments); + focusPrev: function(){ + // summary: + // Focus the last focusable node in the previous widget + // (ex: go to the ComboButton icon section rather than button section) + // tags: + // protected + this.focusChild(this._getNextFocusableChild(this.focusedChild, -1), true); }, - _endDrag: function(e){ + focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){ // summary: - // Called after dragging the Dialog. Saves the position of the dialog in the viewport. + // Focus specified child widget. + // widget: + // Reference to container's child widget + // last: + // If true and if widget has multiple focusable nodes, focus the + // last one instead of the first one // tags: - // private - if(e && e.node && e.node === this.domNode){ - this._relativePosition = dojo.position(e.node); + // protected + + if(!widget){ return; } + + if(this.focusedChild && widget !== this.focusedChild){ + this._onChildBlur(this.focusedChild); // used by _MenuBase } + widget.set("tabIndex", this.tabIndex); // for IE focus outline to appear, must set tabIndex before focs + widget.focus(last ? "end" : "start"); + this._set("focusedChild", widget); }, - _setup: function(){ + _startupChild: function(/*dijit._Widget*/ widget){ // summary: - // Stuff we need to do before showing the Dialog for the first - // time (but we defer it until right beforehand, for - // performance reasons). + // Setup for each child widget + // description: + // Sets tabIndex=-1 on each child, so that the tab key will + // leave the container rather than visiting each child. // tags: // private - var node = this.domNode; - - if(this.titleBar && this.draggable){ - this._moveable = (dojo.isIE == 6) ? - new dojo.dnd.TimedMoveable(node, { handle: this.titleBar }) : // prevent overload, see #5285 - new dojo.dnd.Moveable(node, { handle: this.titleBar, timeout: 0 }); - this._dndListener = dojo.subscribe("/dnd/move/stop",this,"_endDrag"); - }else{ - dojo.addClass(node,"dijitDialogFixed"); - } + widget.set("tabIndex", "-1"); - this.underlayAttrs = { - dialogId: this.id, - "class": dojo.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" ") - }; + this.connect(widget, "_onFocus", function(){ + // Set valid tabIndex so tabbing away from widget goes to right place, see #10272 + widget.set("tabIndex", this.tabIndex); + }); + this.connect(widget, "_onBlur", function(){ + widget.set("tabIndex", "-1"); + }); }, - _size: function(){ + _onContainerFocus: function(evt){ // summary: - // If necessary, shrink dialog contents so dialog fits in viewport + // Handler for when the container gets focus + // description: + // Initially the container itself has a tabIndex, but when it gets + // focus, switch focus to first child... // tags: // private - this._checkIfSingleChild(); + // Note that we can't use _onFocus() because switching focus from the + // _onFocus() handler confuses the focus.js code + // (because it causes _onFocusNode() to be called recursively) + // Also, _onFocus() would fire when focus went directly to a child widget due to mouse click. - // If we resized the dialog contents earlier, reset them back to original size, so - // that if the user later increases the viewport size, the dialog can display w/out a scrollbar. - // Need to do this before the dojo.marginBox(this.domNode) call below. - if(this._singleChild){ - if(this._singleChildOriginalStyle){ - this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle; - } - delete this._singleChildOriginalStyle; - }else{ - dojo.style(this.containerNode, { - width:"auto", - height:"auto" - }); - } + // Ignore spurious focus events: + // 1. focus on a child widget bubbles on FF + // 2. on IE, clicking the scrollbar of a select dropdown moves focus from the focused child item to me + if(evt.target !== this.domNode || this.focusedChild){ return; } - var mb = dojo._getMarginSize(this.domNode); - var viewport = dojo.window.getBox(); - if(mb.w >= viewport.w || mb.h >= viewport.h){ - // Reduce size of dialog contents so that dialog fits in viewport + this.focusFirstChild(); - var w = Math.min(mb.w, Math.floor(viewport.w * 0.75)), - h = Math.min(mb.h, Math.floor(viewport.h * 0.75)); + // and then set the container's tabIndex to -1, + // (don't remove as that breaks Safari 4) + // so that tab or shift-tab will go to the fields after/before + // the container, rather than the container itself + domAttr.set(this.domNode, "tabIndex", "-1"); + }, - if(this._singleChild && this._singleChild.resize){ - this._singleChildOriginalStyle = this._singleChild.domNode.style.cssText; - this._singleChild.resize({w: w, h: h}); - }else{ - dojo.style(this.containerNode, { - width: w + "px", - height: h + "px", - overflow: "auto", - position: "relative" // workaround IE bug moving scrollbar or dragging dialog - }); - } - }else{ - if(this._singleChild && this._singleChild.resize){ - this._singleChild.resize(); - } + _onBlur: function(evt){ + // When focus is moved away the container, and its descendant (popup) widgets, + // then restore the container's tabIndex so that user can tab to it again. + // Note that using _onBlur() so that this doesn't happen when focus is shifted + // to one of my child widgets (typically a popup) + if(this.tabIndex){ + domAttr.set(this.domNode, "tabIndex", this.tabIndex); } + this.focusedChild = null; + this.inherited(arguments); }, - _position: function(){ + _onContainerKeypress: function(evt){ // summary: - // Position modal dialog in the viewport. If no relative offset - // in the viewport has been determined (by dragging, for instance), - // center the node. Otherwise, use the Dialog's stored relative offset, - // and position the node to top: left: values based on the viewport. + // When a key is pressed, if it's an arrow key etc. then + // it's handled here. // tags: // private - if(!dojo.hasClass(dojo.body(),"dojoMove")){ - var node = this.domNode, - viewport = dojo.window.getBox(), - p = this._relativePosition, - bb = p ? null : dojo._getBorderBox(node), - l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)), - t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2)) - ; - dojo.style(node,{ - left: l + "px", - top: t + "px" - }); + if(evt.ctrlKey || evt.altKey){ return; } + var func = this._keyNavCodes[evt.charOrCode]; + if(func){ + func(); + event.stop(evt); } }, - _onKey: function(/*Event*/ evt){ + _onChildBlur: function(/*dijit._Widget*/ /*===== widget =====*/){ // summary: - // Handles the keyboard events for accessibility reasons + // Called when focus leaves a child widget to go + // to a sibling widget. + // Used by MenuBase.js (TODO: move code there) // tags: - // private - - if(evt.charOrCode){ - var dk = dojo.keys; - var node = evt.target; - if(evt.charOrCode === dk.TAB){ - this._getFocusItems(this.domNode); - } - var singleFocusItem = (this._firstFocusItem == this._lastFocusItem); - // see if we are shift-tabbing from first focusable item on dialog - if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){ - if(!singleFocusItem){ - dijit.focus(this._lastFocusItem); // send focus to last item in dialog - } - dojo.stopEvent(evt); - }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){ - if(!singleFocusItem){ - dijit.focus(this._firstFocusItem); // send focus to first item in dialog - } - dojo.stopEvent(evt); - }else{ - // see if the key is for the dialog - while(node){ - if(node == this.domNode || dojo.hasClass(node, "dijitPopup")){ - if(evt.charOrCode == dk.ESCAPE){ - this.onCancel(); - }else{ - return; // just let it go - } - } - node = node.parentNode; - } - // this key is for the disabled document window - if(evt.charOrCode !== dk.TAB){ // allow tabbing into the dialog for a11y - dojo.stopEvent(evt); - // opera won't tab to a div - }else if(!dojo.isOpera){ - try{ - this._firstFocusItem.focus(); - }catch(e){ /*squelch*/ } - } - } - } + // protected }, - show: function(){ + _getFirstFocusableChild: function(){ // summary: - // Display the dialog - // returns: dojo.Deferred - // Deferred object that resolves when the display animation is complete - - if(this.open){ return; } - - if(!this._started){ - this.startup(); - } - - // first time we show the dialog, there's some initialization stuff to do - if(!this._alreadyInitialized){ - this._setup(); - this._alreadyInitialized=true; - } - - if(this._fadeOutDeferred){ - this._fadeOutDeferred.cancel(); - } - - this._modalconnects.push(dojo.connect(window, "onscroll", this, "layout")); - this._modalconnects.push(dojo.connect(window, "onresize", this, function(){ - // IE gives spurious resize events and can actually get stuck - // in an infinite loop if we don't ignore them - var viewport = dojo.window.getBox(); - if(!this._oldViewport || - viewport.h != this._oldViewport.h || - viewport.w != this._oldViewport.w){ - this.layout(); - this._oldViewport = viewport; - } - })); - this._modalconnects.push(dojo.connect(this.domNode, "onkeypress", this, "_onKey")); - - dojo.style(this.domNode, { - opacity:0, - display:"" - }); - - this._set("open", true); - this._onShow(); // lazy load trigger - - this._size(); - this._position(); - - // fade-in Animation object, setup below - var fadeIn; - - this._fadeInDeferred = new dojo.Deferred(dojo.hitch(this, function(){ - fadeIn.stop(); - delete this._fadeInDeferred; - })); - - fadeIn = dojo.fadeIn({ - node: this.domNode, - duration: this.duration, - beforeBegin: dojo.hitch(this, function(){ - dijit._DialogLevelManager.show(this, this.underlayAttrs); - }), - onEnd: dojo.hitch(this, function(){ - if(this.autofocus && dijit._DialogLevelManager.isTop(this)){ - // find focusable items each time dialog is shown since if dialog contains a widget the - // first focusable items can change - this._getFocusItems(this.domNode); - dijit.focus(this._firstFocusItem); - } - this._fadeInDeferred.callback(true); - delete this._fadeInDeferred; - }) - }).play(); - - return this._fadeInDeferred; + // Returns first child that can be focused + return this._getNextFocusableChild(null, 1); // dijit._Widget }, - hide: function(){ + _getLastFocusableChild: function(){ // summary: - // Hide the dialog - // returns: dojo.Deferred - // Deferred object that resolves when the hide animation is complete + // Returns last child that can be focused + return this._getNextFocusableChild(null, -1); // dijit._Widget + }, - // if we haven't been initialized yet then we aren't showing and we can just return - if(!this._alreadyInitialized){ - return; + _getNextFocusableChild: function(child, dir){ + // summary: + // Returns the next or previous focusable child, compared + // to "child" + // child: Widget + // The current widget + // dir: Integer + // * 1 = after + // * -1 = before + if(child){ + child = this._getSiblingOfChild(child, dir); } - if(this._fadeInDeferred){ - this._fadeInDeferred.cancel(); + var children = this.getChildren(); + for(var i=0; i < children.length; i++){ + if(!child){ + child = children[(dir>0) ? 0 : (children.length-1)]; + } + if(child.isFocusable()){ + return child; // dijit._Widget + } + child = this._getSiblingOfChild(child, dir); } + // no focusable child found + return null; // dijit._Widget + } + }); +}); - // fade-in Animation object, setup below - var fadeOut; +}, +'dijit/form/DataList':function(){ +define("dijit/form/DataList", [ + "dojo/_base/declare", // declare + "dojo/dom", // dom.byId + "dojo/_base/lang", // lang.trim + "dojo/query", // query + "dojo/store/Memory", // dojo.store.Memory + "../registry" // registry.add registry.remove +], function(declare, dom, lang, query, MemoryStore, registry){ + + // module: + // dijit/form/DataList + // summary: + // Inefficient but small data store specialized for inlined data via OPTION tags - this._fadeOutDeferred = new dojo.Deferred(dojo.hitch(this, function(){ - fadeOut.stop(); - delete this._fadeOutDeferred; - })); + function toItem(/*DOMNode*/ option){ + // summary: + // Convert <option> node to hash + return { + id: option.value, + value: option.value, + name: lang.trim(option.innerText || option.textContent || '') + }; + } - fadeOut = dojo.fadeOut({ - node: this.domNode, - duration: this.duration, - onEnd: dojo.hitch(this, function(){ - this.domNode.style.display = "none"; - dijit._DialogLevelManager.hide(this); - this.onHide(); - this._fadeOutDeferred.callback(true); - delete this._fadeOutDeferred; - }) - }).play(); + return declare("dijit.form.DataList", MemoryStore, { + // summary: + // Inefficient but small data store specialized for inlined data via OPTION tags + // + // description: + // Provides a store for inlined data like: + // + // | <datalist> + // | <option value="AL">Alabama</option> + // | ... - if(this._scrollConnected){ - this._scrollConnected = false; - } - dojo.forEach(this._modalconnects, dojo.disconnect); - this._modalconnects = []; + constructor: function(/*Object?*/ params, /*DomNode|String*/ srcNodeRef){ + // store pointer to original DOM tree + this.domNode = dom.byId(srcNodeRef); - if(this._relativePosition){ - delete this._relativePosition; + lang.mixin(this, params); + if(this.id){ + registry.add(this); // add to registry so it can be easily found by id } - this._set("open", false); + this.domNode.style.display = "none"; - return this._fadeOutDeferred; - }, - - layout: function(){ - // summary: - // Position the Dialog and the underlay - // tags: - // private - if(this.domNode.style.display != "none"){ - if(dijit._underlay){ // avoid race condition during show() - dijit._underlay.layout(); - } - this._position(); - } + this.inherited(arguments, [{ + data: query("option", this.domNode).map(toItem) + }]); }, destroy: function(){ - if(this._fadeInDeferred){ - this._fadeInDeferred.cancel(); - } - if(this._fadeOutDeferred){ - this._fadeOutDeferred.cancel(); - } - if(this._moveable){ - this._moveable.destroy(); - } - if(this._dndListener){ - dojo.unsubscribe(this._dndListener); - } - dojo.forEach(this._modalconnects, dojo.disconnect); - - dijit._DialogLevelManager.hide(this); + registry.remove(this.id); + }, - this.inherited(arguments); + fetchSelectedItem: function(){ + // summary: + // Get the option marked as selected, like `<option selected>`. + // Not part of dojo.data API. + var option = query("> option[selected]", this.domNode)[0] || query("> option", this.domNode)[0]; + return option && toItem(option); } - } -); + }); +}); -dojo.declare( - "dijit.Dialog", - [dijit.layout.ContentPane, dijit._DialogBase], - {} -); +}, +'url:dijit/templates/Dialog.html':"<div class=\"dijitDialog\" role=\"dialog\" aria-labelledby=\"${id}_title\">\n\t<div data-dojo-attach-point=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t<span data-dojo-attach-point=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"></span>\n\t<span data-dojo-attach-point=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" data-dojo-attach-event=\"ondijitclick: onCancel\" title=\"${buttonCancel}\" role=\"button\" tabIndex=\"-1\">\n\t\t<span data-dojo-attach-point=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t</span>\n\t</div>\n\t\t<div data-dojo-attach-point=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n", +'dijit/form/CheckBox':function(){ +require({cache:{ +'url: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\tdata-dojo-attach-point=\"focusNode\"\n\t \tdata-dojo-attach-event=\"onclick:_onClick\"\n/></div>\n"}}); +define("dijit/form/CheckBox", [ + "require", + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/_base/kernel", + "dojo/query", // query + "dojo/ready", + "./ToggleButton", + "./_CheckBoxMixin", + "dojo/text!./templates/CheckBox.html", + "dojo/NodeList-dom" // NodeList.addClass/removeClass +], function(require, declare, domAttr, kernel, query, ready, ToggleButton, _CheckBoxMixin, template){ + +/*===== + var ToggleButton = dijit.form.ToggleButton; + var _CheckBoxMixin = dijit.form._CheckBoxMixin; +=====*/ -dijit._DialogLevelManager = { + // module: + // dijit/form/CheckBox // summary: - // Controls the various active "levels" on the page, starting with the - // stuff initially visible on the page (at z-index 0), and then having an entry for - // each Dialog shown. + // Checkbox widget - show: function(/*dijit._Widget*/ dialog, /*Object*/ underlayAttrs){ + // Back compat w/1.6, remove for 2.0 + if(!kernel.isAsync){ + ready(0, function(){ + var requires = ["dijit/form/RadioButton"]; + require(requires); // use indirection so modules not rolled into a build + }); + } + + return declare("dijit.form.CheckBox", [ToggleButton, _CheckBoxMixin], { // summary: - // Call right before fade-in animation for new dialog. - // Saves current focus, displays/adjusts underlay for new dialog, - // and sets the z-index of the dialog itself. + // Same as an HTML checkbox, but with fancy styling. // - // New dialog will be displayed on top of all currently displayed dialogs. + // description: + // User interacts with real html inputs. + // On onclick (which occurs by mouse click, space-bar, or + // using the arrow keys to switch the selected radio button), + // we update the state of the checkbox/radio. // - // Caller is responsible for setting focus in new dialog after the fade-in - // animation completes. - - var ds = dijit._dialogStack; - - // Save current focus - ds[ds.length-1].focus = dijit.getFocus(dialog); - - // Display the underlay, or if already displayed then adjust for this new dialog - var underlay = dijit._underlay; - if(!underlay || underlay._destroyed){ - underlay = dijit._underlay = new dijit.DialogUnderlay(underlayAttrs); - }else{ - underlay.set(dialog.underlayAttrs); - } - - // Set z-index a bit above previous dialog - var zIndex = ds[ds.length-1].dialog ? ds[ds.length-1].zIndex + 2 : 950; - if(ds.length == 1){ // first dialog - underlay.show(); - } - dojo.style(dijit._underlay.domNode, 'zIndex', zIndex - 1); - - // Dialog - dojo.style(dialog.domNode, 'zIndex', zIndex); - - ds.push({dialog: dialog, underlayAttrs: underlayAttrs, zIndex: zIndex}); - }, - - hide: function(/*dijit._Widget*/ dialog){ - // summary: - // Called when the specified dialog is hidden/destroyed, after the fade-out - // animation ends, in order to reset page focus, fix the underlay, etc. - // If the specified dialog isn't open then does nothing. + // There are two modes: + // 1. High contrast mode + // 2. Normal mode // - // Caller is responsible for either setting display:none on the dialog domNode, - // or calling dijit.popup.hide(), or removing it from the page DOM. - - var ds = dijit._dialogStack; - - if(ds[ds.length-1].dialog == dialog){ - // Removing the top (or only) dialog in the stack, return focus - // to previous dialog + // In case 1, the regular html inputs are shown and used by the user. + // In case 2, the regular html inputs are invisible but still used by + // the user. They are turned quasi-invisible and overlay the background-image. - ds.pop(); + templateString: template, - var pd = ds[ds.length-1]; // the new active dialog (or the base page itself) + baseClass: "dijitCheckBox", - // Adjust underlay - if(ds.length == 1){ - // Returning to original page. - // Hide the underlay, unless the underlay widget has already been destroyed - // because we are being called during page unload (when all widgets are destroyed) - if(!dijit._underlay._destroyed){ - dijit._underlay.hide(); - } - }else{ - // Popping back to previous dialog, adjust underlay - dojo.style(dijit._underlay.domNode, 'zIndex', pd.zIndex - 1); - dijit._underlay.set(pd.underlayAttrs); - } - - // Adjust focus - if(dialog.refocus){ - // If we are returning control to a previous dialog but for some reason - // that dialog didn't have a focused field, set focus to first focusable item. - // This situation could happen if two dialogs appeared at nearly the same time, - // since a dialog doesn't set it's focus until the fade-in is finished. - var focus = pd.focus; - if(!focus || (pd.dialog && !dojo.isDescendant(focus.node, pd.dialog.domNode))){ - pd.dialog._getFocusItems(pd.dialog.domNode); - focus = pd.dialog._firstFocusItem; - } - - try{ - dijit.focus(focus); - }catch(e){ - /* focus() will fail if user opened the dialog by clicking a non-focusable element */ - } + _setValueAttr: function(/*String|Boolean*/ newValue, /*Boolean*/ priorityChange){ + // summary: + // Handler for value= attribute to constructor, and also calls to + // set('value', val). + // description: + // During initialization, just saves as attribute to the <input type=checkbox>. + // + // After initialization, + // when passed a boolean, controls whether or not the CheckBox is checked. + // If passed a string, changes the value attribute of the CheckBox (the one + // specified as "value" when the CheckBox was constructed (ex: <input + // data-dojo-type="dijit.CheckBox" value="chicken">) + // widget.set('value', string) will check the checkbox and change the value to the + // specified string + // widget.set('value', boolean) will change the checked state. + if(typeof newValue == "string"){ + this._set("value", newValue); + domAttr.set(this.focusNode, 'value', newValue); + newValue = true; } - }else{ - // Removing a dialog out of order (#9944, #10705). - // Don't need to mess with underlay or z-index or anything. - var idx = dojo.indexOf(dojo.map(ds, function(elem){return elem.dialog}), dialog); - if(idx != -1){ - ds.splice(idx, 1); + if(this._created){ + this.set('checked', newValue, priorityChange); } - } - }, - - isTop: function(/*dijit._Widget*/ dialog){ - // summary: - // Returns true if specified Dialog is the top in the task - var ds = dijit._dialogStack; - return ds[ds.length-1].dialog == dialog; - } -}; - -// Stack representing the various active "levels" on the page, starting with the -// stuff initially visible on the page (at z-index 0), and then having an entry for -// each Dialog shown. -// Each element in stack has form { -// dialog: dialogWidget, -// focus: returnFromGetFocus(), -// underlayAttrs: attributes to set on underlay (when this widget is active) -// } -dijit._dialogStack = [ - {dialog: null, focus: null, underlayAttrs: null} // entry for stuff at z-index: 0 -]; - -} - -if(!dojo._hasResource["dijit._HasDropDown"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._HasDropDown"] = true; -dojo.provide("dijit._HasDropDown"); - - - -dojo.declare("dijit._HasDropDown", - null, - { - // summary: - // Mixin for widgets that need drop down ability. + }, + _getValueAttr: function(){ + // summary: + // Hook so get('value') works. + // description: + // If the CheckBox is checked, returns the value attribute. + // Otherwise returns false. + return (this.checked ? this.value : false); + }, - // _buttonNode: [protected] DomNode - // The button/icon/node to click to display the drop down. - // Can be set via a dojoAttachPoint assignment. - // If missing, then either focusNode or domNode (if focusNode is also missing) will be used. - _buttonNode: null, + // Override behavior from Button, since we don't have an iconNode + _setIconClassAttr: null, - // _arrowWrapperNode: [protected] DomNode - // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending - // on where the drop down is set to be positioned. - // Can be set via a dojoAttachPoint assignment. - // If missing, then _buttonNode will be used. - _arrowWrapperNode: null, + postMixInProperties: function(){ + this.inherited(arguments); - // _popupStateNode: [protected] DomNode - // The node to set the popupActive class on. - // Can be set via a dojoAttachPoint assignment. - // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used. - _popupStateNode: null, + // Need to set initial checked state as part of template, so that form submit works. + // domAttr.set(node, "checked", bool) doesn't work on IE until node has been attached + // to <body>, see #8666 + this.checkedAttrSetting = this.checked ? "checked" : ""; + }, - // _aroundNode: [protected] DomNode - // The node to display the popup around. - // Can be set via a dojoAttachPoint assignment. - // If missing, then domNode will be used. - _aroundNode: null, + _fillContent: function(){ + // Override Button::_fillContent() since it doesn't make sense for CheckBox, + // since CheckBox doesn't even have a container + }, - // dropDown: [protected] Widget - // The widget to display as a popup. This widget *must* be - // defined before the startup function is called. - dropDown: null, + _onFocus: function(){ + if(this.id){ + query("label[for='"+this.id+"']").addClass("dijitFocusedLabel"); + } + this.inherited(arguments); + }, - // autoWidth: [protected] Boolean - // Set to true to make the drop down at least as wide as this - // widget. Set to false if the drop down should just be its - // default width - autoWidth: true, + _onBlur: function(){ + if(this.id){ + query("label[for='"+this.id+"']").removeClass("dijitFocusedLabel"); + } + this.inherited(arguments); + } + }); +}); - // forceWidth: [protected] Boolean - // Set to true to make the drop down exactly as wide as this - // widget. Overrides autoWidth. - forceWidth: false, +}, +'dijit/tree/_dndSelector':function(){ +define("dijit/tree/_dndSelector", [ + "dojo/_base/array", // array.filter array.forEach array.map + "dojo/_base/connect", // connect.isCopyKey + "dojo/_base/declare", // declare + "dojo/_base/lang", // lang.hitch + "dojo/mouse", // mouse.isLeft + "dojo/on", + "dojo/touch", + "dojo/_base/window", // win.global + "./_dndContainer" +], function(array, connect, declare, lang, mouse, on, touch, win, _dndContainer){ + + // module: + // dijit/tree/_dndSelector + // summary: + // This is a base class for `dijit.tree.dndSource` , and isn't meant to be used directly. + // It's based on `dojo.dnd.Selector`. - // maxHeight: [protected] Integer - // The max height for our dropdown. - // Any dropdown taller than this will have scrollbars. - // Set to 0 for no max height, or -1 to limit height to available space in viewport - maxHeight: 0, - // dropDownPosition: [const] String[] - // This variable controls the position of the drop down. - // It's an array of strings with the following values: - // - // * before: places drop down to the left of the target node/widget, or to the right in - // the case of RTL scripts like Hebrew and Arabic - // * after: places drop down to the right of the target node/widget, or to the left in - // the case of RTL scripts like Hebrew and Arabic - // * above: drop down goes above target node - // * below: drop down goes below target node - // - // The list is positions is tried, in order, until a position is found where the drop down fits - // within the viewport. - // - dropDownPosition: ["below","above"], + return declare("dijit.tree._dndSelector", _dndContainer, { + // summary: + // This is a base class for `dijit.tree.dndSource` , and isn't meant to be used directly. + // It's based on `dojo.dnd.Selector`. + // tags: + // protected - // _stopClickEvents: Boolean - // When set to false, the click events will not be stopped, in - // case you want to use them in your subwidget - _stopClickEvents: true, + /*===== + // selection: Hash<String, DomNode> + // (id, DomNode) map for every TreeNode that's currently selected. + // The DOMNode is the TreeNode.rowNode. + selection: {}, + =====*/ - _onDropDownMouseDown: function(/*Event*/ e){ + constructor: function(){ // summary: - // Callback when the user mousedown's on the arrow icon - - if(this.disabled || this.readOnly){ return; } + // Initialization + // tags: + // private - dojo.stopEvent(e); + this.selection={}; + this.anchor = null; - this._docHandler = this.connect(dojo.doc, "onmouseup", "_onDropDownMouseUp"); + this.tree.domNode.setAttribute("aria-multiselect", !this.singular); - this.toggleDropDown(); + this.events.push( + on(this.tree.domNode, touch.press, lang.hitch(this,"onMouseDown")), + on(this.tree.domNode, touch.release, lang.hitch(this,"onMouseUp")), + on(this.tree.domNode, touch.move, lang.hitch(this,"onMouseMove")) + ); }, - _onDropDownMouseUp: function(/*Event?*/ e){ - // summary: - // Callback when the user lifts their mouse after mouse down on the arrow icon. - // If the drop is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our - // dropDown node. If the event is missing, then we are not - // a mouseup event. - // - // This is useful for the common mouse movement pattern - // with native browser <select> nodes: - // 1. mouse down on the select node (probably on the arrow) - // 2. move mouse to a menu item while holding down the mouse button - // 3. mouse up. this selects the menu item as though the user had clicked it. - if(e && this._docHandler){ - this.disconnect(this._docHandler); - } - var dropDown = this.dropDown, overMenu = false; - - if(e && this._opened){ - // This code deals with the corner-case when the drop down covers the original widget, - // because it's so large. In that case mouse-up shouldn't select a value from the menu. - // Find out if our target is somewhere in our dropdown widget, - // but not over our _buttonNode (the clickable node) - var c = dojo.position(this._buttonNode, true); - if(!(e.pageX >= c.x && e.pageX <= c.x + c.w) || - !(e.pageY >= c.y && e.pageY <= c.y + c.h)){ - var t = e.target; - while(t && !overMenu){ - if(dojo.hasClass(t, "dijitPopup")){ - overMenu = true; - }else{ - t = t.parentNode; - } - } - if(overMenu){ - t = e.target; - if(dropDown.onItemClick){ - var menuItem; - while(t && !(menuItem = dijit.byNode(t))){ - t = t.parentNode; - } - if(menuItem && menuItem.onClick && menuItem.getParent){ - menuItem.getParent().onItemClick(menuItem, e); - } - } - return; - } - } - } - if(this._opened && dropDown.focus && dropDown.autoFocus !== false){ - // Focus the dropdown widget - do it on a delay so that we - // don't steal our own focus. - window.setTimeout(dojo.hitch(dropDown, "focus"), 1); - } - }, + // singular: Boolean + // Allows selection of only one element, if true. + // Tree hasn't been tested in singular=true mode, unclear if it works. + singular: false, - _onDropDownClick: function(/*Event*/ e){ - // the drop down was already opened on mousedown/keydown; just need to call stopEvent() - if(this._stopClickEvents){ - dojo.stopEvent(e); + // methods + getSelectedTreeNodes: function(){ + // summary: + // Returns a list of selected node(s). + // Used by dndSource on the start of a drag. + // tags: + // protected + var nodes=[], sel = this.selection; + for(var i in sel){ + nodes.push(sel[i]); } + return nodes; }, - buildRendering: function(){ - this.inherited(arguments); - - this._buttonNode = this._buttonNode || this.focusNode || this.domNode; - this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode; + selectNone: function(){ + // summary: + // Unselects all items + // tags: + // private - // Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow - // based on where drop down will normally appear - var defaultPos = { - "after" : this.isLeftToRight() ? "Right" : "Left", - "before" : this.isLeftToRight() ? "Left" : "Right", - "above" : "Up", - "below" : "Down", - "left" : "Left", - "right" : "Right" - }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down"; - dojo.addClass(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton"); + this.setSelection([]); + return this; // self }, - postCreate: function(){ + destroy: function(){ // summary: - // set up nodes and connect our mouse and keypress events - + // Prepares the object to be garbage-collected this.inherited(arguments); + this.selection = this.anchor = null; + }, + addTreeNode: function(/*dijit._TreeNode*/node, /*Boolean?*/isAnchor){ + // summary: + // add node to current selection + // node: Node + // node to add + // isAnchor: Boolean + // Whether the node should become anchor. - this.connect(this._buttonNode, "onmousedown", "_onDropDownMouseDown"); - this.connect(this._buttonNode, "onclick", "_onDropDownClick"); - this.connect(this.focusNode, "onkeypress", "_onKey"); - this.connect(this.focusNode, "onkeyup", "_onKeyUp"); + this.setSelection(this.getSelectedTreeNodes().concat( [node] )); + if(isAnchor){ this.anchor = node; } + return node; }, + removeTreeNode: function(/*dijit._TreeNode*/node){ + // summary: + // remove node from current selection + // node: Node + // node to remove + this.setSelection(this._setDifference(this.getSelectedTreeNodes(), [node])); + return node; + }, + isTreeNodeSelected: function(/*dijit._TreeNode*/node){ + // summary: + // return true if node is currently selected + // node: Node + // the node to check whether it's in the current selection - destroy: function(){ - if(this.dropDown){ - // Destroy the drop down, unless it's already been destroyed. This can happen because - // the drop down is a direct child of <body> even though it's logically my child. - if(!this.dropDown._destroyed){ - this.dropDown.destroyRecursive(); + return node.id && !!this.selection[node.id]; + }, + setSelection: function(/*dijit._treeNode[]*/ newSelection){ + // summary: + // set the list of selected nodes to be exactly newSelection. All changes to the + // selection should be passed through this function, which ensures that derived + // attributes are kept up to date. Anchor will be deleted if it has been removed + // from the selection, but no new anchor will be added by this function. + // newSelection: Node[] + // list of tree nodes to make selected + var oldSelection = this.getSelectedTreeNodes(); + array.forEach(this._setDifference(oldSelection, newSelection), lang.hitch(this, function(node){ + node.setSelected(false); + if(this.anchor == node){ + delete this.anchor; } - delete this.dropDown; - } - this.inherited(arguments); + delete this.selection[node.id]; + })); + array.forEach(this._setDifference(newSelection, oldSelection), lang.hitch(this, function(node){ + node.setSelected(true); + this.selection[node.id] = node; + })); + this._updateSelectionProperties(); }, - - _onKey: function(/*Event*/ e){ + _setDifference: function(xs,ys){ // summary: - // Callback when the user presses a key while focused on the button node + // Returns a copy of xs which lacks any objects + // occurring in ys. Checks for membership by + // modifying and then reading the object, so it will + // not properly handle sets of numbers or strings. - if(this.disabled || this.readOnly){ return; } + array.forEach(ys, function(y){ y.__exclude__ = true; }); + var ret = array.filter(xs, function(x){ return !x.__exclude__; }); - var d = this.dropDown, target = e.target; - if(d && this._opened && d.handleKey){ - if(d.handleKey(e) === false){ - /* false return code means that the drop down handled the key */ - dojo.stopEvent(e); - return; - } - } - if(d && this._opened && e.charOrCode == dojo.keys.ESCAPE){ - this.closeDropDown(); - dojo.stopEvent(e); - }else if(!this._opened && - (e.charOrCode == dojo.keys.DOWN_ARROW || - ( (e.charOrCode == dojo.keys.ENTER || e.charOrCode == " ") && - //ignore enter and space if the event is for a text input - ((target.tagName || "").toLowerCase() !== 'input' || - (target.type && target.type.toLowerCase() !== 'text'))))){ - // Toggle the drop down, but wait until keyup so that the drop down doesn't - // get a stray keyup event, or in the case of key-repeat (because user held - // down key for too long), stray keydown events - this._toggleOnKeyUp = true; - dojo.stopEvent(e); - } + // clean up after ourselves. + array.forEach(ys, function(y){ delete y['__exclude__'] }); + return ret; }, + _updateSelectionProperties: function(){ + // summary: + // Update the following tree properties from the current selection: + // path[s], selectedItem[s], selectedNode[s] - _onKeyUp: function(){ - if(this._toggleOnKeyUp){ - delete this._toggleOnKeyUp; - this.toggleDropDown(); - var d = this.dropDown; // drop down may not exist until toggleDropDown() call - if(d && d.focus){ - setTimeout(dojo.hitch(d, "focus"), 1); - } - } + var selected = this.getSelectedTreeNodes(); + var paths = [], nodes = []; + array.forEach(selected, function(node){ + nodes.push(node); + paths.push(node.getTreePath()); + }); + var items = array.map(nodes,function(node){ return node.item; }); + this.tree._set("paths", paths); + this.tree._set("path", paths[0] || []); + this.tree._set("selectedNodes", nodes); + this.tree._set("selectedNode", nodes[0] || null); + this.tree._set("selectedItems", items); + this.tree._set("selectedItem", items[0] || null); }, - - _onBlur: function(){ + // mouse events + onMouseDown: function(e){ // summary: - // Called magically when focus has shifted away from this widget and it's dropdown + // Event processor for onmousedown/ontouchstart + // e: Event + // onmousedown/ontouchstart event + // tags: + // protected - // Don't focus on button if the user has explicitly focused on something else (happens - // when user clicks another control causing the current popup to close).. - // But if focus is inside of the drop down then reset focus to me, because IE doesn't like - // it when you display:none a node with focus. - var focusMe = dijit._curFocus && this.dropDown && dojo.isDescendant(dijit._curFocus, this.dropDown.domNode); + // ignore click on expando node + if(!this.current || this.tree.isExpandoNode(e.target, this.current)){ return; } - this.closeDropDown(focusMe); + if(!mouse.isLeft(e)){ return; } // ignore right-click - this.inherited(arguments); + e.preventDefault(); + + var treeNode = this.current, + copy = connect.isCopyKey(e), id = treeNode.id; + + // if shift key is not pressed, and the node is already in the selection, + // delay deselection until onmouseup so in the case of DND, deselection + // will be canceled by onmousemove. + if(!this.singular && !e.shiftKey && this.selection[id]){ + this._doDeselect = true; + return; + }else{ + this._doDeselect = false; + } + this.userSelect(treeNode, copy, e.shiftKey); }, - isLoaded: function(){ + onMouseUp: function(e){ // summary: - // Returns whether or not the dropdown is loaded. This can - // be overridden in order to force a call to loadDropDown(). + // Event processor for onmouseup/ontouchend + // e: Event + // onmouseup/ontouchend event // tags: // protected - return true; + // _doDeselect is the flag to indicate that the user wants to either ctrl+click on + // a already selected item (to deselect the item), or click on a not-yet selected item + // (which should remove all current selection, and add the clicked item). This can not + // be done in onMouseDown, because the user may start a drag after mousedown. By moving + // the deselection logic here, the user can drags an already selected item. + if(!this._doDeselect){ return; } + this._doDeselect = false; + this.userSelect(this.current, connect.isCopyKey(e), e.shiftKey); }, - - loadDropDown: function(/* Function */ loadCallback){ + onMouseMove: function(/*===== e =====*/){ // summary: - // Loads the data for the dropdown, and at some point, calls - // the given callback. This is basically a callback when the - // user presses the down arrow button to open the drop down. - // tags: - // protected + // event processor for onmousemove/ontouchmove + // e: Event + // onmousemove/ontouchmove event + this._doDeselect = false; + }, - loadCallback(); + _compareNodes: function(n1, n2){ + if(n1 === n2){ + return 0; + } + + if('sourceIndex' in document.documentElement){ //IE + //TODO: does not yet work if n1 and/or n2 is a text node + return n1.sourceIndex - n2.sourceIndex; + }else if('compareDocumentPosition' in document.documentElement){ //FF, Opera + return n1.compareDocumentPosition(n2) & 2 ? 1: -1; + }else if(document.createRange){ //Webkit + var r1 = doc.createRange(); + r1.setStartBefore(n1); + + var r2 = doc.createRange(); + r2.setStartBefore(n2); + + return r1.compareBoundaryPoints(r1.END_TO_END, r2); + }else{ + throw Error("dijit.tree._compareNodes don't know how to compare two different nodes in this browser"); + } }, - toggleDropDown: function(){ + userSelect: function(node, multi, range){ // summary: - // Callback when the user presses the down arrow button or presses - // the down arrow key to open/close the drop down. - // Toggle the drop-down widget; if it is up, close it, if not, open it + // Add or remove the given node from selection, responding + // to a user action such as a click or keypress. + // multi: Boolean + // Indicates whether this is meant to be a multi-select action (e.g. ctrl-click) + // range: Boolean + // Indicates whether this is meant to be a ranged action (e.g. shift-click) // tags: // protected - if(this.disabled || this.readOnly){ return; } - if(!this._opened){ - // If we aren't loaded, load it first so there isn't a flicker - if(!this.isLoaded()){ - this.loadDropDown(dojo.hitch(this, "openDropDown")); - return; + if(this.singular){ + if(this.anchor == node && multi){ + this.selectNone(); }else{ - this.openDropDown(); + this.setSelection([node]); + this.anchor = node; } }else{ - this.closeDropDown(); + if(range && this.anchor){ + var cr = this._compareNodes(this.anchor.rowNode, node.rowNode), + begin, end, anchor = this.anchor; + + if(cr < 0){ //current is after anchor + begin = anchor; + end = node; + }else{ //current is before anchor + begin = node; + end = anchor; + } + var nodes = []; + //add everything betweeen begin and end inclusively + while(begin != end){ + nodes.push(begin); + begin = this.tree._getNextNode(begin); + } + nodes.push(end); + + this.setSelection(nodes); + }else{ + if( this.selection[ node.id ] && multi ){ + this.removeTreeNode( node ); + }else if(multi){ + this.addTreeNode(node, true); + }else{ + this.setSelection([node]); + this.anchor = node; + } + } } }, - openDropDown: function(){ + getItem: function(/*String*/ key){ // summary: - // Opens the dropdown for this widget. To be called only when this.dropDown - // has been created and is ready to display (ie, it's data is loaded). - // returns: - // return value of dijit.popup.open() + // Returns the dojo.dnd.Item (representing a dragged node) by it's key (id). + // Called by dojo.dnd.Source.checkAcceptance(). // tags: // protected - var dropDown = this.dropDown, - ddNode = dropDown.domNode, - aroundNode = this._aroundNode || this.domNode, - self = this; + var widget = this.selection[key]; + return { + data: widget, + type: ["treeNode"] + }; // dojo.dnd.Item + }, - // Prepare our popup's height and honor maxHeight if it exists. + forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){ + // summary: + // Iterates over selected items; + // see `dojo.dnd.Container.forInItems()` for details + o = o || win.global; + for(var id in this.selection){ + // console.log("selected item id: " + id); + f.call(o, this.getItem(id), id, this); + } + } + }); +}); - // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(), - // ie, dependent on how much space is available (BK) +}, +'dijit/_Container':function(){ +define("dijit/_Container", [ + "dojo/_base/array", // array.forEach array.indexOf + "dojo/_base/declare", // declare + "dojo/dom-construct", // domConstruct.place + "./registry" // registry.byNode() +], function(array, declare, domConstruct, registry){ + + // module: + // dijit/_Container + // summary: + // Mixin for widgets that contain a set of widget children. - if(!this._preparedNode){ - this._preparedNode = true; - // Check if we have explicitly set width and height on the dropdown widget dom node - if(ddNode.style.width){ - this._explicitDDWidth = true; - } - if(ddNode.style.height){ - this._explicitDDHeight = true; - } + return declare("dijit._Container", null, { + // summary: + // Mixin for widgets that contain a set of widget children. + // description: + // Use this mixin for widgets that needs to know about and + // keep track of their widget children. Suitable for widgets like BorderContainer + // and TabContainer which contain (only) a set of child widgets. + // + // It's not suitable for widgets like ContentPane + // which contains mixed HTML (plain DOM nodes in addition to widgets), + // and where contained widgets are not necessarily directly below + // this.containerNode. In that case calls like addChild(node, position) + // wouldn't make sense. + + buildRendering: function(){ + this.inherited(arguments); + if(!this.containerNode){ + // all widgets with descendants must set containerNode + this.containerNode = this.domNode; } + }, - // Code for resizing dropdown (height limitation, or increasing width to match my width) - if(this.maxHeight || this.forceWidth || this.autoWidth){ - var myStyle = { - display: "", - visibility: "hidden" - }; - if(!this._explicitDDWidth){ - myStyle.width = ""; - } - if(!this._explicitDDHeight){ - myStyle.height = ""; - } - dojo.style(ddNode, myStyle); - - // Figure out maximum height allowed (if there is a height restriction) - var maxHeight = this.maxHeight; - if(maxHeight == -1){ - // limit height to space available in viewport either above or below my domNode - // (whichever side has more room) - var viewport = dojo.window.getBox(), - position = dojo.position(aroundNode, false); - maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h))); - } + addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){ + // summary: + // Makes the given widget a child of this widget. + // description: + // Inserts specified child widget's dom node as a child of this widget's + // container node, and possibly does other processing (such as layout). - // Attach dropDown to DOM and make make visibility:hidden rather than display:none - // so we call startup() and also get the size - if(dropDown.startup && !dropDown._started){ - dropDown.startup(); + var refNode = this.containerNode; + if(insertIndex && typeof insertIndex == "number"){ + var children = this.getChildren(); + if(children && children.length >= insertIndex){ + refNode = children[insertIndex-1].domNode; + insertIndex = "after"; } + } + domConstruct.place(widget.domNode, refNode, insertIndex); - dijit.popup.moveOffScreen(dropDown); - // Get size of drop down, and determine if vertical scroll bar needed - var mb = dojo._getMarginSize(ddNode); - var overHeight = (maxHeight && mb.h > maxHeight); - dojo.style(ddNode, { - overflowX: "hidden", - overflowY: overHeight ? "auto" : "hidden" - }); - if(overHeight){ - mb.h = maxHeight; - if("w" in mb){ - mb.w += 16; // room for vertical scrollbar - } - }else{ - delete mb.h; - } + // If I've been started but the child widget hasn't been started, + // start it now. Make sure to do this after widget has been + // inserted into the DOM tree, so it can see that it's being controlled by me, + // so it doesn't try to size itself. + if(this._started && !widget._started){ + widget.startup(); + } + }, - // Adjust dropdown width to match or be larger than my width - if(this.forceWidth){ - mb.w = aroundNode.offsetWidth; - }else if(this.autoWidth){ - mb.w = Math.max(mb.w, aroundNode.offsetWidth); - }else{ - delete mb.w; - } - - // And finally, resize the dropdown to calculated height and width - if(dojo.isFunction(dropDown.resize)){ - dropDown.resize(mb); - }else{ - dojo.marginBox(ddNode, mb); - } + removeChild: function(/*Widget|int*/ widget){ + // summary: + // Removes the passed widget instance from this widget but does + // not destroy it. You can also pass in an integer indicating + // the index within the container to remove + + if(typeof widget == "number"){ + widget = this.getChildren()[widget]; } - var retVal = dijit.popup.open({ - parent: this, - popup: dropDown, - around: aroundNode, - orient: dijit.getPopupAroundAlignment((this.dropDownPosition && this.dropDownPosition.length) ? this.dropDownPosition : ["below"],this.isLeftToRight()), - onExecute: function(){ - self.closeDropDown(true); - }, - onCancel: function(){ - self.closeDropDown(true); - }, - onClose: function(){ - dojo.attr(self._popupStateNode, "popupActive", false); - dojo.removeClass(self._popupStateNode, "dijitHasDropDownOpen"); - self._opened = false; + if(widget){ + var node = widget.domNode; + if(node && node.parentNode){ + node.parentNode.removeChild(node); // detach but don't destroy } - }); - dojo.attr(this._popupStateNode, "popupActive", "true"); - dojo.addClass(self._popupStateNode, "dijitHasDropDownOpen"); - this._opened=true; + } + }, - // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown - return retVal; + hasChildren: function(){ + // summary: + // Returns true if widget has children, i.e. if this.containerNode contains something. + return this.getChildren().length > 0; // Boolean }, - closeDropDown: function(/*Boolean*/ focus){ + _getSiblingOfChild: function(/*dijit._Widget*/ child, /*int*/ dir){ // summary: - // Closes the drop down on this widget - // focus: - // If true, refocuses the button widget + // Get the next or previous widget sibling of child + // dir: + // if 1, get the next sibling + // if -1, get the previous sibling // tags: - // protected + // private + var node = child.domNode, + which = (dir>0 ? "nextSibling" : "previousSibling"); + do{ + node = node[which]; + }while(node && (node.nodeType != 1 || !registry.byNode(node))); + return node && registry.byNode(node); // dijit._Widget + }, - if(this._opened){ - if(focus){ this.focus(); } - dijit.popup.close(this.dropDown); - this._opened = false; - } + getIndexOfChild: function(/*dijit._Widget*/ child){ + // summary: + // Gets the index of the child in this container or -1 if not found + return array.indexOf(this.getChildren(), child); // int } + }); +}); - } -); - -} - -if(!dojo._hasResource["dijit.form.Button"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.Button"] = true; -dojo.provide("dijit.form.Button"); - +}, +'dojo/data/ItemFileReadStore':function(){ +define("dojo/data/ItemFileReadStore", ["../_base/kernel", "../_base/lang", "../_base/declare", "../_base/array", "../_base/xhr", + "../Evented", "../_base/window", "./util/filter", "./util/simpleFetch", "../date/stamp" +], function(kernel, lang, declare, array, xhr, Evented, window, filterUtil, simpleFetch, dateStamp) { + // module: + // dojo/data/ItemFileReadStore + // summary: + // TODOC +var ItemFileReadStore = declare("dojo.data.ItemFileReadStore", [Evented],{ + // summary: + // The ItemFileReadStore implements the dojo.data.api.Read API and reads + // data from JSON files that have contents in this format -- + // { items: [ + // { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]}, + // { name:'Fozzie Bear', wears:['hat', 'tie']}, + // { name:'Miss Piggy', pets:'Foo-Foo'} + // ]} + // Note that it can also contain an 'identifer' property that specified which attribute on the items + // in the array of items that acts as the unique identifier for that item. + // + constructor: function(/* Object */ keywordParameters){ + // summary: constructor + // keywordParameters: {url: String} + // keywordParameters: {data: jsonObject} + // keywordParameters: {typeMap: object) + // The structure of the typeMap object is as follows: + // { + // type0: function || object, + // type1: function || object, + // ... + // typeN: function || object + // } + // Where if it is a function, it is assumed to be an object constructor that takes the + // value of _value as the initialization parameters. If it is an object, then it is assumed + // to be an object of general form: + // { + // type: function, //constructor. + // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately. + // } + this._arrayOfAllItems = []; + this._arrayOfTopLevelItems = []; + this._loadFinished = false; + this._jsonFileUrl = keywordParameters.url; + this._ccUrl = keywordParameters.url; + this.url = keywordParameters.url; + this._jsonData = keywordParameters.data; + this.data = null; + this._datatypeMap = keywordParameters.typeMap || {}; + if(!this._datatypeMap['Date']){ + //If no default mapping for dates, then set this as default. + //We use the dojo.date.stamp here because the ISO format is the 'dojo way' + //of generically representing dates. + this._datatypeMap['Date'] = { + type: Date, + deserialize: function(value){ + return dateStamp.fromISOString(value); + } + }; + } + this._features = {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true}; + this._itemsByIdentity = null; + this._storeRefPropName = "_S"; // Default name for the store reference to attach to every item. + this._itemNumPropName = "_0"; // Default Item Id for isItem to attach to every item. + this._rootItemPropName = "_RI"; // Default Item Id for isItem to attach to every item. + this._reverseRefMap = "_RRM"; // Default attribute for constructing a reverse reference map for use with reference integrity + this._loadInProgress = false; //Got to track the initial load to prevent duelling loads of the dataset. + this._queuedFetches = []; + if(keywordParameters.urlPreventCache !== undefined){ + this.urlPreventCache = keywordParameters.urlPreventCache?true:false; + } + if(keywordParameters.hierarchical !== undefined){ + this.hierarchical = keywordParameters.hierarchical?true:false; + } + if(keywordParameters.clearOnClose){ + this.clearOnClose = true; + } + if("failOk" in keywordParameters){ + this.failOk = keywordParameters.failOk?true:false; + } + }, -dojo.declare("dijit.form.Button", - dijit.form._FormWidget, - { - // summary: - // Basically the same thing as a normal HTML button, but with special styling. - // description: - // Buttons can display a label, an icon, or both. - // A label should always be specified (through innerHTML) or the label - // attribute. It can be hidden via showLabel=false. - // example: - // | <button dojoType="dijit.form.Button" onClick="...">Hello world</button> - // - // example: - // | var button1 = new dijit.form.Button({label: "hello world", onClick: foo}); - // | dojo.body().appendChild(button1.domNode); + url: "", // use "" rather than undefined for the benefit of the parser (#3539) - // label: HTML String - // Text to display in button. - // If the label is hidden (showLabel=false) then and no title has - // been specified, then label is also set as title attribute of icon. - label: "", + //Internal var, crossCheckUrl. Used so that setting either url or _jsonFileUrl, can still trigger a reload + //when clearOnClose and close is used. + _ccUrl: "", - // showLabel: Boolean - // Set this to true to hide the label text and display only the icon. - // (If showLabel=false then iconClass must be specified.) - // Especially useful for toolbars. - // If showLabel=true, the label will become the title (a.k.a. tooltip/hint) of the icon. - // - // The exception case is for computers in high-contrast mode, where the label - // will still be displayed, since the icon doesn't appear. - showLabel: true, + data: null, // define this so that the parser can populate it - // iconClass: String - // Class to apply to DOMNode in button to make it display an icon - iconClass: "", + typeMap: null, //Define so parser can populate. - // type: String - // Defines the type of button. "button", "submit", or "reset". - type: "button", + //Parameter to allow users to specify if a close call should force a reload or not. + //By default, it retains the old behavior of not clearing if close is called. But + //if set true, the store will be reset to default state. Note that by doing this, + //all item handles will become invalid and a new fetch must be issued. + clearOnClose: false, - baseClass: "dijitButton", + //Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url. + //Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option. + //Added for tracker: #6072 + urlPreventCache: false, - templateString: dojo.cache("dijit.form", "templates/Button.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode,focusNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">●</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdojoAttachPoint=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"), + //Parameter for specifying that it is OK for the xhrGet call to fail silently. + failOk: false, - attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { - value: "valueNode" - }), + //Parameter to indicate to process data from the url as hierarchical + //(data items can contain other data items in js form). Default is true + //for backwards compatibility. False means only root items are processed + //as items, all child objects outside of type-mapped objects and those in + //specific reference format, are left straight JS data objects. + hierarchical: true, - _onClick: function(/*Event*/ e){ - // summary: - // Internal function to handle click actions - if(this.disabled){ - return false; + _assertIsItem: function(/* item */ item){ + // summary: + // This function tests whether the item passed in is indeed an item in the store. + // item: + // The item to test for being contained by the store. + if(!this.isItem(item)){ + throw new Error("dojo.data.ItemFileReadStore: Invalid item argument."); } - this._clicked(); // widget click actions - return this.onClick(e); // user click actions }, - _onButtonClick: function(/*Event*/ e){ - // summary: - // Handler when the user activates the button portion. - if(this._onClick(e) === false){ // returning nothing is same as true - e.preventDefault(); // needed for checkbox - }else if(this.type == "submit" && !(this.valueNode||this.focusNode).form){ // see if a nonform widget needs to be signalled - for(var node=this.domNode; node.parentNode/*#5935*/; node=node.parentNode){ - var widget=dijit.byNode(node); - if(widget && typeof widget._onSubmit == "function"){ - widget._onSubmit(e); - break; - } - } - }else if(this.valueNode){ - this.valueNode.click(); - e.preventDefault(); // cancel BUTTON click and continue with hidden INPUT click + _assertIsAttribute: function(/* attribute-name-string */ attribute){ + // summary: + // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store. + // attribute: + // The attribute to test for being contained by the store. + if(typeof attribute !== "string"){ + throw new Error("dojo.data.ItemFileReadStore: Invalid attribute argument."); } }, - buildRendering: function(){ - this.inherited(arguments); - dojo.setSelectable(this.focusNode, false); - }, - - _fillContent: function(/*DomNode*/ source){ - // Overrides _Templated._fillContent(). - // If button label is specified as srcNodeRef.innerHTML rather than - // this.params.label, handle it here. - // TODO: remove the method in 2.0, parser will do it all for me - if(source && (!this.params || !("label" in this.params))){ - this.set('label', source.innerHTML); - } + getValue: function( /* item */ item, + /* attribute-name-string */ attribute, + /* value? */ defaultValue){ + // summary: + // See dojo.data.api.Read.getValue() + var values = this.getValues(item, attribute); + return (values.length > 0)?values[0]:defaultValue; // mixed }, - _setShowLabelAttr: function(val){ - if(this.containerNode){ - dojo.toggleClass(this.containerNode, "dijitDisplayNone", !val); - } - this._set("showLabel", val); - }, + getValues: function(/* item */ item, + /* attribute-name-string */ attribute){ + // summary: + // See dojo.data.api.Read.getValues() - onClick: function(/*Event*/ e){ - // summary: - // Callback for when button is clicked. - // If type="submit", return true to perform submit, or false to cancel it. - // type: - // callback - return true; // Boolean + this._assertIsItem(item); + this._assertIsAttribute(attribute); + // Clone it before returning. refs: #10474 + return (item[attribute] || []).slice(0); // Array }, - _clicked: function(/*Event*/ e){ - // summary: - // Internal overridable function for when the button is clicked + getAttributes: function(/* item */ item){ + // summary: + // See dojo.data.api.Read.getAttributes() + this._assertIsItem(item); + var attributes = []; + for(var key in item){ + // Save off only the real item attributes, not the special id marks for O(1) isItem. + if((key !== this._storeRefPropName) && (key !== this._itemNumPropName) && (key !== this._rootItemPropName) && (key !== this._reverseRefMap)){ + attributes.push(key); + } + } + return attributes; // Array }, - setLabel: function(/*String*/ content){ - // summary: - // Deprecated. Use set('label', ...) instead. - dojo.deprecated("dijit.form.Button.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0"); - this.set("label", content); + hasAttribute: function( /* item */ item, + /* attribute-name-string */ attribute){ + // summary: + // See dojo.data.api.Read.hasAttribute() + this._assertIsItem(item); + this._assertIsAttribute(attribute); + return (attribute in item); }, - _setLabelAttr: function(/*String*/ content){ - // summary: - // Hook for set('label', ...) to work. - // description: - // Set the label (text) of the button; takes an HTML string. - this._set("label", content); - this.containerNode.innerHTML = content; - if(this.showLabel == false && !this.params.title){ - this.titleNode.title = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || ''); + containsValue: function(/* item */ item, + /* attribute-name-string */ attribute, + /* anything */ value){ + // summary: + // See dojo.data.api.Read.containsValue() + var regexp = undefined; + if(typeof value === "string"){ + regexp = filterUtil.patternToRegExp(value, false); } + return this._containsValue(item, attribute, value, regexp); //boolean. }, - _setIconClassAttr: function(/*String*/ val){ - // Custom method so that icon node is hidden when not in use, to avoid excess padding/margin - // appearing around it (even if it's a 0x0 sized <img> node) - - var oldVal = this.iconClass || "dijitNoIcon", - newVal = val || "dijitNoIcon"; - dojo.replaceClass(this.iconNode, newVal, oldVal); - this._set("iconClass", val); - } -}); - - -dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container, dijit._HasDropDown], { - // summary: - // A button with a drop down - // - // example: - // | <button dojoType="dijit.form.DropDownButton" label="Hello world"> - // | <div dojotype="dijit.Menu">...</div> - // | </button> - // - // example: - // | var button1 = new dijit.form.DropDownButton({ label: "hi", dropDown: new dijit.Menu(...) }); - // | dojo.body().appendChild(button1); - // - - baseClass : "dijitDropDownButton", - - templateString: dojo.cache("dijit.form", "templates/DropDownButton.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\" dojoAttachPoint=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"focusNode,titleNode,_arrowWrapperNode\"\n\t\t\trole=\"button\" aria-haspopup=\"true\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\"\n\t\t\t\tdojoAttachPoint=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdojoAttachPoint=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">▼</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"), - - _fillContent: function(){ - // Overrides Button._fillContent(). + _containsValue: function( /* item */ item, + /* attribute-name-string */ attribute, + /* anything */ value, + /* RegExp?*/ regexp){ + // summary: + // Internal function for looking at the values contained by the item. + // description: + // Internal function for looking at the values contained by the item. This + // function allows for denoting if the comparison should be case sensitive for + // strings or not (for handling filtering cases where string case should not matter) // - // My inner HTML contains both the button contents and a drop down widget, like - // <DropDownButton> <span>push me</span> <Menu> ... </Menu> </DropDownButton> - // The first node is assumed to be the button content. The widget is the popup. - - if(this.srcNodeRef){ // programatically created buttons might not define srcNodeRef - //FIXME: figure out how to filter out the widget and use all remaining nodes as button - // content, not just nodes[0] - var nodes = dojo.query("*", this.srcNodeRef); - dijit.form.DropDownButton.superclass._fillContent.call(this, nodes[0]); + // item: + // The data item to examine for attribute values. + // attribute: + // The attribute to inspect. + // value: + // The value to match. + // regexp: + // Optional regular expression generated off value if value was of string type to handle wildcarding. + // If present and attribute values are string, then it can be used for comparison instead of 'value' + return array.some(this.getValues(item, attribute), function(possibleValue){ + if(possibleValue !== null && !lang.isObject(possibleValue) && regexp){ + if(possibleValue.toString().match(regexp)){ + return true; // Boolean + } + }else if(value === possibleValue){ + return true; // Boolean + } + }); + }, - // save pointer to srcNode so we can grab the drop down widget after it's instantiated - this.dropDownContainer = this.srcNodeRef; + isItem: function(/* anything */ something){ + // summary: + // See dojo.data.api.Read.isItem() + if(something && something[this._storeRefPropName] === this){ + if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){ + return true; + } } + return false; // Boolean }, - startup: function(){ - if(this._started){ return; } - - // the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM, - // make it invisible, and store a reference to pass to the popup code. - if(!this.dropDown && this.dropDownContainer){ - var dropDownNode = dojo.query("[widgetId]", this.dropDownContainer)[0]; - this.dropDown = dijit.byNode(dropDownNode); - delete this.dropDownContainer; - } - if(this.dropDown){ - dijit.popup.hide(this.dropDown); - } + isItemLoaded: function(/* anything */ something){ + // summary: + // See dojo.data.api.Read.isItemLoaded() + return this.isItem(something); //boolean + }, - this.inherited(arguments); + loadItem: function(/* object */ keywordArgs){ + // summary: + // See dojo.data.api.Read.loadItem() + this._assertIsItem(keywordArgs.item); }, - isLoaded: function(){ - // Returns whether or not we are loaded - if our dropdown has an href, - // then we want to check that. - var dropDown = this.dropDown; - return (!!dropDown && (!dropDown.href || dropDown.isLoaded)); + getFeatures: function(){ + // summary: + // See dojo.data.api.Read.getFeatures() + return this._features; //Object }, - loadDropDown: function(){ - // Loads our dropdown - var dropDown = this.dropDown; - if(!dropDown){ return; } - if(!this.isLoaded()){ - var handler = dojo.connect(dropDown, "onLoad", this, function(){ - dojo.disconnect(handler); - this.openDropDown(); - }); - dropDown.refresh(); - }else{ - this.openDropDown(); + getLabel: function(/* item */ item){ + // summary: + // See dojo.data.api.Read.getLabel() + if(this._labelAttr && this.isItem(item)){ + return this.getValue(item,this._labelAttr); //String } + return undefined; //undefined }, - isFocusable: function(){ - // Overridden so that focus is handled by the _HasDropDown mixin, not by - // the _FormWidget mixin. - return this.inherited(arguments) && !this._mouseDown; - } -}); - -dojo.declare("dijit.form.ComboButton", dijit.form.DropDownButton, { - // summary: - // A combination button and drop-down button. - // Users can click one side to "press" the button, or click an arrow - // icon to display the drop down. - // - // example: - // | <button dojoType="dijit.form.ComboButton" onClick="..."> - // | <span>Hello world</span> - // | <div dojoType="dijit.Menu">...</div> - // | </button> - // - // example: - // | var button1 = new dijit.form.ComboButton({label: "hello world", onClick: foo, dropDown: "myMenu"}); - // | dojo.body().appendChild(button1.domNode); - // + getLabelAttributes: function(/* item */ item){ + // summary: + // See dojo.data.api.Read.getLabelAttributes() + if(this._labelAttr){ + return [this._labelAttr]; //array + } + return null; //null + }, - templateString: dojo.cache("dijit.form", "templates/ComboButton.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tcellspacing='0' cellpadding='0' role=\"presentation\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonNode\" dojoAttachPoint=\"buttonNode\" dojoAttachEvent=\"ondijitclick:_onButtonClick,onkeypress:_onButtonKeyPress\"\n\t\t><div id=\"${id}_button\" class=\"dijitReset dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><div class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitInline dijitButtonText\" id=\"${id}_label\" dojoAttachPoint=\"containerNode\" role=\"presentation\"></div\n\t\t></div\n\t\t></td\n\t\t><td id=\"${id}_arrow\" class='dijitReset dijitRight dijitButtonNode dijitArrowButton'\n\t\t\tdojoAttachPoint=\"_popupStateNode,focusNode,_buttonNode\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onArrowKeyPress\"\n\t\t\ttitle=\"${optionsTitle}\"\n\t\t\trole=\"button\" aria-haspopup=\"true\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">▼</div\n\t\t></td\n\t\t><td style=\"display:none !important;\"\n\t\t\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" dojoAttachPoint=\"valueNode\"\n\t\t/></td></tr></tbody\n></table>\n"), + _fetchItems: function( /* Object */ keywordArgs, + /* Function */ findCallback, + /* Function */ errorCallback){ + // summary: + // See dojo.data.util.simpleFetch.fetch() + var self = this, + filter = function(requestArgs, arrayOfItems){ + var items = [], + i, key; + if(requestArgs.query){ + var value, + ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false; - attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), { - id: "", - tabIndex: ["focusNode", "titleNode"], - title: "titleNode" - }), + //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the + //same value for each item examined. Much more efficient. + var regexpList = {}; + for(key in requestArgs.query){ + value = requestArgs.query[key]; + if(typeof value === "string"){ + regexpList[key] = filterUtil.patternToRegExp(value, ignoreCase); + }else if(value instanceof RegExp){ + regexpList[key] = value; + } + } + for(i = 0; i < arrayOfItems.length; ++i){ + var match = true; + var candidateItem = arrayOfItems[i]; + if(candidateItem === null){ + match = false; + }else{ + for(key in requestArgs.query){ + value = requestArgs.query[key]; + if(!self._containsValue(candidateItem, key, value, regexpList[key])){ + match = false; + } + } + } + if(match){ + items.push(candidateItem); + } + } + findCallback(items, requestArgs); + }else{ + // We want a copy to pass back in case the parent wishes to sort the array. + // We shouldn't allow resort of the internal list, so that multiple callers + // can get lists and sort without affecting each other. We also need to + // filter out any null values that have been left as a result of deleteItem() + // calls in ItemFileWriteStore. + for(i = 0; i < arrayOfItems.length; ++i){ + var item = arrayOfItems[i]; + if(item !== null){ + items.push(item); + } + } + findCallback(items, requestArgs); + } + }; - // optionsTitle: String - // Text that describes the options menu (accessibility) - optionsTitle: "", + if(this._loadFinished){ + filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); + }else{ + //Do a check on the JsonFileUrl and crosscheck it. + //If it doesn't match the cross-check, it needs to be updated + //This allows for either url or _jsonFileUrl to he changed to + //reset the store load location. Done this way for backwards + //compatibility. People use _jsonFileUrl (even though officially + //private. + if(this._jsonFileUrl !== this._ccUrl){ + kernel.deprecated("dojo.data.ItemFileReadStore: ", + "To change the url, set the url property of the store," + + " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); + this._ccUrl = this._jsonFileUrl; + this.url = this._jsonFileUrl; + }else if(this.url !== this._ccUrl){ + this._jsonFileUrl = this.url; + this._ccUrl = this.url; + } - baseClass: "dijitComboButton", + //See if there was any forced reset of data. + if(this.data != null){ + this._jsonData = this.data; + this.data = null; + } - // Set classes like dijitButtonContentsHover or dijitArrowButtonActive depending on - // mouse action over specified node - cssStateNodes: { - "buttonNode": "dijitButtonNode", - "titleNode": "dijitButtonContents", - "_popupStateNode": "dijitDownArrowButton" - }, + if(this._jsonFileUrl){ + //If fetches come in before the loading has finished, but while + //a load is in progress, we have to defer the fetching to be + //invoked in the callback. + if(this._loadInProgress){ + this._queuedFetches.push({args: keywordArgs, filter: filter}); + }else{ + this._loadInProgress = true; + var getArgs = { + url: self._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk + }; + var getHandler = xhr.get(getArgs); + getHandler.addCallback(function(data){ + try{ + self._getItemsFromLoadedData(data); + self._loadFinished = true; + self._loadInProgress = false; - _focusedNode: null, + filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions)); + self._handleQueuedFetches(); + }catch(e){ + self._loadFinished = true; + self._loadInProgress = false; + errorCallback(e, keywordArgs); + } + }); + getHandler.addErrback(function(error){ + self._loadInProgress = false; + errorCallback(error, keywordArgs); + }); - _onButtonKeyPress: function(/*Event*/ evt){ - // summary: - // Handler for right arrow key when focus is on left part of button - if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "RIGHT_ARROW" : "LEFT_ARROW"]){ - dijit.focus(this._popupStateNode); - dojo.stopEvent(evt); + //Wire up the cancel to abort of the request + //This call cancel on the deferred if it hasn't been called + //yet and then will chain to the simple abort of the + //simpleFetch keywordArgs + var oldAbort = null; + if(keywordArgs.abort){ + oldAbort = keywordArgs.abort; + } + keywordArgs.abort = function(){ + var df = getHandler; + if(df && df.fired === -1){ + df.cancel(); + df = null; + } + if(oldAbort){ + oldAbort.call(keywordArgs); + } + }; + } + }else if(this._jsonData){ + try{ + this._loadFinished = true; + this._getItemsFromLoadedData(this._jsonData); + this._jsonData = null; + filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); + }catch(e){ + errorCallback(e, keywordArgs); + } + }else{ + errorCallback(new Error("dojo.data.ItemFileReadStore: No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs); + } } }, - _onArrowKeyPress: function(/*Event*/ evt){ - // summary: - // Handler for left arrow key when focus is on right part of button - if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "LEFT_ARROW" : "RIGHT_ARROW"]){ - dijit.focus(this.titleNode); - dojo.stopEvent(evt); + _handleQueuedFetches: function(){ + // summary: + // Internal function to execute delayed request in the store. + //Execute any deferred fetches now. + if(this._queuedFetches.length > 0){ + for(var i = 0; i < this._queuedFetches.length; i++){ + var fData = this._queuedFetches[i], + delayedQuery = fData.args, + delayedFilter = fData.filter; + if(delayedFilter){ + delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions)); + }else{ + this.fetchItemByIdentity(delayedQuery); + } + } + this._queuedFetches = []; } }, - - focus: function(/*String*/ position){ - // summary: - // Focuses this widget to according to position, if specified, - // otherwise on arrow node - // position: - // "start" or "end" - if(!this.disabled){ - dijit.focus(position == "start" ? this.titleNode : this._popupStateNode); - } - } -}); - -dojo.declare("dijit.form.ToggleButton", dijit.form.Button, { - // summary: - // A button that can be in two states (checked or not). - // Can be base class for things like tabs or checkbox or radio buttons - - baseClass: "dijitToggleButton", - // checked: Boolean - // Corresponds to the native HTML <input> element's attribute. - // In markup, specified as "checked='checked'" or just "checked". - // True if the button is depressed, or the checkbox is checked, - // or the radio button is selected, etc. - checked: false, - - attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), { - checked:"focusNode" - }), - - _clicked: function(/*Event*/ evt){ - this.set('checked', !this.checked); + _getItemsArray: function(/*object?*/queryOptions){ + // summary: + // Internal function to determine which list of items to search over. + // queryOptions: The query options parameter, if any. + if(queryOptions && queryOptions.deep){ + return this._arrayOfAllItems; + } + return this._arrayOfTopLevelItems; }, - _setCheckedAttr: function(/*Boolean*/ value, /*Boolean?*/ priorityChange){ - this._set("checked", value); - dojo.attr(this.focusNode || this.domNode, "checked", value); - dijit.setWaiState(this.focusNode || this.domNode, "pressed", value); - this._handleOnChange(value, priorityChange); - }, + close: function(/*dojo.data.api.Request || keywordArgs || null */ request){ + // summary: + // See dojo.data.api.Read.close() + if(this.clearOnClose && + this._loadFinished && + !this._loadInProgress){ + //Reset all internalsback to default state. This will force a reload + //on next fetch. This also checks that the data or url param was set + //so that the store knows it can get data. Without one of those being set, + //the next fetch will trigger an error. - setChecked: function(/*Boolean*/ checked){ - // summary: - // Deprecated. Use set('checked', true/false) instead. - dojo.deprecated("setChecked("+checked+") is deprecated. Use set('checked',"+checked+") instead.", "", "2.0"); - this.set('checked', checked); + if(((this._jsonFileUrl == "" || this._jsonFileUrl == null) && + (this.url == "" || this.url == null) + ) && this.data == null){ + console.debug("dojo.data.ItemFileReadStore: WARNING! Data reload " + + " information has not been provided." + + " Please set 'url' or 'data' to the appropriate value before" + + " the next fetch"); + } + this._arrayOfAllItems = []; + this._arrayOfTopLevelItems = []; + this._loadFinished = false; + this._itemsByIdentity = null; + this._loadInProgress = false; + this._queuedFetches = []; + } }, - reset: function(){ - // summary: - // Reset the widget's value to what it was at initialization time - - this._hasBeenBlurred = false; - - // set checked state to original setting - this.set('checked', this.params.checked || false); - } -}); - -} - -if(!dojo._hasResource["dijit.form.ToggleButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.ToggleButton"] = true; -dojo.provide("dijit.form.ToggleButton"); - - - - -} - -if(!dojo._hasResource["dijit.form.CheckBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.CheckBox"] = true; -dojo.provide("dijit.form.CheckBox"); - - - -dojo.declare( - "dijit.form.CheckBox", - dijit.form.ToggleButton, - { - // summary: - // Same as an HTML checkbox, but with fancy styling. - // - // description: - // User interacts with real html inputs. - // On onclick (which occurs by mouse click, space-bar, or - // using the arrow keys to switch the selected radio button), - // we update the state of the checkbox/radio. - // - // There are two modes: - // 1. High contrast mode - // 2. Normal mode - // - // In case 1, the regular html inputs are shown and used by the user. - // In case 2, the regular html inputs are invisible but still used by - // the user. They are turned quasi-invisible and overlay the background-image. - - templateString: dojo.cache("dijit.form", "templates/CheckBox.html", "<div class=\"dijit dijitReset dijitInline\" role=\"presentation\"\n\t><input\n\t \t${!nameAttrSetting} type=\"${type}\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdojoAttachPoint=\"focusNode\"\n\t \tdojoAttachEvent=\"onclick:_onClick\"\n/></div>\n"), - - baseClass: "dijitCheckBox", - - // type: [private] String - // type attribute on <input> node. - // Overrides `dijit.form.Button.type`. Users should not change this value. - type: "checkbox", - - // value: String - // As an initialization parameter, equivalent to value field on normal checkbox - // (if checked, the value is passed as the value when form is submitted). - // - // However, get('value') will return either the string or false depending on - // whether or not the checkbox is checked. + _getItemsFromLoadedData: function(/* Object */ dataObject){ + // summary: + // Function to parse the loaded data into item format and build the internal items array. + // description: + // Function to parse the loaded data into item format and build the internal items array. // - // set('value', string) will check the checkbox and change the value to the - // specified string + // dataObject: + // The JS data object containing the raw data to convery into item format. // - // set('value', boolean) will change the checked state. - value: "on", - - // readOnly: Boolean - // Should this widget respond to user input? - // In markup, this is specified as "readOnly". - // Similar to disabled except readOnly form values are submitted. - readOnly: false, - - // the attributeMap should inherit from dijit.form._FormWidget.prototype.attributeMap - // instead of ToggleButton as the icon mapping has no meaning for a CheckBox - attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { - readOnly: "focusNode" - }), + // returns: array + // Array of items in store item format. - _setReadOnlyAttr: function(/*Boolean*/ value){ - this._set("readOnly", value); - dojo.attr(this.focusNode, 'readOnly', value); - dijit.setWaiState(this.focusNode, "readonly", value); - }, + // First, we define a couple little utility functions... + var addingArrays = false, + self = this; - _setValueAttr: function(/*String|Boolean*/ newValue, /*Boolean*/ priorityChange){ - // summary: - // Handler for value= attribute to constructor, and also calls to - // set('value', val). - // description: - // During initialization, just saves as attribute to the <input type=checkbox>. - // - // After initialization, - // when passed a boolean, controls whether or not the CheckBox is checked. - // If passed a string, changes the value attribute of the CheckBox (the one - // specified as "value" when the CheckBox was constructed (ex: <input - // dojoType="dijit.CheckBox" value="chicken">) - if(typeof newValue == "string"){ - this._set("value", newValue); - dojo.attr(this.focusNode, 'value', newValue); - newValue = true; - } - if(this._created){ - this.set('checked', newValue, priorityChange); - } - }, - _getValueAttr: function(){ + function valueIsAnItem(/* anything */ aValue){ // summary: - // Hook so get('value') works. - // description: - // If the CheckBox is checked, returns the value attribute. - // Otherwise returns false. - return (this.checked ? this.value : false); - }, - - // Override dijit.form.Button._setLabelAttr() since we don't even have a containerNode. - // Normally users won't try to set label, except when CheckBox or RadioButton is the child of a dojox.layout.TabContainer - _setLabelAttr: undefined, + // Given any sort of value that could be in the raw json data, + // return true if we should interpret the value as being an + // item itself, rather than a literal value or a reference. + // example: + // | false == valueIsAnItem("Kermit"); + // | false == valueIsAnItem(42); + // | false == valueIsAnItem(new Date()); + // | false == valueIsAnItem({_type:'Date', _value:'1802-05-14'}); + // | false == valueIsAnItem({_reference:'Kermit'}); + // | true == valueIsAnItem({name:'Kermit', color:'green'}); + // | true == valueIsAnItem({iggy:'pop'}); + // | true == valueIsAnItem({foo:42}); + return (aValue !== null) && + (typeof aValue === "object") && + (!lang.isArray(aValue) || addingArrays) && + (!lang.isFunction(aValue)) && + (aValue.constructor == Object || lang.isArray(aValue)) && + (typeof aValue._reference === "undefined") && + (typeof aValue._type === "undefined") && + (typeof aValue._value === "undefined") && + self.hierarchical; + } - postMixInProperties: function(){ - if(this.value == ""){ - this.value = "on"; + function addItemAndSubItemsToArrayOfAllItems(/* Item */ anItem){ + self._arrayOfAllItems.push(anItem); + for(var attribute in anItem){ + var valueForAttribute = anItem[attribute]; + if(valueForAttribute){ + if(lang.isArray(valueForAttribute)){ + var valueArray = valueForAttribute; + for(var k = 0; k < valueArray.length; ++k){ + var singleValue = valueArray[k]; + if(valueIsAnItem(singleValue)){ + addItemAndSubItemsToArrayOfAllItems(singleValue); + } + } + }else{ + if(valueIsAnItem(valueForAttribute)){ + addItemAndSubItemsToArrayOfAllItems(valueForAttribute); + } + } + } } + } - // Need to set initial checked state as part of template, so that form submit works. - // dojo.attr(node, "checked", bool) doesn't work on IEuntil node has been attached - // to <body>, see #8666 - this.checkedAttrSetting = this.checked ? "checked" : ""; - - this.inherited(arguments); - }, - - _fillContent: function(/*DomNode*/ source){ - // Override Button::_fillContent() since it doesn't make sense for CheckBox, - // since CheckBox doesn't even have a container - }, + this._labelAttr = dataObject.label; - reset: function(){ - // Override ToggleButton.reset() + // We need to do some transformations to convert the data structure + // that we read from the file into a format that will be convenient + // to work with in memory. - this._hasBeenBlurred = false; + // Step 1: Walk through the object hierarchy and build a list of all items + var i, + item; + this._arrayOfAllItems = []; + this._arrayOfTopLevelItems = dataObject.items; - this.set('checked', this.params.checked || false); + for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){ + item = this._arrayOfTopLevelItems[i]; + if(lang.isArray(item)){ + addingArrays = true; + } + addItemAndSubItemsToArrayOfAllItems(item); + item[this._rootItemPropName]=true; + } - // Handle unlikely event that the <input type=checkbox> value attribute has changed - this._set("value", this.params.value || "on"); - dojo.attr(this.focusNode, 'value', this.value); - }, + // Step 2: Walk through all the attribute values of all the items, + // and replace single values with arrays. For example, we change this: + // { name:'Miss Piggy', pets:'Foo-Foo'} + // into this: + // { name:['Miss Piggy'], pets:['Foo-Foo']} + // + // We also store the attribute names so we can validate our store + // reference and item id special properties for the O(1) isItem + var allAttributeNames = {}, + key; - _onFocus: function(){ - if(this.id){ - dojo.query("label[for='"+this.id+"']").addClass("dijitFocusedLabel"); + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; + for(key in item){ + if(key !== this._rootItemPropName){ + var value = item[key]; + if(value !== null){ + if(!lang.isArray(value)){ + item[key] = [value]; + } + }else{ + item[key] = [null]; + } + } + allAttributeNames[key]=key; } - this.inherited(arguments); - }, + } - _onBlur: function(){ - if(this.id){ - dojo.query("label[for='"+this.id+"']").removeClass("dijitFocusedLabel"); - } - this.inherited(arguments); - }, + // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName + // This should go really fast, it will generally never even run the loop. + while(allAttributeNames[this._storeRefPropName]){ + this._storeRefPropName += "_"; + } + while(allAttributeNames[this._itemNumPropName]){ + this._itemNumPropName += "_"; + } + while(allAttributeNames[this._reverseRefMap]){ + this._reverseRefMap += "_"; + } - _onClick: function(/*Event*/ e){ - // summary: - // Internal function to handle click actions - need to check - // readOnly, since button no longer does that check. - if(this.readOnly){ - dojo.stopEvent(e); - return false; + // Step 4: Some data files specify an optional 'identifier', which is + // the name of an attribute that holds the identity of each item. + // If this data file specified an identifier attribute, then build a + // hash table of items keyed by the identity of the items. + var arrayOfValues; + + var identifier = dataObject.identifier; + if(identifier){ + this._itemsByIdentity = {}; + this._features['dojo.data.api.Identity'] = identifier; + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; + arrayOfValues = item[identifier]; + var identity = arrayOfValues[0]; + if(!Object.hasOwnProperty.call(this._itemsByIdentity, identity)){ + this._itemsByIdentity[identity] = item; + }else{ + if(this._jsonFileUrl){ + 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 + "]"); + }else if(this._jsonData){ + 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 + "]"); + } + } } - return this.inherited(arguments); + }else{ + this._features['dojo.data.api.Identity'] = Number; } - } -); - -dojo.declare( - "dijit.form.RadioButton", - dijit.form.CheckBox, - { - // summary: - // Same as an HTML radio, but with fancy styling. - type: "radio", - baseClass: "dijitRadio", + // Step 5: Walk through all the items, and set each item's properties + // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true. + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; + item[this._storeRefPropName] = this; + item[this._itemNumPropName] = i; + } - _setCheckedAttr: function(/*Boolean*/ value){ - // If I am being checked then have to deselect currently checked radio button - this.inherited(arguments); - if(!this._created){ return; } - if(value){ - var _this = this; - // search for radio buttons with the same name that need to be unchecked - dojo.query("INPUT[type=radio]", this.focusNode.form || dojo.doc).forEach( // can't use name= since dojo.query doesn't support [] in the name - function(inputNode){ - if(inputNode.name == _this.name && inputNode != _this.focusNode && inputNode.form == _this.focusNode.form){ - var widget = dijit.getEnclosingWidget(inputNode); - if(widget && widget.checked){ - widget.set('checked', false); + // Step 6: We walk through all the attribute values of all the items, + // looking for type/value literals and item-references. + // + // We replace item-references with pointers to items. For example, we change: + // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } + // into this: + // { name:['Kermit'], friends:[miss_piggy] } + // (where miss_piggy is the object representing the 'Miss Piggy' item). + // + // We replace type/value pairs with typed-literals. For example, we change: + // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'1918-07-18'}] } + // into this: + // { name:['Kermit'], born:(new Date(1918, 6, 18)) } + // + // We also generate the associate map for all items for the O(1) isItem function. + for(i = 0; i < this._arrayOfAllItems.length; ++i){ + item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } + for(key in item){ + arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}] + for(var j = 0; j < arrayOfValues.length; ++j){ + value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}} + if(value !== null && typeof value == "object"){ + if(("_type" in value) && ("_value" in value)){ + var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber' + var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}} + if(!mappingObj){ + throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'"); + }else if(lang.isFunction(mappingObj)){ + arrayOfValues[j] = new mappingObj(value._value); + }else if(lang.isFunction(mappingObj.deserialize)){ + arrayOfValues[j] = mappingObj.deserialize(value._value); + }else{ + throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function"); + } + } + if(value._reference){ + var referenceDescription = value._reference; // example: {name:'Miss Piggy'} + if(!lang.isObject(referenceDescription)){ + // example: 'Miss Piggy' + // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]} + arrayOfValues[j] = this._getItemByIdentity(referenceDescription); + }else{ + // example: {name:'Miss Piggy'} + // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } + for(var k = 0; k < this._arrayOfAllItems.length; ++k){ + var candidateItem = this._arrayOfAllItems[k], + found = true; + for(var refKey in referenceDescription){ + if(candidateItem[refKey] != referenceDescription[refKey]){ + found = false; + } + } + if(found){ + arrayOfValues[j] = candidateItem; + } + } + } + if(this.referenceIntegrity){ + var refItem = arrayOfValues[j]; + if(this.isItem(refItem)){ + this._addReferenceToMap(refItem, item, key); + } + } + }else if(this.isItem(value)){ + //It's a child item (not one referenced through _reference). + //We need to treat this as a referenced item, so it can be cleaned up + //in a write store easily. + if(this.referenceIntegrity){ + this._addReferenceToMap(value, item, key); } } } - ); - } - }, - - _clicked: function(/*Event*/ e){ - if(!this.checked){ - this.set('checked', true); + } } } - } -); - -} - -if(!dojo._hasResource["dijit.form.DropDownButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.DropDownButton"] = true; -dojo.provide("dijit.form.DropDownButton"); - - - - -} - -if(!dojo._hasResource["dojo.regexp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.regexp"] = true; -dojo.provide("dojo.regexp"); - -dojo.getObject("regexp", true, dojo); + }, -/*===== -dojo.regexp = { - // summary: Regular expressions and Builder resources -}; -=====*/ + _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){ + // summary: + // Method to add an reference map entry for an item and attribute. + // description: + // Method to add an reference map entry for an item and attribute. // + // refItem: + // The item that is referenced. + // parentItem: + // The item that holds the new reference to refItem. + // attribute: + // The attribute on parentItem that contains the new reference. -dojo.regexp.escapeString = function(/*String*/str, /*String?*/except){ - // summary: - // Adds escape sequences for special characters in regular expressions - // except: - // a String with special characters to be left unescaped + //Stub function, does nothing. Real processing is in ItemFileWriteStore. + }, - return str.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(ch){ - if(except && except.indexOf(ch) != -1){ - return ch; + getIdentity: function(/* item */ item){ + // summary: + // See dojo.data.api.Identity.getIdentity() + var identifier = this._features['dojo.data.api.Identity']; + if(identifier === Number){ + return item[this._itemNumPropName]; // Number + }else{ + var arrayOfValues = item[identifier]; + if(arrayOfValues){ + return arrayOfValues[0]; // Object || String + } } - return "\\" + ch; - }); // String -}; - -dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boolean?*/nonCapture){ - // summary: - // Builds a regular expression that groups subexpressions - // description: - // A utility function used by some of the RE generators. The - // subexpressions are constructed by the function, re, in the second - // parameter. re builds one subexpression for each elem in the array - // a, in the first parameter. Returns a string for a regular - // expression that groups all the subexpressions. - // arr: - // A single value or an array of values. - // re: - // A function. Takes one parameter and converts it to a regular - // expression. - // nonCapture: - // If true, uses non-capturing match, otherwise matches are retained - // by regular expression. Defaults to false - - // case 1: a is a single value. - if(!(arr instanceof Array)){ - return re(arr); // String - } - - // case 2: a is an array - var b = []; - for(var i = 0; i < arr.length; i++){ - // convert each elem to a RE - b.push(re(arr[i])); - } - - // join the REs as alternatives in a RE group. - return dojo.regexp.group(b.join("|"), nonCapture); // String -}; - -dojo.regexp.group = function(/*String*/expression, /*Boolean?*/nonCapture){ - // summary: - // adds group match to expression - // nonCapture: - // If true, uses non-capturing match, otherwise matches are retained - // by regular expression. - return "(" + (nonCapture ? "?:":"") + expression + ")"; // String -}; + return null; // null + }, -} + fetchItemByIdentity: function(/* Object */ keywordArgs){ + // summary: + // See dojo.data.api.Identity.fetchItemByIdentity() -if(!dojo._hasResource["dojo.data.util.sorter"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.data.util.sorter"] = true; -dojo.provide("dojo.data.util.sorter"); + // Hasn't loaded yet, we have to trigger the load. + var item, + scope; + if(!this._loadFinished){ + var self = this; + //Do a check on the JsonFileUrl and crosscheck it. + //If it doesn't match the cross-check, it needs to be updated + //This allows for either url or _jsonFileUrl to he changed to + //reset the store load location. Done this way for backwards + //compatibility. People use _jsonFileUrl (even though officially + //private. + if(this._jsonFileUrl !== this._ccUrl){ + kernel.deprecated("dojo.data.ItemFileReadStore: ", + "To change the url, set the url property of the store," + + " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); + this._ccUrl = this._jsonFileUrl; + this.url = this._jsonFileUrl; + }else if(this.url !== this._ccUrl){ + this._jsonFileUrl = this.url; + this._ccUrl = this.url; + } -dojo.getObject("data.util.sorter", true, dojo); + //See if there was any forced reset of data. + if(this.data != null && this._jsonData == null){ + this._jsonData = this.data; + this.data = null; + } -dojo.data.util.sorter.basicComparator = function( /*anything*/ a, - /*anything*/ b){ - // summary: - // Basic comparision function that compares if an item is greater or less than another item - // description: - // returns 1 if a > b, -1 if a < b, 0 if equal. - // 'null' values (null, undefined) are treated as larger values so that they're pushed to the end of the list. - // And compared to each other, null is equivalent to undefined. - - //null is a problematic compare, so if null, we set to undefined. - //Makes the check logic simple, compact, and consistent - //And (null == undefined) === true, so the check later against null - //works for undefined and is less bytes. - var r = -1; - if(a === null){ - a = undefined; - } - if(b === null){ - b = undefined; - } - if(a == b){ - r = 0; - }else if(a > b || a == null){ - r = 1; - } - return r; //int {-1,0,1} -}; + if(this._jsonFileUrl){ -dojo.data.util.sorter.createSortFunction = function( /* attributes array */sortSpec, - /*dojo.data.core.Read*/ store){ - // summary: - // Helper function to generate the sorting function based off the list of sort attributes. - // description: - // The sort function creation will look for a property on the store called 'comparatorMap'. If it exists - // it will look in the mapping for comparisons function for the attributes. If one is found, it will - // use it instead of the basic comparator, which is typically used for strings, ints, booleans, and dates. - // Returns the sorting function for this particular list of attributes and sorting directions. - // - // sortSpec: array - // A JS object that array that defines out what attribute names to sort on and whether it should be descenting or asending. - // The objects should be formatted as follows: - // { - // attribute: "attributeName-string" || attribute, - // descending: true|false; // Default is false. - // } - // store: object - // The datastore object to look up item values from. - // - var sortFunctions=[]; + if(this._loadInProgress){ + this._queuedFetches.push({args: keywordArgs}); + }else{ + this._loadInProgress = true; + var getArgs = { + url: self._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk + }; + var getHandler = xhr.get(getArgs); + getHandler.addCallback(function(data){ + var scope = keywordArgs.scope?keywordArgs.scope:window.global; + try{ + self._getItemsFromLoadedData(data); + self._loadFinished = true; + self._loadInProgress = false; + item = self._getItemByIdentity(keywordArgs.identity); + if(keywordArgs.onItem){ + keywordArgs.onItem.call(scope, item); + } + self._handleQueuedFetches(); + }catch(error){ + self._loadInProgress = false; + if(keywordArgs.onError){ + keywordArgs.onError.call(scope, error); + } + } + }); + getHandler.addErrback(function(error){ + self._loadInProgress = false; + if(keywordArgs.onError){ + var scope = keywordArgs.scope?keywordArgs.scope:window.global; + keywordArgs.onError.call(scope, error); + } + }); + } - function createSortFunction(attr, dir, comp, s){ - //Passing in comp and s (comparator and store), makes this - //function much faster. - return function(itemA, itemB){ - var a = s.getValue(itemA, attr); - var b = s.getValue(itemB, attr); - return dir * comp(a,b); //int - }; - } - var sortAttribute; - var map = store.comparatorMap; - var bc = dojo.data.util.sorter.basicComparator; - for(var i = 0; i < sortSpec.length; i++){ - sortAttribute = sortSpec[i]; - var attr = sortAttribute.attribute; - if(attr){ - var dir = (sortAttribute.descending) ? -1 : 1; - var comp = bc; - if(map){ - if(typeof attr !== "string" && ("toString" in attr)){ - attr = attr.toString(); + }else if(this._jsonData){ + // Passed in data, no need to xhr. + self._getItemsFromLoadedData(self._jsonData); + self._jsonData = null; + self._loadFinished = true; + item = self._getItemByIdentity(keywordArgs.identity); + if(keywordArgs.onItem){ + scope = keywordArgs.scope?keywordArgs.scope:window.global; + keywordArgs.onItem.call(scope, item); } - comp = map[attr] || bc; } - sortFunctions.push(createSortFunction(attr, - dir, comp, store)); - } - } - return function(rowA, rowB){ - var i=0; - while(i < sortFunctions.length){ - var ret = sortFunctions[i++](rowA, rowB); - if(ret !== 0){ - return ret;//int + }else{ + // Already loaded. We can just look it up and call back. + item = this._getItemByIdentity(keywordArgs.identity); + if(keywordArgs.onItem){ + scope = keywordArgs.scope?keywordArgs.scope:window.global; + keywordArgs.onItem.call(scope, item); } } - return 0; //int - }; // Function -}; - -} - -if(!dojo._hasResource["dojo.data.util.simpleFetch"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.data.util.simpleFetch"] = true; -dojo.provide("dojo.data.util.simpleFetch"); - - -dojo.getObject("data.util.simpleFetch", true, dojo); - -dojo.data.util.simpleFetch.fetch = function(/* Object? */ request){ - // summary: - // The simpleFetch mixin is designed to serve as a set of function(s) that can - // be mixed into other datastore implementations to accelerate their development. - // The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems() - // call by returning an array of all the found items that matched the query. The simpleFetch mixin - // is not designed to work for datastores that respond to a fetch() call by incrementally - // loading items, or sequentially loading partial batches of the result - // set. For datastores that mixin simpleFetch, simpleFetch - // implements a fetch method that automatically handles eight of the fetch() - // arguments -- onBegin, onItem, onComplete, onError, start, count, sort and scope - // The class mixing in simpleFetch should not implement fetch(), - // but should instead implement a _fetchItems() method. The _fetchItems() - // method takes three arguments, the keywordArgs object that was passed - // to fetch(), a callback function to be called when the result array is - // available, and an error callback to be called if something goes wrong. - // The _fetchItems() method should ignore any keywordArgs parameters for - // start, count, onBegin, onItem, onComplete, onError, sort, and scope. - // The _fetchItems() method needs to correctly handle any other keywordArgs - // parameters, including the query parameter and any optional parameters - // (such as includeChildren). The _fetchItems() method should create an array of - // result items and pass it to the fetchHandler along with the original request object - // -- or, the _fetchItems() method may, if it wants to, create an new request object - // with other specifics about the request that are specific to the datastore and pass - // that as the request object to the handler. - // - // For more information on this specific function, see dojo.data.api.Read.fetch() - request = request || {}; - if(!request.store){ - request.store = this; - } - var self = this; - - var _errorHandler = function(errorData, requestObject){ - if(requestObject.onError){ - var scope = requestObject.scope || dojo.global; - requestObject.onError.call(scope, errorData, requestObject); - } - }; - - var _fetchHandler = function(items, requestObject){ - var oldAbortFunction = requestObject.abort || null; - var aborted = false; - - var startIndex = requestObject.start?requestObject.start:0; - var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length; - - requestObject.abort = function(){ - aborted = true; - if(oldAbortFunction){ - oldAbortFunction.call(requestObject); - } - }; + }, - var scope = requestObject.scope || dojo.global; - if(!requestObject.store){ - requestObject.store = self; - } - if(requestObject.onBegin){ - requestObject.onBegin.call(scope, items.length, requestObject); - } - if(requestObject.sort){ - items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self)); - } - if(requestObject.onItem){ - for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){ - var item = items[i]; - if(!aborted){ - requestObject.onItem.call(scope, item, requestObject); - } + _getItemByIdentity: function(/* Object */ identity){ + // summary: + // Internal function to look an item up by its identity map. + var item = null; + if(this._itemsByIdentity){ + // If this map is defined, we need to just try to get it. If it fails + // the item does not exist. + if(Object.hasOwnProperty.call(this._itemsByIdentity, identity)){ + item = this._itemsByIdentity[identity]; } + }else if (Object.hasOwnProperty.call(this._arrayOfAllItems, identity)){ + item = this._arrayOfAllItems[identity]; } - if(requestObject.onComplete && !aborted){ - var subset = null; - if(!requestObject.onItem){ - subset = items.slice(startIndex, endIndex); - } - requestObject.onComplete.call(scope, subset, requestObject); + if(item === undefined){ + item = null; } - }; - this._fetchItems(request, _fetchHandler, _errorHandler); - return request; // Object -}; + return item; // Object + }, -} + getIdentityAttributes: function(/* item */ item){ + // summary: + // See dojo.data.api.Identity.getIdentityAttributes() -if(!dojo._hasResource["dojo.data.util.filter"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.data.util.filter"] = true; -dojo.provide("dojo.data.util.filter"); + var identifier = this._features['dojo.data.api.Identity']; + if(identifier === Number){ + // If (identifier === Number) it means getIdentity() just returns + // an integer item-number for each item. The dojo.data.api.Identity + // spec says we need to return null if the identity is not composed + // of attributes + return null; // null + }else{ + return [identifier]; // Array + } + }, -dojo.getObject("data.util.filter", true, dojo); + _forceLoad: function(){ + // summary: + // Internal function to force a load of the store if it hasn't occurred yet. This is required + // for specific functions to work properly. + var self = this; + //Do a check on the JsonFileUrl and crosscheck it. + //If it doesn't match the cross-check, it needs to be updated + //This allows for either url or _jsonFileUrl to he changed to + //reset the store load location. Done this way for backwards + //compatibility. People use _jsonFileUrl (even though officially + //private. + if(this._jsonFileUrl !== this._ccUrl){ + kernel.deprecated("dojo.data.ItemFileReadStore: ", + "To change the url, set the url property of the store," + + " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); + this._ccUrl = this._jsonFileUrl; + this.url = this._jsonFileUrl; + }else if(this.url !== this._ccUrl){ + this._jsonFileUrl = this.url; + this._ccUrl = this.url; + } -dojo.data.util.filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/ ignoreCase){ - // summary: - // Helper function to convert a simple pattern to a regular expression for matching. - // description: - // Returns a regular expression object that conforms to the defined conversion rules. - // For example: - // ca* -> /^ca.*$/ - // *ca* -> /^.*ca.*$/ - // *c\*a* -> /^.*c\*a.*$/ - // *c\*a?* -> /^.*c\*a..*$/ - // and so on. - // - // pattern: string - // A simple matching pattern to convert that follows basic rules: - // * Means match anything, so ca* means match anything starting with ca - // ? Means match single character. So, b?b will match to bob and bab, and so on. - // \ is an escape character. So for example, \* means do not treat * as a match, but literal character *. - // To use a \ as a character in the string, it must be escaped. So in the pattern it should be - // represented by \\ to be treated as an ordinary \ character instead of an escape. - // - // ignoreCase: - // An optional flag to indicate if the pattern matching should be treated as case-sensitive or not when comparing - // By default, it is assumed case sensitive. + //See if there was any forced reset of data. + if(this.data != null){ + this._jsonData = this.data; + this.data = null; + } - var rxp = "^"; - var c = null; - for(var i = 0; i < pattern.length; i++){ - c = pattern.charAt(i); - switch(c){ - case '\\': - rxp += c; - i++; - rxp += pattern.charAt(i); - break; - case '*': - rxp += ".*"; break; - case '?': - rxp += "."; break; - case '$': - case '^': - case '/': - case '+': - case '.': - case '|': - case '(': - case ')': - case '{': - case '}': - case '[': - case ']': - rxp += "\\"; //fallthrough - default: - rxp += c; + if(this._jsonFileUrl){ + var getArgs = { + url: this._jsonFileUrl, + handleAs: "json-comment-optional", + preventCache: this.urlPreventCache, + failOk: this.failOk, + sync: true + }; + var getHandler = xhr.get(getArgs); + getHandler.addCallback(function(data){ + try{ + //Check to be sure there wasn't another load going on concurrently + //So we don't clobber data that comes in on it. If there is a load going on + //then do not save this data. It will potentially clobber current data. + //We mainly wanted to sync/wait here. + //TODO: Revisit the loading scheme of this store to improve multi-initial + //request handling. + if(self._loadInProgress !== true && !self._loadFinished){ + self._getItemsFromLoadedData(data); + self._loadFinished = true; + }else if(self._loadInProgress){ + //Okay, we hit an error state we can't recover from. A forced load occurred + //while an async load was occurring. Since we cannot block at this point, the best + //that can be managed is to throw an error. + throw new Error("dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress."); + } + }catch(e){ + console.log(e); + throw e; + } + }); + getHandler.addErrback(function(error){ + throw error; + }); + }else if(this._jsonData){ + self._getItemsFromLoadedData(self._jsonData); + self._jsonData = null; + self._loadFinished = true; } } - rxp += "$"; - if(ignoreCase){ - return new RegExp(rxp,"mi"); //RegExp - }else{ - return new RegExp(rxp,"m"); //RegExp - } - -}; +}); +//Mix in the simple fetch implementation to this class. +lang.extend(ItemFileReadStore,simpleFetch); -} +return ItemFileReadStore; +}); -if(!dojo._hasResource["dijit.form.TextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.TextBox"] = true; -dojo.provide("dijit.form.TextBox"); +}, +'dojo/html':function(){ +define("dojo/html", ["./_base/kernel", "./_base/lang", "./_base/array", "./_base/declare", "./dom", "./dom-construct", "./parser"], function(dojo, lang, darray, declare, dom, domConstruct, parser) { + // module: + // dojo/html + // summary: + // TODOC + lang.getObject("html", true, dojo); + // the parser might be needed.. -dojo.declare( - "dijit.form.TextBox", - dijit.form._FormValueWidget, - { + // idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes + var idCounter = 0; + + dojo.html._secureForInnerHtml = function(/*String*/ cont){ // summary: - // A base class for textbox form inputs + // removes !DOCTYPE and title elements from the html string. + // + // khtml is picky about dom faults, you can't attach a style or <title> node as child of body + // must go into head, so we need to cut out those tags + // cont: + // An html string for insertion into the dom + // + return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String + }; - // trim: Boolean - // Removes leading and trailing whitespace if true. Default is false. - trim: false, +/*==== + dojo.html._emptyNode = function(node){ + // summary: + // removes all child nodes from the given node + // node: DOMNode + // the parent element + }; +=====*/ + dojo.html._emptyNode = domConstruct.empty; - // uppercase: Boolean - // Converts all characters to uppercase if true. Default is false. - uppercase: false, + dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){ + // summary: + // inserts the given content into the given node + // node: + // the parent element + // content: + // the content to be set on the parent element. + // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes - // lowercase: Boolean - // Converts all characters to lowercase if true. Default is false. - lowercase: false, + // always empty + domConstruct.empty(node); - // propercase: Boolean - // Converts the first character of each word to uppercase if true. - propercase: false, + if(cont) { + if(typeof cont == "string") { + cont = domConstruct.toDom(cont, node.ownerDocument); + } + if(!cont.nodeType && lang.isArrayLike(cont)) { + // handle as enumerable, but it may shrink as we enumerate it + for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0) { + domConstruct.place( cont[i], node, "last"); + } + } else { + // pass nodes, documentFragments and unknowns through to dojo.place + domConstruct.place(cont, node, "last"); + } + } - // maxLength: String - // HTML INPUT tag maxLength declaration. - maxLength: "", + // return DomNode + return node; + }; - // selectOnClick: [const] Boolean - // If true, all text will be selected when focused with mouse - selectOnClick: false, + // we wrap up the content-setting operation in a object + declare("dojo.html._ContentSetter", null, + { + // node: DomNode|String + // An node which will be the parent element that we set content into + node: "", - // placeHolder: String - // Defines a hint to help users fill out the input field (as defined in HTML 5). - // This should only contain plain text (no html markup). - placeHolder: "", - - templateString: dojo.cache("dijit.form", "templates/TextBox.html", "<div class=\"dijit dijitReset dijitInline dijitLeft\" id=\"widget_${id}\" role=\"presentation\"\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"), - _singleNodeTemplate: '<input class="dijit dijitReset dijitLeft dijitInputField" dojoAttachPoint="textbox,focusNode" autocomplete="off" type="${type}" ${!nameAttrSetting} />', + // content: String|DomNode|DomNode[] + // The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes + content: "", - _buttonInputDisabled: dojo.isIE ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events + // id: String? + // Usually only used internally, and auto-generated with each instance + id: "", - baseClass: "dijitTextBox", + // cleanContent: Boolean + // Should the content be treated as a full html document, + // and the real content stripped of <html>, <body> wrapper before injection + cleanContent: false, - attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, { - maxLength: "focusNode" - }), - - postMixInProperties: function(){ - var type = this.type.toLowerCase(); - if(this.templateString && this.templateString.toLowerCase() == "input" || ((type == "hidden" || type == "file") && this.templateString == dijit.form.TextBox.prototype.templateString)){ - this.templateString = this._singleNodeTemplate; - } - this.inherited(arguments); - }, + // extractContent: Boolean + // Should the content be treated as a full html document, and the real content stripped of <html>, <body> wrapper before injection + extractContent: false, - _setPlaceHolderAttr: function(v){ - this._set("placeHolder", v); - if(!this._phspan){ - this._attachPoints.push('_phspan'); - /* dijitInputField class gives placeHolder same padding as the input field - * parent node already has dijitInputField class but it doesn't affect this <span> - * since it's position: absolute. - */ - this._phspan = dojo.create('span',{className:'dijitPlaceHolder dijitInputField'},this.textbox,'after'); - } - this._phspan.innerHTML=""; - this._phspan.appendChild(document.createTextNode(v)); - - this._updatePlaceHolder(); - }, - - _updatePlaceHolder: function(){ - if(this._phspan){ - this._phspan.style.display=(this.placeHolder&&!this._focused&&!this.textbox.value)?"":"none"; - } - }, + // parseContent: Boolean + // Should the node by passed to the parser after the new content is set + parseContent: false, - _getValueAttr: function(){ - // summary: - // Hook so get('value') works as we like. - // description: - // For `dijit.form.TextBox` this basically returns the value of the <input>. - // - // For `dijit.form.MappedTextBox` subclasses, which have both - // a "displayed value" and a separate "submit value", - // This treats the "displayed value" as the master value, computing the - // submit value from it via this.parse(). - return this.parse(this.get('displayedValue'), this.constraints); - }, + // parserScope: String + // Flag passed to parser. Root for attribute names to search for. If scopeName is dojo, + // will search for data-dojo-type (or dojoType). For backwards compatibility + // reasons defaults to dojo._scopeName (which is "dojo" except when + // multi-version support is used, when it will be something like dojo16, dojo20, etc.) + parserScope: dojo._scopeName, - _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){ - // summary: - // Hook so set('value', ...) works. - // - // description: - // Sets the value of the widget to "value" which can be of - // any type as determined by the widget. - // - // value: - // The visual element value is also set to a corresponding, - // but not necessarily the same, value. - // - // formattedValue: - // If specified, used to set the visual element value, - // otherwise a computed visual value is used. - // - // priorityChange: - // If true, an onChange event is fired immediately instead of - // waiting for the next blur event. + // startup: Boolean + // Start the child widgets after parsing them. Only obeyed if parseContent is true. + startup: true, - var filteredValue; - if(value !== undefined){ - // TODO: this is calling filter() on both the display value and the actual value. - // I added a comment to the filter() definition about this, but it should be changed. - filteredValue = this.filter(value); - if(typeof formattedValue != "string"){ - if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){ - formattedValue = this.filter(this.format(filteredValue, this.constraints)); - }else{ formattedValue = ''; } - } - } - if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){ - this.textbox.value = formattedValue; - this._set("displayedValue", this.get("displayedValue")); - } + // lifecyle methods + constructor: function(/* Object */params, /* String|DomNode */node){ + // summary: + // Provides a configurable, extensible object to wrap the setting on content on a node + // call the set() method to actually set the content.. - this._updatePlaceHolder(); + // the original params are mixed directly into the instance "this" + lang.mixin(this, params || {}); - this.inherited(arguments, [filteredValue, priorityChange]); - }, + // give precedence to params.node vs. the node argument + // and ensure its a node, not an id string + node = this.node = dom.byId( this.node || node ); - // displayedValue: String - // For subclasses like ComboBox where the displayed value - // (ex: Kentucky) and the serialized value (ex: KY) are different, - // this represents the displayed value. - // - // Setting 'displayedValue' through set('displayedValue', ...) - // updates 'value', and vice-versa. Otherwise 'value' is updated - // from 'displayedValue' periodically, like onBlur etc. - // - // TODO: move declaration to MappedTextBox? - // Problem is that ComboBox references displayedValue, - // for benefit of FilteringSelect. - displayedValue: "", + if(!this.id){ + this.id = [ + "Setter", + (node) ? node.id || node.tagName : "", + idCounter++ + ].join("_"); + } + }, + set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){ + // summary: + // front-end to the set-content sequence + // cont: + // An html string, node or enumerable list of nodes for insertion into the dom + // If not provided, the object's content property will be used + if(undefined !== cont){ + this.content = cont; + } + // in the re-use scenario, set needs to be able to mixin new configuration + if(params){ + this._mixin(params); + } - getDisplayedValue: function(){ - // summary: - // Deprecated. Use get('displayedValue') instead. - // tags: - // deprecated - dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use set('displayedValue') instead.", "", "2.0"); - return this.get('displayedValue'); - }, + this.onBegin(); + this.setContent(); + this.onEnd(); - _getDisplayedValueAttr: function(){ - // summary: - // Hook so get('displayedValue') works. - // description: - // Returns the displayed value (what the user sees on the screen), - // after filtering (ie, trimming spaces etc.). - // - // For some subclasses of TextBox (like ComboBox), the displayed value - // is different from the serialized value that's actually - // sent to the server (see dijit.form.ValidationTextBox.serialize) + return this.node; + }, + setContent: function(){ + // summary: + // sets the content on the node - // TODO: maybe we should update this.displayedValue on every keystroke so that we don't need - // this method - // TODO: this isn't really the displayed value when the user is typing - return this.filter(this.textbox.value); - }, + var node = this.node; + if(!node) { + // can't proceed + throw new Error(this.declaredClass + ": setContent given no node"); + } + try{ + node = dojo.html._setNodeContent(node, this.content); + }catch(e){ + // check if a domfault occurs when we are appending this.errorMessage + // like for instance if domNode is a UL and we try append a DIV - setDisplayedValue: function(/*String*/ value){ - // summary: - // Deprecated. Use set('displayedValue', ...) instead. - // tags: - // deprecated - dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use set('displayedValue', ...) instead.", "", "2.0"); - this.set('displayedValue', value); - }, + // FIXME: need to allow the user to provide a content error message string + var errMess = this.onContentError(e); + try{ + node.innerHTML = errMess; + }catch(e){ + console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e); + } + } + // always put back the node for the next method + this.node = node; // DomNode + }, - _setDisplayedValueAttr: function(/*String*/ value){ - // summary: - // Hook so set('displayedValue', ...) works. - // description: - // Sets the value of the visual element to the string "value". - // The widget value is also set to a corresponding, - // but not necessarily the same, value. + empty: function() { + // summary + // cleanly empty out existing content - if(value === null || value === undefined){ value = '' } - else if(typeof value != "string"){ value = String(value) } + // destroy any widgets from a previous run + // NOTE: if you dont want this you'll need to empty + // the parseResults array property yourself to avoid bad things happenning + if(this.parseResults && this.parseResults.length) { + darray.forEach(this.parseResults, function(w) { + if(w.destroy){ + w.destroy(); + } + }); + delete this.parseResults; + } + // this is fast, but if you know its already empty or safe, you could + // override empty to skip this step + dojo.html._emptyNode(this.node); + }, - this.textbox.value = value; + onBegin: function(){ + // summary + // Called after instantiation, but before set(); + // It allows modification of any of the object properties + // - including the node and content provided - before the set operation actually takes place + // This default implementation checks for cleanContent and extractContent flags to + // optionally pre-process html string content + var cont = this.content; - // sets the serialized value to something corresponding to specified displayedValue - // (if possible), and also updates the textbox.value, for example converting "123" - // to "123.00" - this._setValueAttr(this.get('value'), undefined); + if(lang.isString(cont)){ + if(this.cleanContent){ + cont = dojo.html._secureForInnerHtml(cont); + } - this._set("displayedValue", this.get('displayedValue')); - }, + if(this.extractContent){ + var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); + if(match){ cont = match[1]; } + } + } - format: function(/*String*/ value, /*Object*/ constraints){ - // summary: - // Replacable function to convert a value to a properly formatted string. - // tags: - // protected extension - return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value)); - }, + // clean out the node and any cruft associated with it - like widgets + this.empty(); - parse: function(/*String*/ value, /*Object*/ constraints){ - // summary: - // Replacable function to convert a formatted string to a value - // tags: - // protected extension + this.content = cont; + return this.node; /* DomNode */ + }, - return value; // String - }, + onEnd: function(){ + // summary + // Called after set(), when the new content has been pushed into the node + // It provides an opportunity for post-processing before handing back the node to the caller + // This default implementation checks a parseContent flag to optionally run the dojo parser over the new content + if(this.parseContent){ + // populates this.parseResults if you need those.. + this._parse(); + } + return this.node; /* DomNode */ + }, - _refreshState: function(){ - // summary: - // After the user types some characters, etc., this method is - // called to check the field for validity etc. The base method - // in `dijit.form.TextBox` does nothing, but subclasses override. - // tags: - // protected - }, + tearDown: function(){ + // summary + // manually reset the Setter instance if its being re-used for example for another set() + // description + // tearDown() is not called automatically. + // In normal use, the Setter instance properties are simply allowed to fall out of scope + // but the tearDown method can be called to explicitly reset this instance. + delete this.parseResults; + delete this.node; + delete this.content; + }, - _onInput: function(e){ - if(e && e.type && /key/i.test(e.type) && e.keyCode){ - switch(e.keyCode){ - case dojo.keys.SHIFT: - case dojo.keys.ALT: - case dojo.keys.CTRL: - case dojo.keys.TAB: - return; - } - } - if(this.intermediateChanges){ - var _this = this; - // the setTimeout allows the key to post to the widget input box - setTimeout(function(){ _this._handleOnChange(_this.get('value'), false); }, 0); - } - this._refreshState(); + onContentError: function(err){ + return "Error occured setting content: " + err; + }, - // In case someone is watch()'ing for changes to displayedValue - this._set("displayedValue", this.get("displayedValue")); - }, + _mixin: function(params){ + // mix properties/methods into the instance + // TODO: the intention with tearDown is to put the Setter's state + // back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params) + // so we could do something here to move the original properties aside for later restoration + var empty = {}, key; + for(key in params){ + if(key in empty){ continue; } + // TODO: here's our opportunity to mask the properties we dont consider configurable/overridable + // .. but history shows we'll almost always guess wrong + this[key] = params[key]; + } + }, + _parse: function(){ + // summary: + // runs the dojo parser over the node contents, storing any results in this.parseResults + // Any errors resulting from parsing are passed to _onError for handling - postCreate: function(){ - if(dojo.isIE){ // IE INPUT tag fontFamily has to be set directly using STYLE - // the setTimeout gives IE a chance to render the TextBox and to deal with font inheritance - setTimeout(dojo.hitch(this, function(){ - var s = dojo.getComputedStyle(this.domNode); - if(s){ - var ff = s.fontFamily; - if(ff){ - var inputs = this.domNode.getElementsByTagName("INPUT"); - if(inputs){ - for(var i=0; i < inputs.length; i++){ - inputs[i].style.fontFamily = ff; - } + var rootNode = this.node; + try{ + // store the results (widgets, whatever) for potential retrieval + var inherited = {}; + darray.forEach(["dir", "lang", "textDir"], function(name){ + if(this[name]){ + inherited[name] = this[name]; } - } + }, this); + this.parseResults = parser.parse({ + rootNode: rootNode, + noStart: !this.startup, + inherited: inherited, + scope: this.parserScope + }); + }catch(e){ + this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id); } - }), 0); - } - - // setting the value here is needed since value="" in the template causes "undefined" - // and setting in the DOM (instead of the JS object) helps with form reset actions - this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values should be the same - - this.inherited(arguments); + }, - if(dojo.isMoz || dojo.isOpera){ - this.connect(this.textbox, "oninput", "_onInput"); - }else{ - this.connect(this.textbox, "onkeydown", "_onInput"); - this.connect(this.textbox, "onkeyup", "_onInput"); - this.connect(this.textbox, "onpaste", "_onInput"); - this.connect(this.textbox, "oncut", "_onInput"); + _onError: function(type, err, consoleText){ + // summary: + // shows user the string that is returned by on[type]Error + // overide/implement on[type]Error and return your own string to customize + var errText = this['on' + type + 'Error'].call(this, err); + if(consoleText){ + console.error(consoleText, err); + }else if(errText){ // a empty string won't change current content + dojo.html._setNodeContent(this.node, errText, true); + } } - }, + }); // end dojo.declare() - _blankValue: '', // if the textbox is blank, what value should be reported - filter: function(val){ + dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){ // summary: - // Auto-corrections (such as trimming) that are applied to textbox - // value on blur or form submit. + // inserts (replaces) the given content into the given node. dojo.place(cont, node, "only") + // may be a better choice for simple HTML insertion. // description: - // For MappedTextBox subclasses, this is called twice - // - once with the display value - // - once the value as set/returned by set('value', ...) - // and get('value'), ex: a Number for NumberTextBox. - // - // In the latter case it does corrections like converting null to NaN. In - // the former case the NumberTextBox.filter() method calls this.inherited() - // to execute standard trimming code in TextBox.filter(). - // - // TODO: break this into two methods in 2.0 - // - // tags: - // protected extension - if(val === null){ return this._blankValue; } - if(typeof val != "string"){ return val; } - if(this.trim){ - val = dojo.trim(val); - } - if(this.uppercase){ - val = val.toUpperCase(); - } - if(this.lowercase){ - val = val.toLowerCase(); - } - if(this.propercase){ - val = val.replace(/[^\s]+/g, function(word){ - return word.substring(0,1).toUpperCase() + word.substring(1); - }); - } - return val; - }, + // Unless you need to use the params capabilities of this method, you should use + // dojo.place(cont, node, "only"). dojo.place() has more robust support for injecting + // an HTML string into the DOM, but it only handles inserting an HTML string as DOM + // elements, or inserting a DOM node. dojo.place does not handle NodeList insertions + // or the other capabilities as defined by the params object for this method. + // node: + // the parent element that will receive the content + // cont: + // the content to be set on the parent element. + // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes + // params: + // Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter + // example: + // A safe string/node/nodelist content replacement/injection with hooks for extension + // Example Usage: + // dojo.html.set(node, "some string"); + // dojo.html.set(node, contentNode, {options}); + // dojo.html.set(node, myNode.childNodes, {options}); + if(undefined == cont){ + console.warn("dojo.html.set: no cont argument provided, using empty string"); + cont = ""; + } + if(!params){ + // simple and fast + return dojo.html._setNodeContent(node, cont, true); + }else{ + // more options but slower + // note the arguments are reversed in order, to match the convention for instantiation via the parser + var op = new dojo.html._ContentSetter(lang.mixin( + params, + { content: cont, node: node } + )); + return op.set(); + } + }; - _setBlurValue: function(){ - this._setValueAttr(this.get('value'), true); - }, + return dojo.html; +}); - _onBlur: function(e){ - if(this.disabled){ return; } - this._setBlurValue(); - this.inherited(arguments); +}, +'dijit/_PaletteMixin':function(){ +define("dijit/_PaletteMixin", [ + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/dom-class", // domClass.add domClass.remove + "dojo/dom-construct", // domConstruct.create domConstruct.place + "dojo/_base/event", // event.stop + "dojo/keys", // keys + "dojo/_base/lang", // lang.getObject + "./_CssStateMixin", + "./focus", + "./typematic" +], function(declare, domAttr, domClass, domConstruct, event, keys, lang, _CssStateMixin, focus, typematic){ - if(this._selectOnClickHandle){ - this.disconnect(this._selectOnClickHandle); - } - if(this.selectOnClick && dojo.isMoz){ - this.textbox.selectionStart = this.textbox.selectionEnd = undefined; // clear selection so that the next mouse click doesn't reselect - } - - this._updatePlaceHolder(); - }, +/*===== + var _CssStateMixin = dijit._CssStateMixin; +=====*/ - _onFocus: function(/*String*/ by){ - if(this.disabled || this.readOnly){ return; } +// module: +// dijit/_PaletteMixin +// summary: +// A keyboard accessible palette, for picking a color/emoticon/etc. - // Select all text on focus via click if nothing already selected. - // Since mouse-up will clear the selection need to defer selection until after mouse-up. - // Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event. - if(this.selectOnClick && by == "mouse"){ - this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){ - // Only select all text on first click; otherwise users would have no way to clear - // the selection. - this.disconnect(this._selectOnClickHandle); - - // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up) - // and if not, then select all the text - var textIsNotSelected; - if(dojo.isIE){ - var range = dojo.doc.selection.createRange(); - var parent = range.parentElement(); - textIsNotSelected = parent == this.textbox && range.text.length == 0; - }else{ - textIsNotSelected = this.textbox.selectionStart == this.textbox.selectionEnd; - } - if(textIsNotSelected){ - dijit.selectInputText(this.textbox); - } - }); - } +return declare("dijit._PaletteMixin", [_CssStateMixin], { + // summary: + // A keyboard accessible palette, for picking a color/emoticon/etc. + // description: + // A mixin for a grid showing various entities, so the user can pick a certain entity. - this._updatePlaceHolder(); - - // call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport - // (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip - this.inherited(arguments); + // defaultTimeout: Number + // Number of milliseconds before a held key or button becomes typematic + defaultTimeout: 500, - this._refreshState(); - }, + // timeoutChangeRate: Number + // Fraction of time used to change the typematic timer between events + // 1.0 means that each typematic event fires at defaultTimeout intervals + // < 1.0 means that each typematic event fires at an increasing faster rate + timeoutChangeRate: 0.90, - reset: function(){ - // Overrides dijit._FormWidget.reset(). - // Additionally resets the displayed textbox value to '' - this.textbox.value = ''; - this.inherited(arguments); - } - } -); + // value: String + // Currently selected color/emoticon/etc. + value: "", -dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){ - // summary: - // Select text in the input element argument, from start (default 0), to stop (default end). + // _selectedCell: [private] Integer + // Index of the currently selected cell. Initially, none selected + _selectedCell: -1, - // TODO: use functions in _editor/selection.js? - var _window = dojo.global; - var _document = dojo.doc; - element = dojo.byId(element); - if(isNaN(start)){ start = 0; } - if(isNaN(stop)){ stop = element.value ? element.value.length : 0; } - dijit.focus(element); - if(_document["selection"] && dojo.body()["createTextRange"]){ // IE - if(element.createTextRange){ - var r = element.createTextRange(); - r.collapse(true); - r.moveStart("character", -99999); // move to 0 - r.moveStart("character", start); // delta from 0 is the correct position - r.moveEnd("character", stop-start); - r.select(); - } - }else if(_window["getSelection"]){ - if(element.setSelectionRange){ - element.setSelectionRange(start, stop); - } - } -}; +/*===== + // _currentFocus: [private] DomNode + // The currently focused cell (if the palette itself has focus), or otherwise + // the cell to be focused when the palette itself gets focus. + // Different from value, which represents the selected (i.e. clicked) cell. + _currentFocus: null, +=====*/ -} +/*===== + // _xDim: [protected] Integer + // This is the number of cells horizontally across. + _xDim: null, +=====*/ -if(!dojo._hasResource["dijit.Tooltip"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.Tooltip"] = true; -dojo.provide("dijit.Tooltip"); +/*===== + // _yDim: [protected] Integer + // This is the number of cells vertically down. + _yDim: null, +=====*/ + // tabIndex: String + // Widget tab index. + tabIndex: "0", + // cellClass: [protected] String + // CSS class applied to each cell in the palette + cellClass: "dijitPaletteCell", + // dyeClass: [protected] String + // Name of javascript class for Object created for each cell of the palette. + // dyeClass should implements dijit.Dye interface + dyeClass: '', + + // summary: String + // Localized summary for the palette table + summary: '', + _setSummaryAttr: "paletteTableNode", -dojo.declare( - "dijit._MasterTooltip", - [dijit._Widget, dijit._Templated], - { + _dyeFactory: function(value /*===== , row, col =====*/){ // summary: - // Internal widget that holds the actual tooltip markup, - // which occurs once per page. - // Called by Tooltip widgets which are just containers to hold - // the markup + // Return instance of dijit.Dye for specified cell of palette // tags: - // protected - - // duration: Integer - // Milliseconds to fade in/fade out - duration: dijit.defaultDuration, - - templateString: dojo.cache("dijit", "templates/Tooltip.html", "<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\"\n\t><div class=\"dijitTooltipContainer dijitTooltipContents\" dojoAttachPoint=\"containerNode\" role='alert'></div\n\t><div class=\"dijitTooltipConnector\" dojoAttachPoint=\"connectorNode\"></div\n></div>\n"), - - postCreate: function(){ - dojo.body().appendChild(this.domNode); - - this.bgIframe = new dijit.BackgroundIframe(this.domNode); + // extension + var dyeClassObj = lang.getObject(this.dyeClass); + return new dyeClassObj(value); + }, - // Setup fade-in and fade-out functions. - this.fadeIn = dojo.fadeIn({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onShow") }); - this.fadeOut = dojo.fadeOut({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onHide") }); - }, + _preparePalette: function(choices, titles) { + // summary: + // Subclass must call _preparePalette() from postCreate(), passing in the tooltip + // for each cell + // choices: String[][] + // id's for each cell of the palette, used to create Dye JS object for each cell + // titles: String[] + // Localized tooltip for each cell - show: function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){ - // summary: - // Display tooltip w/specified contents to right of specified node - // (To left if there's no space on the right, or if rtl == true) + this._cells = []; + var url = this._blankGif; - if(this.aroundNode && this.aroundNode === aroundNode){ - return; - } + this.connect(this.gridNode, "ondijitclick", "_onCellClick"); - // reset width; it may have been set by orient() on a previous tooltip show() - this.domNode.width = "auto"; + for(var row=0; row < choices.length; row++){ + var rowNode = domConstruct.create("tr", {tabIndex: "-1"}, this.gridNode); + for(var col=0; col < choices[row].length; col++){ + var value = choices[row][col]; + if(value){ + var cellObject = this._dyeFactory(value, row, col); - if(this.fadeOut.status() == "playing"){ - // previous tooltip is being hidden; wait until the hide completes then show new one - this._onDeck=arguments; - return; - } - this.containerNode.innerHTML=innerHTML; + var cellNode = domConstruct.create("td", { + "class": this.cellClass, + tabIndex: "-1", + title: titles[value], + role: "gridcell" + }); - var pos = dijit.placeOnScreenAroundElement(this.domNode, aroundNode, dijit.getPopupAroundAlignment((position && position.length) ? position : dijit.Tooltip.defaultPosition, !rtl), dojo.hitch(this, "orient")); + // prepare cell inner structure + cellObject.fillCell(cellNode, url); - // show it - dojo.style(this.domNode, "opacity", 0); - this.fadeIn.play(); - this.isShowingNow = true; - this.aroundNode = aroundNode; - }, + domConstruct.place(cellNode, rowNode); - orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ tooltipCorner, /*Object*/ spaceAvailable, /*Object*/ aroundNodeCoords){ - // summary: - // Private function to set CSS for tooltip node based on which position it's in. - // This is called by the dijit popup code. It will also reduce the tooltip's - // width to whatever width is available - // tags: - // protected - this.connectorNode.style.top = ""; //reset to default - - //Adjust the spaceAvailable width, without changing the spaceAvailable object - var tooltipSpaceAvaliableWidth = spaceAvailable.w - this.connectorNode.offsetWidth; + cellNode.index = this._cells.length; - node.className = "dijitTooltip " + - { - "BL-TL": "dijitTooltipBelow dijitTooltipABLeft", - "TL-BL": "dijitTooltipAbove dijitTooltipABLeft", - "BR-TR": "dijitTooltipBelow dijitTooltipABRight", - "TR-BR": "dijitTooltipAbove dijitTooltipABRight", - "BR-BL": "dijitTooltipRight", - "BL-BR": "dijitTooltipLeft" - }[aroundCorner + "-" + tooltipCorner]; - - // reduce tooltip's width to the amount of width available, so that it doesn't overflow screen - this.domNode.style.width = "auto"; - var size = dojo.contentBox(this.domNode); - - var width = Math.min((Math.max(tooltipSpaceAvaliableWidth,1)), size.w); - var widthWasReduced = width < size.w; - - this.domNode.style.width = width+"px"; - - //Adjust width for tooltips that have a really long word or a nowrap setting - if(widthWasReduced){ - this.containerNode.style.overflow = "auto"; //temp change to overflow to detect if our tooltip needs to be wider to support the content - var scrollWidth = this.containerNode.scrollWidth; - this.containerNode.style.overflow = "visible"; //change it back - if(scrollWidth > width){ - scrollWidth = scrollWidth + dojo.style(this.domNode,"paddingLeft") + dojo.style(this.domNode,"paddingRight"); - this.domNode.style.width = scrollWidth + "px"; - } - } - - // Reposition the tooltip connector. - if(tooltipCorner.charAt(0) == 'B' && aroundCorner.charAt(0) == 'B'){ - var mb = dojo.marginBox(node); - var tooltipConnectorHeight = this.connectorNode.offsetHeight; - if(mb.h > spaceAvailable.h){ - // The tooltip starts at the top of the page and will extend past the aroundNode - var aroundNodePlacement = spaceAvailable.h - (aroundNodeCoords.h / 2) - (tooltipConnectorHeight / 2); - this.connectorNode.style.top = aroundNodePlacement + "px"; - this.connectorNode.style.bottom = ""; - }else{ - // Align center of connector with center of aroundNode, except don't let bottom - // of connector extend below bottom of tooltip content, or top of connector - // extend past top of tooltip content - this.connectorNode.style.bottom = Math.min( - Math.max(aroundNodeCoords.h/2 - tooltipConnectorHeight/2, 0), - mb.h - tooltipConnectorHeight) + "px"; - this.connectorNode.style.top = ""; + // save cell info into _cells + this._cells.push({node:cellNode, dye:cellObject}); } - }else{ - // reset the tooltip back to the defaults - this.connectorNode.style.top = ""; - this.connectorNode.style.bottom = ""; - } - - return Math.max(0, size.w - tooltipSpaceAvaliableWidth); - }, - - _onShow: function(){ - // summary: - // Called at end of fade-in operation - // tags: - // protected - if(dojo.isIE){ - // the arrow won't show up on a node w/an opacity filter - this.domNode.style.filter=""; - } - }, - - hide: function(aroundNode){ - // summary: - // Hide the tooltip - - if(this._onDeck && this._onDeck[1] == aroundNode){ - // this hide request is for a show() that hasn't even started yet; - // just cancel the pending show() - this._onDeck=null; - }else if(this.aroundNode === aroundNode){ - // this hide request is for the currently displayed tooltip - this.fadeIn.stop(); - this.isShowingNow = false; - this.aroundNode = null; - this.fadeOut.play(); - }else{ - // just ignore the call, it's for a tooltip that has already been erased } - }, + } + this._xDim = choices[0].length; + this._yDim = choices.length; - _onHide: function(){ - // summary: - // Called at end of fade-out operation - // tags: - // protected + // Now set all events + // The palette itself is navigated to with the tab key on the keyboard + // Keyboard navigation within the Palette is with the arrow keys + // Spacebar selects the cell. + // For the up key the index is changed by negative the x dimension. - this.domNode.style.cssText=""; // to position offscreen again - this.containerNode.innerHTML=""; - if(this._onDeck){ - // a show request has been queued up; do it now - this.show.apply(this, this._onDeck); - this._onDeck=null; - } + var keyIncrementMap = { + UP_ARROW: -this._xDim, + // The down key the index is increase by the x dimension. + DOWN_ARROW: this._xDim, + // Right and left move the index by 1. + RIGHT_ARROW: this.isLeftToRight() ? 1 : -1, + LEFT_ARROW: this.isLeftToRight() ? -1 : 1 + }; + for(var key in keyIncrementMap){ + this._connects.push( + typematic.addKeyListener( + this.domNode, + {charOrCode:keys[key], ctrlKey:false, altKey:false, shiftKey:false}, + this, + function(){ + var increment = keyIncrementMap[key]; + return function(count){ this._navigateByKey(increment, count); }; + }(), + this.timeoutChangeRate, + this.defaultTimeout + ) + ); } + }, - } -); - -dijit.showTooltip = function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position, /*Boolean*/ rtl){ - // summary: - // Display tooltip w/specified contents in specified position. - // See description of dijit.Tooltip.defaultPosition for details on position parameter. - // If position is not specified then dijit.Tooltip.defaultPosition is used. - if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); } - return dijit._masterTT.show(innerHTML, aroundNode, position, rtl); -}; + postCreate: function(){ + this.inherited(arguments); -dijit.hideTooltip = function(aroundNode){ - // summary: - // Hide the tooltip - if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); } - return dijit._masterTT.hide(aroundNode); -}; + // Set initial navigable node. + this._setCurrent(this._cells[0].node); + }, -dojo.declare( - "dijit.Tooltip", - dijit._Widget, - { + focus: function(){ // summary: - // Pops up a tooltip (a help message) when you hover over a node. - - // label: String - // Text to display in the tooltip. - // Specified as innerHTML when creating the widget from markup. - label: "", - - // showDelay: Integer - // Number of milliseconds to wait after hovering over/focusing on the object, before - // the tooltip is displayed. - showDelay: 400, - - // connectId: String|String[] - // Id of domNode(s) to attach the tooltip to. - // When user hovers over specified dom node, the tooltip will appear. - connectId: [], - - // position: String[] - // See description of `dijit.Tooltip.defaultPosition` for details on position parameter. - position: [], - - _setConnectIdAttr: function(/*String*/ newId){ - // summary: - // Connect to node(s) (specified by id) - - // Remove connections to old nodes (if there are any) - dojo.forEach(this._connections || [], function(nested){ - dojo.forEach(nested, dojo.hitch(this, "disconnect")); - }, this); - - // Make connections to nodes in newIds. - var ary = dojo.isArrayLike(newId) ? newId : (newId ? [newId] : []); - this._connections = dojo.map(ary, function(id){ - var node = dojo.byId(id); - return node ? [ - this.connect(node, "onmouseenter", "_onTargetMouseEnter"), - this.connect(node, "onmouseleave", "_onTargetMouseLeave"), - this.connect(node, "onfocus", "_onTargetFocus"), - this.connect(node, "onblur", "_onTargetBlur") - ] : []; - }, this); - - this._set("connectId", newId); + // Focus this widget. Puts focus on the most recently focused cell. - this._connectIds = ary; // save as array - }, + // The cell already has tabIndex set, just need to set CSS and focus it + focus.focus(this._currentFocus); + }, - addTarget: function(/*DOMNODE || String*/ node){ - // summary: - // Attach tooltip to specified node if it's not already connected + _onCellClick: function(/*Event*/ evt){ + // summary: + // Handler for click, enter key & space key. Selects the cell. + // evt: + // The event. + // tags: + // private - // TODO: remove in 2.0 and just use set("connectId", ...) interface + var target = evt.target; - var id = node.id || node; - if(dojo.indexOf(this._connectIds, id) == -1){ - this.set("connectId", this._connectIds.concat(id)); + // Find TD associated with click event. For ColorPalette user likely clicked IMG inside of TD + while(target.tagName != "TD"){ + if(!target.parentNode || target == this.gridNode){ // probably can never happen, but just in case + return; } - }, + target = target.parentNode; + } - removeTarget: function(/*DOMNODE || String*/ node){ - // summary: - // Detach tooltip from specified node + var value = this._getDye(target).getValue(); - // TODO: remove in 2.0 and just use set("connectId", ...) interface - - var id = node.id || node, // map from DOMNode back to plain id string - idx = dojo.indexOf(this._connectIds, id); - if(idx >= 0){ - // remove id (modifies original this._connectIds but that's OK in this case) - this._connectIds.splice(idx, 1); - this.set("connectId", this._connectIds); - } - }, + // First focus the clicked cell, and then send onChange() notification. + // onChange() (via _setValueAttr) must be after the focus call, because + // it may trigger a refocus to somewhere else (like the Editor content area), and that + // second focus should win. + this._setCurrent(target); + focus.focus(target); + this._setValueAttr(value, true); - buildRendering: function(){ - this.inherited(arguments); - dojo.addClass(this.domNode,"dijitTooltipData"); - }, + event.stop(evt); + }, - startup: function(){ - this.inherited(arguments); + _setCurrent: function(/*DomNode*/ node){ + // summary: + // Sets which node is the focused cell. + // description: + // At any point in time there's exactly one + // cell with tabIndex != -1. If focus is inside the palette then + // focus is on that cell. + // + // After calling this method, arrow key handlers and mouse click handlers + // should focus the cell in a setTimeout(). + // tags: + // protected + if("_currentFocus" in this){ + // Remove tabIndex on old cell + domAttr.set(this._currentFocus, "tabIndex", "-1"); + } - // If this tooltip was created in a template, or for some other reason the specified connectId[s] - // didn't exist during the widget's initialization, then connect now. - var ids = this.connectId; - dojo.forEach(dojo.isArrayLike(ids) ? ids : [ids], this.addTarget, this); - }, + // Set tabIndex of new cell + this._currentFocus = node; + if(node){ + domAttr.set(node, "tabIndex", this.tabIndex); + } + }, - _onTargetMouseEnter: function(/*Event*/ e){ - // summary: - // Handler for mouseenter event on the target node - // tags: - // private - this._onHover(e); - }, + _setValueAttr: function(value, priorityChange){ + // summary: + // This selects a cell. It triggers the onChange event. + // value: String value of the cell to select + // tags: + // protected + // priorityChange: + // Optional parameter used to tell the select whether or not to fire + // onChange event. - _onTargetMouseLeave: function(/*Event*/ e){ - // summary: - // Handler for mouseleave event on the target node - // tags: - // private - this._onUnHover(e); - }, + // clear old selected cell + if(this._selectedCell >= 0){ + domClass.remove(this._cells[this._selectedCell].node, this.cellClass + "Selected"); + } + this._selectedCell = -1; - _onTargetFocus: function(/*Event*/ e){ - // summary: - // Handler for focus event on the target node - // tags: - // private + // search for cell matching specified value + if(value){ + for(var i = 0; i < this._cells.length; i++){ + if(value == this._cells[i].dye.getValue()){ + this._selectedCell = i; + domClass.add(this._cells[i].node, this.cellClass + "Selected"); + break; + } + } + } - this._focus = true; - this._onHover(e); - }, + // record new value, or null if no matching cell + this._set("value", this._selectedCell >= 0 ? value : null); - _onTargetBlur: function(/*Event*/ e){ - // summary: - // Handler for blur event on the target node - // tags: - // private + if(priorityChange || priorityChange === undefined){ + this.onChange(value); + } + }, - this._focus = false; - this._onUnHover(e); - }, + onChange: function(/*===== value =====*/){ + // summary: + // Callback when a cell is selected. + // value: String + // Value corresponding to cell. + }, - _onHover: function(/*Event*/ e){ - // summary: - // Despite the name of this method, it actually handles both hover and focus - // events on the target node, setting a timer to show the tooltip. - // tags: - // private - if(!this._showTimer){ - var target = e.target; - this._showTimer = setTimeout(dojo.hitch(this, function(){this.open(target)}), this.showDelay); - } - }, + _navigateByKey: function(increment, typeCount){ + // summary: + // This is the callback for typematic. + // It changes the focus and the highlighed cell. + // increment: + // How much the key is navigated. + // typeCount: + // How many times typematic has fired. + // tags: + // private - _onUnHover: function(/*Event*/ e){ - // summary: - // Despite the name of this method, it actually handles both mouseleave and blur - // events on the target node, hiding the tooltip. - // tags: - // private + // typecount == -1 means the key is released. + if(typeCount == -1){ return; } - // keep a tooltip open if the associated element still has focus (even though the - // mouse moved away) - if(this._focus){ return; } + var newFocusIndex = this._currentFocus.index + increment; + if(newFocusIndex < this._cells.length && newFocusIndex > -1){ + var focusNode = this._cells[newFocusIndex].node; + this._setCurrent(focusNode); - if(this._showTimer){ - clearTimeout(this._showTimer); - delete this._showTimer; - } - this.close(); - }, + // Actually focus the node, for the benefit of screen readers. + // Use setTimeout because IE doesn't like changing focus inside of an event handler + setTimeout(lang.hitch(dijit, "focus", focusNode), 0); + } + }, - open: function(/*DomNode*/ target){ - // summary: - // Display the tooltip; usually not called directly. - // tags: - // private + _getDye: function(/*DomNode*/ cell){ + // summary: + // Get JS object for given cell DOMNode - if(this._showTimer){ - clearTimeout(this._showTimer); - delete this._showTimer; - } - dijit.showTooltip(this.label || this.domNode.innerHTML, target, this.position, !this.isLeftToRight()); + return this._cells[cell.index].dye; + } +}); - this._connectNode = target; - this.onShow(target, this.position); - }, +/*===== +declare("dijit.Dye", + null, + { + // summary: + // Interface for the JS Object associated with a palette cell (i.e. DOMNode) - close: function(){ + constructor: function(alias, row, col){ // summary: - // Hide the tooltip or cancel timer for show of tooltip - // tags: - // private - - if(this._connectNode){ - // if tooltip is currently shown - dijit.hideTooltip(this._connectNode); - delete this._connectNode; - this.onHide(); - } - if(this._showTimer){ - // if tooltip is scheduled to be shown (after a brief delay) - clearTimeout(this._showTimer); - delete this._showTimer; - } + // Initialize according to value or alias like "white" + // alias: String }, - onShow: function(target, position){ + getValue: function(){ // summary: - // Called when the tooltip is shown - // tags: - // callback + // Return "value" of cell; meaning of "value" varies by subclass. + // description: + // For example color hex value, emoticon ascii value etc, entity hex value. }, - onHide: function(){ + fillCell: function(cell, blankGif){ // summary: - // Called when the tooltip is hidden - // tags: - // callback - }, - - uninitialize: function(){ - this.close(); - this.inherited(arguments); + // Add cell DOMNode inner structure + // cell: DomNode + // The surrounding cell + // blankGif: String + // URL for blank cell image } } ); +=====*/ -// dijit.Tooltip.defaultPosition: String[] -// This variable controls the position of tooltips, if the position is not specified to -// the Tooltip widget or *TextBox widget itself. It's an array of strings with the following values: -// -// * before: places tooltip to the left of the target node/widget, or to the right in -// the case of RTL scripts like Hebrew and Arabic -// * after: places tooltip to the right of the target node/widget, or to the left in -// the case of RTL scripts like Hebrew and Arabic -// * above: tooltip goes above target node -// * below: tooltip goes below target node -// -// The list is positions is tried, in order, until a position is found where the tooltip fits -// within the viewport. -// -// Be careful setting this parameter. A value of "above" may work fine until the user scrolls -// the screen so that there's no room above the target node. Nodes with drop downs, like -// DropDownButton or FilteringSelect, are especially problematic, in that you need to be sure -// that the drop down and tooltip don't overlap, even when the viewport is scrolled so that there -// is only room below (or above) the target node, but not both. -dijit.Tooltip.defaultPosition = ["after", "before"]; - -} - -if(!dojo._hasResource["dijit.form.ValidationTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.ValidationTextBox"] = true; -dojo.provide("dijit.form.ValidationTextBox"); - +}); +}, +'dijit/form/ValidationTextBox':function(){ +require({cache:{ +'url:dijit/form/templates/ValidationTextBox.html':"<div class=\"dijit dijitReset dijitInline dijitLeft\"\n\tid=\"widget_${id}\" role=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" data-dojo-attach-point='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"}}); +define("dijit/form/ValidationTextBox", [ + "dojo/_base/declare", // declare + "dojo/i18n", // i18n.getLocalization + "./TextBox", + "../Tooltip", + "dojo/text!./templates/ValidationTextBox.html", + "dojo/i18n!./nls/validate" +], function(declare, i18n, TextBox, Tooltip, template){ +/*===== + var Tooltip = dijit.Tooltip; + var TextBox = dijit.form.TextBox; +=====*/ + // module: + // dijit/form/ValidationTextBox + // summary: + // Base class for textbox widgets with the ability to validate content of various types and provide user feedback. -/*===== - dijit.form.ValidationTextBox.__Constraints = function(){ - // locale: String - // locale used for validation, picks up value from this widget's lang attribute - // _flags_: anything - // various flags passed to regExpGen function - this.locale = ""; - this._flags_ = ""; - } -=====*/ + /*===== + dijit.form.ValidationTextBox.__Constraints = function(){ + // locale: String + // locale used for validation, picks up value from this widget's lang attribute + // _flags_: anything + // various flags passed to regExpGen function + this.locale = ""; + this._flags_ = ""; + } + =====*/ -dojo.declare( - "dijit.form.ValidationTextBox", - dijit.form.TextBox, - { + return declare("dijit.form.ValidationTextBox", TextBox, { // summary: // Base class for textbox widgets with the ability to validate content of various types and provide user feedback. // tags: // protected - templateString: dojo.cache("dijit.form", "templates/ValidationTextBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" role=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"), + templateString: template, baseClass: "dijitTextBox dijitValidationTextBox", // required: Boolean @@ -13584,7 +23102,7 @@ dojo.declare( // Do not specify both regExp and regExpGen regExp: ".*", - regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/ constraints){ + regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/ /*===== constraints =====*/){ // summary: // Overridable function used to generate regExp when dependent on constraints. // Do not specify both regExp and regExpGen. @@ -13605,7 +23123,7 @@ dojo.declare( // summary: // Hook so set('value', ...) works. this.inherited(arguments); - this.validate(this._focused); + this.validate(this.focused); }, validator: function(/*anything*/ value, /*dijit.form.ValidationTextBox.__Constraints*/ constraints){ @@ -13625,7 +23143,7 @@ dojo.declare( return this.textbox.value.search(this._partialre) == 0; }, - isValid: function(/*Boolean*/ isFocused){ + isValid: function(/*Boolean*/ /*===== isFocused =====*/){ // summary: // Tests if value is valid. // Can override with your own routine in a subclass. @@ -13640,7 +23158,7 @@ dojo.declare( return (this.trim ? /^\s*$/ : /^$/).test(value); // Boolean }, - getErrorMessage: function(/*Boolean*/ isFocused){ + getErrorMessage: function(/*Boolean*/ /*===== isFocused =====*/){ // summary: // Return an error message to show if appropriate // tags: @@ -13648,7 +23166,7 @@ dojo.declare( return (this.required && this._isEmpty(this.textbox.value)) ? this.missingMessage : this.invalidMessage; // String }, - getPromptMessage: function(/*Boolean*/ isFocused){ + getPromptMessage: function(/*Boolean*/ /*===== isFocused =====*/){ // summary: // Return a hint message to show when widget is first focused // tags: @@ -13670,7 +23188,7 @@ dojo.declare( var isEmpty = this._isEmpty(this.textbox.value); var isValidSubset = !isValid && isFocused && this._isValidSubset(); this._set("state", isValid ? "" : (((((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && this._maskValidSubsetError) ? "Incomplete" : "Error")); - dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true"); + this.focusNode.setAttribute("aria-invalid", isValid ? "false" : "true"); if(this.state == "Error"){ this._maskValidSubsetError = isFocused && isValidSubset; // we want the error to show up after a blur and refocus @@ -13692,15 +23210,16 @@ dojo.declare( // By default uses a tooltip. // tags: // extension - dijit.hideTooltip(this.domNode); - if(message && this._focused){ - dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight()); + if(message && this.focused){ + Tooltip.show(message, this.domNode, this.tooltipPosition, !this.isLeftToRight()); + }else{ + Tooltip.hide(this.domNode); } }, _refreshState: function(){ // Overrides TextBox._refreshState() - this.validate(this._focused); + this.validate(this.focused); this.inherited(arguments); }, @@ -13725,7 +23244,7 @@ dojo.declare( // parse the regexp and produce a new regexp that matches valid subsets // if the regexp is .* then there's no use in matching subsets since everything is valid if(p != ".*"){ this.regExp.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g, - function (re){ + function(re){ switch(re.charAt(0)){ case '{': case '+': @@ -13757,7 +23276,7 @@ dojo.declare( postMixInProperties: function(){ this.inherited(arguments); - this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang); + this.messages = i18n.getLocalization("dijit.form", "validate", this.lang); if(this.invalidMessage == "$_unset_$"){ this.invalidMessage = this.messages.invalidMessage; } if(!this.invalidMessage){ this.invalidMessage = this.promptMessage; } if(this.missingMessage == "$_unset_$"){ this.missingMessage = this.messages.missingMessage; } @@ -13772,7 +23291,7 @@ dojo.declare( _setRequiredAttr: function(/*Boolean*/ value){ this._set("required", value); - dijit.setWaiState(this.focusNode, "required", value); + this.focusNode.setAttribute("aria-required", value); this._refreshState(); }, @@ -13795,1438 +23314,1696 @@ dojo.declare( this.inherited(arguments); } - } -); - -dojo.declare( - "dijit.form.MappedTextBox", - dijit.form.ValidationTextBox, - { - // summary: - // A dijit.form.ValidationTextBox subclass which provides a base class for widgets that have - // a visible formatted display value, and a serializable - // value in a hidden input field which is actually sent to the server. - // description: - // The visible display may - // be locale-dependent and interactive. The value sent to the server is stored in a hidden - // input field which uses the `name` attribute declared by the original widget. That value sent - // to the server is defined by the dijit.form.MappedTextBox.serialize method and is typically - // locale-neutral. - // tags: - // protected + }); +}); - postMixInProperties: function(){ - this.inherited(arguments); +}, +'dijit/_base/typematic':function(){ +define("dijit/_base/typematic", ["../typematic"], function(){ + // for back-compat, just loads top level module +}); - // we want the name attribute to go to the hidden <input>, not the displayed <input>, - // so override _FormWidget.postMixInProperties() setting of nameAttrSetting - this.nameAttrSetting = ""; - }, +}, +'dijit/_base':function(){ +define("dijit/_base", [ + ".", + "./a11y", // used to be in dijit/_base/manager + "./WidgetSet", // used to be in dijit/_base/manager + "./_base/focus", + "./_base/manager", + "./_base/place", + "./_base/popup", + "./_base/scroll", + "./_base/sniff", + "./_base/typematic", + "./_base/wai", + "./_base/window" +], function(dijit){ + + // module: + // dijit/_base + // summary: + // Includes all the modules in dijit/_base - serialize: function(/*anything*/ val, /*Object?*/ options){ - // summary: - // Overridable function used to convert the get('value') result to a canonical - // (non-localized) string. For example, will print dates in ISO format, and - // numbers the same way as they are represented in javascript. - // tags: - // protected extension - return val.toString ? val.toString() : ""; // String - }, + return dijit._base; +}); - toString: function(){ - // summary: - // Returns widget as a printable string using the widget's value - // tags: - // protected - var val = this.filter(this.get('value')); // call filter in case value is nonstring and filter has been customized - return val != null ? (typeof val == "string" ? val : this.serialize(val, this.constraints)) : ""; // String - }, +}, +'dijit/layout/BorderContainer':function(){ +define("dijit/layout/BorderContainer", [ + "dojo/_base/array", // array.filter array.forEach array.map + "dojo/cookie", // cookie + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add domClass.remove domClass.toggle + "dojo/dom-construct", // domConstruct.destroy domConstruct.place + "dojo/dom-geometry", // domGeometry.marginBox + "dojo/dom-style", // domStyle.style + "dojo/_base/event", // event.stop + "dojo/keys", + "dojo/_base/lang", // lang.getObject lang.hitch + "dojo/on", + "dojo/touch", + "dojo/_base/window", // win.body win.doc win.doc.createElement + "../_WidgetBase", + "../_Widget", + "../_TemplatedMixin", + "./_LayoutWidget", + "./utils" // layoutUtils.layoutChildren +], function(array, cookie, declare, domClass, domConstruct, domGeometry, domStyle, event, keys, lang, on, touch, win, + _WidgetBase, _Widget, _TemplatedMixin, _LayoutWidget, layoutUtils){ - validate: function(){ - // Overrides `dijit.form.TextBox.validate` - this.valueNode.value = this.toString(); - return this.inherited(arguments); - }, +/*===== + var _WidgetBase = dijit._WidgetBase; + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _LayoutWidget = dijit.layout._LayoutWidget; +=====*/ - buildRendering: function(){ - // Overrides `dijit._Templated.buildRendering` +// module: +// dijit/layout/BorderContainer +// summary: +// Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides. - this.inherited(arguments); +var _Splitter = declare("dijit.layout._Splitter", [_Widget, _TemplatedMixin ], +{ + // summary: + // A draggable spacer between two items in a `dijit.layout.BorderContainer`. + // description: + // This is instantiated by `dijit.layout.BorderContainer`. Users should not + // create it directly. + // tags: + // private - // Create a hidden <input> node with the serialized value used for submit - // (as opposed to the displayed value). - // Passing in name as markup rather than calling dojo.create() with an attrs argument - // to make dojo.query(input[name=...]) work on IE. (see #8660) - this.valueNode = dojo.place("<input type='hidden'" + (this.name ? " name='" + this.name.replace(/'/g, """) + "'" : "") + "/>", this.textbox, "after"); - }, +/*===== + // container: [const] dijit.layout.BorderContainer + // Pointer to the parent BorderContainer + container: null, - reset: function(){ - // Overrides `dijit.form.ValidationTextBox.reset` to - // reset the hidden textbox value to '' - this.valueNode.value = ''; - this.inherited(arguments); - } - } -); + // child: [const] dijit.layout._LayoutWidget + // Pointer to the pane associated with this splitter + child: null, -/*===== - dijit.form.RangeBoundTextBox.__Constraints = function(){ - // min: Number - // Minimum signed value. Default is -Infinity - // max: Number - // Maximum signed value. Default is +Infinity - this.min = min; - this.max = max; - } + // region: [const] String + // Region of pane associated with this splitter. + // "top", "bottom", "left", "right". + region: null, =====*/ -dojo.declare( - "dijit.form.RangeBoundTextBox", - dijit.form.MappedTextBox, - { - // summary: - // Base class for textbox form widgets which defines a range of valid values. + // live: [const] Boolean + // If true, the child's size changes and the child widget is redrawn as you drag the splitter; + // otherwise, the size doesn't change until you drop the splitter (by mouse-up) + live: true, - // rangeMessage: String - // The message to display if value is out-of-range - rangeMessage: "", + templateString: '<div class="dijitSplitter" data-dojo-attach-event="onkeypress:_onKeyPress,press:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>', - /*===== - // constraints: dijit.form.RangeBoundTextBox.__Constraints - constraints: {}, - ======*/ + constructor: function(){ + this._handlers = []; + }, - rangeCheck: function(/*Number*/ primitive, /*dijit.form.RangeBoundTextBox.__Constraints*/ constraints){ - // summary: - // Overridable function used to validate the range of the numeric input value. - // tags: - // protected - return ("min" in constraints? (this.compare(primitive,constraints.min) >= 0) : true) && - ("max" in constraints? (this.compare(primitive,constraints.max) <= 0) : true); // Boolean - }, + postMixInProperties: function(){ + this.inherited(arguments); - isInRange: function(/*Boolean*/ isFocused){ - // summary: - // Tests if the value is in the min/max range specified in constraints - // tags: - // protected - return this.rangeCheck(this.get('value'), this.constraints); - }, + this.horizontal = /top|bottom/.test(this.region); + this._factor = /top|left/.test(this.region) ? 1 : -1; + this._cookieName = this.container.id + "_" + this.region; + }, - _isDefinitelyOutOfRange: function(){ - // summary: - // Returns true if the value is out of range and will remain - // out of range even if the user types more characters - var val = this.get('value'); - var isTooLittle = false; - var isTooMuch = false; - if("min" in this.constraints){ - var min = this.constraints.min; - min = this.compare(val, ((typeof min == "number") && min >= 0 && val !=0) ? 0 : min); - isTooLittle = (typeof min == "number") && min < 0; - } - if("max" in this.constraints){ - var max = this.constraints.max; - max = this.compare(val, ((typeof max != "number") || max > 0) ? max : 0); - isTooMuch = (typeof max == "number") && max > 0; + buildRendering: function(){ + this.inherited(arguments); + + domClass.add(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V")); + + if(this.container.persist){ + // restore old size + var persistSize = cookie(this._cookieName); + if(persistSize){ + this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize; } - return isTooLittle || isTooMuch; - }, + } + }, - _isValidSubset: function(){ - // summary: - // Overrides `dijit.form.ValidationTextBox._isValidSubset`. - // Returns true if the input is syntactically valid, and either within - // range or could be made in range by more typing. - return this.inherited(arguments) && !this._isDefinitelyOutOfRange(); - }, + _computeMaxSize: function(){ + // summary: + // Return the maximum size that my corresponding pane can be set to - isValid: function(/*Boolean*/ isFocused){ - // Overrides dijit.form.ValidationTextBox.isValid to check that the value is also in range. - return this.inherited(arguments) && - ((this._isEmpty(this.textbox.value) && !this.required) || this.isInRange(isFocused)); // Boolean - }, + var dim = this.horizontal ? 'h' : 'w', + childSize = domGeometry.getMarginBox(this.child.domNode)[dim], + center = array.filter(this.container.getChildren(), function(child){ return child.region == "center";})[0], + spaceAvailable = domGeometry.getMarginBox(center.domNode)[dim]; // can expand until center is crushed to 0 - getErrorMessage: function(/*Boolean*/ isFocused){ - // Overrides dijit.form.ValidationTextBox.getErrorMessage to print "out of range" message if appropriate - var v = this.get('value'); - if(v !== null && v !== '' && v !== undefined && (typeof v != "number" || !isNaN(v)) && !this.isInRange(isFocused)){ // don't check isInRange w/o a real value - return this.rangeMessage; // String - } - return this.inherited(arguments); - }, + return Math.min(this.child.maxSize, childSize + spaceAvailable); + }, - postMixInProperties: function(){ - this.inherited(arguments); - if(!this.rangeMessage){ - this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang); - this.rangeMessage = this.messages.rangeMessage; - } - }, + _startDrag: function(e){ + if(!this.cover){ + this.cover = win.doc.createElement('div'); + domClass.add(this.cover, "dijitSplitterCover"); + domConstruct.place(this.cover, this.child.domNode, "after"); + } + domClass.add(this.cover, "dijitSplitterCoverActive"); - _setConstraintsAttr: function(/*Object*/ constraints){ - this.inherited(arguments); - if(this.focusNode){ // not set when called from postMixInProperties - if(this.constraints.min !== undefined){ - dijit.setWaiState(this.focusNode, "valuemin", this.constraints.min); - }else{ - dijit.removeWaiState(this.focusNode, "valuemin"); - } - if(this.constraints.max !== undefined){ - dijit.setWaiState(this.focusNode, "valuemax", this.constraints.max); - }else{ - dijit.removeWaiState(this.focusNode, "valuemax"); + // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up. + if(this.fake){ domConstruct.destroy(this.fake); } + if(!(this._resize = this.live)){ //TODO: disable live for IE6? + // create fake splitter to display at old position while we drag + (this.fake = this.domNode.cloneNode(true)).removeAttribute("id"); + domClass.add(this.domNode, "dijitSplitterShadow"); + domConstruct.place(this.fake, this.domNode, "after"); + } + domClass.add(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active"); + if(this.fake){ + domClass.remove(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover"); + } + + //Performance: load data info local vars for onmousevent function closure + var factor = this._factor, + isHorizontal = this.horizontal, + axis = isHorizontal ? "pageY" : "pageX", + pageStart = e[axis], + splitterStyle = this.domNode.style, + dim = isHorizontal ? 'h' : 'w', + childStart = domGeometry.getMarginBox(this.child.domNode)[dim], + max = this._computeMaxSize(), + min = this.child.minSize || 20, + region = this.region, + splitterAttr = region == "top" || region == "bottom" ? "top" : "left", // style attribute of splitter to adjust + splitterStart = parseInt(splitterStyle[splitterAttr], 10), + resize = this._resize, + layoutFunc = lang.hitch(this.container, "_layoutChildren", this.child.id), + de = win.doc; + + this._handlers = this._handlers.concat([ + on(de, touch.move, this._drag = function(e, forceResize){ + var delta = e[axis] - pageStart, + childSize = factor * delta + childStart, + boundChildSize = Math.max(Math.min(childSize, max), min); + + if(resize || forceResize){ + layoutFunc(boundChildSize); } + // TODO: setting style directly (usually) sets content box size, need to set margin box size + splitterStyle[splitterAttr] = delta + splitterStart + factor*(boundChildSize - childSize) + "px"; + }), + on(de, "dragstart", event.stop), + on(win.body(), "selectstart", event.stop), + on(de, touch.release, lang.hitch(this, "_stopDrag")) + ]); + event.stop(e); + }, + + _onMouse: function(e){ + // summary: + // Handler for onmouseenter / onmouseleave events + var o = (e.type == "mouseover" || e.type == "mouseenter"); + domClass.toggle(this.domNode, "dijitSplitterHover", o); + domClass.toggle(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o); + }, + + _stopDrag: function(e){ + try{ + if(this.cover){ + domClass.remove(this.cover, "dijitSplitterCoverActive"); } - }, + if(this.fake){ domConstruct.destroy(this.fake); } + domClass.remove(this.domNode, "dijitSplitterActive dijitSplitter" + + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow"); + this._drag(e); //TODO: redundant with onmousemove? + this._drag(e, true); + }finally{ + this._cleanupHandlers(); + delete this._drag; + } - _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){ - // summary: - // Hook so set('value', ...) works. + if(this.container.persist){ + cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365}); + } + }, - dijit.setWaiState(this.focusNode, "valuenow", value); - this.inherited(arguments); + _cleanupHandlers: function(){ + var h; + while(h = this._handlers.pop()){ h.remove(); } + }, + + _onKeyPress: function(/*Event*/ e){ + // should we apply typematic to this? + this._resize = true; + var horizontal = this.horizontal; + var tick = 1; + switch(e.charOrCode){ + case horizontal ? keys.UP_ARROW : keys.LEFT_ARROW: + tick *= -1; +// break; + case horizontal ? keys.DOWN_ARROW : keys.RIGHT_ARROW: + break; + default: +// this.inherited(arguments); + return; } + var childSize = domGeometry.getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick; + this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize)); + event.stop(e); + }, + + destroy: function(){ + this._cleanupHandlers(); + delete this.child; + delete this.container; + delete this.cover; + delete this.fake; + this.inherited(arguments); } -); +}); -} +var _Gutter = declare("dijit.layout._Gutter", [_Widget, _TemplatedMixin], +{ + // summary: + // Just a spacer div to separate side pane from center pane. + // Basically a trick to lookup the gutter/splitter width from the theme. + // description: + // Instantiated by `dijit.layout.BorderContainer`. Users should not + // create directly. + // tags: + // private -if(!dojo._hasResource["dijit.form.ComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.ComboBox"] = true; -dojo.provide("dijit.form.ComboBox"); + templateString: '<div class="dijitGutter" role="presentation"></div>', + postMixInProperties: function(){ + this.inherited(arguments); + this.horizontal = /top|bottom/.test(this.region); + }, + buildRendering: function(){ + this.inherited(arguments); + domClass.add(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V")); + } +}); +var BorderContainer = declare("dijit.layout.BorderContainer", _LayoutWidget, { + // summary: + // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides. + // + // description: + // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;", + // that contains a child widget marked region="center" and optionally children widgets marked + // region equal to "top", "bottom", "leading", "trailing", "left" or "right". + // Children along the edges will be laid out according to width or height dimensions and may + // include optional splitters (splitter="true") to make them resizable by the user. The remaining + // space is designated for the center region. + // + // The outer size must be specified on the BorderContainer node. Width must be specified for the sides + // and height for the top and bottom, respectively. No dimensions should be specified on the center; + // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like + // "left" and "right" except that they will be reversed in right-to-left environments. + // + // For complex layouts, multiple children can be specified for a single region. In this case, the + // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority) + // and which child is closer to the center (high layoutPriority). layoutPriority can also be used + // instead of the design attribute to control layout precedence of horizontal vs. vertical panes. + // example: + // | <div data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="design: 'sidebar', gutters: false" + // | style="width: 400px; height: 300px;"> + // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region: 'top'">header text</div> + // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region: 'right', splitter: true" style="width: 200px;">table of contents</div> + // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region: 'center'">client area</div> + // | </div> + // design: String + // Which design is used for the layout: + // - "headline" (default) where the top and bottom extend + // the full width of the container + // - "sidebar" where the left and right sides extend from top to bottom. + design: "headline", + // gutters: [const] Boolean + // Give each pane a border and margin. + // Margin determined by domNode.paddingLeft. + // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing. + gutters: true, + // liveSplitters: [const] Boolean + // Specifies whether splitters resize as you drag (true) or only upon mouseup (false) + liveSplitters: true, + // persist: Boolean + // Save splitter positions in a cookie. + persist: false, + baseClass: "dijitBorderContainer", + // _splitterClass: Function||String + // Optional hook to override the default Splitter widget used by BorderContainer + _splitterClass: _Splitter, + postMixInProperties: function(){ + // change class name to indicate that BorderContainer is being used purely for + // layout (like LayoutContainer) rather than for pretty formatting. + if(!this.gutters){ + this.baseClass += "NoGutter"; + } + this.inherited(arguments); + }, -dojo.declare( - "dijit.form.ComboBoxMixin", - dijit._HasDropDown, - { - // summary: - // Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect` - // description: - // All widgets that mix in dijit.form.ComboBoxMixin must extend `dijit.form._FormValueWidget`. - // tags: - // protected + startup: function(){ + if(this._started){ return; } + array.forEach(this.getChildren(), this._setupChild, this); + this.inherited(arguments); + }, - // item: Object - // This is the item returned by the dojo.data.store implementation that - // provides the data for this ComboBox, it's the currently selected item. - item: null, + _setupChild: function(/*dijit._Widget*/ child){ + // Override _LayoutWidget._setupChild(). - // pageSize: Integer - // Argument to data provider. - // Specifies number of search results per page (before hitting "next" button) - pageSize: Infinity, + var region = child.region; + if(region){ + this.inherited(arguments); - // store: [const] Object - // Reference to data provider object used by this ComboBox - store: null, + domClass.add(child.domNode, this.baseClass+"Pane"); - // fetchProperties: Object - // Mixin to the dojo.data store's fetch. - // For example, to set the sort order of the ComboBox menu, pass: - // | { sort: [{attribute:"name",descending: true}] } - // To override the default queryOptions so that deep=false, do: - // | { queryOptions: {ignoreCase: true, deep: false} } - fetchProperties:{}, + var ltr = this.isLeftToRight(); + if(region == "leading"){ region = ltr ? "left" : "right"; } + if(region == "trailing"){ region = ltr ? "right" : "left"; } - // query: Object - // A query that can be passed to 'store' to initially filter the items, - // before doing further filtering based on `searchAttr` and the key. - // Any reference to the `searchAttr` is ignored. - query: {}, + // Create draggable splitter for resizing pane, + // or alternately if splitter=false but BorderContainer.gutters=true then + // insert dummy div just for spacing + if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){ + var _Splitter = child.splitter ? this._splitterClass : _Gutter; + if(lang.isString(_Splitter)){ + _Splitter = lang.getObject(_Splitter); // for back-compat, remove in 2.0 + } + var splitter = new _Splitter({ + id: child.id + "_splitter", + container: this, + child: child, + region: region, + live: this.liveSplitters + }); + splitter.isSplitter = true; + child._splitterWidget = splitter; - // autoComplete: Boolean - // If user types in a partial string, and then tab out of the `<input>` box, - // automatically copy the first entry displayed in the drop down list to - // the `<input>` field - autoComplete: true, + domConstruct.place(splitter.domNode, child.domNode, "after"); - // highlightMatch: String - // One of: "first", "all" or "none". - // - // If the ComboBox/FilteringSelect opens with the search results and the searched - // string can be found, it will be highlighted. If set to "all" - // then will probably want to change `queryExpr` parameter to '*${0}*' - // - // Highlighting is only performed when `labelType` is "text", so as to not - // interfere with any HTML markup an HTML label might contain. - highlightMatch: "first", + // Splitters aren't added as Contained children, so we need to call startup explicitly + splitter.startup(); + } + child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc. + } + }, - // searchDelay: Integer - // Delay in milliseconds between when user types something and we start - // searching based on that value - searchDelay: 100, + layout: function(){ + // Implement _LayoutWidget.layout() virtual method. + this._layoutChildren(); + }, - // searchAttr: String - // Search for items in the data store where this attribute (in the item) - // matches what the user typed - searchAttr: "name", + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // Override _LayoutWidget.addChild(). + this.inherited(arguments); + if(this._started){ + this.layout(); //OPT + } + }, - // labelAttr: String? - // The entries in the drop down list come from this attribute in the - // dojo.data items. - // If not specified, the searchAttr attribute is used instead. - labelAttr: "", + removeChild: function(/*dijit._Widget*/ child){ + // Override _LayoutWidget.removeChild(). - // labelType: String - // Specifies how to interpret the labelAttr in the data store items. - // Can be "html" or "text". - labelType: "text", + var region = child.region; + var splitter = child._splitterWidget; + if(splitter){ + splitter.destroy(); + delete child._splitterWidget; + } + this.inherited(arguments); - // queryExpr: String - // This specifies what query ComboBox/FilteringSelect sends to the data store, - // based on what the user has typed. Changing this expression will modify - // whether the drop down shows only exact matches, a "starting with" match, - // etc. Use it in conjunction with highlightMatch. - // dojo.data query expression pattern. - // `${0}` will be substituted for the user text. - // `*` is used for wildcards. - // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is" - queryExpr: "${0}*", + if(this._started){ + this._layoutChildren(); + } + // Clean up whatever style changes we made to the child pane. + // Unclear how height and width should be handled. + domClass.remove(child.domNode, this.baseClass+"Pane"); + domStyle.set(child.domNode, { + top: "auto", + bottom: "auto", + left: "auto", + right: "auto", + position: "static" + }); + domStyle.set(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto"); + }, - // ignoreCase: Boolean - // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items - ignoreCase: true, + getChildren: function(){ + // Override _LayoutWidget.getChildren() to only return real children, not the splitters. + return array.filter(this.inherited(arguments), function(widget){ + return !widget.isSplitter; + }); + }, - // hasDownArrow: Boolean - // Set this textbox to have a down arrow button, to display the drop down list. - // Defaults to true. - hasDownArrow: true, + // TODO: remove in 2.0 + getSplitter: function(/*String*/region){ + // summary: + // Returns the widget responsible for rendering the splitter associated with region + // tags: + // deprecated + return array.filter(this.getChildren(), function(child){ + return child.region == region; + })[0]._splitterWidget; + }, - templateString: dojo.cache("dijit.form", "templates/DropDownBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n"), + resize: function(newSize, currentSize){ + // Overrides _LayoutWidget.resize(). - baseClass: "dijitTextBox dijitComboBox", + // resetting potential padding to 0px to provide support for 100% width/height + padding + // TODO: this hack doesn't respect the box model and is a temporary fix + if(!this.cs || !this.pe){ + var node = this.domNode; + this.cs = domStyle.getComputedStyle(node); + this.pe = domGeometry.getPadExtents(node, this.cs); + this.pe.r = domStyle.toPixelValue(node, this.cs.paddingRight); + this.pe.b = domStyle.toPixelValue(node, this.cs.paddingBottom); - // dropDownClass: [protected extension] String - // Name of the dropdown widget class used to select a date/time. - // Subclasses should specify this. - dropDownClass: "dijit.form._ComboBoxMenu", + domStyle.set(node, "padding", "0px"); + } - // Set classes like dijitDownArrowButtonHover depending on - // mouse action over button node - cssStateNodes: { - "_buttonNode": "dijitDownArrowButton" - }, + this.inherited(arguments); + }, - // Flags to _HasDropDown to limit height of drop down to make it fit in viewport - maxHeight: -1, + _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){ + // summary: + // This is the main routine for setting size/position of each child. + // description: + // With no arguments, measures the height of top/bottom panes, the width + // of left/right panes, and then sizes all panes accordingly. + // + // With changedRegion specified (as "left", "top", "bottom", or "right"), + // it changes that region's width/height to changedRegionSize and + // then resizes other regions that were affected. + // changedChildId: + // Id of the child which should be resized because splitter was dragged. + // changedChildSize: + // The new width/height (in pixels) to make specified child - // For backwards compatibility let onClick events propagate, even clicks on the down arrow button - _stopClickEvents: false, + if(!this._borderBox || !this._borderBox.h){ + // We are currently hidden, or we haven't been sized by our parent yet. + // Abort. Someone will resize us later. + return; + } - _getCaretPos: function(/*DomNode*/ element){ - // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22 - var pos = 0; - if(typeof(element.selectionStart) == "number"){ - // FIXME: this is totally borked on Moz < 1.3. Any recourse? - pos = element.selectionStart; - }else if(dojo.isIE){ - // in the case of a mouse click in a popup being handled, - // then the dojo.doc.selection is not the textarea, but the popup - // var r = dojo.doc.selection.createRange(); - // hack to get IE 6 to play nice. What a POS browser. - var tr = dojo.doc.selection.createRange().duplicate(); - var ntr = element.createTextRange(); - tr.move("character",0); - ntr.move("character",0); - try{ - // If control doesn't have focus, you get an exception. - // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes). - // There appears to be no workaround for this - googled for quite a while. - ntr.setEndPoint("EndToEnd", tr); - pos = String(ntr.text).replace(/\r/g,"").length; - }catch(e){ - // If focus has shifted, 0 is fine for caret pos. + // Generate list of wrappers of my children in the order that I want layoutChildren() + // to process them (i.e. from the outside to the inside) + var wrappers = array.map(this.getChildren(), function(child, idx){ + return { + pane: child, + weight: [ + child.region == "center" ? Infinity : 0, + child.layoutPriority, + (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1), + idx + ] + }; + }, this); + wrappers.sort(function(a, b){ + var aw = a.weight, bw = b.weight; + for(var i=0; i<aw.length; i++){ + if(aw[i] != bw[i]){ + return aw[i] - bw[i]; } } - return pos; - }, + return 0; + }); - _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){ - location = parseInt(location); - dijit.selectInputText(element, location, location); - }, + // Make new list, combining the externally specified children with splitters and gutters + var childrenAndSplitters = []; + array.forEach(wrappers, function(wrapper){ + var pane = wrapper.pane; + childrenAndSplitters.push(pane); + if(pane._splitterWidget){ + childrenAndSplitters.push(pane._splitterWidget); + } + }); - _setDisabledAttr: function(/*Boolean*/ value){ - // Additional code to set disabled state of ComboBox node. - // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr(). - this.inherited(arguments); - dijit.setWaiState(this.domNode, "disabled", value); - }, + // Compute the box in which to lay out my children + var dim = { + l: this.pe.l, + t: this.pe.t, + w: this._borderBox.w - this.pe.w, + h: this._borderBox.h - this.pe.h + }; - _abortQuery: function(){ - // stop in-progress query - if(this.searchTimer){ - clearTimeout(this.searchTimer); - this.searchTimer = null; - } - if(this._fetchHandle){ - if(this._fetchHandle.abort){ this._fetchHandle.abort(); } - this._fetchHandle = null; - } - }, + // Layout the children, possibly changing size due to a splitter drag + layoutUtils.layoutChildren(this.domNode, dim, childrenAndSplitters, + changedChildId, changedChildSize); + }, - _onInput: function(/*Event*/ evt){ - // summary: - // Handles paste events - if(!this.searchTimer && (evt.type == 'paste'/*IE|WebKit*/ || evt.type == 'input'/*Firefox*/) && this._lastInput != this.textbox.value){ - this.searchTimer = setTimeout(dojo.hitch(this, function(){ - this._onKey({charOrCode: 229}); // fake IME key to cause a search - }), 100); // long delay that will probably be preempted by keyboard input + destroyRecursive: function(){ + // Destroy splitters first, while getChildren() still works + array.forEach(this.getChildren(), function(child){ + var splitter = child._splitterWidget; + if(splitter){ + splitter.destroy(); } - this.inherited(arguments); - }, + delete child._splitterWidget; + }); - _onKey: function(/*Event*/ evt){ - // summary: - // Handles keyboard events + // Then destroy the real children, and myself + this.inherited(arguments); + } +}); - var key = evt.charOrCode; +// This argument can be specified for the children of a BorderContainer. +// Since any widget can be specified as a LayoutContainer child, mix it +// into the base widget class. (This is a hack, but it's effective.) +lang.extend(_WidgetBase, { + // region: [const] String + // Parameter for children of `dijit.layout.BorderContainer`. + // Values: "top", "bottom", "leading", "trailing", "left", "right", "center". + // See the `dijit.layout.BorderContainer` description for details. + region: '', - // except for cutting/pasting case - ctrl + x/v - if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == dojo.keys.SHIFT){ - return; // throw out weird key combinations and spurious events - } - - var doSearch = false; - var pw = this.dropDown; - var dk = dojo.keys; - var highlighted = null; - this._prev_key_backspace = false; - this._abortQuery(); + // layoutPriority: [const] Number + // Parameter for children of `dijit.layout.BorderContainer`. + // Children with a higher layoutPriority will be placed closer to the BorderContainer center, + // between children with a lower layoutPriority. + layoutPriority: 0, - // _HasDropDown will do some of the work: - // 1. when drop down is not yet shown: - // - if user presses the down arrow key, call loadDropDown() - // 2. when drop down is already displayed: - // - on ESC key, call closeDropDown() - // - otherwise, call dropDown.handleKey() to process the keystroke - this.inherited(arguments); + // splitter: [const] Boolean + // Parameter for child of `dijit.layout.BorderContainer` where region != "center". + // If true, enables user to resize the widget by putting a draggable splitter between + // this widget and the region=center widget. + splitter: false, - if(this._opened){ - highlighted = pw.getHighlightedOption(); - } - switch(key){ - case dk.PAGE_DOWN: - case dk.DOWN_ARROW: - case dk.PAGE_UP: - case dk.UP_ARROW: - // Keystroke caused ComboBox_menu to move to a different item. - // Copy new item to <input> box. - if(this._opened){ - this._announceOption(highlighted); - } - dojo.stopEvent(evt); - break; + // minSize: [const] Number + // Parameter for children of `dijit.layout.BorderContainer`. + // Specifies a minimum size (in pixels) for this widget when resized by a splitter. + minSize: 0, - case dk.ENTER: - // prevent submitting form if user presses enter. Also - // prevent accepting the value if either Next or Previous - // are selected - if(highlighted){ - // only stop event on prev/next - if(highlighted == pw.nextButton){ - this._nextSearch(1); - dojo.stopEvent(evt); - break; - }else if(highlighted == pw.previousButton){ - this._nextSearch(-1); - dojo.stopEvent(evt); - break; - } - }else{ - // Update 'value' (ex: KY) according to currently displayed text - this._setBlurValue(); // set value if needed - this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting - } - // default case: - // if enter pressed while drop down is open, or for FilteringSelect, - // if we are in the middle of a query to convert a directly typed in value to an item, - // prevent submit, but allow event to bubble - if(this._opened || this._fetchHandle){ - evt.preventDefault(); - } - // fall through + // maxSize: [const] Number + // Parameter for children of `dijit.layout.BorderContainer`. + // Specifies a maximum size (in pixels) for this widget when resized by a splitter. + maxSize: Infinity +}); - case dk.TAB: - var newvalue = this.get('displayedValue'); - // if the user had More Choices selected fall into the - // _onBlur handler - if(pw && ( - newvalue == pw._messages["previousMessage"] || - newvalue == pw._messages["nextMessage"]) - ){ - break; - } - if(highlighted){ - this._selectOption(); - } - if(this._opened){ - this._lastQuery = null; // in case results come back later - this.closeDropDown(); - } - break; +// For monkey patching +BorderContainer._Splitter = _Splitter; +BorderContainer._Gutter = _Gutter; - case ' ': - if(highlighted){ - // user is effectively clicking a choice in the drop down menu - dojo.stopEvent(evt); - this._selectOption(); - this.closeDropDown(); - }else{ - // user typed a space into the input box, treat as normal character - doSearch = true; - } - break; +return BorderContainer; +}); - case dk.DELETE: - case dk.BACKSPACE: - this._prev_key_backspace = true; - doSearch = true; - break; +}, +'dojo/window':function(){ +define("dojo/window", ["./_base/lang", "./_base/sniff", "./_base/window", "./dom", "./dom-geometry", "./dom-style"], + function(lang, has, baseWindow, dom, geom, style) { - default: - // Non char keys (F1-F12 etc..) shouldn't open list. - // Ascii characters and IME input (Chinese, Japanese etc.) should. - //IME input produces keycode == 229. - doSearch = typeof key == 'string' || key == 229; - } - if(doSearch){ - // need to wait a tad before start search so that the event - // bubbles through DOM and we have value visible - this.item = undefined; // undefined means item needs to be set - this.searchTimer = setTimeout(dojo.hitch(this, "_startSearchFromInput"),1); - } - }, +// module: +// dojo/window +// summary: +// TODOC - _autoCompleteText: function(/*String*/ text){ - // summary: - // Fill in the textbox with the first item from the drop down - // list, and highlight the characters that were - // auto-completed. For example, if user typed "CA" and the - // drop down list appeared, the textbox would be changed to - // "California" and "ifornia" would be highlighted. +var window = lang.getObject("dojo.window", true); - var fn = this.focusNode; +/*===== +dojo.window = { + // summary: + // TODO +}; +window = dojo.window; +=====*/ - // IE7: clear selection so next highlight works all the time - dijit.selectInputText(fn, fn.value.length); - // does text autoComplete the value in the textbox? - var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr'; - if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){ - var cpos = this._getCaretPos(fn); - // only try to extend if we added the last character at the end of the input - if((cpos+1) > fn.value.length){ - // only add to input node as we would overwrite Capitalisation of chars - // actually, that is ok - fn.value = text;//.substr(cpos); - // visually highlight the autocompleted characters - dijit.selectInputText(fn, cpos); - } - }else{ - // text does not autoComplete; replace the whole value and highlight - fn.value = text; - dijit.selectInputText(fn); - } - }, +window.getBox = function(){ + // summary: + // Returns the dimensions and scroll position of the viewable area of a browser window - _openResultList: function(/*Object*/ results, /*Object*/ dataObject){ - // summary: - // Callback when a search completes. - // description: - // 1. generates drop-down list and calls _showResultList() to display it - // 2. if this result list is from user pressing "more choices"/"previous choices" - // then tell screen reader to announce new option - this._fetchHandle = null; - if( this.disabled || - this.readOnly || - (dataObject.query[this.searchAttr] != this._lastQuery) - ){ - return; - } - var wasSelected = this.dropDown._highlighted_option && dojo.hasClass(this.dropDown._highlighted_option, "dijitMenuItemSelected"); - this.dropDown.clearResultList(); - if(!results.length && !this._maxOptions){ // if no results and not just the previous choices button - this.closeDropDown(); - return; - } + var + scrollRoot = (baseWindow.doc.compatMode == 'BackCompat') ? baseWindow.body() : baseWindow.doc.documentElement, + // get scroll position + scroll = geom.docScroll(), // scrollRoot.scrollTop/Left should work + w, h; + + if(has("touch")){ // if(scrollbars not supported) + var uiWindow = baseWindow.doc.parentWindow || baseWindow.doc.defaultView; // use UI window, not dojo.global window. baseWindow.doc.parentWindow probably not needed since it's not defined for webkit + // on mobile, scrollRoot.clientHeight <= uiWindow.innerHeight <= scrollRoot.offsetHeight, return uiWindow.innerHeight + w = uiWindow.innerWidth || scrollRoot.clientWidth; // || scrollRoot.clientXXX probably never evaluated + h = uiWindow.innerHeight || scrollRoot.clientHeight; + }else{ + // on desktops, scrollRoot.clientHeight <= scrollRoot.offsetHeight <= uiWindow.innerHeight, return scrollRoot.clientHeight + // uiWindow.innerWidth/Height includes the scrollbar and cannot be used + w = scrollRoot.clientWidth; + h = scrollRoot.clientHeight; + } + return { + l: scroll.x, + t: scroll.y, + w: w, + h: h + }; +}; - // Fill in the textbox with the first item from the drop down list, - // and highlight the characters that were auto-completed. For - // example, if user typed "CA" and the drop down list appeared, the - // textbox would be changed to "California" and "ifornia" would be - // highlighted. +window.get = function(doc){ + // summary: + // Get window object associated with document doc - dataObject._maxOptions = this._maxOptions; - var nodes = this.dropDown.createOptions( - results, - dataObject, - dojo.hitch(this, "_getMenuLabelFromItem") - ); + // In some IE versions (at least 6.0), document.parentWindow does not return a + // reference to the real window object (maybe a copy), so we must fix it as well + // We use IE specific execScript to attach the real window reference to + // document._parentWindow for later use + if(has("ie") && window !== document.parentWindow){ + /* + In IE 6, only the variable "window" can be used to connect events (others + may be only copies). + */ + doc.parentWindow.execScript("document._parentWindow = window;", "Javascript"); + //to prevent memory leak, unset it after use + //another possibility is to add an onUnload handler which seems overkill to me (liucougar) + var win = doc._parentWindow; + doc._parentWindow = null; + return win; // Window + } - // show our list (only if we have content, else nothing) - this._showResultList(); + return doc.parentWindow || doc.defaultView; // Window +}; - // #4091: - // tell the screen reader that the paging callback finished by - // shouting the next choice - if(dataObject.direction){ - if(1 == dataObject.direction){ - this.dropDown.highlightFirstOption(); - }else if(-1 == dataObject.direction){ - this.dropDown.highlightLastOption(); +window.scrollIntoView = function(/*DomNode*/ node, /*Object?*/ pos){ + // summary: + // Scroll the passed node into view, if it is not already. + + // don't rely on node.scrollIntoView working just because the function is there + + try{ // catch unexpected/unrecreatable errors (#7808) since we can recover using a semi-acceptable native method + node = dom.byId(node); + var doc = node.ownerDocument || baseWindow.doc, + body = doc.body || baseWindow.body(), + html = doc.documentElement || body.parentNode, + isIE = has("ie"), isWK = has("webkit"); + // if an untested browser, then use the native method + if((!(has("mozilla") || isIE || isWK || has("opera")) || node == body || node == html) && (typeof node.scrollIntoView != "undefined")){ + node.scrollIntoView(false); // short-circuit to native if possible + return; + } + var backCompat = doc.compatMode == 'BackCompat', + clientAreaRoot = (isIE >= 9 && node.ownerDocument.parentWindow.frameElement) + ? ((html.clientHeight > 0 && html.clientWidth > 0 && (body.clientHeight == 0 || body.clientWidth == 0 || body.clientHeight > html.clientHeight || body.clientWidth > html.clientWidth)) ? html : body) + : (backCompat ? body : html), + scrollRoot = isWK ? body : clientAreaRoot, + rootWidth = clientAreaRoot.clientWidth, + rootHeight = clientAreaRoot.clientHeight, + rtl = !geom.isBodyLtr(), + nodePos = pos || geom.position(node), + el = node.parentNode, + isFixed = function(el){ + return ((isIE <= 6 || (isIE && backCompat))? false : (style.get(el, 'position').toLowerCase() == "fixed")); + }; + if(isFixed(node)){ return; } // nothing to do + + while(el){ + if(el == body){ el = scrollRoot; } + var elPos = geom.position(el), + fixedPos = isFixed(el); + + if(el == scrollRoot){ + elPos.w = rootWidth; elPos.h = rootHeight; + if(scrollRoot == html && isIE && rtl){ elPos.x += scrollRoot.offsetWidth-elPos.w; } // IE workaround where scrollbar causes negative x + if(elPos.x < 0 || !isIE){ elPos.x = 0; } // IE can have values > 0 + if(elPos.y < 0 || !isIE){ elPos.y = 0; } + }else{ + var pb = geom.getPadBorderExtents(el); + elPos.w -= pb.w; elPos.h -= pb.h; elPos.x += pb.l; elPos.y += pb.t; + var clientSize = el.clientWidth, + scrollBarSize = elPos.w - clientSize; + if(clientSize > 0 && scrollBarSize > 0){ + elPos.w = clientSize; + elPos.x += (rtl && (isIE || el.clientLeft > pb.l/*Chrome*/)) ? scrollBarSize : 0; } - if(wasSelected){ - this._announceOption(this.dropDown.getHighlightedOption()); + clientSize = el.clientHeight; + scrollBarSize = elPos.h - clientSize; + if(clientSize > 0 && scrollBarSize > 0){ + elPos.h = clientSize; } - }else if(this.autoComplete && !this._prev_key_backspace - // when the user clicks the arrow button to show the full list, - // startSearch looks for "*". - // it does not make sense to autocomplete - // if they are just previewing the options available. - && !/^[*]+$/.test(dataObject.query[this.searchAttr])){ - this._announceOption(nodes[1]); // 1st real item } - }, + if(fixedPos){ // bounded by viewport, not parents + if(elPos.y < 0){ + elPos.h += elPos.y; elPos.y = 0; + } + if(elPos.x < 0){ + elPos.w += elPos.x; elPos.x = 0; + } + if(elPos.y + elPos.h > rootHeight){ + elPos.h = rootHeight - elPos.y; + } + if(elPos.x + elPos.w > rootWidth){ + elPos.w = rootWidth - elPos.x; + } + } + // calculate overflow in all 4 directions + var l = nodePos.x - elPos.x, // beyond left: < 0 + t = nodePos.y - Math.max(elPos.y, 0), // beyond top: < 0 + r = l + nodePos.w - elPos.w, // beyond right: > 0 + bot = t + nodePos.h - elPos.h; // beyond bottom: > 0 + if(r * l > 0){ + var s = Math[l < 0? "max" : "min"](l, r); + if(rtl && ((isIE == 8 && !backCompat) || isIE >= 9)){ s = -s; } + nodePos.x += el.scrollLeft; + el.scrollLeft += s; + nodePos.x -= el.scrollLeft; + } + if(bot * t > 0){ + nodePos.y += el.scrollTop; + el.scrollTop += Math[t < 0? "max" : "min"](t, bot); + nodePos.y -= el.scrollTop; + } + el = (el != scrollRoot) && !fixedPos && el.parentNode; + } + }catch(error){ + console.error('scrollIntoView: ' + error); + node.scrollIntoView(false); + } +}; - _showResultList: function(){ - // summary: - // Display the drop down if not already displayed, or if it is displayed, then - // reposition it if necessary (reposition may be necessary if drop down's height changed). +return window; +}); - this.closeDropDown(true); +}, +'dojo/number':function(){ +define("dojo/number", ["./_base/kernel", "./_base/lang", "./i18n", "./i18n!./cldr/nls/number", "./string", "./regexp"], + function(dojo, lang, i18n, nlsNumber, dstring, dregexp) { - // hide the tooltip - this.displayMessage(""); + // module: + // dojo/number + // summary: + // TODOC - this.openDropDown(); +lang.getObject("number", true, dojo); - dijit.setWaiState(this.domNode, "expanded", "true"); - }, +/*===== +dojo.number = { + // summary: localized formatting and parsing routines for Number +} - loadDropDown: function(/*Function*/ callback){ - // Overrides _HasDropDown.loadDropDown(). - // This is called when user has pressed button icon or pressed the down arrow key - // to open the drop down. - - this._startSearchAll(); - }, +dojo.number.__FormatOptions = function(){ + // pattern: String? + // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // with this string. Default value is based on locale. Overriding this property will defeat + // localization. Literal characters in patterns are not supported. + // type: String? + // choose a format type based on the locale from the following: + // decimal, scientific (not yet supported), percent, currency. decimal by default. + // places: Number? + // fixed number of decimal places to show. This overrides any + // information in the provided pattern. + // round: Number? + // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1 + // means do not round. + // locale: String? + // override the locale used to determine formatting rules + // fractional: Boolean? + // If false, show no decimal places, overriding places and pattern settings. + this.pattern = pattern; + this.type = type; + this.places = places; + this.round = round; + this.locale = locale; + this.fractional = fractional; +} +=====*/ - isLoaded: function(){ - // signal to _HasDropDown that it needs to call loadDropDown() to load the - // drop down asynchronously before displaying it - return false; - }, +dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){ + // summary: + // Format a Number as a String, using locale-specific settings + // description: + // Create a string from a Number using a known localized pattern. + // Formatting patterns appropriate to the locale are chosen from the + // [Common Locale Data Repository](http://unicode.org/cldr) as well as the appropriate symbols and + // delimiters. + // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null. + // value: + // the number to be formatted - closeDropDown: function(){ - // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open). - // This method is the callback when the user types ESC or clicking - // the button icon while the drop down is open. It's also called by other code. - this._abortQuery(); - if(this._opened){ - this.inherited(arguments); - dijit.setWaiState(this.domNode, "expanded", "false"); - dijit.removeWaiState(this.focusNode,"activedescendant"); - } - }, + options = lang.mixin({}, options || {}); + var locale = i18n.normalizeLocale(options.locale), + bundle = i18n.getLocalization("dojo.cldr", "number", locale); + options.customs = bundle; + var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"]; + if(isNaN(value) || Math.abs(value) == Infinity){ return null; } // null + return dojo.number._applyPattern(value, pattern, options); // String +}; - _setBlurValue: function(){ - // if the user clicks away from the textbox OR tabs away, set the - // value to the textbox value - // #4617: - // if value is now more choices or previous choices, revert - // the value - var newvalue = this.get('displayedValue'); - var pw = this.dropDown; - if(pw && ( - newvalue == pw._messages["previousMessage"] || - newvalue == pw._messages["nextMessage"] - ) - ){ - this._setValueAttr(this._lastValueReported, true); - }else if(typeof this.item == "undefined"){ - // Update 'value' (ex: KY) according to currently displayed text - this.item = null; - this.set('displayedValue', newvalue); - }else{ - if(this.value != this._lastValueReported){ - dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); - } - this._refreshState(); - } - }, +//dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough +dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough - _onBlur: function(){ - // summary: - // Called magically when focus has shifted away from this widget and it's drop down - this.closeDropDown(); - this.inherited(arguments); - }, +dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){ + // summary: + // Apply pattern to format value as a string using options. Gives no + // consideration to local customs. + // value: + // the number to be formatted. + // pattern: + // a pattern string as described by + // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // options: dojo.number.__FormatOptions? + // _applyPattern is usually called via `dojo.number.format()` which + // populates an extra property in the options parameter, "customs". + // The customs object specifies group and decimal parameters if set. - _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ - // summary: - // Set the displayed valued in the input box, and the hidden value - // that gets submitted, based on a dojo.data store item. - // description: - // Users shouldn't call this function; they should be calling - // set('item', value) - // tags: - // private - if(!displayedValue){ - displayedValue = this.store.getValue(item, this.searchAttr); - } - var value = this._getValueField() != this.searchAttr? this.store.getIdentity(item) : displayedValue; - this._set("item", item); - dijit.form.ComboBox.superclass._setValueAttr.call(this, value, priorityChange, displayedValue); - }, + //TODO: support escapes + options = options || {}; + var group = options.customs.group, + decimal = options.customs.decimal, + patternList = pattern.split(';'), + positivePattern = patternList[0]; + pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern); - _announceOption: function(/*Node*/ node){ - // summary: - // a11y code that puts the highlighted option in the textbox. - // This way screen readers will know what is happening in the - // menu. + //TODO: only test against unescaped + if(pattern.indexOf('%') != -1){ + value *= 100; + }else if(pattern.indexOf('\u2030') != -1){ + value *= 1000; // per mille + }else if(pattern.indexOf('\u00a4') != -1){ + group = options.customs.currencyGroup || group;//mixins instead? + decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead? + pattern = pattern.replace(/\u00a4{1,3}/, function(match){ + var prop = ["symbol", "currency", "displayName"][match.length-1]; + return options[prop] || options.currency || ""; + }); + }else if(pattern.indexOf('E') != -1){ + throw new Error("exponential notation not supported"); + } - if(!node){ - return; - } - // pull the text value from the item attached to the DOM node - var newValue; - if(node == this.dropDown.nextButton || - node == this.dropDown.previousButton){ - newValue = node.innerHTML; - this.item = undefined; - this.value = ''; - }else{ - newValue = this.store.getValue(node.item, this.searchAttr).toString(); - this.set('item', node.item, false, newValue); - } - // get the text that the user manually entered (cut off autocompleted text) - this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length); - // set up ARIA activedescendant - dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id")); - // autocomplete the rest of the option to announce change - this._autoCompleteText(newValue); - }, + //TODO: support @ sig figs? + var numberPatternRE = dojo.number._numberPatternRE; + var numberPattern = positivePattern.match(numberPatternRE); + if(!numberPattern){ + throw new Error("unable to find a number expression in pattern: "+pattern); + } + if(options.fractional === false){ options.places = 0; } + return pattern.replace(numberPatternRE, + dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round})); +}; - _selectOption: function(/*Event*/ evt){ - // summary: - // Menu callback function, called when an item in the menu is selected. - if(evt){ - this._announceOption(evt.target); - } - this.closeDropDown(); - this._setCaretPos(this.focusNode, this.focusNode.value.length); - dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); // set this.value and fire onChange - }, +dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){ + // summary: + // Rounds to the nearest value with the given number of decimal places, away from zero + // description: + // Rounds to the nearest value with the given number of decimal places, away from zero if equal. + // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by + // fractional increments also, such as the nearest quarter. + // NOTE: Subject to floating point errors. See dojox.math.round for experimental workaround. + // value: + // The number to round + // places: + // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding. + // Must be non-negative. + // increment: + // Rounds next place to nearest value of increment/10. 10 by default. + // example: + // >>> dojo.number.round(-0.5) + // -1 + // >>> dojo.number.round(162.295, 2) + // 162.29 // note floating point error. Should be 162.3 + // >>> dojo.number.round(10.71, 0, 2.5) + // 10.75 + var factor = 10 / (increment || 10); + return (factor * +value).toFixed(places) / factor; // Number +}; - _startSearchAll: function(){ - this._startSearch(''); - }, +if((0.9).toFixed() == 0){ + // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit + // is just after the rounding place and is >=5 + var round = dojo.number.round; + dojo.number.round = function(v, p, m){ + var d = Math.pow(10, -p || 0), a = Math.abs(v); + if(!v || a >= d || a * Math.pow(10, p + 1) < 5){ + d = 0; + } + return round(v, p, m) + (v > 0 ? d : -d); + }; +} - _startSearchFromInput: function(){ - this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1")); - }, +/*===== +dojo.number.__FormatAbsoluteOptions = function(){ + // decimal: String? + // the decimal separator + // group: String? + // the group separator + // places: Number?|String? + // number of decimal places. the range "n,m" will format to m places. + // round: Number? + // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1 + // means don't round. + this.decimal = decimal; + this.group = group; + this.places = places; + this.round = round; +} +=====*/ - _getQueryString: function(/*String*/ text){ - return dojo.string.substitute(this.queryExpr, [text]); - }, +dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){ + // summary: + // Apply numeric pattern to absolute value using options. Gives no + // consideration to local customs. + // value: + // the number to be formatted, ignores sign + // pattern: + // the number portion of a pattern (e.g. `#,##0.00`) + options = options || {}; + if(options.places === true){options.places=0;} + if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit - _startSearch: function(/*String*/ key){ - // summary: - // Starts a search for elements matching key (key=="" means to return all items), - // and calls _openResultList() when the search completes, to display the results. - if(!this.dropDown){ - var popupId = this.id + "_popup", - dropDownConstructor = dojo.getObject(this.dropDownClass, false); - this.dropDown = new dropDownConstructor({ - onChange: dojo.hitch(this, this._selectOption), - id: popupId, - dir: this.dir - }); - dijit.removeWaiState(this.focusNode,"activedescendant"); - dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox - } - // create a new query to prevent accidentally querying for a hidden - // value from FilteringSelect's keyField - var query = dojo.clone(this.query); // #5970 - this._lastInput = key; // Store exactly what was entered by the user. - this._lastQuery = query[this.searchAttr] = this._getQueryString(key); - // #5970: set _lastQuery, *then* start the timeout - // otherwise, if the user types and the last query returns before the timeout, - // _lastQuery won't be set and their input gets rewritten - this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){ - this.searchTimer = null; - var fetch = { - queryOptions: { - ignoreCase: this.ignoreCase, - deep: true - }, - query: query, - onBegin: dojo.hitch(this, "_setMaxOptions"), - onComplete: dojo.hitch(this, "_openResultList"), - onError: function(errText){ - _this._fetchHandle = null; - console.error('dijit.form.ComboBox: ' + errText); - _this.closeDropDown(); - }, - start: 0, - count: this.pageSize - }; - dojo.mixin(fetch, _this.fetchProperties); - this._fetchHandle = _this.store.fetch(fetch); - - var nextSearch = function(dataObject, direction){ - dataObject.start += dataObject.count*direction; - // #4091: - // tell callback the direction of the paging so the screen - // reader knows which menu option to shout - dataObject.direction = direction; - this._fetchHandle = this.store.fetch(dataObject); - this.focus(); - }; - this._nextSearch = this.dropDown.onPage = dojo.hitch(this, nextSearch, this._fetchHandle); - }, query, this), this.searchDelay); - }, + var patternParts = pattern.split("."), + comma = typeof options.places == "string" && options.places.indexOf(","), + maxPlaces = options.places; + if(comma){ + maxPlaces = options.places.substring(comma + 1); + }else if(!(maxPlaces >= 0)){ + maxPlaces = (patternParts[1] || []).length; + } + if(!(options.round < 0)){ + value = dojo.number.round(value, maxPlaces, options.round); + } - _setMaxOptions: function(size, request){ - this._maxOptions = size; - }, + var valueParts = String(Math.abs(value)).split("."), + fractional = valueParts[1] || ""; + if(patternParts[1] || options.places){ + if(comma){ + options.places = options.places.substring(0, comma); + } + // Pad fractional with trailing zeros + var pad = options.places !== undefined ? options.places : (patternParts[1] && patternParts[1].lastIndexOf("0") + 1); + if(pad > fractional.length){ + valueParts[1] = dstring.pad(fractional, pad, '0', true); + } - _getValueField: function(){ - // summary: - // Helper for postMixInProperties() to set this.value based on data inlined into the markup. - // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value. - return this.searchAttr; - }, + // Truncate fractional + if(maxPlaces < fractional.length){ + valueParts[1] = fractional.substr(0, maxPlaces); + } + }else{ + if(valueParts[1]){ valueParts.pop(); } + } - //////////// INITIALIZATION METHODS /////////////////////////////////////// + // Pad whole with leading zeros + var patternDigits = patternParts[0].replace(',', ''); + pad = patternDigits.indexOf("0"); + if(pad != -1){ + pad = patternDigits.length - pad; + if(pad > valueParts[0].length){ + valueParts[0] = dstring.pad(valueParts[0], pad); + } - constructor: function(){ - this.query={}; - this.fetchProperties={}; - }, + // Truncate whole + if(patternDigits.indexOf("#") == -1){ + valueParts[0] = valueParts[0].substr(valueParts[0].length - pad); + } + } - postMixInProperties: function(){ - if(!this.store){ - var srcNodeRef = this.srcNodeRef; + // Add group separators + var index = patternParts[0].lastIndexOf(','), + groupSize, groupSize2; + if(index != -1){ + groupSize = patternParts[0].length - index - 1; + var remainder = patternParts[0].substr(0, index); + index = remainder.lastIndexOf(','); + if(index != -1){ + groupSize2 = remainder.length - index - 1; + } + } + var pieces = []; + for(var whole = valueParts[0]; whole;){ + var off = whole.length - groupSize; + pieces.push((off > 0) ? whole.substr(off) : whole); + whole = (off > 0) ? whole.slice(0, off) : ""; + if(groupSize2){ + groupSize = groupSize2; + delete groupSize2; + } + } + valueParts[0] = pieces.reverse().join(options.group || ","); - // if user didn't specify store, then assume there are option tags - this.store = new dijit.form._ComboBoxDataStore(srcNodeRef); + return valueParts.join(options.decimal || "."); +}; - // if there is no value set and there is an option list, set - // the value to the first value to be consistent with native - // Select +/*===== +dojo.number.__RegexpOptions = function(){ + // pattern: String? + // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // with this string. Default value is based on locale. Overriding this property will defeat + // localization. + // type: String? + // choose a format type based on the locale from the following: + // decimal, scientific (not yet supported), percent, currency. decimal by default. + // locale: String? + // override the locale used to determine formatting rules + // strict: Boolean? + // strict parsing, false by default. Strict parsing requires input as produced by the format() method. + // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators + // places: Number|String? + // number of decimal places to accept: Infinity, a positive number, or + // a range "n,m". Defined by pattern or Infinity if pattern not provided. + this.pattern = pattern; + this.type = type; + this.locale = locale; + this.strict = strict; + this.places = places; +} +=====*/ +dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){ + // summary: + // Builds the regular needed to parse a number + // description: + // Returns regular expression with positive and negative match, group + // and decimal separators + return dojo.number._parseInfo(options).regexp; // String +}; - // Firefox and Safari set value - // IE6 and Opera set selectedIndex, which is automatically set - // by the selected attribute of an option tag - // IE6 does not set value, Opera sets value = selectedIndex - if(!("value" in this.params)){ - var item = (this.item = this.store.fetchSelectedItem()); - if(item){ - var valueField = this._getValueField(); - this.value = this.store.getValue(item, valueField); - } +dojo.number._parseInfo = function(/*Object?*/options){ + options = options || {}; + var locale = i18n.normalizeLocale(options.locale), + bundle = i18n.getLocalization("dojo.cldr", "number", locale), + pattern = options.pattern || bundle[(options.type || "decimal") + "Format"], +//TODO: memoize? + group = bundle.group, + decimal = bundle.decimal, + factor = 1; + + if(pattern.indexOf('%') != -1){ + factor /= 100; + }else if(pattern.indexOf('\u2030') != -1){ + factor /= 1000; // per mille + }else{ + var isCurrency = pattern.indexOf('\u00a4') != -1; + if(isCurrency){ + group = bundle.currencyGroup || group; + decimal = bundle.currencyDecimal || decimal; + } + } + + //TODO: handle quoted escapes + var patternList = pattern.split(';'); + if(patternList.length == 1){ + patternList.push("-" + patternList[0]); + } + + var re = dregexp.buildGroupRE(patternList, function(pattern){ + pattern = "(?:"+dregexp.escapeString(pattern, '.')+")"; + return pattern.replace(dojo.number._numberPatternRE, function(format){ + var flags = { + signed: false, + separator: options.strict ? group : [group,""], + fractional: options.fractional, + decimal: decimal, + exponent: false + }, + + parts = format.split('.'), + places = options.places; + + // special condition for percent (factor != 1) + // allow decimal places even if not specified in pattern + if(parts.length == 1 && factor != 1){ + parts[1] = "###"; + } + if(parts.length == 1 || places === 0){ + flags.fractional = false; + }else{ + if(places === undefined){ places = options.pattern ? parts[1].lastIndexOf('0') + 1 : Infinity; } + if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified + if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; } + flags.places = places; + } + var groups = parts[0].split(','); + if(groups.length > 1){ + flags.groupSize = groups.pop().length; + if(groups.length > 1){ + flags.groupSize2 = groups.pop().length; } } + return "("+dojo.number._realNumberRegexp(flags)+")"; + }); + }, true); - this.inherited(arguments); - }, + if(isCurrency){ + // substitute the currency symbol for the placeholder in the pattern + re = re.replace(/([\s\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match, before, target, after){ + var prop = ["symbol", "currency", "displayName"][target.length-1], + symbol = dregexp.escapeString(options[prop] || options.currency || ""); + before = before ? "[\\s\\xa0]" : ""; + after = after ? "[\\s\\xa0]" : ""; + if(!options.strict){ + if(before){before += "*";} + if(after){after += "*";} + return "(?:"+before+symbol+after+")?"; + } + return before+symbol+after; + }); + } - postCreate: function(){ - // summary: - // Subclasses must call this method from their postCreate() methods - // tags: - // protected +//TODO: substitute localized sign/percent/permille/etc.? - // find any associated label element and add to ComboBox node. - var label=dojo.query('label[for="'+this.id+'"]'); - if(label.length){ - label[0].id = (this.id+"_label"); - dijit.setWaiState(this.domNode, "labelledby", label[0].id); + // normalize whitespace and return + return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object +}; - } - this.inherited(arguments); - }, +/*===== +dojo.number.__ParseOptions = function(){ + // pattern: String? + // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // with this string. Default value is based on locale. Overriding this property will defeat + // localization. Literal characters in patterns are not supported. + // type: String? + // choose a format type based on the locale from the following: + // decimal, scientific (not yet supported), percent, currency. decimal by default. + // locale: String? + // override the locale used to determine formatting rules + // strict: Boolean? + // strict parsing, false by default. Strict parsing requires input as produced by the format() method. + // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators + // fractional: Boolean?|Array? + // Whether to include the fractional portion, where the number of decimal places are implied by pattern + // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional. + this.pattern = pattern; + this.type = type; + this.locale = locale; + this.strict = strict; + this.fractional = fractional; +} +=====*/ +dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){ + // summary: + // Convert a properly formatted string to a primitive Number, using + // locale-specific settings. + // description: + // Create a Number from a string using a known localized pattern. + // Formatting patterns are chosen appropriate to the locale + // and follow the syntax described by + // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // Note that literal characters in patterns are not supported. + // expression: + // A string representation of a Number + var info = dojo.number._parseInfo(options), + results = (new RegExp("^"+info.regexp+"$")).exec(expression); + if(!results){ + return NaN; //NaN + } + var absoluteMatch = results[1]; // match for the positive expression + if(!results[1]){ + if(!results[2]){ + return NaN; //NaN + } + // matched the negative pattern + absoluteMatch =results[2]; + info.factor *= -1; + } - _setHasDownArrowAttr: function(val){ - this.hasDownArrow = val; - this._buttonNode.style.display = val ? "" : "none"; - }, + // Transform it to something Javascript can parse as a number. Normalize + // decimal point and strip out group separators or alternate forms of whitespace + absoluteMatch = absoluteMatch. + replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), ""). + replace(info.decimal, "."); + // Adjust for negative sign, percent, etc. as necessary + return absoluteMatch * info.factor; //Number +}; - _getMenuLabelFromItem: function(/*Item*/ item){ - var label = this.labelFunc(item, this.store), - labelType = this.labelType; - // If labelType is not "text" we don't want to screw any markup ot whatever. - if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){ - label = this.doHighlight(label, this._escapeHtml(this._lastInput)); - labelType = "html"; - } - return {html: labelType == "html", label: label}; - }, +/*===== +dojo.number.__RealNumberRegexpFlags = function(){ + // places: Number? + // The integer number of decimal places or a range given as "n,m". If + // not given, the decimal part is optional and the number of places is + // unlimited. + // decimal: String? + // A string for the character used as the decimal point. Default + // is ".". + // fractional: Boolean?|Array? + // Whether decimal places are used. Can be true, false, or [true, + // false]. Default is [true, false] which means optional. + // exponent: Boolean?|Array? + // Express in exponential notation. Can be true, false, or [true, + // false]. Default is [true, false], (i.e. will match if the + // exponential part is present are not). + // eSigned: Boolean?|Array? + // The leading plus-or-minus sign on the exponent. Can be true, + // false, or [true, false]. Default is [true, false], (i.e. will + // match if it is signed or unsigned). flags in regexp.integer can be + // applied. + this.places = places; + this.decimal = decimal; + this.fractional = fractional; + this.exponent = exponent; + this.eSigned = eSigned; +} +=====*/ - doHighlight: function(/*String*/ label, /*String*/ find){ - // summary: - // Highlights the string entered by the user in the menu. By default this - // highlights the first occurrence found. Override this method - // to implement your custom highlighting. - // tags: - // protected +dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){ + // summary: + // Builds a regular expression to match a real number in exponential + // notation - var - // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true - modifiers = (this.ignoreCase ? "i" : "") + (this.highlightMatch == "all" ? "g" : ""), - i = this.queryExpr.indexOf("${0}"); - find = dojo.regexp.escapeString(find); // escape regexp special chars - return this._escapeHtml(label).replace( - // prepend ^ when this.queryExpr == "${0}*" and append $ when this.queryExpr == "*${0}" - new RegExp((i == 0 ? "^" : "") + "("+ find +")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers), - '<span class="dijitComboBoxHighlightMatch">$1</span>' - ); // returns String, (almost) valid HTML (entities encoded) - }, + // assign default values to missing parameters + flags = flags || {}; + //TODO: use mixin instead? + if(!("places" in flags)){ flags.places = Infinity; } + if(typeof flags.decimal != "string"){ flags.decimal = "."; } + if(!("fractional" in flags) || /^0/.test(flags.places)){ flags.fractional = [true, false]; } + if(!("exponent" in flags)){ flags.exponent = [true, false]; } + if(!("eSigned" in flags)){ flags.eSigned = [true, false]; } - _escapeHtml: function(/*String*/ str){ - // TODO Should become dojo.html.entities(), when exists use instead - // summary: - // Adds escape sequences for special characters in XML: &<>"' - str = String(str).replace(/&/gm, "&").replace(/</gm, "<") - .replace(/>/gm, ">").replace(/"/gm, """); - return str; // string + var integerRE = dojo.number._integerRegexp(flags), + decimalRE = dregexp.buildGroupRE(flags.fractional, + function(q){ + var re = ""; + if(q && (flags.places!==0)){ + re = "\\" + flags.decimal; + if(flags.places == Infinity){ + re = "(?:" + re + "\\d+)?"; + }else{ + re += "\\d{" + flags.places + "}"; + } + } + return re; }, + true + ); - reset: function(){ - // Overrides the _FormWidget.reset(). - // Additionally reset the .item (to clean up). - this.item = null; - this.inherited(arguments); - }, + var exponentRE = dregexp.buildGroupRE(flags.exponent, + function(q){ + if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; } + return ""; + } + ); - labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){ - // summary: - // Computes the label to display based on the dojo.data store item. - // returns: - // The label that the ComboBox should display - // tags: - // private + var realRE = integerRE + decimalRE; + // allow for decimals without integers, e.g. .25 + if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";} + return realRE + exponentRE; // String +}; - // Use toString() because XMLStore returns an XMLItem whereas this - // method is expected to return a String (#9354) - return store.getValue(item, this.labelAttr || this.searchAttr).toString(); // String - } +/*===== +dojo.number.__IntegerRegexpFlags = function(){ + // signed: Boolean? + // The leading plus-or-minus sign. Can be true, false, or `[true,false]`. + // Default is `[true, false]`, (i.e. will match if it is signed + // or unsigned). + // separator: String? + // The character used as the thousands separator. Default is no + // separator. For more than one symbol use an array, e.g. `[",", ""]`, + // makes ',' optional. + // groupSize: Number? + // group size between separators + // groupSize2: Number? + // second grouping, where separators 2..n have a different interval than the first separator (for India) + this.signed = signed; + this.separator = separator; + this.groupSize = groupSize; + this.groupSize2 = groupSize2; +} +=====*/ + +dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){ + // summary: + // Builds a regular expression that matches an integer + + // assign default values to missing parameters + flags = flags || {}; + if(!("signed" in flags)){ flags.signed = [true, false]; } + if(!("separator" in flags)){ + flags.separator = ""; + }else if(!("groupSize" in flags)){ + flags.groupSize = 3; } -); -dojo.declare( - "dijit.form._ComboBoxMenu", - [dijit._Widget, dijit._Templated, dijit._CssStateMixin], - { - // summary: - // Focus-less menu for internal use in `dijit.form.ComboBox` - // tags: - // private + var signRE = dregexp.buildGroupRE(flags.signed, + function(q){ return q ? "[-+]" : ""; }, + true + ); - templateString: "<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' style='overflow: \"auto\"; overflow-x: \"hidden\";'>" - +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' role='option'></li>" - +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' role='option'></li>" - +"</ul>", + var numberRE = dregexp.buildGroupRE(flags.separator, + function(sep){ + if(!sep){ + return "(?:\\d+)"; + } - // _messages: Object - // Holds "next" and "previous" text for paging buttons on drop down - _messages: null, - - baseClass: "dijitComboBoxMenu", + sep = dregexp.escapeString(sep); + if(sep == " "){ sep = "\\s"; } + else if(sep == "\xa0"){ sep = "\\s\\xa0"; } - postMixInProperties: function(){ - this.inherited(arguments); - this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang); + var grp = flags.groupSize, grp2 = flags.groupSize2; + //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933 + if(grp2){ + var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})"; + return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE; + } + return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)"; }, + true + ); - buildRendering: function(){ - this.inherited(arguments); + return signRE + numberRE; // String +}; - // fill in template with i18n messages - this.previousButton.innerHTML = this._messages["previousMessage"]; - this.nextButton.innerHTML = this._messages["nextMessage"]; - }, +return dojo.number; +}); - _setValueAttr: function(/*Object*/ value){ - this.value = value; - this.onChange(value); - }, +}, +'dijit/_FocusMixin':function(){ +define("dijit/_FocusMixin", [ + "./focus", + "./_WidgetBase", + "dojo/_base/declare", // declare + "dojo/_base/lang" // lang.extend +], function(focus, _WidgetBase, declare, lang){ - // stubs - onChange: function(/*Object*/ value){ +/*===== + var _WidgetBase = dijit._WidgetBase; +=====*/ + + // module: + // dijit/_FocusMixin + // summary: + // Mixin to widget to provide _onFocus() and _onBlur() methods that + // fire when a widget or it's descendants get/lose focus + + // We don't know where _FocusMixin will occur in the inheritance chain, but we need the _onFocus()/_onBlur() below + // to be last in the inheritance chain, so mixin to _WidgetBase. + lang.extend(_WidgetBase, { + // focused: [readonly] Boolean + // This widget or a widget it contains has focus, or is "active" because + // it was recently clicked. + focused: false, + + onFocus: function(){ // summary: - // Notifies ComboBox/FilteringSelect that user clicked an option in the drop down menu. - // Probably should be called onSelect. + // Called when the widget becomes "active" because + // it or a widget inside of it either has focus, or has recently + // been clicked. // tags: // callback }, - onPage: function(/*Number*/ direction){ + + onBlur: function(){ // summary: - // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page. + // Called when the widget stops being "active" because + // focus moved to something outside of it, or the user + // clicked somewhere outside of it, or the widget was + // hidden. // tags: // callback }, - onClose: function(){ + _onFocus: function(){ // summary: - // Callback from dijit.popup code to this widget, notifying it that it closed + // This is where widgets do processing for when they are active, + // such as changing CSS classes. See onFocus() for more details. // tags: - // private - this._blurOptionNode(); + // protected + this.onFocus(); }, - _createOption: function(/*Object*/ item, labelFunc){ + _onBlur: function(){ // summary: - // Creates an option to appear on the popup menu subclassed by - // `dijit.form.FilteringSelect`. - - var menuitem = dojo.create("li", { - "class": "dijitReset dijitMenuItem" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl"), - role: "option" - }); - var labelObject = labelFunc(item); - if(labelObject.html){ - menuitem.innerHTML = labelObject.label; - }else{ - menuitem.appendChild( - dojo.doc.createTextNode(labelObject.label) - ); - } - // #3250: in blank options, assign a normal height - if(menuitem.innerHTML == ""){ - menuitem.innerHTML = " "; - } - menuitem.item=item; - return menuitem; - }, - - createOptions: function(results, dataObject, labelFunc){ - // summary: - // Fills in the items in the drop down list - // results: - // Array of dojo.data items - // dataObject: - // dojo.data store - // labelFunc: - // Function to produce a label in the drop down list from a dojo.data item - - //this._dataObject=dataObject; - //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList); - // display "Previous . . ." button - this.previousButton.style.display = (dataObject.start == 0) ? "none" : ""; - dojo.attr(this.previousButton, "id", this.id + "_prev"); - // create options using _createOption function defined by parent - // ComboBox (or FilteringSelect) class - // #2309: - // iterate over cache nondestructively - dojo.forEach(results, function(item, i){ - var menuitem = this._createOption(item, labelFunc); - dojo.attr(menuitem, "id", this.id + i); - this.domNode.insertBefore(menuitem, this.nextButton); - }, this); - // display "Next . . ." button - var displayMore = false; - //Try to determine if we should show 'more'... - if(dataObject._maxOptions && dataObject._maxOptions != -1){ - if((dataObject.start + dataObject.count) < dataObject._maxOptions){ - displayMore = true; - }else if((dataObject.start + dataObject.count) > dataObject._maxOptions && dataObject.count == results.length){ - //Weird return from a datastore, where a start + count > maxOptions - // implies maxOptions isn't really valid and we have to go into faking it. - //And more or less assume more if count == results.length - displayMore = true; - } - }else if(dataObject.count == results.length){ - //Don't know the size, so we do the best we can based off count alone. - //So, if we have an exact match to count, assume more. - displayMore = true; - } - - this.nextButton.style.display = displayMore ? "" : "none"; - dojo.attr(this.nextButton,"id", this.id + "_next"); - return this.domNode.childNodes; - }, + // This is where widgets do processing for when they stop being active, + // such as changing CSS classes. See onBlur() for more details. + // tags: + // protected + this.onBlur(); + } + }); - clearResultList: function(){ - // summary: - // Clears the entries in the drop down list, but of course keeps the previous and next buttons. - while(this.domNode.childNodes.length>2){ - this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]); - } - this._blurOptionNode(); - }, + return declare("dijit._FocusMixin", null, { + // summary: + // Mixin to widget to provide _onFocus() and _onBlur() methods that + // fire when a widget or it's descendants get/lose focus - _onMouseDown: function(/*Event*/ evt){ - dojo.stopEvent(evt); - }, + // flag that I want _onFocus()/_onBlur() notifications from focus manager + _focusManager: focus + }); - _onMouseUp: function(/*Event*/ evt){ - if(evt.target === this.domNode || !this._highlighted_option){ - // !this._highlighted_option check to prevent immediate selection when menu appears on top - // of <input>, see #9898. Note that _HasDropDown also has code to prevent this. - return; - }else if(evt.target == this.previousButton){ - this._blurOptionNode(); - this.onPage(-1); - }else if(evt.target == this.nextButton){ - this._blurOptionNode(); - this.onPage(1); - }else{ - var tgt = evt.target; - // while the clicked node is inside the div - while(!tgt.item){ - // recurse to the top - tgt = tgt.parentNode; - } - this._setValueAttr({ target: tgt }, true); - } - }, +}); - _onMouseOver: function(/*Event*/ evt){ - if(evt.target === this.domNode){ return; } - var tgt = evt.target; - if(!(tgt == this.previousButton || tgt == this.nextButton)){ - // while the clicked node is inside the div - while(!tgt.item){ - // recurse to the top - tgt = tgt.parentNode; - } - } - this._focusOptionNode(tgt); - }, +}, +'dojo/data/util/filter':function(){ +define("dojo/data/util/filter", ["dojo/_base/lang"], function(lang) { + // module: + // dojo/data/util/filter + // summary: + // TODOC - _onMouseOut: function(/*Event*/ evt){ - if(evt.target === this.domNode){ return; } - this._blurOptionNode(); - }, +var filter = lang.getObject("dojo.data.util.filter", true); - _focusOptionNode: function(/*DomNode*/ node){ - // summary: - // Does the actual highlight. - if(this._highlighted_option != node){ - this._blurOptionNode(); - this._highlighted_option = node; - dojo.addClass(this._highlighted_option, "dijitMenuItemSelected"); - } - }, +filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/ ignoreCase){ + // summary: + // Helper function to convert a simple pattern to a regular expression for matching. + // description: + // Returns a regular expression object that conforms to the defined conversion rules. + // For example: + // ca* -> /^ca.*$/ + // *ca* -> /^.*ca.*$/ + // *c\*a* -> /^.*c\*a.*$/ + // *c\*a?* -> /^.*c\*a..*$/ + // and so on. + // + // pattern: string + // A simple matching pattern to convert that follows basic rules: + // * Means match anything, so ca* means match anything starting with ca + // ? Means match single character. So, b?b will match to bob and bab, and so on. + // \ is an escape character. So for example, \* means do not treat * as a match, but literal character *. + // To use a \ as a character in the string, it must be escaped. So in the pattern it should be + // represented by \\ to be treated as an ordinary \ character instead of an escape. + // + // ignoreCase: + // An optional flag to indicate if the pattern matching should be treated as case-sensitive or not when comparing + // By default, it is assumed case sensitive. - _blurOptionNode: function(){ - // summary: - // Removes highlight on highlighted option. - if(this._highlighted_option){ - dojo.removeClass(this._highlighted_option, "dijitMenuItemSelected"); - this._highlighted_option = null; - } - }, + var rxp = "^"; + var c = null; + for(var i = 0; i < pattern.length; i++){ + c = pattern.charAt(i); + switch(c){ + case '\\': + rxp += c; + i++; + rxp += pattern.charAt(i); + break; + case '*': + rxp += ".*"; break; + case '?': + rxp += "."; break; + case '$': + case '^': + case '/': + case '+': + case '.': + case '|': + case '(': + case ')': + case '{': + case '}': + case '[': + case ']': + rxp += "\\"; //fallthrough + default: + rxp += c; + } + } + rxp += "$"; + if(ignoreCase){ + return new RegExp(rxp,"mi"); //RegExp + }else{ + return new RegExp(rxp,"m"); //RegExp + } - _highlightNextOption: function(){ - // summary: - // Highlight the item just below the current selection. - // If nothing selected, highlight first option. +}; - // because each press of a button clears the menu, - // the highlighted option sometimes becomes detached from the menu! - // test to see if the option has a parent to see if this is the case. - if(!this.getHighlightedOption()){ - var fc = this.domNode.firstChild; - this._focusOptionNode(fc.style.display == "none" ? fc.nextSibling : fc); - }else{ - var ns = this._highlighted_option.nextSibling; - if(ns && ns.style.display != "none"){ - this._focusOptionNode(ns); - }else{ - this.highlightFirstOption(); - } - } - // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover - dojo.window.scrollIntoView(this._highlighted_option); - }, +return filter; +}); - highlightFirstOption: function(){ - // summary: - // Highlight the first real item in the list (not Previous Choices). - var first = this.domNode.firstChild; - var second = first.nextSibling; - this._focusOptionNode(second.style.display == "none" ? first : second); // remotely possible that Previous Choices is the only thing in the list - dojo.window.scrollIntoView(this._highlighted_option); - }, +}, +'dijit/_WidgetsInTemplateMixin':function(){ +define("dijit/_WidgetsInTemplateMixin", [ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/parser", // parser.parse + "dijit/registry" // registry.findWidgets +], function(array, declare, parser, registry){ + + // module: + // dijit/_WidgetsInTemplateMixin + // summary: + // Mixin to supplement _TemplatedMixin when template contains widgets - highlightLastOption: function(){ - // summary: - // Highlight the last real item in the list (not More Choices). - this._focusOptionNode(this.domNode.lastChild.previousSibling); - dojo.window.scrollIntoView(this._highlighted_option); - }, + return declare("dijit._WidgetsInTemplateMixin", null, { + // summary: + // Mixin to supplement _TemplatedMixin when template contains widgets - _highlightPrevOption: function(){ - // summary: - // Highlight the item just above the current selection. - // If nothing selected, highlight last option (if - // you select Previous and try to keep scrolling up the list). - if(!this.getHighlightedOption()){ - var lc = this.domNode.lastChild; - this._focusOptionNode(lc.style.display == "none" ? lc.previousSibling : lc); - }else{ - var ps = this._highlighted_option.previousSibling; - if(ps && ps.style.display != "none"){ - this._focusOptionNode(ps); - }else{ - this.highlightLastOption(); - } - } - dojo.window.scrollIntoView(this._highlighted_option); - }, + // _earlyTemplatedStartup: Boolean + // A fallback to preserve the 1.0 - 1.3 behavior of children in + // templates having their startup called before the parent widget + // fires postCreate. Defaults to 'false', causing child widgets to + // have their .startup() called immediately before a parent widget + // .startup(), but always after the parent .postCreate(). Set to + // 'true' to re-enable to previous, arguably broken, behavior. + _earlyTemplatedStartup: false, - _page: function(/*Boolean*/ up){ - // summary: - // Handles page-up and page-down keypresses + // widgetsInTemplate: [protected] Boolean + // Should we parse the template to find widgets that might be + // declared in markup inside it? (Remove for 2.0 and assume true) + widgetsInTemplate: true, - var scrollamount = 0; - var oldscroll = this.domNode.scrollTop; - var height = dojo.style(this.domNode, "height"); - // if no item is highlighted, highlight the first option - if(!this.getHighlightedOption()){ - this._highlightNextOption(); - } - while(scrollamount<height){ - if(up){ - // stop at option 1 - if(!this.getHighlightedOption().previousSibling || - this._highlighted_option.previousSibling.style.display == "none"){ - break; - } - this._highlightPrevOption(); - }else{ - // stop at last option - if(!this.getHighlightedOption().nextSibling || - this._highlighted_option.nextSibling.style.display == "none"){ - break; - } - this._highlightNextOption(); - } - // going backwards - var newscroll=this.domNode.scrollTop; - scrollamount+=(newscroll-oldscroll)*(up ? -1:1); - oldscroll=newscroll; - } - }, + _beforeFillContent: function(){ + if(this.widgetsInTemplate){ + // Before copying over content, instantiate widgets in template + var node = this.domNode; - pageUp: function(){ - // summary: - // Handles pageup keypress. - // TODO: just call _page directly from handleKey(). - // tags: - // private - this._page(true); - }, + var cw = (this._startupWidgets = parser.parse(node, { + noStart: !this._earlyTemplatedStartup, + template: true, + inherited: {dir: this.dir, lang: this.lang, textDir: this.textDir}, + propsThis: this, // so data-dojo-props of widgets in the template can reference "this" to refer to me + scope: "dojo" // even in multi-version mode templates use dojoType/data-dojo-type + })); - pageDown: function(){ - // summary: - // Handles pagedown keypress. - // TODO: just call _page directly from handleKey(). - // tags: - // private - this._page(false); - }, + this._supportingWidgets = registry.findWidgets(node); - getHighlightedOption: function(){ - // summary: - // Returns the highlighted option. - var ho = this._highlighted_option; - return (ho && ho.parentNode) ? ho : null; + this._attachTemplateNodes(cw, function(n,p){ + return n[p]; + }); + } }, - handleKey: function(evt){ - // summary: - // Handle keystroke event forwarded from ComboBox, returning false if it's - // a keystroke I recognize and process, true otherwise. - switch(evt.charOrCode){ - case dojo.keys.DOWN_ARROW: - this._highlightNextOption(); - return false; - case dojo.keys.PAGE_DOWN: - this.pageDown(); - return false; - case dojo.keys.UP_ARROW: - this._highlightPrevOption(); - return false; - case dojo.keys.PAGE_UP: - this.pageUp(); - return false; - default: - return true; - } + startup: function(){ + array.forEach(this._startupWidgets, function(w){ + if(w && !w._started && w.startup){ + w.startup(); + } + }); + this.inherited(arguments); } - } -); - -dojo.declare( - "dijit.form.ComboBox", - [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin], - { - // summary: - // Auto-completing text box, and base class for dijit.form.FilteringSelect. - // - // description: - // The drop down box's values are populated from an class called - // a data provider, which returns a list of values based on the characters - // that the user has typed into the input box. - // If OPTION tags are used as the data provider via markup, - // then the OPTION tag's child text node is used as the widget value - // when selected. The OPTION tag's value attribute is ignored. - // To set the default value when using OPTION tags, specify the selected - // attribute on 1 of the child OPTION tags. - // - // Some of the options to the ComboBox are actually arguments to the data - // provider. + }); +}); - _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ - // summary: - // Hook so set('value', value) works. - // description: - // Sets the value of the select. - this._set("item", null); // value not looked up in store - if(!value){ value = ''; } // null translates to blank - dijit.form.ValidationTextBox.prototype._setValueAttr.call(this, value, priorityChange, displayedValue); - } - } -); +}, +'dojo/fx/Toggler':function(){ +define("dojo/fx/Toggler", ["../_base/lang","../_base/declare","../_base/fx", "../_base/connect"], + function(lang, declare, baseFx, connectUtil) { + // module: + // dojo/fx/Toggler + // summary: + // TODOC -dojo.declare("dijit.form._ComboBoxDataStore", null, { +return declare("dojo.fx.Toggler", null, { // summary: - // Inefficient but small data store specialized for inlined `dijit.form.ComboBox` data + // A simple `dojo.Animation` toggler API. // // description: - // Provides a store for inlined data like: - // - // | <select> - // | <option value="AL">Alabama</option> - // | ... - // - // Actually. just implements the subset of dojo.data.Read/Notification - // needed for ComboBox and FilteringSelect to work. + // class constructor for an animation toggler. It accepts a packed + // set of arguments about what type of animation to use in each + // direction, duration, etc. All available members are mixed into + // these animations from the constructor (for example, `node`, + // `showDuration`, `hideDuration`). // - // Note that an item is just a pointer to the <option> DomNode. - - constructor: function( /*DomNode*/ root){ - this.root = root; - if(root.tagName != "SELECT" && root.firstChild){ - root = dojo.query("select", root); - if(root.length > 0){ // SELECT is a child of srcNodeRef - root = root[0]; - }else{ // no select, so create 1 to parent the option tags to define selectedIndex - this.root.innerHTML = "<SELECT>"+this.root.innerHTML+"</SELECT>"; - root = this.root.firstChild; - } - this.root = root; - } - dojo.query("> option", root).forEach(function(node){ - // TODO: this was added in #3858 but unclear why/if it's needed; doesn't seem to be. - // If it is needed then can we just hide the select itself instead? - //node.style.display="none"; - node.innerHTML = dojo.trim(node.innerHTML); - }); + // example: + // | var t = new dojo.fx.Toggler({ + // | node: "nodeId", + // | showDuration: 500, + // | // hideDuration will default to "200" + // | showFunc: dojo.fx.wipeIn, + // | // hideFunc will default to "fadeOut" + // | }); + // | t.show(100); // delay showing for 100ms + // | // ...time passes... + // | t.hide(); - }, + // node: DomNode + // the node to target for the showing and hiding animations + node: null, - getValue: function( /*item*/ item, - /*attribute-name-string*/ attribute, - /*value?*/ defaultValue){ - return (attribute == "value") ? item.value : (item.innerText || item.textContent || ''); - }, + // showFunc: Function + // The function that returns the `dojo.Animation` to show the node + showFunc: baseFx.fadeIn, - isItemLoaded: function(/*anything*/ something){ - return true; - }, + // hideFunc: Function + // The function that returns the `dojo.Animation` to hide the node + hideFunc: baseFx.fadeOut, - getFeatures: function(){ - return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true}; - }, + // showDuration: + // Time in milliseconds to run the show Animation + showDuration: 200, - _fetchItems: function( /*Object*/ args, - /*Function*/ findCallback, - /*Function*/ errorCallback){ - // summary: - // See dojo.data.util.simpleFetch.fetch() - if(!args.query){ args.query = {}; } - if(!args.query.name){ args.query.name = ""; } - if(!args.queryOptions){ args.queryOptions = {}; } - var matcher = dojo.data.util.filter.patternToRegExp(args.query.name, args.queryOptions.ignoreCase), - items = dojo.query("> option", this.root).filter(function(option){ - return (option.innerText || option.textContent || '').match(matcher); - } ); - if(args.sort){ - items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this)); - } - findCallback(items, args); - }, + // hideDuration: + // Time in milliseconds to run the hide Animation + hideDuration: 200, - close: function(/*dojo.data.api.Request || args || null*/ request){ - return; - }, + // FIXME: need a policy for where the toggler should "be" the next + // time show/hide are called if we're stopped somewhere in the + // middle. + // FIXME: also would be nice to specify individual showArgs/hideArgs mixed into + // each animation individually. + // FIXME: also would be nice to have events from the animations exposed/bridged - getLabel: function(/*item*/ item){ - return item.innerHTML; - }, + /*===== + _showArgs: null, + _showAnim: null, + + _hideArgs: null, + _hideAnim: null, - getIdentity: function(/*item*/ item){ - return dojo.attr(item, "value"); + _isShowing: false, + _isHiding: false, + =====*/ + + constructor: function(args){ + var _t = this; + + lang.mixin(_t, args); + _t.node = args.node; + _t._showArgs = lang.mixin({}, args); + _t._showArgs.node = _t.node; + _t._showArgs.duration = _t.showDuration; + _t.showAnim = _t.showFunc(_t._showArgs); + + _t._hideArgs = lang.mixin({}, args); + _t._hideArgs.node = _t.node; + _t._hideArgs.duration = _t.hideDuration; + _t.hideAnim = _t.hideFunc(_t._hideArgs); + + connectUtil.connect(_t.showAnim, "beforeBegin", lang.hitch(_t.hideAnim, "stop", true)); + connectUtil.connect(_t.hideAnim, "beforeBegin", lang.hitch(_t.showAnim, "stop", true)); }, - fetchItemByIdentity: function(/*Object*/ args){ - // summary: - // Given the identity of an item, this method returns the item that has - // that identity through the onItem callback. - // Refer to dojo.data.api.Identity.fetchItemByIdentity() for more details. - // - // description: - // Given arguments like: - // - // | {identity: "CA", onItem: function(item){...} - // - // Call `onItem()` with the DOM node `<option value="CA">California</option>` - var item = dojo.query("> option[value='" + args.identity + "']", this.root)[0]; - args.onItem(item); + show: function(delay){ + // summary: Toggle the node to showing + // delay: Integer? + // Ammount of time to stall playing the show animation + return this.showAnim.play(delay || 0); }, - fetchSelectedItem: function(){ - // summary: - // Get the option marked as selected, like `<option selected>`. - // Not part of dojo.data API. - var root = this.root, - si = root.selectedIndex; - return typeof si == "number" - ? dojo.query("> option:nth-child(" + (si != -1 ? si+1 : 1) + ")", root)[0] - : null; // dojo.data.Item + hide: function(delay){ + // summary: Toggle the node to hidden + // delay: Integer? + // Ammount of time to stall playing the hide animation + return this.hideAnim.play(delay || 0); } }); -//Mix in the simple fetch implementation to this class. -dojo.extend(dijit.form._ComboBoxDataStore,dojo.data.util.simpleFetch); -} +}); -if(!dojo._hasResource["dijit.form.FilteringSelect"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.FilteringSelect"] = true; -dojo.provide("dijit.form.FilteringSelect"); +}, +'dijit/form/FilteringSelect':function(){ +define("dijit/form/FilteringSelect", [ + "dojo/data/util/filter", // filter.patternToRegExp + "dojo/_base/declare", // declare + "dojo/_base/Deferred", // Deferred.when + "dojo/_base/lang", // lang.mixin + "./MappedTextBox", + "./ComboBoxMixin" +], function(filter, declare, Deferred, lang, MappedTextBox, ComboBoxMixin){ + +/*===== + var MappedTextBox = dijit.form.MappedTextBox; + var ComboBoxMixin = dijit.form.ComboBoxMixin; +=====*/ + // module: + // dijit/form/FilteringSelect + // summary: + // An enhanced version of the HTML SELECT tag, populated dynamically -dojo.declare( - "dijit.form.FilteringSelect", - [dijit.form.MappedTextBox, dijit.form.ComboBoxMixin], - { + return declare("dijit.form.FilteringSelect", [MappedTextBox, ComboBoxMixin], { // summary: // An enhanced version of the HTML SELECT tag, populated dynamically // @@ -15265,7 +25042,7 @@ dojo.declare( isValid: function(){ // Overrides ValidationTextBox.isValid() - return this.item || (!this.required && this.get('displayedValue') == ""); // #5974 + return !!this.item || (!this.required && this.get('displayedValue') == ""); // #5974 }, _refreshState: function(){ @@ -15276,39 +25053,37 @@ dojo.declare( _callbackSetLabel: function( /*Array*/ result, - /*Object*/ dataObject, + /*Object*/ query, + /*Object*/ options, /*Boolean?*/ priorityChange){ // summary: - // Callback from dojo.data after lookup of user entered value finishes + // Callback from dojo.store after lookup of user entered value finishes // setValue does a synchronous lookup, // so it calls _callbackSetLabel directly, // and so does not pass dataObject // still need to test against _lastQuery in case it came too late - if((dataObject && dataObject.query[this.searchAttr] != this._lastQuery) || (!dataObject && result.length && this.store.getIdentity(result[0]) != this._lastQuery)){ + if((query && query[this.searchAttr] !== this._lastQuery) || (!query && result.length && this.store.getIdentity(result[0]) != this._lastQuery)){ return; } if(!result.length){ //#3268: don't modify display value on bad input //#3285: change CSS to indicate error - this.valueNode.value = ""; - dijit.form.TextBox.superclass._setValueAttr.call(this, "", priorityChange || (priorityChange === undefined && !this._focused)); - this._set("item", null); - this.validate(this._focused); + this.set("value", '', priorityChange || (priorityChange === undefined && !this.focused), this.textbox.value, null); }else{ this.set('item', result[0], priorityChange); } }, - _openResultList: function(/*Object*/ results, /*Object*/ dataObject){ + _openResultList: function(/*Object*/ results, /*Object*/ query, /*Object*/ options){ // Callback when a data store query completes. // Overrides ComboBox._openResultList() // #3285: tap into search callback to see if user's query resembles a match - if(dataObject.query[this.searchAttr] != this._lastQuery){ + if(query[this.searchAttr] !== this._lastQuery){ return; } - dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments); + this.inherited(arguments); if(this.item === undefined){ // item == undefined for keyboard search // If the search returned no items that means that the user typed @@ -15332,28 +25107,32 @@ dojo.declare( return "value"; }, - _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){ + _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue, /*item?*/ item){ // summary: // Hook so set('value', value) works. // description: // Sets the value of the select. // Also sets the label to the corresponding value by reverse lookup. if(!this._onChangeActive){ priorityChange = null; } - this._lastQuery = value; - if(value === null || value === ''){ - this._setDisplayedValueAttr('', priorityChange); - return; - } - - //#3347: fetchItemByIdentity if no keyAttr specified - var self = this; - this.store.fetchItemByIdentity({ - identity: value, - onItem: function(item){ - self._callbackSetLabel(item? [item] : [], undefined, priorityChange); + if(item === undefined){ + if(value === null || value === ''){ + value = ''; + if(!lang.isString(displayedValue)){ + this._setDisplayedValueAttr(displayedValue||'', priorityChange); + return; + } } - }); + + var self = this; + this._lastQuery = value; + Deferred.when(this.store.get(value), function(item){ + self._callbackSetLabel(item? [item] : [], undefined, undefined, priorityChange); + }); + }else{ + this.valueNode.value = value; + this.inherited(arguments); + } }, _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ @@ -15366,7 +25145,6 @@ dojo.declare( // tags: // private this.inherited(arguments); - this.valueNode.value = this.value; this._lastDisplayedValue = this.textbox.value; }, @@ -15398,9 +25176,22 @@ dojo.declare( // Note that if there's a custom labelFunc() this code if(this.store){ this.closeDropDown(); - var query = dojo.clone(this.query); // #6196: populate query with user-specifics - // escape meta characters of dojo.data.util.filter.patternToRegExp(). - this._lastQuery = query[this.searchAttr] = this._getDisplayQueryString(label); + var query = lang.clone(this.query); // #6196: populate query with user-specifics + + // Generate query + var qs = this._getDisplayQueryString(label), q; + if(this.store._oldAPI){ + // remove this branch for 2.0 + q = qs; + }else{ + // Query on searchAttr is a regex for benefit of dojo.store.Memory, + // but with a toString() method to help dojo.store.JsonRest. + // Search string like "Co*" converted to regex like /^Co.*$/i. + q = filter.patternToRegExp(qs, this.ignoreCase); + q.toString = function(){ return qs; }; + } + this._lastQuery = query[this.searchAttr] = q; + // If the label is not valid, the callback will never set it, // so the last valid value will get the warning textbox. Set the // textbox value now so that the impending warning will make @@ -15409,233 +25200,918 @@ dojo.declare( this._lastDisplayedValue = label; this._set("displayedValue", label); // for watch("displayedValue") notification var _this = this; - var fetch = { - query: query, - queryOptions: { - ignoreCase: this.ignoreCase, - deep: true - }, - onComplete: function(result, dataObject){ - _this._fetchHandle = null; - dojo.hitch(_this, "_callbackSetLabel")(result, dataObject, priorityChange); - }, - onError: function(errText){ - _this._fetchHandle = null; - console.error('dijit.form.FilteringSelect: ' + errText); - dojo.hitch(_this, "_callbackSetLabel")([], undefined, false); - } + var options = { + ignoreCase: this.ignoreCase, + deep: true }; - dojo.mixin(fetch, this.fetchProperties); - this._fetchHandle = this.store.fetch(fetch); + lang.mixin(options, this.fetchProperties); + this._fetchHandle = this.store.query(query, options); + Deferred.when(this._fetchHandle, function(result){ + _this._fetchHandle = null; + _this._callbackSetLabel(result || [], query, options, priorityChange); + }, function(err){ + _this._fetchHandle = null; + if(!_this._cancelingQuery){ // don't treat canceled query as an error + console.error('dijit.form.FilteringSelect: ' + err.toString()); + } + }); } }, undo: function(){ this.set('displayedValue', this._lastDisplayedValue); } + }); +}); + +}, +'dojo/data/util/sorter':function(){ +define("dojo/data/util/sorter", ["dojo/_base/lang"], function(lang) { + // module: + // dojo/data/util/sorter + // summary: + // TODOC + +var sorter = lang.getObject("dojo.data.util.sorter", true); + +sorter.basicComparator = function( /*anything*/ a, + /*anything*/ b){ + // summary: + // Basic comparision function that compares if an item is greater or less than another item + // description: + // returns 1 if a > b, -1 if a < b, 0 if equal. + // 'null' values (null, undefined) are treated as larger values so that they're pushed to the end of the list. + // And compared to each other, null is equivalent to undefined. + + //null is a problematic compare, so if null, we set to undefined. + //Makes the check logic simple, compact, and consistent + //And (null == undefined) === true, so the check later against null + //works for undefined and is less bytes. + var r = -1; + if(a === null){ + a = undefined; } -); + if(b === null){ + b = undefined; + } + if(a == b){ + r = 0; + }else if(a > b || a == null){ + r = 1; + } + return r; //int {-1,0,1} +}; -} +sorter.createSortFunction = function( /* attributes array */sortSpec, /*dojo.data.core.Read*/ store){ + // summary: + // Helper function to generate the sorting function based off the list of sort attributes. + // description: + // The sort function creation will look for a property on the store called 'comparatorMap'. If it exists + // it will look in the mapping for comparisons function for the attributes. If one is found, it will + // use it instead of the basic comparator, which is typically used for strings, ints, booleans, and dates. + // Returns the sorting function for this particular list of attributes and sorting directions. + // + // sortSpec: array + // A JS object that array that defines out what attribute names to sort on and whether it should be descenting or asending. + // The objects should be formatted as follows: + // { + // attribute: "attributeName-string" || attribute, + // descending: true|false; // Default is false. + // } + // store: object + // The datastore object to look up item values from. + // + var sortFunctions=[]; -if(!dojo._hasResource["dijit.form.Form"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.Form"] = true; -dojo.provide("dijit.form.Form"); + function createSortFunction(attr, dir, comp, s){ + //Passing in comp and s (comparator and store), makes this + //function much faster. + return function(itemA, itemB){ + var a = s.getValue(itemA, attr); + var b = s.getValue(itemB, attr); + return dir * comp(a,b); //int + }; + } + var sortAttribute; + var map = store.comparatorMap; + var bc = sorter.basicComparator; + for(var i = 0; i < sortSpec.length; i++){ + sortAttribute = sortSpec[i]; + var attr = sortAttribute.attribute; + if(attr){ + var dir = (sortAttribute.descending) ? -1 : 1; + var comp = bc; + if(map){ + if(typeof attr !== "string" && ("toString" in attr)){ + attr = attr.toString(); + } + comp = map[attr] || bc; + } + sortFunctions.push(createSortFunction(attr, + dir, comp, store)); + } + } + return function(rowA, rowB){ + var i=0; + while(i < sortFunctions.length){ + var ret = sortFunctions[i++](rowA, rowB); + if(ret !== 0){ + return ret;//int + } + } + return 0; //int + }; // Function +}; +return sorter; +}); +}, +'dijit/form/_ButtonMixin':function(){ +define("dijit/form/_ButtonMixin", [ + "dojo/_base/declare", // declare + "dojo/dom", // dom.setSelectable + "dojo/_base/event", // event.stop + "../registry" // registry.byNode +], function(declare, dom, event, registry){ + +// module: +// dijit/form/_ButtonMixin +// summary: +// A mixin to add a thin standard API wrapper to a normal HTML button +return declare("dijit.form._ButtonMixin", null, { + // summary: + // A mixin to add a thin standard API wrapper to a normal HTML button + // description: + // A label should always be specified (through innerHTML) or the label attribute. + // Attach points: + // focusNode (required): this node receives focus + // valueNode (optional): this node's value gets submitted with FORM elements + // containerNode (optional): this node gets the innerHTML assignment for label + // example: + // | <button data-dojo-type="dijit.form.Button" onClick="...">Hello world</button> + // + // example: + // | var button1 = new dijit.form.Button({label: "hello world", onClick: foo}); + // | dojo.body().appendChild(button1.domNode); + // label: HTML String + // Content to display in button. + label: "", + // type: [const] String + // Type of button (submit, reset, button, checkbox, radio) + type: "button", -dojo.declare( - "dijit.form.Form", - [dijit._Widget, dijit._Templated, dijit.form._FormMixin, dijit.layout._ContentPaneResizeMixin], - { + _onClick: function(/*Event*/ e){ // summary: - // Widget corresponding to HTML form tag, for validation and serialization - // - // example: - // | <form dojoType="dijit.form.Form" id="myForm"> - // | Name: <input type="text" name="name" /> - // | </form> - // | myObj = {name: "John Doe"}; - // | dijit.byId('myForm').set('value', myObj); - // | - // | myObj=dijit.byId('myForm').get('value'); + // Internal function to handle click actions + if(this.disabled){ + event.stop(e); + return false; + } + var preventDefault = this.onClick(e) === false; // user click actions + if(!preventDefault && this.type == "submit" && !(this.valueNode||this.focusNode).form){ // see if a non-form widget needs to be signalled + for(var node=this.domNode; node.parentNode; node=node.parentNode){ + var widget=registry.byNode(node); + if(widget && typeof widget._onSubmit == "function"){ + widget._onSubmit(e); + preventDefault = true; + break; + } + } + } + if(preventDefault){ + e.preventDefault(); + } + return !preventDefault; + }, - // HTML <FORM> attributes + postCreate: function(){ + this.inherited(arguments); + dom.setSelectable(this.focusNode, false); + }, - // name: String? - // Name of form for scripting. - name: "", + onClick: function(/*Event*/ /*===== e =====*/){ + // summary: + // Callback for when button is clicked. + // If type="submit", return true to perform submit, or false to cancel it. + // type: + // callback + return true; // Boolean + }, - // action: String? - // Server-side form handler. - action: "", + _setLabelAttr: function(/*String*/ content){ + // summary: + // Hook for set('label', ...) to work. + // description: + // Set the label (text) of the button; takes an HTML string. + this._set("label", content); + (this.containerNode||this.focusNode).innerHTML = content; + } +}); - // method: String? - // HTTP method used to submit the form, either "GET" or "POST". - method: "", +}); - // encType: String? - // Encoding type for the form, ex: application/x-www-form-urlencoded. - encType: "", +}, +'dojo/colors':function(){ +define("dojo/colors", ["./_base/kernel", "./_base/lang", "./_base/Color", "./_base/array"], function(dojo, lang, Color, ArrayUtil) { + // module: + // dojo/colors + // summary: + // TODOC - // accept-charset: String? - // List of supported charsets. - "accept-charset": "", + var ColorExt = lang.getObject("dojo.colors", true); - // accept: String? - // List of MIME types for file upload. - accept: "", +//TODO: this module appears to break naming conventions - // target: String? - // Target frame for the document to be opened in. - target: "", +/*===== + lang.mixin(dojo, { + colors: { + // summary: Color utilities, extending Base dojo.Color + } + }); +=====*/ - templateString: "<form dojoAttachPoint='containerNode' dojoAttachEvent='onreset:_onReset,onsubmit:_onSubmit' ${!nameAttrSetting}></form>", + // this is a standard conversion prescribed by the CSS3 Color Module + var hue2rgb = function(m1, m2, h){ + if(h < 0){ ++h; } + if(h > 1){ --h; } + var h6 = 6 * h; + if(h6 < 1){ return m1 + (m2 - m1) * h6; } + if(2 * h < 1){ return m2; } + if(3 * h < 2){ return m1 + (m2 - m1) * (2 / 3 - h) * 6; } + return m1; + }; + // Override base Color.fromRgb with the impl in this module + dojo.colorFromRgb = Color.fromRgb = function(/*String*/ color, /*dojo.Color?*/ obj){ + // summary: + // get rgb(a) array from css-style color declarations + // description: + // this function can handle all 4 CSS3 Color Module formats: rgb, + // rgba, hsl, hsla, including rgb(a) with percentage values. + var m = color.toLowerCase().match(/^(rgba?|hsla?)\(([\s\.\-,%0-9]+)\)/); + if(m){ + var c = m[2].split(/\s*,\s*/), l = c.length, t = m[1], a; + if((t == "rgb" && l == 3) || (t == "rgba" && l == 4)){ + var r = c[0]; + if(r.charAt(r.length - 1) == "%"){ + // 3 rgb percentage values + a = ArrayUtil.map(c, function(x){ + return parseFloat(x) * 2.56; + }); + if(l == 4){ a[3] = c[3]; } + return Color.fromArray(a, obj); // dojo.Color + } + return Color.fromArray(c, obj); // dojo.Color + } + if((t == "hsl" && l == 3) || (t == "hsla" && l == 4)){ + // normalize hsl values + var H = ((parseFloat(c[0]) % 360) + 360) % 360 / 360, + S = parseFloat(c[1]) / 100, + L = parseFloat(c[2]) / 100, + // calculate rgb according to the algorithm + // recommended by the CSS3 Color Module + m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S, + m1 = 2 * L - m2; + a = [ + hue2rgb(m1, m2, H + 1 / 3) * 256, + hue2rgb(m1, m2, H) * 256, + hue2rgb(m1, m2, H - 1 / 3) * 256, + 1 + ]; + if(l == 4){ a[3] = c[3]; } + return Color.fromArray(a, obj); // dojo.Color + } + } + return null; // dojo.Color + }; - attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { - action: "", - method: "", - encType: "", - "accept-charset": "", - accept: "", - target: "" - }), + var confine = function(c, low, high){ + // summary: + // sanitize a color component by making sure it is a number, + // and clamping it to valid values + c = Number(c); + return isNaN(c) ? high : c < low ? low : c > high ? high : c; // Number + }; - postMixInProperties: function(){ - // Setup name=foo string to be referenced from the template (but only if a name has been specified) - // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660 - this.nameAttrSetting = this.name ? ("name='" + this.name + "'") : ""; - this.inherited(arguments); + Color.prototype.sanitize = function(){ + // summary: makes sure that the object has correct attributes + var t = this; + t.r = Math.round(confine(t.r, 0, 255)); + t.g = Math.round(confine(t.g, 0, 255)); + t.b = Math.round(confine(t.b, 0, 255)); + t.a = confine(t.a, 0, 1); + return this; // dojo.Color + }; + + ColorExt.makeGrey = Color.makeGrey = function(/*Number*/ g, /*Number?*/ a){ + // summary: creates a greyscale color with an optional alpha + return Color.fromArray([g, g, g, a]); // dojo.Color + }; + + // mixin all CSS3 named colors not already in _base, along with SVG 1.0 variant spellings + lang.mixin(Color.named, { + "aliceblue": [240,248,255], + "antiquewhite": [250,235,215], + "aquamarine": [127,255,212], + "azure": [240,255,255], + "beige": [245,245,220], + "bisque": [255,228,196], + "blanchedalmond": [255,235,205], + "blueviolet": [138,43,226], + "brown": [165,42,42], + "burlywood": [222,184,135], + "cadetblue": [95,158,160], + "chartreuse": [127,255,0], + "chocolate": [210,105,30], + "coral": [255,127,80], + "cornflowerblue": [100,149,237], + "cornsilk": [255,248,220], + "crimson": [220,20,60], + "cyan": [0,255,255], + "darkblue": [0,0,139], + "darkcyan": [0,139,139], + "darkgoldenrod": [184,134,11], + "darkgray": [169,169,169], + "darkgreen": [0,100,0], + "darkgrey": [169,169,169], + "darkkhaki": [189,183,107], + "darkmagenta": [139,0,139], + "darkolivegreen": [85,107,47], + "darkorange": [255,140,0], + "darkorchid": [153,50,204], + "darkred": [139,0,0], + "darksalmon": [233,150,122], + "darkseagreen": [143,188,143], + "darkslateblue": [72,61,139], + "darkslategray": [47,79,79], + "darkslategrey": [47,79,79], + "darkturquoise": [0,206,209], + "darkviolet": [148,0,211], + "deeppink": [255,20,147], + "deepskyblue": [0,191,255], + "dimgray": [105,105,105], + "dimgrey": [105,105,105], + "dodgerblue": [30,144,255], + "firebrick": [178,34,34], + "floralwhite": [255,250,240], + "forestgreen": [34,139,34], + "gainsboro": [220,220,220], + "ghostwhite": [248,248,255], + "gold": [255,215,0], + "goldenrod": [218,165,32], + "greenyellow": [173,255,47], + "grey": [128,128,128], + "honeydew": [240,255,240], + "hotpink": [255,105,180], + "indianred": [205,92,92], + "indigo": [75,0,130], + "ivory": [255,255,240], + "khaki": [240,230,140], + "lavender": [230,230,250], + "lavenderblush": [255,240,245], + "lawngreen": [124,252,0], + "lemonchiffon": [255,250,205], + "lightblue": [173,216,230], + "lightcoral": [240,128,128], + "lightcyan": [224,255,255], + "lightgoldenrodyellow": [250,250,210], + "lightgray": [211,211,211], + "lightgreen": [144,238,144], + "lightgrey": [211,211,211], + "lightpink": [255,182,193], + "lightsalmon": [255,160,122], + "lightseagreen": [32,178,170], + "lightskyblue": [135,206,250], + "lightslategray": [119,136,153], + "lightslategrey": [119,136,153], + "lightsteelblue": [176,196,222], + "lightyellow": [255,255,224], + "limegreen": [50,205,50], + "linen": [250,240,230], + "magenta": [255,0,255], + "mediumaquamarine": [102,205,170], + "mediumblue": [0,0,205], + "mediumorchid": [186,85,211], + "mediumpurple": [147,112,219], + "mediumseagreen": [60,179,113], + "mediumslateblue": [123,104,238], + "mediumspringgreen": [0,250,154], + "mediumturquoise": [72,209,204], + "mediumvioletred": [199,21,133], + "midnightblue": [25,25,112], + "mintcream": [245,255,250], + "mistyrose": [255,228,225], + "moccasin": [255,228,181], + "navajowhite": [255,222,173], + "oldlace": [253,245,230], + "olivedrab": [107,142,35], + "orange": [255,165,0], + "orangered": [255,69,0], + "orchid": [218,112,214], + "palegoldenrod": [238,232,170], + "palegreen": [152,251,152], + "paleturquoise": [175,238,238], + "palevioletred": [219,112,147], + "papayawhip": [255,239,213], + "peachpuff": [255,218,185], + "peru": [205,133,63], + "pink": [255,192,203], + "plum": [221,160,221], + "powderblue": [176,224,230], + "rosybrown": [188,143,143], + "royalblue": [65,105,225], + "saddlebrown": [139,69,19], + "salmon": [250,128,114], + "sandybrown": [244,164,96], + "seagreen": [46,139,87], + "seashell": [255,245,238], + "sienna": [160,82,45], + "skyblue": [135,206,235], + "slateblue": [106,90,205], + "slategray": [112,128,144], + "slategrey": [112,128,144], + "snow": [255,250,250], + "springgreen": [0,255,127], + "steelblue": [70,130,180], + "tan": [210,180,140], + "thistle": [216,191,216], + "tomato": [255,99,71], + "turquoise": [64,224,208], + "violet": [238,130,238], + "wheat": [245,222,179], + "whitesmoke": [245,245,245], + "yellowgreen": [154,205,50] + }); + + return Color; +}); + +}, +'dijit/registry':function(){ +define("dijit/registry", [ + "dojo/_base/array", // array.forEach array.map + "dojo/_base/sniff", // has("ie") + "dojo/_base/unload", // unload.addOnWindowUnload + "dojo/_base/window", // win.body + "." // dijit._scopeName +], function(array, has, unload, win, dijit){ + + // module: + // dijit/registry + // summary: + // Registry of existing widget on page, plus some utility methods. + // Must be accessed through AMD api, ex: + // require(["dijit/registry"], function(registry){ registry.byId("foo"); }) + + var _widgetTypeCtr = {}, hash = {}; + + var registry = { + // summary: + // A set of widgets indexed by id + + length: 0, + + add: function(/*dijit._Widget*/ widget){ + // summary: + // Add a widget to the registry. If a duplicate ID is detected, a error is thrown. + // + // widget: dijit._Widget + // Any dijit._Widget subclass. + if(hash[widget.id]){ + throw new Error("Tried to register widget with id==" + widget.id + " but that id is already registered"); + } + hash[widget.id] = widget; + this.length++; }, - execute: function(/*Object*/ formContents){ + remove: function(/*String*/ id){ // summary: - // Deprecated: use submit() - // tags: - // deprecated + // Remove a widget from the registry. Does not destroy the widget; simply + // removes the reference. + if(hash[id]){ + delete hash[id]; + this.length--; + } }, - onExecute: function(){ + byId: function(/*String|Widget*/ id){ // summary: - // Deprecated: use onSubmit() - // tags: - // deprecated + // Find a widget by it's id. + // If passed a widget then just returns the widget. + return typeof id == "string" ? hash[id] : id; // dijit._Widget }, - _setEncTypeAttr: function(/*String*/ value){ - this.encType = value; - dojo.attr(this.domNode, "encType", value); - if(dojo.isIE){ this.domNode.encoding = value; } + byNode: function(/*DOMNode*/ node){ + // summary: + // Returns the widget corresponding to the given DOMNode + return hash[node.getAttribute("widgetId")]; // dijit._Widget }, - postCreate: function(){ - // IE tries to hide encType - // TODO: remove in 2.0, no longer necessary with data-dojo-params - if(dojo.isIE && this.srcNodeRef && this.srcNodeRef.attributes){ - var item = this.srcNodeRef.attributes.getNamedItem('encType'); - if(item && !item.specified && (typeof item.value == "string")){ - this.set('encType', item.value); + toArray: function(){ + // summary: + // Convert registry into a true Array + // + // example: + // Work with the widget .domNodes in a real Array + // | array.map(dijit.registry.toArray(), function(w){ return w.domNode; }); + + var ar = []; + for(var id in hash){ + ar.push(hash[id]); + } + return ar; // dijit._Widget[] + }, + + getUniqueId: function(/*String*/widgetType){ + // summary: + // Generates a unique id for a given widgetType + + var id; + do{ + id = widgetType + "_" + + (widgetType in _widgetTypeCtr ? + ++_widgetTypeCtr[widgetType] : _widgetTypeCtr[widgetType] = 0); + }while(hash[id]); + return dijit._scopeName == "dijit" ? id : dijit._scopeName + "_" + id; // String + }, + + findWidgets: function(/*DomNode*/ root){ + // summary: + // Search subtree under root returning widgets found. + // Doesn't search for nested widgets (ie, widgets inside other widgets). + + var outAry = []; + + function getChildrenHelper(root){ + for(var node = root.firstChild; node; node = node.nextSibling){ + if(node.nodeType == 1){ + var widgetId = node.getAttribute("widgetId"); + if(widgetId){ + var widget = hash[widgetId]; + if(widget){ // may be null on page w/multiple dojo's loaded + outAry.push(widget); + } + }else{ + getChildrenHelper(node); + } + } } } - this.inherited(arguments); + + getChildrenHelper(root); + return outAry; }, - reset: function(/*Event?*/ e){ + _destroyAll: function(){ // summary: - // restores all widget values back to their init values, - // calls onReset() which can cancel the reset by returning false + // Code to destroy all widgets and do other cleanup on page unload - // create fake event so we can know if preventDefault() is called - var faux = { - returnValue: true, // the IE way - preventDefault: function(){ // not IE - this.returnValue = false; - }, - stopPropagation: function(){}, - currentTarget: e ? e.target : this.domNode, - target: e ? e.target : this.domNode - }; - // if return value is not exactly false, and haven't called preventDefault(), then reset - if(!(this.onReset(faux) === false) && faux.returnValue){ - this.inherited(arguments, []); + // Clean up focus manager lingering references to widgets and nodes + dijit._curFocus = null; + dijit._prevFocus = null; + dijit._activeStack = []; + + // Destroy all the widgets, top down + array.forEach(registry.findWidgets(win.body()), function(widget){ + // Avoid double destroy of widgets like Menu that are attached to <body> + // even though they are logically children of other widgets. + if(!widget._destroyed){ + if(widget.destroyRecursive){ + widget.destroyRecursive(); + }else if(widget.destroy){ + widget.destroy(); + } + } + }); + }, + + getEnclosingWidget: function(/*DOMNode*/ node){ + // summary: + // Returns the widget whose DOM tree contains the specified DOMNode, or null if + // the node is not contained within the DOM tree of any widget + while(node){ + var id = node.getAttribute && node.getAttribute("widgetId"); + if(id){ + return hash[id]; + } + node = node.parentNode; } + return null; }, - onReset: function(/*Event?*/ e){ + // In case someone needs to access hash. + // Actually, this is accessed from WidgetSet back-compatibility code + _hash: hash + }; + + if(has("ie")){ + // Only run _destroyAll() for IE because we think it's only necessary in that case, + // and because it causes problems on FF. See bug #3531 for details. + unload.addOnWindowUnload(function(){ + registry._destroyAll(); + }); + } + + /*===== + dijit.registry = { + // summary: + // A list of widgets on a page. + }; + =====*/ + dijit.registry = registry; + + return registry; +}); + +}, +'dijit/tree/_dndContainer':function(){ +define("dijit/tree/_dndContainer", [ + "dojo/aspect", // aspect.after + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add domClass.remove domClass.replace + "dojo/_base/event", // event.stop + "dojo/_base/lang", // lang.getObject lang.mixin lang.hitch + "dojo/mouse", // mouse.enter, mouse.leave + "dojo/on" +], function(aspect, declare, domClass, event, lang, mouse, on){ + + // module: + // dijit/tree/_dndContainer + // summary: + // This is a base class for `dijit.tree._dndSelector`, and isn't meant to be used directly. + // It's modeled after `dojo.dnd.Container`. + + return declare("dijit.tree._dndContainer", null, { + + // summary: + // This is a base class for `dijit.tree._dndSelector`, and isn't meant to be used directly. + // It's modeled after `dojo.dnd.Container`. + // tags: + // protected + + /*===== + // current: DomNode + // The currently hovered TreeNode.rowNode (which is the DOM node + // associated w/a given node in the tree, excluding it's descendants) + current: null, + =====*/ + + constructor: function(tree, params){ // summary: - // Callback when user resets the form. This method is intended - // to be over-ridden. When the `reset` method is called - // programmatically, the return value from `onReset` is used - // to compute whether or not resetting should proceed + // A constructor of the Container + // tree: Node + // Node or node's id to build the container on + // params: dijit.tree.__SourceArgs + // A dict of parameters, which gets mixed into the object // tags: - // callback - return true; // Boolean + // private + this.tree = tree; + this.node = tree.domNode; // TODO: rename; it's not a TreeNode but the whole Tree + lang.mixin(this, params); + + // class-specific variables + this.current = null; // current TreeNode's DOM node + + // states + this.containerState = ""; + domClass.add(this.node, "dojoDndContainer"); + + // set up events + this.events = [ + // container level events + on(this.node, mouse.enter, lang.hitch(this, "onOverEvent")), + on(this.node, mouse.leave, lang.hitch(this, "onOutEvent")), + + // switching between TreeNodes + aspect.after(this.tree, "_onNodeMouseEnter", lang.hitch(this, "onMouseOver"), true), + aspect.after(this.tree, "_onNodeMouseLeave", lang.hitch(this, "onMouseOut"), true), + + // cancel text selection and text dragging + on(this.node, "dragstart", lang.hitch(event, "stop")), + on(this.node, "selectstart", lang.hitch(event, "stop")) + ]; }, - _onReset: function(e){ - this.reset(e); - dojo.stopEvent(e); - return false; + destroy: function(){ + // summary: + // Prepares this object to be garbage-collected + + var h; + while(h = this.events.pop()){ h.remove(); } + + // this.clearItems(); + this.node = this.parent = null; }, - _onSubmit: function(e){ - var fp = dijit.form.Form.prototype; - // TODO: remove this if statement beginning with 2.0 - if(this.execute != fp.execute || this.onExecute != fp.onExecute){ - dojo.deprecated("dijit.form.Form:execute()/onExecute() are deprecated. Use onSubmit() instead.", "", "2.0"); - this.onExecute(); - this.execute(this.getValues()); - } - if(this.onSubmit(e) === false){ // only exactly false stops submit - dojo.stopEvent(e); - } + // mouse events + onMouseOver: function(widget /*===== , evt =====*/){ + // summary: + // Called when mouse is moved over a TreeNode + // widget: TreeNode + // evt: Event + // tags: + // protected + this.current = widget; }, - onSubmit: function(/*Event?*/ e){ + onMouseOut: function(/*===== widget, evt =====*/){ // summary: - // Callback when user submits the form. - // description: - // This method is intended to be over-ridden, but by default it checks and - // returns the validity of form elements. When the `submit` - // method is called programmatically, the return value from - // `onSubmit` is used to compute whether or not submission - // should proceed + // Called when mouse is moved away from a TreeNode + // widget: TreeNode + // evt: Event // tags: - // extension + // protected + this.current = null; + }, - return this.isValid(); // Boolean + _changeState: function(type, newState){ + // summary: + // Changes a named state to new state value + // type: String + // A name of the state to change + // newState: String + // new state + var prefix = "dojoDnd" + type; + var state = type.toLowerCase() + "State"; + //domClass.replace(this.node, prefix + newState, prefix + this[state]); + domClass.replace(this.node, prefix + newState, prefix + this[state]); + this[state] = newState; }, - submit: function(){ + _addItemClass: function(node, type){ // summary: - // programmatically submit form if and only if the `onSubmit` returns true - if(!(this.onSubmit() === false)){ - this.containerNode.submit(); - } + // Adds a class with prefix "dojoDndItem" + // node: Node + // A node + // type: String + // A variable suffix for a class name + domClass.add(node, "dojoDndItem" + type); + }, + + _removeItemClass: function(node, type){ + // summary: + // Removes a class with prefix "dojoDndItem" + // node: Node + // A node + // type: String + // A variable suffix for a class name + domClass.remove(node, "dojoDndItem" + type); + }, + + onOverEvent: function(){ + // summary: + // This function is called once, when mouse is over our container + // tags: + // protected + this._changeState("Container", "Over"); + }, + + onOutEvent: function(){ + // summary: + // This function is called once, when mouse is out of our container + // tags: + // protected + this._changeState("Container", ""); } - } -); + }); +}); -} +}, +'url: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", +'dijit/_base/wai':function(){ +define("dijit/_base/wai", [ + "dojo/dom-attr", // domAttr.attr + "dojo/_base/lang", // lang.mixin + "..", // export symbols to dijit + "../hccss" // not using this module directly, but loading it sets CSS flag on <html> +], function(domAttr, lang, dijit){ + + // module: + // dijit/_base/wai + // summary: + // Deprecated methods for setting/getting wai roles and states. + // New code should call setAttribute()/getAttribute() directly. + // + // Also loads hccss to apply dijit_a11y class to root node if machine is in high-contrast mode. -if(!dojo._hasResource["dijit.form.RadioButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.RadioButton"] = true; -dojo.provide("dijit.form.RadioButton"); + lang.mixin(dijit, { + hasWaiRole: function(/*Element*/ elem, /*String?*/ role){ + // summary: + // Determines if an element has a particular role. + // returns: + // True if elem has the specific role attribute and false if not. + // For backwards compatibility if role parameter not provided, + // returns true if has a role + var waiRole = this.getWaiRole(elem); + return role ? (waiRole.indexOf(role) > -1) : (waiRole.length > 0); + }, + getWaiRole: function(/*Element*/ elem){ + // summary: + // Gets the role for an element (which should be a wai role). + // returns: + // The role of elem or an empty string if elem + // does not have a role. + return lang.trim((domAttr.get(elem, "role") || "").replace("wairole:","")); + }, + setWaiRole: function(/*Element*/ elem, /*String*/ role){ + // summary: + // Sets the role on an element. + // description: + // Replace existing role attribute with new role. -// TODO: for 2.0, move the RadioButton code into this file + domAttr.set(elem, "role", role); + }, -} + removeWaiRole: function(/*Element*/ elem, /*String*/ role){ + // summary: + // Removes the specified role from an element. + // Removes role attribute if no specific role provided (for backwards compat.) + + var roleValue = domAttr.get(elem, "role"); + if(!roleValue){ return; } + if(role){ + var t = lang.trim((" " + roleValue + " ").replace(" " + role + " ", " ")); + domAttr.set(elem, "role", t); + }else{ + elem.removeAttribute("role"); + } + }, + + hasWaiState: function(/*Element*/ elem, /*String*/ state){ + // summary: + // Determines if an element has a given state. + // description: + // Checks for an attribute called "aria-"+state. + // returns: + // true if elem has a value for the given state and + // false if it does not. -if(!dojo._hasResource["dijit.form._FormSelectWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form._FormSelectWidget"] = true; -dojo.provide("dijit.form._FormSelectWidget"); + return elem.hasAttribute ? elem.hasAttribute("aria-"+state) : !!elem.getAttribute("aria-"+state); + }, + getWaiState: function(/*Element*/ elem, /*String*/ state){ + // summary: + // Gets the value of a state on an element. + // description: + // Checks for an attribute called "aria-"+state. + // returns: + // The value of the requested state on elem + // or an empty string if elem has no value for state. + + return elem.getAttribute("aria-"+state) || ""; + }, + + setWaiState: function(/*Element*/ elem, /*String*/ state, /*String*/ value){ + // summary: + // Sets a state on an element. + // description: + // Sets an attribute called "aria-"+state. + + elem.setAttribute("aria-"+state, value); + }, + + removeWaiState: function(/*Element*/ elem, /*String*/ state){ + // summary: + // Removes a state from an element. + // description: + // Sets an attribute called "aria-"+state. + + elem.removeAttribute("aria-"+state); + } + }); + + return dijit; +}); + +}, +'dijit/form/_FormSelectWidget':function(){ +define("dijit/form/_FormSelectWidget", [ + "dojo/_base/array", // array.filter array.forEach array.map array.some + "dojo/aspect", // aspect.after + "dojo/data/util/sorter", // util.sorter.createSortFunction + "dojo/_base/declare", // declare + "dojo/dom", // dom.setSelectable + "dojo/dom-class", // domClass.toggle + "dojo/_base/kernel", // _scopeName + "dojo/_base/lang", // lang.delegate lang.isArray lang.isObject lang.hitch + "dojo/query", // query + "./_FormValueWidget" +], function(array, aspect, sorter, declare, dom, domClass, kernel, lang, query, _FormValueWidget){ + +/*===== + var _FormValueWidget = dijit.form._FormValueWidget; +=====*/ +// module: +// dijit/form/_FormSelectWidget +// summary: +// Extends _FormValueWidget in order to provide "select-specific" +// values - i.e., those values that are unique to <select> elements. /*===== @@ -15656,7 +26132,7 @@ dijit.form.__SelectOption = function(){ } =====*/ -dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { +return declare("dijit.form._FormSelectWidget", _FormValueWidget, { // summary: // Extends _FormValueWidget in order to provide "select-specific" // values - i.e., those values that are unique to <select> elements. @@ -15673,7 +26149,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { options: null, // store: dojo.data.api.Identity - // A store which, at the very least impelements dojo.data.api.Identity + // A store which, at the very least implements dojo.data.api.Identity // to use for getting our list of options - rather than reading them // from the <option> html tags. store: null, @@ -15688,7 +26164,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // onFetch: Function // A callback to do with an onFetch - but before any items are actually - // iterated over (i.e. to filter even futher what you want to add) + // iterated over (i.e. to filter even further what you want to add) onFetch: null, // sortByLabel: Boolean @@ -15739,13 +26215,13 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { if(lookupValue === undefined){ return opts; // dijit.form.__SelectOption[] } - if(dojo.isArray(lookupValue)){ - return dojo.map(lookupValue, "return this.getOptions(item);", this); // dijit.form.__SelectOption[] + if(lang.isArray(lookupValue)){ + return array.map(lookupValue, "return this.getOptions(item);", this); // dijit.form.__SelectOption[] } - if(dojo.isObject(valueOrIdx)){ + if(lang.isObject(valueOrIdx)){ // We were passed an option - so see if it's in our array (directly), // and if it's not, try and find it by value. - if(!dojo.some(this.options, function(o, idx){ + if(!array.some(this.options, function(o, idx){ if(o === lookupValue || (o.value && o.value === lookupValue.value)){ lookupValue = idx; @@ -15765,7 +26241,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { } } if(typeof lookupValue == "number" && lookupValue >= 0 && lookupValue < l){ - return this.options[lookupValue] // dijit.form.__SelectOption + return this.options[lookupValue]; // dijit.form.__SelectOption } return null; // null }, @@ -15776,9 +26252,9 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // of the option is empty or missing, a separator is created instead. // Passing in an array of options will yield slightly better performance // since the children are only loaded once. - if(!dojo.isArray(option)){ option = [option]; } - dojo.forEach(option, function(i){ - if(i && dojo.isObject(i)){ + if(!lang.isArray(option)){ option = [option]; } + array.forEach(option, function(i){ + if(i && lang.isObject(i)){ this.options.push(i); } }, this); @@ -15793,13 +26269,13 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // which case, the select option with a matching value is removed). // You can also pass in an array of those values for a slightly // better performance since the children are only loaded once. - if(!dojo.isArray(valueOrIdx)){ valueOrIdx = [valueOrIdx]; } + if(!lang.isArray(valueOrIdx)){ valueOrIdx = [valueOrIdx]; } var oldOpts = this.getOptions(valueOrIdx); - dojo.forEach(oldOpts, function(i){ + array.forEach(oldOpts, function(i){ // We can get null back in our array - if our option was not found. In // that case, we don't want to blow up... if(i){ - this.options = dojo.filter(this.options, function(node, idx){ + this.options = array.filter(this.options, function(node){ return (node.value !== i.value || node.label !== i.label); }); this._removeOptionItem(i); @@ -15812,10 +26288,10 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // summary: // Updates the values of the given option. The option to update // is matched based on the value of the entered option. Passing - // in an array of new options will yeild better performance since + // in an array of new options will yield better performance since // the children will only be loaded once. - if(!dojo.isArray(newOption)){ newOption = [newOption]; } - dojo.forEach(newOption, function(i){ + if(!lang.isArray(newOption)){ newOption = [newOption]; } + array.forEach(newOption, function(i){ var oldOpt = this.getOptions(i), k; if(oldOpt){ for(k in i){ oldOpt[k] = i[k]; } @@ -15833,8 +26309,8 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // function returns the original store, in case you want to reuse // it or something. // store: dojo.data.api.Identity - // The store you would like to use - it MUST implement Identity, - // and MAY implement Notification. + // The store you would like to use - it MUST implement dojo.data.api.Identity, + // and MAY implement dojo.data.api.Notification. // selectedValue: anything? // The value that this widget should set itself to *after* the store // has been loaded @@ -15844,13 +26320,14 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { fetchArgs = fetchArgs || {}; if(oStore !== store){ // Our store has changed, so update our notifications - dojo.forEach(this._notifyConnections || [], dojo.disconnect); - delete this._notifyConnections; + var h; + while(h = this._notifyConnections.pop()){ h.remove(); } + if(store && store.getFeatures()["dojo.data.api.Notification"]){ this._notifyConnections = [ - dojo.connect(store, "onNew", this, "_onNewItem"), - dojo.connect(store, "onDelete", this, "_onDeleteItem"), - dojo.connect(store, "onSet", this, "_onSetItem") + aspect.after(store, "onNew", lang.hitch(this, "_onNewItem"), true), + aspect.after(store, "onDelete", lang.hitch(this, "_onDeleteItem"), true), + aspect.after(store, "onSet", lang.hitch(this, "_onSetItem"), true) ]; } this._set("store", store); @@ -15867,28 +26344,28 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // Add our new options if(store){ this._loadingStore = true; - store.fetch(dojo.delegate(fetchArgs, { + store.fetch(lang.delegate(fetchArgs, { onComplete: function(items, opts){ if(this.sortByLabel && !fetchArgs.sort && items.length){ - items.sort(dojo.data.util.sorter.createSortFunction([{ + items.sort(sorter.createSortFunction([{ attribute: store.getLabelAttributes(items[0])[0] }], store)); } - + if(fetchArgs.onFetch){ items = fetchArgs.onFetch.call(this, items, opts); } // TODO: Add these guys as a batch, instead of separately - dojo.forEach(items, function(i){ + array.forEach(items, function(i){ this._addOptionForItem(i); }, this); - + // Set our value (which might be undefined), and then tweak // it to send a change event with the real value this._loadingStore = false; this.set("value", "_pendingValue" in this ? this._pendingValue : selectedValue); delete this._pendingValue; - + if(!this.loadChildrenOnOpen){ this._loadChildren(); }else{ @@ -15922,30 +26399,30 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { return; } var opts = this.getOptions() || []; - if(!dojo.isArray(newValue)){ + if(!lang.isArray(newValue)){ newValue = [newValue]; } - dojo.forEach(newValue, function(i, idx){ - if(!dojo.isObject(i)){ + array.forEach(newValue, function(i, idx){ + if(!lang.isObject(i)){ i = i + ""; } if(typeof i === "string"){ - newValue[idx] = dojo.filter(opts, function(node){ + newValue[idx] = array.filter(opts, function(node){ return node.value === i; })[0] || {value: "", label: ""}; } }, this); // Make sure some sane default is set - newValue = dojo.filter(newValue, function(i){ return i && i.value; }); + newValue = array.filter(newValue, function(i){ return i && i.value; }); if(!this.multiple && (!newValue[0] || !newValue[0].value) && opts.length){ newValue[0] = opts[0]; } - dojo.forEach(opts, function(i){ - i.selected = dojo.some(newValue, function(v){ return v.value === i.value; }); + array.forEach(opts, function(i){ + i.selected = array.some(newValue, function(v){ return v.value === i.value; }); }); - var val = dojo.map(newValue, function(i){ return i.value; }), - disp = dojo.map(newValue, function(i){ return i.label; }); + var val = array.map(newValue, function(i){ return i.value; }), + disp = array.map(newValue, function(i){ return i.label; }); this._set("value", this.multiple ? val : val[0]); this._setDisplay(this.multiple ? disp : disp[0]); @@ -15957,10 +26434,10 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // summary: // returns the displayed value of the widget var val = this.get("value"); - if(!dojo.isArray(val)){ + if(!lang.isArray(val)){ val = [val]; } - var ret = dojo.map(this.getOptions(val), function(v){ + var ret = array.map(this.getOptions(val), function(v){ if(v && "label" in v){ return v.label; }else if(v){ @@ -15976,11 +26453,11 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // Loads the children represented by this widget's options. // reset the menu to make it populatable on the next click if(this._loadingStore){ return; } - dojo.forEach(this._getChildren(), function(child){ + array.forEach(this._getChildren(), function(child){ child.destroyRecursive(); }); // Add each menu item - dojo.forEach(this.options, this._addOptionItem, this); + array.forEach(this.options, this._addOptionItem, this); // Update states this._updateSelection(); @@ -15991,16 +26468,16 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // Sets the "selected" class on the item for styling purposes this._set("value", this._getValueFromOpts()); var val = this.value; - if(!dojo.isArray(val)){ + if(!lang.isArray(val)){ val = [val]; } if(val && val[0]){ - dojo.forEach(this._getChildren(), function(child){ - var isSelected = dojo.some(val, function(v){ + array.forEach(this._getChildren(), function(child){ + var isSelected = array.some(val, function(v){ return child.option && (v === child.option.value); }); - dojo.toggleClass(child.domNode, this.baseClass + "SelectedOption", isSelected); - dijit.setWaiState(child.domNode, "selected", isSelected); + domClass.toggle(child.domNode, this.baseClass + "SelectedOption", isSelected); + child.domNode.setAttribute("aria-selected", isSelected); }, this); } }, @@ -16012,7 +26489,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { var opts = this.getOptions() || []; if(!this.multiple && opts.length){ // Mirror what a select does - choose the first one - var opt = dojo.filter(opts, function(i){ + var opt = array.filter(opts, function(i){ return i.selected; })[0]; if(opt && opt.value){ @@ -16023,7 +26500,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { } }else if(this.multiple){ // Set value to be the sum of all selected - return dojo.map(dojo.filter(opts, function(i){ + return array.map(array.filter(opts, function(i){ return i.selected; }), function(i){ return i.value; @@ -16064,8 +26541,8 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { var store = this.store; if(!store.isItemLoaded(item)){ // We are not loaded - so let's load it and add later - store.loadItem({item: item, onComplete: function(i){ - this._addOptionForItem(item); + store.loadItem({item: item, onItem: function(i){ + this._addOptionForItem(i); }, scope: this}); return; @@ -16079,11 +26556,12 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // Saves off our value, if we have an initial one set so we // can use it if we have a store as well (see startup()) this._oValue = (keywordArgs || {}).value || null; + this._notifyConnections = []; }, buildRendering: function(){ this.inherited(arguments); - dojo.setSelectable(this.focusNode, false); + dom.setSelectable(this.focusNode, false); }, _fillContent: function(){ @@ -16093,13 +26571,13 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // function. var opts = this.options; if(!opts){ - opts = this.options = this.srcNodeRef ? dojo.query(">", + opts = this.options = this.srcNodeRef ? query("> *", this.srcNodeRef).map(function(node){ if(node.getAttribute("type") === "separator"){ return { value: "", label: "", selected: false, disabled: false }; } return { - value: (node.getAttribute("data-" + dojo._scopeName + "-value") || node.getAttribute("value")), + value: (node.getAttribute("data-" + kernel._scopeName + "-value") || node.getAttribute("value")), label: String(node.innerHTML), // FIXME: disabled and selected are not valid on complex markup children (which is why we're // looking for data-dojo-value above. perhaps we should data-dojo-props="" this whole thing?) @@ -16112,7 +26590,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { if(!this.value){ this._set("value", this._getValueFromOpts()); }else if(this.multiple && typeof this.value == "string"){ - this_set("value", this.value.split(",")); + this._set("value", this.value.split(",")); } }, @@ -16134,7 +26612,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // Connects in our store, if we have one defined this.inherited(arguments); var store = this.store, fetchArgs = {}; - dojo.forEach(["query", "queryOptions", "onFetch"], function(i){ + array.forEach(["query", "queryOptions", "onFetch"], function(i){ if(this[i]){ fetchArgs[i] = this[i]; } @@ -16151,11 +26629,12 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { destroy: function(){ // summary: // Clean up our connections - dojo.forEach(this._notifyConnections || [], dojo.disconnect); + var h; + while(h = this._notifyConnections.pop()){ h.remove(); } this.inherited(arguments); }, - _addOptionItem: function(/*dijit.form.__SelectOption*/ option){ + _addOptionItem: function(/*dijit.form.__SelectOption*/ /*===== option =====*/){ // summary: // User-overridable function which, for the given option, adds an // item to the select. If the option doesn't have a value, then a @@ -16163,13 +26642,13 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { // in the created option widget. }, - _removeOptionItem: function(/*dijit.form.__SelectOption*/ option){ + _removeOptionItem: function(/*dijit.form.__SelectOption*/ /*===== option =====*/){ // summary: // User-overridable function which, for the given option, removes // its item from the select. }, - _setDisplay: function(/*String or String[]*/ newDisplay){ + _setDisplay: function(/*String or String[]*/ /*===== newDisplay =====*/){ // summary: // Overridable function which will set the display for the // widget. newDisplay is either a string (in the case of @@ -16189,7 +26668,7 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { return this.getOptions(this.get("value")); }, - _pseudoLoadChildren: function(/*item[]*/ items){ + _pseudoLoadChildren: function(/*item[]*/ /*===== items =====*/){ // summary: // a function that will "fake" loading children, if needed, and // if we have set to not load children until the widget opens. @@ -16205,1327 +26684,51 @@ dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { } }); -} - -if(!dojo._hasResource["dijit._KeyNavContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit._KeyNavContainer"] = true; -dojo.provide("dijit._KeyNavContainer"); - - +}); -dojo.declare("dijit._KeyNavContainer", - dijit._Container, - { +}, +'dijit/form/Select':function(){ +require({cache:{ +'url:dijit/form/templates/Select.html':"<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdata-dojo-attach-point=\"_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\" data-dojo-attach-point=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} data-dojo-attach-point=\"valueNode\" value=\"${value}\" aria-hidden=\"true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdata-dojo-attach-point=\"titleNode\" role=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">▼</div\n\t\t></td\n\t></tr></tbody\n></table>\n"}}); +define("dijit/form/Select", [ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.set + "dojo/dom-class", // domClass.add domClass.remove domClass.toggle + "dojo/dom-construct", // domConstruct.create + "dojo/dom-geometry", // domGeometry.setMarginBox + "dojo/_base/event", // event.stop + "dojo/i18n", // i18n.getLocalization + "dojo/_base/lang", // lang.hitch + "./_FormSelectWidget", + "../_HasDropDown", + "../Menu", + "../MenuItem", + "../MenuSeparator", + "../Tooltip", + "dojo/text!./templates/Select.html", + "dojo/i18n!./nls/validate" +], function(array, declare, domAttr, domClass, domConstruct, domGeometry, event, i18n, lang, + _FormSelectWidget, _HasDropDown, Menu, MenuItem, MenuSeparator, Tooltip, template){ - // summary: - // A _Container with keyboard navigation of its children. - // description: - // To use this mixin, call connectKeyNavHandlers() in - // postCreate() and call startupKeyNavChildren() in startup(). - // It provides normalized keyboard and focusing code for Container - // widgets. /*===== - // focusedChild: [protected] Widget - // The currently focused child widget, or null if there isn't one - focusedChild: null, + var _FormSelectWidget = dijit.form._FormSelectWidget; + var _HasDropDown = dijit._HasDropDown; + var _FormSelectWidget = dijit._FormSelectWidget; + var Menu = dijit.Menu; + var MenuItem = dijit.MenuItem; + var MenuSeparator = dijit.MenuSeparator; + var Tooltip = dijit.Tooltip; =====*/ - // tabIndex: Integer - // Tab index of the container; same as HTML tabIndex attribute. - // Note then when user tabs into the container, focus is immediately - // moved to the first item in the container. - tabIndex: "0", - - _keyNavCodes: {}, - - connectKeyNavHandlers: function(/*dojo.keys[]*/ prevKeyCodes, /*dojo.keys[]*/ nextKeyCodes){ - // summary: - // Call in postCreate() to attach the keyboard handlers - // to the container. - // preKeyCodes: dojo.keys[] - // Key codes for navigating to the previous child. - // nextKeyCodes: dojo.keys[] - // Key codes for navigating to the next child. - // tags: - // protected - - var keyCodes = (this._keyNavCodes = {}); - var prev = dojo.hitch(this, this.focusPrev); - var next = dojo.hitch(this, this.focusNext); - dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; }); - dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; }); - keyCodes[dojo.keys.HOME] = dojo.hitch(this, "focusFirstChild"); - keyCodes[dojo.keys.END] = dojo.hitch(this, "focusLastChild"); - this.connect(this.domNode, "onkeypress", "_onContainerKeypress"); - this.connect(this.domNode, "onfocus", "_onContainerFocus"); - }, - - startupKeyNavChildren: function(){ - // summary: - // Call in startup() to set child tabindexes to -1 - // tags: - // protected - dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild")); - }, - - addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){ - // summary: - // Add a child to our _Container - dijit._KeyNavContainer.superclass.addChild.apply(this, arguments); - this._startupChild(widget); - }, - - focus: function(){ - // summary: - // Default focus() implementation: focus the first child. - this.focusFirstChild(); - }, - - focusFirstChild: function(){ - // summary: - // Focus the first focusable child in the container. - // tags: - // protected - var child = this._getFirstFocusableChild(); - if(child){ // edge case: Menu could be empty or hidden - this.focusChild(child); - } - }, - - focusLastChild: function(){ - // summary: - // Focus the last focusable child in the container. - // tags: - // protected - var child = this._getLastFocusableChild(); - if(child){ // edge case: Menu could be empty or hidden - this.focusChild(child); - } - }, - - focusNext: function(){ - // summary: - // Focus the next widget - // tags: - // protected - var child = this._getNextFocusableChild(this.focusedChild, 1); - this.focusChild(child); - }, - - focusPrev: function(){ - // summary: - // Focus the last focusable node in the previous widget - // (ex: go to the ComboButton icon section rather than button section) - // tags: - // protected - var child = this._getNextFocusableChild(this.focusedChild, -1); - this.focusChild(child, true); - }, - - focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){ - // summary: - // Focus widget. - // widget: - // Reference to container's child widget - // last: - // If true and if widget has multiple focusable nodes, focus the - // last one instead of the first one - // tags: - // protected - - if(this.focusedChild && widget !== this.focusedChild){ - this._onChildBlur(this.focusedChild); - } - widget.set("tabIndex", this.tabIndex); // for IE focus outline to appear, must set tabIndex before focs - widget.focus(last ? "end" : "start"); - this._set("focusedChild", widget); - }, - - _startupChild: function(/*dijit._Widget*/ widget){ - // summary: - // Setup for each child widget - // description: - // Sets tabIndex=-1 on each child, so that the tab key will - // leave the container rather than visiting each child. - // tags: - // private - - widget.set("tabIndex", "-1"); - - this.connect(widget, "_onFocus", function(){ - // Set valid tabIndex so tabbing away from widget goes to right place, see #10272 - widget.set("tabIndex", this.tabIndex); - }); - this.connect(widget, "_onBlur", function(){ - widget.set("tabIndex", "-1"); - }); - }, - - _onContainerFocus: function(evt){ - // summary: - // Handler for when the container gets focus - // description: - // Initially the container itself has a tabIndex, but when it gets - // focus, switch focus to first child... - // tags: - // private - - // Note that we can't use _onFocus() because switching focus from the - // _onFocus() handler confuses the focus.js code - // (because it causes _onFocusNode() to be called recursively) - - // focus bubbles on Firefox, - // so just make sure that focus has really gone to the container - if(evt.target !== this.domNode){ return; } - - this.focusFirstChild(); - - // and then set the container's tabIndex to -1, - // (don't remove as that breaks Safari 4) - // so that tab or shift-tab will go to the fields after/before - // the container, rather than the container itself - dojo.attr(this.domNode, "tabIndex", "-1"); - }, - - _onBlur: function(evt){ - // When focus is moved away the container, and its descendant (popup) widgets, - // then restore the container's tabIndex so that user can tab to it again. - // Note that using _onBlur() so that this doesn't happen when focus is shifted - // to one of my child widgets (typically a popup) - if(this.tabIndex){ - dojo.attr(this.domNode, "tabIndex", this.tabIndex); - } - this.inherited(arguments); - }, - - _onContainerKeypress: function(evt){ - // summary: - // When a key is pressed, if it's an arrow key etc. then - // it's handled here. - // tags: - // private - if(evt.ctrlKey || evt.altKey){ return; } - var func = this._keyNavCodes[evt.charOrCode]; - if(func){ - func(); - dojo.stopEvent(evt); - } - }, - - _onChildBlur: function(/*dijit._Widget*/ widget){ - // summary: - // Called when focus leaves a child widget to go - // to a sibling widget. - // tags: - // protected - }, - - _getFirstFocusableChild: function(){ - // summary: - // Returns first child that can be focused - return this._getNextFocusableChild(null, 1); // dijit._Widget - }, - - _getLastFocusableChild: function(){ - // summary: - // Returns last child that can be focused - return this._getNextFocusableChild(null, -1); // dijit._Widget - }, - - _getNextFocusableChild: function(child, dir){ - // summary: - // Returns the next or previous focusable child, compared - // to "child" - // child: Widget - // The current widget - // dir: Integer - // * 1 = after - // * -1 = before - if(child){ - child = this._getSiblingOfChild(child, dir); - } - var children = this.getChildren(); - for(var i=0; i < children.length; i++){ - if(!child){ - child = children[(dir>0) ? 0 : (children.length-1)]; - } - if(child.isFocusable()){ - return child; // dijit._Widget - } - child = this._getSiblingOfChild(child, dir); - } - // no focusable child found - return null; // dijit._Widget - } - } -); - -} - -if(!dojo._hasResource["dijit.MenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.MenuItem"] = true; -dojo.provide("dijit.MenuItem"); - - - - - - -dojo.declare("dijit.MenuItem", - [dijit._Widget, dijit._Templated, dijit._Contained, dijit._CssStateMixin], - { - // summary: - // A line item in a Menu Widget - - // Make 3 columns - // icon, label, and expand arrow (BiDi-dependent) indicating sub-menu - templateString: dojo.cache("dijit", "templates/MenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" role=\"menuitem\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" role=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\">\n\t\t<div dojoAttachPoint=\"arrowWrapper\" style=\"visibility: hidden\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuExpand\"/>\n\t\t\t<span class=\"dijitMenuExpandA11y\">+</span>\n\t\t</div>\n\t</td>\n</tr>\n"), - - attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { - label: { node: "containerNode", type: "innerHTML" }, - iconClass: { node: "iconNode", type: "class" } - }), - - baseClass: "dijitMenuItem", - - // label: String - // Menu text - label: '', - - // iconClass: String - // Class to apply to DOMNode to make it display an icon. - iconClass: "", - - // accelKey: String - // Text for the accelerator (shortcut) key combination. - // Note that although Menu can display accelerator keys there - // is no infrastructure to actually catch and execute these - // accelerators. - accelKey: "", - - // disabled: Boolean - // If true, the menu item is disabled. - // If false, the menu item is enabled. - disabled: false, - - _fillContent: function(/*DomNode*/ source){ - // If button label is specified as srcNodeRef.innerHTML rather than - // this.params.label, handle it here. - if(source && !("label" in this.params)){ - this.set('label', source.innerHTML); - } - }, - - buildRendering: function(){ - this.inherited(arguments); - var label = this.id+"_text"; - dojo.attr(this.containerNode, "id", label); - if(this.accelKeyNode){ - dojo.attr(this.accelKeyNode, "id", this.id + "_accel"); - label += " " + this.id + "_accel"; - } - dijit.setWaiState(this.domNode, "labelledby", label); - dojo.setSelectable(this.domNode, false); - }, - - _onHover: function(){ - // summary: - // Handler when mouse is moved onto menu item - // tags: - // protected - this.getParent().onItemHover(this); - }, - - _onUnhover: function(){ - // summary: - // Handler when mouse is moved off of menu item, - // possibly to a child menu, or maybe to a sibling - // menuitem or somewhere else entirely. - // tags: - // protected - - // if we are unhovering the currently selected item - // then unselect it - this.getParent().onItemUnhover(this); - - // When menu is hidden (collapsed) due to clicking a MenuItem and having it execute, - // FF and IE don't generate an onmouseout event for the MenuItem. - // So, help out _CssStateMixin in this case. - this._set("hovering", false); - }, - - _onClick: function(evt){ - // summary: - // Internal handler for click events on MenuItem. - // tags: - // private - this.getParent().onItemClick(this, evt); - dojo.stopEvent(evt); - }, - - onClick: function(/*Event*/ evt){ - // summary: - // User defined function to handle clicks - // tags: - // callback - }, - - focus: function(){ - // summary: - // Focus on this MenuItem - try{ - if(dojo.isIE == 8){ - // needed for IE8 which won't scroll TR tags into view on focus yet calling scrollIntoView creates flicker (#10275) - this.containerNode.focus(); - } - dijit.focus(this.focusNode); - }catch(e){ - // this throws on IE (at least) in some scenarios - } - }, - - _onFocus: function(){ - // summary: - // This is called by the focus manager when focus - // goes to this MenuItem or a child menu. - // tags: - // protected - this._setSelected(true); - this.getParent()._onItemFocus(this); - - this.inherited(arguments); - }, - - _setSelected: function(selected){ - // summary: - // Indicate that this node is the currently selected one - // tags: - // private - - /*** - * TODO: remove this method and calls to it, when _onBlur() is working for MenuItem. - * Currently _onBlur() gets called when focus is moved from the MenuItem to a child menu. - * That's not supposed to happen, but the problem is: - * In order to allow dijit.popup's getTopPopup() to work,a sub menu's popupParent - * points to the parent Menu, bypassing the parent MenuItem... thus the - * MenuItem is not in the chain of active widgets and gets a premature call to - * _onBlur() - */ - - dojo.toggleClass(this.domNode, "dijitMenuItemSelected", selected); - }, - - setLabel: function(/*String*/ content){ - // summary: - // Deprecated. Use set('label', ...) instead. - // tags: - // deprecated - dojo.deprecated("dijit.MenuItem.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0"); - this.set("label", content); - }, - - setDisabled: function(/*Boolean*/ disabled){ - // summary: - // Deprecated. Use set('disabled', bool) instead. - // tags: - // deprecated - dojo.deprecated("dijit.Menu.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0"); - this.set('disabled', disabled); - }, - _setDisabledAttr: function(/*Boolean*/ value){ - // summary: - // Hook for attr('disabled', ...) to work. - // Enable or disable this menu item. - - dijit.setWaiState(this.focusNode, 'disabled', value ? 'true' : 'false'); - this._set("disabled", value); - }, - _setAccelKeyAttr: function(/*String*/ value){ - // summary: - // Hook for attr('accelKey', ...) to work. - // Set accelKey on this menu item. - - this.accelKeyNode.style.display=value?"":"none"; - this.accelKeyNode.innerHTML=value; - //have to use colSpan to make it work in IE - dojo.attr(this.containerNode,'colSpan',value?"1":"2"); - - this._set("accelKey", value); - } - }); - -} - -if(!dojo._hasResource["dijit.PopupMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.PopupMenuItem"] = true; -dojo.provide("dijit.PopupMenuItem"); - - - -dojo.declare("dijit.PopupMenuItem", - dijit.MenuItem, - { - _fillContent: function(){ - // summary: - // When Menu is declared in markup, this code gets the menu label and - // the popup widget from the srcNodeRef. - // description: - // srcNodeRefinnerHTML contains both the menu item text and a popup widget - // The first part holds the menu item text and the second part is the popup - // example: - // | <div dojoType="dijit.PopupMenuItem"> - // | <span>pick me</span> - // | <popup> ... </popup> - // | </div> - // tags: - // protected - - if(this.srcNodeRef){ - var nodes = dojo.query("*", this.srcNodeRef); - dijit.PopupMenuItem.superclass._fillContent.call(this, nodes[0]); - - // save pointer to srcNode so we can grab the drop down widget after it's instantiated - this.dropDownContainer = this.srcNodeRef; - } - }, - - startup: function(){ - if(this._started){ return; } - this.inherited(arguments); - - // we didn't copy the dropdown widget from the this.srcNodeRef, so it's in no-man's - // land now. move it to dojo.doc.body. - if(!this.popup){ - var node = dojo.query("[widgetId]", this.dropDownContainer)[0]; - this.popup = dijit.byNode(node); - } - dojo.body().appendChild(this.popup.domNode); - this.popup.startup(); - - this.popup.domNode.style.display="none"; - if(this.arrowWrapper){ - dojo.style(this.arrowWrapper, "visibility", ""); - } - dijit.setWaiState(this.focusNode, "haspopup", "true"); - }, - - destroyDescendants: function(){ - if(this.popup){ - // Destroy the popup, unless it's already been destroyed. This can happen because - // the popup is a direct child of <body> even though it's logically my child. - if(!this.popup._destroyed){ - this.popup.destroyRecursive(); - } - delete this.popup; - } - this.inherited(arguments); - } - }); - -} - -if(!dojo._hasResource["dijit.CheckedMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.CheckedMenuItem"] = true; -dojo.provide("dijit.CheckedMenuItem"); - - - -dojo.declare("dijit.CheckedMenuItem", - dijit.MenuItem, - { - // summary: - // A checkbox-like menu item for toggling on and off - - templateString: dojo.cache("dijit", "templates/CheckedMenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" role=\"menuitemcheckbox\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset dijitMenuItemIconCell\" role=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuItemIcon dijitCheckedMenuItemIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t<span class=\"dijitCheckedMenuItemIconChar\">✓</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\"> </td>\n</tr>\n"), - - // checked: Boolean - // Our checked state - checked: false, - _setCheckedAttr: function(/*Boolean*/ checked){ - // summary: - // Hook so attr('checked', bool) works. - // Sets the class and state for the check box. - dojo.toggleClass(this.domNode, "dijitCheckedMenuItemChecked", checked); - dijit.setWaiState(this.domNode, "checked", checked); - this._set("checked", checked); - }, - - onChange: function(/*Boolean*/ checked){ - // summary: - // User defined function to handle check/uncheck events - // tags: - // callback - }, - - _onClick: function(/*Event*/ e){ - // summary: - // Clicking this item just toggles its state - // tags: - // private - if(!this.disabled){ - this.set("checked", !this.checked); - this.onChange(this.checked); - } - this.inherited(arguments); - } - }); - -} - -if(!dojo._hasResource["dijit.MenuSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.MenuSeparator"] = true; -dojo.provide("dijit.MenuSeparator"); - - - - - -dojo.declare("dijit.MenuSeparator", - [dijit._Widget, dijit._Templated, dijit._Contained], - { - // summary: - // A line between two menu items - - 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"), - - buildRendering: function(){ - this.inherited(arguments); - dojo.setSelectable(this.domNode, false); - }, - - isFocusable: function(){ - // summary: - // Override to always return false - // tags: - // protected - - return false; // Boolean - } - }); - -} - -if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.Menu"] = true; -dojo.provide("dijit.Menu"); - - - - - - - - - - -// "dijit/MenuItem", "dijit/PopupMenuItem", "dijit/CheckedMenuItem", "dijit/MenuSeparator" for Back-compat (TODO: remove in 2.0) - -dojo.declare("dijit._MenuBase", - [dijit._Widget, dijit._Templated, dijit._KeyNavContainer], -{ - // summary: - // Base class for Menu and MenuBar - - // parentMenu: [readonly] Widget - // pointer to menu that displayed me - parentMenu: null, - - // popupDelay: Integer - // number of milliseconds before hovering (without clicking) causes the popup to automatically open. - popupDelay: 500, - - startup: function(){ - if(this._started){ return; } - - dojo.forEach(this.getChildren(), function(child){ child.startup(); }); - this.startupKeyNavChildren(); - - this.inherited(arguments); - }, - - onExecute: function(){ - // summary: - // Attach point for notification about when a menu item has been executed. - // This is an internal mechanism used for Menus to signal to their parent to - // close them, because they are about to execute the onClick handler. In - // general developers should not attach to or override this method. - // tags: - // protected - }, - - onCancel: function(/*Boolean*/ closeAll){ - // summary: - // Attach point for notification about when the user cancels the current menu - // This is an internal mechanism used for Menus to signal to their parent to - // close them. In general developers should not attach to or override this method. - // tags: - // protected - }, - - _moveToPopup: function(/*Event*/ evt){ - // summary: - // This handles the right arrow key (left arrow key on RTL systems), - // which will either open a submenu, or move to the next item in the - // ancestor MenuBar - // tags: - // private - - if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){ - this.focusedChild._onClick(evt); - }else{ - var topMenu = this._getTopMenu(); - if(topMenu && topMenu._isMenuBar){ - topMenu.focusNext(); - } - } - }, - - _onPopupHover: function(/*Event*/ evt){ - // summary: - // This handler is called when the mouse moves over the popup. - // tags: - // private - - // if the mouse hovers over a menu popup that is in pending-close state, - // then stop the close operation. - // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker) - if(this.currentPopup && this.currentPopup._pendingClose_timer){ - var parentMenu = this.currentPopup.parentMenu; - // highlight the parent menu item pointing to this popup - if(parentMenu.focusedChild){ - parentMenu.focusedChild._setSelected(false); - } - parentMenu.focusedChild = this.currentPopup.from_item; - parentMenu.focusedChild._setSelected(true); - // cancel the pending close - this._stopPendingCloseTimer(this.currentPopup); - } - }, - - onItemHover: function(/*MenuItem*/ item){ - // summary: - // Called when cursor is over a MenuItem. - // tags: - // protected - - // Don't do anything unless user has "activated" the menu by: - // 1) clicking it - // 2) opening it from a parent menu (which automatically focuses it) - if(this.isActive){ - this.focusChild(item); - if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){ - this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup"), this.popupDelay); - } - } - // if the user is mixing mouse and keyboard navigation, - // then the menu may not be active but a menu item has focus, - // but it's not the item that the mouse just hovered over. - // To avoid both keyboard and mouse selections, use the latest. - if(this.focusedChild){ - this.focusChild(item); - } - this._hoveredChild = item; - }, - - _onChildBlur: function(item){ - // summary: - // Called when a child MenuItem becomes inactive because focus - // has been removed from the MenuItem *and* it's descendant menus. - // tags: - // private - this._stopPopupTimer(); - item._setSelected(false); - // Close all popups that are open and descendants of this menu - var itemPopup = item.popup; - if(itemPopup){ - this._stopPendingCloseTimer(itemPopup); - itemPopup._pendingClose_timer = setTimeout(function(){ - itemPopup._pendingClose_timer = null; - if(itemPopup.parentMenu){ - itemPopup.parentMenu.currentPopup = null; - } - dijit.popup.close(itemPopup); // this calls onClose - }, this.popupDelay); - } - }, - - onItemUnhover: function(/*MenuItem*/ item){ - // summary: - // Callback fires when mouse exits a MenuItem - // tags: - // protected - - if(this.isActive){ - this._stopPopupTimer(); - } - if(this._hoveredChild == item){ this._hoveredChild = null; } - }, - - _stopPopupTimer: function(){ - // summary: - // Cancels the popup timer because the user has stop hovering - // on the MenuItem, etc. - // tags: - // private - if(this.hover_timer){ - clearTimeout(this.hover_timer); - this.hover_timer = null; - } - }, - - _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){ - // summary: - // Cancels the pending-close timer because the close has been preempted - // tags: - // private - if(popup._pendingClose_timer){ - clearTimeout(popup._pendingClose_timer); - popup._pendingClose_timer = null; - } - }, - - _stopFocusTimer: function(){ - // summary: - // Cancels the pending-focus timer because the menu was closed before focus occured - // tags: - // private - if(this._focus_timer){ - clearTimeout(this._focus_timer); - this._focus_timer = null; - } - }, - - _getTopMenu: function(){ - // summary: - // Returns the top menu in this chain of Menus - // tags: - // private - for(var top=this; top.parentMenu; top=top.parentMenu); - return top; - }, - - onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){ - // summary: - // Handle clicks on an item. - // tags: - // private - - // this can't be done in _onFocus since the _onFocus events occurs asynchronously - if(typeof this.isShowingNow == 'undefined'){ // non-popup menu - this._markActive(); - } - - this.focusChild(item); - - if(item.disabled){ return false; } - - if(item.popup){ - this._openPopup(); - }else{ - // before calling user defined handler, close hierarchy of menus - // and restore focus to place it was when menu was opened - this.onExecute(); - - // user defined handler for click - item.onClick(evt); - } - }, - - _openPopup: function(){ - // summary: - // Open the popup to the side of/underneath the current menu item - // tags: - // protected - - this._stopPopupTimer(); - var from_item = this.focusedChild; - if(!from_item){ return; } // the focused child lost focus since the timer was started - var popup = from_item.popup; - if(popup.isShowingNow){ return; } - if(this.currentPopup){ - this._stopPendingCloseTimer(this.currentPopup); - dijit.popup.close(this.currentPopup); - } - popup.parentMenu = this; - popup.from_item = from_item; // helps finding the parent item that should be focused for this popup - var self = this; - dijit.popup.open({ - parent: this, - popup: popup, - around: from_item.domNode, - orient: this._orient || (this.isLeftToRight() ? - {'TR': 'TL', 'TL': 'TR', 'BR': 'BL', 'BL': 'BR'} : - {'TL': 'TR', 'TR': 'TL', 'BL': 'BR', 'BR': 'BL'}), - onCancel: function(){ // called when the child menu is canceled - // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus - // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro) - self.focusChild(from_item); // put focus back on my node - self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved) - from_item._setSelected(true); // oops, _cleanUp() deselected the item - self.focusedChild = from_item; // and unset focusedChild - }, - onExecute: dojo.hitch(this, "_cleanUp") - }); - - this.currentPopup = popup; - // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items - popup.connect(popup.domNode, "onmouseenter", dojo.hitch(self, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close - - if(popup.focus){ - // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar), - // if the cursor happens to collide with the popup, it will generate an onmouseover event - // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that - // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742) - popup._focus_timer = setTimeout(dojo.hitch(popup, function(){ - this._focus_timer = null; - this.focus(); - }), 0); - } - }, - - _markActive: function(){ - // summary: - // Mark this menu's state as active. - // Called when this Menu gets focus from: - // 1) clicking it (mouse or via space/arrow key) - // 2) being opened by a parent menu. - // This is not called just from mouse hover. - // Focusing a menu via TAB does NOT automatically set isActive - // since TAB is a navigation operation and not a selection one. - // For Windows apps, pressing the ALT key focuses the menubar - // menus (similar to TAB navigation) but the menu is not active - // (ie no dropdown) until an item is clicked. - this.isActive = true; - dojo.replaceClass(this.domNode, "dijitMenuActive", "dijitMenuPassive"); - }, - - onOpen: function(/*Event*/ e){ - // summary: - // Callback when this menu is opened. - // This is called by the popup manager as notification that the menu - // was opened. - // tags: - // private - - this.isShowingNow = true; - this._markActive(); - }, - - _markInactive: function(){ - // summary: - // Mark this menu's state as inactive. - this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here - dojo.replaceClass(this.domNode, "dijitMenuPassive", "dijitMenuActive"); - }, - - onClose: function(){ - // summary: - // Callback when this menu is closed. - // This is called by the popup manager as notification that the menu - // was closed. - // tags: - // private - - this._stopFocusTimer(); - this._markInactive(); - this.isShowingNow = false; - this.parentMenu = null; - }, - - _closeChild: function(){ - // summary: - // Called when submenu is clicked or focus is lost. Close hierarchy of menus. - // tags: - // private - this._stopPopupTimer(); - - var fromItem = this.focusedChild && this.focusedChild.from_item; - - if(this.currentPopup){ - // If focus is on my child menu then move focus to me, - // because IE doesn't like it when you display:none a node with focus - if(dijit._curFocus && dojo.isDescendant(dijit._curFocus, this.currentPopup.domNode)){ - this.focusedChild.focusNode.focus(); - } - // Close all popups that are open and descendants of this menu - dijit.popup.close(this.currentPopup); - this.currentPopup = null; - } - - if(this.focusedChild){ // unhighlight the focused item - this.focusedChild._setSelected(false); - this.focusedChild._onUnhover(); - this.focusedChild = null; - } - }, - - _onItemFocus: function(/*MenuItem*/ item){ - // summary: - // Called when child of this Menu gets focus from: - // 1) clicking it - // 2) tabbing into it - // 3) being opened by a parent menu. - // This is not called just from mouse hover. - if(this._hoveredChild && this._hoveredChild != item){ - this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection - } - }, - - _onBlur: function(){ - // summary: - // Called when focus is moved away from this Menu and it's submenus. - // tags: - // protected - this._cleanUp(); - this.inherited(arguments); - }, - - _cleanUp: function(){ - // summary: - // Called when the user is done with this menu. Closes hierarchy of menus. - // tags: - // private - - this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close - if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose - this._markInactive(); - } - } -}); - -dojo.declare("dijit.Menu", - dijit._MenuBase, - { - // summary - // A context menu you can assign to multiple elements - - // TODO: most of the code in here is just for context menu (right-click menu) - // support. In retrospect that should have been a separate class (dijit.ContextMenu). - // Split them for 2.0 - - constructor: function(){ - this._bindings = []; - }, - - templateString: dojo.cache("dijit", "templates/Menu.html", "<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" role=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\" cellspacing=\"0\">\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"), - - baseClass: "dijitMenu", - - // targetNodeIds: [const] String[] - // Array of dom node ids of nodes to attach to. - // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes. - targetNodeIds: [], - - // contextMenuForWindow: [const] Boolean - // If true, right clicking anywhere on the window will cause this context menu to open. - // If false, must specify targetNodeIds. - contextMenuForWindow: false, - - // leftClickToOpen: [const] Boolean - // If true, menu will open on left click instead of right click, similiar to a file menu. - leftClickToOpen: false, - - // refocus: Boolean - // When this menu closes, re-focus the element which had focus before it was opened. - refocus: true, - - postCreate: function(){ - if(this.contextMenuForWindow){ - this.bindDomNode(dojo.body()); - }else{ - // TODO: should have _setTargetNodeIds() method to handle initialization and a possible - // later set('targetNodeIds', ...) call. There's also a problem that targetNodeIds[] - // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610) - dojo.forEach(this.targetNodeIds, this.bindDomNode, this); - } - var k = dojo.keys, l = this.isLeftToRight(); - this._openSubMenuKey = l ? k.RIGHT_ARROW : k.LEFT_ARROW; - this._closeSubMenuKey = l ? k.LEFT_ARROW : k.RIGHT_ARROW; - this.connectKeyNavHandlers([k.UP_ARROW], [k.DOWN_ARROW]); - }, - - _onKeyPress: function(/*Event*/ evt){ - // summary: - // Handle keyboard based menu navigation. - // tags: - // protected - - if(evt.ctrlKey || evt.altKey){ return; } - - switch(evt.charOrCode){ - case this._openSubMenuKey: - this._moveToPopup(evt); - dojo.stopEvent(evt); - break; - case this._closeSubMenuKey: - if(this.parentMenu){ - if(this.parentMenu._isMenuBar){ - this.parentMenu.focusPrev(); - }else{ - this.onCancel(false); - } - }else{ - dojo.stopEvent(evt); - } - break; - } - }, - - // thanks burstlib! - _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){ - // summary: - // Returns the window reference of the passed iframe - // tags: - // private - var win = dojo.window.get(this._iframeContentDocument(iframe_el)) || - // Moz. TODO: is this available when defaultView isn't? - this._iframeContentDocument(iframe_el)['__parent__'] || - (iframe_el.name && dojo.doc.frames[iframe_el.name]) || null; - return win; // Window - }, - - _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){ - // summary: - // Returns a reference to the document object inside iframe_el - // tags: - // protected - var doc = iframe_el.contentDocument // W3 - || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE - || (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document) - || null; - return doc; // HTMLDocument - }, - - bindDomNode: function(/*String|DomNode*/ node){ - // summary: - // Attach menu to given node - node = dojo.byId(node); - - var cn; // Connect node - - // Support context menus on iframes. Rather than binding to the iframe itself we need - // to bind to the <body> node inside the iframe. - if(node.tagName.toLowerCase() == "iframe"){ - var iframe = node, - win = this._iframeContentWindow(iframe); - cn = dojo.withGlobal(win, dojo.body); - }else{ - - // To capture these events at the top level, attach to <html>, not <body>. - // Otherwise right-click context menu just doesn't work. - cn = (node == dojo.body() ? dojo.doc.documentElement : node); - } - - - // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode()) - var binding = { - node: node, - iframe: iframe - }; - - // Save info about binding in _bindings[], and make node itself record index(+1) into - // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may - // start with a number, which fails on FF/safari. - dojo.attr(node, "_dijitMenu" + this.id, this._bindings.push(binding)); - - // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished - // loading yet, in which case we need to wait for the onload event first, and then connect - // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so - // we need to monitor keyboard events in addition to the oncontextmenu event. - var doConnects = dojo.hitch(this, function(cn){ - return [ - // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu, - // rather than shift-F10? - dojo.connect(cn, this.leftClickToOpen ? "onclick" : "oncontextmenu", this, function(evt){ - // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler - dojo.stopEvent(evt); - this._scheduleOpen(evt.target, iframe, {x: evt.pageX, y: evt.pageY}); - }), - dojo.connect(cn, "onkeydown", this, function(evt){ - if(evt.shiftKey && evt.keyCode == dojo.keys.F10){ - dojo.stopEvent(evt); - this._scheduleOpen(evt.target, iframe); // no coords - open near target node - } - }) - ]; - }); - binding.connects = cn ? doConnects(cn) : []; - - if(iframe){ - // Setup handler to [re]bind to the iframe when the contents are initially loaded, - // and every time the contents change. - // Need to do this b/c we are actually binding to the iframe's <body> node. - // Note: can't use dojo.connect(), see #9609. - - binding.onloadHandler = dojo.hitch(this, function(){ - // want to remove old connections, but IE throws exceptions when trying to - // access the <body> node because it's already gone, or at least in a state of limbo - - var win = this._iframeContentWindow(iframe); - cn = dojo.withGlobal(win, dojo.body); - binding.connects = doConnects(cn); - }); - if(iframe.addEventListener){ - iframe.addEventListener("load", binding.onloadHandler, false); - }else{ - iframe.attachEvent("onload", binding.onloadHandler); - } - } - }, - - unBindDomNode: function(/*String|DomNode*/ nodeName){ - // summary: - // Detach menu from given node - - var node; - try{ - node = dojo.byId(nodeName); - }catch(e){ - // On IE the dojo.byId() call will get an exception if the attach point was - // the <body> node of an <iframe> that has since been reloaded (and thus the - // <body> node is in a limbo state of destruction. - return; - } - - // node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array - var attrName = "_dijitMenu" + this.id; - if(node && dojo.hasAttr(node, attrName)){ - var bid = dojo.attr(node, attrName)-1, b = this._bindings[bid]; - dojo.forEach(b.connects, dojo.disconnect); - - // Remove listener for iframe onload events - var iframe = b.iframe; - if(iframe){ - if(iframe.removeEventListener){ - iframe.removeEventListener("load", b.onloadHandler, false); - }else{ - iframe.detachEvent("onload", b.onloadHandler); - } - } - - dojo.removeAttr(node, attrName); - delete this._bindings[bid]; - } - }, - - _scheduleOpen: function(/*DomNode?*/ target, /*DomNode?*/ iframe, /*Object?*/ coords){ - // summary: - // Set timer to display myself. Using a timer rather than displaying immediately solves - // two problems: - // - // 1. IE: without the delay, focus work in "open" causes the system - // context menu to appear in spite of stopEvent. - // - // 2. Avoid double-shows on linux, where shift-F10 generates an oncontextmenu event - // even after a dojo.stopEvent(e). (Shift-F10 on windows doesn't generate the - // oncontextmenu event.) - - if(!this._openTimer){ - this._openTimer = setTimeout(dojo.hitch(this, function(){ - delete this._openTimer; - this._openMyself({ - target: target, - iframe: iframe, - coords: coords - }); - }), 1); - } - }, - - _openMyself: function(args){ - // summary: - // Internal function for opening myself when the user does a right-click or something similar. - // args: - // This is an Object containing: - // * target: - // The node that is being clicked - // * iframe: - // If an <iframe> is being clicked, iframe points to that iframe - // * coords: - // Put menu at specified x/y position in viewport, or if iframe is - // specified, then relative to iframe. - // - // _openMyself() formerly took the event object, and since various code references - // evt.target (after connecting to _openMyself()), using an Object for parameters - // (so that old code still works). - - var target = args.target, - iframe = args.iframe, - coords = args.coords; - - // Get coordinates to open menu, either at specified (mouse) position or (if triggered via keyboard) - // then near the node the menu is assigned to. - if(coords){ - if(iframe){ - // Specified coordinates are on <body> node of an <iframe>, convert to match main document - var od = target.ownerDocument, - ifc = dojo.position(iframe, true), - win = this._iframeContentWindow(iframe), - scroll = dojo.withGlobal(win, "_docScroll", dojo); - - var cs = dojo.getComputedStyle(iframe), - tp = dojo._toPixelValue, - left = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingLeft)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderLeftWidth) : 0), - top = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingTop)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderTopWidth) : 0); - - coords.x += ifc.x + left - scroll.x; - coords.y += ifc.y + top - scroll.y; - } - }else{ - coords = dojo.position(target, true); - coords.x += 10; - coords.y += 10; - } - - var self=this; - var savedFocus = dijit.getFocus(this); - function closeAndRestoreFocus(){ - // user has clicked on a menu or popup - if(self.refocus){ - dijit.focus(savedFocus); - } - dijit.popup.close(self); - } - dijit.popup.open({ - popup: this, - x: coords.x, - y: coords.y, - onExecute: closeAndRestoreFocus, - onCancel: closeAndRestoreFocus, - orient: this.isLeftToRight() ? 'L' : 'R' - }); - this.focus(); - - this._onBlur = function(){ - this.inherited('_onBlur', arguments); - // Usually the parent closes the child widget but if this is a context - // menu then there is no parent - dijit.popup.close(this); - // don't try to restore focus; user has clicked another part of the screen - // and set focus there - }; - }, - - uninitialize: function(){ - dojo.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this); - this.inherited(arguments); - } -} -); - -} - -if(!dojo._hasResource["dijit.form.Select"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.Select"] = true; -dojo.provide("dijit.form.Select"); - - - - - +// module: +// dijit/form/Select +// summary: +// This is a "styleable" select box - it is basically a DropDownButton which +// can take a <select> as its input. -dojo.declare("dijit.form._SelectMenu", dijit.Menu, { +var _SelectMenu = declare("dijit.form._SelectMenu", Menu, { // summary: // An internally-used menu for dropdown that allows us a vertical scrollbar buildRendering: function(){ @@ -17534,25 +26737,25 @@ dojo.declare("dijit.form._SelectMenu", dijit.Menu, { // otherwise, we won't respond correctly to heights/overflows this.inherited(arguments); var o = (this.menuTableNode = this.domNode); - var n = (this.domNode = dojo.create("div", {style: {overflowX: "hidden", overflowY: "scroll"}})); + var n = (this.domNode = domConstruct.create("div", {style: {overflowX: "hidden", overflowY: "scroll"}})); if(o.parentNode){ o.parentNode.replaceChild(n, o); } - dojo.removeClass(o, "dijitMenuTable"); + domClass.remove(o, "dijitMenuTable"); n.className = o.className + " dijitSelectMenu"; o.className = "dijitReset dijitMenuTable"; - dijit.setWaiRole(o,"listbox"); - dijit.setWaiRole(n,"presentation"); + o.setAttribute("role", "listbox"); + n.setAttribute("role", "presentation"); n.appendChild(o); }, postCreate: function(){ // summary: - // stop mousemove from selecting text on IE to be consistent with other browsers + // stop mousemove from selecting text on IE to be consistent with other browsers this.inherited(arguments); - this.connect(this.domNode, "onmousemove", dojo.stopEvent); + this.connect(this.domNode, "onmousemove", event.stop); }, resize: function(/*Object*/ mb){ @@ -17565,7 +26768,7 @@ dojo.declare("dijit.form._SelectMenu", dijit.Menu, { // mb: Object // The margin box to set this dropdown to. if(mb){ - dojo.marginBox(this.domNode, mb); + domGeometry.setMarginBox(this.domNode, mb); if("w" in mb){ // We've explicitly set the wrapper <div>'s width, so set <table> width to match. // 100% is safer than a pixel value because there may be a scroll bar with @@ -17576,25 +26779,21 @@ dojo.declare("dijit.form._SelectMenu", dijit.Menu, { } }); -dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropDown], { +var Select = declare("dijit.form.Select", [_FormSelectWidget, _HasDropDown], { // summary: // This is a "styleable" select box - it is basically a DropDownButton which // can take a <select> as its input. baseClass: "dijitSelect", - templateString: dojo.cache("dijit.form", "templates/Select.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdojoAttachPoint=\"_buttonNode,tableNode,focusNode\" cellspacing='0' cellpadding='0'\n\trole=\"combobox\" aria-haspopup=\"true\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents dijitButtonNode\" role=\"presentation\"\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\" dojoAttachPoint=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} dojoAttachPoint=\"valueNode\" value=\"${value}\" aria-hidden=\"true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdojoAttachPoint=\"titleNode\" role=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">▼</div\n\t\t></td\n\t></tr></tbody\n></table>\n"), - - // attributeMap: Object - // Add in our style to be applied to the focus node - attributeMap: dojo.mixin(dojo.clone(dijit.form._FormSelectWidget.prototype.attributeMap),{style:"tableNode"}), + templateString: template, // required: Boolean // Can be true or false, default is false. required: false, - // state: String - // Shows current state (ie, validation result) of input (Normal, Warning, or Error) + // state: [readonly] String + // "Incomplete" if this select is required but unset (i.e. blank value), "" otherwise state: "", // message: String @@ -17607,7 +26806,7 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD // emptyLabel: string // What to display in an "empty" dropdown - emptyLabel: " ", + emptyLabel: " ", // // _isLoaded: Boolean // Whether or not we have been loaded @@ -17627,8 +26826,8 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD this.value = this.options[si >= 0 ? si : 0].value; } // Create the dropDown widget - this.dropDown = new dijit.form._SelectMenu({id: this.id + "_menu"}); - dojo.addClass(this.dropDown.domNode, this.baseClass + "Menu"); + this.dropDown = new _SelectMenu({id: this.id + "_menu"}); + domClass.add(this.dropDown.domNode, this.baseClass + "Menu"); }, _getMenuItemForOption: function(/*dijit.form.__SelectOption*/ option){ @@ -17637,17 +26836,17 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD // used to display it. This can be overridden as needed if(!option.value && !option.label){ // We are a separator (no label set for it) - return new dijit.MenuSeparator(); + return new MenuSeparator(); }else{ // Just a regular menu option - var click = dojo.hitch(this, "_setValueAttr", option); - var item = new dijit.MenuItem({ + var click = lang.hitch(this, "_setValueAttr", option); + var item = new MenuItem({ option: option, label: option.label || this.emptyLabel, onClick: click, disabled: option.disabled || false }); - dijit.setWaiRole(item.focusNode, "listitem"); + item.focusNode.setAttribute("role", "listitem"); return item; } }, @@ -17689,8 +26888,8 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD }else{ // Drop down menu is blank but add one blank entry just so something appears on the screen // to let users know that they are no choices (mimicing native select behavior) - dojo.forEach(this._getChildren(), function(child){ child.destroyRecursive(); }); - var item = new dijit.MenuItem({label: " "}); + array.forEach(this._getChildren(), function(child){ child.destroyRecursive(); }); + var item = new MenuItem({label: " "}); this.dropDown.addChild(item); } }else{ @@ -17708,7 +26907,19 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD _setValueAttr: function(value){ this.inherited(arguments); - dojo.attr(this.valueNode, "value", this.get("value")); + domAttr.set(this.valueNode, "value", this.get("value")); + this.validate(this.focused); // to update this.state + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + this.inherited(arguments); + this.validate(this.focused); // to update this.state + }, + + _setRequiredAttr: function(/*Boolean*/ value){ + this._set("required", value); + this.focusNode.setAttribute("aria-required", value); + this.validate(this.focused); // to update this.state }, _setDisplay: function(/*String*/ newDisplay){ @@ -17716,32 +26927,31 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD // sets the display for the given value (or values) var lbl = newDisplay || this.emptyLabel; this.containerNode.innerHTML = '<span class="dijitReset dijitInline ' + this.baseClass + 'Label">' + lbl + '</span>'; - dijit.setWaiState(this.focusNode, "valuetext", lbl); + this.focusNode.setAttribute("aria-valuetext", lbl); }, validate: function(/*Boolean*/ isFocused){ // summary: - // Called by oninit, onblur, and onkeypress. + // Called by oninit, onblur, and onkeypress, and whenever required/disabled state changes // description: // Show missing or invalid messages if appropriate, and highlight textbox field. // Used when a select is initially set to no value and the user is required to // set the value. - - var isValid = this.isValid(isFocused); - this._set("state", isValid ? "" : "Error"); - dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true"); + + var isValid = this.disabled || this.isValid(isFocused); + this._set("state", isValid ? "" : "Incomplete"); + this.focusNode.setAttribute("aria-invalid", isValid ? "false" : "true"); var message = isValid ? "" : this._missingMsg; - if(this.message !== message){ - this._set("message", message); - dijit.hideTooltip(this.domNode); - if(message){ - dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight()); - } + if(message && this.focused && this._hasBeenBlurred){ + Tooltip.show(message, this.domNode, this.tooltipPosition, !this.isLeftToRight()); + }else{ + Tooltip.hide(this.domNode); } + this._set("message", message); return isValid; }, - isValid: function(/*Boolean*/ isFocused){ + isValid: function(/*Boolean*/ /*===== isFocused =====*/){ // summary: // Whether or not this is a valid value. The only way a Select // can be invalid is when it's required but nothing is selected. @@ -17752,16 +26962,15 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD // summary: // Overridden so that the state will be cleared. this.inherited(arguments); - dijit.hideTooltip(this.domNode); - this._set("state", ""); - this._set("message", "") + Tooltip.hide(this.domNode); + this.validate(this.focused); // to update this.state }, postMixInProperties: function(){ // summary: // set the missing message this.inherited(arguments); - this._missingMsg = dojo.i18n.getLocalization("dijit.form", "validate", + this._missingMsg = i18n.getLocalization("dijit.form", "validate", this.lang).missingMessage; }, @@ -17771,12 +26980,12 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD this.inherited(arguments); - this.connect(this.domNode, "onmousemove", dojo.stopEvent); + this.connect(this.domNode, "onmousemove", event.stop); }, _setStyleAttr: function(/*String||Object*/ value){ this.inherited(arguments); - dojo.toggleClass(this.domNode, this.baseClass + "FixedWidth", !!this.tableNode.style.width); + domClass.toggle(this.domNode, this.baseClass + "FixedWidth", !!this.domNode.style.width); }, isLoaded: function(){ @@ -17809,5780 +27018,3530 @@ dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropD delete this.dropDown; } this.inherited(arguments); - } -}); - -} - -if(!dojo._hasResource["dijit.form.SimpleTextarea"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.form.SimpleTextarea"] = true; -dojo.provide("dijit.form.SimpleTextarea"); - - - -dojo.declare("dijit.form.SimpleTextarea", - dijit.form.TextBox, - { - // summary: - // A simple textarea that degrades, and responds to - // minimal LayoutContainer usage, and works with dijit.form.Form. - // Doesn't automatically size according to input, like Textarea. - // - // example: - // | <textarea dojoType="dijit.form.SimpleTextarea" name="foo" value="bar" rows=30 cols=40></textarea> - // - // example: - // | new dijit.form.SimpleTextarea({ rows:20, cols:30 }, "foo"); - - baseClass: "dijitTextBox dijitTextArea", - - attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, { - rows:"textbox", cols: "textbox" - }), - - // rows: Number - // The number of rows of text. - rows: "3", - - // rows: Number - // The number of characters per line. - cols: "20", - - templateString: "<textarea ${!nameAttrSetting} dojoAttachPoint='focusNode,containerNode,textbox' autocomplete='off'></textarea>", - - postMixInProperties: function(){ - // Copy value from srcNodeRef, unless user specified a value explicitly (or there is no srcNodeRef) - // TODO: parser will handle this in 2.0 - if(!this.value && this.srcNodeRef){ - this.value = this.srcNodeRef.value; - } - this.inherited(arguments); }, - buildRendering: function(){ + _onFocus: function(){ + this.validate(true); // show tooltip if second focus of required tooltip, but no selection this.inherited(arguments); - if(dojo.isIE && this.cols){ // attribute selectors is not supported in IE6 - dojo.addClass(this.textbox, "dijitTextAreaCols"); - } }, - filter: function(/*String*/ value){ - // Override TextBox.filter to deal with newlines... specifically (IIRC) this is for IE which writes newlines - // as \r\n instead of just \n - if(value){ - value = value.replace(/\r/g,""); - } - return this.inherited(arguments); - }, - - _previousValue: "", - _onInput: function(/*Event?*/ e){ - // Override TextBox._onInput() to enforce maxLength restriction - if(this.maxLength){ - var maxLength = parseInt(this.maxLength); - var value = this.textbox.value.replace(/\r/g,''); - var overflow = value.length - maxLength; - if(overflow > 0){ - if(e){ dojo.stopEvent(e); } - var textarea = this.textbox; - if(textarea.selectionStart){ - var pos = textarea.selectionStart; - var cr = 0; - if(dojo.isOpera){ - cr = (this.textbox.value.substring(0,pos).match(/\r/g) || []).length; - } - this.textbox.value = value.substring(0,pos-overflow-cr)+value.substring(pos-cr); - textarea.setSelectionRange(pos-overflow, pos-overflow); - }else if(dojo.doc.selection){ //IE - textarea.focus(); - var range = dojo.doc.selection.createRange(); - // delete overflow characters - range.moveStart("character", -overflow); - range.text = ''; - // show cursor - range.select(); - } - } - this._previousValue = this.textbox.value; - } + _onBlur: function(){ + Tooltip.hide(this.domNode); this.inherited(arguments); } }); -} - -if(!dojo._hasResource["dijit.InlineEditBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.InlineEditBox"] = true; -dojo.provide("dijit.InlineEditBox"); - - - - +Select._Menu = _SelectMenu; // for monkey patching +return Select; +}); +}, +'dojo/store/util/QueryResults':function(){ +define("dojo/store/util/QueryResults", ["../../_base/array", "../../_base/lang", "../../_base/Deferred" +], function(array, lang, Deferred) { + // module: + // dojo/store/util/QueryResults + // summary: + // The module defines a query results wrapper +var util = lang.getObject("dojo.store.util", true); -dojo.declare("dijit.InlineEditBox", - dijit._Widget, - { +util.QueryResults = function(results){ // summary: - // An element with in-line edit capabilites + // A function that wraps the results of a store query with additional + // methods. // // description: - // Behavior for an existing node (`<p>`, `<div>`, `<span>`, etc.) so that - // when you click it, an editor shows up in place of the original - // text. Optionally, Save and Cancel button are displayed below the edit widget. - // When Save is clicked, the text is pulled from the edit - // widget and redisplayed and the edit widget is again hidden. - // By default a plain Textarea widget is used as the editor (or for - // inline values a TextBox), but you can specify an editor such as - // dijit.Editor (for editing HTML) or a Slider (for adjusting a number). - // An edit widget must support the following API to be used: - // - displayedValue or value as initialization parameter, - // and available through set('displayedValue') / set('value') - // - void focus() - // - DOM-node focusNode = node containing editable text - - // editing: [readonly] Boolean - // Is the node currently in edit mode? - editing: false, - - // autoSave: Boolean - // Changing the value automatically saves it; don't have to push save button - // (and save button isn't even displayed) - autoSave: true, - - // buttonSave: String - // Save button label - buttonSave: "", - - // buttonCancel: String - // Cancel button label - buttonCancel: "", - - // renderAsHtml: Boolean - // Set this to true if the specified Editor's value should be interpreted as HTML - // rather than plain text (ex: `dijit.Editor`) - renderAsHtml: false, - - // editor: String|Function - // Class name (or reference to the Class) for Editor widget - editor: "dijit.form.TextBox", - - // editorWrapper: String|Function - // Class name (or reference to the Class) for widget that wraps the editor widget, displaying save/cancel - // buttons. - editorWrapper: "dijit._InlineEditor", - - // editorParams: Object - // Set of parameters for editor, like {required: true} - editorParams: {}, - - // disabled: Boolean - // If true, clicking the InlineEditBox to edit it will have no effect. - disabled: false, - - onChange: function(value){ - // summary: - // Set this handler to be notified of changes to value. - // tags: - // callback - }, - - onCancel: function(){ - // summary: - // Set this handler to be notified when editing is cancelled. - // tags: - // callback - }, - - // width: String - // Width of editor. By default it's width=100% (ie, block mode). - width: "100%", + // QueryResults is a basic wrapper that allows for array-like iteration + // over any kind of returned data from a query. While the simplest store + // will return a plain array of data, other stores may return deferreds or + // promises; this wrapper makes sure that *all* results can be treated + // the same. + // + // Additional methods include `forEach`, `filter` and `map`. + // + // returns: Object + // An array-like object that can be used for iterating over. + // + // example: + // Query a store and iterate over the results. + // + // | store.query({ prime: true }).forEach(function(item){ + // | // do something + // | }); - // value: String - // The display value of the widget in read-only mode - value: "", + if(!results){ + return results; + } + // if it is a promise it may be frozen + if(results.then){ + results = lang.delegate(results); + } + function addIterativeMethod(method){ + if(!results[method]){ + results[method] = function(){ + var args = arguments; + return Deferred.when(results, function(results){ + Array.prototype.unshift.call(args, results); + return util.QueryResults(array[method].apply(array, args)); + }); + }; + } + } + addIterativeMethod("forEach"); + addIterativeMethod("filter"); + addIterativeMethod("map"); + if(!results.total){ + results.total = Deferred.when(results, function(results){ + return results.length; + }); + } + return results; +}; - // noValueIndicator: [const] String - // The text that gets displayed when there is no value (so that the user has a place to click to edit) - noValueIndicator: dojo.isIE <= 6 ? // font-family needed on IE6 but it messes up IE8 - "<span style='font-family: wingdings; text-decoration: underline;'> ✍ </span>" : - "<span style='text-decoration: underline;'> ✍ </span>", +return util.QueryResults; +}); - constructor: function(){ - // summary: - // Sets up private arrays etc. - // tags: - // private - this.editorParams = {}; - }, +}, +'dijit/form/_ListBase':function(){ +define("dijit/form/_ListBase", [ + "dojo/_base/declare", // declare + "dojo/window" // winUtils.scrollIntoView +], function(declare, winUtils){ + +// module: +// dijit/form/_ListBase +// summary: +// Focus-less menu to handle UI events consistently - postMixInProperties: function(){ - this.inherited(arguments); +return declare( "dijit.form._ListBase", null, { + // summary: + // Focus-less menu to handle UI events consistently + // Abstract methods that must be defined externally: + // onSelect: item is active (mousedown but not yet mouseup, or keyboard arrow selected but no Enter) + // onDeselect: cancels onSelect + // tags: + // private - // save pointer to original source node, since Widget nulls-out srcNodeRef - this.displayNode = this.srcNodeRef; + // selected: DOMnode + // currently selected node + selected: null, - // connect handlers to the display node - var events = { - ondijitclick: "_onClick", - onmouseover: "_onMouseOver", - onmouseout: "_onMouseOut", - onfocus: "_onMouseOver", - onblur: "_onMouseOut" - }; - for(var name in events){ - this.connect(this.displayNode, name, events[name]); - } - dijit.setWaiRole(this.displayNode, "button"); - if(!this.displayNode.getAttribute("tabIndex")){ - this.displayNode.setAttribute("tabIndex", 0); + _getTarget: function(/*Event*/ evt){ + var tgt = evt.target; + var container = this.containerNode; + if(tgt == container || tgt == this.domNode){ return null; } + while(tgt && tgt.parentNode != container){ + // recurse to the top + tgt = tgt.parentNode; } - - if(!this.value && !("value" in this.params)){ // "" is a good value if specified directly so check params){ - this.value = dojo.trim(this.renderAsHtml ? this.displayNode.innerHTML : - (this.displayNode.innerText||this.displayNode.textContent||"")); - } - if(!this.value){ - this.displayNode.innerHTML = this.noValueIndicator; - } - - dojo.addClass(this.displayNode, 'dijitInlineEditBoxDisplayMode'); + return tgt; }, - setDisabled: function(/*Boolean*/ disabled){ + selectFirstNode: function(){ // summary: - // Deprecated. Use set('disabled', ...) instead. - // tags: - // deprecated - dojo.deprecated("dijit.InlineEditBox.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0"); - this.set('disabled', disabled); - }, - - _setDisabledAttr: function(/*Boolean*/ disabled){ - // summary: - // Hook to make set("disabled", ...) work. - // Set disabled state of widget. - dijit.setWaiState(this.domNode, "disabled", disabled); - if(disabled){ - this.displayNode.removeAttribute("tabIndex"); - }else{ - this.displayNode.setAttribute("tabIndex", 0); + // Select the first displayed item in the list. + var first = this.containerNode.firstChild; + while(first && first.style.display == "none"){ + first = first.nextSibling; } - dojo.toggleClass(this.displayNode, "dijitInlineEditBoxDisplayModeDisabled", disabled); - this._set("disabled", disabled); + this._setSelectedAttr(first); }, - _onMouseOver: function(){ + selectLastNode: function(){ // summary: - // Handler for onmouseover and onfocus event. - // tags: - // private - if(!this.disabled){ - dojo.addClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover"); + // Select the last displayed item in the list + var last = this.containerNode.lastChild; + while(last && last.style.display == "none"){ + last = last.previousSibling; } + this._setSelectedAttr(last); }, - _onMouseOut: function(){ + selectNextNode: function(){ // summary: - // Handler for onmouseout and onblur event. - // tags: - // private - dojo.removeClass(this.displayNode, "dijitInlineEditBoxDisplayModeHover"); - }, - - _onClick: function(/*Event*/ e){ - // summary: - // Handler for onclick event. - // tags: - // private - if(this.disabled){ return; } - if(e){ dojo.stopEvent(e); } - this._onMouseOut(); - - // Since FF gets upset if you move a node while in an event handler for that node... - setTimeout(dojo.hitch(this, "edit"), 0); - }, - - edit: function(){ - // summary: - // Display the editor widget in place of the original (read only) markup. - // tags: - // private - - if(this.disabled || this.editing){ return; } - this.editing = true; - - // save some display node values that can be restored later - this._savedPosition = dojo.style(this.displayNode, "position") || "static"; - this._savedOpacity = dojo.style(this.displayNode, "opacity") || "1"; - this._savedTabIndex = dojo.attr(this.displayNode, "tabIndex") || "0"; - - if(this.wrapperWidget){ - var ew = this.wrapperWidget.editWidget; - ew.set("displayedValue" in ew ? "displayedValue" : "value", this.value); + // Select the item just below the current selection. + // If nothing selected, select first node. + var selectedNode = this._getSelectedAttr(); + if(!selectedNode){ + this.selectFirstNode(); }else{ - // Placeholder for edit widget - // Put place holder (and eventually editWidget) before the display node so that it's positioned correctly - // when Calendar dropdown appears, which happens automatically on focus. - var placeholder = dojo.create("span", null, this.domNode, "before"); - - // Create the editor wrapper (the thing that holds the editor widget and the save/cancel buttons) - var ewc = typeof this.editorWrapper == "string" ? dojo.getObject(this.editorWrapper) : this.editorWrapper; - this.wrapperWidget = new ewc({ - value: this.value, - buttonSave: this.buttonSave, - buttonCancel: this.buttonCancel, - dir: this.dir, - lang: this.lang, - tabIndex: this._savedTabIndex, - editor: this.editor, - inlineEditBox: this, - sourceStyle: dojo.getComputedStyle(this.displayNode), - save: dojo.hitch(this, "save"), - cancel: dojo.hitch(this, "cancel") - }, placeholder); - if(!this._started){ - this.startup(); + var next = selectedNode.nextSibling; + while(next && next.style.display == "none"){ + next = next.nextSibling; + } + if(!next){ + this.selectFirstNode(); + }else{ + this._setSelectedAttr(next); } } - var ww = this.wrapperWidget; - - if(dojo.isIE){ - dijit.focus(dijit.getFocus()); // IE (at least 8) needs help with tab order changes - } - // to avoid screen jitter, we first create the editor with position:absolute, visibility:hidden, - // and then when it's finished rendering, we switch from display mode to editor - // position:absolute releases screen space allocated to the display node - // opacity:0 is the same as visibility:hidden but is still focusable - // visiblity:hidden removes focus outline - - dojo.style(this.displayNode, { position: "absolute", opacity: "0", display: "none" }); // makes display node invisible, display style used for focus-ability - dojo.style(ww.domNode, { position: this._savedPosition, visibility: "visible", opacity: "1" }); - dojo.attr(this.displayNode, "tabIndex", "-1"); // needed by WebKit for TAB from editor to skip displayNode - - // Replace the display widget with edit widget, leaving them both displayed for a brief time so that - // focus can be shifted without incident. (browser may needs some time to render the editor.) - setTimeout(dojo.hitch(this, function(){ - ww.focus(); // both nodes are showing, so we can switch focus safely - ww._resetValue = ww.getValue(); - }), 0); }, - _onBlur: function(){ + selectPreviousNode: function(){ // summary: - // Called when focus moves outside the InlineEditBox. - // Performs garbage collection. - // tags: - // private - - this.inherited(arguments); - if(!this.editing){ - /* causes IE focus problems, see TooltipDialog_a11y.html... - setTimeout(dojo.hitch(this, function(){ - if(this.wrapperWidget){ - this.wrapperWidget.destroy(); - delete this.wrapperWidget; - } - }), 0); - */ - } - }, - - destroy: function(){ - if(this.wrapperWidget && !this.wrapperWidget._destroyed){ - this.wrapperWidget.destroy(); - delete this.wrapperWidget; + // Select the item just above the current selection. + // If nothing selected, select last node (if + // you select Previous and try to keep scrolling up the list). + var selectedNode = this._getSelectedAttr(); + if(!selectedNode){ + this.selectLastNode(); + }else{ + var prev = selectedNode.previousSibling; + while(prev && prev.style.display == "none"){ + prev = prev.previousSibling; + } + if(!prev){ + this.selectLastNode(); + }else{ + this._setSelectedAttr(prev); + } } - this.inherited(arguments); }, - _showText: function(/*Boolean*/ focus){ + _setSelectedAttr: function(/*DomNode*/ node){ // summary: - // Revert to display mode, and optionally focus on display node - // tags: - // private - - var ww = this.wrapperWidget; - dojo.style(ww.domNode, { position: "absolute", visibility: "hidden", opacity: "0" }); // hide the editor from mouse/keyboard events - dojo.style(this.displayNode, { position: this._savedPosition, opacity: this._savedOpacity, display: "" }); // make the original text visible - dojo.attr(this.displayNode, "tabIndex", this._savedTabIndex); - if(focus){ - dijit.focus(this.displayNode); + // Does the actual select. + if(this.selected != node){ + var selectedNode = this._getSelectedAttr(); + if(selectedNode){ + this.onDeselect(selectedNode); + this.selected = null; + } + if(node && node.parentNode == this.containerNode){ + this.selected = node; + winUtils.scrollIntoView(node); + this.onSelect(node); + } + }else if(node){ + this.onSelect(node); } }, - save: function(/*Boolean*/ focus){ + _getSelectedAttr: function(){ // summary: - // Save the contents of the editor and revert to display mode. - // focus: Boolean - // Focus on the display mode text - // tags: - // private - - if(this.disabled || !this.editing){ return; } - this.editing = false; - - var ww = this.wrapperWidget; - var value = ww.getValue(); - this.set('value', value); // display changed, formatted value - - this._showText(focus); // set focus as needed - }, - - setValue: function(/*String*/ val){ - // summary: - // Deprecated. Use set('value', ...) instead. - // tags: - // deprecated - dojo.deprecated("dijit.InlineEditBox.setValue() is deprecated. Use set('value', ...) instead.", "", "2.0"); - return this.set("value", val); - }, - - _setValueAttr: function(/*String*/ val){ - // summary: - // Hook to make set("value", ...) work. - // Inserts specified HTML value into this node, or an "input needed" character if node is blank. - - val = dojo.trim(val); - var renderVal = this.renderAsHtml ? val : val.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """).replace(/\n/g, "<br>"); - this.displayNode.innerHTML = renderVal || this.noValueIndicator; - this._set("value", val); + // Returns the selected node. + var v = this.selected; + return (v && v.parentNode == this.containerNode) ? v : (this.selected = null); + } +}); - if(this._started){ - // tell the world that we have changed - setTimeout(dojo.hitch(this, "onChange", val), 0); // setTimeout prevents browser freeze for long-running event handlers - } - }, +}); - getValue: function(){ - // summary: - // Deprecated. Use get('value') instead. - // tags: - // deprecated - dojo.deprecated("dijit.InlineEditBox.getValue() is deprecated. Use get('value') instead.", "", "2.0"); - return this.get("value"); - }, +}, +'dijit/form/_FormWidget':function(){ +define("dijit/form/_FormWidget", [ + "dojo/_base/declare", // declare + "dojo/_base/kernel", // kernel.deprecated + "dojo/ready", + "../_Widget", + "../_CssStateMixin", + "../_TemplatedMixin", + "./_FormWidgetMixin" +], function(declare, kernel, ready, _Widget, _CssStateMixin, _TemplatedMixin, _FormWidgetMixin){ - cancel: function(/*Boolean*/ focus){ - // summary: - // Revert to display mode, discarding any changes made in the editor - // tags: - // private +/*===== +var _Widget = dijit._Widget; +var _TemplatedMixin = dijit._TemplatedMixin; +var _CssStateMixin = dijit._CssStateMixin; +var _FormWidgetMixin = dijit.form._FormWidgetMixin; +=====*/ - if(this.disabled || !this.editing){ return; } - this.editing = false; +// module: +// dijit/form/_FormWidget +// summary: +// FormWidget - // tell the world that we have no changes - setTimeout(dojo.hitch(this, "onCancel"), 0); // setTimeout prevents browser freeze for long-running event handlers - this._showText(focus); - } -}); +// Back compat w/1.6, remove for 2.0 +if(!kernel.isAsync){ + ready(0, function(){ + var requires = ["dijit/form/_FormValueWidget"]; + require(requires); // use indirection so modules not rolled into a build + }); +} -dojo.declare( - "dijit._InlineEditor", - [dijit._Widget, dijit._Templated], -{ +return declare("dijit.form._FormWidget", [_Widget, _TemplatedMixin, _CssStateMixin, _FormWidgetMixin], { // summary: - // Internal widget used by InlineEditBox, displayed when in editing mode - // to display the editor and maybe save/cancel buttons. Calling code should - // connect to save/cancel methods to detect when editing is finished - // - // Has mainly the same parameters as InlineEditBox, plus these values: + // Base class for widgets corresponding to native HTML elements such as <checkbox> or <button>, + // which can be children of a <form> node or a `dijit.form.Form` widget. // - // style: Object - // Set of CSS attributes of display node, to replicate in editor + // description: + // Represents a single HTML element. + // All these widgets should have these attributes just like native HTML input elements. + // You can set them during widget construction or afterwards, via `dijit._Widget.attr`. // - // value: String - // Value as an HTML string or plain text string, depending on renderAsHTML flag - - templateString: dojo.cache("dijit", "templates/InlineEditBox.html", "<span data-dojo-attach-point=\"editNode\" role=\"presentation\" style=\"position: absolute; visibility:hidden\" class=\"dijitReset dijitInline\"\n\tdata-dojo-attach-event=\"onkeypress: _onKeyPress\"\n\t><span data-dojo-attach-point=\"editorPlaceholder\"></span\n\t><span data-dojo-attach-point=\"buttonContainer\"\n\t\t><button data-dojo-type=\"dijit.form.Button\" data-dojo-props=\"label: '${buttonSave}', 'class': 'saveButton'\"\n\t\t\tdata-dojo-attach-point=\"saveButton\" data-dojo-attach-event=\"onClick:save\"></button\n\t\t><button data-dojo-type=\"dijit.form.Button\" data-dojo-props=\"label: '${buttonCancel}', 'class': 'cancelButton'\"\n\t\t\tdata-dojo-attach-point=\"cancelButton\" data-dojo-attach-event=\"onClick:cancel\"></button\n\t></span\n></span>\n"), - widgetsInTemplate: true, - - postMixInProperties: function(){ - this.inherited(arguments); - this.messages = dojo.i18n.getLocalization("dijit", "common", this.lang); - dojo.forEach(["buttonSave", "buttonCancel"], function(prop){ - if(!this[prop]){ this[prop] = this.messages[prop]; } - }, this); - }, - - buildRendering: function(){ - this.inherited(arguments); - - // Create edit widget in place in the template - var cls = typeof this.editor == "string" ? dojo.getObject(this.editor) : this.editor; - - // Copy the style from the source - // Don't copy ALL properties though, just the necessary/applicable ones. - // wrapperStyle/destStyle code is to workaround IE bug where getComputedStyle().fontSize - // is a relative value like 200%, rather than an absolute value like 24px, and - // the 200% can refer *either* to a setting on the node or it's ancestor (see #11175) - var srcStyle = this.sourceStyle, - editStyle = "line-height:" + srcStyle.lineHeight + ";", - destStyle = dojo.getComputedStyle(this.domNode); - dojo.forEach(["Weight","Family","Size","Style"], function(prop){ - var textStyle = srcStyle["font"+prop], - wrapperStyle = destStyle["font"+prop]; - if(wrapperStyle != textStyle){ - editStyle += "font-"+prop+":"+srcStyle["font"+prop]+";"; - } - }, this); - dojo.forEach(["marginTop","marginBottom","marginLeft", "marginRight"], function(prop){ - this.domNode.style[prop] = srcStyle[prop]; - }, this); - var width = this.inlineEditBox.width; - if(width == "100%"){ - // block mode - editStyle += "width:100%;"; - this.domNode.style.display = "block"; - }else{ - // inline-block mode - editStyle += "width:" + (width + (Number(width) == width ? "px" : "")) + ";"; - } - var editorParams = dojo.delegate(this.inlineEditBox.editorParams, { - style: editStyle, - dir: this.dir, - lang: this.lang - }); - editorParams[ "displayedValue" in cls.prototype ? "displayedValue" : "value"] = this.value; - this.editWidget = new cls(editorParams, this.editorPlaceholder); - - if(this.inlineEditBox.autoSave){ - // Remove the save/cancel buttons since saving is done by simply tabbing away or - // selecting a value from the drop down list - dojo.destroy(this.buttonContainer); - } - }, - - postCreate: function(){ - this.inherited(arguments); - - var ew = this.editWidget; - - if(this.inlineEditBox.autoSave){ - // Selecting a value from a drop down list causes an onChange event and then we save - this.connect(ew, "onChange", "_onChange"); - - // ESC and TAB should cancel and save. Note that edit widgets do a stopEvent() on ESC key (to - // prevent Dialog from closing when the user just wants to revert the value in the edit widget), - // so this is the only way we can see the key press event. - this.connect(ew, "onKeyPress", "_onKeyPress"); - }else{ - // If possible, enable/disable save button based on whether the user has changed the value - if("intermediateChanges" in ew){ - ew.set("intermediateChanges", true); - this.connect(ew, "onChange", "_onIntermediateChange"); - this.saveButton.set("disabled", true); - } - } - }, + // They also share some common methods. - _onIntermediateChange: function(val){ + setDisabled: function(/*Boolean*/ disabled){ // summary: - // Called for editor widgets that support the intermediateChanges=true flag as a way - // to detect when to enable/disabled the save button - this.saveButton.set("disabled", (this.getValue() == this._resetValue) || !this.enableSave()); - }, - - destroy: function(){ - this.editWidget.destroy(true); // let the parent wrapper widget clean up the DOM - this.inherited(arguments); + // Deprecated. Use set('disabled', ...) instead. + kernel.deprecated("setDisabled("+disabled+") is deprecated. Use set('disabled',"+disabled+") instead.", "", "2.0"); + this.set('disabled', disabled); }, - getValue: function(){ + setValue: function(/*String*/ value){ // summary: - // Return the [display] value of the edit widget - var ew = this.editWidget; - return String(ew.get("displayedValue" in ew ? "displayedValue" : "value")); + // Deprecated. Use set('value', ...) instead. + kernel.deprecated("dijit.form._FormWidget:setValue("+value+") is deprecated. Use set('value',"+value+") instead.", "", "2.0"); + this.set('value', value); }, - _onKeyPress: function(e){ + getValue: function(){ // summary: - // Handler for keypress in the edit box in autoSave mode. - // description: - // For autoSave widgets, if Esc/Enter, call cancel/save. - // tags: - // private - - if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){ - if(e.altKey || e.ctrlKey){ return; } - // If Enter/Esc pressed, treat as save/cancel. - if(e.charOrCode == dojo.keys.ESCAPE){ - dojo.stopEvent(e); - this.cancel(true); // sets editing=false which short-circuits _onBlur processing - }else if(e.charOrCode == dojo.keys.ENTER && e.target.tagName == "INPUT"){ - dojo.stopEvent(e); - this._onChange(); // fire _onBlur and then save - } - - // _onBlur will handle TAB automatically by allowing - // the TAB to change focus before we mess with the DOM: #6227 - // Expounding by request: - // The current focus is on the edit widget input field. - // save() will hide and destroy this widget. - // We want the focus to jump from the currently hidden - // displayNode, but since it's hidden, it's impossible to - // unhide it, focus it, and then have the browser focus - // away from it to the next focusable element since each - // of these events is asynchronous and the focus-to-next-element - // is already queued. - // So we allow the browser time to unqueue the move-focus event - // before we do all the hide/show stuff. - } + // Deprecated. Use get('value') instead. + kernel.deprecated(this.declaredClass+"::getValue() is deprecated. Use get('value') instead.", "", "2.0"); + return this.get('value'); }, - _onBlur: function(){ - // summary: - // Called when focus moves outside the editor - // tags: - // private - + postMixInProperties: function(){ + // Setup name=foo string to be referenced from the template (but only if a name has been specified) + // Unfortunately we can't use _setNameAttr to set the name due to IE limitations, see #8484, #8660. + // Regarding escaping, see heading "Attribute values" in + // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 + this.nameAttrSetting = this.name ? ('name="' + this.name.replace(/'/g, """) + '"') : ''; this.inherited(arguments); - if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){ - if(this.getValue() == this._resetValue){ - this.cancel(false); - }else if(this.enableSave()){ - this.save(false); - } - } - }, - - _onChange: function(){ - // summary: - // Called when the underlying widget fires an onChange event, - // such as when the user selects a value from the drop down list of a ComboBox, - // which means that the user has finished entering the value and we should save. - // tags: - // private - - if(this.inlineEditBox.autoSave && this.inlineEditBox.editing && this.enableSave()){ - dojo.style(this.inlineEditBox.displayNode, { display: "" }); - dijit.focus(this.inlineEditBox.displayNode); // fires _onBlur which will save the formatted value - } - }, - - enableSave: function(){ - // summary: - // User overridable function returning a Boolean to indicate - // if the Save button should be enabled or not - usually due to invalid conditions - // tags: - // extension - return ( - this.editWidget.isValid - ? this.editWidget.isValid() - : true - ); }, - focus: function(){ - // summary: - // Focus the edit widget. - // tags: - // protected - - this.editWidget.focus(); - setTimeout(dojo.hitch(this, function(){ - if(this.editWidget.focusNode && this.editWidget.focusNode.tagName == "INPUT"){ - dijit.selectInputText(this.editWidget.focusNode); - } - }), 0); - } + // Override automatic assigning type --> focusNode, it causes exception on IE. + // Instead, type must be specified as ${type} in the template, as part of the original DOM + _setTypeAttr: null }); -} - -if(!dojo._hasResource["dojo.cookie"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.cookie"] = true; -dojo.provide("dojo.cookie"); - - +}); -/*===== -dojo.__cookieProps = function(){ - // expires: Date|String|Number? - // If a number, the number of days from today at which the cookie - // will expire. If a date, the date past which the cookie will expire. - // If expires is in the past, the cookie will be deleted. - // If expires is omitted or is 0, the cookie will expire when the browser closes. << FIXME: 0 seems to disappear right away? FF3. - // path: String? - // The path to use for the cookie. - // domain: String? - // The domain to use for the cookie. - // secure: Boolean? - // Whether to only send the cookie on secure connections - this.expires = expires; - this.path = path; - this.domain = domain; - this.secure = secure; -} -=====*/ +}, +'dojo/DeferredList':function(){ +define("dojo/DeferredList", ["./_base/kernel", "./_base/Deferred", "./_base/array"], function(dojo, Deferred, darray) { + // module: + // dojo/DeferredList + // summary: + // TODOC -dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/props){ - // summary: - // Get or set a cookie. - // description: - // If one argument is passed, returns the value of the cookie - // For two or more arguments, acts as a setter. - // name: - // Name of the cookie - // value: - // Value for the cookie - // props: - // Properties for the cookie - // example: - // set a cookie with the JSON-serialized contents of an object which - // will expire 5 days from now: - // | dojo.cookie("configObj", dojo.toJson(config), { expires: 5 }); - // - // example: - // de-serialize a cookie back into a JavaScript object: - // | var config = dojo.fromJson(dojo.cookie("configObj")); +dojo.DeferredList = function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/ consumeErrors, /*Function?*/ canceller){ + // summary: + // Provides event handling for a group of Deferred objects. + // description: + // DeferredList takes an array of existing deferreds and returns a new deferred of its own + // this new deferred will typically have its callback fired when all of the deferreds in + // the given list have fired their own deferreds. The parameters `fireOnOneCallback` and + // fireOnOneErrback, will fire before all the deferreds as appropriate // - // example: - // delete a cookie: - // | dojo.cookie("configObj", null, {expires: -1}); - var c = document.cookie; - if(arguments.length == 1){ - var matches = c.match(new RegExp("(?:^|; )" + dojo.regexp.escapeString(name) + "=([^;]*)")); - return matches ? decodeURIComponent(matches[1]) : undefined; // String or undefined - }else{ - props = props || {}; -// FIXME: expires=0 seems to disappear right away, not on close? (FF3) Change docs? - var exp = props.expires; - if(typeof exp == "number"){ - var d = new Date(); - d.setTime(d.getTime() + exp*24*60*60*1000); - exp = props.expires = d; - } - if(exp && exp.toUTCString){ props.expires = exp.toUTCString(); } - - value = encodeURIComponent(value); - var updatedCookie = name + "=" + value, propName; - for(propName in props){ - updatedCookie += "; " + propName; - var propValue = props[propName]; - if(propValue !== true){ updatedCookie += "=" + propValue; } - } - document.cookie = updatedCookie; + // list: + // The list of deferreds to be synchronizied with this DeferredList + // fireOnOneCallback: + // Will cause the DeferredLists callback to be fired as soon as any + // of the deferreds in its list have been fired instead of waiting until + // the entire list has finished + // fireonOneErrback: + // Will cause the errback to fire upon any of the deferreds errback + // canceller: + // A deferred canceller function, see dojo.Deferred + var resultList = []; + Deferred.call(this); + var self = this; + if(list.length === 0 && !fireOnOneCallback){ + this.resolve([0, []]); } -}; - -dojo.cookie.isSupported = function(){ - // summary: - // Use to determine if the current browser supports cookies or not. - // - // Returns true if user allows cookies. - // Returns false if user doesn't allow cookies. + var finished = 0; + darray.forEach(list, function(item, i){ + item.then(function(result){ + if(fireOnOneCallback){ + self.resolve([i, result]); + }else{ + addResult(true, result); + } + },function(error){ + if(fireOnOneErrback){ + self.reject(error); + }else{ + addResult(false, error); + } + if(consumeErrors){ + return null; + } + throw error; + }); + function addResult(succeeded, result){ + resultList[i] = [succeeded, result]; + finished++; + if(finished === list.length){ + self.resolve(resultList); + } - if(!("cookieEnabled" in navigator)){ - this("__djCookieTest__", "CookiesAllowed"); - navigator.cookieEnabled = this("__djCookieTest__") == "CookiesAllowed"; - if(navigator.cookieEnabled){ - this("__djCookieTest__", "", {expires: -1}); } - } - return navigator.cookieEnabled; + }); }; +dojo.DeferredList.prototype = new Deferred(); -} - -if(!dojo._hasResource["dijit.layout.StackController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout.StackController"] = true; -dojo.provide("dijit.layout.StackController"); - - - - - - - -dojo.declare( - "dijit.layout.StackController", - [dijit._Widget, dijit._Templated, dijit._Container], - { - // summary: - // Set of buttons to select a page in a page list. - // description: - // Monitors the specified StackContainer, and whenever a page is - // added, deleted, or selected, updates itself accordingly. - - templateString: "<span role='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>", - - // containerId: [const] String - // The id of the page container that I point to - containerId: "", - - // buttonWidget: [const] String - // The name of the button widget to create to correspond to each page - buttonWidget: "dijit.layout._StackButton", - - constructor: function(){ - this.pane2button = {}; // mapping from pane id to buttons - this.pane2connects = {}; // mapping from pane id to this.connect() handles - this.pane2watches = {}; // mapping from pane id to watch() handles - }, - - buildRendering: function(){ - this.inherited(arguments); - dijit.setWaiRole(this.domNode, "tablist"); // TODO: unneeded? it's in template above. - }, +dojo.DeferredList.prototype.gatherResults = function(deferredList){ + // summary: + // Gathers the results of the deferreds for packaging + // as the parameters to the Deferred Lists' callback + // deferredList: dojo.DeferredList + // The deferred list from which this function gathers results. + // returns: dojo.DeferredList + // The newly created deferred list which packs results as + // parameters to its callback. - postCreate: function(){ - this.inherited(arguments); + var d = new dojo.DeferredList(deferredList, false, true, false); + d.addCallback(function(results){ + var ret = []; + darray.forEach(results, function(result){ + ret.push(result[1]); + }); + return ret; + }); + return d; +}; - // Listen to notifications from StackContainer - this.subscribe(this.containerId+"-startup", "onStartup"); - this.subscribe(this.containerId+"-addChild", "onAddChild"); - this.subscribe(this.containerId+"-removeChild", "onRemoveChild"); - this.subscribe(this.containerId+"-selectChild", "onSelectChild"); - this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress"); - }, +return dojo.DeferredList; +}); - onStartup: function(/*Object*/ info){ - // summary: - // Called after StackContainer has finished initializing - // tags: - // private - dojo.forEach(info.children, this.onAddChild, this); - if(info.selected){ - // Show button corresponding to selected pane (unless selected - // is null because there are no panes) - this.onSelectChild(info.selected); - } - }, +}, +'dojo/dnd/common':function(){ +define("dojo/dnd/common", ["../main"], function(dojo) { + // module: + // dojo/dnd/common + // summary: + // TODOC - destroy: function(){ - for(var pane in this.pane2button){ - this.onRemoveChild(dijit.byId(pane)); - } - this.inherited(arguments); - }, +dojo.getObject("dnd", true, dojo); - onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){ - // summary: - // Called whenever a page is added to the container. - // Create button corresponding to the page. - // tags: - // private - - // create an instance of the button widget - var cls = dojo.getObject(this.buttonWidget); - var button = new cls({ - id: this.id + "_" + page.id, - label: page.title, - dir: page.dir, - lang: page.lang, - showLabel: page.showTitle, - iconClass: page.iconClass, - closeButton: page.closable, - title: page.tooltip - }); - dijit.setWaiState(button.focusNode,"selected", "false"); +dojo.dnd.getCopyKeyState = dojo.isCopyKey; +dojo.dnd._uniqueId = 0; +dojo.dnd.getUniqueId = function(){ + // summary: + // returns a unique string for use with any DOM element + var id; + do{ + id = dojo._scopeName + "Unique" + (++dojo.dnd._uniqueId); + }while(dojo.byId(id)); + return id; +}; - // map from page attribute to corresponding tab button attribute - var pageAttrList = ["title", "showTitle", "iconClass", "closable", "tooltip"], - buttonAttrList = ["label", "showLabel", "iconClass", "closeButton", "title"]; +dojo.dnd._empty = {}; - // watch() so events like page title changes are reflected in tab button - this.pane2watches[page.id] = dojo.map(pageAttrList, function(pageAttr, idx){ - return page.watch(pageAttr, function(name, oldVal, newVal){ - button.set(buttonAttrList[idx], newVal); - }); - }); - - // connections so that clicking a tab button selects the corresponding page - this.pane2connects[page.id] = [ - this.connect(button, 'onClick', dojo.hitch(this,"onButtonClick", page)), - this.connect(button, 'onClickCloseButton', dojo.hitch(this,"onCloseButtonClick", page)) - ]; +dojo.dnd.isFormElement = function(/*Event*/ e){ + // summary: + // returns true if user clicked on a form element + var t = e.target; + if(t.nodeType == 3 /*TEXT_NODE*/){ + t = t.parentNode; + } + return " button textarea input select option ".indexOf(" " + t.tagName.toLowerCase() + " ") >= 0; // Boolean +}; - this.addChild(button, insertIndex); - this.pane2button[page.id] = button; - page.controlButton = button; // this value might be overwritten if two tabs point to same container - if(!this._currentChild){ // put the first child into the tab order - button.focusNode.setAttribute("tabIndex", "0"); - dijit.setWaiState(button.focusNode, "selected", "true"); - this._currentChild = page; - } - // make sure all tabs have the same length - if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){ - this._rectifyRtlTabList(); - } - }, +return dojo.dnd; +}); - onRemoveChild: function(/*dijit._Widget*/ page){ - // summary: - // Called whenever a page is removed from the container. - // Remove the button corresponding to the page. - // tags: - // private - - if(this._currentChild === page){ this._currentChild = null; } - - // disconnect/unwatch connections/watches related to page being removed - dojo.forEach(this.pane2connects[page.id], dojo.hitch(this, "disconnect")); - delete this.pane2connects[page.id]; - dojo.forEach(this.pane2watches[page.id], function(w){ w.unwatch(); }); - delete this.pane2watches[page.id]; - - var button = this.pane2button[page.id]; - if(button){ - this.removeChild(button); - delete this.pane2button[page.id]; - button.destroy(); - } - delete page.controlButton; - }, +}, +'dijit/_base/place':function(){ +define("dijit/_base/place", [ + "dojo/_base/array", // array.forEach + "dojo/_base/lang", // lang.isArray + "dojo/window", // windowUtils.getBox + "../place", + ".." // export to dijit namespace +], function(array, lang, windowUtils, place, dijit){ + + // module: + // dijit/_base/place + // summary: + // Back compatibility module, new code should use dijit/place directly instead of using this module. - onSelectChild: function(/*dijit._Widget*/ page){ - // summary: - // Called when a page has been selected in the StackContainer, either by me or by another StackController - // tags: - // private + dijit.getViewport = function(){ + // summary: + // Deprecated method to return the dimensions and scroll position of the viewable area of a browser window. + // New code should use windowUtils.getBox() - if(!page){ return; } + return windowUtils.getBox(); + }; - if(this._currentChild){ - var oldButton=this.pane2button[this._currentChild.id]; - oldButton.set('checked', false); - dijit.setWaiState(oldButton.focusNode, "selected", "false"); - oldButton.focusNode.setAttribute("tabIndex", "-1"); - } + /*===== + dijit.placeOnScreen = function(node, pos, corners, padding){ + // summary: + // Positions one of the node's corners at specified position + // such that node is fully visible in viewport. + // Deprecated, new code should use dijit.place.at() instead. + }; + =====*/ + dijit.placeOnScreen = place.at; - var newButton=this.pane2button[page.id]; - newButton.set('checked', true); - dijit.setWaiState(newButton.focusNode, "selected", "true"); - this._currentChild = page; - newButton.focusNode.setAttribute("tabIndex", "0"); - var container = dijit.byId(this.containerId); - dijit.setWaiState(container.containerNode, "labelledby", newButton.id); - }, + /*===== + dijit.placeOnScreenAroundElement = function(node, aroundElement, aroundCorners, layoutNode){ + // summary: + // Like dijit.placeOnScreenAroundNode(), except it accepts an arbitrary object + // for the "around" argument and finds a proper processor to place a node. + // Deprecated, new code should use dijit.place.around() instead. + }; + ====*/ + dijit.placeOnScreenAroundElement = function(node, aroundNode, aroundCorners, layoutNode){ + // Convert old style {"BL": "TL", "BR": "TR"} type argument + // to style needed by dijit.place code: + // [ + // {aroundCorner: "BL", corner: "TL" }, + // {aroundCorner: "BR", corner: "TR" } + // ] + var positions; + if(lang.isArray(aroundCorners)){ + positions = aroundCorners; + }else{ + positions = []; + for(var key in aroundCorners){ + positions.push({aroundCorner: key, corner: aroundCorners[key]}); + } + } - onButtonClick: function(/*dijit._Widget*/ page){ - // summary: - // Called whenever one of my child buttons is pressed in an attempt to select a page - // tags: - // private + return place.around(node, aroundNode, positions, true, layoutNode); + }; - var container = dijit.byId(this.containerId); - container.selectChild(page); - }, + /*===== + dijit.placeOnScreenAroundNode = function(node, aroundNode, aroundCorners, layoutNode){ + // summary: + // Position node adjacent or kitty-corner to aroundNode + // such that it's fully visible in viewport. + // Deprecated, new code should use dijit.place.around() instead. + }; + =====*/ + dijit.placeOnScreenAroundNode = dijit.placeOnScreenAroundElement; - onCloseButtonClick: function(/*dijit._Widget*/ page){ - // summary: - // Called whenever one of my child buttons [X] is pressed in an attempt to close a page - // tags: - // private - - var container = dijit.byId(this.containerId); - container.closeChild(page); - if(this._currentChild){ - var b = this.pane2button[this._currentChild.id]; - if(b){ - dijit.focus(b.focusNode || b.domNode); - } - } - }, + /*===== + dijit.placeOnScreenAroundRectangle = function(node, aroundRect, aroundCorners, layoutNode){ + // summary: + // Like dijit.placeOnScreenAroundNode(), except that the "around" + // parameter is an arbitrary rectangle on the screen (x, y, width, height) + // instead of a dom node. + // Deprecated, new code should use dijit.place.around() instead. + }; + =====*/ + dijit.placeOnScreenAroundRectangle = dijit.placeOnScreenAroundElement; - // TODO: this is a bit redundant with forward, back api in StackContainer - adjacent: function(/*Boolean*/ forward){ - // summary: - // Helper for onkeypress to find next/previous button - // tags: - // private + dijit.getPopupAroundAlignment = function(/*Array*/ position, /*Boolean*/ leftToRight){ + // summary: + // Deprecated method, unneeded when using dijit/place directly. + // Transforms the passed array of preferred positions into a format suitable for + // passing as the aroundCorners argument to dijit.placeOnScreenAroundElement. + // + // position: String[] + // This variable controls the position of the drop down. + // It's an array of strings with the following values: + // + // * before: places drop down to the left of the target node/widget, or to the right in + // the case of RTL scripts like Hebrew and Arabic + // * after: places drop down to the right of the target node/widget, or to the left in + // the case of RTL scripts like Hebrew and Arabic + // * above: drop down goes above target node + // * below: drop down goes below target node + // + // The list is positions is tried, in order, until a position is found where the drop down fits + // within the viewport. + // + // leftToRight: Boolean + // Whether the popup will be displaying in leftToRight mode. + // + var align = {}; + array.forEach(position, function(pos){ + var ltr = leftToRight; + switch(pos){ + case "after": + align[leftToRight ? "BR" : "BL"] = leftToRight ? "BL" : "BR"; + break; + case "before": + align[leftToRight ? "BL" : "BR"] = leftToRight ? "BR" : "BL"; + break; + case "below-alt": + ltr = !ltr; + // fall through + case "below": + // first try to align left borders, next try to align right borders (or reverse for RTL mode) + align[ltr ? "BL" : "BR"] = ltr ? "TL" : "TR"; + align[ltr ? "BR" : "BL"] = ltr ? "TR" : "TL"; + break; + case "above-alt": + ltr = !ltr; + // fall through + case "above": + default: + // first try to align left borders, next try to align right borders (or reverse for RTL mode) + align[ltr ? "TL" : "TR"] = ltr ? "BL" : "BR"; + align[ltr ? "TR" : "TL"] = ltr ? "BR" : "BL"; + break; + } + }); + return align; + }; - if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; } - // find currently focused button in children array - var children = this.getChildren(); - var current = dojo.indexOf(children, this.pane2button[this._currentChild.id]); - // pick next button to focus on - var offset = forward ? 1 : children.length - 1; - return children[ (current + offset) % children.length ]; // dijit._Widget - }, + return dijit; +}); - onkeypress: function(/*Event*/ e){ - // summary: - // Handle keystrokes on the page list, for advancing to next/previous button - // and closing the current page if the page is closable. - // tags: - // private - - if(this.disabled || e.altKey ){ return; } - var forward = null; - if(e.ctrlKey || !e._djpage){ - var k = dojo.keys; - switch(e.charOrCode){ - case k.LEFT_ARROW: - case k.UP_ARROW: - if(!e._djpage){ forward = false; } - break; - case k.PAGE_UP: - if(e.ctrlKey){ forward = false; } - break; - case k.RIGHT_ARROW: - case k.DOWN_ARROW: - if(!e._djpage){ forward = true; } - break; - case k.PAGE_DOWN: - if(e.ctrlKey){ forward = true; } - break; - case k.HOME: - case k.END: - var children = this.getChildren(); - if(children && children.length){ - children[e.charOrCode == k.HOME ? 0 : children.length-1].onClick(); - } - dojo.stopEvent(e); - break; - case k.DELETE: - if(this._currentChild.closable){ - this.onCloseButtonClick(this._currentChild); - } - dojo.stopEvent(e); - break; - default: - if(e.ctrlKey){ - if(e.charOrCode === k.TAB){ - this.adjacent(!e.shiftKey).onClick(); - dojo.stopEvent(e); - }else if(e.charOrCode == "w"){ - if(this._currentChild.closable){ - this.onCloseButtonClick(this._currentChild); - } - dojo.stopEvent(e); // avoid browser tab closing. - } - } - } - // handle next/previous page navigation (left/right arrow, etc.) - if(forward !== null){ - this.adjacent(forward).onClick(); - dojo.stopEvent(e); - } - } - }, +}, +'dijit/MenuSeparator':function(){ +require({cache:{ +'url: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>"}}); +define("dijit/MenuSeparator", [ + "dojo/_base/declare", // declare + "dojo/dom", // dom.setSelectable + "./_WidgetBase", + "./_TemplatedMixin", + "./_Contained", + "dojo/text!./templates/MenuSeparator.html" +], function(declare, dom, _WidgetBase, _TemplatedMixin, _Contained, template){ - onContainerKeyPress: function(/*Object*/ info){ - // summary: - // Called when there was a keypress on the container - // tags: - // private - info.e._djpage = info.page; - this.onkeypress(info.e); - } - }); +/*===== + var _WidgetBase = dijit._WidgetBase; + var _TemplatedMixin = dijit._TemplatedMixin; + var _Contained = dijit._Contained; +=====*/ + // module: + // dijit/MenuSeparator + // summary: + // A line between two menu items -dojo.declare("dijit.layout._StackButton", - dijit.form.ToggleButton, - { + return declare("dijit.MenuSeparator", [_WidgetBase, _TemplatedMixin, _Contained], { // summary: - // Internal widget used by StackContainer. - // description: - // The button-like or tab-like object you click to select or delete a page - // tags: - // private + // A line between two menu items - // Override _FormWidget.tabIndex. - // StackContainer buttons are not in the tab order by default. - // Probably we should be calling this.startupKeyNavChildren() instead. - tabIndex: "-1", + templateString: template, - buildRendering: function(/*Event*/ evt){ + buildRendering: function(){ this.inherited(arguments); - dijit.setWaiRole((this.focusNode || this.domNode), "tab"); + dom.setSelectable(this.domNode, false); }, - onClick: function(/*Event*/ evt){ + isFocusable: function(){ // summary: - // This is for TabContainer where the tabs are <span> rather than button, - // so need to set focus explicitly (on some browsers) - // Note that you shouldn't override this method, but you can connect to it. - dijit.focus(this.focusNode); - - // ... now let StackController catch the event and tell me what to do - }, + // Override to always return false + // tags: + // protected - onClickCloseButton: function(/*Event*/ evt){ - // summary: - // StackContainer connects to this function; if your widget contains a close button - // then clicking it should call this function. - // Note that you shouldn't override this method, but you can connect to it. - evt.stopPropagation(); + return false; // Boolean } }); +}); -} - -if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout.StackContainer"] = true; -dojo.provide("dijit.layout.StackContainer"); - - - - - - - -dojo.declare( - "dijit.layout.StackContainer", - dijit.layout._LayoutWidget, - { - // summary: - // A container that has multiple children, but shows only - // one child at a time - // - // description: - // A container for widgets (ContentPanes, for example) That displays - // only one Widget at a time. - // - // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild - // - // Can be base class for container, Wizard, Show, etc. - - // doLayout: Boolean - // If true, change the size of my currently displayed child to match my size - doLayout: true, - - // persist: Boolean - // Remembers the selected child across sessions - persist: false, - - baseClass: "dijitStackContainer", +}, +'dijit/form/_ComboBoxMenu':function(){ +define("dijit/form/_ComboBoxMenu", [ + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add domClass.remove + "dojo/dom-construct", // domConstruct.create + "dojo/dom-style", // domStyle.get + "dojo/keys", // keys.DOWN_ARROW keys.PAGE_DOWN keys.PAGE_UP keys.UP_ARROW + "../_WidgetBase", + "../_TemplatedMixin", + "./_ComboBoxMenuMixin", + "./_ListMouseMixin" +], function(declare, domClass, domConstruct, domStyle, keys, + _WidgetBase, _TemplatedMixin, _ComboBoxMenuMixin, _ListMouseMixin){ /*===== - // selectedChildWidget: [readonly] dijit._Widget - // References the currently selected child widget, if any. - // Adjust selected child with selectChild() method. - selectedChildWidget: null, + var _WidgetBase = dijit._WidgetBase; + var _TemplatedMixin = dijit._TemplatedMixin; + var _ComboBoxMenuMixin = dijit.form._ComboBoxMenuMixin; + var _ListMouseMixin = dijit.form._ListMouseMixin; =====*/ - buildRendering: function(){ - this.inherited(arguments); - dojo.addClass(this.domNode, "dijitLayoutContainer"); - dijit.setWaiRole(this.containerNode, "tabpanel"); - }, - - postCreate: function(){ - this.inherited(arguments); - this.connect(this.domNode, "onkeypress", this._onKeyPress); - }, - - startup: function(){ - if(this._started){ return; } - - var children = this.getChildren(); - - // Setup each page panel to be initially hidden - dojo.forEach(children, this._setupChild, this); - - // Figure out which child to initially display, defaulting to first one - if(this.persist){ - this.selectedChildWidget = dijit.byId(dojo.cookie(this.id + "_selectedChild")); - }else{ - dojo.some(children, function(child){ - if(child.selected){ - this.selectedChildWidget = child; - } - return child.selected; - }, this); - } - var selected = this.selectedChildWidget; - if(!selected && children[0]){ - selected = this.selectedChildWidget = children[0]; - selected.selected = true; - } - - // Publish information about myself so any StackControllers can initialize. - // This needs to happen before this.inherited(arguments) so that for - // TabContainer, this._contentBox doesn't include the space for the tab labels. - dojo.publish(this.id+"-startup", [{children: children, selected: selected}]); + // module: + // dijit/form/_ComboBoxMenu + // summary: + // Focus-less menu for internal use in `dijit.form.ComboBox` - // Startup each child widget, and do initial layout like setting this._contentBox, - // then calls this.resize() which does the initial sizing on the selected child. - this.inherited(arguments); - }, + return declare("dijit.form._ComboBoxMenu",[_WidgetBase, _TemplatedMixin, _ListMouseMixin, _ComboBoxMenuMixin], { + // summary: + // Focus-less menu for internal use in `dijit.form.ComboBox` + // Abstract methods that must be defined externally: + // onChange: item was explicitly chosen (mousedown somewhere on the menu and mouseup somewhere on the menu) + // onPage: next(1) or previous(-1) button pressed + // tags: + // private - resize: function(){ - // Resize is called when we are first made visible (it's called from startup() - // if we are initially visible). If this is the first time we've been made - // visible then show our first child. - var selected = this.selectedChildWidget; - if(selected && !this._hasBeenShown){ - this._hasBeenShown = true; - this._showChild(selected); - } - this.inherited(arguments); - }, + templateString: "<div class='dijitReset dijitMenu' data-dojo-attach-point='containerNode' style='overflow: auto; overflow-x: hidden;'>" + +"<div class='dijitMenuItem dijitMenuPreviousButton' data-dojo-attach-point='previousButton' role='option'></div>" + +"<div class='dijitMenuItem dijitMenuNextButton' data-dojo-attach-point='nextButton' role='option'></div>" + +"</div>", - _setupChild: function(/*dijit._Widget*/ child){ - // Overrides _LayoutWidget._setupChild() + baseClass: "dijitComboBoxMenu", - this.inherited(arguments); + postCreate: function(){ + this.inherited(arguments); + if(!this.isLeftToRight()){ + domClass.add(this.previousButton, "dijitMenuItemRtl"); + domClass.add(this.nextButton, "dijitMenuItemRtl"); + } + }, - dojo.replaceClass(child.domNode, "dijitHidden", "dijitVisible"); + _createMenuItem: function(){ + return domConstruct.create("div", { + "class": "dijitReset dijitMenuItem" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl"), + role: "option" + }); + }, - // remove the title attribute so it doesn't show up when i hover - // over a node - child.domNode.title = ""; - }, + onHover: function(/*DomNode*/ node){ + // summary: + // Add hover CSS + domClass.add(node, "dijitMenuItemHover"); + }, - addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ - // Overrides _Container.addChild() to do layout and publish events + onUnhover: function(/*DomNode*/ node){ + // summary: + // Remove hover CSS + domClass.remove(node, "dijitMenuItemHover"); + }, - this.inherited(arguments); + onSelect: function(/*DomNode*/ node){ + // summary: + // Add selected CSS + domClass.add(node, "dijitMenuItemSelected"); + }, - if(this._started){ - dojo.publish(this.id+"-addChild", [child, insertIndex]); + onDeselect: function(/*DomNode*/ node){ + // summary: + // Remove selected CSS + domClass.remove(node, "dijitMenuItemSelected"); + }, - // in case the tab titles have overflowed from one line to two lines - // (or, if this if first child, from zero lines to one line) - // TODO: w/ScrollingTabController this is no longer necessary, although - // ScrollTabController.resize() does need to get called to show/hide - // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild() - this.layout(); + _page: function(/*Boolean*/ up){ + // summary: + // Handles page-up and page-down keypresses - // if this is the first child, then select it - if(!this.selectedChildWidget){ - this.selectChild(child); + var scrollamount = 0; + var oldscroll = this.domNode.scrollTop; + var height = domStyle.get(this.domNode, "height"); + // if no item is highlighted, highlight the first option + if(!this.getHighlightedOption()){ + this.selectNextNode(); } - } - }, - - removeChild: function(/*dijit._Widget*/ page){ - // Overrides _Container.removeChild() to do layout and publish events - - this.inherited(arguments); - - if(this._started){ - // this will notify any tablists to remove a button; do this first because it may affect sizing - dojo.publish(this.id + "-removeChild", [page]); - } - - // If we are being destroyed than don't run the code below (to select another page), because we are deleting - // every page one by one - if(this._beingDestroyed){ return; } - - // Select new page to display, also updating TabController to show the respective tab. - // Do this before layout call because it can affect the height of the TabController. - if(this.selectedChildWidget === page){ - this.selectedChildWidget = undefined; - if(this._started){ - var children = this.getChildren(); - if(children.length){ - this.selectChild(children[0]); + while(scrollamount<height){ + var highlighted_option = this.getHighlightedOption(); + if(up){ + // stop at option 1 + if(!highlighted_option.previousSibling || + highlighted_option.previousSibling.style.display == "none"){ + break; + } + this.selectPreviousNode(); + }else{ + // stop at last option + if(!highlighted_option.nextSibling || + highlighted_option.nextSibling.style.display == "none"){ + break; + } + this.selectNextNode(); } + // going backwards + var newscroll = this.domNode.scrollTop; + scrollamount += (newscroll-oldscroll)*(up ? -1:1); + oldscroll = newscroll; } - } - - if(this._started){ - // In case the tab titles now take up one line instead of two lines - // (note though that ScrollingTabController never overflows to multiple lines), - // or the height has changed slightly because of addition/removal of tab which close icon - this.layout(); - } - }, - - selectChild: function(/*dijit._Widget|String*/ page, /*Boolean*/ animate){ - // summary: - // Show the given widget (which must be one of my children) - // page: - // Reference to child widget or id of child widget - - page = dijit.byId(page); - - if(this.selectedChildWidget != page){ - // Deselect old page and select new one - var d = this._transition(page, this.selectedChildWidget, animate); - this._set("selectedChildWidget", page); - dojo.publish(this.id+"-selectChild", [page]); - - if(this.persist){ - dojo.cookie(this.id + "_selectedChild", this.selectedChildWidget.id); - } - } - - return d; // If child has an href, promise that fires when the child's href finishes loading - }, - - _transition: function(/*dijit._Widget*/ newWidget, /*dijit._Widget*/ oldWidget, /*Boolean*/ animate){ - // summary: - // Hide the old widget and display the new widget. - // Subclasses should override this. - // tags: - // protected extension - if(oldWidget){ - this._hideChild(oldWidget); - } - var d = this._showChild(newWidget); + }, - // Size the new widget, in case this is the first time it's being shown, - // or I have been resized since the last time it was shown. - // Note that page must be visible for resizing to work. - if(newWidget.resize){ - if(this.doLayout){ - newWidget.resize(this._containerContentBox || this._contentBox); - }else{ - // the child should pick it's own size but we still need to call resize() - // (with no arguments) to let the widget lay itself out - newWidget.resize(); + handleKey: function(evt){ + // summary: + // Handle keystroke event forwarded from ComboBox, returning false if it's + // a keystroke I recognize and process, true otherwise. + switch(evt.charOrCode){ + case keys.DOWN_ARROW: + this.selectNextNode(); + return false; + case keys.PAGE_DOWN: + this._page(false); + return false; + case keys.UP_ARROW: + this.selectPreviousNode(); + return false; + case keys.PAGE_UP: + this._page(true); + return false; + default: + return true; } } + }); +}); - return d; // If child has an href, promise that fires when the child's href finishes loading - }, - - _adjacent: function(/*Boolean*/ forward){ - // summary: - // Gets the next/previous child widget in this container from the current selection. - var children = this.getChildren(); - var index = dojo.indexOf(children, this.selectedChildWidget); - index += forward ? 1 : children.length - 1; - return children[ index % children.length ]; // dijit._Widget - }, - - forward: function(){ - // summary: - // Advance to next page. - return this.selectChild(this._adjacent(true), true); - }, - - back: function(){ - // summary: - // Go back to previous page. - return this.selectChild(this._adjacent(false), true); - }, - - _onKeyPress: function(e){ - dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]); - }, - - layout: function(){ - // Implement _LayoutWidget.layout() virtual method. - if(this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize){ - this.selectedChildWidget.resize(this._containerContentBox || this._contentBox); - } - }, +}, +'url:dijit/layout/templates/ScrollingTabController.html':"<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div data-dojo-type=\"dijit.layout._ScrollingTabControllerMenuButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\"\n\t\t\tdata-dojo-props=\"containerId: '${containerId}', iconClass: 'dijitTabStripMenuIcon',\n\t\t\t\t\tdropDownPosition: ['below-alt', 'above-alt']\"\n\t\t\tdata-dojo-attach-point=\"_menuBtn\" showLabel=\"false\" title=\"\">▼</div>\n\t<div data-dojo-type=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\"\n\t\t\tdata-dojo-props=\"iconClass:'dijitTabStripSlideLeftIcon', showLabel:false, title:''\"\n\t\t\tdata-dojo-attach-point=\"_leftBtn\" data-dojo-attach-event=\"onClick: doSlideLeft\">◀</div>\n\t<div data-dojo-type=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\"\n\t\t\tdata-dojo-props=\"iconClass:'dijitTabStripSlideRightIcon', showLabel:false, title:''\"\n\t\t\tdata-dojo-attach-point=\"_rightBtn\" data-dojo-attach-event=\"onClick: doSlideRight\">▶</div>\n\t<div class='dijitTabListWrapper' data-dojo-attach-point='tablistWrapper'>\n\t\t<div role='tablist' data-dojo-attach-event='onkeypress:onkeypress'\n\t\t\t\tdata-dojo-attach-point='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>", +'dijit/Dialog':function(){ +require({cache:{ +'url:dijit/templates/Dialog.html':"<div class=\"dijitDialog\" role=\"dialog\" aria-labelledby=\"${id}_title\">\n\t<div data-dojo-attach-point=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t<span data-dojo-attach-point=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"></span>\n\t<span data-dojo-attach-point=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" data-dojo-attach-event=\"ondijitclick: onCancel\" title=\"${buttonCancel}\" role=\"button\" tabIndex=\"-1\">\n\t\t<span data-dojo-attach-point=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t</span>\n\t</div>\n\t\t<div data-dojo-attach-point=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n"}}); +define("dijit/Dialog", [ + "require", + "dojo/_base/array", // array.forEach array.indexOf array.map + "dojo/_base/connect", // connect._keypress + "dojo/_base/declare", // declare + "dojo/_base/Deferred", // Deferred + "dojo/dom", // dom.isDescendant + "dojo/dom-class", // domClass.add domClass.contains + "dojo/dom-geometry", // domGeometry.position + "dojo/dom-style", // domStyle.set + "dojo/_base/event", // event.stop + "dojo/_base/fx", // fx.fadeIn fx.fadeOut + "dojo/i18n", // i18n.getLocalization + "dojo/_base/kernel", // kernel.isAsync + "dojo/keys", + "dojo/_base/lang", // lang.mixin lang.hitch + "dojo/on", + "dojo/ready", + "dojo/_base/sniff", // has("ie") has("opera") + "dojo/_base/window", // win.body + "dojo/window", // winUtils.getBox + "dojo/dnd/Moveable", // Moveable + "dojo/dnd/TimedMoveable", // TimedMoveable + "./focus", + "./_base/manager", // manager.defaultDuration + "./_Widget", + "./_TemplatedMixin", + "./_CssStateMixin", + "./form/_FormMixin", + "./_DialogMixin", + "./DialogUnderlay", + "./layout/ContentPane", + "dojo/text!./templates/Dialog.html", + ".", // for back-compat, exporting dijit._underlay (remove in 2.0) + "dojo/i18n!./nls/common" +], function(require, array, connect, declare, Deferred, + dom, domClass, domGeometry, domStyle, event, fx, i18n, kernel, keys, lang, on, ready, has, win, winUtils, + Moveable, TimedMoveable, focus, manager, _Widget, _TemplatedMixin, _CssStateMixin, _FormMixin, _DialogMixin, + DialogUnderlay, ContentPane, template, dijit){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _CssStateMixin = dijit._CssStateMixin; + var _FormMixin = dijit.form._FormMixin; + var _DialogMixin = dijit._DialogMixin; +=====*/ - _showChild: function(/*dijit._Widget*/ page){ - // summary: - // Show the specified child by changing it's CSS, and call _onShow()/onShow() so - // it can do any updates it needs regarding loading href's etc. - // returns: - // Promise that fires when page has finished showing, or true if there's no href - var children = this.getChildren(); - page.isFirstChild = (page == children[0]); - page.isLastChild = (page == children[children.length-1]); - page._set("selected", true); - dojo.replaceClass(page.domNode, "dijitVisible", "dijitHidden"); + // module: + // dijit/Dialog + // summary: + // A modal dialog Widget - return page._onShow() || true; - }, - _hideChild: function(/*dijit._Widget*/ page){ + /*===== + dijit._underlay = function(kwArgs){ // summary: - // Hide the specified child by changing it's CSS, and call _onHide() so - // it's notified. - page._set("selected", false); - dojo.replaceClass(page.domNode, "dijitHidden", "dijitVisible"); - - page.onHide(); - }, + // A shared instance of a `dijit.DialogUnderlay` + // + // description: + // A shared instance of a `dijit.DialogUnderlay` created and + // used by `dijit.Dialog`, though never created until some Dialog + // or subclass thereof is shown. + }; + =====*/ - closeChild: function(/*dijit._Widget*/ page){ + var _DialogBase = declare("dijit._DialogBase", [_TemplatedMixin, _FormMixin, _DialogMixin, _CssStateMixin], { // summary: - // Callback when user clicks the [X] to remove a page. - // If onClose() returns true then remove and destroy the child. - // tags: - // private - var remove = page.onClose(this, page); - if(remove){ - this.removeChild(page); - // makes sure we can clean up executeScripts in ContentPane onUnLoad - page.destroyRecursive(); - } - }, - - destroyDescendants: function(/*Boolean*/ preserveDom){ - dojo.forEach(this.getChildren(), function(child){ - this.removeChild(child); - child.destroyRecursive(preserveDom); - }, this); - } -}); - -// For back-compat, remove for 2.0 - - -// These arguments can be specified for the children of a StackContainer. -// Since any widget can be specified as a StackContainer child, mix them -// into the base widget class. (This is a hack, but it's effective.) -dojo.extend(dijit._Widget, { - // selected: Boolean - // Parameter for children of `dijit.layout.StackContainer` or subclasses. - // Specifies that this widget should be the initially displayed pane. - // Note: to change the selected child use `dijit.layout.StackContainer.selectChild` - selected: false, - - // closable: Boolean - // Parameter for children of `dijit.layout.StackContainer` or subclasses. - // True if user can close (destroy) this child, such as (for example) clicking the X on the tab. - closable: false, - - // iconClass: String - // Parameter for children of `dijit.layout.StackContainer` or subclasses. - // CSS Class specifying icon to use in label associated with this pane. - iconClass: "", - - // showTitle: Boolean - // Parameter for children of `dijit.layout.StackContainer` or subclasses. - // When true, display title of this widget as tab label etc., rather than just using - // icon specified in iconClass - showTitle: true -}); - -} - -if(!dojo._hasResource["dijit.layout.AccordionPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout.AccordionPane"] = true; -dojo.provide("dijit.layout.AccordionPane"); - - + // A modal dialog Widget + // + // description: + // Pops up a modal dialog window, blocking access to the screen + // and also graying out the screen Dialog is extended from + // ContentPane so it supports all the same parameters (href, etc.) + // + // example: + // | <div data-dojo-type="dijit.Dialog" data-dojo-props="href: 'test.html'"></div> + // + // example: + // | var foo = new dijit.Dialog({ title: "test dialog", content: "test content" }; + // | dojo.body().appendChild(foo.domNode); + // | foo.startup(); -dojo.declare("dijit.layout.AccordionPane", dijit.layout.ContentPane, { - // summary: - // Deprecated widget. Use `dijit.layout.ContentPane` instead. - // tags: - // deprecated + templateString: template, - constructor: function(){ - dojo.deprecated("dijit.layout.AccordionPane deprecated, use ContentPane instead", "", "2.0"); - }, + baseClass: "dijitDialog", - onSelected: function(){ - // summary: - // called when this pane is selected - } -}); + cssStateNodes: { + closeButtonNode: "dijitDialogCloseIcon" + }, -} + // Map widget attributes to DOMNode attributes. + _setTitleAttr: [ + { node: "titleNode", type: "innerHTML" }, + { node: "titleBar", type: "attribute" } + ], -if(!dojo._hasResource["dijit.layout.AccordionContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout.AccordionContainer"] = true; -dojo.provide("dijit.layout.AccordionContainer"); + // open: [readonly] Boolean + // True if Dialog is currently displayed on screen. + open: false, + // duration: Integer + // The time in milliseconds it takes the dialog to fade in and out + duration: manager.defaultDuration, + // refocus: Boolean + // A Toggle to modify the default focus behavior of a Dialog, which + // is to re-focus the element which had focus before being opened. + // False will disable refocusing. Default: true + refocus: true, + // autofocus: Boolean + // A Toggle to modify the default focus behavior of a Dialog, which + // is to focus on the first dialog element after opening the dialog. + // False will disable autofocusing. Default: true + autofocus: true, + // _firstFocusItem: [private readonly] DomNode + // The pointer to the first focusable node in the dialog. + // Set by `dijit._DialogMixin._getFocusItems`. + _firstFocusItem: null, + // _lastFocusItem: [private readonly] DomNode + // The pointer to which node has focus prior to our dialog. + // Set by `dijit._DialogMixin._getFocusItems`. + _lastFocusItem: null, + // doLayout: [protected] Boolean + // Don't change this parameter from the default value. + // This ContentPane parameter doesn't make sense for Dialog, since Dialog + // is never a child of a layout container, nor can you specify the size of + // Dialog in order to control the size of an inner widget. + doLayout: false, + // draggable: Boolean + // Toggles the moveable aspect of the Dialog. If true, Dialog + // can be dragged by it's title. If false it will remain centered + // in the viewport. + draggable: true, -//dojo.require("dijit.layout.AccordionPane "); // for back compat, remove for 2.0 + //aria-describedby: String + // Allows the user to add an aria-describedby attribute onto the dialog. The value should + // be the id of the container element of text that describes the dialog purpose (usually + // the first text in the dialog). + // <div data-dojo-type="dijit.Dialog" aria-describedby="intro" .....> + // <div id="intro">Introductory text</div> + // <div>rest of dialog contents</div> + // </div> + "aria-describedby":"", -// Design notes: -// -// An AccordionContainer is a StackContainer, but each child (typically ContentPane) -// is wrapped in a _AccordionInnerContainer. This is hidden from the caller. -// -// The resulting markup will look like: -// -// <div class=dijitAccordionContainer> -// <div class=dijitAccordionInnerContainer> (one pane) -// <div class=dijitAccordionTitle> (title bar) ... </div> -// <div class=dijtAccordionChildWrapper> (content pane) </div> -// </div> -// </div> -// -// Normally the dijtAccordionChildWrapper is hidden for all but one child (the shown -// child), so the space for the content pane is all the title bars + the one dijtAccordionChildWrapper, -// which on claro has a 1px border plus a 2px bottom margin. -// -// During animation there are two dijtAccordionChildWrapper's shown, so we need -// to compensate for that. + postMixInProperties: function(){ + var _nlsResources = i18n.getLocalization("dijit", "common"); + lang.mixin(this, _nlsResources); + this.inherited(arguments); + }, -dojo.declare( - "dijit.layout.AccordionContainer", - dijit.layout.StackContainer, - { - // summary: - // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time, - // and switching between panes is visualized by sliding the other panes up/down. - // example: - // | <div dojoType="dijit.layout.AccordionContainer"> - // | <div dojoType="dijit.layout.ContentPane" title="pane 1"> - // | </div> - // | <div dojoType="dijit.layout.ContentPane" title="pane 2"> - // | <p>This is some text</p> - // | </div> - // | </div> + postCreate: function(){ + domStyle.set(this.domNode, { + display: "none", + position:"absolute" + }); + win.body().appendChild(this.domNode); - // duration: Integer - // Amount of time (in ms) it takes to slide panes - duration: dijit.defaultDuration, + this.inherited(arguments); - // buttonWidget: [const] String - // The name of the widget used to display the title of each pane - buttonWidget: "dijit.layout._AccordionButton", + this.connect(this, "onExecute", "hide"); + this.connect(this, "onCancel", "hide"); + this._modalconnects = []; + }, -/*===== - // _verticalSpace: Number - // Pixels of space available for the open pane - // (my content box size minus the cumulative size of all the title bars) - _verticalSpace: 0, -=====*/ - baseClass: "dijitAccordionContainer", + onLoad: function(){ + // summary: + // Called when data has been loaded from an href. + // Unlike most other callbacks, this function can be connected to (via `dojo.connect`) + // but should *not* be overridden. + // tags: + // callback - buildRendering: function(){ + // when href is specified we need to reposition the dialog after the data is loaded + // and find the focusable elements + this._position(); + if(this.autofocus && DialogLevelManager.isTop(this)){ + this._getFocusItems(this.domNode); + focus.focus(this._firstFocusItem); + } this.inherited(arguments); - this.domNode.style.overflow = "hidden"; // TODO: put this in dijit.css - dijit.setWaiRole(this.domNode, "tablist"); // TODO: put this in template }, - startup: function(){ - if(this._started){ return; } - this.inherited(arguments); - if(this.selectedChildWidget){ - var style = this.selectedChildWidget.containerNode.style; - style.display = ""; - style.overflow = "auto"; - this.selectedChildWidget._wrapperWidget.set("selected", true); - } + _endDrag: function(){ + // summary: + // Called after dragging the Dialog. Saves the position of the dialog in the viewport, + // and also adjust position to be fully within the viewport, so user doesn't lose access to handle + var nodePosition = domGeometry.position(this.domNode), + viewport = winUtils.getBox(); + nodePosition.y = Math.min(Math.max(nodePosition.y, 0), (viewport.h - nodePosition.h)); + nodePosition.x = Math.min(Math.max(nodePosition.x, 0), (viewport.w - nodePosition.w)); + this._relativePosition = nodePosition; + this._position(); }, - layout: function(){ - // Implement _LayoutWidget.layout() virtual method. - // Set the height of the open pane based on what room remains. - - var openPane = this.selectedChildWidget; - - if(!openPane){ return;} + _setup: function(){ + // summary: + // Stuff we need to do before showing the Dialog for the first + // time (but we defer it until right beforehand, for + // performance reasons). + // tags: + // private - // space taken up by title, plus wrapper div (with border/margin) for open pane - var wrapperDomNode = openPane._wrapperWidget.domNode, - wrapperDomNodeMargin = dojo._getMarginExtents(wrapperDomNode), - wrapperDomNodePadBorder = dojo._getPadBorderExtents(wrapperDomNode), - wrapperContainerNode = openPane._wrapperWidget.containerNode, - wrapperContainerNodeMargin = dojo._getMarginExtents(wrapperContainerNode), - wrapperContainerNodePadBorder = dojo._getPadBorderExtents(wrapperContainerNode), - mySize = this._contentBox; + var node = this.domNode; - // get cumulative height of all the unselected title bars - var totalCollapsedHeight = 0; - dojo.forEach(this.getChildren(), function(child){ - if(child != openPane){ - totalCollapsedHeight += dojo._getMarginSize(child._wrapperWidget.domNode).h; - } - }); - this._verticalSpace = mySize.h - totalCollapsedHeight - wrapperDomNodeMargin.h - - wrapperDomNodePadBorder.h - wrapperContainerNodeMargin.h - wrapperContainerNodePadBorder.h - - openPane._buttonWidget.getTitleHeight(); + if(this.titleBar && this.draggable){ + this._moveable = new ((has("ie") == 6) ? TimedMoveable // prevent overload, see #5285 + : Moveable)(node, { handle: this.titleBar }); + this.connect(this._moveable, "onMoveStop", "_endDrag"); + }else{ + domClass.add(node,"dijitDialogFixed"); + } - // Memo size to make displayed child - this._containerContentBox = { - h: this._verticalSpace, - w: this._contentBox.w - wrapperDomNodeMargin.w - wrapperDomNodePadBorder.w - - wrapperContainerNodeMargin.w - wrapperContainerNodePadBorder.w + this.underlayAttrs = { + dialogId: this.id, + "class": array.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" ") }; - - if(openPane){ - openPane.resize(this._containerContentBox); - } }, - _setupChild: function(child){ - // Overrides _LayoutWidget._setupChild(). - // Put wrapper widget around the child widget, showing title - - child._wrapperWidget = new dijit.layout._AccordionInnerContainer({ - contentWidget: child, - buttonWidget: this.buttonWidget, - id: child.id + "_wrapper", - dir: child.dir, - lang: child.lang, - parent: this - }); + _size: function(){ + // summary: + // If necessary, shrink dialog contents so dialog fits in viewport + // tags: + // private - this.inherited(arguments); - }, + this._checkIfSingleChild(); - addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ - if(this._started){ - // Adding a child to a started Accordion is complicated because children have - // wrapper widgets. Default code path (calling this.inherited()) would add - // the new child inside another child's wrapper. + // If we resized the dialog contents earlier, reset them back to original size, so + // that if the user later increases the viewport size, the dialog can display w/out a scrollbar. + // Need to do this before the domGeometry.position(this.domNode) call below. + if(this._singleChild){ + if(this._singleChildOriginalStyle){ + this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle; + } + delete this._singleChildOriginalStyle; + }else{ + domStyle.set(this.containerNode, { + width:"auto", + height:"auto" + }); + } - // First add in child as a direct child of this AccordionContainer - dojo.place(child.domNode, this.containerNode, insertIndex); + var bb = domGeometry.position(this.domNode); + var viewport = winUtils.getBox(); + if(bb.w >= viewport.w || bb.h >= viewport.h){ + // Reduce size of dialog contents so that dialog fits in viewport - if(!child._started){ - child.startup(); - } - - // Then stick the wrapper widget around the child widget - this._setupChild(child); + var w = Math.min(bb.w, Math.floor(viewport.w * 0.75)), + h = Math.min(bb.h, Math.floor(viewport.h * 0.75)); - // Code below copied from StackContainer - dojo.publish(this.id+"-addChild", [child, insertIndex]); - this.layout(); - if(!this.selectedChildWidget){ - this.selectChild(child); + if(this._singleChild && this._singleChild.resize){ + this._singleChildOriginalStyle = this._singleChild.domNode.style.cssText; + this._singleChild.resize({w: w, h: h}); + }else{ + domStyle.set(this.containerNode, { + width: w + "px", + height: h + "px", + overflow: "auto", + position: "relative" // workaround IE bug moving scrollbar or dragging dialog + }); } }else{ - // We haven't been started yet so just add in the child widget directly, - // and the wrapper will be created on startup() - this.inherited(arguments); + if(this._singleChild && this._singleChild.resize){ + this._singleChild.resize(); + } } }, - removeChild: function(child){ - // Overrides _LayoutWidget.removeChild(). - - // Destroy wrapper widget first, before StackContainer.getChildren() call. - // Replace wrapper widget with true child widget (ContentPane etc.). - // This step only happens if the AccordionContainer has been started; otherwise there's no wrapper. - if(child._wrapperWidget){ - dojo.place(child.domNode, child._wrapperWidget.domNode, "after"); - child._wrapperWidget.destroy(); - delete child._wrapperWidget; + _position: function(){ + // summary: + // Position modal dialog in the viewport. If no relative offset + // in the viewport has been determined (by dragging, for instance), + // center the node. Otherwise, use the Dialog's stored relative offset, + // and position the node to top: left: values based on the viewport. + if(!domClass.contains(win.body(), "dojoMove")){ // don't do anything if called during auto-scroll + var node = this.domNode, + viewport = winUtils.getBox(), + p = this._relativePosition, + bb = p ? null : domGeometry.position(node), + l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)), + t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2)) + ; + domStyle.set(node,{ + left: l + "px", + top: t + "px" + }); } - - dojo.removeClass(child.domNode, "dijitHidden"); - - this.inherited(arguments); }, - getChildren: function(){ - // Overrides _Container.getChildren() to return content panes rather than internal AccordionInnerContainer panes - return dojo.map(this.inherited(arguments), function(child){ - return child.declaredClass == "dijit.layout._AccordionInnerContainer" ? child.contentWidget : child; - }, this); - }, + _onKey: function(/*Event*/ evt){ + // summary: + // Handles the keyboard events for accessibility reasons + // tags: + // private - destroy: function(){ - if(this._animation){ - this._animation.stop(); - } - dojo.forEach(this.getChildren(), function(child){ - // If AccordionContainer has been started, then each child has a wrapper widget which - // also needs to be destroyed. - if(child._wrapperWidget){ - child._wrapperWidget.destroy(); + if(evt.charOrCode){ + var node = evt.target; + if(evt.charOrCode === keys.TAB){ + this._getFocusItems(this.domNode); + } + var singleFocusItem = (this._firstFocusItem == this._lastFocusItem); + // see if we are shift-tabbing from first focusable item on dialog + if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === keys.TAB){ + if(!singleFocusItem){ + focus.focus(this._lastFocusItem); // send focus to last item in dialog + } + event.stop(evt); + }else if(node == this._lastFocusItem && evt.charOrCode === keys.TAB && !evt.shiftKey){ + if(!singleFocusItem){ + focus.focus(this._firstFocusItem); // send focus to first item in dialog + } + event.stop(evt); }else{ - child.destroyRecursive(); + // see if the key is for the dialog + while(node){ + if(node == this.domNode || domClass.contains(node, "dijitPopup")){ + if(evt.charOrCode == keys.ESCAPE){ + this.onCancel(); + }else{ + return; // just let it go + } + } + node = node.parentNode; + } + // this key is for the disabled document window + if(evt.charOrCode !== keys.TAB){ // allow tabbing into the dialog for a11y + event.stop(evt); + // opera won't tab to a div + }else if(!has("opera")){ + try{ + this._firstFocusItem.focus(); + }catch(e){ /*squelch*/ } + } } - }); - this.inherited(arguments); - }, - - _showChild: function(child){ - // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode - child._wrapperWidget.containerNode.style.display="block"; - return this.inherited(arguments); + } }, - _hideChild: function(child){ - // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode - child._wrapperWidget.containerNode.style.display="none"; - this.inherited(arguments); - }, + show: function(){ + // summary: + // Display the dialog + // returns: dojo.Deferred + // Deferred object that resolves when the display animation is complete - _transition: function(/*dijit._Widget?*/ newWidget, /*dijit._Widget?*/ oldWidget, /*Boolean*/ animate){ - // Overrides StackContainer._transition() to provide sliding of title bars etc. + if(this.open){ return; } - if(dojo.isIE < 8){ - // workaround animation bugs by not animating; not worth supporting animation for IE6 & 7 - animate = false; + if(!this._started){ + this.startup(); } - if(this._animation){ - // there's an in-progress animation. speedily end it so we can do the newly requested one - this._animation.stop(true); - delete this._animation; + // first time we show the dialog, there's some initialization stuff to do + if(!this._alreadyInitialized){ + this._setup(); + this._alreadyInitialized=true; } - var self = this; - - if(newWidget){ - newWidget._wrapperWidget.set("selected", true); - - var d = this._showChild(newWidget); // prepare widget to be slid in - - // Size the new widget, in case this is the first time it's being shown, - // or I have been resized since the last time it was shown. - // Note that page must be visible for resizing to work. - if(this.doLayout && newWidget.resize){ - newWidget.resize(this._containerContentBox); - } + if(this._fadeOutDeferred){ + this._fadeOutDeferred.cancel(); } - if(oldWidget){ - oldWidget._wrapperWidget.set("selected", false); - if(!animate){ - this._hideChild(oldWidget); + this._modalconnects.push(on(window, "scroll", lang.hitch(this, "layout"))); + this._modalconnects.push(on(window, "resize", lang.hitch(this, function(){ + // IE gives spurious resize events and can actually get stuck + // in an infinite loop if we don't ignore them + var viewport = winUtils.getBox(); + if(!this._oldViewport || + viewport.h != this._oldViewport.h || + viewport.w != this._oldViewport.w){ + this.layout(); + this._oldViewport = viewport; } - } + }))); + this._modalconnects.push(on(this.domNode, connect._keypress, lang.hitch(this, "_onKey"))); - if(animate){ - var newContents = newWidget._wrapperWidget.containerNode, - oldContents = oldWidget._wrapperWidget.containerNode; + domStyle.set(this.domNode, { + opacity:0, + display:"" + }); - // During the animation we will be showing two dijitAccordionChildWrapper nodes at once, - // which on claro takes up 4px extra space (compared to stable AccordionContainer). - // Have to compensate for that by immediately shrinking the pane being closed. - var wrapperContainerNode = newWidget._wrapperWidget.containerNode, - wrapperContainerNodeMargin = dojo._getMarginExtents(wrapperContainerNode), - wrapperContainerNodePadBorder = dojo._getPadBorderExtents(wrapperContainerNode), - animationHeightOverhead = wrapperContainerNodeMargin.h + wrapperContainerNodePadBorder.h; + this._set("open", true); + this._onShow(); // lazy load trigger - oldContents.style.height = (self._verticalSpace - animationHeightOverhead) + "px"; + this._size(); + this._position(); - this._animation = new dojo.Animation({ - node: newContents, - duration: this.duration, - curve: [1, this._verticalSpace - animationHeightOverhead - 1], - onAnimate: function(value){ - value = Math.floor(value); // avoid fractional values - newContents.style.height = value + "px"; - oldContents.style.height = (self._verticalSpace - animationHeightOverhead - value) + "px"; - }, - onEnd: function(){ - delete self._animation; - newContents.style.height = "auto"; - oldWidget._wrapperWidget.containerNode.style.display = "none"; - oldContents.style.height = "auto"; - self._hideChild(oldWidget); + // fade-in Animation object, setup below + var fadeIn; + + this._fadeInDeferred = new Deferred(lang.hitch(this, function(){ + fadeIn.stop(); + delete this._fadeInDeferred; + })); + + fadeIn = fx.fadeIn({ + node: this.domNode, + duration: this.duration, + beforeBegin: lang.hitch(this, function(){ + DialogLevelManager.show(this, this.underlayAttrs); + }), + onEnd: lang.hitch(this, function(){ + if(this.autofocus && DialogLevelManager.isTop(this)){ + // find focusable items each time dialog is shown since if dialog contains a widget the + // first focusable items can change + this._getFocusItems(this.domNode); + focus.focus(this._firstFocusItem); } - }); - this._animation.onStop = this._animation.onEnd; - this._animation.play(); - } + this._fadeInDeferred.callback(true); + delete this._fadeInDeferred; + }) + }).play(); - return d; // If child has an href, promise that fires when the widget has finished loading + return this._fadeInDeferred; }, - // note: we are treating the container as controller here - _onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){ + hide: function(){ // summary: - // Handle keypress events - // description: - // This is called from a handler on AccordionContainer.domNode - // (setup in StackContainer), and is also called directly from - // the click handler for accordion labels - if(this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){ + // Hide the dialog + // returns: dojo.Deferred + // Deferred object that resolves when the hide animation is complete + + // if we haven't been initialized yet then we aren't showing and we can just return + if(!this._alreadyInitialized){ return; } - var k = dojo.keys, - c = e.charOrCode; - if((fromTitle && (c == k.LEFT_ARROW || c == k.UP_ARROW)) || - (e.ctrlKey && c == k.PAGE_UP)){ - this._adjacent(false)._buttonWidget._onTitleClick(); - dojo.stopEvent(e); - }else if((fromTitle && (c == k.RIGHT_ARROW || c == k.DOWN_ARROW)) || - (e.ctrlKey && (c == k.PAGE_DOWN || c == k.TAB))){ - this._adjacent(true)._buttonWidget._onTitleClick(); - dojo.stopEvent(e); + if(this._fadeInDeferred){ + this._fadeInDeferred.cancel(); } - } - } -); - -dojo.declare("dijit.layout._AccordionInnerContainer", - [dijit._Widget, dijit._CssStateMixin], { - // summary: - // Internal widget placed as direct child of AccordionContainer.containerNode. - // When other widgets are added as children to an AccordionContainer they are wrapped in - // this widget. - -/*===== - // buttonWidget: String - // Name of class to use to instantiate title - // (Wish we didn't have a separate widget for just the title but maintaining it - // for backwards compatibility, is it worth it?) - buttonWidget: null, -=====*/ -/*===== - // contentWidget: dijit._Widget - // Pointer to the real child widget - contentWidget: null, -=====*/ - - baseClass: "dijitAccordionInnerContainer", + // fade-in Animation object, setup below + var fadeOut; - // tell nested layout widget that we will take care of sizing - isContainer: true, - isLayoutContainer: true, + this._fadeOutDeferred = new Deferred(lang.hitch(this, function(){ + fadeOut.stop(); + delete this._fadeOutDeferred; + })); + // fire onHide when the promise resolves. + this._fadeOutDeferred.then(lang.hitch(this, 'onHide')); - buildRendering: function(){ - // Builds a template like: - // <div class=dijitAccordionInnerContainer> - // Button - // <div class=dijitAccordionChildWrapper> - // ContentPane - // </div> - // </div> + fadeOut = fx.fadeOut({ + node: this.domNode, + duration: this.duration, + onEnd: lang.hitch(this, function(){ + this.domNode.style.display = "none"; + DialogLevelManager.hide(this); + this._fadeOutDeferred.callback(true); + delete this._fadeOutDeferred; + }) + }).play(); - // Create wrapper div, placed where the child is now - this.domNode = dojo.place("<div class='" + this.baseClass + "'>", this.contentWidget.domNode, "after"); - - // wrapper div's first child is the button widget (ie, the title bar) - var child = this.contentWidget, - cls = dojo.getObject(this.buttonWidget); - this.button = child._buttonWidget = (new cls({ - contentWidget: child, - label: child.title, - title: child.tooltip, - dir: child.dir, - lang: child.lang, - iconClass: child.iconClass, - id: child.id + "_button", - parent: this.parent - })).placeAt(this.domNode); - - // and then the actual content widget (changing it from prior-sibling to last-child), - // wrapped by a <div class=dijitAccordionChildWrapper> - this.containerNode = dojo.place("<div class='dijitAccordionChildWrapper' style='display:none'>", this.domNode); - dojo.place(this.contentWidget.domNode, this.containerNode); - }, + if(this._scrollConnected){ + this._scrollConnected = false; + } + var h; + while(h = this._modalconnects.pop()){ + h.remove(); + } - postCreate: function(){ - this.inherited(arguments); + if(this._relativePosition){ + delete this._relativePosition; + } + this._set("open", false); - // Map changes in content widget's title etc. to changes in the button - var button = this.button; - this._contentWidgetWatches = [ - this.contentWidget.watch('title', dojo.hitch(this, function(name, oldValue, newValue){ - button.set("label", newValue); - })), - this.contentWidget.watch('tooltip', dojo.hitch(this, function(name, oldValue, newValue){ - button.set("title", newValue); - })), - this.contentWidget.watch('iconClass', dojo.hitch(this, function(name, oldValue, newValue){ - button.set("iconClass", newValue); - })) - ]; + return this._fadeOutDeferred; }, - _setSelectedAttr: function(/*Boolean*/ isSelected){ - this._set("selected", isSelected); - this.button.set("selected", isSelected); - if(isSelected){ - var cw = this.contentWidget; - if(cw.onSelected){ cw.onSelected(); } + layout: function(){ + // summary: + // Position the Dialog and the underlay + // tags: + // private + if(this.domNode.style.display != "none"){ + if(dijit._underlay){ // avoid race condition during show() + dijit._underlay.layout(); + } + this._position(); } }, - startup: function(){ - // Called by _Container.addChild() - this.contentWidget.startup(); - }, - destroy: function(){ - this.button.destroyRecursive(); - - dojo.forEach(this._contentWidgetWatches || [], function(w){ w.unwatch(); }); + if(this._fadeInDeferred){ + this._fadeInDeferred.cancel(); + } + if(this._fadeOutDeferred){ + this._fadeOutDeferred.cancel(); + } + if(this._moveable){ + this._moveable.destroy(); + } + var h; + while(h = this._modalconnects.pop()){ + h.remove(); + } - delete this.contentWidget._buttonWidget; - delete this.contentWidget._wrapperWidget; + DialogLevelManager.hide(this); this.inherited(arguments); - }, - - destroyDescendants: function(){ - // since getChildren isn't working for me, have to code this manually - this.contentWidget.destroyRecursive(); } -}); - -dojo.declare("dijit.layout._AccordionButton", - [dijit._Widget, dijit._Templated, dijit._CssStateMixin], - { - // summary: - // The title bar to click to open up an accordion pane. - // Internal widget used by AccordionContainer. - // tags: - // private - - templateString: dojo.cache("dijit.layout", "templates/AccordionButton.html", "<div dojoAttachEvent='onclick:_onTitleClick' class='dijitAccordionTitle'>\n\t<div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='onkeypress:_onTitleKeyPress'\n\t\t\tclass='dijitAccordionTitleFocus' role=\"tab\" aria-expanded=\"false\"\n\t\t><span class='dijitInline dijitAccordionArrow' role=\"presentation\"></span\n\t\t><span class='arrowTextUp' role=\"presentation\">+</span\n\t\t><span class='arrowTextDown' role=\"presentation\">-</span\n\t\t><img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" dojoAttachPoint='iconNode' style=\"vertical-align: middle\" role=\"presentation\"/>\n\t\t<span role=\"presentation\" dojoAttachPoint='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n"), - attributeMap: dojo.mixin(dojo.clone(dijit.layout.ContentPane.prototype.attributeMap), { - label: {node: "titleTextNode", type: "innerHTML" }, - title: {node: "titleTextNode", type: "attribute", attribute: "title"}, - iconClass: { node: "iconNode", type: "class" } - }), - - baseClass: "dijitAccordionTitle", - - getParent: function(){ - // summary: - // Returns the AccordionContainer parent. - // tags: - // private - return this.parent; - }, - - buildRendering: function(){ - this.inherited(arguments); - var titleTextNodeId = this.id.replace(' ','_'); - dojo.attr(this.titleTextNode, "id", titleTextNodeId+"_title"); - dijit.setWaiState(this.focusNode, "labelledby", dojo.attr(this.titleTextNode, "id")); - dojo.setSelectable(this.domNode, false); - }, + }); - getTitleHeight: function(){ - // summary: - // Returns the height of the title dom node. - return dojo._getMarginSize(this.domNode).h; // Integer - }, + var Dialog = declare("dijit.Dialog", [ContentPane, _DialogBase], {}); + Dialog._DialogBase = _DialogBase; // for monkey patching - // TODO: maybe the parent should set these methods directly rather than forcing the code - // into the button widget? - _onTitleClick: function(){ + var DialogLevelManager = Dialog._DialogLevelManager = { // summary: - // Callback when someone clicks my title. - var parent = this.getParent(); - parent.selectChild(this.contentWidget, true); - dijit.focus(this.focusNode); - }, + // Controls the various active "levels" on the page, starting with the + // stuff initially visible on the page (at z-index 0), and then having an entry for + // each Dialog shown. - _onTitleKeyPress: function(/*Event*/ evt){ - return this.getParent()._onKeyPress(evt, this.contentWidget); - }, - - _setSelectedAttr: function(/*Boolean*/ isSelected){ - this._set("selected", isSelected); - dijit.setWaiState(this.focusNode, "expanded", isSelected); - dijit.setWaiState(this.focusNode, "selected", isSelected); - this.focusNode.setAttribute("tabIndex", isSelected ? "0" : "-1"); - } -}); + _beginZIndex: 950, -} + show: function(/*dijit._Widget*/ dialog, /*Object*/ underlayAttrs){ + // summary: + // Call right before fade-in animation for new dialog. + // Saves current focus, displays/adjusts underlay for new dialog, + // and sets the z-index of the dialog itself. + // + // New dialog will be displayed on top of all currently displayed dialogs. + // + // Caller is responsible for setting focus in new dialog after the fade-in + // animation completes. -if(!dojo._hasResource["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout.BorderContainer"] = true; -dojo.provide("dijit.layout.BorderContainer"); + // Save current focus + ds[ds.length-1].focus = focus.curNode; + // Display the underlay, or if already displayed then adjust for this new dialog + var underlay = dijit._underlay; + if(!underlay || underlay._destroyed){ + underlay = dijit._underlay = new DialogUnderlay(underlayAttrs); + }else{ + underlay.set(dialog.underlayAttrs); + } + // Set z-index a bit above previous dialog + var zIndex = ds[ds.length-1].dialog ? ds[ds.length-1].zIndex + 2 : Dialog._DialogLevelManager._beginZIndex; + if(ds.length == 1){ // first dialog + underlay.show(); + } + domStyle.set(dijit._underlay.domNode, 'zIndex', zIndex - 1); + // Dialog + domStyle.set(dialog.domNode, 'zIndex', zIndex); + ds.push({dialog: dialog, underlayAttrs: underlayAttrs, zIndex: zIndex}); + }, -dojo.declare( - "dijit.layout.BorderContainer", - dijit.layout._LayoutWidget, -{ - // summary: - // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides. - // - // description: - // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;", - // that contains a child widget marked region="center" and optionally children widgets marked - // region equal to "top", "bottom", "leading", "trailing", "left" or "right". - // Children along the edges will be laid out according to width or height dimensions and may - // include optional splitters (splitter="true") to make them resizable by the user. The remaining - // space is designated for the center region. - // - // The outer size must be specified on the BorderContainer node. Width must be specified for the sides - // and height for the top and bottom, respectively. No dimensions should be specified on the center; - // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like - // "left" and "right" except that they will be reversed in right-to-left environments. - // - // For complex layouts, multiple children can be specified for a single region. In this case, the - // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority) - // and which child is closer to the center (high layoutPriority). layoutPriority can also be used - // instead of the design attribute to conrol layout precedence of horizontal vs. vertical panes. - // example: - // | <div dojoType="dijit.layout.BorderContainer" design="sidebar" gutters="false" - // | style="width: 400px; height: 300px;"> - // | <div dojoType="dijit.layout.ContentPane" region="top">header text</div> - // | <div dojoType="dijit.layout.ContentPane" region="right" splitter="true" style="width: 200px;">table of contents</div> - // | <div dojoType="dijit.layout.ContentPane" region="center">client area</div> - // | </div> + hide: function(/*dijit._Widget*/ dialog){ + // summary: + // Called when the specified dialog is hidden/destroyed, after the fade-out + // animation ends, in order to reset page focus, fix the underlay, etc. + // If the specified dialog isn't open then does nothing. + // + // Caller is responsible for either setting display:none on the dialog domNode, + // or calling dijit.popup.hide(), or removing it from the page DOM. - // design: String - // Which design is used for the layout: - // - "headline" (default) where the top and bottom extend - // the full width of the container - // - "sidebar" where the left and right sides extend from top to bottom. - design: "headline", + if(ds[ds.length-1].dialog == dialog){ + // Removing the top (or only) dialog in the stack, return focus + // to previous dialog - // gutters: [const] Boolean - // Give each pane a border and margin. - // Margin determined by domNode.paddingLeft. - // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing. - gutters: true, + ds.pop(); - // liveSplitters: [const] Boolean - // Specifies whether splitters resize as you drag (true) or only upon mouseup (false) - liveSplitters: true, + var pd = ds[ds.length-1]; // the new active dialog (or the base page itself) - // persist: Boolean - // Save splitter positions in a cookie. - persist: false, + // Adjust underlay + if(ds.length == 1){ + // Returning to original page. + // Hide the underlay, unless the underlay widget has already been destroyed + // because we are being called during page unload (when all widgets are destroyed) + if(!dijit._underlay._destroyed){ + dijit._underlay.hide(); + } + }else{ + // Popping back to previous dialog, adjust underlay + domStyle.set(dijit._underlay.domNode, 'zIndex', pd.zIndex - 1); + dijit._underlay.set(pd.underlayAttrs); + } - baseClass: "dijitBorderContainer", + // Adjust focus + if(dialog.refocus){ + // If we are returning control to a previous dialog but for some reason + // that dialog didn't have a focused field, set focus to first focusable item. + // This situation could happen if two dialogs appeared at nearly the same time, + // since a dialog doesn't set it's focus until the fade-in is finished. + var focus = pd.focus; + if(pd.dialog && (!focus || !dom.isDescendant(focus, pd.dialog.domNode))){ + pd.dialog._getFocusItems(pd.dialog.domNode); + focus = pd.dialog._firstFocusItem; + } - // _splitterClass: String - // Optional hook to override the default Splitter widget used by BorderContainer - _splitterClass: "dijit.layout._Splitter", + if(focus){ + // Refocus the button that spawned the Dialog. This will fail in corner cases including + // page unload on IE, because the dijit/form/Button that launched the Dialog may get destroyed + // before this code runs. (#15058) + try{ + focus.focus(); + }catch(e){} + } + } + }else{ + // Removing a dialog out of order (#9944, #10705). + // Don't need to mess with underlay or z-index or anything. + var idx = array.indexOf(array.map(ds, function(elem){return elem.dialog}), dialog); + if(idx != -1){ + ds.splice(idx, 1); + } + } + }, - postMixInProperties: function(){ - // change class name to indicate that BorderContainer is being used purely for - // layout (like LayoutContainer) rather than for pretty formatting. - if(!this.gutters){ - this.baseClass += "NoGutter"; + isTop: function(/*dijit._Widget*/ dialog){ + // summary: + // Returns true if specified Dialog is the top in the task + return ds[ds.length-1].dialog == dialog; } - this.inherited(arguments); - }, + }; - startup: function(){ - if(this._started){ return; } - dojo.forEach(this.getChildren(), this._setupChild, this); - this.inherited(arguments); - }, + // Stack representing the various active "levels" on the page, starting with the + // stuff initially visible on the page (at z-index 0), and then having an entry for + // each Dialog shown. + // Each element in stack has form { + // dialog: dialogWidget, + // focus: returnFromGetFocus(), + // underlayAttrs: attributes to set on underlay (when this widget is active) + // } + var ds = Dialog._dialogStack = [ + {dialog: null, focus: null, underlayAttrs: null} // entry for stuff at z-index: 0 + ]; + + // Back compat w/1.6, remove for 2.0 + if(!kernel.isAsync){ + ready(0, function(){ + var requires = ["dijit/TooltipDialog"]; + require(requires); // use indirection so modules not rolled into a build + }); + } - _setupChild: function(/*dijit._Widget*/ child){ - // Override _LayoutWidget._setupChild(). + return Dialog; +}); - var region = child.region; - if(region){ - this.inherited(arguments); +}, +'dijit/_base/focus':function(){ +define("dijit/_base/focus", [ + "dojo/_base/array", // array.forEach + "dojo/dom", // dom.isDescendant + "dojo/_base/lang", // lang.isArray + "dojo/topic", // publish + "dojo/_base/window", // win.doc win.doc.selection win.global win.global.getSelection win.withGlobal + "../focus", + ".." // for exporting symbols to dijit +], function(array, dom, lang, topic, win, focus, dijit){ + + // module: + // dijit/_base/focus + // summary: + // Deprecated module to monitor currently focused node and stack of currently focused widgets. + // New code should access dijit/focus directly. - dojo.addClass(child.domNode, this.baseClass+"Pane"); + lang.mixin(dijit, { + // _curFocus: DomNode + // Currently focused item on screen + _curFocus: null, - var ltr = this.isLeftToRight(); - if(region == "leading"){ region = ltr ? "left" : "right"; } - if(region == "trailing"){ region = ltr ? "right" : "left"; } + // _prevFocus: DomNode + // Previously focused item on screen + _prevFocus: null, - // Create draggable splitter for resizing pane, - // or alternately if splitter=false but BorderContainer.gutters=true then - // insert dummy div just for spacing - if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){ - var _Splitter = dojo.getObject(child.splitter ? this._splitterClass : "dijit.layout._Gutter"); - var splitter = new _Splitter({ - id: child.id + "_splitter", - container: this, - child: child, - region: region, - live: this.liveSplitters - }); - splitter.isSplitter = true; - child._splitterWidget = splitter; + isCollapsed: function(){ + // summary: + // Returns true if there is no text selected + return dijit.getBookmark().isCollapsed; + }, - dojo.place(splitter.domNode, child.domNode, "after"); + getBookmark: function(){ + // summary: + // Retrieves a bookmark that can be used with moveToBookmark to return to the same range + var bm, rg, tg, sel = win.doc.selection, cf = focus.curNode; + + if(win.global.getSelection){ + //W3C Range API for selections. + sel = win.global.getSelection(); + if(sel){ + if(sel.isCollapsed){ + tg = cf? cf.tagName : ""; + if(tg){ + //Create a fake rangelike item to restore selections. + tg = tg.toLowerCase(); + if(tg == "textarea" || + (tg == "input" && (!cf.type || cf.type.toLowerCase() == "text"))){ + sel = { + start: cf.selectionStart, + end: cf.selectionEnd, + node: cf, + pRange: true + }; + return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object. + } + } + bm = {isCollapsed:true}; + if(sel.rangeCount){ + bm.mark = sel.getRangeAt(0).cloneRange(); + } + }else{ + rg = sel.getRangeAt(0); + bm = {isCollapsed: false, mark: rg.cloneRange()}; + } + } + }else if(sel){ + // If the current focus was a input of some sort and no selection, don't bother saving + // a native bookmark. This is because it causes issues with dialog/page selection restore. + // So, we need to create psuedo bookmarks to work with. + tg = cf ? cf.tagName : ""; + tg = tg.toLowerCase(); + if(cf && tg && (tg == "button" || tg == "textarea" || tg == "input")){ + if(sel.type && sel.type.toLowerCase() == "none"){ + return { + isCollapsed: true, + mark: null + } + }else{ + rg = sel.createRange(); + return { + isCollapsed: rg.text && rg.text.length?false:true, + mark: { + range: rg, + pRange: true + } + }; + } + } + bm = {}; - // Splitters aren't added as Contained children, so we need to call startup explicitly - splitter.startup(); + //'IE' way for selections. + try{ + // createRange() throws exception when dojo in iframe + //and nothing selected, see #9632 + rg = sel.createRange(); + bm.isCollapsed = !(sel.type == 'Text' ? rg.htmlText.length : rg.length); + }catch(e){ + bm.isCollapsed = true; + return bm; + } + if(sel.type.toUpperCase() == 'CONTROL'){ + if(rg.length){ + bm.mark=[]; + var i=0,len=rg.length; + while(i<len){ + bm.mark.push(rg.item(i++)); + } + }else{ + bm.isCollapsed = true; + bm.mark = null; + } + }else{ + bm.mark = rg.getBookmark(); + } + }else{ + console.warn("No idea how to store the current selection for this browser!"); } - child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc. - } - }, + return bm; // Object + }, - layout: function(){ - // Implement _LayoutWidget.layout() virtual method. - this._layoutChildren(); - }, + moveToBookmark: function(/*Object*/ bookmark){ + // summary: + // Moves current selection to a bookmark + // bookmark: + // This should be a returned object from dijit.getBookmark() + + var _doc = win.doc, + mark = bookmark.mark; + if(mark){ + if(win.global.getSelection){ + //W3C Rangi API (FF, WebKit, Opera, etc) + var sel = win.global.getSelection(); + if(sel && sel.removeAllRanges){ + if(mark.pRange){ + var n = mark.node; + n.selectionStart = mark.start; + n.selectionEnd = mark.end; + }else{ + sel.removeAllRanges(); + sel.addRange(mark); + } + }else{ + console.warn("No idea how to restore selection for this browser!"); + } + }else if(_doc.selection && mark){ + //'IE' way. + var rg; + if(mark.pRange){ + rg = mark.range; + }else if(lang.isArray(mark)){ + rg = _doc.body.createControlRange(); + //rg.addElement does not have call/apply method, so can not call it directly + //rg is not available in "range.addElement(item)", so can't use that either + array.forEach(mark, function(n){ + rg.addElement(n); + }); + }else{ + rg = _doc.body.createTextRange(); + rg.moveToBookmark(mark); + } + rg.select(); + } + } + }, - addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ - // Override _LayoutWidget.addChild(). - this.inherited(arguments); - if(this._started){ - this.layout(); //OPT - } - }, + getFocus: function(/*Widget?*/ menu, /*Window?*/ openedForWindow){ + // summary: + // Called as getFocus(), this returns an Object showing the current focus + // and selected text. + // + // Called as getFocus(widget), where widget is a (widget representing) a button + // that was just pressed, it returns where focus was before that button + // was pressed. (Pressing the button may have either shifted focus to the button, + // or removed focus altogether.) In this case the selected text is not returned, + // since it can't be accurately determined. + // + // menu: dijit._Widget or {domNode: DomNode} structure + // The button that was just pressed. If focus has disappeared or moved + // to this button, returns the previous focus. In this case the bookmark + // information is already lost, and null is returned. + // + // openedForWindow: + // iframe in which menu was opened + // + // returns: + // A handle to restore focus/selection, to be passed to `dijit.focus` + var node = !focus.curNode || (menu && dom.isDescendant(focus.curNode, menu.domNode)) ? dijit._prevFocus : focus.curNode; + return { + node: node, + bookmark: node && (node == focus.curNode) && win.withGlobal(openedForWindow || win.global, dijit.getBookmark), + openedForWindow: openedForWindow + }; // Object + }, - removeChild: function(/*dijit._Widget*/ child){ - // Override _LayoutWidget.removeChild(). + // _activeStack: dijit._Widget[] + // List of currently active widgets (focused widget and it's ancestors) + _activeStack: [], - var region = child.region; - var splitter = child._splitterWidget - if(splitter){ - splitter.destroy(); - delete child._splitterWidget; - } - this.inherited(arguments); - - if(this._started){ - this._layoutChildren(); - } - // Clean up whatever style changes we made to the child pane. - // Unclear how height and width should be handled. - dojo.removeClass(child.domNode, this.baseClass+"Pane"); - dojo.style(child.domNode, { - top: "auto", - bottom: "auto", - left: "auto", - right: "auto", - position: "static" - }); - dojo.style(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto"); - }, + registerIframe: function(/*DomNode*/ iframe){ + // summary: + // Registers listeners on the specified iframe so that any click + // or focus event on that iframe (or anything in it) is reported + // as a focus/click event on the <iframe> itself. + // description: + // Currently only used by editor. + // returns: + // Handle to pass to unregisterIframe() + return focus.registerIframe(iframe); + }, - getChildren: function(){ - // Override _LayoutWidget.getChildren() to only return real children, not the splitters. - return dojo.filter(this.inherited(arguments), function(widget){ - return !widget.isSplitter; - }); - }, + unregisterIframe: function(/*Object*/ handle){ + // summary: + // Unregisters listeners on the specified iframe created by registerIframe. + // After calling be sure to delete or null out the handle itself. + // handle: + // Handle returned by registerIframe() - // TODO: remove in 2.0 - getSplitter: function(/*String*/region){ - // summary: - // Returns the widget responsible for rendering the splitter associated with region - // tags: - // deprecated - return dojo.filter(this.getChildren(), function(child){ - return child.region == region; - })[0]._splitterWidget; - }, + handle && handle.remove(); + }, - resize: function(newSize, currentSize){ - // Overrides _LayoutWidget.resize(). + registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){ + // summary: + // Registers listeners on the specified window (either the main + // window or an iframe's window) to detect when the user has clicked somewhere + // or focused somewhere. + // description: + // Users should call registerIframe() instead of this method. + // targetWindow: + // If specified this is the window associated with the iframe, + // i.e. iframe.contentWindow. + // effectiveNode: + // If specified, report any focus events inside targetWindow as + // an event on effectiveNode, rather than on evt.target. + // returns: + // Handle to pass to unregisterWin() - // resetting potential padding to 0px to provide support for 100% width/height + padding - // TODO: this hack doesn't respect the box model and is a temporary fix - if(!this.cs || !this.pe){ - var node = this.domNode; - this.cs = dojo.getComputedStyle(node); - this.pe = dojo._getPadExtents(node, this.cs); - this.pe.r = dojo._toPixelValue(node, this.cs.paddingRight); - this.pe.b = dojo._toPixelValue(node, this.cs.paddingBottom); + return focus.registerWin(targetWindow, effectiveNode); + }, - dojo.style(node, "padding", "0px"); - } + unregisterWin: function(/*Handle*/ handle){ + // summary: + // Unregisters listeners on the specified window (either the main + // window or an iframe's window) according to handle returned from registerWin(). + // After calling be sure to delete or null out the handle itself. - this.inherited(arguments); - }, + handle && handle.remove(); + } + }); - _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){ + // Override focus singleton's focus function so that dijit.focus() + // has backwards compatible behavior of restoring selection (although + // probably no one is using that). + focus.focus = function(/*Object || DomNode */ handle){ // summary: - // This is the main routine for setting size/position of each child. - // description: - // With no arguments, measures the height of top/bottom panes, the width - // of left/right panes, and then sizes all panes accordingly. - // - // With changedRegion specified (as "left", "top", "bottom", or "right"), - // it changes that region's width/height to changedRegionSize and - // then resizes other regions that were affected. - // changedChildId: - // Id of the child which should be resized because splitter was dragged. - // changedChildSize: - // The new width/height (in pixels) to make specified child + // Sets the focused node and the selection according to argument. + // To set focus to an iframe's content, pass in the iframe itself. + // handle: + // object returned by get(), or a DomNode - if(!this._borderBox || !this._borderBox.h){ - // We are currently hidden, or we haven't been sized by our parent yet. - // Abort. Someone will resize us later. - return; - } + if(!handle){ return; } - // Generate list of wrappers of my children in the order that I want layoutChildren() - // to process them (i.e. from the outside to the inside) - var wrappers = dojo.map(this.getChildren(), function(child, idx){ - return { - pane: child, - weight: [ - child.region == "center" ? Infinity : 0, - child.layoutPriority, - (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1), - idx - ] - }; - }, this); - wrappers.sort(function(a, b){ - var aw = a.weight, bw = b.weight; - for(var i=0; i<aw.length; i++){ - if(aw[i] != bw[i]){ - return aw[i] - bw[i]; - } - } - return 0; - }); + var node = "node" in handle ? handle.node : handle, // because handle is either DomNode or a composite object + bookmark = handle.bookmark, + openedForWindow = handle.openedForWindow, + collapsed = bookmark ? bookmark.isCollapsed : false; - // Make new list, combining the externally specified children with splitters and gutters - var childrenAndSplitters = []; - dojo.forEach(wrappers, function(wrapper){ - var pane = wrapper.pane; - childrenAndSplitters.push(pane); - if(pane._splitterWidget){ - childrenAndSplitters.push(pane._splitterWidget); + // Set the focus + // Note that for iframe's we need to use the <iframe> to follow the parentNode chain, + // but we need to set focus to iframe.contentWindow + if(node){ + var focusNode = (node.tagName.toLowerCase() == "iframe") ? node.contentWindow : node; + if(focusNode && focusNode.focus){ + try{ + // Gecko throws sometimes if setting focus is impossible, + // node not displayed or something like that + focusNode.focus(); + }catch(e){/*quiet*/} } - }); - - // Compute the box in which to lay out my children - var dim = { - l: this.pe.l, - t: this.pe.t, - w: this._borderBox.w - this.pe.w, - h: this._borderBox.h - this.pe.h - }; - - // Layout the children, possibly changing size due to a splitter drag - dijit.layout.layoutChildren(this.domNode, dim, childrenAndSplitters, - changedChildId, changedChildSize); - }, + focus._onFocusNode(node); + } - destroyRecursive: function(){ - // Destroy splitters first, while getChildren() still works - dojo.forEach(this.getChildren(), function(child){ - var splitter = child._splitterWidget; - if(splitter){ - splitter.destroy(); + // set the selection + // do not need to restore if current selection is not empty + // (use keyboard to select a menu item) or if previous selection was collapsed + // as it may cause focus shift (Esp in IE). + if(bookmark && win.withGlobal(openedForWindow || win.global, dijit.isCollapsed) && !collapsed){ + if(openedForWindow){ + openedForWindow.focus(); } - delete child._splitterWidget; - }); - - // Then destroy the real children, and myself - this.inherited(arguments); - } -}); - -// This argument can be specified for the children of a BorderContainer. -// Since any widget can be specified as a LayoutContainer child, mix it -// into the base widget class. (This is a hack, but it's effective.) -dojo.extend(dijit._Widget, { - // region: [const] String - // Parameter for children of `dijit.layout.BorderContainer`. - // Values: "top", "bottom", "leading", "trailing", "left", "right", "center". - // See the `dijit.layout.BorderContainer` description for details. - region: '', - - // layoutPriority: [const] Number - // Parameter for children of `dijit.layout.BorderContainer`. - // Children with a higher layoutPriority will be placed closer to the BorderContainer center, - // between children with a lower layoutPriority. - layoutPriority: 0, + try{ + win.withGlobal(openedForWindow || win.global, dijit.moveToBookmark, null, [bookmark]); + }catch(e2){ + /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */ + } + } + }; - // splitter: [const] Boolean - // Parameter for child of `dijit.layout.BorderContainer` where region != "center". - // If true, enables user to resize the widget by putting a draggable splitter between - // this widget and the region=center widget. - splitter: false, + // For back compatibility, monitor changes to focused node and active widget stack, + // publishing events and copying changes from focus manager variables into dijit (top level) variables + focus.watch("curNode", function(name, oldVal, newVal){ + dijit._curFocus = newVal; + dijit._prevFocus = oldVal; + if(newVal){ + topic.publish("focusNode", newVal); // publish + } + }); + focus.watch("activeStack", function(name, oldVal, newVal){ + dijit._activeStack = newVal; + }); - // minSize: [const] Number - // Parameter for children of `dijit.layout.BorderContainer`. - // Specifies a minimum size (in pixels) for this widget when resized by a splitter. - minSize: 0, + focus.on("widget-blur", function(widget, by){ + topic.publish("widgetBlur", widget, by); // publish + }); + focus.on("widget-focus", function(widget, by){ + topic.publish("widgetFocus", widget, by); // publish + }); - // maxSize: [const] Number - // Parameter for children of `dijit.layout.BorderContainer`. - // Specifies a maximum size (in pixels) for this widget when resized by a splitter. - maxSize: Infinity + return dijit; }); -dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ], -{ - // summary: - // A draggable spacer between two items in a `dijit.layout.BorderContainer`. - // description: - // This is instantiated by `dijit.layout.BorderContainer`. Users should not - // create it directly. - // tags: - // private +}, +'dijit/tree/dndSource':function(){ +define("dijit/tree/dndSource", [ + "dojo/_base/array", // array.forEach array.indexOf array.map + "dojo/_base/connect", // isCopyKey + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add + "dojo/dom-geometry", // domGeometry.position + "dojo/_base/lang", // lang.mixin lang.hitch + "dojo/on", // subscribe + "dojo/touch", + "dojo/topic", + "dojo/dnd/Manager", // DNDManager.manager + "./_dndSelector" +], function(array, connect, declare, domClass, domGeometry, lang, on, touch, topic, DNDManager, _dndSelector){ + +// module: +// dijit/tree/dndSource +// summary: +// Handles drag and drop operations (as a source or a target) for `dijit.Tree` /*===== - // container: [const] dijit.layout.BorderContainer - // Pointer to the parent BorderContainer - container: null, - - // child: [const] dijit.layout._LayoutWidget - // Pointer to the pane associated with this splitter - child: null, - - // region: [const] String - // Region of pane associated with this splitter. - // "top", "bottom", "left", "right". - region: null, +dijit.tree.__SourceArgs = function(){ + // summary: + // A dict of parameters for Tree source configuration. + // isSource: Boolean? + // Can be used as a DnD source. Defaults to true. + // accept: String[] + // List of accepted types (text strings) for a target; defaults to + // ["text", "treeNode"] + // copyOnly: Boolean? + // Copy items, if true, use a state of Ctrl key otherwise, + // dragThreshold: Number + // The move delay in pixels before detecting a drag; 0 by default + // betweenThreshold: Integer + // Distance from upper/lower edge of node to allow drop to reorder nodes + this.isSource = isSource; + this.accept = accept; + this.autoSync = autoSync; + this.copyOnly = copyOnly; + this.dragThreshold = dragThreshold; + this.betweenThreshold = betweenThreshold; +} =====*/ - // live: [const] Boolean - // If true, the child's size changes and the child widget is redrawn as you drag the splitter; - // otherwise, the size doesn't change until you drop the splitter (by mouse-up) - live: true, +return declare("dijit.tree.dndSource", _dndSelector, { + // summary: + // Handles drag and drop operations (as a source or a target) for `dijit.Tree` - templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" role="separator"><div class="dijitSplitterThumb"></div></div>', + // isSource: [private] Boolean + // Can be used as a DnD source. + isSource: true, - postMixInProperties: function(){ - this.inherited(arguments); + // accept: String[] + // List of accepted types (text strings) for the Tree; defaults to + // ["text"] + accept: ["text", "treeNode"], - this.horizontal = /top|bottom/.test(this.region); - this._factor = /top|left/.test(this.region) ? 1 : -1; - this._cookieName = this.container.id + "_" + this.region; - }, + // copyOnly: [private] Boolean + // Copy items, if true, use a state of Ctrl key otherwise + copyOnly: false, - buildRendering: function(){ - this.inherited(arguments); + // dragThreshold: Number + // The move delay in pixels before detecting a drag; 5 by default + dragThreshold: 5, - dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V")); + // betweenThreshold: Integer + // Distance from upper/lower edge of node to allow drop to reorder nodes + betweenThreshold: 0, - if(this.container.persist){ - // restore old size - var persistSize = dojo.cookie(this._cookieName); - if(persistSize){ - this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize; + constructor: function(/*dijit.Tree*/ tree, /*dijit.tree.__SourceArgs*/ params){ + // summary: + // a constructor of the Tree DnD Source + // tags: + // private + if(!params){ params = {}; } + lang.mixin(this, params); + this.isSource = typeof params.isSource == "undefined" ? true : params.isSource; + var type = params.accept instanceof Array ? params.accept : ["text", "treeNode"]; + this.accept = null; + if(type.length){ + this.accept = {}; + for(var i = 0; i < type.length; ++i){ + this.accept[type[i]] = 1; } } - }, - - _computeMaxSize: function(){ - // summary: - // Return the maximum size that my corresponding pane can be set to - - var dim = this.horizontal ? 'h' : 'w', - childSize = dojo.marginBox(this.child.domNode)[dim], - center = dojo.filter(this.container.getChildren(), function(child){ return child.region == "center";})[0], - spaceAvailable = dojo.marginBox(center.domNode)[dim]; // can expand until center is crushed to 0 - return Math.min(this.child.maxSize, childSize + spaceAvailable); - }, - - _startDrag: function(e){ - if(!this.cover){ - this.cover = dojo.doc.createElement('div'); - dojo.addClass(this.cover, "dijitSplitterCover"); - dojo.place(this.cover, this.child.domNode, "after"); - } - dojo.addClass(this.cover, "dijitSplitterCoverActive"); + // class-specific variables + this.isDragging = false; + this.mouseDown = false; + this.targetAnchor = null; // DOMNode corresponding to the currently moused over TreeNode + this.targetBox = null; // coordinates of this.targetAnchor + this.dropPosition = ""; // whether mouse is over/after/before this.targetAnchor + this._lastX = 0; + this._lastY = 0; - // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up. - if(this.fake){ dojo.destroy(this.fake); } - if(!(this._resize = this.live)){ //TODO: disable live for IE6? - // create fake splitter to display at old position while we drag - (this.fake = this.domNode.cloneNode(true)).removeAttribute("id"); - dojo.addClass(this.domNode, "dijitSplitterShadow"); - dojo.place(this.fake, this.domNode, "after"); - } - dojo.addClass(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active"); - if(this.fake){ - dojo.removeClass(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover"); + // states + this.sourceState = ""; + if(this.isSource){ + domClass.add(this.node, "dojoDndSource"); } - - //Performance: load data info local vars for onmousevent function closure - var factor = this._factor, - isHorizontal = this.horizontal, - axis = isHorizontal ? "pageY" : "pageX", - pageStart = e[axis], - splitterStyle = this.domNode.style, - dim = isHorizontal ? 'h' : 'w', - childStart = dojo.marginBox(this.child.domNode)[dim], - max = this._computeMaxSize(), - min = this.child.minSize || 20, - region = this.region, - splitterAttr = region == "top" || region == "bottom" ? "top" : "left", // style attribute of splitter to adjust - splitterStart = parseInt(splitterStyle[splitterAttr], 10), - resize = this._resize, - layoutFunc = dojo.hitch(this.container, "_layoutChildren", this.child.id), - de = dojo.doc; - - this._handlers = (this._handlers || []).concat([ - dojo.connect(de, "onmousemove", this._drag = function(e, forceResize){ - var delta = e[axis] - pageStart, - childSize = factor * delta + childStart, - boundChildSize = Math.max(Math.min(childSize, max), min); - - if(resize || forceResize){ - layoutFunc(boundChildSize); - } - // TODO: setting style directly (usually) sets content box size, need to set margin box size - splitterStyle[splitterAttr] = delta + splitterStart + factor*(boundChildSize - childSize) + "px"; - }), - dojo.connect(de, "ondragstart", dojo.stopEvent), - dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent), - dojo.connect(de, "onmouseup", this, "_stopDrag") - ]); - dojo.stopEvent(e); - }, - - _onMouse: function(e){ - var o = (e.type == "mouseover" || e.type == "mouseenter"); - dojo.toggleClass(this.domNode, "dijitSplitterHover", o); - dojo.toggleClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o); - }, - - _stopDrag: function(e){ - try{ - if(this.cover){ - dojo.removeClass(this.cover, "dijitSplitterCoverActive"); - } - if(this.fake){ dojo.destroy(this.fake); } - dojo.removeClass(this.domNode, "dijitSplitterActive dijitSplitter" - + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow"); - this._drag(e); //TODO: redundant with onmousemove? - this._drag(e, true); - }finally{ - this._cleanupHandlers(); - delete this._drag; + this.targetState = ""; + if(this.accept){ + domClass.add(this.node, "dojoDndTarget"); } - if(this.container.persist){ - dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365}); - } + // set up events + this.topics = [ + topic.subscribe("/dnd/source/over", lang.hitch(this, "onDndSourceOver")), + topic.subscribe("/dnd/start", lang.hitch(this, "onDndStart")), + topic.subscribe("/dnd/drop", lang.hitch(this, "onDndDrop")), + topic.subscribe("/dnd/cancel", lang.hitch(this, "onDndCancel")) + ]; }, - _cleanupHandlers: function(){ - dojo.forEach(this._handlers, dojo.disconnect); - delete this._handlers; + // methods + checkAcceptance: function(/*===== source, nodes =====*/){ + // summary: + // Checks if the target can accept nodes from this source + // source: dijit.tree.dndSource + // The source which provides items + // nodes: DOMNode[] + // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if + // source is a dijit.Tree. + // tags: + // extension + return true; // Boolean }, - _onKeyPress: function(/*Event*/ e){ - // should we apply typematic to this? - this._resize = true; - var horizontal = this.horizontal; - var tick = 1; - var dk = dojo.keys; - switch(e.charOrCode){ - case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW: - tick *= -1; -// break; - case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW: - break; - default: -// this.inherited(arguments); - return; - } - var childSize = dojo._getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick; - this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize)); - dojo.stopEvent(e); + copyState: function(keyPressed){ + // summary: + // Returns true, if we need to copy items, false to move. + // It is separated to be overwritten dynamically, if needed. + // keyPressed: Boolean + // The "copy" control key was pressed + // tags: + // protected + return this.copyOnly || keyPressed; // Boolean }, - destroy: function(){ - this._cleanupHandlers(); - delete this.child; - delete this.container; - delete this.cover; - delete this.fake; - this.inherited(arguments); - } -}); - -dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated], -{ - // summary: - // Just a spacer div to separate side pane from center pane. - // Basically a trick to lookup the gutter/splitter width from the theme. - // description: - // Instantiated by `dijit.layout.BorderContainer`. Users should not - // create directly. - // tags: - // private - - templateString: '<div class="dijitGutter" role="presentation"></div>', - - postMixInProperties: function(){ + // summary: + // Prepares the object to be garbage-collected. this.inherited(arguments); - this.horizontal = /top|bottom/.test(this.region); + var h; + while(h = this.topics.pop()){ h.remove(); } + this.targetAnchor = null; }, - buildRendering: function(){ - this.inherited(arguments); - dojo.addClass(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V")); - } -}); - -} - -if(!dojo._hasResource["dijit.layout._TabContainerBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout._TabContainerBase"] = true; -dojo.provide("dijit.layout._TabContainerBase"); - - - - -dojo.declare("dijit.layout._TabContainerBase", - [dijit.layout.StackContainer, dijit._Templated], - { - // summary: - // Abstract base class for TabContainer. Must define _makeController() to instantiate - // and return the widget that displays the tab labels - // description: - // A TabContainer is a container that has multiple panes, but shows only - // one pane at a time. There are a set of tabs corresponding to each pane, - // where each tab has the name (aka title) of the pane, and optionally a close button. - - // tabPosition: String - // Defines where tabs go relative to tab content. - // "top", "bottom", "left-h", "right-h" - tabPosition: "top", - - baseClass: "dijitTabContainer", - - // tabStrip: [const] Boolean - // Defines whether the tablist gets an extra class for layouting, putting a border/shading - // around the set of tabs. Not supported by claro theme. - tabStrip: false, - - // nested: [const] Boolean - // If true, use styling for a TabContainer nested inside another TabContainer. - // For tundra etc., makes tabs look like links, and hides the outer - // border since the outer TabContainer already has a border. - nested: false, - - 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"), - - postMixInProperties: function(){ - // set class name according to tab position, ex: dijitTabContainerTop - this.baseClass += this.tabPosition.charAt(0).toUpperCase() + this.tabPosition.substr(1).replace(/-.*/, ""); - - this.srcNodeRef && dojo.style(this.srcNodeRef, "visibility", "hidden"); + _onDragMouse: function(e){ + // summary: + // Helper method for processing onmousemove/onmouseover events while drag is in progress. + // Keeps track of current drop target. - this.inherited(arguments); - }, + var m = DNDManager.manager(), + oldTarget = this.targetAnchor, // the TreeNode corresponding to TreeNode mouse was previously over + newTarget = this.current, // TreeNode corresponding to TreeNode mouse is currently over + oldDropPosition = this.dropPosition; // the previous drop position (over/before/after) - buildRendering: function(){ - this.inherited(arguments); + // calculate if user is indicating to drop the dragged node before, after, or over + // (i.e., to become a child of) the target node + var newDropPosition = "Over"; + if(newTarget && this.betweenThreshold > 0){ + // If mouse is over a new TreeNode, then get new TreeNode's position and size + if(!this.targetBox || oldTarget != newTarget){ + this.targetBox = domGeometry.position(newTarget.rowNode, true); + } + if((e.pageY - this.targetBox.y) <= this.betweenThreshold){ + newDropPosition = "Before"; + }else if((e.pageY - this.targetBox.y) >= (this.targetBox.h - this.betweenThreshold)){ + newDropPosition = "After"; + } + } - // Create the tab list that will have a tab (a.k.a. tab button) for each tab panel - this.tablist = this._makeController(this.tablistNode); + if(newTarget != oldTarget || newDropPosition != oldDropPosition){ + if(oldTarget){ + this._removeItemClass(oldTarget.rowNode, oldDropPosition); + } + if(newTarget){ + this._addItemClass(newTarget.rowNode, newDropPosition); + } - if(!this.doLayout){ dojo.addClass(this.domNode, "dijitTabContainerNoLayout"); } + // Check if it's ok to drop the dragged node on/before/after the target node. + if(!newTarget){ + m.canDrop(false); + }else if(newTarget == this.tree.rootNode && newDropPosition != "Over"){ + // Can't drop before or after tree's root node; the dropped node would just disappear (at least visually) + m.canDrop(false); + }else{ + // Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140) + var model = this.tree.model, + sameId = false; + if(m.source == this){ + for(var dragId in this.selection){ + var dragNode = this.selection[dragId]; + if(dragNode.item === newTarget.item){ + sameId = true; + break; + } + } + } + if(sameId){ + m.canDrop(false); + }else if(this.checkItemAcceptance(newTarget.rowNode, m.source, newDropPosition.toLowerCase()) + && !this._isParentChildDrop(m.source, newTarget.rowNode)){ + m.canDrop(true); + }else{ + m.canDrop(false); + } + } - if(this.nested){ - /* workaround IE's lack of support for "a > b" selectors by - * tagging each node in the template. - */ - dojo.addClass(this.domNode, "dijitTabContainerNested"); - dojo.addClass(this.tablist.containerNode, "dijitTabContainerTabListNested"); - dojo.addClass(this.tablistSpacer, "dijitTabContainerSpacerNested"); - dojo.addClass(this.containerNode, "dijitTabPaneWrapperNested"); - }else{ - dojo.addClass(this.domNode, "tabStrip-" + (this.tabStrip ? "enabled" : "disabled")); + this.targetAnchor = newTarget; + this.dropPosition = newDropPosition; } }, - _setupChild: function(/*dijit._Widget*/ tab){ - // Overrides StackContainer._setupChild(). - dojo.addClass(tab.domNode, "dijitTabPane"); - this.inherited(arguments); - }, - - startup: function(){ - if(this._started){ return; } - - // wire up the tablist and its tabs - this.tablist.startup(); - + onMouseMove: function(e){ + // summary: + // Called for any onmousemove/ontouchmove events over the Tree + // e: Event + // onmousemouse/ontouchmove event + // tags: + // private + if(this.isDragging && this.targetState == "Disabled"){ return; } this.inherited(arguments); - }, - - layout: function(){ - // Overrides StackContainer.layout(). - // Configure the content pane to take up all the space except for where the tabs are - - if(!this._contentBox || typeof(this._contentBox.l) == "undefined"){return;} - - var sc = this.selectedChildWidget; - - if(this.doLayout){ - // position and size the titles and the container node - var titleAlign = this.tabPosition.replace(/-h/, ""); - this.tablist.layoutAlign = titleAlign; - var children = [this.tablist, { - domNode: this.tablistSpacer, - layoutAlign: titleAlign - }, { - domNode: this.containerNode, - layoutAlign: "client" - }]; - dijit.layout.layoutChildren(this.domNode, this._contentBox, children); - - // Compute size to make each of my children. - // children[2] is the margin-box size of this.containerNode, set by layoutChildren() call above - this._containerContentBox = dijit.layout.marginBox2contentBox(this.containerNode, children[2]); - - if(sc && sc.resize){ - sc.resize(this._containerContentBox); - } + var m = DNDManager.manager(); + if(this.isDragging){ + this._onDragMouse(e); }else{ - // just layout the tab controller, so it can position left/right buttons etc. - if(this.tablist.resize){ - //make the tabs zero width so that they don't interfere with width calc, then reset - var s = this.tablist.domNode.style; - s.width="0"; - var width = dojo.contentBox(this.domNode).w; - s.width=""; - this.tablist.resize({w: width}); - } - - // and call resize() on the selected pane just to tell it that it's been made visible - if(sc && sc.resize){ - sc.resize(); + if(this.mouseDown && this.isSource && + (Math.abs(e.pageX-this._lastX)>=this.dragThreshold || Math.abs(e.pageY-this._lastY)>=this.dragThreshold)){ + var nodes = this.getSelectedTreeNodes(); + if(nodes.length){ + if(nodes.length > 1){ + //filter out all selected items which has one of their ancestor selected as well + var seen = this.selection, i = 0, r = [], n, p; + nextitem: while((n = nodes[i++])){ + for(p = n.getParent(); p && p !== this.tree; p = p.getParent()){ + if(seen[p.id]){ //parent is already selected, skip this node + continue nextitem; + } + } + //this node does not have any ancestors selected, add it + r.push(n); + } + nodes = r; + } + nodes = array.map(nodes, function(n){return n.domNode}); + m.startDrag(this, nodes, this.copyState(connect.isCopyKey(e))); + } } } }, - destroy: function(){ - if(this.tablist){ - this.tablist.destroy(); - } + onMouseDown: function(e){ + // summary: + // Event processor for onmousedown/ontouchstart + // e: Event + // onmousedown/ontouchend event + // tags: + // private + this.mouseDown = true; + this.mouseButton = e.button; + this._lastX = e.pageX; + this._lastY = e.pageY; this.inherited(arguments); - } -}); - -} - -if(!dojo._hasResource["dijit.layout.TabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout.TabController"] = true; -dojo.provide("dijit.layout.TabController"); - - - - - - -// Menu is used for an accessible close button, would be nice to have a lighter-weight solution - - -dojo.declare("dijit.layout.TabController", - dijit.layout.StackController, -{ - // summary: - // Set of tabs (the things with titles and a close button, that you click to show a tab panel). - // Used internally by `dijit.layout.TabContainer`. - // description: - // Lets the user select the currently shown pane in a TabContainer or StackContainer. - // TabController also monitors the TabContainer, and whenever a pane is - // added or deleted updates itself accordingly. - // tags: - // private - - templateString: "<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>", - - // tabPosition: String - // Defines where tabs go relative to the content. - // "top", "bottom", "left-h", "right-h" - tabPosition: "top", - - // buttonWidget: String - // The name of the tab widget to create to correspond to each page - buttonWidget: "dijit.layout._TabButton", + }, - _rectifyRtlTabList: function(){ + onMouseUp: function(e){ // summary: - // 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 - - if(0 >= this.tabPosition.indexOf('-h')){ return; } - if(!this.pane2button){ return; } - - var maxWidth = 0; - for(var pane in this.pane2button){ - var ow = this.pane2button[pane].innerDiv.scrollWidth; - maxWidth = Math.max(maxWidth, ow); - } - //unify the length of all the tabs - for(pane in this.pane2button){ - this.pane2button[pane].innerDiv.style.width = maxWidth + 'px'; + // Event processor for onmouseup/ontouchend + // e: Event + // onmouseup/ontouchend event + // tags: + // private + if(this.mouseDown){ + this.mouseDown = false; + this.inherited(arguments); } - } -}); - -dojo.declare("dijit.layout._TabButton", - dijit.layout._StackButton, - { - // summary: - // A tab (the thing you click to select a pane). - // description: - // Contains the title of the pane, and optionally a close-button to destroy the pane. - // This is an internal widget and should not be instantiated directly. - // tags: - // private - - // baseClass: String - // The CSS class applied to the domNode. - baseClass: "dijitTab", - - // Apply dijitTabCloseButtonHover when close button is hovered - cssStateNodes: { - closeNode: "dijitTabCloseButton" }, - templateString: dojo.cache("dijit.layout", "templates/_TabButton.html", "<div role=\"presentation\" dojoAttachPoint=\"titleNode\" dojoAttachEvent='onclick:onClick'>\n <div role=\"presentation\" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'>\n <div role=\"presentation\" class='dijitTabContent' dojoAttachPoint='tabContent'>\n \t<div role=\"presentation\" dojoAttachPoint='focusNode'>\n\t\t <img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon dijitTabButtonIcon\" dojoAttachPoint='iconNode' />\n\t\t <span dojoAttachPoint='containerNode' class='tabLabel'></span>\n\t\t <span class=\"dijitInline dijitTabCloseButton dijitTabCloseIcon\" dojoAttachPoint='closeNode'\n\t\t \t\tdojoAttachEvent='onclick: onClickCloseButton' role=\"presentation\">\n\t\t <span dojoAttachPoint='closeText' class='dijitTabCloseText'>[x]</span\n\t\t ></span>\n\t\t\t</div>\n </div>\n </div>\n</div>\n"), - - // Override _FormWidget.scrollOnFocus. - // Don't scroll the whole tab container into view when the button is focused. - scrollOnFocus: false, - - buildRendering: function(){ + onMouseOut: function(){ + // summary: + // Event processor for when mouse is moved away from a TreeNode + // tags: + // private this.inherited(arguments); - - dojo.setSelectable(this.containerNode, false); + this._unmarkTargetAnchor(); }, - startup: function(){ - this.inherited(arguments); - var n = this.domNode; - - // Required to give IE6 a kick, as it initially hides the - // tabs until they are focused on. - setTimeout(function(){ - n.className = n.className; - }, 1); + checkItemAcceptance: function(/*===== target, source, position =====*/){ + // summary: + // Stub function to be overridden if one wants to check for the ability to drop at the node/item level + // description: + // In the base case, this is called to check if target can become a child of source. + // When betweenThreshold is set, position="before" or "after" means that we + // are asking if the source node can be dropped before/after the target node. + // target: DOMNode + // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to + // Use dijit.getEnclosingWidget(target) to get the TreeNode. + // source: dijit.tree.dndSource + // The (set of) nodes we are dropping + // position: String + // "over", "before", or "after" + // tags: + // extension + return true; }, - _setCloseButtonAttr: function(/*Boolean*/ disp){ + // topic event processors + onDndSourceOver: function(source){ // summary: - // Hide/show close button - this._set("closeButton", disp); - dojo.toggleClass(this.innerDiv, "dijitClosable", disp); - this.closeNode.style.display = disp ? "" : "none"; - if(disp){ - var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); - if(this.closeNode){ - dojo.attr(this.closeNode,"title", _nlsResources.itemClose); - } - // add context menu onto title button - var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); - this._closeMenu = new dijit.Menu({ - id: this.id+"_Menu", - dir: this.dir, - lang: this.lang, - targetNodeIds: [this.domNode] - }); - - this._closeMenu.addChild(new dijit.MenuItem({ - label: _nlsResources.itemClose, - dir: this.dir, - lang: this.lang, - onClick: dojo.hitch(this, "onClickCloseButton") - })); - }else{ - if(this._closeMenu){ - this._closeMenu.destroyRecursive(); - delete this._closeMenu; - } + // Topic event processor for /dnd/source/over, called when detected a current source. + // source: Object + // The dijit.tree.dndSource / dojo.dnd.Source which has the mouse over it + // tags: + // private + if(this != source){ + this.mouseDown = false; + this._unmarkTargetAnchor(); + }else if(this.isDragging){ + var m = DNDManager.manager(); + m.canDrop(false); } }, - _setLabelAttr: function(/*String*/ content){ + onDndStart: function(source, nodes, copy){ // summary: - // Hook for set('label', ...) to work. - // description: - // takes an HTML string. - // Inherited ToggleButton implementation will Set the label (text) of the button; - // Need to set the alt attribute of icon on tab buttons if no label displayed - this.inherited(arguments); - if(this.showLabel == false && !this.params.title){ - this.iconNode.alt = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || ''); - } - }, + // Topic event processor for /dnd/start, called to initiate the DnD operation + // source: Object + // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items + // nodes: DomNode[] + // The list of transferred items, dndTreeNode nodes if dragging from a Tree + // copy: Boolean + // Copy items, if true, move items otherwise + // tags: + // private - destroy: function(){ - if(this._closeMenu){ - this._closeMenu.destroyRecursive(); - delete this._closeMenu; + if(this.isSource){ + this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : ""); } - this.inherited(arguments); - } -}); - -} - -if(!dojo._hasResource["dijit.layout.ScrollingTabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout.ScrollingTabController"] = true; -dojo.provide("dijit.layout.ScrollingTabController"); - - - - - - -dojo.declare("dijit.layout.ScrollingTabController", - dijit.layout.TabController, - { - // summary: - // Set of tabs with left/right arrow keys and a menu to switch between tabs not - // all fitting on a single row. - // Works only for horizontal tabs (either above or below the content, not to the left - // or right). - // tags: - // private - - templateString: dojo.cache("dijit.layout", "templates/ScrollingTabController.html", "<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerMenuButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_menuBtn\" containerId=\"${containerId}\" iconClass=\"dijitTabStripMenuIcon\"\n\t\t\tdropDownPosition=\"below-alt, above-alt\"\n\t\t\tdojoAttachPoint=\"_menuBtn\" showLabel=\"false\">▼</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_leftBtn\" iconClass=\"dijitTabStripSlideLeftIcon\"\n\t\t\tdojoAttachPoint=\"_leftBtn\" dojoAttachEvent=\"onClick: doSlideLeft\" showLabel=\"false\">◀</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\"\n\t\t\tclass=\"tabStripButton-${tabPosition}\"\n\t\t\tid=\"${id}_rightBtn\" iconClass=\"dijitTabStripSlideRightIcon\"\n\t\t\tdojoAttachPoint=\"_rightBtn\" dojoAttachEvent=\"onClick: doSlideRight\" showLabel=\"false\">▶</div>\n\t<div class='dijitTabListWrapper' dojoAttachPoint='tablistWrapper'>\n\t\t<div role='tablist' dojoAttachEvent='onkeypress:onkeypress'\n\t\t\t\tdojoAttachPoint='containerNode' class='nowrapTabStrip'></div>\n\t</div>\n</div>\n"), - - // useMenu: [const] Boolean - // True if a menu should be used to select tabs when they are too - // wide to fit the TabContainer, false otherwise. - useMenu: true, - - // useSlider: [const] Boolean - // True if a slider should be used to select tabs when they are too - // wide to fit the TabContainer, false otherwise. - useSlider: true, - - // tabStripClass: [const] String - // The css class to apply to the tab strip, if it is visible. - tabStripClass: "", - - widgetsInTemplate: true, - - // _minScroll: Number - // The distance in pixels from the edge of the tab strip which, - // if a scroll animation is less than, forces the scroll to - // go all the way to the left/right. - _minScroll: 5, - - attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { - "class": "containerNode" - }), - - buildRendering: function(){ - this.inherited(arguments); - var n = this.domNode; + var accepted = this.checkAcceptance(source, nodes); - this.scrollNode = this.tablistWrapper; - this._initButtons(); + this._changeState("Target", accepted ? "" : "Disabled"); - if(!this.tabStripClass){ - this.tabStripClass = "dijitTabContainer" + - this.tabPosition.charAt(0).toUpperCase() + - this.tabPosition.substr(1).replace(/-.*/, "") + - "None"; - dojo.addClass(n, "tabStrip-disabled") + if(this == source){ + DNDManager.manager().overSource(this); } - dojo.addClass(this.tablistWrapper, this.tabStripClass); - }, - - onStartup: function(){ - this.inherited(arguments); - - // Do not show the TabController until the related - // StackController has added it's children. This gives - // a less visually jumpy instantiation. - dojo.style(this.domNode, "visibility", "visible"); - this._postStartup = true; + this.isDragging = true; }, - onAddChild: function(page, insertIndex){ - this.inherited(arguments); - - // changes to the tab button label or iconClass will have changed the width of the - // buttons, so do a resize - dojo.forEach(["label", "iconClass"], function(attr){ - this.pane2watches[page.id].push( - this.pane2button[page.id].watch(attr, dojo.hitch(this, function(name, oldValue, newValue){ - if(this._postStartup && this._dim){ - this.resize(this._dim); - } - })) - ); - }, this); - - // Increment the width of the wrapper when a tab is added - // This makes sure that the buttons never wrap. - // The value 200 is chosen as it should be bigger than most - // Tab button widths. - dojo.style(this.containerNode, "width", - (dojo.style(this.containerNode, "width") + 200) + "px"); - }, + itemCreator: function(nodes /*===== , target, source =====*/){ + // summary: + // Returns objects passed to `Tree.model.newItem()` based on DnD nodes + // dropped onto the tree. Developer must override this method to enable + // dropping from external sources onto this Tree, unless the Tree.model's items + // happen to look like {id: 123, name: "Apple" } with no other attributes. + // description: + // For each node in nodes[], which came from source, create a hash of name/value + // pairs to be passed to Tree.model.newItem(). Returns array of those hashes. + // nodes: DomNode[] + // target: DomNode + // source: dojo.dnd.Source + // returns: Object[] + // Array of name/value hashes for each new item to be added to the Tree, like: + // | [ + // | { id: 123, label: "apple", foo: "bar" }, + // | { id: 456, label: "pear", zaz: "bam" } + // | ] + // tags: + // extension - onRemoveChild: function(page, insertIndex){ - // null out _selectedTab because we are about to delete that dom node - var button = this.pane2button[page.id]; - if(this._selectedTab === button.domNode){ - this._selectedTab = null; - } + // TODO: for 2.0 refactor so itemCreator() is called once per drag node, and + // make signature itemCreator(sourceItem, node, target) (or similar). - this.inherited(arguments); + return array.map(nodes, function(node){ + return { + "id": node.id, + "name": node.textContent || node.innerText || "" + }; + }); // Object[] }, - _initButtons: function(){ + onDndDrop: function(source, nodes, copy){ // summary: - // Creates the buttons used to scroll to view tabs that - // may not be visible if the TabContainer is too narrow. + // Topic event processor for /dnd/drop, called to finish the DnD operation. + // description: + // Updates data store items according to where node was dragged from and dropped + // to. The tree will then respond to those data store updates and redraw itself. + // source: Object + // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items + // nodes: DomNode[] + // The list of transferred items, dndTreeNode nodes if dragging from a Tree + // copy: Boolean + // Copy items, if true, move items otherwise + // tags: + // protected + if(this.containerState == "Over"){ + var tree = this.tree, + model = tree.model, + target = this.targetAnchor; - // Make a list of the buttons to display when the tab labels become - // wider than the TabContainer, and hide the other buttons. - // Also gets the total width of the displayed buttons. - this._btnWidth = 0; - this._buttons = dojo.query("> .tabStripButton", this.domNode).filter(function(btn){ - if((this.useMenu && btn == this._menuBtn.domNode) || - (this.useSlider && (btn == this._rightBtn.domNode || btn == this._leftBtn.domNode))){ - this._btnWidth += dojo._getMarginSize(btn).w; - return true; + this.isDragging = false; + + // Compute the new parent item + var newParentItem; + var insertIndex; + newParentItem = (target && target.item) || tree.item; + if(this.dropPosition == "Before" || this.dropPosition == "After"){ + // TODO: if there is no parent item then disallow the drop. + // Actually this should be checked during onMouseMove too, to make the drag icon red. + newParentItem = (target.getParent() && target.getParent().item) || tree.item; + // Compute the insert index for reordering + insertIndex = target.getIndexInParent(); + if(this.dropPosition == "After"){ + insertIndex = target.getIndexInParent() + 1; + } }else{ - dojo.style(btn, "display", "none"); - return false; + newParentItem = (target && target.item) || tree.item; } - }, this); - }, - _getTabsWidth: function(){ - var children = this.getChildren(); - if(children.length){ - var leftTab = children[this.isLeftToRight() ? 0 : children.length - 1].domNode, - rightTab = children[this.isLeftToRight() ? children.length - 1 : 0].domNode; - return rightTab.offsetLeft + dojo.style(rightTab, "width") - leftTab.offsetLeft; - }else{ - return 0; - } - }, - - _enableBtn: function(width){ - // summary: - // Determines if the tabs are wider than the width of the TabContainer, and - // thus that we need to display left/right/menu navigation buttons. - var tabsWidth = this._getTabsWidth(); - width = width || dojo.style(this.scrollNode, "width"); - return tabsWidth > 0 && width < tabsWidth; - }, - - resize: function(dim){ - // summary: - // Hides or displays the buttons used to scroll the tab list and launch the menu - // that selects tabs. + // If necessary, use this variable to hold array of hashes to pass to model.newItem() + // (one entry in the array for each dragged node). + var newItemsParams; - if(this.domNode.offsetWidth == 0){ - return; - } + array.forEach(nodes, function(node, idx){ + // dojo.dnd.Item representing the thing being dropped. + // Don't confuse the use of item here (meaning a DnD item) with the + // uses below where item means dojo.data item. + var sourceItem = source.getItem(node.id); - // Save the dimensions to be used when a child is renamed. - this._dim = dim; + // Information that's available if the source is another Tree + // (possibly but not necessarily this tree, possibly but not + // necessarily the same model as this Tree) + if(array.indexOf(sourceItem.type, "treeNode") != -1){ + var childTreeNode = sourceItem.data, + childItem = childTreeNode.item, + oldParentItem = childTreeNode.getParent().item; + } - // Set my height to be my natural height (tall enough for one row of tab labels), - // and my content-box width based on margin-box width specified in dim parameter. - // But first reset scrollNode.height in case it was set by layoutChildren() call - // in a previous run of this method. - this.scrollNode.style.height = "auto"; - this._contentBox = dijit.layout.marginBox2contentBox(this.domNode, {h: 0, w: dim.w}); - this._contentBox.h = this.scrollNode.offsetHeight; - dojo.contentBox(this.domNode, this._contentBox); + if(source == this){ + // This is a node from my own tree, and we are moving it, not copying. + // Remove item from old parent's children attribute. + // TODO: dijit.tree.dndSelector should implement deleteSelectedNodes() + // and this code should go there. - // Show/hide the left/right/menu navigation buttons depending on whether or not they - // are needed. - var enable = this._enableBtn(this._contentBox.w); - this._buttons.style("display", enable ? "" : "none"); + if(typeof insertIndex == "number"){ + if(newParentItem == oldParentItem && childTreeNode.getIndexInParent() < insertIndex){ + insertIndex -= 1; + } + } + model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex); + }else if(model.isItem(childItem)){ + // Item from same model + // (maybe we should only do this branch if the source is a tree?) + model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex); + }else{ + // Get the hash to pass to model.newItem(). A single call to + // itemCreator() returns an array of hashes, one for each drag source node. + if(!newItemsParams){ + newItemsParams = this.itemCreator(nodes, target.rowNode, source); + } - // Position and size the navigation buttons and the tablist - this._leftBtn.layoutAlign = "left"; - this._rightBtn.layoutAlign = "right"; - this._menuBtn.layoutAlign = this.isLeftToRight() ? "right" : "left"; - dijit.layout.layoutChildren(this.domNode, this._contentBox, - [this._menuBtn, this._leftBtn, this._rightBtn, {domNode: this.scrollNode, layoutAlign: "client"}]); + // Create new item in the tree, based on the drag source. + model.newItem(newItemsParams[idx], newParentItem, insertIndex); + } + }, this); - // set proper scroll so that selected tab is visible - if(this._selectedTab){ - if(this._anim && this._anim.status() == "playing"){ - this._anim.stop(); - } - var w = this.scrollNode, - sl = this._convertToScrollLeft(this._getScrollForSelectedTab()); - w.scrollLeft = sl; + // Expand the target node (if it's currently collapsed) so the user can see + // where their node was dropped. In particular since that node is still selected. + this.tree._expandNode(target); } - - // Enable/disabled left right buttons depending on whether or not user can scroll to left or right - this._setButtonClass(this._getScroll()); - - this._postResize = true; - - // Return my size so layoutChildren() can use it. - // Also avoids IE9 layout glitch on browser resize when scroll buttons present - return {h: this._contentBox.h, w: dim.w}; - }, - - _getScroll: function(){ - // summary: - // Returns the current scroll of the tabs where 0 means - // "scrolled all the way to the left" and some positive number, based on # - // of pixels of possible scroll (ex: 1000) means "scrolled all the way to the right" - var sl = (this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit) ? this.scrollNode.scrollLeft : - dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width") - + (dojo.isIE == 8 ? -1 : 1) * this.scrollNode.scrollLeft; - return sl; + this.onDndCancel(); }, - _convertToScrollLeft: function(val){ + onDndCancel: function(){ // summary: - // Given a scroll value where 0 means "scrolled all the way to the left" - // and some positive number, based on # of pixels of possible scroll (ex: 1000) - // means "scrolled all the way to the right", return value to set this.scrollNode.scrollLeft - // to achieve that scroll. - // - // This method is to adjust for RTL funniness in various browsers and versions. - if(this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit){ - return val; - }else{ - var maxScroll = dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width"); - return (dojo.isIE == 8 ? -1 : 1) * (val - maxScroll); - } + // Topic event processor for /dnd/cancel, called to cancel the DnD operation + // tags: + // private + this._unmarkTargetAnchor(); + this.isDragging = false; + this.mouseDown = false; + delete this.mouseButton; + this._changeState("Source", ""); + this._changeState("Target", ""); }, - onSelectChild: function(/*dijit._Widget*/ page){ + // When focus moves in/out of the entire Tree + onOverEvent: function(){ // summary: - // Smoothly scrolls to a tab when it is selected. - - var tab = this.pane2button[page.id]; - if(!tab || !page){return;} - - // Scroll to the selected tab, except on startup, when scrolling is handled in resize() - var node = tab.domNode; - if(this._postResize && node != this._selectedTab){ - this._selectedTab = node; - - var sl = this._getScroll(); - - if(sl > node.offsetLeft || - sl + dojo.style(this.scrollNode, "width") < - node.offsetLeft + dojo.style(node, "width")){ - this.createSmoothScroll().play(); - } - } - + // This method is called when mouse is moved over our container (like onmouseenter) + // tags: + // private this.inherited(arguments); + DNDManager.manager().overSource(this); }, - - _getScrollBounds: function(){ + onOutEvent: function(){ // summary: - // Returns the minimum and maximum scroll setting to show the leftmost and rightmost - // tabs (respectively) - var children = this.getChildren(), - scrollNodeWidth = dojo.style(this.scrollNode, "width"), // about 500px - containerWidth = dojo.style(this.containerNode, "width"), // 50,000px - maxPossibleScroll = containerWidth - scrollNodeWidth, // scrolling until right edge of containerNode visible - tabsWidth = this._getTabsWidth(); - - if(children.length && tabsWidth > scrollNodeWidth){ - // Scrolling should happen - return { - min: this.isLeftToRight() ? 0 : children[children.length-1].domNode.offsetLeft, - max: this.isLeftToRight() ? - (children[children.length-1].domNode.offsetLeft + dojo.style(children[children.length-1].domNode, "width")) - scrollNodeWidth : - maxPossibleScroll - }; - }else{ - // No scrolling needed, all tabs visible, we stay either scrolled to far left or far right (depending on dir) - var onlyScrollPosition = this.isLeftToRight() ? 0 : maxPossibleScroll; - return { - min: onlyScrollPosition, - max: onlyScrollPosition - }; + // This method is called when mouse is moved out of our container (like onmouseleave) + // tags: + // private + this._unmarkTargetAnchor(); + var m = DNDManager.manager(); + if(this.isDragging){ + m.canDrop(false); } - }, - - _getScrollForSelectedTab: function(){ - // summary: - // Returns the scroll value setting so that the selected tab - // will appear in the center - var w = this.scrollNode, - n = this._selectedTab, - scrollNodeWidth = dojo.style(this.scrollNode, "width"), - scrollBounds = this._getScrollBounds(); - - // TODO: scroll minimal amount (to either right or left) so that - // selected tab is fully visible, and just return if it's already visible? - var pos = (n.offsetLeft + dojo.style(n, "width")/2) - scrollNodeWidth/2; - pos = Math.min(Math.max(pos, scrollBounds.min), scrollBounds.max); + m.outSource(this); - // TODO: - // If scrolling close to the left side or right side, scroll - // all the way to the left or right. See this._minScroll. - // (But need to make sure that doesn't scroll the tab out of view...) - return pos; + this.inherited(arguments); }, - createSmoothScroll: function(x){ + _isParentChildDrop: function(source, targetRow){ // summary: - // Creates a dojo._Animation object that smoothly scrolls the tab list - // either to a fixed horizontal pixel value, or to the selected tab. - // description: - // If an number argument is passed to the function, that horizontal - // pixel position is scrolled to. Otherwise the currently selected - // tab is scrolled to. - // x: Integer? - // An optional pixel value to scroll to, indicating distance from left. - - // Calculate position to scroll to - if(arguments.length > 0){ - // position specified by caller, just make sure it's within bounds - var scrollBounds = this._getScrollBounds(); - x = Math.min(Math.max(x, scrollBounds.min), scrollBounds.max); - }else{ - // scroll to center the current tab - x = this._getScrollForSelectedTab(); - } + // Checks whether the dragged items are parent rows in the tree which are being + // dragged into their own children. + // + // source: + // The DragSource object. + // + // targetRow: + // The tree row onto which the dragged nodes are being dropped. + // + // tags: + // private - if(this._anim && this._anim.status() == "playing"){ - this._anim.stop(); + // If the dragged object is not coming from the tree this widget belongs to, + // it cannot be invalid. + if(!source.tree || source.tree != this.tree){ + return false; } - var self = this, - w = this.scrollNode, - anim = new dojo._Animation({ - beforeBegin: function(){ - if(this.curve){ delete this.curve; } - var oldS = w.scrollLeft, - newS = self._convertToScrollLeft(x); - anim.curve = new dojo._Line(oldS, newS); - }, - onAnimate: function(val){ - w.scrollLeft = val; - } - }); - this._anim = anim; - // Disable/enable left/right buttons according to new scroll position - this._setButtonClass(x); + var root = source.tree.domNode; + var ids = source.selection; - return anim; // dojo._Animation - }, + var node = targetRow.parentNode; - _getBtnNode: function(/*Event*/ e){ - // summary: - // Gets a button DOM node from a mouse click event. - // e: - // The mouse click event. - var n = e.target; - while(n && !dojo.hasClass(n, "tabStripButton")){ - n = n.parentNode; + // Iterate up the DOM hierarchy from the target drop row, + // checking of any of the dragged nodes have the same ID. + while(node != root && !ids[node.id]){ + node = node.parentNode; } - return n; - }, - - doSlideRight: function(/*Event*/ e){ - // summary: - // Scrolls the menu to the right. - // e: - // The mouse click event. - this.doSlide(1, this._getBtnNode(e)); - }, - doSlideLeft: function(/*Event*/ e){ - // summary: - // Scrolls the menu to the left. - // e: - // The mouse click event. - this.doSlide(-1,this._getBtnNode(e)); + return node.id && ids[node.id]; }, - doSlide: function(/*Number*/ direction, /*DomNode*/ node){ + _unmarkTargetAnchor: function(){ // summary: - // Scrolls the tab list to the left or right by 75% of the widget width. - // direction: - // If the direction is 1, the widget scrolls to the right, if it is - // -1, it scrolls to the left. - - if(node && dojo.hasClass(node, "dijitTabDisabled")){return;} - - var sWidth = dojo.style(this.scrollNode, "width"); - var d = (sWidth * 0.75) * direction; - - var to = this._getScroll() + d; - - this._setButtonClass(to); - - this.createSmoothScroll(to).play(); + // Removes hover class of the current target anchor + // tags: + // private + if(!this.targetAnchor){ return; } + this._removeItemClass(this.targetAnchor.rowNode, this.dropPosition); + this.targetAnchor = null; + this.targetBox = null; + this.dropPosition = null; }, - _setButtonClass: function(/*Number*/ scroll){ + _markDndStatus: function(copy){ // summary: - // Disables the left scroll button if the tabs are scrolled all the way to the left, - // or the right scroll button in the opposite case. - // scroll: Integer - // amount of horizontal scroll - - var scrollBounds = this._getScrollBounds(); - this._leftBtn.set("disabled", scroll <= scrollBounds.min); - this._rightBtn.set("disabled", scroll >= scrollBounds.max); + // Changes source's state based on "copy" status + this._changeState("Source", copy ? "Copied" : "Moved"); } }); - -dojo.declare("dijit.layout._ScrollingTabControllerButtonMixin", null, { - baseClass: "dijitTab tabStripButton", - - templateString: dojo.cache("dijit.layout", "templates/_ScrollingTabControllerButton.html", "<div dojoAttachEvent=\"onclick:_onButtonClick\">\n\t<div role=\"presentation\" class=\"dijitTabInnerDiv\" dojoattachpoint=\"innerDiv,focusNode\">\n\t\t<div role=\"presentation\" class=\"dijitTabContent dijitButtonContents\" dojoattachpoint=\"tabContent\">\n\t\t\t<img role=\"presentation\" alt=\"\" src=\"${_blankGif}\" class=\"dijitTabStripIcon\" dojoAttachPoint=\"iconNode\"/>\n\t\t\t<span dojoAttachPoint=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>\n"), - - // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be - // able to tab to the left/right/menu buttons - tabIndex: "", - - // Similarly, override FormWidget.isFocusable() because clicking a button shouldn't focus it - // either (this override avoids focus() call in FormWidget.js) - isFocusable: function(){ return false; } }); -dojo.declare("dijit.layout._ScrollingTabControllerButton", - [dijit.form.Button, dijit.layout._ScrollingTabControllerButtonMixin]); - -dojo.declare( - "dijit.layout._ScrollingTabControllerMenuButton", - [dijit.form.Button, dijit._HasDropDown, dijit.layout._ScrollingTabControllerButtonMixin], -{ - // id of the TabContainer itself - containerId: "", +}, +'dijit/a11y':function(){ +define("dijit/a11y", [ + "dojo/_base/array", // array.forEach array.map + "dojo/_base/config", // defaultDuration + "dojo/_base/declare", // declare + "dojo/dom", // dom.byId + "dojo/dom-attr", // domAttr.attr domAttr.has + "dojo/dom-style", // style.style + "dojo/_base/sniff", // has("ie") + "./_base/manager", // manager._isElementShown + "." // for exporting methods to dijit namespace +], function(array, config, declare, dom, domAttr, domStyle, has, manager, dijit){ + + // module: + // dijit/a11y + // summary: + // Accessibility utility functions (keyboard, tab stops, etc.) - // -1 so user can't tab into the button, but so that button can still be focused programatically. - // Because need to move focus to the button (or somewhere) before the menu is hidden or IE6 will crash. - tabIndex: "-1", + var shown = (dijit._isElementShown = function(/*Element*/ elem){ + var s = domStyle.get(elem); + return (s.visibility != "hidden") + && (s.visibility != "collapsed") + && (s.display != "none") + && (domAttr.get(elem, "type") != "hidden"); + }); - isLoaded: function(){ - // recreate menu every time, in case the TabContainer's list of children (or their icons/labels) have changed - return false; - }, + dijit.hasDefaultTabStop = function(/*Element*/ elem){ + // summary: + // Tests if element is tab-navigable even without an explicit tabIndex setting - loadDropDown: function(callback){ - this.dropDown = new dijit.Menu({ - id: this.containerId + "_menu", - dir: this.dir, - lang: this.lang - }); - var container = dijit.byId(this.containerId); - dojo.forEach(container.getChildren(), function(page){ - var menuItem = new dijit.MenuItem({ - id: page.id + "_stcMi", - label: page.title, - iconClass: page.iconClass, - dir: page.dir, - lang: page.lang, - onClick: function(){ - container.selectChild(page); + // No explicit tabIndex setting, need to investigate node type + switch(elem.nodeName.toLowerCase()){ + case "a": + // An <a> w/out a tabindex is only navigable if it has an href + return domAttr.has(elem, "href"); + case "area": + case "button": + case "input": + case "object": + case "select": + case "textarea": + // These are navigable by default + return true; + case "iframe": + // If it's an editor <iframe> then it's tab navigable. + var body; + try{ + // non-IE + var contentDocument = elem.contentDocument; + if("designMode" in contentDocument && contentDocument.designMode == "on"){ + return true; + } + body = contentDocument.body; + }catch(e1){ + // contentWindow.document isn't accessible within IE7/8 + // if the iframe.src points to a foreign url and this + // page contains an element, that could get focus + try{ + body = elem.contentWindow.document.body; + }catch(e2){ + return false; + } } - }); - this.dropDown.addChild(menuItem); - }, this); - callback(); - }, - - closeDropDown: function(/*Boolean*/ focus){ - this.inherited(arguments); - if(this.dropDown){ - this.dropDown.destroyRecursive(); - delete this.dropDown; + return body && (body.contentEditable == 'true' || + (body.firstChild && body.firstChild.contentEditable == 'true')); + default: + return elem.contentEditable == 'true'; } - } -}); - -} - -if(!dojo._hasResource["dijit.layout.TabContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.layout.TabContainer"] = true; -dojo.provide("dijit.layout.TabContainer"); - - + }; + var isTabNavigable = (dijit.isTabNavigable = function(/*Element*/ elem){ + // summary: + // Tests if an element is tab-navigable + // TODO: convert (and rename method) to return effective tabIndex; will save time in _getTabNavigable() + if(domAttr.get(elem, "disabled")){ + return false; + }else if(domAttr.has(elem, "tabIndex")){ + // Explicit tab index setting + return domAttr.get(elem, "tabIndex") >= 0; // boolean + }else{ + // No explicit tabIndex setting, so depends on node type + return dijit.hasDefaultTabStop(elem); + } + }); -dojo.declare("dijit.layout.TabContainer", - dijit.layout._TabContainerBase, - { + dijit._getTabNavigable = function(/*DOMNode*/ root){ // summary: - // A Container with tabs to select each child (only one of which is displayed at a time). + // Finds descendants of the specified root node. + // // description: - // A TabContainer is a container that has multiple panes, but shows only - // one pane at a time. There are a set of tabs corresponding to each pane, - // where each tab has the name (aka title) of the pane, and optionally a close button. - - // useMenu: [const] Boolean - // True if a menu should be used to select tabs when they are too - // wide to fit the TabContainer, false otherwise. - useMenu: true, - - // useSlider: [const] Boolean - // True if a slider should be used to select tabs when they are too - // wide to fit the TabContainer, false otherwise. - useSlider: true, - - // controllerWidget: String - // An optional parameter to override the widget used to display the tab labels - controllerWidget: "", - - _makeController: function(/*DomNode*/ srcNode){ - // summary: - // Instantiate tablist controller widget and return reference to it. - // Callback from _TabContainerBase.postCreate(). - // tags: - // protected extension - - var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"), - TabController = dojo.getObject(this.controllerWidget); + // Finds the following descendants of the specified root node: + // * the first tab-navigable element in document order + // without a tabIndex or with tabIndex="0" + // * the last tab-navigable element in document order + // without a tabIndex or with tabIndex="0" + // * the first element in document order with the lowest + // positive tabIndex value + // * the last element in document order with the highest + // positive tabIndex value + var first, last, lowest, lowestTabindex, highest, highestTabindex, radioSelected = {}; - return new TabController({ - id: this.id + "_tablist", - dir: this.dir, - lang: this.lang, - tabPosition: this.tabPosition, - doLayout: this.doLayout, - containerId: this.id, - "class": cls, - nested: this.nested, - useMenu: this.useMenu, - useSlider: this.useSlider, - tabStripClass: this.tabStrip ? this.baseClass + (this.tabStrip ? "":"No") + "Strip": null - }, srcNode); - }, + function radioName(node){ + // If this element is part of a radio button group, return the name for that group. + return node && node.tagName.toLowerCase() == "input" && + node.type && node.type.toLowerCase() == "radio" && + node.name && node.name.toLowerCase(); + } - postMixInProperties: function(){ - this.inherited(arguments); + var walkTree = function(/*DOMNode*/parent){ + for(var child = parent.firstChild; child; child = child.nextSibling){ + // Skip text elements, hidden elements, and also non-HTML elements (those in custom namespaces) in IE, + // since show() invokes getAttribute("type"), which crash on VML nodes in IE. + if(child.nodeType != 1 || (has("ie") && child.scopeName !== "HTML") || !shown(child)){ + continue; + } - // Scrolling controller only works for horizontal non-nested tabs - if(!this.controllerWidget){ - this.controllerWidget = (this.tabPosition == "top" || this.tabPosition == "bottom") && !this.nested ? - "dijit.layout.ScrollingTabController" : "dijit.layout.TabController"; + if(isTabNavigable(child)){ + var tabindex = domAttr.get(child, "tabIndex"); + if(!domAttr.has(child, "tabIndex") || tabindex == 0){ + if(!first){ + first = child; + } + last = child; + }else if(tabindex > 0){ + if(!lowest || tabindex < lowestTabindex){ + lowestTabindex = tabindex; + lowest = child; + } + if(!highest || tabindex >= highestTabindex){ + highestTabindex = tabindex; + highest = child; + } + } + var rn = radioName(child); + if(domAttr.get(child, "checked") && rn){ + radioSelected[rn] = child; + } + } + if(child.nodeName.toUpperCase() != 'SELECT'){ + walkTree(child); + } } + }; + if(shown(root)){ + walkTree(root); + } + function rs(node){ + // substitute checked radio button for unchecked one, if there is a checked one with the same name. + return radioSelected[radioName(node)] || node; } -}); - -} - -if(!dojo._hasResource["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.number"] = true; -dojo.provide("dojo.number"); - - - - - -dojo.getObject("number", true, dojo); - -/*===== -dojo.number = { - // summary: localized formatting and parsing routines for Number -} -dojo.number.__FormatOptions = function(){ - // pattern: String? - // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) - // with this string. Default value is based on locale. Overriding this property will defeat - // localization. Literal characters in patterns are not supported. - // type: String? - // choose a format type based on the locale from the following: - // decimal, scientific (not yet supported), percent, currency. decimal by default. - // places: Number? - // fixed number of decimal places to show. This overrides any - // information in the provided pattern. - // round: Number? - // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1 - // means do not round. - // locale: String? - // override the locale used to determine formatting rules - // fractional: Boolean? - // If false, show no decimal places, overriding places and pattern settings. - this.pattern = pattern; - this.type = type; - this.places = places; - this.round = round; - this.locale = locale; - this.fractional = fractional; -} -=====*/ + return { first: rs(first), last: rs(last), lowest: rs(lowest), highest: rs(highest) }; + }; + dijit.getFirstInTabbingOrder = function(/*String|DOMNode*/ root){ + // summary: + // Finds the descendant of the specified root node + // that is first in the tabbing order + var elems = dijit._getTabNavigable(dom.byId(root)); + return elems.lowest ? elems.lowest : elems.first; // DomNode + }; -dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){ - // summary: - // Format a Number as a String, using locale-specific settings - // description: - // Create a string from a Number using a known localized pattern. - // Formatting patterns appropriate to the locale are chosen from the - // [Common Locale Data Repository](http://unicode.org/cldr) as well as the appropriate symbols and - // delimiters. - // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null. - // value: - // the number to be formatted + dijit.getLastInTabbingOrder = function(/*String|DOMNode*/ root){ + // summary: + // Finds the descendant of the specified root node + // that is last in the tabbing order + var elems = dijit._getTabNavigable(dom.byId(root)); + return elems.last ? elems.last : elems.highest; // DomNode + }; - options = dojo.mixin({}, options || {}); - var locale = dojo.i18n.normalizeLocale(options.locale), - bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale); - options.customs = bundle; - var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"]; - if(isNaN(value) || Math.abs(value) == Infinity){ return null; } // null - return dojo.number._applyPattern(value, pattern, options); // String -}; + return { + hasDefaultTabStop: dijit.hasDefaultTabStop, + isTabNavigable: dijit.isTabNavigable, + _getTabNavigable: dijit._getTabNavigable, + getFirstInTabbingOrder: dijit.getFirstInTabbingOrder, + getLastInTabbingOrder: dijit.getLastInTabbingOrder + }; +}); -//dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough -dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough +}, +'dijit/form/_ToggleButtonMixin':function(){ +define("dijit/form/_ToggleButtonMixin", [ + "dojo/_base/declare", // declare + "dojo/dom-attr" // domAttr.set +], function(declare, domAttr){ + +// module: +// dijit/form/_ToggleButtonMixin +// summary: +// A mixin to provide functionality to allow a button that can be in two states (checked or not). -dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){ +return declare("dijit.form._ToggleButtonMixin", null, { // summary: - // Apply pattern to format value as a string using options. Gives no - // consideration to local customs. - // value: - // the number to be formatted. - // pattern: - // a pattern string as described by - // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) - // options: dojo.number.__FormatOptions? - // _applyPattern is usually called via `dojo.number.format()` which - // populates an extra property in the options parameter, "customs". - // The customs object specifies group and decimal parameters if set. + // A mixin to provide functionality to allow a button that can be in two states (checked or not). - //TODO: support escapes - options = options || {}; - var group = options.customs.group, - decimal = options.customs.decimal, - patternList = pattern.split(';'), - positivePattern = patternList[0]; - pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern); - - //TODO: only test against unescaped - if(pattern.indexOf('%') != -1){ - value *= 100; - }else if(pattern.indexOf('\u2030') != -1){ - value *= 1000; // per mille - }else if(pattern.indexOf('\u00a4') != -1){ - group = options.customs.currencyGroup || group;//mixins instead? - decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead? - pattern = pattern.replace(/\u00a4{1,3}/, function(match){ - var prop = ["symbol", "currency", "displayName"][match.length-1]; - return options[prop] || options.currency || ""; - }); - }else if(pattern.indexOf('E') != -1){ - throw new Error("exponential notation not supported"); - } - - //TODO: support @ sig figs? - var numberPatternRE = dojo.number._numberPatternRE; - var numberPattern = positivePattern.match(numberPatternRE); - if(!numberPattern){ - throw new Error("unable to find a number expression in pattern: "+pattern); - } - if(options.fractional === false){ options.places = 0; } - return pattern.replace(numberPatternRE, - dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round})); -}; - -dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){ - // summary: - // Rounds to the nearest value with the given number of decimal places, away from zero - // description: - // Rounds to the nearest value with the given number of decimal places, away from zero if equal. - // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by - // fractional increments also, such as the nearest quarter. - // NOTE: Subject to floating point errors. See dojox.math.round for experimental workaround. - // value: - // The number to round - // places: - // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding. - // Must be non-negative. - // increment: - // Rounds next place to nearest value of increment/10. 10 by default. - // example: - // >>> dojo.number.round(-0.5) - // -1 - // >>> dojo.number.round(162.295, 2) - // 162.29 // note floating point error. Should be 162.3 - // >>> dojo.number.round(10.71, 0, 2.5) - // 10.75 - var factor = 10 / (increment || 10); - return (factor * +value).toFixed(places) / factor; // Number -}; - -if((0.9).toFixed() == 0){ - // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit - // is just after the rounding place and is >=5 - (function(){ - var round = dojo.number.round; - dojo.number.round = function(v, p, m){ - var d = Math.pow(10, -p || 0), a = Math.abs(v); - if(!v || a >= d || a * Math.pow(10, p + 1) < 5){ - d = 0; - } - return round(v, p, m) + (v > 0 ? d : -d); - }; - })(); -} - -/*===== -dojo.number.__FormatAbsoluteOptions = function(){ - // decimal: String? - // the decimal separator - // group: String? - // the group separator - // places: Number?|String? - // number of decimal places. the range "n,m" will format to m places. - // round: Number? - // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1 - // means don't round. - this.decimal = decimal; - this.group = group; - this.places = places; - this.round = round; -} -=====*/ + // checked: Boolean + // Corresponds to the native HTML <input> element's attribute. + // In markup, specified as "checked='checked'" or just "checked". + // True if the button is depressed, or the checkbox is checked, + // or the radio button is selected, etc. + checked: false, -dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){ - // summary: - // Apply numeric pattern to absolute value using options. Gives no - // consideration to local customs. - // value: - // the number to be formatted, ignores sign - // pattern: - // the number portion of a pattern (e.g. `#,##0.00`) - options = options || {}; - if(options.places === true){options.places=0;} - if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit + // aria-pressed for toggle buttons, and aria-checked for checkboxes + _aria_attr: "aria-pressed", - var patternParts = pattern.split("."), - comma = typeof options.places == "string" && options.places.indexOf(","), - maxPlaces = options.places; - if(comma){ - maxPlaces = options.places.substring(comma + 1); - }else if(!(maxPlaces >= 0)){ - maxPlaces = (patternParts[1] || []).length; - } - if(!(options.round < 0)){ - value = dojo.number.round(value, maxPlaces, options.round); - } + _onClick: function(/*Event*/ evt){ + var original = this.checked; + this._set('checked', !original); // partially set the toggled value, assuming the toggle will work, so it can be overridden in the onclick handler + var ret = this.inherited(arguments); // the user could reset the value here + this.set('checked', ret ? this.checked : original); // officially set the toggled or user value, or reset it back + return ret; + }, - var valueParts = String(Math.abs(value)).split("."), - fractional = valueParts[1] || ""; - if(patternParts[1] || options.places){ - if(comma){ - options.places = options.places.substring(0, comma); - } - // Pad fractional with trailing zeros - var pad = options.places !== undefined ? options.places : (patternParts[1] && patternParts[1].lastIndexOf("0") + 1); - if(pad > fractional.length){ - valueParts[1] = dojo.string.pad(fractional, pad, '0', true); - } + _setCheckedAttr: function(/*Boolean*/ value, /*Boolean?*/ priorityChange){ + this._set("checked", value); + domAttr.set(this.focusNode || this.domNode, "checked", value); + (this.focusNode || this.domNode).setAttribute(this._aria_attr, value ? "true" : "false"); // aria values should be strings + this._handleOnChange(value, priorityChange); + }, - // Truncate fractional - if(maxPlaces < fractional.length){ - valueParts[1] = fractional.substr(0, maxPlaces); - } - }else{ - if(valueParts[1]){ valueParts.pop(); } - } + reset: function(){ + // summary: + // Reset the widget's value to what it was at initialization time - // Pad whole with leading zeros - var patternDigits = patternParts[0].replace(',', ''); - pad = patternDigits.indexOf("0"); - if(pad != -1){ - pad = patternDigits.length - pad; - if(pad > valueParts[0].length){ - valueParts[0] = dojo.string.pad(valueParts[0], pad); - } + this._hasBeenBlurred = false; - // Truncate whole - if(patternDigits.indexOf("#") == -1){ - valueParts[0] = valueParts[0].substr(valueParts[0].length - pad); - } + // set checked state to original setting + this.set('checked', this.params.checked || false); } +}); - // Add group separators - var index = patternParts[0].lastIndexOf(','), - groupSize, groupSize2; - if(index != -1){ - groupSize = patternParts[0].length - index - 1; - var remainder = patternParts[0].substr(0, index); - index = remainder.lastIndexOf(','); - if(index != -1){ - groupSize2 = remainder.length - index - 1; - } - } - var pieces = []; - for(var whole = valueParts[0]; whole;){ - var off = whole.length - groupSize; - pieces.push((off > 0) ? whole.substr(off) : whole); - whole = (off > 0) ? whole.slice(0, off) : ""; - if(groupSize2){ - groupSize = groupSize2; - delete groupSize2; - } - } - valueParts[0] = pieces.reverse().join(options.group || ","); +}); - return valueParts.join(options.decimal || "."); -}; +}, +'dijit/_Widget':function(){ +define("dijit/_Widget", [ + "dojo/aspect", // aspect.around + "dojo/_base/config", // config.isDebug + "dojo/_base/connect", // connect.connect + "dojo/_base/declare", // declare + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/lang", // lang.hitch + "dojo/query", + "dojo/ready", + "./registry", // registry.byNode + "./_WidgetBase", + "./_OnDijitClickMixin", + "./_FocusMixin", + "dojo/uacss", // browser sniffing (included for back-compat; subclasses may be using) + "./hccss" // high contrast mode sniffing (included to set CSS classes on <body>, module ret value unused) +], function(aspect, config, connect, declare, kernel, lang, query, ready, + registry, _WidgetBase, _OnDijitClickMixin, _FocusMixin){ /*===== -dojo.number.__RegexpOptions = function(){ - // pattern: String? - // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) - // with this string. Default value is based on locale. Overriding this property will defeat - // localization. - // type: String? - // choose a format type based on the locale from the following: - // decimal, scientific (not yet supported), percent, currency. decimal by default. - // locale: String? - // override the locale used to determine formatting rules - // strict: Boolean? - // strict parsing, false by default. Strict parsing requires input as produced by the format() method. - // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators - // places: Number|String? - // number of decimal places to accept: Infinity, a positive number, or - // a range "n,m". Defined by pattern or Infinity if pattern not provided. - this.pattern = pattern; - this.type = type; - this.locale = locale; - this.strict = strict; - this.places = places; -} + var _WidgetBase = dijit._WidgetBase; + var _OnDijitClickMixin = dijit._OnDijitClickMixin; + var _FocusMixin = dijit._FocusMixin; =====*/ -dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){ - // summary: - // Builds the regular needed to parse a number - // description: - // Returns regular expression with positive and negative match, group - // and decimal separators - return dojo.number._parseInfo(options).regexp; // String -}; - -dojo.number._parseInfo = function(/*Object?*/options){ - options = options || {}; - var locale = dojo.i18n.normalizeLocale(options.locale), - bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale), - pattern = options.pattern || bundle[(options.type || "decimal") + "Format"], -//TODO: memoize? - group = bundle.group, - decimal = bundle.decimal, - factor = 1; - - if(pattern.indexOf('%') != -1){ - factor /= 100; - }else if(pattern.indexOf('\u2030') != -1){ - factor /= 1000; // per mille - }else{ - var isCurrency = pattern.indexOf('\u00a4') != -1; - if(isCurrency){ - group = bundle.currencyGroup || group; - decimal = bundle.currencyDecimal || decimal; - } - } - - //TODO: handle quoted escapes - var patternList = pattern.split(';'); - if(patternList.length == 1){ - patternList.push("-" + patternList[0]); - } - - var re = dojo.regexp.buildGroupRE(patternList, function(pattern){ - pattern = "(?:"+dojo.regexp.escapeString(pattern, '.')+")"; - return pattern.replace(dojo.number._numberPatternRE, function(format){ - var flags = { - signed: false, - separator: options.strict ? group : [group,""], - fractional: options.fractional, - decimal: decimal, - exponent: false - }, - - parts = format.split('.'), - places = options.places; - - // special condition for percent (factor != 1) - // allow decimal places even if not specified in pattern - if(parts.length == 1 && factor != 1){ - parts[1] = "###"; - } - if(parts.length == 1 || places === 0){ - flags.fractional = false; - }else{ - if(places === undefined){ places = options.pattern ? parts[1].lastIndexOf('0') + 1 : Infinity; } - if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified - if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; } - flags.places = places; - } - var groups = parts[0].split(','); - if(groups.length > 1){ - flags.groupSize = groups.pop().length; - if(groups.length > 1){ - flags.groupSize2 = groups.pop().length; - } - } - return "("+dojo.number._realNumberRegexp(flags)+")"; - }); - }, true); - if(isCurrency){ - // substitute the currency symbol for the placeholder in the pattern - re = re.replace(/([\s\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match, before, target, after){ - var prop = ["symbol", "currency", "displayName"][target.length-1], - symbol = dojo.regexp.escapeString(options[prop] || options.currency || ""); - before = before ? "[\\s\\xa0]" : ""; - after = after ? "[\\s\\xa0]" : ""; - if(!options.strict){ - if(before){before += "*";} - if(after){after += "*";} - return "(?:"+before+symbol+after+")?"; - } - return before+symbol+after; - }); - } -//TODO: substitute localized sign/percent/permille/etc.? +// module: +// dijit/_Widget +// summary: +// Old base for widgets. New widgets should extend _WidgetBase instead - // normalize whitespace and return - return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object -}; -/*===== -dojo.number.__ParseOptions = function(){ - // pattern: String? - // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) - // with this string. Default value is based on locale. Overriding this property will defeat - // localization. Literal characters in patterns are not supported. - // type: String? - // choose a format type based on the locale from the following: - // decimal, scientific (not yet supported), percent, currency. decimal by default. - // locale: String? - // override the locale used to determine formatting rules - // strict: Boolean? - // strict parsing, false by default. Strict parsing requires input as produced by the format() method. - // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators - // fractional: Boolean?|Array? - // Whether to include the fractional portion, where the number of decimal places are implied by pattern - // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional. - this.pattern = pattern; - this.type = type; - this.locale = locale; - this.strict = strict; - this.fractional = fractional; -} -=====*/ -dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){ +function connectToDomNode(){ // summary: - // Convert a properly formatted string to a primitive Number, using - // locale-specific settings. - // description: - // Create a Number from a string using a known localized pattern. - // Formatting patterns are chosen appropriate to the locale - // and follow the syntax described by - // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) - // Note that literal characters in patterns are not supported. - // expression: - // A string representation of a Number - var info = dojo.number._parseInfo(options), - results = (new RegExp("^"+info.regexp+"$")).exec(expression); - if(!results){ - return NaN; //NaN - } - var absoluteMatch = results[1]; // match for the positive expression - if(!results[1]){ - if(!results[2]){ - return NaN; //NaN - } - // matched the negative pattern - absoluteMatch =results[2]; - info.factor *= -1; - } - - // Transform it to something Javascript can parse as a number. Normalize - // decimal point and strip out group separators or alternate forms of whitespace - absoluteMatch = absoluteMatch. - replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), ""). - replace(info.decimal, "."); - // Adjust for negative sign, percent, etc. as necessary - return absoluteMatch * info.factor; //Number -}; - -/*===== -dojo.number.__RealNumberRegexpFlags = function(){ - // places: Number? - // The integer number of decimal places or a range given as "n,m". If - // not given, the decimal part is optional and the number of places is - // unlimited. - // decimal: String? - // A string for the character used as the decimal point. Default - // is ".". - // fractional: Boolean?|Array? - // Whether decimal places are used. Can be true, false, or [true, - // false]. Default is [true, false] which means optional. - // exponent: Boolean?|Array? - // Express in exponential notation. Can be true, false, or [true, - // false]. Default is [true, false], (i.e. will match if the - // exponential part is present are not). - // eSigned: Boolean?|Array? - // The leading plus-or-minus sign on the exponent. Can be true, - // false, or [true, false]. Default is [true, false], (i.e. will - // match if it is signed or unsigned). flags in regexp.integer can be - // applied. - this.places = places; - this.decimal = decimal; - this.fractional = fractional; - this.exponent = exponent; - this.eSigned = eSigned; + // If user connects to a widget method === this function, then they will + // instead actually be connecting the equivalent event on this.domNode } -=====*/ - -dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){ - // summary: - // Builds a regular expression to match a real number in exponential - // notation - - // assign default values to missing parameters - flags = flags || {}; - //TODO: use mixin instead? - if(!("places" in flags)){ flags.places = Infinity; } - if(typeof flags.decimal != "string"){ flags.decimal = "."; } - if(!("fractional" in flags) || /^0/.test(flags.places)){ flags.fractional = [true, false]; } - if(!("exponent" in flags)){ flags.exponent = [true, false]; } - if(!("eSigned" in flags)){ flags.eSigned = [true, false]; } - - var integerRE = dojo.number._integerRegexp(flags), - decimalRE = dojo.regexp.buildGroupRE(flags.fractional, - function(q){ - var re = ""; - if(q && (flags.places!==0)){ - re = "\\" + flags.decimal; - if(flags.places == Infinity){ - re = "(?:" + re + "\\d+)?"; - }else{ - re += "\\d{" + flags.places + "}"; - } - } - return re; - }, - true - ); - var exponentRE = dojo.regexp.buildGroupRE(flags.exponent, - function(q){ - if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; } - return ""; +// Trap dojo.connect() calls to connectToDomNode methods, and redirect to _Widget.on() +function aroundAdvice(originalConnect){ + return function(obj, event, scope, method){ + if(obj && typeof event == "string" && obj[event] == connectToDomNode){ + return obj.on(event.substring(2).toLowerCase(), lang.hitch(scope, method)); } - ); - - var realRE = integerRE + decimalRE; - // allow for decimals without integers, e.g. .25 - if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";} - return realRE + exponentRE; // String -}; - -/*===== -dojo.number.__IntegerRegexpFlags = function(){ - // signed: Boolean? - // The leading plus-or-minus sign. Can be true, false, or `[true,false]`. - // Default is `[true, false]`, (i.e. will match if it is signed - // or unsigned). - // separator: String? - // The character used as the thousands separator. Default is no - // separator. For more than one symbol use an array, e.g. `[",", ""]`, - // makes ',' optional. - // groupSize: Number? - // group size between separators - // groupSize2: Number? - // second grouping, where separators 2..n have a different interval than the first separator (for India) - this.signed = signed; - this.separator = separator; - this.groupSize = groupSize; - this.groupSize2 = groupSize2; + return originalConnect.apply(connect, arguments); + }; } -=====*/ - -dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){ - // summary: - // Builds a regular expression that matches an integer - - // assign default values to missing parameters - flags = flags || {}; - if(!("signed" in flags)){ flags.signed = [true, false]; } - if(!("separator" in flags)){ - flags.separator = ""; - }else if(!("groupSize" in flags)){ - flags.groupSize = 3; - } - - var signRE = dojo.regexp.buildGroupRE(flags.signed, - function(q){ return q ? "[-+]" : ""; }, - true - ); - - var numberRE = dojo.regexp.buildGroupRE(flags.separator, - function(sep){ - if(!sep){ - return "(?:\\d+)"; - } - - sep = dojo.regexp.escapeString(sep); - if(sep == " "){ sep = "\\s"; } - else if(sep == "\xa0"){ sep = "\\s\\xa0"; } - - var grp = flags.groupSize, grp2 = flags.groupSize2; - //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933 - if(grp2){ - var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})"; - return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE; - } - return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)"; - }, - true - ); - - return signRE + numberRE; // String -}; - +aspect.around(connect, "connect", aroundAdvice); +if(kernel.connect){ + aspect.around(kernel, "connect", aroundAdvice); } -if(!dojo._hasResource["dijit.ProgressBar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.ProgressBar"] = true; -dojo.provide("dijit.ProgressBar"); - - - - - - -dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], { +var _Widget = declare("dijit._Widget", [_WidgetBase, _OnDijitClickMixin, _FocusMixin], { // summary: - // A progress indication widget, showing the amount completed - // (often the percentage completed) of a task. + // Base class for all Dijit widgets. // - // example: - // | <div dojoType="ProgressBar" - // | places="0" - // | value="..." maximum="..."> - // | </div> - - // progress: [const] String (Percentage or Number) - // Number or percentage indicating amount of task completed. - // Deprecated. Use "value" instead. - progress: "0", - - // value: String (Percentage or Number) - // Number or percentage indicating amount of task completed. - // With "%": percentage value, 0% <= progress <= 100%, or - // without "%": absolute value, 0 <= progress <= maximum. - // Infinity means that the progress bar is indeterminate. - value: "", - - // maximum: [const] Float - // Max sample number - maximum: 100, - - // places: [const] Number - // Number of places to show in values; 0 by default - places: 0, - - // indeterminate: [const] Boolean - // If false: show progress value (number or percentage). - // If true: show that a process is underway but that the amount completed is unknown. - // Deprecated. Use "value" instead. - indeterminate: false, - - // label: String? - // Label on progress bar. Defaults to percentage for determinate progress bar and - // blank for indeterminate progress bar. - label:"", - - // name: String - // this is the field name (for a form) if set. This needs to be set if you want to use - // this widget in a dijit.form.Form widget (such as dijit.Dialog) - name: '', + // Extends _WidgetBase, adding support for: + // - declaratively/programatically specifying widget initialization parameters like + // onMouseMove="foo" that call foo when this.domNode gets a mousemove event + // - ondijitclick + // Support new data-dojo-attach-event="ondijitclick: ..." that is triggered by a mouse click or a SPACE/ENTER keypress + // - focus related functions + // In particular, the onFocus()/onBlur() callbacks. Driven internally by + // dijit/_base/focus.js. + // - deprecated methods + // - onShow(), onHide(), onClose() + // + // Also, by loading code in dijit/_base, turns on: + // - browser sniffing (putting browser id like .dj_ie on <html> node) + // - high contrast mode sniffing (add .dijit_a11y class to <body> if machine is in high contrast mode) - templateString: dojo.cache("dijit", "templates/ProgressBar.html", "<div class=\"dijitProgressBar dijitProgressBarEmpty\" role=\"progressbar\"\n\t><div dojoAttachPoint=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\" role=\"presentation\"></div\n\t\t><span style=\"visibility:hidden\"> </span\n\t></div\n\t><div dojoAttachPoint=\"labelNode\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"></div\n\t><img dojoAttachPoint=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n/></div>\n"), - // _indeterminateHighContrastImagePath: [private] dojo._URL - // URL to image to use for indeterminate progress bar when display is in high contrast mode - _indeterminateHighContrastImagePath: - dojo.moduleUrl("dijit", "themes/a11y/indeterminate_progress.gif"), + ////////////////// DEFERRED CONNECTS /////////////////// - postMixInProperties: function(){ - this.inherited(arguments); - if(!("value" in this.params)){ - this.value = this.indeterminate ? Infinity : this.progress; - } + onClick: connectToDomNode, + /*===== + onClick: function(event){ + // summary: + // Connect to this function to receive notifications of mouse click events. + // event: + // mouse Event + // tags: + // callback }, - - buildRendering: function(){ - this.inherited(arguments); - this.indeterminateHighContrastImage.setAttribute("src", - this._indeterminateHighContrastImagePath.toString()); - this.update(); + =====*/ + onDblClick: connectToDomNode, + /*===== + onDblClick: function(event){ + // summary: + // Connect to this function to receive notifications of mouse double click events. + // event: + // mouse Event + // tags: + // callback }, - - update: function(/*Object?*/attributes){ + =====*/ + onKeyDown: connectToDomNode, + /*===== + onKeyDown: function(event){ // summary: - // Internal method to change attributes of ProgressBar, similar to set(hash). Users should call - // set("value", ...) rather than calling this method directly. - // attributes: - // May provide progress and/or maximum properties on this parameter; - // see attribute specs for details. - // example: - // | myProgressBar.update({'indeterminate': true}); - // | myProgressBar.update({'progress': 80}); - // | myProgressBar.update({'indeterminate': true, label:"Loading ..." }) + // Connect to this function to receive notifications of keys being pressed down. + // event: + // key Event // tags: - // private - - // TODO: deprecate this method and use set() instead - - dojo.mixin(this, attributes || {}); - var tip = this.internalProgress, ap = this.domNode; - var percent = 1; - if(this.indeterminate){ - dijit.removeWaiState(ap, "valuenow"); - dijit.removeWaiState(ap, "valuemin"); - dijit.removeWaiState(ap, "valuemax"); - }else{ - if(String(this.progress).indexOf("%") != -1){ - percent = Math.min(parseFloat(this.progress)/100, 1); - this.progress = percent * this.maximum; - }else{ - this.progress = Math.min(this.progress, this.maximum); - percent = this.progress / this.maximum; - } - - dijit.setWaiState(ap, "describedby", this.labelNode.id); - dijit.setWaiState(ap, "valuenow", this.progress); - dijit.setWaiState(ap, "valuemin", 0); - dijit.setWaiState(ap, "valuemax", this.maximum); - } - this.labelNode.innerHTML = this.report(percent); - - dojo.toggleClass(this.domNode, "dijitProgressBarIndeterminate", this.indeterminate); - tip.style.width = (percent * 100) + "%"; - this.onChange(); + // callback }, - - _setValueAttr: function(v){ - this._set("value", v); - if(v == Infinity){ - this.update({indeterminate:true}); - }else{ - this.update({indeterminate:false, progress:v}); - } + =====*/ + onKeyPress: connectToDomNode, + /*===== + onKeyPress: function(event){ + // summary: + // Connect to this function to receive notifications of printable keys being typed. + // event: + // key Event + // tags: + // callback }, - - _setLabelAttr: function(label){ - this._set("label", label); - this.update(); + =====*/ + onKeyUp: connectToDomNode, + /*===== + onKeyUp: function(event){ + // summary: + // Connect to this function to receive notifications of keys being released. + // event: + // key Event + // tags: + // callback }, - - _setIndeterminateAttr: function(indeterminate){ - // Deprecated, use set("value", ...) instead - this.indeterminate = indeterminate; - this.update(); + =====*/ + onMouseDown: connectToDomNode, + /*===== + onMouseDown: function(event){ + // summary: + // Connect to this function to receive notifications of when the mouse button is pressed down. + // event: + // mouse Event + // tags: + // callback }, - - report: function(/*float*/percent){ + =====*/ + onMouseMove: connectToDomNode, + /*===== + onMouseMove: function(event){ // summary: - // Generates message to show inside progress bar (normally indicating amount of task completed). - // May be overridden. + // Connect to this function to receive notifications of when the mouse moves over nodes contained within this widget. + // event: + // mouse Event // tags: - // extension - - return this.label ? this.label : - (this.indeterminate ? " " : dojo.number.format(percent, { type: "percent", places: this.places, locale: this.lang })); + // callback }, - - onChange: function(){ + =====*/ + onMouseOut: connectToDomNode, + /*===== + onMouseOut: function(event){ // summary: - // Callback fired when progress updates. + // Connect to this function to receive notifications of when the mouse moves off of nodes contained within this widget. + // event: + // mouse Event // tags: - // extension - } -}); - -} - -if(!dojo._hasResource["dijit.ToolbarSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.ToolbarSeparator"] = true; -dojo.provide("dijit.ToolbarSeparator"); - - - - -dojo.declare("dijit.ToolbarSeparator", - [ dijit._Widget, dijit._Templated ], - { + // callback + }, + =====*/ + onMouseOver: connectToDomNode, + /*===== + onMouseOver: function(event){ // summary: - // A spacer between two `dijit.Toolbar` items - templateString: '<div class="dijitToolbarSeparator dijitInline" role="presentation"></div>', - buildRendering: function(){ - this.inherited(arguments); - dojo.setSelectable(this.domNode, false); - }, - isFocusable: function(){ - // summary: - // This widget isn't focusable, so pass along that fact. - // tags: - // protected - return false; - } - - }); - -} - -if(!dojo._hasResource["dijit.Toolbar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.Toolbar"] = true; -dojo.provide("dijit.Toolbar"); - - - - - - -// Note: require of ToolbarSeparator is for back-compat, remove for 2.0 - -dojo.declare("dijit.Toolbar", - [dijit._Widget, dijit._Templated, dijit._KeyNavContainer], - { - // summary: - // A Toolbar widget, used to hold things like `dijit.Editor` buttons - - templateString: - '<div class="dijit" role="toolbar" tabIndex="${tabIndex}" dojoAttachPoint="containerNode">' + - // '<table style="table-layout: fixed" class="dijitReset dijitToolbarTable">' + // factor out style - // '<tr class="dijitReset" dojoAttachPoint="containerNode"></tr>'+ - // '</table>' + - '</div>', - - baseClass: "dijitToolbar", - - postCreate: function(){ - this.inherited(arguments); - - this.connectKeyNavHandlers( - this.isLeftToRight() ? [dojo.keys.LEFT_ARROW] : [dojo.keys.RIGHT_ARROW], - this.isLeftToRight() ? [dojo.keys.RIGHT_ARROW] : [dojo.keys.LEFT_ARROW] - ); + // Connect to this function to receive notifications of when the mouse moves onto nodes contained within this widget. + // event: + // mouse Event + // tags: + // callback }, - - startup: function(){ - if(this._started){ return; } - - this.startupKeyNavChildren(); - - this.inherited(arguments); - } -} -); - -} - -if(!dojo._hasResource["dojo.DeferredList"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.DeferredList"] = true; -dojo.provide("dojo.DeferredList"); - - -dojo.DeferredList = function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/ consumeErrors, /*Function?*/ canceller){ - // summary: - // Provides event handling for a group of Deferred objects. - // description: - // DeferredList takes an array of existing deferreds and returns a new deferred of its own - // this new deferred will typically have its callback fired when all of the deferreds in - // the given list have fired their own deferreds. The parameters `fireOnOneCallback` and - // fireOnOneErrback, will fire before all the deferreds as appropriate - // - // list: - // The list of deferreds to be synchronizied with this DeferredList - // fireOnOneCallback: - // Will cause the DeferredLists callback to be fired as soon as any - // of the deferreds in its list have been fired instead of waiting until - // the entire list has finished - // fireonOneErrback: - // Will cause the errback to fire upon any of the deferreds errback - // canceller: - // A deferred canceller function, see dojo.Deferred - var resultList = []; - dojo.Deferred.call(this); - var self = this; - if(list.length === 0 && !fireOnOneCallback){ - this.resolve([0, []]); - } - var finished = 0; - dojo.forEach(list, function(item, i){ - item.then(function(result){ - if(fireOnOneCallback){ - self.resolve([i, result]); - }else{ - addResult(true, result); - } - },function(error){ - if(fireOnOneErrback){ - self.reject(error); - }else{ - addResult(false, error); - } - if(consumeErrors){ - return null; - } - throw error; - }); - function addResult(succeeded, result){ - resultList[i] = [succeeded, result]; - finished++; - if(finished === list.length){ - self.resolve(resultList); - } - - } - }); -}; -dojo.DeferredList.prototype = new dojo.Deferred(); - -dojo.DeferredList.prototype.gatherResults= function(deferredList){ - // summary: - // Gathers the results of the deferreds for packaging - // as the parameters to the Deferred Lists' callback - - var d = new dojo.DeferredList(deferredList, false, true, false); - d.addCallback(function(results){ - var ret = []; - dojo.forEach(results, function(result){ - ret.push(result[1]); - }); - return ret; - }); - return d; -}; - -} - -if(!dojo._hasResource["dijit.tree.TreeStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.tree.TreeStoreModel"] = true; -dojo.provide("dijit.tree.TreeStoreModel"); - - -dojo.declare( - "dijit.tree.TreeStoreModel", - null, - { + =====*/ + onMouseLeave: connectToDomNode, + /*===== + onMouseLeave: function(event){ // summary: - // Implements dijit.Tree.model connecting to a store with a single - // root item. Any methods passed into the constructor will override - // the ones defined here. - - // store: dojo.data.Store - // Underlying store - store: null, - - // childrenAttrs: String[] - // One or more attribute names (attributes in the dojo.data item) that specify that item's children - childrenAttrs: ["children"], - - // newItemIdAttr: String - // Name of attribute in the Object passed to newItem() that specifies the id. - // - // If newItemIdAttr is set then it's used when newItem() is called to see if an - // item with the same id already exists, and if so just links to the old item - // (so that the old item ends up with two parents). - // - // Setting this to null or "" will make every drop create a new item. - newItemIdAttr: "id", - - // labelAttr: String - // If specified, get label for tree node from this attribute, rather - // than by calling store.getLabel() - labelAttr: "", - - // root: [readonly] dojo.data.Item - // Pointer to the root item (read only, not a parameter) - root: null, - - // query: anything - // Specifies datastore query to return the root item for the tree. - // Must only return a single item. Alternately can just pass in pointer - // to root item. - // example: - // | {id:'ROOT'} - query: null, - - // deferItemLoadingUntilExpand: Boolean - // Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes - // until they are expanded. This allows for lazying loading where only one - // loadItem (and generally one network call, consequently) per expansion - // (rather than one for each child). - // This relies on partial loading of the children items; each children item of a - // fully loaded item should contain the label and info about having children. - deferItemLoadingUntilExpand: false, - - constructor: function(/* Object */ args){ - // summary: - // Passed the arguments listed above (store, etc) - // tags: - // private - - dojo.mixin(this, args); - - this.connects = []; - - var store = this.store; - if(!store.getFeatures()['dojo.data.api.Identity']){ - throw new Error("dijit.Tree: store must support dojo.data.Identity"); - } - - // if the store supports Notification, subscribe to the notification events - if(store.getFeatures()['dojo.data.api.Notification']){ - this.connects = this.connects.concat([ - dojo.connect(store, "onNew", this, "onNewItem"), - dojo.connect(store, "onDelete", this, "onDeleteItem"), - dojo.connect(store, "onSet", this, "onSetItem") - ]); - } - }, - - destroy: function(){ - dojo.forEach(this.connects, dojo.disconnect); - // TODO: should cancel any in-progress processing of getRoot(), getChildren() - }, - - // ======================================================================= - // Methods for traversing hierarchy - - getRoot: function(onItem, onError){ - // summary: - // Calls onItem with the root item for the tree, possibly a fabricated item. - // Calls onError on error. - if(this.root){ - onItem(this.root); - }else{ - this.store.fetch({ - query: this.query, - onComplete: dojo.hitch(this, function(items){ - if(items.length != 1){ - throw new Error(this.declaredClass + ": query " + dojo.toJson(this.query) + " returned " + items.length + - " items, but must return exactly one item"); - } - this.root = items[0]; - onItem(this.root); - }), - onError: onError - }); - } - }, - - mayHaveChildren: function(/*dojo.data.Item*/ item){ - // summary: - // Tells if an item has or may have children. Implementing logic here - // avoids showing +/- expando icon for nodes that we know don't have children. - // (For efficiency reasons we may not want to check if an element actually - // has children until user clicks the expando node) - return dojo.some(this.childrenAttrs, function(attr){ - return this.store.hasAttribute(item, attr); - }, this); - }, - - getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){ - // summary: - // Calls onComplete() with array of child items of given parent item, all loaded. - - var store = this.store; - if(!store.isItemLoaded(parentItem)){ - // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand - // mode, so we will load it and just return the children (without loading each - // child item) - var getChildren = dojo.hitch(this, arguments.callee); - store.loadItem({ - item: parentItem, - onItem: function(parentItem){ - getChildren(parentItem, onComplete, onError); - }, - onError: onError - }); - return; - } - // get children of specified item - var childItems = []; - for(var i=0; i<this.childrenAttrs.length; i++){ - var vals = store.getValues(parentItem, this.childrenAttrs[i]); - childItems = childItems.concat(vals); - } - - // count how many items need to be loaded - var _waitCount = 0; - if(!this.deferItemLoadingUntilExpand){ - dojo.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } }); - } - - if(_waitCount == 0){ - // all items are already loaded (or we aren't loading them). proceed... - onComplete(childItems); - }else{ - // still waiting for some or all of the items to load - dojo.forEach(childItems, function(item, idx){ - if(!store.isItemLoaded(item)){ - store.loadItem({ - item: item, - onItem: function(item){ - childItems[idx] = item; - if(--_waitCount == 0){ - // all nodes have been loaded, send them to the tree - onComplete(childItems); - } - }, - onError: onError - }); - } - }); - } - }, - - // ======================================================================= - // Inspecting items - - isItem: function(/* anything */ something){ - return this.store.isItem(something); // Boolean - }, - - fetchItemByIdentity: function(/* object */ keywordArgs){ - this.store.fetchItemByIdentity(keywordArgs); - }, - - getIdentity: function(/* item */ item){ - return this.store.getIdentity(item); // Object - }, - - getLabel: function(/*dojo.data.Item*/ item){ - // summary: - // Get the label for an item - if(this.labelAttr){ - return this.store.getValue(item,this.labelAttr); // String - }else{ - return this.store.getLabel(item); // String - } - }, - - // ======================================================================= - // Write interface - - newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){ - // summary: - // Creates a new item. See `dojo.data.api.Write` for details on args. - // Used in drag & drop when item from external source dropped onto tree. - // description: - // Developers will need to override this method if new items get added - // to parents with multiple children attributes, in order to define which - // children attribute points to the new item. - - var pInfo = {parent: parent, attribute: this.childrenAttrs[0]}, LnewItem; - - if(this.newItemIdAttr && args[this.newItemIdAttr]){ - // Maybe there's already a corresponding item in the store; if so, reuse it. - this.fetchItemByIdentity({identity: args[this.newItemIdAttr], scope: this, onItem: function(item){ - if(item){ - // There's already a matching item in store, use it - this.pasteItem(item, null, parent, true, insertIndex); - }else{ - // Create new item in the tree, based on the drag source. - LnewItem=this.store.newItem(args, pInfo); - if (LnewItem && (insertIndex!=undefined)){ - // Move new item to desired position - this.pasteItem(LnewItem, parent, parent, false, insertIndex); - } - } - }}); - }else{ - // [as far as we know] there is no id so we must assume this is a new item - LnewItem=this.store.newItem(args, pInfo); - if (LnewItem && (insertIndex!=undefined)){ - // Move new item to desired position - this.pasteItem(LnewItem, parent, parent, false, insertIndex); - } - } - }, - - pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){ - // summary: - // Move or copy an item from one parent item to another. - // Used in drag & drop - var store = this.store, - parentAttr = this.childrenAttrs[0]; // name of "children" attr in parent item - - // remove child from source item, and record the attribute that child occurred in - if(oldParentItem){ - dojo.forEach(this.childrenAttrs, function(attr){ - if(store.containsValue(oldParentItem, attr, childItem)){ - if(!bCopy){ - var values = dojo.filter(store.getValues(oldParentItem, attr), function(x){ - return x != childItem; - }); - store.setValues(oldParentItem, attr, values); - } - parentAttr = attr; - } - }); - } - - // modify target item's children attribute to include this item - if(newParentItem){ - if(typeof insertIndex == "number"){ - // call slice() to avoid modifying the original array, confusing the data store - var childItems = store.getValues(newParentItem, parentAttr).slice(); - childItems.splice(insertIndex, 0, childItem); - store.setValues(newParentItem, parentAttr, childItems); - }else{ - store.setValues(newParentItem, parentAttr, - store.getValues(newParentItem, parentAttr).concat(childItem)); - } - } - }, - - // ======================================================================= - // Callbacks - - onChange: function(/*dojo.data.Item*/ item){ - // summary: - // Callback whenever an item has changed, so that Tree - // can update the label, icon, etc. Note that changes - // to an item's children or parent(s) will trigger an - // onChildrenChange() so you can ignore those changes here. - // tags: - // callback - }, - - onChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ - // summary: - // Callback to do notifications about new, updated, or deleted items. - // tags: - // callback - }, - - onDelete: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ - // summary: - // Callback when an item has been deleted. - // description: - // Note that there will also be an onChildrenChange() callback for the parent - // of this item. - // tags: - // callback - }, - - // ======================================================================= - // Events from data store - - onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ - // summary: - // Handler for when new items appear in the store, either from a drop operation - // or some other way. Updates the tree view (if necessary). - // description: - // If the new item is a child of an existing item, - // calls onChildrenChange() with the new list of children - // for that existing item. - // - // tags: - // extension - - // We only care about the new item if it has a parent that corresponds to a TreeNode - // we are currently displaying - if(!parentInfo){ - return; - } - - // Call onChildrenChange() on parent (ie, existing) item with new list of children - // In the common case, the new list of children is simply parentInfo.newValue or - // [ parentInfo.newValue ], although if items in the store has multiple - // child attributes (see `childrenAttr`), then it's a superset of parentInfo.newValue, - // so call getChildren() to be sure to get right answer. - this.getChildren(parentInfo.item, dojo.hitch(this, function(children){ - this.onChildrenChange(parentInfo.item, children); - })); - }, - - onDeleteItem: function(/*Object*/ item){ - // summary: - // Handler for delete notifications from underlying store - this.onDelete(item); - }, - - onSetItem: function(/* item */ item, - /* attribute-name-string */ attribute, - /* object | array */ oldValue, - /* object | array */ newValue){ - // summary: - // Updates the tree view according to changes in the data store. - // description: - // Handles updates to an item's children by calling onChildrenChange(), and - // other updates to an item by calling onChange(). - // - // See `onNewItem` for more details on handling updates to an item's children. - // tags: - // extension - - if(dojo.indexOf(this.childrenAttrs, attribute) != -1){ - // item's children list changed - this.getChildren(item, dojo.hitch(this, function(children){ - // See comments in onNewItem() about calling getChildren() - this.onChildrenChange(item, children); - })); - }else{ - // item's label/icon/etc. changed. - this.onChange(item); - } - } - }); - -} - -if(!dojo._hasResource["dijit.tree.ForestStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.tree.ForestStoreModel"] = true; -dojo.provide("dijit.tree.ForestStoreModel"); - - - -dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.TreeStoreModel, { - // summary: - // Interface between a dijit.Tree and a dojo.data store that doesn't have a root item, - // a.k.a. a store that has multiple "top level" items. - // - // description - // Use this class to wrap a dojo.data store, making all the items matching the specified query - // appear as children of a fabricated "root item". If no query is specified then all the - // items returned by fetch() on the underlying store become children of the root item. - // This class allows dijit.Tree to assume a single root item, even if the store doesn't have one. - // - // When using this class the developer must override a number of methods according to their app and - // data, including: - // - onNewRootItem - // - onAddToRoot - // - onLeaveRoot - // - onNewItem - // - onSetItem - - // Parameters to constructor - - // rootId: String - // ID of fabricated root item - rootId: "$root$", - - // rootLabel: String - // Label of fabricated root item - rootLabel: "ROOT", - - // query: String - // Specifies the set of children of the root item. - // example: - // | {type:'continent'} - query: null, - - // End of parameters to constructor - - constructor: function(params){ + // Connect to this function to receive notifications of when the mouse moves off of this widget. + // event: + // mouse Event + // tags: + // callback + }, + =====*/ + onMouseEnter: connectToDomNode, + /*===== + onMouseEnter: function(event){ // summary: - // Sets up variables, etc. + // Connect to this function to receive notifications of when the mouse moves onto this widget. + // event: + // mouse Event // tags: - // private - - // Make dummy root item - this.root = { - store: this, - root: true, - id: params.rootId, - label: params.rootLabel, - children: params.rootChildren // optional param - }; + // callback }, - - // ======================================================================= - // Methods for traversing hierarchy - - mayHaveChildren: function(/*dojo.data.Item*/ item){ + =====*/ + onMouseUp: connectToDomNode, + /*===== + onMouseUp: function(event){ // summary: - // Tells if an item has or may have children. Implementing logic here - // avoids showing +/- expando icon for nodes that we know don't have children. - // (For efficiency reasons we may not want to check if an element actually - // has children until user clicks the expando node) + // Connect to this function to receive notifications of when the mouse button is released. + // event: + // mouse Event // tags: - // extension - return item === this.root || this.inherited(arguments); + // callback }, + =====*/ - getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ callback, /*function*/ onError){ - // summary: - // Calls onComplete() with array of child items of given parent item, all loaded. - if(parentItem === this.root){ - if(this.root.children){ - // already loaded, just return - callback(this.root.children); - }else{ - this.store.fetch({ - query: this.query, - onComplete: dojo.hitch(this, function(items){ - this.root.children = items; - callback(items); - }), - onError: onError - }); + constructor: function(params){ + // extract parameters like onMouseMove that should connect directly to this.domNode + this._toConnect = {}; + for(var name in params){ + if(this[name] === connectToDomNode){ + this._toConnect[name.replace(/^on/, "").toLowerCase()] = params[name]; + delete params[name]; } - }else{ - this.inherited(arguments); } }, - // ======================================================================= - // Inspecting items - - isItem: function(/* anything */ something){ - return (something === this.root) ? true : this.inherited(arguments); - }, + postCreate: function(){ + this.inherited(arguments); - fetchItemByIdentity: function(/* object */ keywordArgs){ - if(keywordArgs.identity == this.root.id){ - var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; - if(keywordArgs.onItem){ - keywordArgs.onItem.call(scope, this.root); - } - }else{ - this.inherited(arguments); + // perform connection from this.domNode to user specified handlers (ex: onMouseMove) + for(var name in this._toConnect){ + this.on(name, this._toConnect[name]); } + delete this._toConnect; }, - getIdentity: function(/* item */ item){ - return (item === this.root) ? this.root.id : this.inherited(arguments); + on: function(/*String*/ type, /*Function*/ func){ + if(this[this._onMap(type)] === connectToDomNode){ + // Use connect.connect() rather than on() to get handling for "onmouseenter" on non-IE, etc. + // Also, need to specify context as "this" rather than the default context of the DOMNode + return connect.connect(this.domNode, type.toLowerCase(), this, func); + } + return this.inherited(arguments); }, - getLabel: function(/* item */ item){ - return (item === this.root) ? this.root.label : this.inherited(arguments); + _setFocusedAttr: function(val){ + // Remove this method in 2.0 (or sooner), just here to set _focused == focused, for back compat + // (but since it's a private variable we aren't required to keep supporting it). + this._focused = val; + this._set("focused", val); }, - // ======================================================================= - // Write interface + ////////////////// DEPRECATED METHODS /////////////////// - newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){ + setAttribute: function(/*String*/ attr, /*anything*/ value){ // summary: - // Creates a new item. See dojo.data.api.Write for details on args. - // Used in drag & drop when item from external source dropped onto tree. - if(parent === this.root){ - this.onNewRootItem(args); - return this.store.newItem(args); - }else{ - return this.inherited(arguments); - } + // Deprecated. Use set() instead. + // tags: + // deprecated + kernel.deprecated(this.declaredClass+"::setAttribute(attr, value) is deprecated. Use set() instead.", "", "2.0"); + this.set(attr, value); }, - onNewRootItem: function(args){ + attr: function(/*String|Object*/name, /*Object?*/value){ // summary: - // User can override this method to modify a new element that's being - // added to the root of the tree, for example to add a flag like root=true - }, + // Set or get properties on a widget instance. + // name: + // The property to get or set. If an object is passed here and not + // a string, its keys are used as names of attributes to be set + // and the value of the object as values to set in the widget. + // value: + // Optional. If provided, attr() operates as a setter. If omitted, + // the current value of the named property is returned. + // description: + // This method is deprecated, use get() or set() directly. - pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){ - // summary: - // Move or copy an item from one parent item to another. - // Used in drag & drop - if(oldParentItem === this.root){ - if(!bCopy){ - // It's onLeaveRoot()'s responsibility to modify the item so it no longer matches - // this.query... thus triggering an onChildrenChange() event to notify the Tree - // that this element is no longer a child of the root node - this.onLeaveRoot(childItem); + // Print deprecation warning but only once per calling function + if(config.isDebug){ + var alreadyCalledHash = arguments.callee._ach || (arguments.callee._ach = {}), + caller = (arguments.callee.caller || "unknown caller").toString(); + if(!alreadyCalledHash[caller]){ + kernel.deprecated(this.declaredClass + "::attr() is deprecated. Use get() or set() instead, called from " + + caller, "", "2.0"); + alreadyCalledHash[caller] = true; } } - dijit.tree.TreeStoreModel.prototype.pasteItem.call(this, childItem, - oldParentItem === this.root ? null : oldParentItem, - newParentItem === this.root ? null : newParentItem, - bCopy, - insertIndex - ); - if(newParentItem === this.root){ - // It's onAddToRoot()'s responsibility to modify the item so it matches - // this.query... thus triggering an onChildrenChange() event to notify the Tree - // that this element is now a child of the root node - this.onAddToRoot(childItem); + + var args = arguments.length; + if(args >= 2 || typeof name === "object"){ // setter + return this.set.apply(this, arguments); + }else{ // getter + return this.get(name); } }, - // ======================================================================= - // Handling for top level children - - onAddToRoot: function(/* item */ item){ + getDescendants: function(){ // summary: - // Called when item added to root of tree; user must override this method - // to modify the item so that it matches the query for top level items - // example: - // | store.setValue(item, "root", true); - // tags: - // extension - console.log(this, ": item ", item, " added to root"); - }, + // Returns all the widgets contained by this, i.e., all widgets underneath this.containerNode. + // This method should generally be avoided as it returns widgets declared in templates, which are + // supposed to be internal/hidden, but it's left here for back-compat reasons. - onLeaveRoot: function(/* item */ item){ - // summary: - // Called when item removed from root of tree; user must override this method - // to modify the item so it doesn't match the query for top level items - // example: - // | store.unsetAttribute(item, "root"); - // tags: - // extension - console.log(this, ": item ", item, " removed from root"); + kernel.deprecated(this.declaredClass+"::getDescendants() is deprecated. Use getChildren() instead.", "", "2.0"); + return this.containerNode ? query('[widgetId]', this.containerNode).map(registry.byNode) : []; // dijit._Widget[] }, - // ======================================================================= - // Events from data store - - _requeryTop: function(){ - // reruns the query for the children of the root node, - // sending out an onSet notification if those children have changed - var oldChildren = this.root.children || []; - this.store.fetch({ - query: this.query, - onComplete: dojo.hitch(this, function(newChildren){ - this.root.children = newChildren; + ////////////////// MISCELLANEOUS METHODS /////////////////// - // If the list of children or the order of children has changed... - if(oldChildren.length != newChildren.length || - dojo.some(oldChildren, function(item, idx){ return newChildren[idx] != item;})){ - this.onChildrenChange(this.root, newChildren); - } - }) - }); + _onShow: function(){ + // summary: + // Internal method called when this widget is made visible. + // See `onShow` for details. + this.onShow(); }, - onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ + onShow: function(){ // summary: - // Handler for when new items appear in the store. Developers should override this - // method to be more efficient based on their app/data. - // description: - // Note that the default implementation requeries the top level items every time - // a new item is created, since any new item could be a top level item (even in - // addition to being a child of another item, since items can have multiple parents). + // Called when this widget becomes the selected pane in a + // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`, + // `dijit.layout.AccordionContainer`, etc. // - // If developers can detect which items are possible top level items (based on the item and the - // parentInfo parameters), they should override this method to only call _requeryTop() for top - // level items. Often all top level items have parentInfo==null, but - // that will depend on which store you use and what your data is like. + // Also called to indicate display of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`. // tags: - // extension - this._requeryTop(); - - this.inherited(arguments); + // callback }, - onDeleteItem: function(/*Object*/ item){ + onHide: function(){ // summary: - // Handler for delete notifications from underlying store - - // check if this was a child of root, and if so send notification that root's children - // have changed - if(dojo.indexOf(this.root.children, item) != -1){ - this._requeryTop(); - } - - this.inherited(arguments); + // Called when another widget becomes the selected pane in a + // `dijit.layout.TabContainer`, `dijit.layout.StackContainer`, + // `dijit.layout.AccordionContainer`, etc. + // + // Also called to indicate hide of a `dijit.Dialog`, `dijit.TooltipDialog`, or `dijit.TitlePane`. + // tags: + // callback }, - onSetItem: function(/* item */ item, - /* attribute-name-string */ attribute, - /* object | array */ oldValue, - /* object | array */ newValue){ + onClose: function(){ // summary: - // Updates the tree view according to changes to an item in the data store. - // Developers should override this method to be more efficient based on their app/data. - // description: - // Handles updates to an item's children by calling onChildrenChange(), and - // other updates to an item by calling onChange(). - // - // Also, any change to any item re-executes the query for the tree's top-level items, - // since this modified item may have started/stopped matching the query for top level items. + // Called when this widget is being displayed as a popup (ex: a Calendar popped + // up from a DateTextBox), and it is hidden. + // This is called from the dijit.popup code, and should not be called directly. // - // If possible, developers should override this function to only call _requeryTop() when - // the change to the item has caused it to stop/start being a top level item in the tree. + // Also used as a parameter for children of `dijit.layout.StackContainer` or subclasses. + // Callback if a user tries to close the child. Child will be closed if this function returns true. // tags: // extension - this._requeryTop(); - this.inherited(arguments); + return true; // Boolean } - }); +// For back-compat, remove in 2.0. +if(!kernel.isAsync){ + ready(0, function(){ + var requires = ["dijit/_base"]; + require(requires); // use indirection so modules not rolled into a build + }); } +return _Widget; +}); -if(!dojo._hasResource["dojo.dnd.Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.dnd.Container"] = true; -dojo.provide("dojo.dnd.Container"); - - - - -/* - Container states: - "" - normal state - "Over" - mouse over a container - Container item states: - "" - normal state - "Over" - mouse over a container item -*/ +}, +'dojo/touch':function(){ +define("dojo/touch", ["./_base/kernel", "./on", "./has", "./mouse"], function(dojo, on, has, mouse){ +// module: +// dojo/touch /*===== -dojo.declare("dojo.dnd.__ContainerArgs", [], { - creator: function(){ + dojo.touch = { // summary: - // a creator function, which takes a data item, and returns an object like that: - // {node: newNode, data: usedData, type: arrayOfStrings} - }, - - // skipForm: Boolean - // don't start the drag operation, if clicked on form elements - skipForm: false, - - // dropParent: Node||String - // node or node's id to use as the parent node for dropped items - // (must be underneath the 'node' parameter in the DOM) - dropParent: null, + // This module provides unified touch event handlers by exporting + // press, move, release and cancel which can also run well on desktop. + // Based on http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html + // + // example: + // 1. Used with dojo.connect() + // | dojo.connect(node, dojo.touch.press, function(e){}); + // | dojo.connect(node, dojo.touch.move, function(e){}); + // | dojo.connect(node, dojo.touch.release, function(e){}); + // | dojo.connect(node, dojo.touch.cancel, function(e){}); + // + // 2. Used with dojo.on + // | define(["dojo/on", "dojo/touch"], function(on, touch){ + // | on(node, touch.press, function(e){}); + // | on(node, touch.move, function(e){}); + // | on(node, touch.release, function(e){}); + // | on(node, touch.cancel, function(e){}); + // + // 3. Used with dojo.touch.* directly + // | dojo.touch.press(node, function(e){}); + // | dojo.touch.move(node, function(e){}); + // | dojo.touch.release(node, function(e){}); + // | dojo.touch.cancel(node, function(e){}); + + press: function(node, listener){ + // summary: + // Register a listener to 'touchstart'|'mousedown' for the given node + // node: Dom + // Target node to listen to + // listener: Function + // Callback function + // returns: + // A handle which will be used to remove the listener by handle.remove() + }, + move: function(node, listener){ + // summary: + // Register a listener to 'touchmove'|'mousemove' for the given node + // node: Dom + // Target node to listen to + // listener: Function + // Callback function + // returns: + // A handle which will be used to remove the listener by handle.remove() + }, + release: function(node, listener){ + // summary: + // Register a listener to 'touchend'|'mouseup' for the given node + // node: Dom + // Target node to listen to + // listener: Function + // Callback function + // returns: + // A handle which will be used to remove the listener by handle.remove() + }, + cancel: function(node, listener){ + // summary: + // Register a listener to 'touchcancel'|'mouseleave' for the given node + // node: Dom + // Target node to listen to + // listener: Function + // Callback function + // returns: + // A handle which will be used to remove the listener by handle.remove() + } + }; +=====*/ - // _skipStartup: Boolean - // skip startup(), which collects children, for deferred initialization - // (this is used in the markup mode) - _skipStartup: false + function _handle(/*String - press | move | release | cancel*/type){ + return function(node, listener){//called by on(), see dojo.on + return on(node, type, listener); + }; + } + var touch = has("touch"); + //device neutral events - dojo.touch.press|move|release|cancel + dojo.touch = { + press: _handle(touch ? "touchstart": "mousedown"), + move: _handle(touch ? "touchmove": "mousemove"), + release: _handle(touch ? "touchend": "mouseup"), + cancel: touch ? _handle("touchcancel") : mouse.leave + }; + return dojo.touch; }); - -dojo.dnd.Item = function(){ +}, +'url:dijit/form/templates/Select.html':"<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdata-dojo-attach-point=\"_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\" data-dojo-attach-point=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} data-dojo-attach-point=\"valueNode\" value=\"${value}\" aria-hidden=\"true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdata-dojo-attach-point=\"titleNode\" role=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">▼</div\n\t\t></td\n\t></tr></tbody\n></table>\n", +'dojo/fx':function(){ +define("dojo/fx", [ + "./_base/lang", + "./Evented", + "./_base/kernel", + "./_base/array", + "./_base/connect", + "./_base/fx", + "./dom", + "./dom-style", + "./dom-geometry", + "./ready", + "require" // for context sensitive loading of Toggler +], function(lang, Evented, dojo, arrayUtil, connect, baseFx, dom, domStyle, geom, ready, require) { + + // module: + // dojo/fx // summary: - // Represents (one of) the source node(s) being dragged. - // Contains (at least) the "type" and "data" attributes. - // type: String[] - // Type(s) of this item, by default this is ["text"] - // data: Object - // Logical representation of the object being dragged. - // If the drag object's type is "text" then data is a String, - // if it's another type then data could be a different Object, - // perhaps a name/value hash. - - this.type = type; - this.data = data; -} -=====*/ + // TODOC + -dojo.declare("dojo.dnd.Container", null, { - // summary: - // a Container object, which knows when mouse hovers over it, - // and over which element it hovers - - // object attributes (for markup) - skipForm: false, - /*===== - // current: DomNode - // The DOM node the mouse is currently hovered over - current: null, - - // map: Hash<String, dojo.dnd.Item> - // Map from an item's id (which is also the DOMNode's id) to - // the dojo.dnd.Item itself. - map: {}, + dojo.fx = { + // summary: Effects library on top of Base animations + }; + var coreFx = dojo.fx; =====*/ - constructor: function(node, params){ - // summary: - // a constructor of the Container - // node: Node - // node or node's id to build the container on - // params: dojo.dnd.__ContainerArgs - // a dictionary of parameters - this.node = dojo.byId(node); - if(!params){ params = {}; } - this.creator = params.creator || null; - this.skipForm = params.skipForm; - this.parent = params.dropParent && dojo.byId(params.dropParent); - - // class-specific variables - this.map = {}; - this.current = null; +// For back-compat, remove in 2.0. +if(!dojo.isAsync){ + ready(0, function(){ + var requires = ["./fx/Toggler"]; + require(requires); // use indirection so modules not rolled into a build + }); +} - // states - this.containerState = ""; - dojo.addClass(this.node, "dojoDndContainer"); - - // mark up children - if(!(params && params._skipStartup)){ - this.startup(); - } + var coreFx = dojo.fx = {}; - // set up events - this.events = [ - dojo.connect(this.node, "onmouseover", this, "onMouseOver"), - dojo.connect(this.node, "onmouseout", this, "onMouseOut"), - // cancel text selection and text dragging - dojo.connect(this.node, "ondragstart", this, "onSelectStart"), - dojo.connect(this.node, "onselectstart", this, "onSelectStart") - ]; - }, - - // object attributes (for markup) - creator: function(){ - // summary: - // creator function, dummy at the moment - }, - - // abstract access to the map - getItem: function(/*String*/ key){ - // summary: - // returns a data item by its key (id) - return this.map[key]; // dojo.dnd.Item - }, - setItem: function(/*String*/ key, /*dojo.dnd.Item*/ data){ - // summary: - // associates a data item with its key (id) - this.map[key] = data; - }, - delItem: function(/*String*/ key){ - // summary: - // removes a data item from the map by its key (id) - delete this.map[key]; - }, - forInItems: function(/*Function*/ f, /*Object?*/ o){ - // summary: - // iterates over a data map skipping members that - // are present in the empty object (IE and/or 3rd-party libraries). - o = o || dojo.global; - var m = this.map, e = dojo.dnd._empty; - for(var i in m){ - if(i in e){ continue; } - f.call(o, m[i], i, this); - } - return o; // Object - }, - clearItems: function(){ - // summary: - // removes all data items from the map - this.map = {}; - }, - - // methods - getAllNodes: function(){ - // summary: - // returns a list (an array) of all valid child nodes - return dojo.query("> .dojoDndItem", this.parent); // NodeList - }, - sync: function(){ - // summary: - // sync up the node list with the data map - var map = {}; - this.getAllNodes().forEach(function(node){ - if(node.id){ - var item = this.getItem(node.id); - if(item){ - map[node.id] = item; - return; + var _baseObj = { + _fire: function(evt, args){ + if(this[evt]){ + this[evt].apply(this, args||[]); } - }else{ - node.id = dojo.dnd.getUniqueId(); + return this; } - var type = node.getAttribute("dndType"), - data = node.getAttribute("dndData"); - map[node.id] = { - data: data || node.innerHTML, - type: type ? type.split(/\s*,\s*/) : ["text"] - }; + }; + + var _chain = function(animations){ + this._index = -1; + this._animations = animations||[]; + this._current = this._onAnimateCtx = this._onEndCtx = null; + + this.duration = 0; + arrayUtil.forEach(this._animations, function(a){ + this.duration += a.duration; + if(a.delay){ this.duration += a.delay; } }, this); - this.map = map; - return this; // self - }, - insertNodes: function(data, before, anchor){ - // summary: - // inserts an array of new nodes before/after an anchor node - // data: Array - // a list of data items, which should be processed by the creator function - // before: Boolean - // insert before the anchor, if true, and after the anchor otherwise - // anchor: Node - // the anchor node to be used as a point of insertion - if(!this.parent.firstChild){ - anchor = null; - }else if(before){ - if(!anchor){ - anchor = this.parent.firstChild; - } - }else{ - if(anchor){ - anchor = anchor.nextSibling; - } - } - if(anchor){ - for(var i = 0; i < data.length; ++i){ - var t = this._normalizedCreator(data[i]); - this.setItem(t.node.id, {data: t.data, type: t.type}); - this.parent.insertBefore(t.node, anchor); + }; + _chain.prototype = new Evented(); + lang.extend(_chain, { + _onAnimate: function(){ + this._fire("onAnimate", arguments); + }, + _onEnd: function(){ + connect.disconnect(this._onAnimateCtx); + connect.disconnect(this._onEndCtx); + this._onAnimateCtx = this._onEndCtx = null; + if(this._index + 1 == this._animations.length){ + this._fire("onEnd"); + }else{ + // switch animations + this._current = this._animations[++this._index]; + this._onAnimateCtx = connect.connect(this._current, "onAnimate", this, "_onAnimate"); + this._onEndCtx = connect.connect(this._current, "onEnd", this, "_onEnd"); + this._current.play(0, true); } - }else{ - for(var i = 0; i < data.length; ++i){ - var t = this._normalizedCreator(data[i]); - this.setItem(t.node.id, {data: t.data, type: t.type}); - this.parent.appendChild(t.node); + }, + play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){ + if(!this._current){ this._current = this._animations[this._index = 0]; } + if(!gotoStart && this._current.status() == "playing"){ return this; } + var beforeBegin = connect.connect(this._current, "beforeBegin", this, function(){ + this._fire("beforeBegin"); + }), + onBegin = connect.connect(this._current, "onBegin", this, function(arg){ + this._fire("onBegin", arguments); + }), + onPlay = connect.connect(this._current, "onPlay", this, function(arg){ + this._fire("onPlay", arguments); + connect.disconnect(beforeBegin); + connect.disconnect(onBegin); + connect.disconnect(onPlay); + }); + if(this._onAnimateCtx){ + connect.disconnect(this._onAnimateCtx); } - } - return this; // self - }, - destroy: function(){ - // summary: - // prepares this object to be garbage-collected - dojo.forEach(this.events, dojo.disconnect); - this.clearItems(); - this.node = this.parent = this.current = null; - }, - - // markup methods - markupFactory: function(params, node){ - params._skipStartup = true; - return new dojo.dnd.Container(node, params); - }, - startup: function(){ - // summary: - // collects valid child items and populate the map - - // set up the real parent node - if(!this.parent){ - // use the standard algorithm, if not assigned - this.parent = this.node; - if(this.parent.tagName.toLowerCase() == "table"){ - var c = this.parent.getElementsByTagName("tbody"); - if(c && c.length){ this.parent = c[0]; } + this._onAnimateCtx = connect.connect(this._current, "onAnimate", this, "_onAnimate"); + if(this._onEndCtx){ + connect.disconnect(this._onEndCtx); } - } - this.defaultCreator = dojo.dnd._defaultCreator(this.parent); - - // process specially marked children - this.sync(); - }, - - // mouse events - onMouseOver: function(e){ - // summary: - // event processor for onmouseover - // e: Event - // mouse event - var n = e.relatedTarget; - while(n){ - if(n == this.node){ break; } - try{ - n = n.parentNode; - }catch(x){ - n = null; + this._onEndCtx = connect.connect(this._current, "onEnd", this, "_onEnd"); + this._current.play.apply(this._current, arguments); + return this; + }, + pause: function(){ + if(this._current){ + var e = connect.connect(this._current, "onPause", this, function(arg){ + this._fire("onPause", arguments); + connect.disconnect(e); + }); + this._current.pause(); } - } - if(!n){ - this._changeState("Container", "Over"); - this.onOverEvent(); - } - n = this._getChildByEvent(e); - if(this.current == n){ return; } - if(this.current){ this._removeItemClass(this.current, "Over"); } - if(n){ this._addItemClass(n, "Over"); } - this.current = n; - }, - onMouseOut: function(e){ - // summary: - // event processor for onmouseout - // e: Event - // mouse event - for(var n = e.relatedTarget; n;){ - if(n == this.node){ return; } - try{ - n = n.parentNode; - }catch(x){ - n = null; + return this; + }, + gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){ + this.pause(); + var offset = this.duration * percent; + this._current = null; + arrayUtil.some(this._animations, function(a){ + if(a.duration <= offset){ + this._current = a; + return true; + } + offset -= a.duration; + return false; + }); + if(this._current){ + this._current.gotoPercent(offset / this._current.duration, andPlay); } - } - if(this.current){ - this._removeItemClass(this.current, "Over"); - this.current = null; - } - this._changeState("Container", ""); - this.onOutEvent(); - }, - onSelectStart: function(e){ - // summary: - // event processor for onselectevent and ondragevent - // e: Event - // mouse event - if(!this.skipForm || !dojo.dnd.isFormElement(e)){ - dojo.stopEvent(e); - } - }, - - // utilities - onOverEvent: function(){ - // summary: - // this function is called once, when mouse is over our container - }, - onOutEvent: function(){ - // summary: - // this function is called once, when mouse is out of our container - }, - _changeState: function(type, newState){ - // summary: - // changes a named state to new state value - // type: String - // a name of the state to change - // newState: String - // new state - var prefix = "dojoDnd" + type; - var state = type.toLowerCase() + "State"; - //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]); - dojo.replaceClass(this.node, prefix + newState, prefix + this[state]); - this[state] = newState; - }, - _addItemClass: function(node, type){ - // summary: - // adds a class with prefix "dojoDndItem" - // node: Node - // a node - // type: String - // a variable suffix for a class name - dojo.addClass(node, "dojoDndItem" + type); - }, - _removeItemClass: function(node, type){ - // summary: - // removes a class with prefix "dojoDndItem" - // node: Node - // a node - // type: String - // a variable suffix for a class name - dojo.removeClass(node, "dojoDndItem" + type); - }, - _getChildByEvent: function(e){ - // summary: - // gets a child, which is under the mouse at the moment, or null - // e: Event - // a mouse event - var node = e.target; - if(node){ - for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){ - if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; } + return this; + }, + stop: function(/*boolean?*/ gotoEnd){ + if(this._current){ + if(gotoEnd){ + for(; this._index + 1 < this._animations.length; ++this._index){ + this._animations[this._index].stop(true); + } + this._current = this._animations[this._index]; + } + var e = connect.connect(this._current, "onStop", this, function(arg){ + this._fire("onStop", arguments); + connect.disconnect(e); + }); + this._current.stop(); } + return this; + }, + status: function(){ + return this._current ? this._current.status() : "stopped"; + }, + destroy: function(){ + if(this._onAnimateCtx){ connect.disconnect(this._onAnimateCtx); } + if(this._onEndCtx){ connect.disconnect(this._onEndCtx); } } - return null; - }, - _normalizedCreator: function(/*dojo.dnd.Item*/ item, /*String*/ hint){ - // summary: - // adds all necessary data to the output of the user-supplied creator function - var t = (this.creator || this.defaultCreator).call(this, item, hint); - if(!dojo.isArray(t.type)){ t.type = ["text"]; } - if(!t.node.id){ t.node.id = dojo.dnd.getUniqueId(); } - dojo.addClass(t.node, "dojoDndItem"); - return t; - } -}); + }); + lang.extend(_chain, _baseObj); -dojo.dnd._createNode = function(tag){ - // summary: - // returns a function, which creates an element of given tag - // (SPAN by default) and sets its innerHTML to given text - // tag: String - // a tag name or empty for SPAN - if(!tag){ return dojo.dnd._createSpan; } - return function(text){ // Function - return dojo.create(tag, {innerHTML: text}); // Node + coreFx.chain = /*===== dojo.fx.chain = =====*/ function(/*dojo.Animation[]*/ animations){ + // summary: + // Chain a list of `dojo.Animation`s to run in sequence + // + // description: + // Return a `dojo.Animation` which will play all passed + // `dojo.Animation` instances in sequence, firing its own + // synthesized events simulating a single animation. (eg: + // onEnd of this animation means the end of the chain, + // not the individual animations within) + // + // example: + // Once `node` is faded out, fade in `otherNode` + // | dojo.fx.chain([ + // | dojo.fadeIn({ node:node }), + // | dojo.fadeOut({ node:otherNode }) + // | ]).play(); + // + return new _chain(animations); // dojo.Animation }; -}; - -dojo.dnd._createTrTd = function(text){ - // summary: - // creates a TR/TD structure with given text as an innerHTML of TD - // text: String - // a text for TD - var tr = dojo.create("tr"); - dojo.create("td", {innerHTML: text}, tr); - return tr; // Node -}; -dojo.dnd._createSpan = function(text){ - // summary: - // creates a SPAN element with given text as its innerHTML - // text: String - // a text for SPAN - return dojo.create("span", {innerHTML: text}); // Node -}; + var _combine = function(animations){ + this._animations = animations||[]; + this._connects = []; + this._finished = 0; -// dojo.dnd._defaultCreatorNodes: Object -// a dictionary that maps container tag names to child tag names -dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"}; + this.duration = 0; + arrayUtil.forEach(animations, function(a){ + var duration = a.duration; + if(a.delay){ duration += a.delay; } + if(this.duration < duration){ this.duration = duration; } + this._connects.push(connect.connect(a, "onEnd", this, "_onEnd")); + }, this); -dojo.dnd._defaultCreator = function(node){ - // summary: - // takes a parent node, and returns an appropriate creator function - // node: Node - // a container node - var tag = node.tagName.toLowerCase(); - var c = tag == "tbody" || tag == "thead" ? dojo.dnd._createTrTd : - dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]); - return function(item, hint){ // Function - var isObj = item && dojo.isObject(item), data, type, n; - if(isObj && item.tagName && item.nodeType && item.getAttribute){ - // process a DOM node - data = item.getAttribute("dndData") || item.innerHTML; - type = item.getAttribute("dndType"); - type = type ? type.split(/\s*,\s*/) : ["text"]; - n = item; // this node is going to be moved rather than copied - }else{ - // process a DnD item object or a string - data = (isObj && item.data) ? item.data : item; - type = (isObj && item.type) ? item.type : ["text"]; - n = (hint == "avatar" ? dojo.dnd._createSpan : c)(String(data)); - } - if(!n.id){ - n.id = dojo.dnd.getUniqueId(); - } - return {node: n, data: data, type: type}; + this._pseudoAnimation = new baseFx.Animation({curve: [0, 1], duration: this.duration}); + var self = this; + arrayUtil.forEach(["beforeBegin", "onBegin", "onPlay", "onAnimate", "onPause", "onStop", "onEnd"], + function(evt){ + self._connects.push(connect.connect(self._pseudoAnimation, evt, + function(){ self._fire(evt, arguments); } + )); + } + ); }; -}; - -} - -if(!dojo._hasResource["dijit.tree._dndContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.tree._dndContainer"] = true; -dojo.provide("dijit.tree._dndContainer"); - - - - -dojo.getObject("tree", true, dojo); - -dijit.tree._compareNodes = function(n1, n2){ - if(n1 === n2){ - return 0; - } - - if('sourceIndex' in document.documentElement){ //IE - //TODO: does not yet work if n1 and/or n2 is a text node - return n1.sourceIndex - n2.sourceIndex; - }else if('compareDocumentPosition' in document.documentElement){ //FF, Opera - return n1.compareDocumentPosition(n2) & 2 ? 1: -1; - }else if(document.createRange){ //Webkit - var r1 = doc.createRange(); - r1.setStartBefore(n1); - - var r2 = doc.createRange(); - r2.setStartBefore(n2); - - return r1.compareBoundaryPoints(r1.END_TO_END, r2); - }else{ - throw Error("dijit.tree._compareNodes don't know how to compare two different nodes in this browser"); - } -}; - -dojo.declare("dijit.tree._dndContainer", - null, - { - - // summary: - // This is a base class for `dijit.tree._dndSelector`, and isn't meant to be used directly. - // It's modeled after `dojo.dnd.Container`. - // tags: - // protected - - /*===== - // current: DomNode - // The currently hovered TreeNode.rowNode (which is the DOM node - // associated w/a given node in the tree, excluding it's descendants) - current: null, - =====*/ - - constructor: function(tree, params){ - // summary: - // A constructor of the Container - // tree: Node - // Node or node's id to build the container on - // params: dijit.tree.__SourceArgs - // A dict of parameters, which gets mixed into the object - // tags: - // private - this.tree = tree; - this.node = tree.domNode; // TODO: rename; it's not a TreeNode but the whole Tree - dojo.mixin(this, params); - - // class-specific variables - this.map = {}; - this.current = null; // current TreeNode's DOM node - - // states - this.containerState = ""; - dojo.addClass(this.node, "dojoDndContainer"); - - // set up events - this.events = [ - // container level events - dojo.connect(this.node, "onmouseenter", this, "onOverEvent"), - dojo.connect(this.node, "onmouseleave", this, "onOutEvent"), - - // switching between TreeNodes - dojo.connect(this.tree, "_onNodeMouseEnter", this, "onMouseOver"), - dojo.connect(this.tree, "_onNodeMouseLeave", this, "onMouseOut"), - - // cancel text selection and text dragging - dojo.connect(this.node, "ondragstart", dojo, "stopEvent"), - dojo.connect(this.node, "onselectstart", dojo, "stopEvent") - ]; - }, - - getItem: function(/*String*/ key){ - // summary: - // Returns the dojo.dnd.Item (representing a dragged node) by it's key (id). - // Called by dojo.dnd.Source.checkAcceptance(). - // tags: - // protected - - var widget = this.selection[key], - ret = { - data: widget, - type: ["treeNode"] - }; - - return ret; // dojo.dnd.Item + lang.extend(_combine, { + _doAction: function(action, args){ + arrayUtil.forEach(this._animations, function(a){ + a[action].apply(a, args); + }); + return this; }, - - destroy: function(){ - // summary: - // Prepares this object to be garbage-collected - - dojo.forEach(this.events, dojo.disconnect); - // this.clearItems(); - this.node = this.parent = null; + _onEnd: function(){ + if(++this._finished > this._animations.length){ + this._fire("onEnd"); + } }, - - // mouse events - onMouseOver: function(/*TreeNode*/ widget, /*Event*/ evt){ - // summary: - // Called when mouse is moved over a TreeNode - // tags: - // protected - this.current = widget; + _call: function(action, args){ + var t = this._pseudoAnimation; + t[action].apply(t, args); }, - - onMouseOut: function(/*TreeNode*/ widget, /*Event*/ evt){ - // summary: - // Called when mouse is moved away from a TreeNode - // tags: - // protected - this.current = null; + play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){ + this._finished = 0; + this._doAction("play", arguments); + this._call("play", arguments); + return this; }, - - _changeState: function(type, newState){ - // summary: - // Changes a named state to new state value - // type: String - // A name of the state to change - // newState: String - // new state - var prefix = "dojoDnd" + type; - var state = type.toLowerCase() + "State"; - //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]); - dojo.replaceClass(this.node, prefix + newState, prefix + this[state]); - this[state] = newState; + pause: function(){ + this._doAction("pause", arguments); + this._call("pause", arguments); + return this; }, - - _addItemClass: function(node, type){ - // summary: - // Adds a class with prefix "dojoDndItem" - // node: Node - // A node - // type: String - // A variable suffix for a class name - dojo.addClass(node, "dojoDndItem" + type); + gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){ + var ms = this.duration * percent; + arrayUtil.forEach(this._animations, function(a){ + a.gotoPercent(a.duration < ms ? 1 : (ms / a.duration), andPlay); + }); + this._call("gotoPercent", arguments); + return this; }, - - _removeItemClass: function(node, type){ - // summary: - // Removes a class with prefix "dojoDndItem" - // node: Node - // A node - // type: String - // A variable suffix for a class name - dojo.removeClass(node, "dojoDndItem" + type); + stop: function(/*boolean?*/ gotoEnd){ + this._doAction("stop", arguments); + this._call("stop", arguments); + return this; }, - - onOverEvent: function(){ - // summary: - // This function is called once, when mouse is over our container - // tags: - // protected - this._changeState("Container", "Over"); + status: function(){ + return this._pseudoAnimation.status(); }, - - onOutEvent: function(){ - // summary: - // This function is called once, when mouse is out of our container - // tags: - // protected - this._changeState("Container", ""); + destroy: function(){ + arrayUtil.forEach(this._connects, connect.disconnect); } -}); + }); + lang.extend(_combine, _baseObj); -} + coreFx.combine = /*===== dojo.fx.combine = =====*/ function(/*dojo.Animation[]*/ animations){ + // summary: + // Combine a list of `dojo.Animation`s to run in parallel + // + // description: + // Combine an array of `dojo.Animation`s to run in parallel, + // providing a new `dojo.Animation` instance encompasing each + // animation, firing standard animation events. + // + // example: + // Fade out `node` while fading in `otherNode` simultaneously + // | dojo.fx.combine([ + // | dojo.fadeIn({ node:node }), + // | dojo.fadeOut({ node:otherNode }) + // | ]).play(); + // + // example: + // When the longest animation ends, execute a function: + // | var anim = dojo.fx.combine([ + // | dojo.fadeIn({ node: n, duration:700 }), + // | dojo.fadeOut({ node: otherNode, duration: 300 }) + // | ]); + // | dojo.connect(anim, "onEnd", function(){ + // | // overall animation is done. + // | }); + // | anim.play(); // play the animation + // + return new _combine(animations); // dojo.Animation + }; -if(!dojo._hasResource["dijit.tree._dndSelector"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.tree._dndSelector"] = true; -dojo.provide("dijit.tree._dndSelector"); + coreFx.wipeIn = /*===== dojo.fx.wipeIn = =====*/ function(/*Object*/ args){ + // summary: + // Expand a node to it's natural height. + // + // description: + // Returns an animation that will expand the + // node defined in 'args' object from it's current height to + // it's natural height (with no scrollbar). + // Node must have no margin/border/padding. + // + // args: Object + // A hash-map of standard `dojo.Animation` constructor properties + // (such as easing: node: duration: and so on) + // + // example: + // | dojo.fx.wipeIn({ + // | node:"someId" + // | }).play() + var node = args.node = dom.byId(args.node), s = node.style, o; + var anim = baseFx.animateProperty(lang.mixin({ + properties: { + height: { + // wrapped in functions so we wait till the last second to query (in case value has changed) + start: function(){ + // start at current [computed] height, but use 1px rather than 0 + // because 0 causes IE to display the whole panel + o = s.overflow; + s.overflow = "hidden"; + if(s.visibility == "hidden" || s.display == "none"){ + s.height = "1px"; + s.display = ""; + s.visibility = ""; + return 1; + }else{ + var height = domStyle.get(node, "height"); + return Math.max(height, 1); + } + }, + end: function(){ + return node.scrollHeight; + } + } + } + }, args)); + var fini = function(){ + s.height = "auto"; + s.overflow = o; + }; + connect.connect(anim, "onStop", fini); + connect.connect(anim, "onEnd", fini); + return anim; // dojo.Animation + }; -dojo.declare("dijit.tree._dndSelector", - dijit.tree._dndContainer, - { + coreFx.wipeOut = /*===== dojo.fx.wipeOut = =====*/ function(/*Object*/ args){ // summary: - // This is a base class for `dijit.tree.dndSource` , and isn't meant to be used directly. - // It's based on `dojo.dnd.Selector`. - // tags: - // protected + // Shrink a node to nothing and hide it. + // + // description: + // Returns an animation that will shrink node defined in "args" + // from it's current height to 1px, and then hide it. + // + // args: Object + // A hash-map of standard `dojo.Animation` constructor properties + // (such as easing: node: duration: and so on) + // + // example: + // | dojo.fx.wipeOut({ node:"someId" }).play() - /*===== - // selection: Hash<String, DomNode> - // (id, DomNode) map for every TreeNode that's currently selected. - // The DOMNode is the TreeNode.rowNode. - selection: {}, - =====*/ + var node = args.node = dom.byId(args.node), s = node.style, o; - constructor: function(tree, params){ - // summary: - // Initialization - // tags: - // private + var anim = baseFx.animateProperty(lang.mixin({ + properties: { + height: { + end: 1 // 0 causes IE to display the whole panel + } + } + }, args)); - this.selection={}; - this.anchor = null; + connect.connect(anim, "beforeBegin", function(){ + o = s.overflow; + s.overflow = "hidden"; + s.display = ""; + }); + var fini = function(){ + s.overflow = o; + s.height = "auto"; + s.display = "none"; + }; + connect.connect(anim, "onStop", fini); + connect.connect(anim, "onEnd", fini); - dijit.setWaiState(this.tree.domNode, "multiselect", !this.singular); + return anim; // dojo.Animation + }; - this.events.push( - dojo.connect(this.tree.domNode, "onmousedown", this,"onMouseDown"), - dojo.connect(this.tree.domNode, "onmouseup", this,"onMouseUp"), - dojo.connect(this.tree.domNode, "onmousemove", this,"onMouseMove") - ); - }, + coreFx.slideTo = /*===== dojo.fx.slideTo = =====*/ function(/*Object*/ args){ + // summary: + // Slide a node to a new top/left position + // + // description: + // Returns an animation that will slide "node" + // defined in args Object from its current position to + // the position defined by (args.left, args.top). + // + // args: Object + // A hash-map of standard `dojo.Animation` constructor properties + // (such as easing: node: duration: and so on). Special args members + // are `top` and `left`, which indicate the new position to slide to. + // + // example: + // | .slideTo({ node: node, left:"40", top:"50", units:"px" }).play() - // singular: Boolean - // Allows selection of only one element, if true. - // Tree hasn't been tested in singular=true mode, unclear if it works. - singular: false, + var node = args.node = dom.byId(args.node), + top = null, left = null; - // methods - getSelectedTreeNodes: function(){ - // summary: - // Returns a list of selected node(s). - // Used by dndSource on the start of a drag. - // tags: - // protected - var nodes=[], sel = this.selection; - for(var i in sel){ - nodes.push(sel[i]); + var init = (function(n){ + return function(){ + var cs = domStyle.getComputedStyle(n); + var pos = cs.position; + top = (pos == 'absolute' ? n.offsetTop : parseInt(cs.top) || 0); + left = (pos == 'absolute' ? n.offsetLeft : parseInt(cs.left) || 0); + if(pos != 'absolute' && pos != 'relative'){ + var ret = geom.position(n, true); + top = ret.y; + left = ret.x; + n.style.position="absolute"; + n.style.top=top+"px"; + n.style.left=left+"px"; + } + }; + })(node); + init(); + + var anim = baseFx.animateProperty(lang.mixin({ + properties: { + top: args.top || 0, + left: args.left || 0 } - return nodes; - }, + }, args)); + connect.connect(anim, "beforeBegin", anim, init); - selectNone: function(){ - // summary: - // Unselects all items - // tags: - // private + return anim; // dojo.Animation + }; - this.setSelection([]); - return this; // self - }, + return coreFx; +}); - destroy: function(){ - // summary: - // Prepares the object to be garbage-collected - this.inherited(arguments); - this.selection = this.anchor = null; - }, - addTreeNode: function(/*dijit._TreeNode*/node, /*Boolean?*/isAnchor){ - // summary - // add node to current selection - // node: Node - // node to add - // isAnchor: Boolean - // Whether the node should become anchor. +}, +'dijit/_DialogMixin':function(){ +define("dijit/_DialogMixin", [ + "dojo/_base/declare", // declare + "./a11y" // _getTabNavigable +], function(declare, a11y){ + + // module: + // dijit/_DialogMixin + // summary: + // _DialogMixin provides functions useful to Dialog and TooltipDialog - this.setSelection(this.getSelectedTreeNodes().concat( [node] )); - if(isAnchor){ this.anchor = node; } - return node; - }, - removeTreeNode: function(/*dijit._TreeNode*/node){ - // summary - // remove node from current selection - // node: Node - // node to remove - this.setSelection(this._setDifference(this.getSelectedTreeNodes(), [node])) - return node; - }, - isTreeNodeSelected: function(/*dijit._TreeNode*/node){ - // summary - // return true if node is currently selected - // node: Node - // the node to check whether it's in the current selection + return declare("dijit._DialogMixin", null, { + // summary: + // This provides functions useful to Dialog and TooltipDialog - return node.id && !!this.selection[node.id]; - }, - setSelection: function(/*dijit._treeNode[]*/ newSelection){ - // summary - // set the list of selected nodes to be exactly newSelection. All changes to the - // selection should be passed through this function, which ensures that derived - // attributes are kept up to date. Anchor will be deleted if it has been removed - // from the selection, but no new anchor will be added by this function. - // newSelection: Node[] - // list of tree nodes to make selected - var oldSelection = this.getSelectedTreeNodes(); - dojo.forEach(this._setDifference(oldSelection, newSelection), dojo.hitch(this, function(node){ - node.setSelected(false); - if(this.anchor == node){ - delete this.anchor; - } - delete this.selection[node.id]; - })); - dojo.forEach(this._setDifference(newSelection, oldSelection), dojo.hitch(this, function(node){ - node.setSelected(true); - this.selection[node.id] = node; - })); - this._updateSelectionProperties(); + execute: function(/*Object*/ /*===== formContents =====*/){ + // summary: + // Callback when the user hits the submit button. + // Override this method to handle Dialog execution. + // description: + // After the user has pressed the submit button, the Dialog + // first calls onExecute() to notify the container to hide the + // dialog and restore focus to wherever it used to be. + // + // *Then* this method is called. + // type: + // callback }, - _setDifference: function(xs,ys){ - // summary - // Returns a copy of xs which lacks any objects - // occurring in ys. Checks for membership by - // modifying and then reading the object, so it will - // not properly handle sets of numbers or strings. - - dojo.forEach(ys, function(y){ y.__exclude__ = true; }); - var ret = dojo.filter(xs, function(x){ return !x.__exclude__; }); - // clean up after ourselves. - dojo.forEach(ys, function(y){ delete y['__exclude__'] }); - return ret; - }, - _updateSelectionProperties: function() { - // summary - // Update the following tree properties from the current selection: - // path[s], selectedItem[s], selectedNode[s] - - var selected = this.getSelectedTreeNodes(); - var paths = [], nodes = []; - dojo.forEach(selected, function(node) { - nodes.push(node); - paths.push(node.getTreePath()); - }); - var items = dojo.map(nodes,function(node) { return node.item; }); - this.tree._set("paths", paths); - this.tree._set("path", paths[0] || []); - this.tree._set("selectedNodes", nodes); - this.tree._set("selectedNode", nodes[0] || null); - this.tree._set("selectedItems", items); - this.tree._set("selectedItem", items[0] || null); - }, - // mouse events - onMouseDown: function(e){ + onCancel: function(){ // summary: - // Event processor for onmousedown - // e: Event - // mouse event - // tags: + // Called when user has pressed the Dialog's cancel button, to notify container. + // description: + // Developer shouldn't override or connect to this method; + // it's a private communication device between the TooltipDialog + // and the thing that opened it (ex: `dijit.form.DropDownButton`) + // type: // protected - - // ignore click on expando node - if(!this.current || this.tree.isExpandoNode( e.target, this.current)){ return; } - - if(e.button == dojo.mouseButtons.RIGHT){ return; } // ignore right-click - - dojo.stopEvent(e); - - var treeNode = this.current, - copy = dojo.isCopyKey(e), id = treeNode.id; - - // if shift key is not pressed, and the node is already in the selection, - // delay deselection until onmouseup so in the case of DND, deselection - // will be canceled by onmousemove. - if(!this.singular && !e.shiftKey && this.selection[id]){ - this._doDeselect = true; - return; - }else{ - this._doDeselect = false; - } - this.userSelect(treeNode, copy, e.shiftKey); }, - onMouseUp: function(e){ + onExecute: function(){ // summary: - // Event processor for onmouseup - // e: Event - // mouse event - // tags: + // Called when user has pressed the dialog's OK button, to notify container. + // description: + // Developer shouldn't override or connect to this method; + // it's a private communication device between the TooltipDialog + // and the thing that opened it (ex: `dijit.form.DropDownButton`) + // type: // protected - - // _doDeselect is the flag to indicate that the user wants to either ctrl+click on - // a already selected item (to deselect the item), or click on a not-yet selected item - // (which should remove all current selection, and add the clicked item). This can not - // be done in onMouseDown, because the user may start a drag after mousedown. By moving - // the deselection logic here, the user can drags an already selected item. - if(!this._doDeselect){ return; } - this._doDeselect = false; - this.userSelect(this.current, dojo.isCopyKey( e ), e.shiftKey); - }, - onMouseMove: function(e){ - // summary - // event processor for onmousemove - // e: Event - // mouse event - this._doDeselect = false; }, - userSelect: function(node, multi, range){ + _onSubmit: function(){ // summary: - // Add or remove the given node from selection, responding - // to a user action such as a click or keypress. - // multi: Boolean - // Indicates whether this is meant to be a multi-select action (e.g. ctrl-click) - // range: Boolean - // Indicates whether this is meant to be a ranged action (e.g. shift-click) - // tags: + // Callback when user hits submit button + // type: // protected - - if(this.singular){ - if(this.anchor == node && multi){ - this.selectNone(); - }else{ - this.setSelection([node]); - this.anchor = node; - } - }else{ - if(range && this.anchor){ - var cr = dijit.tree._compareNodes(this.anchor.rowNode, node.rowNode), - begin, end, anchor = this.anchor; - - if(cr < 0){ //current is after anchor - begin = anchor; - end = node; - }else{ //current is before anchor - begin = node; - end = anchor; - } - nodes = []; - //add everything betweeen begin and end inclusively - while(begin != end) { - nodes.push(begin) - begin = this.tree._getNextNode(begin); - } - nodes.push(end) - - this.setSelection(nodes); - }else{ - if( this.selection[ node.id ] && multi ) { - this.removeTreeNode( node ); - } else if(multi) { - this.addTreeNode(node, true); - } else { - this.setSelection([node]); - this.anchor = node; - } - } - } + this.onExecute(); // notify container that we are about to execute + this.execute(this.get('value')); }, - forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){ + _getFocusItems: function(){ // summary: - // Iterates over selected items; - // see `dojo.dnd.Container.forInItems()` for details - o = o || dojo.global; - for(var id in this.selection){ - // console.log("selected item id: " + id); - f.call(o, this.getItem(id), id, this); - } + // Finds focusable items in dialog, + // and sets this._firstFocusItem and this._lastFocusItem + // tags: + // protected + + var elems = a11y._getTabNavigable(this.containerNode); + this._firstFocusItem = elems.lowest || elems.first || this.closeButtonNode || this.domNode; + this._lastFocusItem = elems.last || elems.highest || this._firstFocusItem; } + }); }); -} - -if(!dojo._hasResource["dijit.Tree"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.Tree"] = true; -dojo.provide("dijit.Tree"); - - - - - - - - - +}, +'dijit/Tree':function(){ +require({cache:{ +'url:dijit/templates/TreeNode.html':"<div class=\"dijitTreeNode\" role=\"presentation\"\n\t><div data-dojo-attach-point=\"rowNode\" class=\"dijitTreeRow\" role=\"presentation\" data-dojo-attach-event=\"onmouseenter:_onMouseEnter, onmouseleave:_onMouseLeave, onclick:_onClick, ondblclick:_onDblClick\"\n\t\t><img src=\"${_blankGif}\" alt=\"\" data-dojo-attach-point=\"expandoNode\" class=\"dijitTreeExpando\" role=\"presentation\"\n\t\t/><span data-dojo-attach-point=\"expandoNodeText\" class=\"dijitExpandoText\" role=\"presentation\"\n\t\t></span\n\t\t><span data-dojo-attach-point=\"contentNode\"\n\t\t\tclass=\"dijitTreeContent\" role=\"presentation\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" data-dojo-attach-point=\"iconNode\" class=\"dijitIcon dijitTreeIcon\" role=\"presentation\"\n\t\t\t/><span data-dojo-attach-point=\"labelNode\" class=\"dijitTreeLabel\" role=\"treeitem\" tabindex=\"-1\" aria-selected=\"false\" data-dojo-attach-event=\"onfocus:_onLabelFocus\"></span>\n\t\t</span\n\t></div>\n\t<div data-dojo-attach-point=\"containerNode\" class=\"dijitTreeContainer\" role=\"presentation\" style=\"display: none;\"></div>\n</div>\n", +'url:dijit/templates/Tree.html':"<div class=\"dijitTree dijitTreeContainer\" role=\"tree\"\n\tdata-dojo-attach-event=\"onkeypress:_onKeyPress\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" data-dojo-attach-point=\"indentDetector\"></div>\n</div>\n"}}); +define("dijit/Tree", [ + "dojo/_base/array", // array.filter array.forEach array.map + "dojo/_base/connect", // connect.isCopyKey() + "dojo/cookie", // cookie + "dojo/_base/declare", // declare + "dojo/_base/Deferred", // Deferred + "dojo/DeferredList", // DeferredList + "dojo/dom", // dom.isDescendant + "dojo/dom-class", // domClass.add domClass.remove domClass.replace domClass.toggle + "dojo/dom-geometry", // domGeometry.setMarginBox domGeometry.position + "dojo/dom-style",// domStyle.set + "dojo/_base/event", // event.stop + "dojo/fx", // fxUtils.wipeIn fxUtils.wipeOut + "dojo/_base/kernel", // kernel.deprecated + "dojo/keys", // arrows etc. + "dojo/_base/lang", // lang.getObject lang.mixin lang.hitch + "dojo/topic", + "./focus", + "./registry", // registry.getEnclosingWidget(), manager.defaultDuration + "./_base/manager", // manager.getEnclosingWidget(), manager.defaultDuration + "./_Widget", + "./_TemplatedMixin", + "./_Container", + "./_Contained", + "./_CssStateMixin", + "dojo/text!./templates/TreeNode.html", + "dojo/text!./templates/Tree.html", + "./tree/TreeStoreModel", + "./tree/ForestStoreModel", + "./tree/_dndSelector" +], function(array, connect, cookie, declare, Deferred, DeferredList, + dom, domClass, domGeometry, domStyle, event, fxUtils, kernel, keys, lang, topic, + focus, registry, manager, _Widget, _TemplatedMixin, _Container, _Contained, _CssStateMixin, + treeNodeTemplate, treeTemplate, TreeStoreModel, ForestStoreModel, _dndSelector){ +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _CssStateMixin = dijit._CssStateMixin; + var _Container = dijit._Container; + var _Contained = dijit._Contained; +=====*/ +// module: +// dijit/Tree +// summary: +// dijit.Tree widget, and internal dijit._TreeNode widget -dojo.declare( +var TreeNode = declare( "dijit._TreeNode", - [dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained, dijit._CssStateMixin], + [_Widget, _TemplatedMixin, _Container, _Contained, _CssStateMixin], { // summary: // Single node within a tree. This class is used internally @@ -23590,7 +30549,7 @@ dojo.declare( // tags: // private - // item: [const] dojo.data.Item + // item: [const] Item // the dojo.data entry this tree represents item: null, @@ -23602,6 +30561,7 @@ dojo.declare( // label: String // Text of this tree node label: "", + _setLabelAttr: {node: "labelNode", type: "innerText"}, // isExpandable: [private] Boolean // This node has children, so show the expando node (+ sign) @@ -23617,7 +30577,7 @@ dojo.declare( // then after dojo.data query it becomes "LOADING" and, finally "LOADED" state: "UNCHECKED", - templateString: dojo.cache("dijit", "templates/TreeNode.html", "<div class=\"dijitTreeNode\" 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"), + templateString: treeNodeTemplate, baseClass: "dijitTreeNode", @@ -23627,10 +30587,8 @@ dojo.declare( labelNode: "dijitTreeLabel" }, - attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { - label: {node: "labelNode", type: "innerText"}, - tooltip: {node: "rowNode", type: "attribute", attribute: "title"} - }), + // Tooltip is defined in _WidgetBase but we need to handle the mapping to DOM here + _setTooltipAttr: {node: "rowNode", type: "attribute", attribute: "title"}, buildRendering: function(){ this.inherited(arguments); @@ -23642,7 +30600,7 @@ dojo.declare( this._updateItemClasses(this.item); if(this.isExpandable){ - dijit.setWaiState(this.labelNode, "expanded", this.isExpanded); + this.labelNode.setAttribute("aria-expanded", this.isExpanded); } //aria-selected should be false on all selectable elements. @@ -23659,13 +30617,13 @@ dojo.declare( // Math.max() is to prevent negative padding on hidden root node (when indent == -1) var pixels = (Math.max(indent, 0) * this.tree._nodePixelIndent) + "px"; - dojo.style(this.domNode, "backgroundPosition", pixels + " 0px"); - dojo.style(this.rowNode, this.isLeftToRight() ? "paddingLeft" : "paddingRight", pixels); + domStyle.set(this.domNode, "backgroundPosition", pixels + " 0px"); + domStyle.set(this.rowNode, this.isLeftToRight() ? "paddingLeft" : "paddingRight", pixels); - dojo.forEach(this.getChildren(), function(child){ + array.forEach(this.getChildren(), function(child){ child.set("indent", indent+1); }); - + this._set("indent", indent); }, @@ -23723,9 +30681,9 @@ dojo.declare( var oldCls = this[clsName]; this[clsName] = this.tree["get" + upper + "Class"](item, this.isExpanded); - dojo.replaceClass(this[nodeName], this[clsName] || "", oldCls || ""); - - dojo.style(this[nodeName], this.tree["get" + upper + "Style"](item, this.isExpanded) || {}); + domClass.replace(this[nodeName], this[clsName] || "", oldCls || ""); + + domStyle.set(this[nodeName], this.tree["get" + upper + "Style"](item, this.isExpanded) || {}); }, _updateLayout: function(){ @@ -23734,11 +30692,11 @@ dojo.declare( // tags: // private var parent = this.getParent(); - if(!parent || parent.rowNode.style.display == "none"){ + if(!parent || !parent.rowNode || parent.rowNode.style.display == "none"){ /* if we are hiding the root node then make every first level child look like a root node */ - dojo.addClass(this.domNode, "dijitTreeIsRoot"); + domClass.add(this.domNode, "dijitTreeIsRoot"); }else{ - dojo.toggleClass(this.domNode, "dijitTreeIsLast", !this.getNextSibling()); + domClass.toggle(this.domNode, "dijitTreeIsLast", !this.getNextSibling()); } }, @@ -23754,7 +30712,7 @@ dojo.declare( idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3); // apply the appropriate class to the expando node - dojo.replaceClass(this.expandoNode, styles[idx], styles); + domClass.replace(this.expandoNode, styles[idx], styles); // provide a non-image based indicator for images-off mode this.expandoNodeText.innerHTML = _a11yStates[idx]; @@ -23778,27 +30736,27 @@ dojo.declare( // All the state information for when a node is expanded, maybe this should be // set when the animation completes instead this.isExpanded = true; - dijit.setWaiState(this.labelNode, "expanded", "true"); + this.labelNode.setAttribute("aria-expanded", "true"); if(this.tree.showRoot || this !== this.tree.rootNode){ - dijit.setWaiRole(this.containerNode, "group"); + this.containerNode.setAttribute("role", "group"); } - dojo.addClass(this.contentNode,'dijitTreeContentExpanded'); + domClass.add(this.contentNode,'dijitTreeContentExpanded'); this._setExpando(); this._updateItemClasses(this.item); if(this == this.tree.rootNode){ - dijit.setWaiState(this.tree.domNode, "expanded", "true"); + this.tree.domNode.setAttribute("aria-expanded", "true"); } var def, - wipeIn = dojo.fx.wipeIn({ - node: this.containerNode, duration: dijit.defaultDuration, + wipeIn = fxUtils.wipeIn({ + node: this.containerNode, duration: manager.defaultDuration, onEnd: function(){ def.callback(true); } }); // Deferred that fires when expand is complete - def = (this._expandDeferred = new dojo.Deferred(function(){ + def = (this._expandDeferred = new Deferred(function(){ // Canceller wipeIn.stop(); })); @@ -23821,17 +30779,17 @@ dojo.declare( } this.isExpanded = false; - dijit.setWaiState(this.labelNode, "expanded", "false"); + this.labelNode.setAttribute("aria-expanded", "false"); if(this == this.tree.rootNode){ - dijit.setWaiState(this.tree.domNode, "expanded", "false"); + this.tree.domNode.setAttribute("aria-expanded", "false"); } - dojo.removeClass(this.contentNode,'dijitTreeContentExpanded'); + domClass.remove(this.contentNode,'dijitTreeContentExpanded'); this._setExpando(); this._updateItemClasses(this.item); if(!this._wipeOut){ - this._wipeOut = dojo.fx.wipeOut({ - node: this.containerNode, duration: dijit.defaultDuration + this._wipeOut = fxUtils.wipeOut({ + node: this.containerNode, duration: manager.defaultDuration }); } this._wipeOut.play(); @@ -23859,8 +30817,8 @@ dojo.declare( // Orphan all my existing children. // If items contains some of the same items as before then we will reattach them. // Don't call this.removeChild() because that will collapse the tree etc. - dojo.forEach(this.getChildren(), function(child){ - dijit._Container.prototype.removeChild.call(this, child); + array.forEach(this.getChildren(), function(child){ + _Container.prototype.removeChild.call(this, child); }, this); this.state = "LOADED"; @@ -23871,7 +30829,7 @@ dojo.declare( // Create _TreeNode widget for each specified tree node, unless one already // exists and isn't being used (presumably it's from a DnD move and was recently // released - dojo.forEach(items, function(item){ + array.forEach(items, function(item){ var id = model.getIdentity(item), existingNodes = tree._itemNodesMap[id], node; @@ -23893,6 +30851,7 @@ dojo.declare( tooltip: tree.getTooltip(item), dir: tree.dir, lang: tree.lang, + textDir: tree.textDir, indent: this.indent + 1 }); if(existingNodes){ @@ -23905,14 +30864,14 @@ dojo.declare( // If node was previously opened then open it again now (this may trigger // more data store accesses, recursively) - if(this.tree.autoExpand || this.tree._state(item)){ + if(this.tree.autoExpand || this.tree._state(node)){ defs.push(tree._expandNode(node)); } }, this); // note that updateLayout() needs to be called on each child after // _all_ the children exist - dojo.forEach(this.getChildren(), function(child, idx){ + array.forEach(this.getChildren(), function(child){ child._updateLayout(); }); }else{ @@ -23940,7 +30899,7 @@ dojo.declare( } } - return new dojo.DeferredList(defs); // dojo.Deferred + return new DeferredList(defs); // dojo.Deferred }, getTreePath: function(){ @@ -23955,7 +30914,7 @@ dojo.declare( return path; }, - getIdentity: function() { + getIdentity: function(){ return this.tree.model.getIdentity(this.item); }, @@ -23968,7 +30927,7 @@ dojo.declare( this.collapse(); } - dojo.forEach(children, function(child){ + array.forEach(children, function(child){ child._updateLayout(); }); }, @@ -23984,7 +30943,7 @@ dojo.declare( this._setExpando(false); }, - _onLabelFocus: function(evt){ + _onLabelFocus: function(){ // summary: // Called when this row is focused (possibly programatically) // Note that we aren't using _onFocus() builtin to dijit @@ -24001,8 +30960,8 @@ dojo.declare( // description: // In particular, setting a node as selected involves setting tabIndex // so that when user tabs to the tree, focus will go to that node (only). - dijit.setWaiState(this.labelNode, "selected", selected); - dojo.toggleClass(this.rowNode, "dijitTreeRowSelected", selected); + this.labelNode.setAttribute("aria-selected", selected); + domClass.toggle(this.rowNode, "dijitTreeRowSelected", selected); }, setFocusable: function(/*Boolean*/ selected){ @@ -24045,13 +31004,20 @@ dojo.declare( // tags: // private this.tree._onNodeMouseLeave(this, evt); + }, + + _setTextDirAttr: function(textDir){ + if(textDir &&((this.textDir != textDir) || !this._created)){ + this._set("textDir", textDir); + this.applyTextDir(this.labelNode, this.labelNode.innerText || this.labelNode.textContent || ""); + array.forEach(this.getChildren(), function(childNode){ + childNode.set("textDir", textDir); + }, this); + } } }); -dojo.declare( - "dijit.Tree", - [dijit._Widget, dijit._Templated], -{ +var Tree = declare("dijit.Tree", [_Widget, _TemplatedMixin], { // summary: // This widget displays hierarchical data from a store. @@ -24091,7 +31057,7 @@ dojo.declare( // Since setting the paths may be asynchronous (because ofwaiting on dojo.data), set("paths", ...) // returns a Deferred to indicate when the set is complete. paths: [], - + // path: String[] or Item[] // Backward compatible singular variant of paths. path: [], @@ -24115,7 +31081,7 @@ dojo.declare( // If true, double-clicking a folder node's label will open it, rather than calling onDblClick() openOnDblClick: false, - templateString: dojo.cache("dijit", "templates/Tree.html", "<div class=\"dijitTree dijitTreeContainer\" role=\"tree\"\n\tdojoAttachEvent=\"onkeypress:_onKeyPress\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" dojoAttachPoint=\"indentDetector\"></div>\n</div>\n"), + templateString: treeTemplate, // persist: Boolean // Enables/disables use of cookies for state saving. @@ -24125,11 +31091,11 @@ dojo.declare( // Fully expand the tree on load. Overrides `persist`. autoExpand: false, - // dndController: [protected] String - // Class name to use as as the dnd controller. Specifying this class enables DnD. - // Generally you should specify this as "dijit.tree.dndSource". - // Default of "dijit.tree._dndSelector" handles selection only (no actual DnD). - dndController: "dijit.tree._dndSelector", + // dndController: [protected] Function|String + // Class to use as as the dnd controller. Specifying this class enables DnD. + // Generally you should specify this as dijit.tree.dndSource. + // Setting of dijit.tree._dndSelector handles selection only (no actual DnD). + dndController: _dndSelector, // parameters to pull off of the tree and pass on to the dndController as its params dndParams: ["onDndDrop","itemCreator","onDndCancel","checkAcceptance", "checkItemAcceptance", "dragThreshold", "betweenThreshold"], @@ -24238,7 +31204,7 @@ dojo.declare( _publish: function(/*String*/ topicName, /*Object*/ message){ // summary: // Publish a message for this widget/topic - dojo.publish(this.id, [dojo.mixin({tree: this, event: topicName}, message || {})]); + topic.publish(this.id, lang.mixin({tree: this, event: topicName}, message || {})); // publish }, postMixInProperties: function(){ @@ -24252,11 +31218,11 @@ dojo.declare( this._itemNodesMap={}; - if(!this.cookieName){ + if(!this.cookieName && this.id){ this.cookieName = this.id + "SaveStateCookie"; } - this._loadDeferred = new dojo.Deferred(); + this._loadDeferred = new Deferred(); this.inherited(arguments); }, @@ -24279,8 +31245,8 @@ dojo.declare( this.inherited(arguments); if(this.dndController){ - if(dojo.isString(this.dndController)){ - this.dndController = dojo.getObject(this.dndController); + if(lang.isString(this.dndController)){ + this.dndController = lang.getObject(this.dndController); } var params={}; for(var i=0; i<this.dndParams.length;i++){ @@ -24296,7 +31262,7 @@ dojo.declare( // summary: // User specified a store&query rather than model, so create model from store/query this._v10Compat = true; - dojo.deprecated("Tree: from version 2.0, should specify a model object rather than a store/query"); + kernel.deprecated("Tree: from version 2.0, should specify a model object rather than a store/query"); var modelParams = { id: this.id + "_ForestStoreModel", @@ -24307,15 +31273,15 @@ dojo.declare( // Only override the model's mayHaveChildren() method if the user has specified an override if(this.params.mayHaveChildren){ - modelParams.mayHaveChildren = dojo.hitch(this, "mayHaveChildren"); + modelParams.mayHaveChildren = lang.hitch(this, "mayHaveChildren"); } if(this.params.getItemChildren){ - modelParams.getChildren = dojo.hitch(this, function(item, onComplete, onError){ + modelParams.getChildren = lang.hitch(this, function(item, onComplete, onError){ this.getItemChildren((this._v10Compat && item === this.model.root) ? null : item, onComplete, onError); }); } - this.model = new dijit.tree.ForestStoreModel(modelParams); + this.model = new ForestStoreModel(modelParams); // For backwards compatibility, the visibility of the root node is controlled by // whether or not the user has specified a label @@ -24337,22 +31303,23 @@ dojo.declare( // Initial load of the tree. // Load root node (possibly hidden) and it's children. this.model.getRoot( - dojo.hitch(this, function(item){ + lang.hitch(this, function(item){ var rn = (this.rootNode = this.tree._createTreeNode({ item: item, tree: this, isExpandable: true, label: this.label || this.getLabel(item), + textDir: this.textDir, indent: this.showRoot ? 0 : -1 })); if(!this.showRoot){ rn.rowNode.style.display="none"; // if root is not visible, move tree role to the invisible // root node's containerNode, see #12135 - dijit.setWaiRole(this.domNode, 'presentation'); - - dijit.setWaiRole(rn.labelNode, 'presentation'); - dijit.setWaiRole(rn.containerNode, 'tree'); + this.domNode.setAttribute("role", "presentation"); + + rn.labelNode.setAttribute("role", "presentation"); + rn.containerNode.setAttribute("role", "tree"); } this.domNode.appendChild(rn.domNode); var identity = this.model.getIdentity(item); @@ -24365,7 +31332,7 @@ dojo.declare( rn._updateLayout(); // sets "dijitTreeIsRoot" CSS classname // load top level children and then fire onLoad() event - this._expandNode(rn).addCallback(dojo.hitch(this, function(){ + this._expandNode(rn).addCallback(lang.hitch(this, function(){ this._loadDeferred.callback(true); this.onLoad(); })); @@ -24376,34 +31343,34 @@ dojo.declare( ); }, - getNodesByItem: function(/*dojo.data.Item or id*/ item){ + getNodesByItem: function(/*Item or id*/ item){ // summary: // Returns all tree nodes that refer to an item // returns: // Array of tree nodes that refer to passed item if(!item){ return []; } - var identity = dojo.isString(item) ? item : this.model.getIdentity(item); + var identity = lang.isString(item) ? item : this.model.getIdentity(item); // return a copy so widget don't get messed up by changes to returned array return [].concat(this._itemNodesMap[identity]); }, - _setSelectedItemAttr: function(/*dojo.data.Item or id*/ item){ + _setSelectedItemAttr: function(/*Item or id*/ item){ this.set('selectedItems', [item]); }, - _setSelectedItemsAttr: function(/*dojo.data.Items or ids*/ items){ + _setSelectedItemsAttr: function(/*Items or ids*/ items){ // summary: // Select tree nodes related to passed items. // WARNING: if model use multi-parented items or desired tree node isn't already loaded // behavior is undefined. Use set('paths', ...) instead. var tree = this; - this._loadDeferred.addCallback( dojo.hitch(this, function(){ - var identities = dojo.map(items, function(item){ - return (!item || dojo.isString(item)) ? item : tree.model.getIdentity(item); + this._loadDeferred.addCallback( lang.hitch(this, function(){ + var identities = array.map(items, function(item){ + return (!item || lang.isString(item)) ? item : tree.model.getIdentity(item); }); var nodes = []; - dojo.forEach(identities, function(id){ + array.forEach(identities, function(id){ nodes = nodes.concat(tree._itemNodesMap[id] || []); }); this.set('selectedNodes', nodes); @@ -24413,14 +31380,14 @@ dojo.declare( _setPathAttr: function(/*Item[] || String[]*/ path){ // summary: // Singular variant of _setPathsAttr - if(path.length) { + if(path.length){ return this.set("paths", [path]); - } else { - //Empty list is interpreted as "select nothing" + }else{ + // Empty list is interpreted as "select nothing" return this.set("paths", []); } }, - + _setPathsAttr: function(/*Item[][] || String[][]*/ paths){ // summary: // Select the tree nodes identified by passed paths. @@ -24433,12 +31400,12 @@ dojo.declare( // We may need to wait for some nodes to expand, so setting // each path will involve a Deferred. We bring those deferreds // together witha DeferredList. - return new dojo.DeferredList(dojo.map(paths, function(path){ - var d = new dojo.Deferred(); - + return new DeferredList(array.map(paths, function(path){ + var d = new Deferred(); + // normalize path to use identity - path = dojo.map(path, function(item){ - return dojo.isString(item) ? item : tree.model.getIdentity(item); + path = array.map(path, function(item){ + return lang.isString(item) ? item : tree.model.getIdentity(item); }); if(path.length){ @@ -24453,7 +31420,7 @@ dojo.declare( function selectPath(path, nodes, def){ // Traverse path; the next path component should be among "nodes". var nextPath = path.shift(); - var nextNode = dojo.filter(nodes, function(node){ + var nextNode = array.filter(nodes, function(node){ return node.getIdentity() == nextPath; })[0]; if(!!nextNode){ @@ -24463,16 +31430,16 @@ dojo.declare( //Successfully reached the end of this path def.callback(nextNode); } - } else { + }else{ def.errback("Could not expand path at " + nextPath); } } - + function setNodes(newNodes){ //After all expansion is finished, set the selection to //the set of nodes successfully found. - tree.set("selectedNodes", dojo.map( - dojo.filter(newNodes,function(x){return x[0];}), + tree.set("selectedNodes", array.map( + array.filter(newNodes,function(x){return x[0];}), function(x){return x[1];})); } }, @@ -24481,7 +31448,7 @@ dojo.declare( this.set('selectedNodes', [node]); }, _setSelectedNodesAttr: function(nodes){ - this._loadDeferred.addCallback( dojo.hitch(this, function(){ + this._loadDeferred.addCallback( lang.hitch(this, function(){ this.dndController.setSelection(nodes); })); }, @@ -24490,7 +31457,7 @@ dojo.declare( ////////////// Data store related functions ////////////////////// // These just get passed to the model; they are here for back-compat - mayHaveChildren: function(/*dojo.data.Item*/ item){ + mayHaveChildren: function(/*dojo.data.Item*/ /*===== item =====*/){ // summary: // Deprecated. This should be specified on the model itself. // @@ -24502,7 +31469,7 @@ dojo.declare( // deprecated }, - getItemChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete){ + getItemChildren: function(/*===== parentItem, onComplete =====*/){ // summary: // Deprecated. This should be specified on the model itself. // @@ -24530,48 +31497,62 @@ dojo.declare( return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf" }, - getLabelClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + getLabelClass: function(/*===== item, opened =====*/){ // summary: // Overridable function to return CSS class name to display label + // item: dojo.data.Item + // opened: Boolean + // returns: String + // CSS class name // tags: // extension }, - getRowClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + getRowClass: function(/*===== item, opened =====*/){ // summary: // Overridable function to return CSS class name to display row + // item: dojo.data.Item + // opened: Boolean + // returns: String + // CSS class name // tags: // extension }, - getIconStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + getIconStyle: function(/*===== item, opened =====*/){ // summary: // Overridable function to return CSS styles to display icon - // returns: + // item: dojo.data.Item + // opened: Boolean + // returns: Object // Object suitable for input to dojo.style() like {backgroundImage: "url(...)"} // tags: // extension }, - getLabelStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + getLabelStyle: function(/*===== item, opened =====*/){ // summary: // Overridable function to return CSS styles to display label + // item: dojo.data.Item + // opened: Boolean // returns: // Object suitable for input to dojo.style() like {color: "red", background: "green"} // tags: // extension }, - getRowStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + getRowStyle: function(/*===== item, opened =====*/){ // summary: // Overridable function to return CSS styles to display row + // item: dojo.data.Item + // opened: Boolean // returns: // Object suitable for input to dojo.style() like {background-color: "#bbb"} // tags: // extension }, - getTooltip: function(/*dojo.data.Item*/ item){ + getTooltip: function(/*dojo.data.Item*/ /*===== item =====*/){ // summary: // Overridable function to get the tooltip for a tree node (given the item) // tags: @@ -24585,8 +31566,7 @@ dojo.declare( // summary: // Translates keypress events into commands for the controller if(e.altKey){ return; } - var dk = dojo.keys; - var treeNode = dijit.getEnclosingWidget(e.target); + var treeNode = registry.getEnclosingWidget(e.target); if(!treeNode){ return; } var key = e.charOrCode; @@ -24594,7 +31574,7 @@ dojo.declare( // Check for key navigation. if(!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey){ this._onLetterKeyNav( { node: treeNode, key: key.toLowerCase() } ); - dojo.stopEvent(e); + event.stop(e); } }else{ // handle non-printables (arrow keys) // clear record of recent printables (being saved for multi-char letter navigation), @@ -24608,30 +31588,30 @@ dojo.declare( if(!map){ // setup table mapping keys to events map = {}; - map[dk.ENTER]="_onEnterKey"; + map[keys.ENTER]="_onEnterKey"; //On WebKit based browsers, the combination ctrl-enter //does not get passed through. To allow accessible //multi-select on those browsers, the space key is //also used for selection. - map[dk.SPACE]= map[" "] = "_onEnterKey"; - map[this.isLeftToRight() ? dk.LEFT_ARROW : dk.RIGHT_ARROW]="_onLeftArrow"; - map[this.isLeftToRight() ? dk.RIGHT_ARROW : dk.LEFT_ARROW]="_onRightArrow"; - map[dk.UP_ARROW]="_onUpArrow"; - map[dk.DOWN_ARROW]="_onDownArrow"; - map[dk.HOME]="_onHomeKey"; - map[dk.END]="_onEndKey"; + map[keys.SPACE]= map[" "] = "_onEnterKey"; + map[this.isLeftToRight() ? keys.LEFT_ARROW : keys.RIGHT_ARROW]="_onLeftArrow"; + map[this.isLeftToRight() ? keys.RIGHT_ARROW : keys.LEFT_ARROW]="_onRightArrow"; + map[keys.UP_ARROW]="_onUpArrow"; + map[keys.DOWN_ARROW]="_onDownArrow"; + map[keys.HOME]="_onHomeKey"; + map[keys.END]="_onEndKey"; this._keyHandlerMap = map; } if(this._keyHandlerMap[key]){ this[this._keyHandlerMap[key]]( { node: treeNode, item: treeNode.item, evt: e } ); - dojo.stopEvent(e); + event.stop(e); } } }, _onEnterKey: function(/*Object*/ message){ this._publish("execute", { item: message.item, node: message.node } ); - this.dndController.userSelect(message.node, dojo.isCopyKey( message.evt ), message.evt.shiftKey); + this.dndController.userSelect(message.node, connect.isCopyKey( message.evt ), message.evt.shiftKey); this.onClick(message.item, message.node, message.evt); }, @@ -24716,7 +31696,7 @@ dojo.declare( } }, - _onEndKey: function(/*Object*/ message){ + _onEndKey: function(){ // summary: // End key pressed; go to last visible node. @@ -24787,7 +31767,7 @@ dojo.declare( isExpandoNode: function(node, widget){ // summary: // check whether a dom node is the expandoNode for a particular TreeNode widget - return dojo.isDescendant(node, widget.expandoNode); + return dom.isDescendant(node, widget.expandoNode); }, _onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){ // summary: @@ -24806,7 +31786,7 @@ dojo.declare( this.onClick(nodeWidget.item, nodeWidget, e); this.focusNode(nodeWidget); } - dojo.stopEvent(e); + event.stop(e); }, _onDblClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){ // summary: @@ -24825,7 +31805,7 @@ dojo.declare( this.onDblClick(nodeWidget.item, nodeWidget, e); this.focusNode(nodeWidget); } - dojo.stopEvent(e); + event.stop(e); }, _onExpandoClick: function(/*Object*/ message){ @@ -24845,27 +31825,37 @@ dojo.declare( } }, - onClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){ + onClick: function(/*===== item, node, evt =====*/){ // summary: // Callback when a tree node is clicked + // item: dojo.data.Item + // node: TreeNode + // evt: Event // tags: // callback }, - onDblClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){ + onDblClick: function(/*===== item, node, evt =====*/){ // summary: // Callback when a tree node is double-clicked + // item: dojo.data.Item + // node: TreeNode + // evt: Event // tags: // callback }, - onOpen: function(/* dojo.data */ item, /*TreeNode*/ node){ + onOpen: function(/*===== item, node =====*/){ // summary: // Callback when a node is opened + // item: dojo.data.Item + // node: TreeNode // tags: // callback }, - onClose: function(/* dojo.data */ item, /*TreeNode*/ node){ + onClose: function(/*===== item, node =====*/){ // summary: // Callback when a node is closed + // item: dojo.data.Item + // node: TreeNode // tags: // callback }, @@ -24913,10 +31903,7 @@ dojo.declare( node.collapse(); this.onClose(node.item, node); - if(node.item){ - this._state(node.item,false); - this._saveState(); - } + this._state(node, false); } }, @@ -24945,7 +31932,7 @@ dojo.declare( // Setup deferred to signal when the load and expand are finished. // Save that deferred in this._expandDeferred as a flag that operation is in progress. - var def = (node._expandNodeDeferred = new dojo.Deferred()); + var def = (node._expandNodeDeferred = new Deferred()); // Get the children model.getChildren( @@ -24982,10 +31969,7 @@ dojo.declare( this.onOpen(node.item, node); - if(item){ - this._state(item, true); - this._saveState(); - } + this._state(node, true); } return def; // dojo.Deferred @@ -25000,7 +31984,7 @@ dojo.declare( // protected // set focus so that the label will be voiced using screen readers - dijit.focus(node.labelNode); + focus.focus(node.labelNode); }, _onNodeFocus: function(/*dijit._Widget*/ node){ @@ -25023,13 +32007,13 @@ dojo.declare( } }, - _onNodeMouseEnter: function(/*dijit._Widget*/ node){ + _onNodeMouseEnter: function(/*dijit._Widget*/ /*===== node =====*/){ // summary: // Called when mouse is over a node (onmouseenter event), // this is monitored by the DND code }, - _onNodeMouseLeave: function(/*dijit._Widget*/ node){ + _onNodeMouseLeave: function(/*dijit._Widget*/ /*===== node =====*/){ // summary: // Called when mouse leaves a node (onmouseleave event), // this is monitored by the DND code @@ -25047,7 +32031,7 @@ dojo.declare( if(nodes){ var label = this.getLabel(item), tooltip = this.getTooltip(item); - dojo.forEach(nodes, function(node){ + array.forEach(nodes, function(node){ node.set({ item: item, // theoretically could be new JS Object representing same item label: label, @@ -25066,7 +32050,7 @@ dojo.declare( parentNodes = this._itemNodesMap[identity]; if(parentNodes){ - dojo.forEach(parentNodes,function(parentNode){ + array.forEach(parentNodes,function(parentNode){ parentNode.setChildItems(newChildrenList); }); } @@ -25080,7 +32064,7 @@ dojo.declare( nodes = this._itemNodesMap[identity]; if(nodes){ - dojo.forEach(nodes,function(node){ + array.forEach(nodes,function(node){ // Remove node from set of selected nodes (if it's selected) this.dndController.removeTreeNode(node); @@ -25100,43 +32084,39 @@ dojo.declare( _initState: function(){ // summary: // Load in which nodes should be opened automatically - if(this.persist){ - var cookie = dojo.cookie(this.cookieName); - this._openedItemIds = {}; - if(cookie){ - dojo.forEach(cookie.split(','), function(item){ - this._openedItemIds[item] = true; + this._openedNodes = {}; + if(this.persist && this.cookieName){ + var oreo = cookie(this.cookieName); + if(oreo){ + array.forEach(oreo.split(','), function(item){ + this._openedNodes[item] = true; }, this); } } }, - _state: function(item,expanded){ + _state: function(node, expanded){ // summary: - // Query or set expanded state for an item, + // Query or set expanded state for an node if(!this.persist){ return false; } - var id=this.model.getIdentity(item); + var path = array.map(node.getTreePath(), function(item){ + return this.model.getIdentity(item); + }, this).join("/"); if(arguments.length === 1){ - return this._openedItemIds[id]; - } - if(expanded){ - this._openedItemIds[id] = true; + return this._openedNodes[path]; }else{ - delete this._openedItemIds[id]; - } - }, - _saveState: function(){ - // summary: - // Create and save a cookie with the currently expanded nodes identifiers - if(!this.persist){ - return; - } - var ary = []; - for(var id in this._openedItemIds){ - ary.push(id); + if(expanded){ + this._openedNodes[path] = true; + }else{ + delete this._openedNodes[path]; + } + var ary = []; + for(var id in this._openedNodes){ + ary.push(id); + } + cookie(this.cookieName, ary.join(","), {expires:365}); } - dojo.cookie(this.cookieName, ary.join(","), {expires:365}); }, destroy: function(){ @@ -25147,7 +32127,7 @@ dojo.declare( if(this.rootNode){ this.rootNode.destroyRecursive(); } - if(this.dndController && !dojo.isString(this.dndController)){ + if(this.dndController && !lang.isString(this.dndController)){ this.dndController.destroy(); } this.rootNode = null; @@ -25162,13 +32142,13 @@ dojo.declare( resize: function(changeSize){ if(changeSize){ - dojo.marginBox(this.domNode, changeSize); + domGeometry.setMarginBox(this.domNode, changeSize); } // The only JS sizing involved w/tree is the indentation, which is specified // in CSS and read in through this dummy indentDetector node (tree must be // visible and attached to the DOM to read this) - this._nodePixelIndent = dojo._getMarginSize(this.tree.indentDetector).w; + this._nodePixelIndent = domGeometry.position(this.tree.indentDetector).w; if(this.tree.rootNode){ // If tree has already loaded, then reset indent for all the nodes @@ -25184,2631 +32164,84 @@ dojo.declare( // However it will probably be removed in a future release in favor of a way // of just specifying a widget for the label, rather than one that contains // the children too. - return new dijit._TreeNode(args); - } -}); - -// For back-compat. TODO: remove in 2.0 - -} - -if(!dojo._hasResource["dojo.dnd.Avatar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.dnd.Avatar"] = true; -dojo.provide("dojo.dnd.Avatar"); - - - -dojo.declare("dojo.dnd.Avatar", null, { - // summary: - // Object that represents transferred DnD items visually - // manager: Object - // a DnD manager object - - constructor: function(manager){ - this.manager = manager; - this.construct(); + return new TreeNode(args); }, - // methods - construct: function(){ - // summary: - // constructor function; - // it is separate so it can be (dynamically) overwritten in case of need - this.isA11y = dojo.hasClass(dojo.body(),"dijit_a11y"); - var a = dojo.create("table", { - "class": "dojoDndAvatar", - style: { - position: "absolute", - zIndex: "1999", - margin: "0px" - } - }), - source = this.manager.source, node, - b = dojo.create("tbody", null, a), - tr = dojo.create("tr", null, b), - td = dojo.create("td", null, tr), - icon = this.isA11y ? dojo.create("span", { - id : "a11yIcon", - innerHTML : this.manager.copy ? '+' : "<" - }, td) : null, - span = dojo.create("span", { - innerHTML: source.generateText ? this._generateText() : "" - }, td), - k = Math.min(5, this.manager.nodes.length), i = 0; - // we have to set the opacity on IE only after the node is live - dojo.attr(tr, { - "class": "dojoDndAvatarHeader", - style: {opacity: 0.9} - }); - for(; i < k; ++i){ - if(source.creator){ - // create an avatar representation of the node - node = source._normalizedCreator(source.getItem(this.manager.nodes[i].id).data, "avatar").node; - }else{ - // or just clone the node and hope it works - node = this.manager.nodes[i].cloneNode(true); - if(node.tagName.toLowerCase() == "tr"){ - // insert extra table nodes - var table = dojo.create("table"), - tbody = dojo.create("tbody", null, table); - tbody.appendChild(node); - node = table; - } - } - node.id = ""; - tr = dojo.create("tr", null, b); - td = dojo.create("td", null, tr); - td.appendChild(node); - dojo.attr(tr, { - "class": "dojoDndAvatarItem", - style: {opacity: (9 - i) / 10} - }); + _setTextDirAttr: function(textDir){ + if(textDir && this.textDir!= textDir){ + this._set("textDir",textDir); + this.rootNode.set("textDir", textDir); } - this.node = a; - }, - destroy: function(){ - // summary: - // destructor for the avatar; called to remove all references so it can be garbage-collected - dojo.destroy(this.node); - this.node = false; - }, - update: function(){ - // summary: - // updates the avatar to reflect the current DnD state - dojo[(this.manager.canDropFlag ? "add" : "remove") + "Class"](this.node, "dojoDndAvatarCanDrop"); - if (this.isA11y){ - var icon = dojo.byId("a11yIcon"); - var text = '+'; // assume canDrop && copy - if (this.manager.canDropFlag && !this.manager.copy) { - text = '< '; // canDrop && move - }else if (!this.manager.canDropFlag && !this.manager.copy) { - text = "o"; //!canDrop && move - }else if(!this.manager.canDropFlag){ - text = 'x'; // !canDrop && copy - } - icon.innerHTML=text; - } - // replace text - dojo.query(("tr.dojoDndAvatarHeader td span" +(this.isA11y ? " span" : "")), this.node).forEach( - function(node){ - node.innerHTML = this._generateText(); - }, this); - }, - _generateText: function(){ - // summary: generates a proper text to reflect copying or moving of items - return this.manager.nodes.length.toString(); } }); -} - -if(!dojo._hasResource["dojo.dnd.Manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.dnd.Manager"] = true; -dojo.provide("dojo.dnd.Manager"); - - - - +Tree._TreeNode = TreeNode; // for monkey patching -dojo.declare("dojo.dnd.Manager", null, { - // summary: - // the manager of DnD operations (usually a singleton) - constructor: function(){ - this.avatar = null; - this.source = null; - this.nodes = []; - this.copy = true; - this.target = null; - this.canDropFlag = false; - this.events = []; - }, - - // avatar's offset from the mouse - OFFSET_X: 16, - OFFSET_Y: 16, - - // methods - overSource: function(source){ - // summary: - // called when a source detected a mouse-over condition - // source: Object - // the reporter - if(this.avatar){ - this.target = (source && source.targetState != "Disabled") ? source : null; - this.canDropFlag = Boolean(this.target); - this.avatar.update(); - } - dojo.publish("/dnd/source/over", [source]); - }, - outSource: function(source){ - // summary: - // called when a source detected a mouse-out condition - // source: Object - // the reporter - if(this.avatar){ - if(this.target == source){ - this.target = null; - this.canDropFlag = false; - this.avatar.update(); - dojo.publish("/dnd/source/over", [null]); - } - }else{ - dojo.publish("/dnd/source/over", [null]); - } - }, - startDrag: function(source, nodes, copy){ - // summary: - // called to initiate the DnD operation - // source: Object - // the source which provides items - // nodes: Array - // the list of transferred items - // copy: Boolean - // copy items, if true, move items otherwise - this.source = source; - this.nodes = nodes; - this.copy = Boolean(copy); // normalizing to true boolean - this.avatar = this.makeAvatar(); - dojo.body().appendChild(this.avatar.node); - dojo.publish("/dnd/start", [source, nodes, this.copy]); - this.events = [ - dojo.connect(dojo.doc, "onmousemove", this, "onMouseMove"), - dojo.connect(dojo.doc, "onmouseup", this, "onMouseUp"), - dojo.connect(dojo.doc, "onkeydown", this, "onKeyDown"), - dojo.connect(dojo.doc, "onkeyup", this, "onKeyUp"), - // cancel text selection and text dragging - dojo.connect(dojo.doc, "ondragstart", dojo.stopEvent), - dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent) - ]; - var c = "dojoDnd" + (copy ? "Copy" : "Move"); - dojo.addClass(dojo.body(), c); - }, - canDrop: function(flag){ - // summary: - // called to notify if the current target can accept items - var canDropFlag = Boolean(this.target && flag); - if(this.canDropFlag != canDropFlag){ - this.canDropFlag = canDropFlag; - this.avatar.update(); - } - }, - stopDrag: function(){ - // summary: - // stop the DnD in progress - dojo.removeClass(dojo.body(), ["dojoDndCopy", "dojoDndMove"]); - dojo.forEach(this.events, dojo.disconnect); - this.events = []; - this.avatar.destroy(); - this.avatar = null; - this.source = this.target = null; - this.nodes = []; - }, - makeAvatar: function(){ - // summary: - // makes the avatar; it is separate to be overwritten dynamically, if needed - return new dojo.dnd.Avatar(this); - }, - updateAvatar: function(){ - // summary: - // updates the avatar; it is separate to be overwritten dynamically, if needed - this.avatar.update(); - }, - - // mouse event processors - onMouseMove: function(e){ - // summary: - // event processor for onmousemove - // e: Event - // mouse event - var a = this.avatar; - if(a){ - dojo.dnd.autoScrollNodes(e); - //dojo.dnd.autoScroll(e); - var s = a.node.style; - s.left = (e.pageX + this.OFFSET_X) + "px"; - s.top = (e.pageY + this.OFFSET_Y) + "px"; - var copy = Boolean(this.source.copyState(dojo.isCopyKey(e))); - if(this.copy != copy){ - this._setCopyStatus(copy); - } - } - }, - onMouseUp: function(e){ - // summary: - // event processor for onmouseup - // e: Event - // mouse event - if(this.avatar){ - if(this.target && this.canDropFlag){ - var copy = Boolean(this.source.copyState(dojo.isCopyKey(e))), - params = [this.source, this.nodes, copy, this.target, e]; - dojo.publish("/dnd/drop/before", params); - dojo.publish("/dnd/drop", params); - }else{ - dojo.publish("/dnd/cancel"); - } - this.stopDrag(); - } - }, - - // keyboard event processors - onKeyDown: function(e){ - // summary: - // event processor for onkeydown: - // watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag - // e: Event - // keyboard event - if(this.avatar){ - switch(e.keyCode){ - case dojo.keys.CTRL: - var copy = Boolean(this.source.copyState(true)); - if(this.copy != copy){ - this._setCopyStatus(copy); - } - break; - case dojo.keys.ESCAPE: - dojo.publish("/dnd/cancel"); - this.stopDrag(); - break; - } - } - }, - onKeyUp: function(e){ - // summary: - // event processor for onkeyup, watching for CTRL for copy/move status - // e: Event - // keyboard event - if(this.avatar && e.keyCode == dojo.keys.CTRL){ - var copy = Boolean(this.source.copyState(false)); - if(this.copy != copy){ - this._setCopyStatus(copy); - } - } - }, - - // utilities - _setCopyStatus: function(copy){ - // summary: - // changes the copy status - // copy: Boolean - // the copy status - this.copy = copy; - this.source._markDndStatus(this.copy); - this.updateAvatar(); - dojo.replaceClass(dojo.body(), - "dojoDnd" + (this.copy ? "Copy" : "Move"), - "dojoDnd" + (this.copy ? "Move" : "Copy")); - } +return Tree; }); -// dojo.dnd._manager: -// The manager singleton variable. Can be overwritten if needed. -dojo.dnd._manager = null; - -dojo.dnd.manager = function(){ - // summary: - // Returns the current DnD manager. Creates one if it is not created yet. - if(!dojo.dnd._manager){ - dojo.dnd._manager = new dojo.dnd.Manager(); - } - return dojo.dnd._manager; // Object -}; - -} - -if(!dojo._hasResource["dijit.tree.dndSource"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dijit.tree.dndSource"] = true; -dojo.provide("dijit.tree.dndSource"); - - - +}, +'dijit/form/_FormValueWidget':function(){ +define("dijit/form/_FormValueWidget", [ + "dojo/_base/declare", // declare + "dojo/_base/sniff", // has("ie") + "./_FormWidget", + "./_FormValueMixin" +], function(declare, has, _FormWidget, _FormValueMixin){ /*===== -dijit.tree.__SourceArgs = function(){ - // summary: - // A dict of parameters for Tree source configuration. - // isSource: Boolean? - // Can be used as a DnD source. Defaults to true. - // accept: String[] - // List of accepted types (text strings) for a target; defaults to - // ["text", "treeNode"] - // copyOnly: Boolean? - // Copy items, if true, use a state of Ctrl key otherwise, - // dragThreshold: Number - // The move delay in pixels before detecting a drag; 0 by default - // betweenThreshold: Integer - // Distance from upper/lower edge of node to allow drop to reorder nodes - this.isSource = isSource; - this.accept = accept; - this.autoSync = autoSync; - this.copyOnly = copyOnly; - this.dragThreshold = dragThreshold; - this.betweenThreshold = betweenThreshold; -} +var _FormWidget = dijit.form._FormWidget; +var _FormValueMixin = dijit.form._FormValueMixin; =====*/ -dojo.declare("dijit.tree.dndSource", dijit.tree._dndSelector, { - // summary: - // Handles drag and drop operations (as a source or a target) for `dijit.Tree` - - // isSource: [private] Boolean - // Can be used as a DnD source. - isSource: true, - - // accept: String[] - // List of accepted types (text strings) for the Tree; defaults to - // ["text"] - accept: ["text", "treeNode"], - - // copyOnly: [private] Boolean - // Copy items, if true, use a state of Ctrl key otherwise - copyOnly: false, - - // dragThreshold: Number - // The move delay in pixels before detecting a drag; 5 by default - dragThreshold: 5, - - // betweenThreshold: Integer - // Distance from upper/lower edge of node to allow drop to reorder nodes - betweenThreshold: 0, - - constructor: function(/*dijit.Tree*/ tree, /*dijit.tree.__SourceArgs*/ params){ - // summary: - // a constructor of the Tree DnD Source - // tags: - // private - if(!params){ params = {}; } - dojo.mixin(this, params); - this.isSource = typeof params.isSource == "undefined" ? true : params.isSource; - var type = params.accept instanceof Array ? params.accept : ["text", "treeNode"]; - this.accept = null; - if(type.length){ - this.accept = {}; - for(var i = 0; i < type.length; ++i){ - this.accept[type[i]] = 1; - } - } - - // class-specific variables - this.isDragging = false; - this.mouseDown = false; - this.targetAnchor = null; // DOMNode corresponding to the currently moused over TreeNode - this.targetBox = null; // coordinates of this.targetAnchor - this.dropPosition = ""; // whether mouse is over/after/before this.targetAnchor - this._lastX = 0; - this._lastY = 0; - - // states - this.sourceState = ""; - if(this.isSource){ - dojo.addClass(this.node, "dojoDndSource"); - } - this.targetState = ""; - if(this.accept){ - dojo.addClass(this.node, "dojoDndTarget"); - } - - // set up events - this.topics = [ - dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"), - dojo.subscribe("/dnd/start", this, "onDndStart"), - dojo.subscribe("/dnd/drop", this, "onDndDrop"), - dojo.subscribe("/dnd/cancel", this, "onDndCancel") - ]; - }, - - // methods - checkAcceptance: function(source, nodes){ - // summary: - // Checks if the target can accept nodes from this source - // source: dijit.tree.dndSource - // The source which provides items - // nodes: DOMNode[] - // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if - // source is a dijit.Tree. - // tags: - // extension - return true; // Boolean - }, - - copyState: function(keyPressed){ - // summary: - // Returns true, if we need to copy items, false to move. - // It is separated to be overwritten dynamically, if needed. - // keyPressed: Boolean - // The "copy" control key was pressed - // tags: - // protected - return this.copyOnly || keyPressed; // Boolean - }, - destroy: function(){ - // summary: - // Prepares the object to be garbage-collected. - this.inherited("destroy",arguments); - dojo.forEach(this.topics, dojo.unsubscribe); - this.targetAnchor = null; - }, - - _onDragMouse: function(e){ - // summary: - // Helper method for processing onmousemove/onmouseover events while drag is in progress. - // Keeps track of current drop target. - - var m = dojo.dnd.manager(), - oldTarget = this.targetAnchor, // the TreeNode corresponding to TreeNode mouse was previously over - newTarget = this.current, // TreeNode corresponding to TreeNode mouse is currently over - oldDropPosition = this.dropPosition; // the previous drop position (over/before/after) - - // calculate if user is indicating to drop the dragged node before, after, or over - // (i.e., to become a child of) the target node - var newDropPosition = "Over"; - if(newTarget && this.betweenThreshold > 0){ - // If mouse is over a new TreeNode, then get new TreeNode's position and size - if(!this.targetBox || oldTarget != newTarget){ - this.targetBox = dojo.position(newTarget.rowNode, true); - } - if((e.pageY - this.targetBox.y) <= this.betweenThreshold){ - newDropPosition = "Before"; - }else if((e.pageY - this.targetBox.y) >= (this.targetBox.h - this.betweenThreshold)){ - newDropPosition = "After"; - } - } - - if(newTarget != oldTarget || newDropPosition != oldDropPosition){ - if(oldTarget){ - this._removeItemClass(oldTarget.rowNode, oldDropPosition); - } - if(newTarget){ - this._addItemClass(newTarget.rowNode, newDropPosition); - } - - // Check if it's ok to drop the dragged node on/before/after the target node. - if(!newTarget){ - m.canDrop(false); - }else if(newTarget == this.tree.rootNode && newDropPosition != "Over"){ - // Can't drop before or after tree's root node; the dropped node would just disappear (at least visually) - m.canDrop(false); - }else if(m.source == this && (newTarget.id in this.selection)){ - // Guard against dropping onto yourself (TODO: guard against dropping onto your descendant, #7140) - m.canDrop(false); - }else if(this.checkItemAcceptance(newTarget.rowNode, m.source, newDropPosition.toLowerCase()) - && !this._isParentChildDrop(m.source, newTarget.rowNode)){ - m.canDrop(true); - }else{ - m.canDrop(false); - } - - this.targetAnchor = newTarget; - this.dropPosition = newDropPosition; - } - }, - - onMouseMove: function(e){ - // summary: - // Called for any onmousemove events over the Tree - // e: Event - // onmousemouse event - // tags: - // private - if(this.isDragging && this.targetState == "Disabled"){ return; } - this.inherited(arguments); - var m = dojo.dnd.manager(); - if(this.isDragging){ - this._onDragMouse(e); - }else{ - if(this.mouseDown && this.isSource && - (Math.abs(e.pageX-this._lastX)>=this.dragThreshold || Math.abs(e.pageY-this._lastY)>=this.dragThreshold)){ - var nodes = this.getSelectedTreeNodes(); - if(nodes.length){ - if(nodes.length > 1){ - //filter out all selected items which has one of their ancestor selected as well - var seen = this.selection, i = 0, r = [], n, p; - nextitem: while((n = nodes[i++])){ - for(p = n.getParent(); p && p !== this.tree; p = p.getParent()){ - if(seen[p.id]){ //parent is already selected, skip this node - continue nextitem; - } - } - //this node does not have any ancestors selected, add it - r.push(n); - } - nodes = r; - } - nodes = dojo.map(nodes, function(n){return n.domNode}); - m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e))); - } - } - } - }, - - onMouseDown: function(e){ - // summary: - // Event processor for onmousedown - // e: Event - // onmousedown event - // tags: - // private - this.mouseDown = true; - this.mouseButton = e.button; - this._lastX = e.pageX; - this._lastY = e.pageY; - this.inherited(arguments); - }, - - onMouseUp: function(e){ - // summary: - // Event processor for onmouseup - // e: Event - // onmouseup event - // tags: - // private - if(this.mouseDown){ - this.mouseDown = false; - this.inherited(arguments); - } - }, - - onMouseOut: function(){ - // summary: - // Event processor for when mouse is moved away from a TreeNode - // tags: - // private - this.inherited(arguments); - this._unmarkTargetAnchor(); - }, - - checkItemAcceptance: function(target, source, position){ - // summary: - // Stub function to be overridden if one wants to check for the ability to drop at the node/item level - // description: - // In the base case, this is called to check if target can become a child of source. - // When betweenThreshold is set, position="before" or "after" means that we - // are asking if the source node can be dropped before/after the target node. - // target: DOMNode - // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to - // Use dijit.getEnclosingWidget(target) to get the TreeNode. - // source: dijit.tree.dndSource - // The (set of) nodes we are dropping - // position: String - // "over", "before", or "after" - // tags: - // extension - return true; - }, - - // topic event processors - onDndSourceOver: function(source){ - // summary: - // Topic event processor for /dnd/source/over, called when detected a current source. - // source: Object - // The dijit.tree.dndSource / dojo.dnd.Source which has the mouse over it - // tags: - // private - if(this != source){ - this.mouseDown = false; - this._unmarkTargetAnchor(); - }else if(this.isDragging){ - var m = dojo.dnd.manager(); - m.canDrop(false); - } - }, - onDndStart: function(source, nodes, copy){ - // summary: - // Topic event processor for /dnd/start, called to initiate the DnD operation - // source: Object - // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items - // nodes: DomNode[] - // The list of transferred items, dndTreeNode nodes if dragging from a Tree - // copy: Boolean - // Copy items, if true, move items otherwise - // tags: - // private - - if(this.isSource){ - this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : ""); - } - var accepted = this.checkAcceptance(source, nodes); - - this._changeState("Target", accepted ? "" : "Disabled"); - - if(this == source){ - dojo.dnd.manager().overSource(this); - } - - this.isDragging = true; - }, - - itemCreator: function(/*DomNode[]*/ nodes, target, /*dojo.dnd.Source*/ source){ - // summary: - // Returns objects passed to `Tree.model.newItem()` based on DnD nodes - // dropped onto the tree. Developer must override this method to enable - // dropping from external sources onto this Tree, unless the Tree.model's items - // happen to look like {id: 123, name: "Apple" } with no other attributes. - // description: - // For each node in nodes[], which came from source, create a hash of name/value - // pairs to be passed to Tree.model.newItem(). Returns array of those hashes. - // returns: Object[] - // Array of name/value hashes for each new item to be added to the Tree, like: - // | [ - // | { id: 123, label: "apple", foo: "bar" }, - // | { id: 456, label: "pear", zaz: "bam" } - // | ] - // tags: - // extension - - // TODO: for 2.0 refactor so itemCreator() is called once per drag node, and - // make signature itemCreator(sourceItem, node, target) (or similar). - - return dojo.map(nodes, function(node){ - return { - "id": node.id, - "name": node.textContent || node.innerText || "" - }; - }); // Object[] - }, - - onDndDrop: function(source, nodes, copy){ - // summary: - // Topic event processor for /dnd/drop, called to finish the DnD operation. - // description: - // Updates data store items according to where node was dragged from and dropped - // to. The tree will then respond to those data store updates and redraw itself. - // source: Object - // The dijit.tree.dndSource / dojo.dnd.Source which is providing the items - // nodes: DomNode[] - // The list of transferred items, dndTreeNode nodes if dragging from a Tree - // copy: Boolean - // Copy items, if true, move items otherwise - // tags: - // protected - if(this.containerState == "Over"){ - var tree = this.tree, - model = tree.model, - target = this.targetAnchor, - requeryRoot = false; // set to true iff top level items change - - this.isDragging = false; - - // Compute the new parent item - var targetWidget = target; - var newParentItem; - var insertIndex; - newParentItem = (targetWidget && targetWidget.item) || tree.item; - if(this.dropPosition == "Before" || this.dropPosition == "After"){ - // TODO: if there is no parent item then disallow the drop. - // Actually this should be checked during onMouseMove too, to make the drag icon red. - newParentItem = (targetWidget.getParent() && targetWidget.getParent().item) || tree.item; - // Compute the insert index for reordering - insertIndex = targetWidget.getIndexInParent(); - if(this.dropPosition == "After"){ - insertIndex = targetWidget.getIndexInParent() + 1; - } - }else{ - newParentItem = (targetWidget && targetWidget.item) || tree.item; - } - - // If necessary, use this variable to hold array of hashes to pass to model.newItem() - // (one entry in the array for each dragged node). - var newItemsParams; - - dojo.forEach(nodes, function(node, idx){ - // dojo.dnd.Item representing the thing being dropped. - // Don't confuse the use of item here (meaning a DnD item) with the - // uses below where item means dojo.data item. - var sourceItem = source.getItem(node.id); - - // Information that's available if the source is another Tree - // (possibly but not necessarily this tree, possibly but not - // necessarily the same model as this Tree) - if(dojo.indexOf(sourceItem.type, "treeNode") != -1){ - var childTreeNode = sourceItem.data, - childItem = childTreeNode.item, - oldParentItem = childTreeNode.getParent().item; - } - - if(source == this){ - // This is a node from my own tree, and we are moving it, not copying. - // Remove item from old parent's children attribute. - // TODO: dijit.tree.dndSelector should implement deleteSelectedNodes() - // and this code should go there. - - if(typeof insertIndex == "number"){ - if(newParentItem == oldParentItem && childTreeNode.getIndexInParent() < insertIndex){ - insertIndex -= 1; - } - } - model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex); - }else if(model.isItem(childItem)){ - // Item from same model - // (maybe we should only do this branch if the source is a tree?) - model.pasteItem(childItem, oldParentItem, newParentItem, copy, insertIndex); - }else{ - // Get the hash to pass to model.newItem(). A single call to - // itemCreator() returns an array of hashes, one for each drag source node. - if(!newItemsParams){ - newItemsParams = this.itemCreator(nodes, target.rowNode, source); - } - - // Create new item in the tree, based on the drag source. - model.newItem(newItemsParams[idx], newParentItem, insertIndex); - } - }, this); - - // Expand the target node (if it's currently collapsed) so the user can see - // where their node was dropped. In particular since that node is still selected. - this.tree._expandNode(targetWidget); - } - this.onDndCancel(); - }, - - onDndCancel: function(){ - // summary: - // Topic event processor for /dnd/cancel, called to cancel the DnD operation - // tags: - // private - this._unmarkTargetAnchor(); - this.isDragging = false; - this.mouseDown = false; - delete this.mouseButton; - this._changeState("Source", ""); - this._changeState("Target", ""); - }, - - // When focus moves in/out of the entire Tree - onOverEvent: function(){ - // summary: - // This method is called when mouse is moved over our container (like onmouseenter) - // tags: - // private - this.inherited(arguments); - dojo.dnd.manager().overSource(this); - }, - onOutEvent: function(){ - // summary: - // This method is called when mouse is moved out of our container (like onmouseleave) - // tags: - // private - this._unmarkTargetAnchor(); - var m = dojo.dnd.manager(); - if(this.isDragging){ - m.canDrop(false); - } - m.outSource(this); - - this.inherited(arguments); - }, - - _isParentChildDrop: function(source, targetRow){ - // summary: - // Checks whether the dragged items are parent rows in the tree which are being - // dragged into their own children. - // - // source: - // The DragSource object. - // - // targetRow: - // The tree row onto which the dragged nodes are being dropped. - // - // tags: - // private - - // If the dragged object is not coming from the tree this widget belongs to, - // it cannot be invalid. - if(!source.tree || source.tree != this.tree){ - return false; - } - - - var root = source.tree.domNode; - var ids = source.selection; - - var node = targetRow.parentNode; +// module: +// dijit/form/_FormValueWidget +// summary: +// FormValueWidget - // Iterate up the DOM hierarchy from the target drop row, - // checking of any of the dragged nodes have the same ID. - while(node != root && !ids[node.id]){ - node = node.parentNode; - } - return node.id && ids[node.id]; - }, +return declare("dijit.form._FormValueWidget", [_FormWidget, _FormValueMixin], +{ + // summary: + // Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values. + // description: + // Each _FormValueWidget represents a single input value, and has a (possibly hidden) <input> element, + // to which it serializes it's input value, so that form submission (either normal submission or via FormBind?) + // works as expected. - _unmarkTargetAnchor: function(){ - // summary: - // Removes hover class of the current target anchor - // tags: - // private - if(!this.targetAnchor){ return; } - this._removeItemClass(this.targetAnchor.rowNode, this.dropPosition); - this.targetAnchor = null; - this.targetBox = null; - this.dropPosition = null; - }, + // Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared + // directly in the template as read by the parser in order to function. IE is known to specifically + // require the 'name' attribute at element creation time. See #8484, #8660. - _markDndStatus: function(copy){ + _layoutHackIE7: function(){ // summary: - // Changes source's state based on "copy" status - this._changeState("Source", copy ? "Copied" : "Moved"); - } -}); - -} - -if(!dojo._hasResource["dojo.data.ItemFileReadStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.data.ItemFileReadStore"] = true; -dojo.provide("dojo.data.ItemFileReadStore"); - - - - - -dojo.declare("dojo.data.ItemFileReadStore", null,{ - // summary: - // The ItemFileReadStore implements the dojo.data.api.Read API and reads - // data from JSON files that have contents in this format -- - // { items: [ - // { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]}, - // { name:'Fozzie Bear', wears:['hat', 'tie']}, - // { name:'Miss Piggy', pets:'Foo-Foo'} - // ]} - // Note that it can also contain an 'identifer' property that specified which attribute on the items - // in the array of items that acts as the unique identifier for that item. - // - constructor: function(/* Object */ keywordParameters){ - // summary: constructor - // keywordParameters: {url: String} - // keywordParameters: {data: jsonObject} - // keywordParameters: {typeMap: object) - // The structure of the typeMap object is as follows: - // { - // type0: function || object, - // type1: function || object, - // ... - // typeN: function || object - // } - // Where if it is a function, it is assumed to be an object constructor that takes the - // value of _value as the initialization parameters. If it is an object, then it is assumed - // to be an object of general form: - // { - // type: function, //constructor. - // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately. - // } - - this._arrayOfAllItems = []; - this._arrayOfTopLevelItems = []; - this._loadFinished = false; - this._jsonFileUrl = keywordParameters.url; - this._ccUrl = keywordParameters.url; - this.url = keywordParameters.url; - this._jsonData = keywordParameters.data; - this.data = null; - this._datatypeMap = keywordParameters.typeMap || {}; - if(!this._datatypeMap['Date']){ - //If no default mapping for dates, then set this as default. - //We use the dojo.date.stamp here because the ISO format is the 'dojo way' - //of generically representing dates. - this._datatypeMap['Date'] = { - type: Date, - deserialize: function(value){ - return dojo.date.stamp.fromISOString(value); - } - }; - } - this._features = {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true}; - this._itemsByIdentity = null; - this._storeRefPropName = "_S"; // Default name for the store reference to attach to every item. - this._itemNumPropName = "_0"; // Default Item Id for isItem to attach to every item. - this._rootItemPropName = "_RI"; // Default Item Id for isItem to attach to every item. - this._reverseRefMap = "_RRM"; // Default attribute for constructing a reverse reference map for use with reference integrity - this._loadInProgress = false; //Got to track the initial load to prevent duelling loads of the dataset. - this._queuedFetches = []; - if(keywordParameters.urlPreventCache !== undefined){ - this.urlPreventCache = keywordParameters.urlPreventCache?true:false; - } - if(keywordParameters.hierarchical !== undefined){ - this.hierarchical = keywordParameters.hierarchical?true:false; - } - if(keywordParameters.clearOnClose){ - this.clearOnClose = true; - } - if("failOk" in keywordParameters){ - this.failOk = keywordParameters.failOk?true:false; - } - }, - - url: "", // use "" rather than undefined for the benefit of the parser (#3539) - - //Internal var, crossCheckUrl. Used so that setting either url or _jsonFileUrl, can still trigger a reload - //when clearOnClose and close is used. - _ccUrl: "", - - data: null, // define this so that the parser can populate it - - typeMap: null, //Define so parser can populate. - - //Parameter to allow users to specify if a close call should force a reload or not. - //By default, it retains the old behavior of not clearing if close is called. But - //if set true, the store will be reset to default state. Note that by doing this, - //all item handles will become invalid and a new fetch must be issued. - clearOnClose: false, - - //Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url. - //Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option. - //Added for tracker: #6072 - urlPreventCache: false, - - //Parameter for specifying that it is OK for the xhrGet call to fail silently. - failOk: false, - - //Parameter to indicate to process data from the url as hierarchical - //(data items can contain other data items in js form). Default is true - //for backwards compatibility. False means only root items are processed - //as items, all child objects outside of type-mapped objects and those in - //specific reference format, are left straight JS data objects. - hierarchical: true, - - _assertIsItem: function(/* item */ item){ - // summary: - // This function tests whether the item passed in is indeed an item in the store. - // item: - // The item to test for being contained by the store. - if(!this.isItem(item)){ - throw new Error("dojo.data.ItemFileReadStore: Invalid item argument."); - } - }, - - _assertIsAttribute: function(/* attribute-name-string */ attribute){ - // summary: - // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store. - // attribute: - // The attribute to test for being contained by the store. - if(typeof attribute !== "string"){ - throw new Error("dojo.data.ItemFileReadStore: Invalid attribute argument."); - } - }, - - getValue: function( /* item */ item, - /* attribute-name-string */ attribute, - /* value? */ defaultValue){ - // summary: - // See dojo.data.api.Read.getValue() - var values = this.getValues(item, attribute); - return (values.length > 0)?values[0]:defaultValue; // mixed - }, - - getValues: function(/* item */ item, - /* attribute-name-string */ attribute){ - // summary: - // See dojo.data.api.Read.getValues() - - this._assertIsItem(item); - this._assertIsAttribute(attribute); - // Clone it before returning. refs: #10474 - return (item[attribute] || []).slice(0); // Array - }, - - getAttributes: function(/* item */ item){ - // summary: - // See dojo.data.api.Read.getAttributes() - this._assertIsItem(item); - var attributes = []; - for(var key in item){ - // Save off only the real item attributes, not the special id marks for O(1) isItem. - if((key !== this._storeRefPropName) && (key !== this._itemNumPropName) && (key !== this._rootItemPropName) && (key !== this._reverseRefMap)){ - attributes.push(key); - } - } - return attributes; // Array - }, - - hasAttribute: function( /* item */ item, - /* attribute-name-string */ attribute){ - // summary: - // See dojo.data.api.Read.hasAttribute() - this._assertIsItem(item); - this._assertIsAttribute(attribute); - return (attribute in item); - }, - - containsValue: function(/* item */ item, - /* attribute-name-string */ attribute, - /* anything */ value){ - // summary: - // See dojo.data.api.Read.containsValue() - var regexp = undefined; - if(typeof value === "string"){ - regexp = dojo.data.util.filter.patternToRegExp(value, false); - } - return this._containsValue(item, attribute, value, regexp); //boolean. - }, - - _containsValue: function( /* item */ item, - /* attribute-name-string */ attribute, - /* anything */ value, - /* RegExp?*/ regexp){ - // summary: - // Internal function for looking at the values contained by the item. - // description: - // Internal function for looking at the values contained by the item. This - // function allows for denoting if the comparison should be case sensitive for - // strings or not (for handling filtering cases where string case should not matter) - // - // item: - // The data item to examine for attribute values. - // attribute: - // The attribute to inspect. - // value: - // The value to match. - // regexp: - // Optional regular expression generated off value if value was of string type to handle wildcarding. - // If present and attribute values are string, then it can be used for comparison instead of 'value' - return dojo.some(this.getValues(item, attribute), function(possibleValue){ - if(possibleValue !== null && !dojo.isObject(possibleValue) && regexp){ - if(possibleValue.toString().match(regexp)){ - return true; // Boolean - } - }else if(value === possibleValue){ - return true; // Boolean - } - }); - }, - - isItem: function(/* anything */ something){ - // summary: - // See dojo.data.api.Read.isItem() - if(something && something[this._storeRefPropName] === this){ - if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){ - return true; - } - } - return false; // Boolean - }, - - isItemLoaded: function(/* anything */ something){ - // summary: - // See dojo.data.api.Read.isItemLoaded() - return this.isItem(something); //boolean - }, - - loadItem: function(/* object */ keywordArgs){ - // summary: - // See dojo.data.api.Read.loadItem() - this._assertIsItem(keywordArgs.item); - }, - - getFeatures: function(){ - // summary: - // See dojo.data.api.Read.getFeatures() - return this._features; //Object - }, - - getLabel: function(/* item */ item){ - // summary: - // See dojo.data.api.Read.getLabel() - if(this._labelAttr && this.isItem(item)){ - return this.getValue(item,this._labelAttr); //String - } - return undefined; //undefined - }, - - getLabelAttributes: function(/* item */ item){ - // summary: - // See dojo.data.api.Read.getLabelAttributes() - if(this._labelAttr){ - return [this._labelAttr]; //array - } - return null; //null - }, - - _fetchItems: function( /* Object */ keywordArgs, - /* Function */ findCallback, - /* Function */ errorCallback){ - // summary: - // See dojo.data.util.simpleFetch.fetch() - var self = this, - filter = function(requestArgs, arrayOfItems){ - var items = [], - i, key; - if(requestArgs.query){ - var value, - ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false; - - //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the - //same value for each item examined. Much more efficient. - var regexpList = {}; - for(key in requestArgs.query){ - value = requestArgs.query[key]; - if(typeof value === "string"){ - regexpList[key] = dojo.data.util.filter.patternToRegExp(value, ignoreCase); - }else if(value instanceof RegExp){ - regexpList[key] = value; - } - } - for(i = 0; i < arrayOfItems.length; ++i){ - var match = true; - var candidateItem = arrayOfItems[i]; - if(candidateItem === null){ - match = false; - }else{ - for(key in requestArgs.query){ - value = requestArgs.query[key]; - if(!self._containsValue(candidateItem, key, value, regexpList[key])){ - match = false; - } - } - } - if(match){ - items.push(candidateItem); - } - } - findCallback(items, requestArgs); - }else{ - // We want a copy to pass back in case the parent wishes to sort the array. - // We shouldn't allow resort of the internal list, so that multiple callers - // can get lists and sort without affecting each other. We also need to - // filter out any null values that have been left as a result of deleteItem() - // calls in ItemFileWriteStore. - for(i = 0; i < arrayOfItems.length; ++i){ - var item = arrayOfItems[i]; - if(item !== null){ - items.push(item); - } - } - findCallback(items, requestArgs); - } - }; - - if(this._loadFinished){ - filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); - }else{ - //Do a check on the JsonFileUrl and crosscheck it. - //If it doesn't match the cross-check, it needs to be updated - //This allows for either url or _jsonFileUrl to he changed to - //reset the store load location. Done this way for backwards - //compatibility. People use _jsonFileUrl (even though officially - //private. - if(this._jsonFileUrl !== this._ccUrl){ - dojo.deprecated("dojo.data.ItemFileReadStore: ", - "To change the url, set the url property of the store," + - " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); - this._ccUrl = this._jsonFileUrl; - this.url = this._jsonFileUrl; - }else if(this.url !== this._ccUrl){ - this._jsonFileUrl = this.url; - this._ccUrl = this.url; - } - - //See if there was any forced reset of data. - if(this.data != null){ - this._jsonData = this.data; - this.data = null; - } - - if(this._jsonFileUrl){ - //If fetches come in before the loading has finished, but while - //a load is in progress, we have to defer the fetching to be - //invoked in the callback. - if(this._loadInProgress){ - this._queuedFetches.push({args: keywordArgs, filter: filter}); - }else{ - this._loadInProgress = true; - var getArgs = { - url: self._jsonFileUrl, - handleAs: "json-comment-optional", - preventCache: this.urlPreventCache, - failOk: this.failOk - }; - var getHandler = dojo.xhrGet(getArgs); - getHandler.addCallback(function(data){ - try{ - self._getItemsFromLoadedData(data); - self._loadFinished = true; - self._loadInProgress = false; - - filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions)); - self._handleQueuedFetches(); - }catch(e){ - self._loadFinished = true; - self._loadInProgress = false; - errorCallback(e, keywordArgs); - } - }); - getHandler.addErrback(function(error){ - self._loadInProgress = false; - errorCallback(error, keywordArgs); - }); - - //Wire up the cancel to abort of the request - //This call cancel on the deferred if it hasn't been called - //yet and then will chain to the simple abort of the - //simpleFetch keywordArgs - var oldAbort = null; - if(keywordArgs.abort){ - oldAbort = keywordArgs.abort; - } - keywordArgs.abort = function(){ - var df = getHandler; - if(df && df.fired === -1){ - df.cancel(); - df = null; - } - if(oldAbort){ - oldAbort.call(keywordArgs); - } - }; - } - }else if(this._jsonData){ - try{ - this._loadFinished = true; - this._getItemsFromLoadedData(this._jsonData); - this._jsonData = null; - filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); - }catch(e){ - errorCallback(e, keywordArgs); - } - }else{ - errorCallback(new Error("dojo.data.ItemFileReadStore: No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs); - } - } - }, - - _handleQueuedFetches: function(){ - // summary: - // Internal function to execute delayed request in the store. - //Execute any deferred fetches now. - if(this._queuedFetches.length > 0){ - for(var i = 0; i < this._queuedFetches.length; i++){ - var fData = this._queuedFetches[i], - delayedQuery = fData.args, - delayedFilter = fData.filter; - if(delayedFilter){ - delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions)); - }else{ - this.fetchItemByIdentity(delayedQuery); - } - } - this._queuedFetches = []; - } - }, - - _getItemsArray: function(/*object?*/queryOptions){ - // summary: - // Internal function to determine which list of items to search over. - // queryOptions: The query options parameter, if any. - if(queryOptions && queryOptions.deep){ - return this._arrayOfAllItems; - } - return this._arrayOfTopLevelItems; - }, - - close: function(/*dojo.data.api.Request || keywordArgs || null */ request){ - // summary: - // See dojo.data.api.Read.close() - if(this.clearOnClose && - this._loadFinished && - !this._loadInProgress){ - //Reset all internalsback to default state. This will force a reload - //on next fetch. This also checks that the data or url param was set - //so that the store knows it can get data. Without one of those being set, - //the next fetch will trigger an error. - - if(((this._jsonFileUrl == "" || this._jsonFileUrl == null) && - (this.url == "" || this.url == null) - ) && this.data == null){ - console.debug("dojo.data.ItemFileReadStore: WARNING! Data reload " + - " information has not been provided." + - " Please set 'url' or 'data' to the appropriate value before" + - " the next fetch"); - } - this._arrayOfAllItems = []; - this._arrayOfTopLevelItems = []; - this._loadFinished = false; - this._itemsByIdentity = null; - this._loadInProgress = false; - this._queuedFetches = []; - } - }, - - _getItemsFromLoadedData: function(/* Object */ dataObject){ - // summary: - // Function to parse the loaded data into item format and build the internal items array. - // description: - // Function to parse the loaded data into item format and build the internal items array. - // - // dataObject: - // The JS data object containing the raw data to convery into item format. - // - // returns: array - // Array of items in store item format. - - // First, we define a couple little utility functions... - var addingArrays = false, - self = this; - - function valueIsAnItem(/* anything */ aValue){ - // summary: - // Given any sort of value that could be in the raw json data, - // return true if we should interpret the value as being an - // item itself, rather than a literal value or a reference. - // example: - // | false == valueIsAnItem("Kermit"); - // | false == valueIsAnItem(42); - // | false == valueIsAnItem(new Date()); - // | false == valueIsAnItem({_type:'Date', _value:'1802-05-14'}); - // | false == valueIsAnItem({_reference:'Kermit'}); - // | true == valueIsAnItem({name:'Kermit', color:'green'}); - // | true == valueIsAnItem({iggy:'pop'}); - // | true == valueIsAnItem({foo:42}); - var isItem = ( - (aValue !== null) && - (typeof aValue === "object") && - (!dojo.isArray(aValue) || addingArrays) && - (!dojo.isFunction(aValue)) && - (aValue.constructor == Object || dojo.isArray(aValue)) && - (typeof aValue._reference === "undefined") && - (typeof aValue._type === "undefined") && - (typeof aValue._value === "undefined") && - self.hierarchical - ); - return isItem; - } - - function addItemAndSubItemsToArrayOfAllItems(/* Item */ anItem){ - self._arrayOfAllItems.push(anItem); - for(var attribute in anItem){ - var valueForAttribute = anItem[attribute]; - if(valueForAttribute){ - if(dojo.isArray(valueForAttribute)){ - var valueArray = valueForAttribute; - for(var k = 0; k < valueArray.length; ++k){ - var singleValue = valueArray[k]; - if(valueIsAnItem(singleValue)){ - addItemAndSubItemsToArrayOfAllItems(singleValue); - } - } - }else{ - if(valueIsAnItem(valueForAttribute)){ - addItemAndSubItemsToArrayOfAllItems(valueForAttribute); - } - } - } - } - } - - this._labelAttr = dataObject.label; - - // We need to do some transformations to convert the data structure - // that we read from the file into a format that will be convenient - // to work with in memory. - - // Step 1: Walk through the object hierarchy and build a list of all items - var i, - item; - this._arrayOfAllItems = []; - this._arrayOfTopLevelItems = dataObject.items; - - for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){ - item = this._arrayOfTopLevelItems[i]; - if(dojo.isArray(item)){ - addingArrays = true; - } - addItemAndSubItemsToArrayOfAllItems(item); - item[this._rootItemPropName]=true; - } - - // Step 2: Walk through all the attribute values of all the items, - // and replace single values with arrays. For example, we change this: - // { name:'Miss Piggy', pets:'Foo-Foo'} - // into this: - // { name:['Miss Piggy'], pets:['Foo-Foo']} - // - // We also store the attribute names so we can validate our store - // reference and item id special properties for the O(1) isItem - var allAttributeNames = {}, - key; - - for(i = 0; i < this._arrayOfAllItems.length; ++i){ - item = this._arrayOfAllItems[i]; - for(key in item){ - if(key !== this._rootItemPropName){ - var value = item[key]; - if(value !== null){ - if(!dojo.isArray(value)){ - item[key] = [value]; - } - }else{ - item[key] = [null]; - } - } - allAttributeNames[key]=key; - } - } - - // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName - // This should go really fast, it will generally never even run the loop. - while(allAttributeNames[this._storeRefPropName]){ - this._storeRefPropName += "_"; - } - while(allAttributeNames[this._itemNumPropName]){ - this._itemNumPropName += "_"; - } - while(allAttributeNames[this._reverseRefMap]){ - this._reverseRefMap += "_"; - } - - // Step 4: Some data files specify an optional 'identifier', which is - // the name of an attribute that holds the identity of each item. - // If this data file specified an identifier attribute, then build a - // hash table of items keyed by the identity of the items. - var arrayOfValues; - - var identifier = dataObject.identifier; - if(identifier){ - this._itemsByIdentity = {}; - this._features['dojo.data.api.Identity'] = identifier; - for(i = 0; i < this._arrayOfAllItems.length; ++i){ - item = this._arrayOfAllItems[i]; - arrayOfValues = item[identifier]; - var identity = arrayOfValues[0]; - if(!Object.hasOwnProperty.call(this._itemsByIdentity, identity)){ - this._itemsByIdentity[identity] = item; - }else{ - if(this._jsonFileUrl){ - 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 + "]"); - }else if(this._jsonData){ - 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 + "]"); - } - } - } - }else{ - this._features['dojo.data.api.Identity'] = Number; - } - - // Step 5: Walk through all the items, and set each item's properties - // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true. - for(i = 0; i < this._arrayOfAllItems.length; ++i){ - item = this._arrayOfAllItems[i]; - item[this._storeRefPropName] = this; - item[this._itemNumPropName] = i; - } - - // Step 6: We walk through all the attribute values of all the items, - // looking for type/value literals and item-references. - // - // We replace item-references with pointers to items. For example, we change: - // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } - // into this: - // { name:['Kermit'], friends:[miss_piggy] } - // (where miss_piggy is the object representing the 'Miss Piggy' item). - // - // We replace type/value pairs with typed-literals. For example, we change: - // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'1918-07-18'}] } - // into this: - // { name:['Kermit'], born:(new Date(1918, 6, 18)) } - // - // We also generate the associate map for all items for the O(1) isItem function. - for(i = 0; i < this._arrayOfAllItems.length; ++i){ - item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } - for(key in item){ - arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}] - for(var j = 0; j < arrayOfValues.length; ++j){ - value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}} - if(value !== null && typeof value == "object"){ - if(("_type" in value) && ("_value" in value)){ - var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber' - var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}} - if(!mappingObj){ - throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'"); - }else if(dojo.isFunction(mappingObj)){ - arrayOfValues[j] = new mappingObj(value._value); - }else if(dojo.isFunction(mappingObj.deserialize)){ - arrayOfValues[j] = mappingObj.deserialize(value._value); - }else{ - throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function"); - } - } - if(value._reference){ - var referenceDescription = value._reference; // example: {name:'Miss Piggy'} - if(!dojo.isObject(referenceDescription)){ - // example: 'Miss Piggy' - // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]} - arrayOfValues[j] = this._getItemByIdentity(referenceDescription); - }else{ - // example: {name:'Miss Piggy'} - // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } - for(var k = 0; k < this._arrayOfAllItems.length; ++k){ - var candidateItem = this._arrayOfAllItems[k], - found = true; - for(var refKey in referenceDescription){ - if(candidateItem[refKey] != referenceDescription[refKey]){ - found = false; - } - } - if(found){ - arrayOfValues[j] = candidateItem; - } - } - } - if(this.referenceIntegrity){ - var refItem = arrayOfValues[j]; - if(this.isItem(refItem)){ - this._addReferenceToMap(refItem, item, key); - } - } - }else if(this.isItem(value)){ - //It's a child item (not one referenced through _reference). - //We need to treat this as a referenced item, so it can be cleaned up - //in a write store easily. - if(this.referenceIntegrity){ - this._addReferenceToMap(value, item, key); - } - } - } - } - } - } - }, - - _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){ - // summary: - // Method to add an reference map entry for an item and attribute. - // description: - // Method to add an reference map entry for an item and attribute. // - // refItem: - // The item that is referenced. - // parentItem: - // The item that holds the new reference to refItem. - // attribute: - // The attribute on parentItem that contains the new reference. - - //Stub function, does nothing. Real processing is in ItemFileWriteStore. - }, - - getIdentity: function(/* item */ item){ - // summary: - // See dojo.data.api.Identity.getIdentity() - var identifier = this._features['dojo.data.api.Identity']; - if(identifier === Number){ - return item[this._itemNumPropName]; // Number - }else{ - var arrayOfValues = item[identifier]; - if(arrayOfValues){ - return arrayOfValues[0]; // Object || String - } - } - return null; // null - }, - - fetchItemByIdentity: function(/* Object */ keywordArgs){ - // summary: - // See dojo.data.api.Identity.fetchItemByIdentity() - - // Hasn't loaded yet, we have to trigger the load. - var item, - scope; - if(!this._loadFinished){ - var self = this; - //Do a check on the JsonFileUrl and crosscheck it. - //If it doesn't match the cross-check, it needs to be updated - //This allows for either url or _jsonFileUrl to he changed to - //reset the store load location. Done this way for backwards - //compatibility. People use _jsonFileUrl (even though officially - //private. - if(this._jsonFileUrl !== this._ccUrl){ - dojo.deprecated("dojo.data.ItemFileReadStore: ", - "To change the url, set the url property of the store," + - " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); - this._ccUrl = this._jsonFileUrl; - this.url = this._jsonFileUrl; - }else if(this.url !== this._ccUrl){ - this._jsonFileUrl = this.url; - this._ccUrl = this.url; - } - - //See if there was any forced reset of data. - if(this.data != null && this._jsonData == null){ - this._jsonData = this.data; - this.data = null; - } - - if(this._jsonFileUrl){ + // Work around table sizing bugs on IE7 by forcing redraw - if(this._loadInProgress){ - this._queuedFetches.push({args: keywordArgs}); - }else{ - this._loadInProgress = true; - var getArgs = { - url: self._jsonFileUrl, - handleAs: "json-comment-optional", - preventCache: this.urlPreventCache, - failOk: this.failOk - }; - var getHandler = dojo.xhrGet(getArgs); - getHandler.addCallback(function(data){ - var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; - try{ - self._getItemsFromLoadedData(data); - self._loadFinished = true; - self._loadInProgress = false; - item = self._getItemByIdentity(keywordArgs.identity); - if(keywordArgs.onItem){ - keywordArgs.onItem.call(scope, item); - } - self._handleQueuedFetches(); - }catch(error){ - self._loadInProgress = false; - if(keywordArgs.onError){ - keywordArgs.onError.call(scope, error); - } - } - }); - getHandler.addErrback(function(error){ - self._loadInProgress = false; - if(keywordArgs.onError){ - var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; - keywordArgs.onError.call(scope, error); + if(has("ie") == 7){ // fix IE7 layout bug when the widget is scrolled out of sight + var domNode = this.domNode; + var parent = domNode.parentNode; + var pingNode = domNode.firstChild || domNode; // target node most unlikely to have a custom filter + var origFilter = pingNode.style.filter; // save custom filter, most likely nothing + var _this = this; + while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet + (function ping(){ + var disconnectHandle = _this.connect(parent, "onscroll", + function(){ + _this.disconnect(disconnectHandle); // only call once + pingNode.style.filter = (new Date()).getMilliseconds(); // set to anything that's unique + setTimeout(function(){ pingNode.style.filter = origFilter }, 0); // restore custom filter, if any } - }); - } - - }else if(this._jsonData){ - // Passed in data, no need to xhr. - self._getItemsFromLoadedData(self._jsonData); - self._jsonData = null; - self._loadFinished = true; - item = self._getItemByIdentity(keywordArgs.identity); - if(keywordArgs.onItem){ - scope = keywordArgs.scope?keywordArgs.scope:dojo.global; - keywordArgs.onItem.call(scope, item); - } - } - }else{ - // Already loaded. We can just look it up and call back. - item = this._getItemByIdentity(keywordArgs.identity); - if(keywordArgs.onItem){ - scope = keywordArgs.scope?keywordArgs.scope:dojo.global; - keywordArgs.onItem.call(scope, item); + ); + })(); + parent = parent.parentNode; } } - }, - - _getItemByIdentity: function(/* Object */ identity){ - // summary: - // Internal function to look an item up by its identity map. - var item = null; - if(this._itemsByIdentity && - Object.hasOwnProperty.call(this._itemsByIdentity, identity)){ - item = this._itemsByIdentity[identity]; - }else if (Object.hasOwnProperty.call(this._arrayOfAllItems, identity)){ - item = this._arrayOfAllItems[identity]; - } - if(item === undefined){ - item = null; - } - return item; // Object - }, - - getIdentityAttributes: function(/* item */ item){ - // summary: - // See dojo.data.api.Identity.getIdentityAttributes() - - var identifier = this._features['dojo.data.api.Identity']; - if(identifier === Number){ - // If (identifier === Number) it means getIdentity() just returns - // an integer item-number for each item. The dojo.data.api.Identity - // spec says we need to return null if the identity is not composed - // of attributes - return null; // null - }else{ - return [identifier]; // Array - } - }, - - _forceLoad: function(){ - // summary: - // Internal function to force a load of the store if it hasn't occurred yet. This is required - // for specific functions to work properly. - var self = this; - //Do a check on the JsonFileUrl and crosscheck it. - //If it doesn't match the cross-check, it needs to be updated - //This allows for either url or _jsonFileUrl to he changed to - //reset the store load location. Done this way for backwards - //compatibility. People use _jsonFileUrl (even though officially - //private. - if(this._jsonFileUrl !== this._ccUrl){ - dojo.deprecated("dojo.data.ItemFileReadStore: ", - "To change the url, set the url property of the store," + - " not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0"); - this._ccUrl = this._jsonFileUrl; - this.url = this._jsonFileUrl; - }else if(this.url !== this._ccUrl){ - this._jsonFileUrl = this.url; - this._ccUrl = this.url; - } - - //See if there was any forced reset of data. - if(this.data != null){ - this._jsonData = this.data; - this.data = null; - } - - if(this._jsonFileUrl){ - var getArgs = { - url: this._jsonFileUrl, - handleAs: "json-comment-optional", - preventCache: this.urlPreventCache, - failOk: this.failOk, - sync: true - }; - var getHandler = dojo.xhrGet(getArgs); - getHandler.addCallback(function(data){ - try{ - //Check to be sure there wasn't another load going on concurrently - //So we don't clobber data that comes in on it. If there is a load going on - //then do not save this data. It will potentially clobber current data. - //We mainly wanted to sync/wait here. - //TODO: Revisit the loading scheme of this store to improve multi-initial - //request handling. - if(self._loadInProgress !== true && !self._loadFinished){ - self._getItemsFromLoadedData(data); - self._loadFinished = true; - }else if(self._loadInProgress){ - //Okay, we hit an error state we can't recover from. A forced load occurred - //while an async load was occurring. Since we cannot block at this point, the best - //that can be managed is to throw an error. - throw new Error("dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress."); - } - }catch(e){ - console.log(e); - throw e; - } - }); - getHandler.addErrback(function(error){ - throw error; - }); - }else if(this._jsonData){ - self._getItemsFromLoadedData(self._jsonData); - self._jsonData = null; - self._loadFinished = true; - } } }); -//Mix in the simple fetch implementation to this class. -dojo.extend(dojo.data.ItemFileReadStore,dojo.data.util.simpleFetch); - -} - -if(!dojo._hasResource["dojo.data.ItemFileWriteStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. -dojo._hasResource["dojo.data.ItemFileWriteStore"] = true; -dojo.provide("dojo.data.ItemFileWriteStore"); - - - -dojo.declare("dojo.data.ItemFileWriteStore", dojo.data.ItemFileReadStore, { - constructor: function(/* object */ keywordParameters){ - // keywordParameters: {typeMap: object) - // The structure of the typeMap object is as follows: - // { - // type0: function || object, - // type1: function || object, - // ... - // typeN: function || object - // } - // Where if it is a function, it is assumed to be an object constructor that takes the - // value of _value as the initialization parameters. It is serialized assuming object.toString() - // serialization. If it is an object, then it is assumed - // to be an object of general form: - // { - // type: function, //constructor. - // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately. - // serialize: function(object) //The function that converts the object back into the proper file format form. - // } - - // ItemFileWriteStore extends ItemFileReadStore to implement these additional dojo.data APIs - this._features['dojo.data.api.Write'] = true; - this._features['dojo.data.api.Notification'] = true; - - // For keeping track of changes so that we can implement isDirty and revert - this._pending = { - _newItems:{}, - _modifiedItems:{}, - _deletedItems:{} - }; - - if(!this._datatypeMap['Date'].serialize){ - this._datatypeMap['Date'].serialize = function(obj){ - return dojo.date.stamp.toISOString(obj, {zulu:true}); - }; - } - //Disable only if explicitly set to false. - if(keywordParameters && (keywordParameters.referenceIntegrity === false)){ - this.referenceIntegrity = false; - } - - // this._saveInProgress is set to true, briefly, from when save() is first called to when it completes - this._saveInProgress = false; - }, - - referenceIntegrity: true, //Flag that defaultly enabled reference integrity tracking. This way it can also be disabled pogrammatially or declaratively. - - _assert: function(/* boolean */ condition){ - if(!condition){ - throw new Error("assertion failed in ItemFileWriteStore"); - } - }, - - _getIdentifierAttribute: function(){ - var identifierAttribute = this.getFeatures()['dojo.data.api.Identity']; - // this._assert((identifierAttribute === Number) || (dojo.isString(identifierAttribute))); - return identifierAttribute; - }, - - -/* dojo.data.api.Write */ - - newItem: function(/* Object? */ keywordArgs, /* Object? */ parentInfo){ - // summary: See dojo.data.api.Write.newItem() - - this._assert(!this._saveInProgress); - - if(!this._loadFinished){ - // We need to do this here so that we'll be able to find out what - // identifierAttribute was specified in the data file. - this._forceLoad(); - } - - if(typeof keywordArgs != "object" && typeof keywordArgs != "undefined"){ - throw new Error("newItem() was passed something other than an object"); - } - var newIdentity = null; - var identifierAttribute = this._getIdentifierAttribute(); - if(identifierAttribute === Number){ - newIdentity = this._arrayOfAllItems.length; - }else{ - newIdentity = keywordArgs[identifierAttribute]; - if(typeof newIdentity === "undefined"){ - throw new Error("newItem() was not passed an identity for the new item"); - } - if(dojo.isArray(newIdentity)){ - throw new Error("newItem() was not passed an single-valued identity"); - } - } - - // make sure this identity is not already in use by another item, if identifiers were - // defined in the file. Otherwise it would be the item count, - // which should always be unique in this case. - if(this._itemsByIdentity){ - this._assert(typeof this._itemsByIdentity[newIdentity] === "undefined"); - } - this._assert(typeof this._pending._newItems[newIdentity] === "undefined"); - this._assert(typeof this._pending._deletedItems[newIdentity] === "undefined"); - - var newItem = {}; - newItem[this._storeRefPropName] = this; - newItem[this._itemNumPropName] = this._arrayOfAllItems.length; - if(this._itemsByIdentity){ - this._itemsByIdentity[newIdentity] = newItem; - //We have to set the identifier now, otherwise we can't look it - //up at calls to setValueorValues in parentInfo handling. - newItem[identifierAttribute] = [newIdentity]; - } - this._arrayOfAllItems.push(newItem); - - //We need to construct some data for the onNew call too... - var pInfo = null; - - // Now we need to check to see where we want to assign this thingm if any. - if(parentInfo && parentInfo.parent && parentInfo.attribute){ - pInfo = { - item: parentInfo.parent, - attribute: parentInfo.attribute, - oldValue: undefined - }; - - //See if it is multi-valued or not and handle appropriately - //Generally, all attributes are multi-valued for this store - //So, we only need to append if there are already values present. - var values = this.getValues(parentInfo.parent, parentInfo.attribute); - if(values && values.length > 0){ - var tempValues = values.slice(0, values.length); - if(values.length === 1){ - pInfo.oldValue = values[0]; - }else{ - pInfo.oldValue = values.slice(0, values.length); - } - tempValues.push(newItem); - this._setValueOrValues(parentInfo.parent, parentInfo.attribute, tempValues, false); - pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute); - }else{ - this._setValueOrValues(parentInfo.parent, parentInfo.attribute, newItem, false); - pInfo.newValue = newItem; - } - }else{ - //Toplevel item, add to both top list as well as all list. - newItem[this._rootItemPropName]=true; - this._arrayOfTopLevelItems.push(newItem); - } - - this._pending._newItems[newIdentity] = newItem; - - //Clone over the properties to the new item - for(var key in keywordArgs){ - if(key === this._storeRefPropName || key === this._itemNumPropName){ - // Bummer, the user is trying to do something like - // newItem({_S:"foo"}). Unfortunately, our superclass, - // ItemFileReadStore, is already using _S in each of our items - // to hold private info. To avoid a naming collision, we - // need to move all our private info to some other property - // of all the items/objects. So, we need to iterate over all - // the items and do something like: - // item.__S = item._S; - // item._S = undefined; - // But first we have to make sure the new "__S" variable is - // not in use, which means we have to iterate over all the - // items checking for that. - throw new Error("encountered bug in ItemFileWriteStore.newItem"); - } - var value = keywordArgs[key]; - if(!dojo.isArray(value)){ - value = [value]; - } - newItem[key] = value; - if(this.referenceIntegrity){ - for(var i = 0; i < value.length; i++){ - var val = value[i]; - if(this.isItem(val)){ - this._addReferenceToMap(val, newItem, key); - } - } - } - } - this.onNew(newItem, pInfo); // dojo.data.api.Notification call - return newItem; // item - }, - - _removeArrayElement: function(/* Array */ array, /* anything */ element){ - var index = dojo.indexOf(array, element); - if(index != -1){ - array.splice(index, 1); - return true; - } - return false; - }, - - deleteItem: function(/* item */ item){ - // summary: See dojo.data.api.Write.deleteItem() - this._assert(!this._saveInProgress); - this._assertIsItem(item); - - // Remove this item from the _arrayOfAllItems, but leave a null value in place - // of the item, so as not to change the length of the array, so that in newItem() - // we can still safely do: newIdentity = this._arrayOfAllItems.length; - var indexInArrayOfAllItems = item[this._itemNumPropName]; - var identity = this.getIdentity(item); - - //If we have reference integrity on, we need to do reference cleanup for the deleted item - if(this.referenceIntegrity){ - //First scan all the attributes of this items for references and clean them up in the map - //As this item is going away, no need to track its references anymore. - - //Get the attributes list before we generate the backup so it - //doesn't pollute the attributes list. - var attributes = this.getAttributes(item); - - //Backup the map, we'll have to restore it potentially, in a revert. - if(item[this._reverseRefMap]){ - item["backup_" + this._reverseRefMap] = dojo.clone(item[this._reverseRefMap]); - } - - //TODO: This causes a reversion problem. This list won't be restored on revert since it is - //attached to the 'value'. item, not ours. Need to back tese up somehow too. - //Maybe build a map of the backup of the entries and attach it to the deleted item to be restored - //later. Or just record them and call _addReferenceToMap on them in revert. - dojo.forEach(attributes, function(attribute){ - dojo.forEach(this.getValues(item, attribute), function(value){ - if(this.isItem(value)){ - //We have to back up all the references we had to others so they can be restored on a revert. - if(!item["backupRefs_" + this._reverseRefMap]){ - item["backupRefs_" + this._reverseRefMap] = []; - } - item["backupRefs_" + this._reverseRefMap].push({id: this.getIdentity(value), attr: attribute}); - this._removeReferenceFromMap(value, item, attribute); - } - }, this); - }, this); - - //Next, see if we have references to this item, if we do, we have to clean them up too. - var references = item[this._reverseRefMap]; - if(references){ - //Look through all the items noted as references to clean them up. - for(var itemId in references){ - var containingItem = null; - if(this._itemsByIdentity){ - containingItem = this._itemsByIdentity[itemId]; - }else{ - containingItem = this._arrayOfAllItems[itemId]; - } - //We have a reference to a containing item, now we have to process the - //attributes and clear all references to the item being deleted. - if(containingItem){ - for(var attribute in references[itemId]){ - var oldValues = this.getValues(containingItem, attribute) || []; - var newValues = dojo.filter(oldValues, function(possibleItem){ - return !(this.isItem(possibleItem) && this.getIdentity(possibleItem) == identity); - }, this); - //Remove the note of the reference to the item and set the values on the modified attribute. - this._removeReferenceFromMap(item, containingItem, attribute); - if(newValues.length < oldValues.length){ - this._setValueOrValues(containingItem, attribute, newValues, true); - } - } - } - } - } - } - - this._arrayOfAllItems[indexInArrayOfAllItems] = null; - - item[this._storeRefPropName] = null; - if(this._itemsByIdentity){ - delete this._itemsByIdentity[identity]; - } - this._pending._deletedItems[identity] = item; - - //Remove from the toplevel items, if necessary... - if(item[this._rootItemPropName]){ - this._removeArrayElement(this._arrayOfTopLevelItems, item); - } - this.onDelete(item); // dojo.data.api.Notification call - return true; - }, - - setValue: function(/* item */ item, /* attribute-name-string */ attribute, /* almost anything */ value){ - // summary: See dojo.data.api.Write.set() - return this._setValueOrValues(item, attribute, value, true); // boolean - }, - - setValues: function(/* item */ item, /* attribute-name-string */ attribute, /* array */ values){ - // summary: See dojo.data.api.Write.setValues() - return this._setValueOrValues(item, attribute, values, true); // boolean - }, - - unsetAttribute: function(/* item */ item, /* attribute-name-string */ attribute){ - // summary: See dojo.data.api.Write.unsetAttribute() - return this._setValueOrValues(item, attribute, [], true); - }, - - _setValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute, /* anything */ newValueOrValues, /*boolean?*/ callOnSet){ - this._assert(!this._saveInProgress); - - // Check for valid arguments - this._assertIsItem(item); - this._assert(dojo.isString(attribute)); - this._assert(typeof newValueOrValues !== "undefined"); - // Make sure the user isn't trying to change the item's identity - var identifierAttribute = this._getIdentifierAttribute(); - if(attribute == identifierAttribute){ - throw new Error("ItemFileWriteStore does not have support for changing the value of an item's identifier."); - } - - // To implement the Notification API, we need to make a note of what - // the old attribute value was, so that we can pass that info when - // we call the onSet method. - var oldValueOrValues = this._getValueOrValues(item, attribute); - - var identity = this.getIdentity(item); - if(!this._pending._modifiedItems[identity]){ - // Before we actually change the item, we make a copy of it to - // record the original state, so that we'll be able to revert if - // the revert method gets called. If the item has already been - // modified then there's no need to do this now, since we already - // have a record of the original state. - var copyOfItemState = {}; - for(var key in item){ - if((key === this._storeRefPropName) || (key === this._itemNumPropName) || (key === this._rootItemPropName)){ - copyOfItemState[key] = item[key]; - }else if(key === this._reverseRefMap){ - copyOfItemState[key] = dojo.clone(item[key]); - }else{ - copyOfItemState[key] = item[key].slice(0, item[key].length); - } - } - // Now mark the item as dirty, and save the copy of the original state - this._pending._modifiedItems[identity] = copyOfItemState; - } - - // Okay, now we can actually change this attribute on the item - var success = false; - - if(dojo.isArray(newValueOrValues) && newValueOrValues.length === 0){ - - // If we were passed an empty array as the value, that counts - // as "unsetting" the attribute, so we need to remove this - // attribute from the item. - success = delete item[attribute]; - newValueOrValues = undefined; // used in the onSet Notification call below - - if(this.referenceIntegrity && oldValueOrValues){ - var oldValues = oldValueOrValues; - if(!dojo.isArray(oldValues)){ - oldValues = [oldValues]; - } - for(var i = 0; i < oldValues.length; i++){ - var value = oldValues[i]; - if(this.isItem(value)){ - this._removeReferenceFromMap(value, item, attribute); - } - } - } - }else{ - var newValueArray; - if(dojo.isArray(newValueOrValues)){ - var newValues = newValueOrValues; - // Unfortunately, it's not safe to just do this: - // newValueArray = newValues; - // Instead, we need to copy the array, which slice() does very nicely. - // This is so that our internal data structure won't - // get corrupted if the user mucks with the values array *after* - // calling setValues(). - newValueArray = newValueOrValues.slice(0, newValueOrValues.length); - }else{ - newValueArray = [newValueOrValues]; - } - - //We need to handle reference integrity if this is on. - //In the case of set, we need to see if references were added or removed - //and update the reference tracking map accordingly. - if(this.referenceIntegrity){ - if(oldValueOrValues){ - var oldValues = oldValueOrValues; - if(!dojo.isArray(oldValues)){ - oldValues = [oldValues]; - } - //Use an associative map to determine what was added/removed from the list. - //Should be O(n) performant. First look at all the old values and make a list of them - //Then for any item not in the old list, we add it. If it was already present, we remove it. - //Then we pass over the map and any references left it it need to be removed (IE, no match in - //the new values list). - var map = {}; - dojo.forEach(oldValues, function(possibleItem){ - if(this.isItem(possibleItem)){ - var id = this.getIdentity(possibleItem); - map[id.toString()] = true; - } - }, this); - dojo.forEach(newValueArray, function(possibleItem){ - if(this.isItem(possibleItem)){ - var id = this.getIdentity(possibleItem); - if(map[id.toString()]){ - delete map[id.toString()]; - }else{ - this._addReferenceToMap(possibleItem, item, attribute); - } - } - }, this); - for(var rId in map){ - var removedItem; - if(this._itemsByIdentity){ - removedItem = this._itemsByIdentity[rId]; - }else{ - removedItem = this._arrayOfAllItems[rId]; - } - this._removeReferenceFromMap(removedItem, item, attribute); - } - }else{ - //Everything is new (no old values) so we have to just - //insert all the references, if any. - for(var i = 0; i < newValueArray.length; i++){ - var value = newValueArray[i]; - if(this.isItem(value)){ - this._addReferenceToMap(value, item, attribute); - } - } - } - } - item[attribute] = newValueArray; - success = true; - } - - // Now we make the dojo.data.api.Notification call - if(callOnSet){ - this.onSet(item, attribute, oldValueOrValues, newValueOrValues); - } - return success; // boolean - }, - - _addReferenceToMap: function(/*item*/ refItem, /*item*/ parentItem, /*string*/ attribute){ - // summary: - // Method to add an reference map entry for an item and attribute. - // description: - // Method to add an reference map entry for an item and attribute. // - // refItem: - // The item that is referenced. - // parentItem: - // The item that holds the new reference to refItem. - // attribute: - // The attribute on parentItem that contains the new reference. - - var parentId = this.getIdentity(parentItem); - var references = refItem[this._reverseRefMap]; - - if(!references){ - references = refItem[this._reverseRefMap] = {}; - } - var itemRef = references[parentId]; - if(!itemRef){ - itemRef = references[parentId] = {}; - } - itemRef[attribute] = true; - }, - - _removeReferenceFromMap: function(/* item */ refItem, /* item */ parentItem, /*strin*/ attribute){ - // summary: - // Method to remove an reference map entry for an item and attribute. - // description: - // Method to remove an reference map entry for an item and attribute. This will - // also perform cleanup on the map such that if there are no more references at all to - // the item, its reference object and entry are removed. - // - // refItem: - // The item that is referenced. - // parentItem: - // The item holding a reference to refItem. - // attribute: - // The attribute on parentItem that contains the reference. - var identity = this.getIdentity(parentItem); - var references = refItem[this._reverseRefMap]; - var itemId; - if(references){ - for(itemId in references){ - if(itemId == identity){ - delete references[itemId][attribute]; - if(this._isEmpty(references[itemId])){ - delete references[itemId]; - } - } - } - if(this._isEmpty(references)){ - delete refItem[this._reverseRefMap]; - } - } - }, - - _dumpReferenceMap: function(){ - // summary: - // Function to dump the reverse reference map of all items in the store for debug purposes. - // description: - // Function to dump the reverse reference map of all items in the store for debug purposes. - var i; - for(i = 0; i < this._arrayOfAllItems.length; i++){ - var item = this._arrayOfAllItems[i]; - if(item && item[this._reverseRefMap]){ - console.log("Item: [" + this.getIdentity(item) + "] is referenced by: " + dojo.toJson(item[this._reverseRefMap])); - } - } - }, - - _getValueOrValues: function(/* item */ item, /* attribute-name-string */ attribute){ - var valueOrValues = undefined; - if(this.hasAttribute(item, attribute)){ - var valueArray = this.getValues(item, attribute); - if(valueArray.length == 1){ - valueOrValues = valueArray[0]; - }else{ - valueOrValues = valueArray; - } - } - return valueOrValues; - }, - - _flatten: function(/* anything */ value){ - if(this.isItem(value)){ - var item = value; - // Given an item, return an serializable object that provides a - // reference to the item. - // For example, given kermit: - // var kermit = store.newItem({id:2, name:"Kermit"}); - // we want to return - // {_reference:2} - var identity = this.getIdentity(item); - var referenceObject = {_reference: identity}; - return referenceObject; - }else{ - if(typeof value === "object"){ - for(var type in this._datatypeMap){ - var typeMap = this._datatypeMap[type]; - if(dojo.isObject(typeMap) && !dojo.isFunction(typeMap)){ - if(value instanceof typeMap.type){ - if(!typeMap.serialize){ - throw new Error("ItemFileWriteStore: No serializer defined for type mapping: [" + type + "]"); - } - return {_type: type, _value: typeMap.serialize(value)}; - } - } else if(value instanceof typeMap){ - //SImple mapping, therefore, return as a toString serialization. - return {_type: type, _value: value.toString()}; - } - } - } - return value; - } - }, - - _getNewFileContentString: function(){ - // summary: - // Generate a string that can be saved to a file. - // The result should look similar to: - // http://trac.dojotoolkit.org/browser/dojo/trunk/tests/data/countries.json - var serializableStructure = {}; - - var identifierAttribute = this._getIdentifierAttribute(); - if(identifierAttribute !== Number){ - serializableStructure.identifier = identifierAttribute; - } - if(this._labelAttr){ - serializableStructure.label = this._labelAttr; - } - serializableStructure.items = []; - for(var i = 0; i < this._arrayOfAllItems.length; ++i){ - var item = this._arrayOfAllItems[i]; - if(item !== null){ - var serializableItem = {}; - for(var key in item){ - if(key !== this._storeRefPropName && key !== this._itemNumPropName && key !== this._reverseRefMap && key !== this._rootItemPropName){ - var attribute = key; - var valueArray = this.getValues(item, attribute); - if(valueArray.length == 1){ - serializableItem[attribute] = this._flatten(valueArray[0]); - }else{ - var serializableArray = []; - for(var j = 0; j < valueArray.length; ++j){ - serializableArray.push(this._flatten(valueArray[j])); - serializableItem[attribute] = serializableArray; - } - } - } - } - serializableStructure.items.push(serializableItem); - } - } - var prettyPrint = true; - return dojo.toJson(serializableStructure, prettyPrint); - }, - - _isEmpty: function(something){ - // summary: - // Function to determine if an array or object has no properties or values. - // something: - // The array or object to examine. - var empty = true; - if(dojo.isObject(something)){ - var i; - for(i in something){ - empty = false; - break; - } - }else if(dojo.isArray(something)){ - if(something.length > 0){ - empty = false; - } - } - return empty; //boolean - }, - - save: function(/* object */ keywordArgs){ - // summary: See dojo.data.api.Write.save() - this._assert(!this._saveInProgress); - - // this._saveInProgress is set to true, briefly, from when save is first called to when it completes - this._saveInProgress = true; - - var self = this; - var saveCompleteCallback = function(){ - self._pending = { - _newItems:{}, - _modifiedItems:{}, - _deletedItems:{} - }; - - self._saveInProgress = false; // must come after this._pending is cleared, but before any callbacks - if(keywordArgs && keywordArgs.onComplete){ - var scope = keywordArgs.scope || dojo.global; - keywordArgs.onComplete.call(scope); - } - }; - var saveFailedCallback = function(err){ - self._saveInProgress = false; - if(keywordArgs && keywordArgs.onError){ - var scope = keywordArgs.scope || dojo.global; - keywordArgs.onError.call(scope, err); - } - }; - - if(this._saveEverything){ - var newFileContentString = this._getNewFileContentString(); - this._saveEverything(saveCompleteCallback, saveFailedCallback, newFileContentString); - } - if(this._saveCustom){ - this._saveCustom(saveCompleteCallback, saveFailedCallback); - } - if(!this._saveEverything && !this._saveCustom){ - // Looks like there is no user-defined save-handler function. - // That's fine, it just means the datastore is acting as a "mock-write" - // store -- changes get saved in memory but don't get saved to disk. - saveCompleteCallback(); - } - }, - - revert: function(){ - // summary: See dojo.data.api.Write.revert() - this._assert(!this._saveInProgress); - - var identity; - for(identity in this._pending._modifiedItems){ - // find the original item and the modified item that replaced it - var copyOfItemState = this._pending._modifiedItems[identity]; - var modifiedItem = null; - if(this._itemsByIdentity){ - modifiedItem = this._itemsByIdentity[identity]; - }else{ - modifiedItem = this._arrayOfAllItems[identity]; - } - - // Restore the original item into a full-fledged item again, we want to try to - // keep the same object instance as if we don't it, causes bugs like #9022. - copyOfItemState[this._storeRefPropName] = this; - for(key in modifiedItem){ - delete modifiedItem[key]; - } - dojo.mixin(modifiedItem, copyOfItemState); - } - var deletedItem; - for(identity in this._pending._deletedItems){ - deletedItem = this._pending._deletedItems[identity]; - deletedItem[this._storeRefPropName] = this; - var index = deletedItem[this._itemNumPropName]; - - //Restore the reverse refererence map, if any. - if(deletedItem["backup_" + this._reverseRefMap]){ - deletedItem[this._reverseRefMap] = deletedItem["backup_" + this._reverseRefMap]; - delete deletedItem["backup_" + this._reverseRefMap]; - } - this._arrayOfAllItems[index] = deletedItem; - if(this._itemsByIdentity){ - this._itemsByIdentity[identity] = deletedItem; - } - if(deletedItem[this._rootItemPropName]){ - this._arrayOfTopLevelItems.push(deletedItem); - } - } - //We have to pass through it again and restore the reference maps after all the - //undeletes have occurred. - for(identity in this._pending._deletedItems){ - deletedItem = this._pending._deletedItems[identity]; - if(deletedItem["backupRefs_" + this._reverseRefMap]){ - dojo.forEach(deletedItem["backupRefs_" + this._reverseRefMap], function(reference){ - var refItem; - if(this._itemsByIdentity){ - refItem = this._itemsByIdentity[reference.id]; - }else{ - refItem = this._arrayOfAllItems[reference.id]; - } - this._addReferenceToMap(refItem, deletedItem, reference.attr); - }, this); - delete deletedItem["backupRefs_" + this._reverseRefMap]; - } - } - - for(identity in this._pending._newItems){ - var newItem = this._pending._newItems[identity]; - newItem[this._storeRefPropName] = null; - // null out the new item, but don't change the array index so - // so we can keep using _arrayOfAllItems.length. - this._arrayOfAllItems[newItem[this._itemNumPropName]] = null; - if(newItem[this._rootItemPropName]){ - this._removeArrayElement(this._arrayOfTopLevelItems, newItem); - } - if(this._itemsByIdentity){ - delete this._itemsByIdentity[identity]; - } - } - - this._pending = { - _newItems:{}, - _modifiedItems:{}, - _deletedItems:{} - }; - return true; // boolean - }, - - isDirty: function(/* item? */ item){ - // summary: See dojo.data.api.Write.isDirty() - if(item){ - // return true if the item is dirty - var identity = this.getIdentity(item); - return new Boolean(this._pending._newItems[identity] || - this._pending._modifiedItems[identity] || - this._pending._deletedItems[identity]).valueOf(); // boolean - }else{ - // return true if the store is dirty -- which means return true - // if there are any new items, dirty items, or modified items - if(!this._isEmpty(this._pending._newItems) || - !this._isEmpty(this._pending._modifiedItems) || - !this._isEmpty(this._pending._deletedItems)){ - return true; - } - return false; // boolean - } - }, - -/* dojo.data.api.Notification */ - - onSet: function(/* item */ item, - /*attribute-name-string*/ attribute, - /*object | array*/ oldValue, - /*object | array*/ newValue){ - // summary: See dojo.data.api.Notification.onSet() - - // No need to do anything. This method is here just so that the - // client code can connect observers to it. - }, - - onNew: function(/* item */ newItem, /*object?*/ parentInfo){ - // summary: See dojo.data.api.Notification.onNew() - - // No need to do anything. This method is here just so that the - // client code can connect observers to it. - }, - - onDelete: function(/* item */ deletedItem){ - // summary: See dojo.data.api.Notification.onDelete() - - // No need to do anything. This method is here just so that the - // client code can connect observers to it. - }, - - close: function(/* object? */ request){ - // summary: - // Over-ride of base close function of ItemFileReadStore to add in check for store state. - // description: - // Over-ride of base close function of ItemFileReadStore to add in check for store state. - // If the store is still dirty (unsaved changes), then an error will be thrown instead of - // clearing the internal state for reload from the url. - - //Clear if not dirty ... or throw an error - if(this.clearOnClose){ - if(!this.isDirty()){ - this.inherited(arguments); - }else{ - //Only throw an error if the store was dirty and we were loading from a url (cannot reload from url until state is saved). - throw new Error("dojo.data.ItemFileWriteStore: There are unsaved changes present in the store. Please save or revert the changes before invoking close."); - } - } - } }); -} - - -dojo.i18n._preloadLocalizations("dojo.nls.tt-rss-layer", ["ROOT","ar","ca","cs","da","de","de-de","el","en","en-gb","en-us","es","es-es","fi","fi-fi","fr","fr-fr","he","he-il","hu","it","it-it","ja","ja-jp","ko","ko-kr","nb","nl","nl-nl","pl","pt","pt-br","pt-pt","ru","sk","sl","sv","th","tr","xx","zh","zh-cn","zh-tw"]); +}, +'*now':function(r){r(['dojo/i18n!*preload*dojo/nls/tt-rss-layer*["ar","ca","cs","da","de-de","el","en-gb","en-us","es-es","fi-fi","fr-fr","he-il","hu","it-it","ja-jp","ko-kr","nl-nl","nb","pl","pt-br","pt-pt","ru","sk","sl","sv","th","tr","zh-tw","zh-cn","ROOT"]']);} +}}); +define("dojo/tt-rss-layer", [], 1); |