diff options
Diffstat (limited to 'js/dojo-1.7.2/dijit/dijit-all.js.uncompressed.js')
| -rw-r--r-- | js/dojo-1.7.2/dijit/dijit-all.js.uncompressed.js | 33908 |
1 files changed, 33908 insertions, 0 deletions
diff --git a/js/dojo-1.7.2/dijit/dijit-all.js.uncompressed.js b/js/dojo-1.7.2/dijit/dijit-all.js.uncompressed.js new file mode 100644 index 0000000..526399e --- /dev/null +++ b/js/dojo-1.7.2/dijit/dijit-all.js.uncompressed.js @@ -0,0 +1,33908 @@ +/* + 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 +*/ + +/* + This is an optimized version of Dojo, built for deployment and not for + development. To get sources and documentation, please visit: + + http://dojotoolkit.org +*/ + +//>>built +require({cache:{ +'dijit/_editor/plugins/FontChoice':function(){ +define([ + "dojo/_base/array", // array.indexOf array.map + "dojo/_base/declare", // declare + "dojo/dom-construct", // domConstruct.place + "dojo/i18n", // i18n.getLocalization + "dojo/_base/lang", // lang.delegate lang.hitch lang.isString + "dojo/store/Memory", // MemoryStore + "dojo/_base/window", // win.withGlobal + "../../registry", // registry.getUniqueId + "../../_Widget", + "../../_TemplatedMixin", + "../../_WidgetsInTemplateMixin", + "../../form/FilteringSelect", + "../_Plugin", + "../range", + "../selection", + "dojo/i18n!../nls/FontChoice" +], function(array, declare, domConstruct, i18n, lang, MemoryStore, win, + registry, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, FilteringSelect, _Plugin, rangeapi, selectionapi){ + +/*===== + var _Plugin = dijit._editor._Plugin; + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _WidgetsInTemplateMixin = dijit._WidgetsInTemplateMixin; + var FilteringSelect = dijit.form.FilteringSelect; +=====*/ + + +// module: +// dijit/_editor/plugins/FontChoice +// summary: +// fontchoice, fontsize, and formatblock editor plugins + + +var _FontDropDown = declare("dijit._editor.plugins._FontDropDown", + [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], { + // summary: + // Base class for widgets that contains a label (like "Font:") + // and a FilteringSelect drop down to pick a value. + // Used as Toolbar entry. + + // label: [public] String + // The label to apply to this particular FontDropDown. + label: "", + + // plainText: [public] boolean + // Flag to indicate that the returned label should be plain text + // instead of an example. + plainText: false, + + // templateString: [public] String + // The template used to construct the labeled dropdown. + templateString: + "<span style='white-space: nowrap' class='dijit dijitReset dijitInline'>" + + "<label class='dijitLeft dijitInline' for='${selectId}'>${label}</label>" + + "<input data-dojo-type='dijit.form.FilteringSelect' required='false' " + + "data-dojo-props='labelType:\"html\", labelAttr:\"label\", searchAttr:\"name\"' " + + "tabIndex='-1' id='${selectId}' data-dojo-attach-point='select' value=''/>" + + "</span>", + + postMixInProperties: function(){ + // summary: + // Over-ride to set specific properties. + this.inherited(arguments); + + this.strings = i18n.getLocalization("dijit._editor", "FontChoice"); + + // Set some substitution variables used in the template + this.label = this.strings[this.command]; + this.id = registry.getUniqueId(this.declaredClass.replace(/\./g,"_")); // TODO: unneeded?? + this.selectId = this.id + "_select"; // used in template + + this.inherited(arguments); + }, + + postCreate: function(){ + // summary: + // Over-ride for the default postCreate action + // This establishes the filtering selects and the like. + + // Initialize the list of items in the drop down by creating data store with items like: + // {value: 1, name: "xx-small", label: "<font size=1>xx-small</font-size>" } + this.select.set("store", new MemoryStore({ + idProperty: "value", + data: array.map(this.values, function(value){ + var name = this.strings[value] || value; + return { + label: this.getLabel(value, name), + name: name, + value: value + }; + }, this) + })); + + this.select.set("value", "", false); + this.disabled = this.select.get("disabled"); + }, + + _setValueAttr: function(value, priorityChange){ + // summary: + // Over-ride for the default action of setting the + // widget value, maps the input to known values + // value: Object|String + // The value to set in the select. + // priorityChange: + // Optional parameter used to tell the select whether or not to fire + // onChange event. + + // if the value is not a permitted value, just set empty string to prevent showing the warning icon + priorityChange = priorityChange !== false; + this.select.set('value', array.indexOf(this.values,value) < 0 ? "" : value, priorityChange); + if(!priorityChange){ + // Clear the last state in case of updateState calls. Ref: #10466 + this.select._lastValueReported=null; + } + }, + + _getValueAttr: function(){ + // summary: + // Allow retrieving the value from the composite select on + // call to button.get("value"); + return this.select.get('value'); + }, + + focus: function(){ + // summary: + // Over-ride for focus control of this widget. Delegates focus down to the + // filtering select. + this.select.focus(); + }, + + _setDisabledAttr: function(value){ + // summary: + // Over-ride for the button's 'disabled' attribute so that it can be + // disabled programmatically. + + // Save off ths disabled state so the get retrieves it correctly + //without needing to have a function proxy it. + this.disabled = value; + this.select.set("disabled", value); + } +}); + + +var _FontNameDropDown = declare("dijit._editor.plugins._FontNameDropDown", _FontDropDown, { + // summary: + // Dropdown to select a font; goes in editor toolbar. + + // generic: Boolean + // Use generic (web standard) font names + generic: false, + + // command: [public] String + // The editor 'command' implemented by this plugin. + command: "fontName", + + postMixInProperties: function(){ + // summary: + // Over-ride for the default posr mixin control + if(!this.values){ + this.values = this.generic ? + ["serif", "sans-serif", "monospace", "cursive", "fantasy"] : // CSS font-family generics + ["Arial", "Times New Roman", "Comic Sans MS", "Courier New"]; + } + this.inherited(arguments); + }, + + getLabel: function(value, name){ + // summary: + // Function used to generate the labels of the format dropdown + // will return a formatted, or plain label based on the value + // of the plainText option. + // value: String + // The 'insert value' associated with a name + // name: String + // The text name of the value + if(this.plainText){ + return name; + }else{ + return "<div style='font-family: "+value+"'>" + name + "</div>"; + } + }, + + _setValueAttr: function(value, priorityChange){ + // summary: + // Over-ride for the default action of setting the + // widget value, maps the input to known values + + priorityChange = priorityChange !== false; + if(this.generic){ + var map = { + "Arial": "sans-serif", + "Helvetica": "sans-serif", + "Myriad": "sans-serif", + "Times": "serif", + "Times New Roman": "serif", + "Comic Sans MS": "cursive", + "Apple Chancery": "cursive", + "Courier": "monospace", + "Courier New": "monospace", + "Papyrus": "fantasy", + "Estrangelo Edessa": "cursive", // Windows 7 + "Gabriola": "fantasy" // Windows 7 + }; + value = map[value] || value; + } + this.inherited(arguments, [value, priorityChange]); + } +}); + +var _FontSizeDropDown = declare("dijit._editor.plugins._FontSizeDropDown", _FontDropDown, { + // summary: + // Dropdown to select a font size; goes in editor toolbar. + + // command: [public] String + // The editor 'command' implemented by this plugin. + command: "fontSize", + + // values: [public] Number[] + // The HTML font size values supported by this plugin + values: [1,2,3,4,5,6,7], // sizes according to the old HTML FONT SIZE + + getLabel: function(value, name){ + // summary: + // Function used to generate the labels of the format dropdown + // will return a formatted, or plain label based on the value + // of the plainText option. + // We're stuck using the deprecated FONT tag to correspond + // with the size measurements used by the editor + // value: String + // The 'insert value' associated with a name + // name: String + // The text name of the value + if(this.plainText){ + return name; + }else{ + return "<font size=" + value + "'>" + name + "</font>"; + } + }, + + _setValueAttr: function(value, priorityChange){ + // summary: + // Over-ride for the default action of setting the + // widget value, maps the input to known values + priorityChange = priorityChange !== false; + if(value.indexOf && value.indexOf("px") != -1){ + var pixels = parseInt(value, 10); + value = {10:1, 13:2, 16:3, 18:4, 24:5, 32:6, 48:7}[pixels] || value; + } + + this.inherited(arguments, [value, priorityChange]); + } +}); + + +var _FormatBlockDropDown = declare("dijit._editor.plugins._FormatBlockDropDown", _FontDropDown, { + // summary: + // Dropdown to select a format (like paragraph or heading); goes in editor toolbar. + + // command: [public] String + // The editor 'command' implemented by this plugin. + command: "formatBlock", + + // values: [public] Array + // The HTML format tags supported by this plugin + values: ["noFormat", "p", "h1", "h2", "h3", "pre"], + + postCreate: function(){ + // Init and set the default value to no formatting. Update state will adjust it + // as needed. + this.inherited(arguments); + this.set("value", "noFormat", false); + }, + + getLabel: function(value, name){ + // summary: + // Function used to generate the labels of the format dropdown + // will return a formatted, or plain label based on the value + // of the plainText option. + // value: String + // The 'insert value' associated with a name + // name: String + // The text name of the value + if(this.plainText || value == "noFormat"){ + return name; + }else{ + return "<" + value + ">" + name + "</" + value + ">"; + } + }, + + _execCommand: function(editor, command, choice){ + // summary: + // Over-ride for default exec-command label. + // Allows us to treat 'none' as special. + if(choice === "noFormat"){ + var start; + var end; + var sel = rangeapi.getSelection(editor.window); + if(sel && sel.rangeCount > 0){ + var range = sel.getRangeAt(0); + var node, tag; + if(range){ + start = range.startContainer; + end = range.endContainer; + + // find containing nodes of start/end. + while(start && start !== editor.editNode && + start !== editor.document.body && + start.nodeType !== 1){ + start = start.parentNode; + } + + while(end && end !== editor.editNode && + end !== editor.document.body && + end.nodeType !== 1){ + end = end.parentNode; + } + + var processChildren = lang.hitch(this, function(node, ary){ + if(node.childNodes && node.childNodes.length){ + var i; + for(i = 0; i < node.childNodes.length; i++){ + var c = node.childNodes[i]; + if(c.nodeType == 1){ + if(win.withGlobal(editor.window, "inSelection", selectionapi, [c])){ + var tag = c.tagName? c.tagName.toLowerCase(): ""; + if(array.indexOf(this.values, tag) !== -1){ + ary.push(c); + } + processChildren(c, ary); + } + } + } + } + }); + + var unformatNodes = lang.hitch(this, function(nodes){ + // summary: + // Internal function to clear format nodes. + // nodes: + // The array of nodes to strip formatting from. + if(nodes && nodes.length){ + editor.beginEditing(); + while(nodes.length){ + this._removeFormat(editor, nodes.pop()); + } + editor.endEditing(); + } + }); + + var clearNodes = []; + if(start == end){ + //Contained within the same block, may be collapsed, but who cares, see if we + // have a block element to remove. + var block; + node = start; + while(node && node !== editor.editNode && node !== editor.document.body){ + if(node.nodeType == 1){ + tag = node.tagName? node.tagName.toLowerCase(): ""; + if(array.indexOf(this.values, tag) !== -1){ + block = node; + break; + } + } + node = node.parentNode; + } + + //Also look for all child nodes in the selection that may need to be + //cleared of formatting + processChildren(start, clearNodes); + if(block){ clearNodes = [block].concat(clearNodes); } + unformatNodes(clearNodes); + }else{ + // Probably a multi select, so we have to process it. Whee. + node = start; + while(win.withGlobal(editor.window, "inSelection", selectionapi, [node])){ + if(node.nodeType == 1){ + tag = node.tagName? node.tagName.toLowerCase(): ""; + if(array.indexOf(this.values, tag) !== -1){ + clearNodes.push(node); + } + processChildren(node,clearNodes); + } + node = node.nextSibling; + } + unformatNodes(clearNodes); + } + editor.onDisplayChanged(); + } + } + }else{ + editor.execCommand(command, choice); + } + }, + + _removeFormat: function(editor, node){ + // summary: + // function to remove the block format node. + // node: + // The block format node to remove (and leave the contents behind) + if(editor.customUndo){ + // So of course IE doesn't work right with paste-overs. + // We have to do this manually, which is okay since IE already uses + // customUndo and we turned it on for WebKit. WebKit pasted funny, + // so couldn't use the execCommand approach + while(node.firstChild){ + domConstruct.place(node.firstChild, node, "before"); + } + node.parentNode.removeChild(node); + }else{ + // Everyone else works fine this way, a paste-over and is native + // undo friendly. + win.withGlobal(editor.window, + "selectElementChildren", selectionapi, [node]); + var html = win.withGlobal(editor.window, + "getSelectedHtml", selectionapi, [null]); + win.withGlobal(editor.window, + "selectElement", selectionapi, [node]); + editor.execCommand("inserthtml", html||""); + } + } +}); + +// TODO: for 2.0, split into FontChoice plugin into three separate classes, +// one for each command (and change registry below) +var FontChoice = declare("dijit._editor.plugins.FontChoice", _Plugin,{ + // summary: + // This plugin provides three drop downs for setting style in the editor + // (font, font size, and format block), as controlled by command. + // + // description: + // The commands provided by this plugin are: + // + // * fontName + // | Provides a drop down to select from a list of font names + // * fontSize + // | Provides a drop down to select from a list of font sizes + // * formatBlock + // | Provides a drop down to select from a list of block styles + // | + // + // which can easily be added to an editor by including one or more of the above commands + // in the `plugins` attribute as follows: + // + // | plugins="['fontName','fontSize',...]" + // + // It is possible to override the default dropdown list by providing an Array for the `custom` property when + // instantiating this plugin, e.g. + // + // | plugins="[{name:'dijit._editor.plugins.FontChoice', command:'fontName', custom:['Verdana','Myriad','Garamond']},...]" + // + // Alternatively, for `fontName` only, `generic:true` may be specified to provide a dropdown with + // [CSS generic font families](http://www.w3.org/TR/REC-CSS2/fonts.html#generic-font-families) + // + // Note that the editor is often unable to properly handle font styling information defined outside + // the context of the current editor instance, such as pre-populated HTML. + + // useDefaultCommand: [protected] Boolean + // Override _Plugin.useDefaultCommand... + // processing is handled by this plugin, not by dijit.Editor. + useDefaultCommand: false, + + _initButton: function(){ + // summary: + // Overrides _Plugin._initButton(), to initialize the FilteringSelect+label in toolbar, + // rather than a simple button. + // tags: + // protected + + // Create the widget to go into the toolbar (the so-called "button") + var clazz = { + fontName: _FontNameDropDown, + fontSize: _FontSizeDropDown, + formatBlock: _FormatBlockDropDown + }[this.command], + params = this.params; + + // For back-compat reasons support setting custom values via "custom" parameter + // rather than "values" parameter + if(this.params.custom){ + params.values = this.params.custom; + } + + var editor = this.editor; + this.button = new clazz(lang.delegate({dir: editor.dir, lang: editor.lang}, params)); + + // Reflect changes to the drop down in the editor + this.connect(this.button.select, "onChange", function(choice){ + // User invoked change, since all internal updates set priorityChange to false and will + // not trigger an onChange event. + this.editor.focus(); + + if(this.command == "fontName" && choice.indexOf(" ") != -1){ choice = "'" + choice + "'"; } + + // Invoke, the editor already normalizes commands called through its + // execCommand. + if(this.button._execCommand){ + this.button._execCommand(this.editor, this.command, choice); + }else{ + this.editor.execCommand(this.command, choice); + } + }); + }, + + updateState: function(){ + // summary: + // Overrides _Plugin.updateState(). This controls updating the menu + // options to the right values on state changes in the document (that trigger a + // test of the actions.) + // It set value of drop down in toolbar to reflect font/font size/format block + // of text at current caret position. + // tags: + // protected + var _e = this.editor; + var _c = this.command; + if(!_e || !_e.isLoaded || !_c.length){ return; } + + if(this.button){ + var disabled = this.get("disabled"); + this.button.set("disabled", disabled); + if(disabled){ return; } + var value; + try{ + value = _e.queryCommandValue(_c) || ""; + }catch(e){ + //Firefox may throw error above if the editor is just loaded, ignore it + value = ""; + } + + // strip off single quotes, if any + var quoted = lang.isString(value) && value.match(/'([^']*)'/); + if(quoted){ value = quoted[1]; } + + if(_c === "formatBlock"){ + if(!value || value == "p"){ + // Some browsers (WebKit) doesn't actually get the tag info right. + // and IE returns paragraph when in a DIV!, so incorrect a lot, + // so we have double-check it. + value = null; + var elem; + // Try to find the current element where the caret is. + var sel = rangeapi.getSelection(this.editor.window); + if(sel && sel.rangeCount > 0){ + var range = sel.getRangeAt(0); + if(range){ + elem = range.endContainer; + } + } + + // Okay, now see if we can find one of the formatting types we're in. + while(elem && elem !== _e.editNode && elem !== _e.document){ + var tg = elem.tagName?elem.tagName.toLowerCase():""; + if(tg && array.indexOf(this.button.values, tg) > -1){ + value = tg; + break; + } + elem = elem.parentNode; + } + if(!value){ + // Still no value, so lets select 'none'. + value = "noFormat"; + } + }else{ + // Check that the block format is one allowed, if not, + // null it so that it gets set to empty. + if(array.indexOf(this.button.values, value) < 0){ + value = "noFormat"; + } + } + } + if(value !== this.button.get("value")){ + // Set the value, but denote it is not a priority change, so no + // onchange fires. + this.button.set('value', value, false); + } + } + } +}); + +// Register these plugins +array.forEach(["fontName", "fontSize", "formatBlock"], function(name){ + _Plugin.registry[name] = function(args){ + return new FontChoice({ + command: name, + plainText: args.plainText + }); + }; +}); + +}); + +}, +'dijit/form/nls/validate':function(){ +define({ root: +//begin v1.x content +({ + invalidMessage: "The value entered is not valid.", + missingMessage: "This value is required.", + rangeMessage: "This value is out of range." +}) +//end v1.x content +, +"zh": true, +"zh-tw": true, +"tr": true, +"th": true, +"sv": true, +"sl": true, +"sk": true, +"ru": true, +"ro": true, +"pt": true, +"pt-pt": true, +"pl": true, +"nl": true, +"nb": true, +"ko": true, +"kk": true, +"ja": true, +"it": true, +"hu": true, +"hr": true, +"he": true, +"fr": true, +"fi": true, +"es": true, +"el": true, +"de": true, +"da": true, +"cs": true, +"ca": true, +"az": true, +"ar": true +}); + +}, +'url:dijit/templates/CheckedMenuItem.html':"<tr class=\"dijitReset dijitMenuItem\" data-dojo-attach-point=\"focusNode\" role=\"menuitemcheckbox\" 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=\"dijitMenuItemIcon dijitCheckedMenuItemIcon\" data-dojo-attach-point=\"iconNode\"/>\n\t\t<span class=\"dijitCheckedMenuItemIconChar\">✓</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" data-dojo-attach-point=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" data-dojo-attach-point=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\"> </td>\n</tr>\n", +'dijit/form/TextBox':function(){ +define([ + "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){ + +/*===== + var _FormValueWidget = dijit.form._FormValueWidget; + var _TextBoxMixin = dijit.form._TextBoxMixin; +=====*/ + + // module: + // dijit/form/TextBox + // summary: + // A base class for textbox form inputs + + var TextBox = declare(/*====="dijit.form.TextBox", =====*/ [_FormValueWidget, _TextBoxMixin], { + // summary: + // A base class for textbox form inputs + + templateString: template, + _singleNodeTemplate: '<input class="dijit dijitReset dijitLeft dijitInputField" data-dojo-attach-point="textbox,focusNode" autocomplete="off" type="${type}" ${!nameAttrSetting} />', + + _buttonInputDisabled: has("ie") ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events + + baseClass: "dijitTextBox", + + 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); + }, + + _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); + } + }, + + _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(); + }, + + _updatePlaceHolder: function(){ + if(this._phspan){ + this._phspan.style.display=(this.placeHolder&&!this.focused&&!this.textbox.value)?"":"none"; + } + }, + + _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){ + this.inherited(arguments); + this._updatePlaceHolder(); + }, + + 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'); + }, + + 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); + }, + + _onBlur: function(e){ + if(this.disabled){ return; } + this.inherited(arguments); + this._updatePlaceHolder(); + }, + + _onFocus: function(/*String*/ by){ + if(this.disabled || this.readOnly){ return; } + this.inherited(arguments); + this._updatePlaceHolder(); + } + }); + + 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(); + } + } + }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"; + } + lang.setObject("dijit.form.TextBox", TextBox); // don't do direct assignment, it confuses API doc parser + + return TextBox; +}); + +}, +'dojo/currency':function(){ +define(["./_base/kernel", "./_base/lang", "./_base/array", "./number", "./i18n", "./i18n!./cldr/nls/currency", "./cldr/monetary"], function(dojo, lang, darray, dnumber, i18n, nlsCurrency, cldrMonetary) { + // module: + // dojo/currency + // summary: + // TODOC + +lang.getObject("currency", true, dojo); + +/*===== +dojo.currency = { + // summary: localized formatting and parsing routines for currencies + // + // description: extends dojo.number to provide culturally-appropriate formatting of values + // in various world currencies, including use of a currency symbol. The currencies are specified + // by a three-letter international symbol in all uppercase, and support for the currencies is + // provided by the data in `dojo.cldr`. The scripts generating dojo.cldr specify which + // currency support is included. A fixed number of decimal places is determined based + // on the currency type and is not determined by the 'pattern' argument. The fractional + // portion is optional, by default, and variable length decimals are not supported. +} +=====*/ + +dojo.currency._mixInDefaults = function(options){ + options = options || {}; + options.type = "currency"; + + // Get locale-dependent currency data, like the symbol + var bundle = i18n.getLocalization("dojo.cldr", "currency", options.locale) || {}; + + // Mixin locale-independent currency data, like # of places + var iso = options.currency; + var data = cldrMonetary.getData(iso); + + darray.forEach(["displayName","symbol","group","decimal"], function(prop){ + data[prop] = bundle[iso+"_"+prop]; + }); + + data.fractional = [true, false]; + + // Mixin with provided options + return lang.mixin(data, options); +}; + +/*===== +dojo.declare("dojo.currency.__FormatOptions", [dojo.number.__FormatOptions], { + // type: String? + // Should not be set. Value is assumed to be "currency". + // symbol: String? + // localized currency symbol. The default will be looked up in table of supported currencies in `dojo.cldr` + // A [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code will be used if not found. + // currency: String? + // an [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code, a three letter sequence like "USD". + // For use with dojo.currency only. + // places: Number? + // number of decimal places to show. Default is defined based on which currency is used. + type: "", + symbol: "", + currency: "", + places: "" +}); +=====*/ + +dojo.currency.format = function(/*Number*/value, /*dojo.currency.__FormatOptions?*/options){ +// summary: +// Format a Number as a currency, using locale-specific settings +// +// description: +// Create a string from a Number using a known, localized pattern. +// [Formatting patterns](http://www.unicode.org/reports/tr35/#Number_Elements) +// appropriate to the locale are chosen from the [CLDR](http://unicode.org/cldr) +// as well as the appropriate symbols and delimiters and number of decimal places. +// +// value: +// the number to be formatted. + + return dnumber.format(value, dojo.currency._mixInDefaults(options)); +}; + +dojo.currency.regexp = function(/*dojo.number.__RegexpOptions?*/options){ +// +// summary: +// Builds the regular needed to parse a currency value +// +// description: +// Returns regular expression with positive and negative match, group and decimal separators +// Note: the options.places default, the number of decimal places to accept, is defined by the currency type. + return dnumber.regexp(dojo.currency._mixInDefaults(options)); // String +}; + +/*===== +dojo.declare("dojo.currency.__ParseOptions", [dojo.number.__ParseOptions], { + // type: String? + // Should not be set. Value is assumed to be currency. + // currency: String? + // an [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code, a three letter sequence like "USD". + // For use with dojo.currency only. + // symbol: String? + // localized currency symbol. The default will be looked up in table of supported currencies in `dojo.cldr` + // A [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code will be used if not found. + // places: Number? + // fixed number of decimal places to accept. The default is determined based on which currency is used. + // fractional: Boolean?|Array? + // Whether to include the fractional portion, where the number of decimal places are implied by the currency + // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional. + // By default for currencies, it the fractional portion is optional. + type: "", + currency: "", + symbol: "", + places: "", + fractional: "" +}); +=====*/ + +dojo.currency.parse = function(/*String*/expression, /*dojo.currency.__ParseOptions?*/options){ + // + // summary: + // Convert a properly formatted currency string to a primitive Number, + // using locale-specific settings. + // + // description: + // Create a Number from a string using a known, localized pattern. + // [Formatting patterns](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) + // are chosen appropriate to the locale, as well as the appropriate symbols and delimiters + // and number of decimal places. + // + // expression: A string representation of a currency value + + return dnumber.parse(expression, dojo.currency._mixInDefaults(options)); +}; + +return dojo.currency; +}); + +}, +'dijit/DialogUnderlay':function(){ +define([ + "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){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; +=====*/ + + // module: + // dijit/DialogUnderlay + // summary: + // The component that blocks the screen behind a `dijit.Dialog` + + 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. + + // 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>", + + // Parameters on creation or updatable later + + // dialogId: String + // Id of the dialog.... DialogUnderlay's id is based on this id + dialogId: "", + + // class: String + // This class name is used on the DialogUnderlay node, in addition to dijitDialogUnderlay + "class": "", + + _setDialogIdAttr: function(id){ + domAttr.set(this.node, "id", id + "_underlay"); + this._set("dialogId", id); + }, + + _setClassAttr: function(clazz){ + this.node.className = "dijitDialogUnderlay " + clazz; + this._set("class", clazz); + }, + + 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"; + } + }); +}); + +}, +'url: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\" data-dojo-attach-point=\"buttonNode\" data-dojo-attach-event=\"ondijitclick:_onClick,onkeypress:_onButtonKeyPress\"\n\t\t><div id=\"${id}_button\" class=\"dijitReset dijitButtonContents\"\n\t\t\tdata-dojo-attach-point=\"titleNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><div class=\"dijitReset dijitInline dijitIcon\" data-dojo-attach-point=\"iconNode\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitInline dijitButtonText\" id=\"${id}_label\" data-dojo-attach-point=\"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\tdata-dojo-attach-point=\"_popupStateNode,focusNode,_buttonNode\"\n\t\t\tdata-dojo-attach-event=\"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}\" data-dojo-attach-point=\"valueNode\"\n\t\t/></td></tr></tbody\n></table>\n", +'dijit/layout/ScrollingTabController':function(){ +define([ + "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){ + +/*===== +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. + + +var ScrollingTabController = declare("dijit.layout.ScrollingTabController", [TabController, _WidgetsInTemplateMixin], { + // 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 + + baseClass: "dijitTabController dijitScrollingTabController", + + 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") + } + + domClass.add(this.tablistWrapper, this.tabStripClass); + }, + + 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; + }, + + 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"); + }, + + 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; + } + + this.inherited(arguments); + }, + + _initButtons: function(){ + // summary: + // Creates the buttons used to scroll to view tabs that + // may not be visible if the TabContainer is too narrow. + + // 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; + } + }, + + _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 || domStyle.get(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. + + // Save the dimensions to be used when a child is renamed. + this._dim = dim; + + // 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); + + // 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()); + } + + // 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" + 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; + }, + + _convertToScrollLeft: function(val){ + // 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() || 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(); + + if(sl > node.offsetLeft || + sl + domStyle.get(this.scrollNode, "width") < + node.offsetLeft + domStyle.get(node, "width")){ + this.createSmoothScroll().play(); + } + } + } + + this.inherited(arguments); + }, + + _getScrollBounds: function(){ + // summary: + // 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(); + + 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 + }; + } + }, + + _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(); + + // 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); + + // 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: + // 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(); + } + + if(this._anim && this._anim.status() == "playing"){ + this._anim.stop(); + } + + 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: + // 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; + }, + + 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)); + }, + + doSlide: function(/*Number*/ direction, /*DomNode*/ node){ + // 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 && 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/_editor/html':function(){ +define([ + "dojo/_base/lang", // lang.isString + "dojo/_base/sniff", // has("ie") + ".." // for exporting symbols to dijit._editor (remove for 2.0) +], function(lang, has, dijit){ + +// module: +// dijit/_editor/html +// summary: +// Utility functions used by editor + +lang.getObject("_editor", true, dijit); + +dijit._editor.escapeXml=function(/*String*/str, /*Boolean?*/noSingleQuotes){ + // summary: + // Adds escape sequences for special characters in XML: &<>"' + // Optionally skips escapes for single quotes + str = str.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """); + if(!noSingleQuotes){ + str = str.replace(/'/gm, "'"); + } + return str; // string +}; + +dijit._editor.getNodeHtml=function(/* DomNode */node){ + var output; + switch(node.nodeType){ + case 1: //element node + var lName = node.nodeName.toLowerCase(); + if(!lName || lName.charAt(0) == "/"){ + // IE does some strange things with malformed HTML input, like + // treating a close tag </span> without an open tag <span>, as + // a new tag with tagName of /span. Corrupts output HTML, remove + // them. Other browsers don't prefix tags that way, so will + // never show up. + return ""; + } + output = '<' + lName; + + //store the list of attributes and sort it to have the + //attributes appear in the dictionary order + var attrarray = []; + var attr; + if(has("ie") && node.outerHTML){ + var s = node.outerHTML; + s = s.substr(0, s.indexOf('>')) + .replace(/(['"])[^"']*\1/g, ''); //to make the following regexp safe + var reg = /(\b\w+)\s?=/g; + var m, key; + while((m = reg.exec(s))){ + key = m[1]; + if(key.substr(0,3) != '_dj'){ + if(key == 'src' || key == 'href'){ + if(node.getAttribute('_djrealurl')){ + attrarray.push([key,node.getAttribute('_djrealurl')]); + continue; + } + } + var val, match; + switch(key){ + case 'style': + val = node.style.cssText.toLowerCase(); + break; + case 'class': + val = node.className; + break; + case 'width': + if(lName === "img"){ + // This somehow gets lost on IE for IMG tags and the like + // and we have to find it in outerHTML, known IE oddity. + match=/width=(\S+)/i.exec(s); + if(match){ + val = match[1]; + } + break; + } + case 'height': + if(lName === "img"){ + // This somehow gets lost on IE for IMG tags and the like + // and we have to find it in outerHTML, known IE oddity. + match=/height=(\S+)/i.exec(s); + if(match){ + val = match[1]; + } + break; + } + default: + val = node.getAttribute(key); + } + if(val != null){ + attrarray.push([key, val.toString()]); + } + } + } + }else{ + var i = 0; + while((attr = node.attributes[i++])){ + //ignore all attributes starting with _dj which are + //internal temporary attributes used by the editor + var n = attr.name; + if(n.substr(0,3) != '_dj' /*&& + (attr.specified == undefined || attr.specified)*/){ + var v = attr.value; + if(n == 'src' || n == 'href'){ + if(node.getAttribute('_djrealurl')){ + v = node.getAttribute('_djrealurl'); + } + } + attrarray.push([n,v]); + } + } + } + attrarray.sort(function(a,b){ + return a[0] < b[0] ? -1 : (a[0] == b[0] ? 0 : 1); + }); + var j = 0; + while((attr = attrarray[j++])){ + output += ' ' + attr[0] + '="' + + (lang.isString(attr[1]) ? dijit._editor.escapeXml(attr[1], true) : attr[1]) + '"'; + } + if(lName === "script"){ + // Browsers handle script tags differently in how you get content, + // but innerHTML always seems to work, so insert its content that way + // Yes, it's bad to allow script tags in the editor code, but some people + // seem to want to do it, so we need to at least return them right. + // other plugins/filters can strip them. + output += '>' + node.innerHTML +'</' + lName + '>'; + }else{ + if(node.childNodes.length){ + output += '>' + dijit._editor.getChildrenHtml(node)+'</' + lName +'>'; + }else{ + switch(lName){ + case 'br': + case 'hr': + case 'img': + case 'input': + case 'base': + case 'meta': + case 'area': + case 'basefont': + // These should all be singly closed + output += ' />'; + break; + default: + // Assume XML style separate closure for everything else. + output += '></' + lName + '>'; + } + } + } + break; + case 4: // cdata + case 3: // text + // FIXME: + output = dijit._editor.escapeXml(node.nodeValue, true); + break; + case 8: //comment + // FIXME: + output = '<!--' + dijit._editor.escapeXml(node.nodeValue, true) + '-->'; + break; + default: + output = "<!-- Element not recognized - Type: " + node.nodeType + " Name: " + node.nodeName + "-->"; + } + return output; +}; + +dijit._editor.getChildrenHtml = function(/* DomNode */dom){ + // summary: + // Returns the html content of a DomNode and children + var out = ""; + if(!dom){ return out; } + var nodes = dom["childNodes"] || dom; + + //IE issue. + //If we have an actual node we can check parent relationships on for IE, + //We should check, as IE sometimes builds invalid DOMS. If no parent, we can't check + //And should just process it and hope for the best. + var checkParent = !has("ie") || nodes !== dom; + + var node, i = 0; + while((node = nodes[i++])){ + //IE is broken. DOMs are supposed to be a tree. But in the case of malformed HTML, IE generates a graph + //meaning one node ends up with multiple references (multiple parents). This is totally wrong and invalid, but + //such is what it is. We have to keep track and check for this because otherise the source output HTML will have dups. + //No other browser generates a graph. Leave it to IE to break a fundamental DOM rule. So, we check the parent if we can + //If we can't, nothing more we can do other than walk it. + if(!checkParent || node.parentNode == dom){ + out += dijit._editor.getNodeHtml(node); + } + } + return out; // String +}; + +return dijit._editor; +}); + +}, +'dijit/_editor/nls/commands':function(){ +define({ root: +//begin v1.x content +({ + 'bold': 'Bold', + 'copy': 'Copy', + 'cut': 'Cut', + 'delete': 'Delete', + 'indent': 'Indent', + 'insertHorizontalRule': 'Horizontal Rule', + 'insertOrderedList': 'Numbered List', + 'insertUnorderedList': 'Bullet List', + 'italic': 'Italic', + 'justifyCenter': 'Align Center', + 'justifyFull': 'Justify', + 'justifyLeft': 'Align Left', + 'justifyRight': 'Align Right', + 'outdent': 'Outdent', + 'paste': 'Paste', + 'redo': 'Redo', + 'removeFormat': 'Remove Format', + 'selectAll': 'Select All', + 'strikethrough': 'Strikethrough', + 'subscript': 'Subscript', + 'superscript': 'Superscript', + 'underline': 'Underline', + 'undo': 'Undo', + 'unlink': 'Remove Link', + 'createLink': 'Create Link', + 'toggleDir': 'Toggle Direction', + 'insertImage': 'Insert Image', + 'insertTable': 'Insert/Edit Table', + 'toggleTableBorder': 'Toggle Table Border', + 'deleteTable': 'Delete Table', + 'tableProp': 'Table Property', + 'htmlToggle': 'HTML Source', + 'foreColor': 'Foreground Color', + 'hiliteColor': 'Background Color', + 'plainFormatBlock': 'Paragraph Style', + 'formatBlock': 'Paragraph Style', + 'fontSize': 'Font Size', + 'fontName': 'Font Name', + 'tabIndent': 'Tab Indent', + "fullScreen": "Toggle Full Screen", + "viewSource": "View HTML Source", + "print": "Print", + "newPage": "New Page", + /* Error messages */ + 'systemShortcut': 'The "${0}" action is only available in your browser using a keyboard shortcut. Use ${1}.', + 'ctrlKey':'ctrl+${0}', + 'appleKey':'\u2318${0}' // "command" or open-apple key on Macintosh +}) +//end v1.x content +, +"zh": true, +"zh-tw": true, +"tr": true, +"th": true, +"sv": true, +"sl": true, +"sk": true, +"ru": true, +"ro": true, +"pt": true, +"pt-pt": true, +"pl": true, +"nl": true, +"nb": true, +"ko": true, +"kk": true, +"ja": true, +"it": true, +"hu": true, +"hr": true, +"he": true, +"fr": true, +"fi": true, +"es": true, +"el": true, +"de": true, +"da": true, +"cs": true, +"ca": true, +"az": true, +"ar": true +}); + +}, +'dijit/_HasDropDown':function(){ +define([ + "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: + // 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: + // + // * 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"], + + // _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; + } + } + 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(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); + } + }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); + } + + // 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"); + }, + + 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; + } + this.inherited(arguments); + }, + + _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{ + afterLoad(); + } + return d; + }, + + 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 + + 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{ + 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{ + delete mb.w; + } + + // 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; + } + } + + }); +}); + +}, +'dijit/tree/TreeStoreModel':function(){ +define([ + "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: + // 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. + // + // 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 + + 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"); + } + + // 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) + ]); + } + }, + + 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 + }); + } + }, + + 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); + }, + + 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 = 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); + } + + // count how many items need to be loaded + var _waitCount = 0; + if(!this.deferItemLoadingUntilExpand){ + array.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 + 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 + }); + } + }); + } + }, + + // ======================================================================= + // 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){ + 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{ + // item's label/icon/etc. changed. + this.onChange(item); + } + } + }); +}); + +}, +'dijit/_editor/plugins/EnterKeyHandling':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/dom-construct", // domConstruct.destroy domConstruct.place + "dojo/_base/event", // event.stop + "dojo/keys", // keys.ENTER + "dojo/_base/lang", + "dojo/_base/sniff", // has("ie") has("mozilla") has("webkit") + "dojo/_base/window", // win.global win.withGlobal + "dojo/window", // winUtils.scrollIntoView + "../_Plugin", + "../RichText", + "../range", + "../selection" +], function(declare, domConstruct, event, keys, lang, has, win, winUtils, _Plugin, RichText, rangeapi, selectionapi){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + +// module: +// dijit/_editor/plugins/EnterKeyHandling +// summary: +// This plugin tries to make all browsers behave consistently with regard to +// how ENTER behaves in the editor window. It traps the ENTER key and alters +// the way DOM is constructed in certain cases to try to commonize the generated +// DOM and behaviors across browsers. + + +return declare("dijit._editor.plugins.EnterKeyHandling", _Plugin, { + // summary: + // This plugin tries to make all browsers behave consistently with regard to + // how ENTER behaves in the editor window. It traps the ENTER key and alters + // the way DOM is constructed in certain cases to try to commonize the generated + // DOM and behaviors across browsers. + // + // description: + // This plugin has three modes: + // + // * blockNodeForEnter=BR + // * blockNodeForEnter=DIV + // * blockNodeForEnter=P + // + // In blockNodeForEnter=P, the ENTER key starts a new + // paragraph, and shift-ENTER starts a new line in the current paragraph. + // For example, the input: + // + // | first paragraph <shift-ENTER> + // | second line of first paragraph <ENTER> + // | second paragraph + // + // will generate: + // + // | <p> + // | first paragraph + // | <br/> + // | second line of first paragraph + // | </p> + // | <p> + // | second paragraph + // | </p> + // + // In BR and DIV mode, the ENTER key conceptually goes to a new line in the + // current paragraph, and users conceptually create a new paragraph by pressing ENTER twice. + // For example, if the user enters text into an editor like this: + // + // | one <ENTER> + // | two <ENTER> + // | three <ENTER> + // | <ENTER> + // | four <ENTER> + // | five <ENTER> + // | six <ENTER> + // + // It will appear on the screen as two 'paragraphs' of three lines each. Markupwise, this generates: + // + // BR: + // | one<br/> + // | two<br/> + // | three<br/> + // | <br/> + // | four<br/> + // | five<br/> + // | six<br/> + // + // DIV: + // | <div>one</div> + // | <div>two</div> + // | <div>three</div> + // | <div> </div> + // | <div>four</div> + // | <div>five</div> + // | <div>six</div> + + // blockNodeForEnter: String + // This property decides the behavior of Enter key. It can be either P, + // DIV, BR, or empty (which means disable this feature). Anything else + // will trigger errors. The default is 'BR' + // + // See class description for more details. + blockNodeForEnter: 'BR', + + constructor: function(args){ + if(args){ + if("blockNodeForEnter" in args){ + args.blockNodeForEnter = args.blockNodeForEnter.toUpperCase(); + } + lang.mixin(this,args); + } + }, + + setEditor: function(editor){ + // Overrides _Plugin.setEditor(). + if(this.editor === editor){ return; } + this.editor = editor; + if(this.blockNodeForEnter == 'BR'){ + // While Moz has a mode tht mostly works, it's still a little different, + // So, try to just have a common mode and be consistent. Which means + // we need to enable customUndo, if not already enabled. + this.editor.customUndo = true; + editor.onLoadDeferred.addCallback(lang.hitch(this,function(d){ + this.connect(editor.document, "onkeypress", function(e){ + if(e.charOrCode == keys.ENTER){ + // Just do it manually. The handleEnterKey has a shift mode that + // Always acts like <br>, so just use it. + var ne = lang.mixin({},e); + ne.shiftKey = true; + if(!this.handleEnterKey(ne)){ + event.stop(e); + } + } + }); + return d; + })); + }else if(this.blockNodeForEnter){ + // add enter key handler + // FIXME: need to port to the new event code!! + var h = lang.hitch(this,this.handleEnterKey); + editor.addKeyHandler(13, 0, 0, h); //enter + editor.addKeyHandler(13, 0, 1, h); //shift+enter + this.connect(this.editor,'onKeyPressed','onKeyPressed'); + } + }, + onKeyPressed: function(){ + // summary: + // Handler for keypress events. + // tags: + // private + if(this._checkListLater){ + if(win.withGlobal(this.editor.window, 'isCollapsed', dijit)){ + var liparent=win.withGlobal(this.editor.window, 'getAncestorElement', selection, ['LI']); + if(!liparent){ + // circulate the undo detection code by calling RichText::execCommand directly + RichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter); + // set the innerHTML of the new block node + var block = win.withGlobal(this.editor.window, 'getAncestorElement', selection, [this.blockNodeForEnter]); + if(block){ + block.innerHTML=this.bogusHtmlContent; + if(has("ie")){ + // move to the start by moving backwards one char + var r = this.editor.document.selection.createRange(); + r.move('character',-1); + r.select(); + } + }else{ + console.error('onKeyPressed: Cannot find the new block node'); // FIXME + } + }else{ + if(has("mozilla")){ + if(liparent.parentNode.parentNode.nodeName == 'LI'){ + liparent=liparent.parentNode.parentNode; + } + } + var fc=liparent.firstChild; + if(fc && fc.nodeType == 1 && (fc.nodeName == 'UL' || fc.nodeName == 'OL')){ + liparent.insertBefore(fc.ownerDocument.createTextNode('\xA0'),fc); + var newrange = rangeapi.create(this.editor.window); + newrange.setStart(liparent.firstChild,0); + var selection = rangeapi.getSelection(this.editor.window, true); + selection.removeAllRanges(); + selection.addRange(newrange); + } + } + } + this._checkListLater = false; + } + if(this._pressedEnterInBlock){ + // the new created is the original current P, so we have previousSibling below + if(this._pressedEnterInBlock.previousSibling){ + this.removeTrailingBr(this._pressedEnterInBlock.previousSibling); + } + delete this._pressedEnterInBlock; + } + }, + + // bogusHtmlContent: [private] String + // HTML to stick into a new empty block + bogusHtmlContent: ' ', // + + // blockNodes: [private] Regex + // Regex for testing if a given tag is a block level (display:block) tag + blockNodes: /^(?:P|H1|H2|H3|H4|H5|H6|LI)$/, + + handleEnterKey: function(e){ + // summary: + // Handler for enter key events when blockNodeForEnter is DIV or P. + // description: + // Manually handle enter key event to make the behavior consistent across + // all supported browsers. See class description for details. + // tags: + // private + + var selection, range, newrange, startNode, endNode, brNode, doc=this.editor.document,br,rs,txt; + if(e.shiftKey){ // shift+enter always generates <br> + var parent = win.withGlobal(this.editor.window, "getParentElement", selectionapi); + var header = rangeapi.getAncestor(parent,this.blockNodes); + if(header){ + if(header.tagName == 'LI'){ + return true; // let browser handle + } + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + if(!range.collapsed){ + range.deleteContents(); + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + } + if(rangeapi.atBeginningOfContainer(header, range.startContainer, range.startOffset)){ + br=doc.createElement('br'); + newrange = rangeapi.create(this.editor.window); + header.insertBefore(br,header.firstChild); + newrange.setStartAfter(br); + selection.removeAllRanges(); + selection.addRange(newrange); + }else if(rangeapi.atEndOfContainer(header, range.startContainer, range.startOffset)){ + newrange = rangeapi.create(this.editor.window); + br=doc.createElement('br'); + header.appendChild(br); + header.appendChild(doc.createTextNode('\xA0')); + newrange.setStart(header.lastChild,0); + selection.removeAllRanges(); + selection.addRange(newrange); + }else{ + rs = range.startContainer; + if(rs && rs.nodeType == 3){ + // Text node, we have to split it. + txt = rs.nodeValue; + win.withGlobal(this.editor.window, function(){ + startNode = doc.createTextNode(txt.substring(0, range.startOffset)); + endNode = doc.createTextNode(txt.substring(range.startOffset)); + brNode = doc.createElement("br"); + + if(endNode.nodeValue == "" && has("webkit")){ + endNode = doc.createTextNode('\xA0') + } + domConstruct.place(startNode, rs, "after"); + domConstruct.place(brNode, startNode, "after"); + domConstruct.place(endNode, brNode, "after"); + domConstruct.destroy(rs); + newrange = rangeapi.create(); + newrange.setStart(endNode,0); + selection.removeAllRanges(); + selection.addRange(newrange); + }); + return false; + } + return true; // let browser handle + } + }else{ + selection = rangeapi.getSelection(this.editor.window); + if(selection.rangeCount){ + range = selection.getRangeAt(0); + if(range && range.startContainer){ + if(!range.collapsed){ + range.deleteContents(); + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + } + rs = range.startContainer; + if(rs && rs.nodeType == 3){ + // Text node, we have to split it. + win.withGlobal(this.editor.window, lang.hitch(this, function(){ + var endEmpty = false; + + var offset = range.startOffset; + if(rs.length < offset){ + //We are not splitting the right node, try to locate the correct one + ret = this._adjustNodeAndOffset(rs, offset); + rs = ret.node; + offset = ret.offset; + } + txt = rs.nodeValue; + + startNode = doc.createTextNode(txt.substring(0, offset)); + endNode = doc.createTextNode(txt.substring(offset)); + brNode = doc.createElement("br"); + + if(!endNode.length){ + endNode = doc.createTextNode('\xA0'); + endEmpty = true; + } + + if(startNode.length){ + domConstruct.place(startNode, rs, "after"); + }else{ + startNode = rs; + } + domConstruct.place(brNode, startNode, "after"); + domConstruct.place(endNode, brNode, "after"); + domConstruct.destroy(rs); + newrange = rangeapi.create(); + newrange.setStart(endNode,0); + newrange.setEnd(endNode, endNode.length); + selection.removeAllRanges(); + selection.addRange(newrange); + if(endEmpty && !has("webkit")){ + selectionapi.remove(); + }else{ + selectionapi.collapse(true); + } + })); + }else{ + var targetNode; + if(range.startOffset >= 0){ + targetNode = rs.childNodes[range.startOffset]; + } + win.withGlobal(this.editor.window, lang.hitch(this, function(){ + var brNode = doc.createElement("br"); + var endNode = doc.createTextNode('\xA0'); + if(!targetNode){ + rs.appendChild(brNode); + rs.appendChild(endNode); + }else{ + domConstruct.place(brNode, targetNode, "before"); + domConstruct.place(endNode, brNode, "after"); + } + newrange = rangeapi.create(win.global); + newrange.setStart(endNode,0); + newrange.setEnd(endNode, endNode.length); + selection.removeAllRanges(); + selection.addRange(newrange); + selectionapi.collapse(true); + })); + } + } + }else{ + // don't change this: do not call this.execCommand, as that may have other logic in subclass + RichText.prototype.execCommand.call(this.editor, 'inserthtml', '<br>'); + } + } + return false; + } + var _letBrowserHandle = true; + + // first remove selection + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + if(!range.collapsed){ + range.deleteContents(); + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + } + + var block = rangeapi.getBlockAncestor(range.endContainer, null, this.editor.editNode); + var blockNode = block.blockNode; + + // if this is under a LI or the parent of the blockNode is LI, just let browser to handle it + if((this._checkListLater = (blockNode && (blockNode.nodeName == 'LI' || blockNode.parentNode.nodeName == 'LI')))){ + if(has("mozilla")){ + // press enter in middle of P may leave a trailing <br/>, let's remove it later + this._pressedEnterInBlock = blockNode; + } + // if this li only contains spaces, set the content to empty so the browser will outdent this item + if(/^(\s| | |\xA0|<span\b[^>]*\bclass=['"]Apple-style-span['"][^>]*>(\s| | |\xA0)<\/span>)?(<br>)?$/.test(blockNode.innerHTML)){ + // empty LI node + blockNode.innerHTML = ''; + if(has("webkit")){ // WebKit tosses the range when innerHTML is reset + newrange = rangeapi.create(this.editor.window); + newrange.setStart(blockNode, 0); + selection.removeAllRanges(); + selection.addRange(newrange); + } + this._checkListLater = false; // nothing to check since the browser handles outdent + } + return true; + } + + // text node directly under body, let's wrap them in a node + if(!block.blockNode || block.blockNode===this.editor.editNode){ + try{ + RichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter); + }catch(e2){ /*squelch FF3 exception bug when editor content is a single BR*/ } + // get the newly created block node + // FIXME + block = {blockNode:win.withGlobal(this.editor.window, "getAncestorElement", selectionapi, [this.blockNodeForEnter]), + blockContainer: this.editor.editNode}; + if(block.blockNode){ + if(block.blockNode != this.editor.editNode && + (!(block.blockNode.textContent || block.blockNode.innerHTML).replace(/^\s+|\s+$/g, "").length)){ + this.removeTrailingBr(block.blockNode); + return false; + } + }else{ // we shouldn't be here if formatblock worked + block.blockNode = this.editor.editNode; + } + selection = rangeapi.getSelection(this.editor.window); + range = selection.getRangeAt(0); + } + + var newblock = doc.createElement(this.blockNodeForEnter); + newblock.innerHTML=this.bogusHtmlContent; + this.removeTrailingBr(block.blockNode); + var endOffset = range.endOffset; + var node = range.endContainer; + if(node.length < endOffset){ + //We are not checking the right node, try to locate the correct one + var ret = this._adjustNodeAndOffset(node, endOffset); + node = ret.node; + endOffset = ret.offset; + } + if(rangeapi.atEndOfContainer(block.blockNode, node, endOffset)){ + if(block.blockNode === block.blockContainer){ + block.blockNode.appendChild(newblock); + }else{ + domConstruct.place(newblock, block.blockNode, "after"); + } + _letBrowserHandle = false; + // lets move caret to the newly created block + newrange = rangeapi.create(this.editor.window); + newrange.setStart(newblock, 0); + selection.removeAllRanges(); + selection.addRange(newrange); + if(this.editor.height){ + winUtils.scrollIntoView(newblock); + } + }else if(rangeapi.atBeginningOfContainer(block.blockNode, + range.startContainer, range.startOffset)){ + domConstruct.place(newblock, block.blockNode, block.blockNode === block.blockContainer ? "first" : "before"); + if(newblock.nextSibling && this.editor.height){ + // position input caret - mostly WebKit needs this + newrange = rangeapi.create(this.editor.window); + newrange.setStart(newblock.nextSibling, 0); + selection.removeAllRanges(); + selection.addRange(newrange); + // browser does not scroll the caret position into view, do it manually + winUtils.scrollIntoView(newblock.nextSibling); + } + _letBrowserHandle = false; + }else{ //press enter in the middle of P/DIV/Whatever/ + if(block.blockNode === block.blockContainer){ + block.blockNode.appendChild(newblock); + }else{ + domConstruct.place(newblock, block.blockNode, "after"); + } + _letBrowserHandle = false; + + // Clone any block level styles. + if(block.blockNode.style){ + if(newblock.style){ + if(block.blockNode.style.cssText){ + newblock.style.cssText = block.blockNode.style.cssText; + } + } + } + + // Okay, we probably have to split. + rs = range.startContainer; + var firstNodeMoved; + if(rs && rs.nodeType == 3){ + // Text node, we have to split it. + var nodeToMove, tNode; + endOffset = range.endOffset; + if(rs.length < endOffset){ + //We are not splitting the right node, try to locate the correct one + ret = this._adjustNodeAndOffset(rs, endOffset); + rs = ret.node; + endOffset = ret.offset; + } + + txt = rs.nodeValue; + startNode = doc.createTextNode(txt.substring(0, endOffset)); + endNode = doc.createTextNode(txt.substring(endOffset, txt.length)); + + // Place the split, then remove original nodes. + domConstruct.place(startNode, rs, "before"); + domConstruct.place(endNode, rs, "after"); + domConstruct.destroy(rs); + + // Okay, we split the text. Now we need to see if we're + // parented to the block element we're splitting and if + // not, we have to split all the way up. Ugh. + var parentC = startNode.parentNode; + while(parentC !== block.blockNode){ + var tg = parentC.tagName; + var newTg = doc.createElement(tg); + // Clone over any 'style' data. + if(parentC.style){ + if(newTg.style){ + if(parentC.style.cssText){ + newTg.style.cssText = parentC.style.cssText; + } + } + } + // If font also need to clone over any font data. + if(parentC.tagName === "FONT"){ + if(parentC.color){ + newTg.color = parentC.color; + } + if(parentC.face){ + newTg.face = parentC.face; + } + if(parentC.size){ // this check was necessary on IE + newTg.size = parentC.size; + } + } + + nodeToMove = endNode; + while(nodeToMove){ + tNode = nodeToMove.nextSibling; + newTg.appendChild(nodeToMove); + nodeToMove = tNode; + } + domConstruct.place(newTg, parentC, "after"); + startNode = parentC; + endNode = newTg; + parentC = parentC.parentNode; + } + + // Lastly, move the split out tags to the new block. + // as they should now be split properly. + nodeToMove = endNode; + if(nodeToMove.nodeType == 1 || (nodeToMove.nodeType == 3 && nodeToMove.nodeValue)){ + // Non-blank text and non-text nodes need to clear out that blank space + // before moving the contents. + newblock.innerHTML = ""; + } + firstNodeMoved = nodeToMove; + while(nodeToMove){ + tNode = nodeToMove.nextSibling; + newblock.appendChild(nodeToMove); + nodeToMove = tNode; + } + } + + //lets move caret to the newly created block + newrange = rangeapi.create(this.editor.window); + var nodeForCursor; + var innerMostFirstNodeMoved = firstNodeMoved; + if(this.blockNodeForEnter !== 'BR'){ + while(innerMostFirstNodeMoved){ + nodeForCursor = innerMostFirstNodeMoved; + tNode = innerMostFirstNodeMoved.firstChild; + innerMostFirstNodeMoved = tNode; + } + if(nodeForCursor && nodeForCursor.parentNode){ + newblock = nodeForCursor.parentNode; + newrange.setStart(newblock, 0); + selection.removeAllRanges(); + selection.addRange(newrange); + if(this.editor.height){ + winUtils.scrollIntoView(newblock); + } + if(has("mozilla")){ + // press enter in middle of P may leave a trailing <br/>, let's remove it later + this._pressedEnterInBlock = block.blockNode; + } + }else{ + _letBrowserHandle = true; + } + }else{ + newrange.setStart(newblock, 0); + selection.removeAllRanges(); + selection.addRange(newrange); + if(this.editor.height){ + winUtils.scrollIntoView(newblock); + } + if(has("mozilla")){ + // press enter in middle of P may leave a trailing <br/>, let's remove it later + this._pressedEnterInBlock = block.blockNode; + } + } + } + return _letBrowserHandle; + }, + + _adjustNodeAndOffset: function(/*DomNode*/node, /*Int*/offset){ + // summary: + // In the case there are multiple text nodes in a row the offset may not be within the node. If the offset is larger than the node length, it will attempt to find + // the next text sibling until it locates the text node in which the offset refers to + // node: + // The node to check. + // offset: + // The position to find within the text node + // tags: + // private. + while(node.length < offset && node.nextSibling && node.nextSibling.nodeType==3){ + //Adjust the offset and node in the case of multiple text nodes in a row + offset = offset - node.length; + node = node.nextSibling; + } + return {"node": node, "offset": offset}; + }, + + removeTrailingBr: function(container){ + // summary: + // If last child of container is a <br>, then remove it. + // tags: + // private + var para = /P|DIV|LI/i.test(container.tagName) ? + container : selectionapi.getParentOfType(container,['P','DIV','LI']); + + if(!para){ return; } + if(para.lastChild){ + if((para.childNodes.length > 1 && para.lastChild.nodeType == 3 && /^[\s\xAD]*$/.test(para.lastChild.nodeValue)) || + para.lastChild.tagName=='BR'){ + + domConstruct.destroy(para.lastChild); + } + } + if(!para.childNodes.length){ + para.innerHTML=this.bogusHtmlContent; + } + } +}); + +}); + +}, +'dijit/_MenuBase':function(){ +define([ + "./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(); + } + } + }, + + _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(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; + }, + + _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); + } + }, + + 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); + 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); + } + }, + + _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; + domClass.replace(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 + 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; + } + + 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(); + } + } +}); + +}); + +}, +'dijit/PopupMenuBarItem':function(){ +define([ + "dojo/_base/declare", // declare + "./PopupMenuItem", + "./MenuBarItem" +], function(declare, PopupMenuItem, MenuBarItem){ + + // module: + // dijit/PopupMenuBarItem + // summary: + // Item in a MenuBar like "File" or "Edit", that spawns a submenu when pressed (or hovered) + + var _MenuBarItemMixin = MenuBarItem._MenuBarItemMixin; + +/*===== + var PopupMenuItem = dijit.PopupMenuItem; + var _MenuBarItemMixin = dijit._MenuBarItemMixin; +=====*/ + + return declare("dijit.PopupMenuBarItem", [PopupMenuItem, _MenuBarItemMixin], { + // summary: + // Item in a MenuBar like "File" or "Edit", that spawns a submenu when pressed (or hovered) + }); +}); + +}, +'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){ + +/*===== +var TreeStoreModel = dijit.tree.TreeStoreModel; +=====*/ + +// 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: + // 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){ + // 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); + } + }, + + // ======================================================================= + // Inspecting items + + isItem: function(/* anything */ something){ + return (something === this.root) ? true : this.inherited(arguments); + }, + + 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); + } + }, + + getIdentity: function(/* item */ item){ + return (item === this.root) ? this.root.id : this.inherited(arguments); + }, + + getLabel: function(/* item */ item){ + return (item === this.root) ? this.root.label : this.inherited(arguments); + }, + + // ======================================================================= + // 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); + } + }, + + onNewRootItem: function(/* dojo.dnd.Item */ /*===== args =====*/){ + // 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 + }, + + 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); + } + }, + + // ======================================================================= + // Handling for top level children + + onAddToRoot: function(/* item */ item){ + // 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"); + }, + + 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"); + }, + + // ======================================================================= + // 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). + // + // 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); + }, + + 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(); + } + + 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 + + this._requeryTop(); + this.inherited(arguments); + } + +}); + +}); + +}, +'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/TitlePane':function(){ +define([ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dom", // dom.setSelectable + "dojo/dom-attr", // domAttr.set or get domAttr.remove + "dojo/dom-class", // domClass.replace + "dojo/dom-geometry", // domGeometry.setMarginBox domGeometry.getMarginBox + "dojo/_base/event", // event.stop + "dojo/fx", // fxUtils.wipeIn fxUtils.wipeOut + "dojo/_base/kernel", // kernel.deprecated + "dojo/keys", // keys.DOWN_ARROW keys.ENTER + "./_CssStateMixin", + "./_TemplatedMixin", + "./layout/ContentPane", + "dojo/text!./templates/TitlePane.html", + "./_base/manager" // defaultDuration +], function(array, declare, dom, domAttr, domClass, domGeometry, event, fxUtils, kernel, keys, + _CssStateMixin, _TemplatedMixin, ContentPane, template, manager){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _CssStateMixin = dijit._CssStateMixin; + var ContentPane = dijit.layout.ContentPane; +=====*/ + +// module: +// dijit/TitlePane +// summary: +// A pane with a title on top, that can be expanded or collapsed. + + +return declare("dijit.TitlePane", [ContentPane, _TemplatedMixin, _CssStateMixin], { + // summary: + // A pane with a title on top, that can be expanded or collapsed. + // + // description: + // An accessible container with a title Heading, and a content + // section that slides open and closed. TitlePane is an extension to + // `dijit.layout.ContentPane`, providing all the useful content-control aspects from it. + // + // example: + // | // load a TitlePane from remote file: + // | var foo = new dijit.TitlePane({ href: "foobar.html", title:"Title" }); + // | foo.startup(); + // + // example: + // | <!-- markup href example: --> + // | <div data-dojo-type="dijit.TitlePane" data-dojo-props="href: 'foobar.html', title: 'Title'"></div> + // + // example: + // | <!-- markup with inline data --> + // | <div data-dojo-type="dijit.TitlePane" title="Title"> + // | <p>I am content</p> + // | </div> + + // title: String + // Title of the pane + title: "", + _setTitleAttr: { node: "titleNode", type: "innerHTML" }, // override default where title becomes a hover tooltip + + // open: Boolean + // Whether pane is opened or closed. + open: true, + + // toggleable: Boolean + // Whether pane can be opened or closed by clicking the title bar. + toggleable: true, + + // tabIndex: String + // Tabindex setting for the title (so users can tab to the title then + // use space/enter to open/close the title pane) + tabIndex: "0", + + // duration: Integer + // Time in milliseconds to fade in/fade out + duration: manager.defaultDuration, + + // baseClass: [protected] String + // The root className to be placed on this widget's domNode. + baseClass: "dijitTitlePane", + + templateString: template, + + // doLayout: [protected] Boolean + // Don't change this parameter from the default value. + // This ContentPane parameter doesn't make sense for TitlePane, since TitlePane + // is never a child of a layout container, nor should TitlePane try to control + // the size of an inner widget. + doLayout: false, + + // Tooltip is defined in _WidgetBase but we need to handle the mapping to DOM here + _setTooltipAttr: {node: "focusNode", type: "attribute", attribute: "title"}, // focusNode spans the entire width, titleNode doesn't + + buildRendering: function(){ + this.inherited(arguments); + dom.setSelectable(this.titleNode, false); + }, + + postCreate: function(){ + this.inherited(arguments); + + // Hover and focus effect on title bar, except for non-toggleable TitlePanes + // This should really be controlled from _setToggleableAttr() but _CssStateMixin + // doesn't provide a way to disconnect a previous _trackMouseState() call + if(this.toggleable){ + this._trackMouseState(this.titleBarNode, "dijitTitlePaneTitle"); + } + + // setup open/close animations + var hideNode = this.hideNode, wipeNode = this.wipeNode; + this._wipeIn = fxUtils.wipeIn({ + node: wipeNode, + duration: this.duration, + beforeBegin: function(){ + hideNode.style.display=""; + } + }); + this._wipeOut = fxUtils.wipeOut({ + node: wipeNode, + duration: this.duration, + onEnd: function(){ + hideNode.style.display="none"; + } + }); + }, + + _setOpenAttr: function(/*Boolean*/ open, /*Boolean*/ animate){ + // summary: + // Hook to make set("open", boolean) control the open/closed state of the pane. + // open: Boolean + // True if you want to open the pane, false if you want to close it. + + array.forEach([this._wipeIn, this._wipeOut], function(animation){ + if(animation && animation.status() == "playing"){ + animation.stop(); + } + }); + + if(animate){ + var anim = this[open ? "_wipeIn" : "_wipeOut"]; + anim.play(); + }else{ + this.hideNode.style.display = this.wipeNode.style.display = open ? "" : "none"; + } + + // load content (if this is the first time we are opening the TitlePane + // and content is specified as an href, or href was set when hidden) + if(this._started){ + if(open){ + this._onShow(); + }else{ + this.onHide(); + } + } + + this.arrowNodeInner.innerHTML = open ? "-" : "+"; + + this.containerNode.setAttribute("aria-hidden", open ? "false" : "true"); + this.focusNode.setAttribute("aria-pressed", open ? "true" : "false"); + + this._set("open", open); + + this._setCss(); + }, + + _setToggleableAttr: function(/*Boolean*/ canToggle){ + // summary: + // Hook to make set("toggleable", boolean) work. + // canToggle: Boolean + // True to allow user to open/close pane by clicking title bar. + + this.focusNode.setAttribute("role", canToggle ? "button" : "heading"); + if(canToggle){ + // TODO: if canToggle is switched from true to false shouldn't we remove this setting? + this.focusNode.setAttribute("aria-controls", this.id+"_pane"); + domAttr.set(this.focusNode, "tabIndex", this.tabIndex); + }else{ + domAttr.remove(this.focusNode, "tabIndex"); + } + + this._set("toggleable", canToggle); + + this._setCss(); + }, + + _setContentAttr: function(/*String|DomNode|Nodelist*/ content){ + // summary: + // Hook to make set("content", ...) work. + // Typically called when an href is loaded. Our job is to make the animation smooth. + + if(!this.open || !this._wipeOut || this._wipeOut.status() == "playing"){ + // we are currently *closing* the pane (or the pane is closed), so just let that continue + this.inherited(arguments); + }else{ + if(this._wipeIn && this._wipeIn.status() == "playing"){ + this._wipeIn.stop(); + } + + // freeze container at current height so that adding new content doesn't make it jump + domGeometry.setMarginBox(this.wipeNode, { h: domGeometry.getMarginBox(this.wipeNode).h }); + + // add the new content (erasing the old content, if any) + this.inherited(arguments); + + // call _wipeIn.play() to animate from current height to new height + if(this._wipeIn){ + this._wipeIn.play(); + }else{ + this.hideNode.style.display = ""; + } + } + }, + + toggle: function(){ + // summary: + // Switches between opened and closed state + // tags: + // private + + this._setOpenAttr(!this.open, true); + }, + + _setCss: function(){ + // summary: + // Set the open/close css state for the TitlePane + // tags: + // private + + var node = this.titleBarNode || this.focusNode; + var oldCls = this._titleBarClass; + this._titleBarClass = "dijit" + (this.toggleable ? "" : "Fixed") + (this.open ? "Open" : "Closed"); + domClass.replace(node, this._titleBarClass, oldCls || ""); + + this.arrowNodeInner.innerHTML = this.open ? "-" : "+"; + }, + + _onTitleKey: function(/*Event*/ e){ + // summary: + // Handler for when user hits a key + // tags: + // private + + if(e.charOrCode == keys.ENTER || e.charOrCode == ' '){ + if(this.toggleable){ + this.toggle(); + } + event.stop(e); + }else if(e.charOrCode == keys.DOWN_ARROW && this.open){ + this.containerNode.focus(); + e.preventDefault(); + } + }, + + _onTitleClick: function(){ + // summary: + // Handler when user clicks the title bar + // tags: + // private + if(this.toggleable){ + this.toggle(); + } + }, + + setTitle: function(/*String*/ title){ + // summary: + // Deprecated. Use set('title', ...) instead. + // tags: + // deprecated + kernel.deprecated("dijit.TitlePane.setTitle() is deprecated. Use set('title', ...) instead.", "", "2.0"); + this.set("title", title); + } +}); + +}); + +}, +'dijit/form/_ComboBoxMenuMixin':function(){ +define([ + "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` + +return declare( "dijit.form._ComboBoxMenuMixin", null, { + // summary: + // Focus-less menu for internal use in `dijit.form.ComboBox` + // tags: + // private + + // _messages: Object + // Holds "next" and "previous" text for paging buttons on drop down + _messages: null, + + postMixInProperties: function(){ + this.inherited(arguments); + this._messages = i18n.getLocalization("dijit.form", "ComboBox", this.lang); + }, + + buildRendering: function(){ + this.inherited(arguments); + + // fill in template with i18n messages + this.previousButton.innerHTML = this._messages["previousMessage"]; + this.nextButton.innerHTML = this._messages["nextMessage"]; + }, + + _setValueAttr: function(/*Object*/ value){ + this.value = value; + this.onChange(value); + }, + + 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); + } + }, + + // stubs + onChange: function(/*Number*/ /*===== direction =====*/){ + // summary: + // Notifies ComboBox/FilteringSelect that user selected an option. + // tags: + // callback + }, + + onPage: function(/*Number*/ /*===== direction =====*/){ + // summary: + // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page. + // tags: + // callback + }, + + onClose: function(){ + // summary: + // Callback from dijit.popup code to this widget, notifying it that it closed + // tags: + // private + this._setSelectedAttr(null); + }, + + _createOption: function(/*Object*/ item, labelFunc){ + // summary: + // Creates an option to appear on the popup menu subclassed by + // `dijit.form.FilteringSelect`. + + 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 = " "; // + } + + // update menuitem.dir if BidiSupport was required + this.applyTextDir(menuitem, (menuitem.innerText || menuitem.textContent || "")); + + menuitem.item=item; + return menuitem; + }, + + 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; + } + }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; + } + + this.nextButton.style.display = displayMore ? "" : "none"; + domAttr.set(this.nextButton,"id", this.id + "_next"); + return this.containerNode.childNodes; + }, + + 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); + }, + + highlightFirstOption: function(){ + // summary: + // Highlight the first real item in the list (not Previous Choices). + this.selectFirstNode(); + }, + + highlightLastOption: function(){ + // summary: + // 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(); + } + }, + + selectLastNode: function(){ + this.inherited(arguments); + if(this.getHighlightedOption() == this.nextButton){ + this.selectPreviousNode(); + } + }, + + getHighlightedOption: function(){ + return this._getSelectedAttr(); + } +}); + +}); + +}, +'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", +'dijit/form/ToggleButton':function(){ +define("dijit/form/ToggleButton", [ + "dojo/_base/declare", // declare + "dojo/_base/kernel", // kernel.deprecated + "./Button", + "./_ToggleButtonMixin" +], function(declare, kernel, Button, _ToggleButtonMixin){ + +/*===== + 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); + } + }); +}); + +}, +'dijit/form/NumberSpinner':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/keys", // keys.END keys.HOME + "./_Spinner", + "./NumberTextBox" +], function(declare, event, keys, _Spinner, NumberTextBox){ + +/*===== + var _Spinner = dijit.form._Spinner; + var NumberTextBox = dijit.form.NumberTextBox; +=====*/ + +// module: +// dijit/form/NumberSpinner +// summary: +// Extends NumberTextBox to add up/down arrows and pageup/pagedown for incremental change to the value + + +return declare("dijit.form.NumberSpinner", [_Spinner, NumberTextBox.Mixin], { + // summary: + // Extends NumberTextBox to add up/down arrows and pageup/pagedown for incremental change to the value + // + // description: + // A `dijit.form.NumberTextBox` extension to provide keyboard accessible value selection + // as well as icons for spinning direction. When using the keyboard, the typematic rules + // apply, meaning holding the key will gradually increase or decrease the value and + // accelerate. + // + // example: + // | new dijit.form.NumberSpinner({ constraints:{ max:300, min:100 }}, "someInput"); + + adjust: function(/*Object*/ val, /*Number*/ delta){ + // summary: + // Change Number val by the given amount + // tags: + // protected + + var tc = this.constraints, + v = isNaN(val), + gotMax = !isNaN(tc.max), + gotMin = !isNaN(tc.min) + ; + if(v && delta != 0){ // blank or invalid value and they want to spin, so create defaults + val = (delta > 0) ? + gotMin ? tc.min : gotMax ? tc.max : 0 : + gotMax ? this.constraints.max : gotMin ? tc.min : 0 + ; + } + var newval = val + delta; + if(v || isNaN(newval)){ return val; } + if(gotMax && (newval > tc.max)){ + newval = tc.max; + } + if(gotMin && (newval < tc.min)){ + newval = tc.min; + } + return newval; + }, + + _onKeyPress: function(e){ + if((e.charOrCode == keys.HOME || e.charOrCode == keys.END) && !(e.ctrlKey || e.altKey || e.metaKey) + && typeof this.get('value') != 'undefined' /* gibberish, so HOME and END are default editing keys*/){ + var value = this.constraints[(e.charOrCode == keys.HOME ? "min" : "max")]; + if(typeof value == "number"){ + this._setValueAttr(value, false); + } + // eat home or end key whether we change the value or not + event.stop(e); + } + } +}); + +}); + +}, +'dijit/form/Textarea':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/dom-style", // domStyle.set + "./_ExpandingTextAreaMixin", + "./SimpleTextarea" +], function(declare, domStyle, _ExpandingTextAreaMixin, SimpleTextarea){ + +/*===== + var _ExpandingTextAreaMixin = dijit.form._ExpandingTextAreaMixin; + var SimpleTextarea = dijit.form.SimpleTextarea; +=====*/ + +// module: +// dijit/form/Textarea +// summary: +// A textarea widget that adjusts it's height according to the amount of data. + + +return declare("dijit.form.Textarea", [SimpleTextarea, _ExpandingTextAreaMixin], { + // summary: + // A textarea widget that adjusts it's height according to the amount of data. + // + // description: + // A textarea that dynamically expands/contracts (changing it's height) as + // the user types, to display all the text without requiring a scroll bar. + // + // Takes nearly all the parameters (name, value, etc.) that a vanilla textarea takes. + // Rows is not supported since this widget adjusts the height. + // + // example: + // | <textarea data-dojo-type="dijit.form.TextArea">...</textarea> + + + // TODO: for 2.0, rename this to ExpandingTextArea, and rename SimpleTextarea to TextArea + + baseClass: "dijitTextBox dijitTextArea dijitExpandingTextArea", + + // Override SimpleTextArea.cols to default to width:100%, for backward compatibility + cols: "", + + buildRendering: function(){ + this.inherited(arguments); + + // tweak textarea style to reduce browser differences + domStyle.set(this.textbox, { overflowY: 'hidden', overflowX: 'auto', boxSizing: 'border-box', MsBoxSizing: 'border-box', WebkitBoxSizing: 'border-box', MozBoxSizing: 'border-box' }); + } +}); + +}); + +}, +'dijit/form/DateTextBox':function(){ +define([ + "dojo/_base/declare", // declare + "../Calendar", + "./_DateTimeTextBox" +], function(declare, Calendar, _DateTimeTextBox){ + +/*===== + var Calendar = dijit.Calendar; + var _DateTimeTextBox = dijit.form._DateTimeTextBox; +=====*/ + + // module: + // dijit/form/DateTextBox + // summary: + // A validating, serializable, range-bound date text box with a drop down calendar + + + return declare("dijit.form.DateTextBox", _DateTimeTextBox, { + // summary: + // A validating, serializable, range-bound date text box with a drop down calendar + // + // Example: + // | new dijit.form.DateTextBox({value: new Date(2009, 0, 20)}) + // + // Example: + // | <input data-dojo-type='dijit.form.DateTextBox' value='2009-01-20'> + + baseClass: "dijitTextBox dijitComboBox dijitDateTextBox", + popupClass: Calendar, + _selector: "date", + + // value: Date + // The value of this widget as a JavaScript Date object, with only year/month/day specified. + // If specified in markup, use the format specified in `stamp.fromISOString`. + // set("value", ...) accepts either a Date object or a string. + value: new Date("") // value.toString()="NaN" + }); +}); + +}, +'dijit/form/ComboButton':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/keys", // keys + "../focus", // focus.focus() + "./DropDownButton", + "dojo/text!./templates/ComboButton.html" +], function(declare, event, keys, focus, DropDownButton, template){ + +/*===== + var DropDownButton = dijit.form.DropDownButton; +=====*/ + +// module: +// dijit/form/ComboButton +// summary: +// A combination button and drop-down button. + +return declare("dijit.form.ComboButton", 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 data-dojo-type="dijit.form.ComboButton" onClick="..."> + // | <span>Hello world</span> + // | <div data-dojo-type="dijit.Menu">...</div> + // | </button> + // + // example: + // | var button1 = new dijit.form.ComboButton({label: "hello world", onClick: foo, dropDown: "myMenu"}); + // | dojo.body().appendChild(button1.domNode); + // + + templateString: template, + + // Map widget attributes to DOMNode attributes. + _setIdAttr: "", // override _FormWidgetMixin which puts id on the focusNode + _setTabIndexAttr: ["focusNode", "titleNode"], + _setTitleAttr: "titleNode", + + // optionsTitle: String + // Text that describes the options menu (accessibility) + optionsTitle: "", + + baseClass: "dijitComboButton", + + // Set classes like dijitButtonContentsHover or dijitArrowButtonActive depending on + // mouse action over specified node + cssStateNodes: { + "buttonNode": "dijitButtonNode", + "titleNode": "dijitButtonContents", + "_popupStateNode": "dijitDownArrowButton" + }, + + _focusedNode: null, + + _onButtonKeyPress: function(/*Event*/ evt){ + // summary: + // Handler for right arrow key when focus is on left part of button + if(evt.charOrCode == keys[this.isLeftToRight() ? "RIGHT_ARROW" : "LEFT_ARROW"]){ + focus.focus(this._popupStateNode); + event.stop(evt); + } + }, + + _onArrowKeyPress: function(/*Event*/ evt){ + // summary: + // Handler for left arrow key when focus is on right part of button + if(evt.charOrCode == keys[this.isLeftToRight() ? "LEFT_ARROW" : "RIGHT_ARROW"]){ + focus.focus(this.titleNode); + event.stop(evt); + } + }, + + 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){ + focus.focus(position == "start" ? this.titleNode : this._popupStateNode); + } + } +}); + +}); + +}, +'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. + + + // 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. + + + 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 + + templateString: template, + + // label: String + // Title of the pane + label: "", + _setLabelAttr: {node: "titleTextNode", type: "innerHTML" }, + + // title: String + // Tooltip that appears on hover + title: "", + _setTitleAttr: {node: "titleTextNode", type: "attribute", attribute: "title"}, + + // iconClassAttr: String + // CSS class for icon to left of label + iconClassAttr: "", + _setIconClassAttr: { 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(' ','_'); + domAttr.set(this.titleTextNode, "id", titleTextNodeId+"_title"); + this.focusNode.setAttribute("aria-labelledby", domAttr.get(this.titleTextNode, "id")); + dom.setSelectable(this.domNode, false); + }, + + getTitleHeight: function(){ + // summary: + // Returns the height of the title dom node. + return domGeometry.getMarginSize(this.domNode).h; // Integer + }, + + // 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); + }, + + _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. + +/*===== + // 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, +=====*/ + +/*===== + // contentWidget: dijit._Widget + // Pointer to the real child widget + contentWidget: null, +=====*/ + + baseClass: "dijitAccordionInnerContainer", + + // tell nested layout widget that we will take care of sizing + isLayoutContainer: true, + + buildRendering: function(){ + // Builds a template like: + // <div class=dijitAccordionInnerContainer> + // Button + // <div class=dijitAccordionChildWrapper> + // ContentPane + // </div> + // </div> + + // Create wrapper div, placed where the child is now + this.domNode = domConstruct.place("<div class='" + this.baseClass + + "' role='presentation'>", this.contentWidget.domNode, "after"); + + // 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); + + // 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); + }, + + postCreate: function(){ + this.inherited(arguments); + + // 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); + })) + ]; + }, + + _setSelectedAttr: function(/*Boolean*/ isSelected){ + this._set("selected", isSelected); + this.button.set("selected", isSelected); + if(isSelected){ + var cw = this.contentWidget; + if(cw.onSelected){ cw.onSelected(); } + } + }, + + startup: function(){ + // Called by _Container.addChild() + this.contentWidget.startup(); + }, + + destroy: function(){ + this.button.destroyRecursive(); + + array.forEach(this._contentWidgetWatches || [], function(w){ w.unwatch(); }); + + delete this.contentWidget._buttonWidget; + delete this.contentWidget._wrapperWidget; + + this.inherited(arguments); + }, + + destroyDescendants: function(/*Boolean*/ preserveDom){ + // since getChildren isn't working for me, have to code this manually + this.contentWidget.destroyRecursive(preserveDom); + } + }); + + var AccordionContainer = declare("dijit.layout.AccordionContainer", 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 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> + + // duration: Integer + // Amount of time (in ms) it takes to slide panes + duration: manager.defaultDuration, + + // 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); + } + }, + + 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); + } + }, + + _setupChild: function(child){ + // Overrides _LayoutWidget._setupChild(). + // Put wrapper widget around the child widget, showing title + + child._wrapperWidget = AccordionInnerContainer({ + contentWidget: child, + buttonWidget: this.buttonWidget, + id: child.id + "_wrapper", + dir: child.dir, + lang: child.lang, + textDir: child.textDir, + parent: this + }); + + this.inherited(arguments); + }, + + 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); + } + }, + + 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){ + domConstruct.place(child.domNode, child._wrapperWidget.domNode, "after"); + child._wrapperWidget.destroy(); + delete child._wrapperWidget; + } + + domClass.remove(child.domNode, "dijitHidden"); + + this.inherited(arguments); + }, + + 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(){ + 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); + }, + + _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); + }, + + _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; + } + + 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; + } + + 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(oldWidget){ + oldWidget._wrapperWidget.set("selected", false); + if(!animate){ + this._hideChild(oldWidget); + } + } + + if(animate){ + var newContents = newWidget._wrapperWidget.containerNode, + oldContents = oldWidget._wrapperWidget.containerNode; + + // 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 + }); + } + + // For monkey patching + AccordionContainer._InnerContainer = AccordionInnerContainer; + AccordionContainer._Button = AccordionButton; + + return AccordionContainer; +}); + +}, +'dijit/layout/SplitContainer':function(){ +define("dijit/layout/SplitContainer", [ + "dojo/_base/array", // array.forEach array.indexOf array.some + "dojo/cookie", // cookie + "dojo/_base/declare", // declare + "dojo/dom", // dom.setSelectable + "dojo/dom-class", // domClass.add + "dojo/dom-construct", // domConstruct.create domConstruct.destroy + "dojo/dom-geometry", // domGeometry.marginBox domGeometry.position + "dojo/dom-style", // domStyle.style + "dojo/_base/event", // event.stop + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/lang", // lang.extend lang.hitch + "dojo/on", + "dojo/_base/sniff", // has("mozilla") + "dojo/_base/window", // win.doc.createElement win.doc.documentElement + "../registry", // registry.getUniqueId() + "../_WidgetBase", + "./_LayoutWidget" +], function(array, cookie, declare, dom, domClass, domConstruct, domGeometry, domStyle, + event, kernel, lang, on, has, win, registry, _WidgetBase, _LayoutWidget){ + +/*===== +var _WidgetBase = dijit._WidgetBase; +var _LayoutWidget = dijit.layout._LayoutWidget; +=====*/ + +// module: +// dijit/layout/SplitContainer +// summary: +// Deprecated. Use `dijit.layout.BorderContainer` instead. + +// +// FIXME: make it prettier +// FIXME: active dragging upwards doesn't always shift other bars (direction calculation is wrong in this case) +// FIXME: sizeWidth should be a CSS attribute (at 7 because css wants it to be 7 until we fix to css) +// + +// These arguments can be specified for the children of a SplitContainer. +// Since any widget can be specified as a SplitContainer child, mix them +// into the base widget class. (This is a hack, but it's effective.) +lang.extend(_WidgetBase, { + // sizeMin: [deprecated] Integer + // Deprecated. Parameter for children of `dijit.layout.SplitContainer`. + // Minimum size (width or height) of a child of a SplitContainer. + // The value is relative to other children's sizeShare properties. + sizeMin: 10, + + // sizeShare: [deprecated] Integer + // Deprecated. Parameter for children of `dijit.layout.SplitContainer`. + // Size (width or height) of a child of a SplitContainer. + // The value is relative to other children's sizeShare properties. + // For example, if there are two children and each has sizeShare=10, then + // each takes up 50% of the available space. + sizeShare: 10 +}); + +return declare("dijit.layout.SplitContainer", _LayoutWidget, { + // summary: + // Deprecated. Use `dijit.layout.BorderContainer` instead. + // description: + // A Container widget with sizing handles in-between each child. + // Contains multiple children widgets, all of which are displayed side by side + // (either horizontally or vertically); there's a bar between each of the children, + // and you can adjust the relative size of each child by dragging the bars. + // + // You must specify a size (width and height) for the SplitContainer. + // tags: + // deprecated + + constructor: function(){ + kernel.deprecated("dijit.layout.SplitContainer is deprecated", "use BorderContainer with splitter instead", 2.0); + }, + + // activeSizing: Boolean + // If true, the children's size changes as you drag the bar; + // otherwise, the sizes don't change until you drop the bar (by mouse-up) + activeSizing: false, + + // sizerWidth: Integer + // Size in pixels of the bar between each child + sizerWidth: 7, + + // orientation: String + // either 'horizontal' or vertical; indicates whether the children are + // arranged side-by-side or up/down. + orientation: 'horizontal', + + // persist: Boolean + // Save splitter positions in a cookie + persist: true, + + baseClass: "dijitSplitContainer", + + postMixInProperties: function(){ + this.inherited("postMixInProperties",arguments); + this.isHorizontal = (this.orientation == 'horizontal'); + }, + + postCreate: function(){ + this.inherited(arguments); + this.sizers = []; + + // overflow has to be explicitly hidden for splitContainers using gekko (trac #1435) + // to keep other combined css classes from inadvertantly making the overflow visible + if(has("mozilla")){ + this.domNode.style.overflow = '-moz-scrollbars-none'; // hidden doesn't work + } + + // create the fake dragger + if(typeof this.sizerWidth == "object"){ + try{ //FIXME: do this without a try/catch + this.sizerWidth = parseInt(this.sizerWidth.toString()); + }catch(e){ this.sizerWidth = 7; } + } + var sizer = win.doc.createElement('div'); + this.virtualSizer = sizer; + sizer.style.position = 'relative'; + + // #1681: work around the dreaded 'quirky percentages in IE' layout bug + // If the splitcontainer's dimensions are specified in percentages, it + // will be resized when the virtualsizer is displayed in _showSizingLine + // (typically expanding its bounds unnecessarily). This happens because + // we use position: relative for .dijitSplitContainer. + // The workaround: instead of changing the display style attribute, + // switch to changing the zIndex (bring to front/move to back) + + sizer.style.zIndex = 10; + sizer.className = this.isHorizontal ? 'dijitSplitContainerVirtualSizerH' : 'dijitSplitContainerVirtualSizerV'; + this.domNode.appendChild(sizer); + dom.setSelectable(sizer, false); + }, + + destroy: function(){ + delete this.virtualSizer; + if(this._ownconnects){ + var h; + while(h = this._ownconnects.pop()){ h.remove(); } + } + this.inherited(arguments); + }, + startup: function(){ + if(this._started){ return; } + + array.forEach(this.getChildren(), function(child, i, children){ + // attach the children and create the draggers + this._setupChild(child); + + if(i < children.length-1){ + this._addSizer(); + } + }, this); + + if(this.persist){ + this._restoreState(); + } + + this.inherited(arguments); + }, + + _setupChild: function(/*dijit._Widget*/ child){ + this.inherited(arguments); + child.domNode.style.position = "absolute"; + domClass.add(child.domNode, "dijitSplitPane"); + }, + + _onSizerMouseDown: function(e){ + if(e.target.id){ + for(var i=0;i<this.sizers.length;i++){ + if(this.sizers[i].id == e.target.id){ + break; + } + } + if(i<this.sizers.length){ + this.beginSizing(e,i); + } + } + }, + _addSizer: function(index){ + index = index === undefined ? this.sizers.length : index; + + // TODO: use a template for this!!! + var sizer = win.doc.createElement('div'); + sizer.id=registry.getUniqueId('dijit_layout_SplitterContainer_Splitter'); + this.sizers.splice(index,0,sizer); + this.domNode.appendChild(sizer); + + sizer.className = this.isHorizontal ? 'dijitSplitContainerSizerH' : 'dijitSplitContainerSizerV'; + + // add the thumb div + var thumb = win.doc.createElement('div'); + thumb.className = 'thumb'; + sizer.appendChild(thumb); + + // FIXME: are you serious? why aren't we using mover start/stop combo? + this.connect(sizer, "onmousedown", '_onSizerMouseDown'); + + dom.setSelectable(sizer, false); + }, + + removeChild: function(widget){ + // summary: + // Remove sizer, but only if widget is really our child and + // we have at least one sizer to throw away + if(this.sizers.length){ + var i = array.indexOf(this.getChildren(), widget); + if(i != -1){ + if(i == this.sizers.length){ + i--; + } + domConstruct.destroy(this.sizers[i]); + this.sizers.splice(i,1); + } + } + + // Remove widget and repaint + this.inherited(arguments); + if(this._started){ + this.layout(); + } + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // summary: + // Add a child widget to the container + // child: + // a widget to add + // insertIndex: + // postion in the "stack" to add the child widget + + this.inherited(arguments); + + if(this._started){ + // Do the stuff that startup() does for each widget + var children = this.getChildren(); + if(children.length > 1){ + this._addSizer(insertIndex); + } + + // and then reposition (ie, shrink) every pane to make room for the new guy + this.layout(); + } + }, + + layout: function(){ + // summary: + // Do layout of panels + + // base class defines this._contentBox on initial creation and also + // on resize + this.paneWidth = this._contentBox.w; + this.paneHeight = this._contentBox.h; + + var children = this.getChildren(); + if(!children.length){ return; } + + // + // calculate space + // + + var space = this.isHorizontal ? this.paneWidth : this.paneHeight; + if(children.length > 1){ + space -= this.sizerWidth * (children.length - 1); + } + + // + // calculate total of SizeShare values + // + var outOf = 0; + array.forEach(children, function(child){ + outOf += child.sizeShare; + }); + + // + // work out actual pixels per sizeshare unit + // + var pixPerUnit = space / outOf; + + // + // set the SizeActual member of each pane + // + var totalSize = 0; + array.forEach(children.slice(0, children.length - 1), function(child){ + var size = Math.round(pixPerUnit * child.sizeShare); + child.sizeActual = size; + totalSize += size; + }); + + children[children.length-1].sizeActual = space - totalSize; + + // + // make sure the sizes are ok + // + this._checkSizes(); + + // + // now loop, positioning each pane and letting children resize themselves + // + + var pos = 0; + var size = children[0].sizeActual; + this._movePanel(children[0], pos, size); + children[0].position = pos; + pos += size; + + // if we don't have any sizers, our layout method hasn't been called yet + // so bail until we are called..TODO: REVISIT: need to change the startup + // algorithm to guaranteed the ordering of calls to layout method + if(!this.sizers){ + return; + } + + array.some(children.slice(1), function(child, i){ + // error-checking + if(!this.sizers[i]){ + return true; + } + // first we position the sizing handle before this pane + this._moveSlider(this.sizers[i], pos, this.sizerWidth); + this.sizers[i].position = pos; + pos += this.sizerWidth; + + size = child.sizeActual; + this._movePanel(child, pos, size); + child.position = pos; + pos += size; + }, this); + }, + + _movePanel: function(panel, pos, size){ + var box; + if(this.isHorizontal){ + panel.domNode.style.left = pos + 'px'; // TODO: resize() takes l and t parameters too, don't need to set manually + panel.domNode.style.top = 0; + box = {w: size, h: this.paneHeight}; + if(panel.resize){ + panel.resize(box); + }else{ + domGeometry.setMarginBox(panel.domNode, box); + } + }else{ + panel.domNode.style.left = 0; // TODO: resize() takes l and t parameters too, don't need to set manually + panel.domNode.style.top = pos + 'px'; + box = {w: this.paneWidth, h: size}; + if(panel.resize){ + panel.resize(box); + }else{ + domGeometry.setMarginBox(panel.domNode, box); + } + } + }, + + _moveSlider: function(slider, pos, size){ + if(this.isHorizontal){ + slider.style.left = pos + 'px'; + slider.style.top = 0; + domGeometry.setMarginBox(slider, { w: size, h: this.paneHeight }); + }else{ + slider.style.left = 0; + slider.style.top = pos + 'px'; + domGeometry.setMarginBox(slider, { w: this.paneWidth, h: size }); + } + }, + + _growPane: function(growth, pane){ + if(growth > 0){ + if(pane.sizeActual > pane.sizeMin){ + if((pane.sizeActual - pane.sizeMin) > growth){ + + // stick all the growth in this pane + pane.sizeActual = pane.sizeActual - growth; + growth = 0; + }else{ + // put as much growth in here as we can + growth -= pane.sizeActual - pane.sizeMin; + pane.sizeActual = pane.sizeMin; + } + } + } + return growth; + }, + + _checkSizes: function(){ + + var totalMinSize = 0; + var totalSize = 0; + var children = this.getChildren(); + + array.forEach(children, function(child){ + totalSize += child.sizeActual; + totalMinSize += child.sizeMin; + }); + + // only make adjustments if we have enough space for all the minimums + + if(totalMinSize <= totalSize){ + + var growth = 0; + + array.forEach(children, function(child){ + if(child.sizeActual < child.sizeMin){ + growth += child.sizeMin - child.sizeActual; + child.sizeActual = child.sizeMin; + } + }); + + if(growth > 0){ + var list = this.isDraggingLeft ? children.reverse() : children; + array.forEach(list, function(child){ + growth = this._growPane(growth, child); + }, this); + } + }else{ + array.forEach(children, function(child){ + child.sizeActual = Math.round(totalSize * (child.sizeMin / totalMinSize)); + }); + } + }, + + beginSizing: function(e, i){ + var children = this.getChildren(); + this.paneBefore = children[i]; + this.paneAfter = children[i+1]; + + this.isSizing = true; + this.sizingSplitter = this.sizers[i]; + + if(!this.cover){ + this.cover = domConstruct.create('div', { + style: { + position:'absolute', + zIndex:5, + top: 0, + left: 0, + width: "100%", + height: "100%" + } + }, this.domNode); + }else{ + this.cover.style.zIndex = 5; + } + this.sizingSplitter.style.zIndex = 6; + + // TODO: REVISIT - we want MARGIN_BOX and core hasn't exposed that yet (but can't we use it anyway if we pay attention? we do elsewhere.) + this.originPos = domGeometry.position(children[0].domNode, true); + var client, screen; + if(this.isHorizontal){ + client = e.layerX || e.offsetX || 0; + screen = e.pageX; + this.originPos = this.originPos.x; + }else{ + client = e.layerY || e.offsetY || 0; + screen = e.pageY; + this.originPos = this.originPos.y; + } + this.startPoint = this.lastPoint = screen; + this.screenToClientOffset = screen - client; + this.dragOffset = this.lastPoint - this.paneBefore.sizeActual - this.originPos - this.paneBefore.position; + + if(!this.activeSizing){ + this._showSizingLine(); + } + + // + // attach mouse events + // + this._ownconnects = [ + on(win.doc.documentElement, "mousemove", lang.hitch(this, "changeSizing")), + on(win.doc.documentElement, "mouseup", lang.hitch(this, "endSizing")) + ]; + + event.stop(e); + }, + + changeSizing: function(e){ + if(!this.isSizing){ return; } + this.lastPoint = this.isHorizontal ? e.pageX : e.pageY; + this.movePoint(); + if(this.activeSizing){ + this._updateSize(); + }else{ + this._moveSizingLine(); + } + event.stop(e); + }, + + endSizing: function(){ + if(!this.isSizing){ return; } + if(this.cover){ + this.cover.style.zIndex = -1; + } + if(!this.activeSizing){ + this._hideSizingLine(); + } + + this._updateSize(); + + this.isSizing = false; + + if(this.persist){ + this._saveState(this); + } + + var h; + while(h = this._ownconnects.pop()){ h.remove(); } + }, + + movePoint: function(){ + + // make sure lastPoint is a legal point to drag to + var p = this.lastPoint - this.screenToClientOffset; + + var a = p - this.dragOffset; + a = this.legaliseSplitPoint(a); + p = a + this.dragOffset; + + this.lastPoint = p + this.screenToClientOffset; + }, + + legaliseSplitPoint: function(a){ + + a += this.sizingSplitter.position; + + this.isDraggingLeft = !!(a > 0); + + if(!this.activeSizing){ + var min = this.paneBefore.position + this.paneBefore.sizeMin; + if(a < min){ + a = min; + } + + var max = this.paneAfter.position + (this.paneAfter.sizeActual - (this.sizerWidth + this.paneAfter.sizeMin)); + if(a > max){ + a = max; + } + } + + a -= this.sizingSplitter.position; + + this._checkSizes(); + + return a; + }, + + _updateSize: function(){ + //FIXME: sometimes this.lastPoint is NaN + var pos = this.lastPoint - this.dragOffset - this.originPos; + + var start_region = this.paneBefore.position; + var end_region = this.paneAfter.position + this.paneAfter.sizeActual; + + this.paneBefore.sizeActual = pos - start_region; + this.paneAfter.position = pos + this.sizerWidth; + this.paneAfter.sizeActual = end_region - this.paneAfter.position; + + array.forEach(this.getChildren(), function(child){ + child.sizeShare = child.sizeActual; + }); + + if(this._started){ + this.layout(); + } + }, + + _showSizingLine: function(){ + + this._moveSizingLine(); + + domGeometry.setMarginBox(this.virtualSizer, + this.isHorizontal ? { w: this.sizerWidth, h: this.paneHeight } : { w: this.paneWidth, h: this.sizerWidth }); + + this.virtualSizer.style.display = 'block'; + }, + + _hideSizingLine: function(){ + this.virtualSizer.style.display = 'none'; + }, + + _moveSizingLine: function(){ + var pos = (this.lastPoint - this.startPoint) + this.sizingSplitter.position; + domStyle.set(this.virtualSizer,(this.isHorizontal ? "left" : "top"),pos+"px"); + // this.virtualSizer.style[ this.isHorizontal ? "left" : "top" ] = pos + 'px'; // FIXME: remove this line if the previous is better + }, + + _getCookieName: function(i){ + return this.id + "_" + i; + }, + + _restoreState: function(){ + array.forEach(this.getChildren(), function(child, i){ + var cookieName = this._getCookieName(i); + var cookieValue = cookie(cookieName); + if(cookieValue){ + var pos = parseInt(cookieValue); + if(typeof pos == "number"){ + child.sizeShare = pos; + } + } + }, this); + }, + + _saveState: function(){ + if(!this.persist){ + return; + } + array.forEach(this.getChildren(), function(child, i){ + cookie(this._getCookieName(i), child.sizeShare, {expires:365}); + }, this); + } +}); + +}); + +}, +'url:dijit/templates/Calendar.html':"<table cellspacing=\"0\" cellpadding=\"0\" class=\"dijitCalendarContainer\" role=\"grid\" aria-labelledby=\"${id}_mddb ${id}_year\">\n\t<thead>\n\t\t<tr class=\"dijitReset dijitCalendarMonthContainer\" valign=\"top\">\n\t\t\t<th class='dijitReset dijitCalendarArrow' data-dojo-attach-point=\"decrementMonth\">\n\t\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitCalendarIncrementControl dijitCalendarDecrease\" role=\"presentation\"/>\n\t\t\t\t<span data-dojo-attach-point=\"decreaseArrowNode\" class=\"dijitA11ySideArrow\">-</span>\n\t\t\t</th>\n\t\t\t<th class='dijitReset' colspan=\"5\">\n\t\t\t\t<div data-dojo-attach-point=\"monthNode\">\n\t\t\t\t</div>\n\t\t\t</th>\n\t\t\t<th class='dijitReset dijitCalendarArrow' data-dojo-attach-point=\"incrementMonth\">\n\t\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitCalendarIncrementControl dijitCalendarIncrease\" role=\"presentation\"/>\n\t\t\t\t<span data-dojo-attach-point=\"increaseArrowNode\" class=\"dijitA11ySideArrow\">+</span>\n\t\t\t</th>\n\t\t</tr>\n\t\t<tr>\n\t\t\t${!dayCellsHtml}\n\t\t</tr>\n\t</thead>\n\t<tbody data-dojo-attach-point=\"dateRowsNode\" data-dojo-attach-event=\"onclick: _onDayClick\" class=\"dijitReset dijitCalendarBodyContainer\">\n\t\t\t${!dateRowsHtml}\n\t</tbody>\n\t<tfoot class=\"dijitReset dijitCalendarYearContainer\">\n\t\t<tr>\n\t\t\t<td class='dijitReset' valign=\"top\" colspan=\"7\" role=\"presentation\">\n\t\t\t\t<div class=\"dijitCalendarYearLabel\">\n\t\t\t\t\t<span data-dojo-attach-point=\"previousYearLabelNode\" class=\"dijitInline dijitCalendarPreviousYear\" role=\"button\"></span>\n\t\t\t\t\t<span data-dojo-attach-point=\"currentYearLabelNode\" class=\"dijitInline dijitCalendarSelectedYear\" role=\"button\" id=\"${id}_year\"></span>\n\t\t\t\t\t<span data-dojo-attach-point=\"nextYearLabelNode\" class=\"dijitInline dijitCalendarNextYear\" role=\"button\"></span>\n\t\t\t\t</div>\n\t\t\t</td>\n\t\t</tr>\n\t</tfoot>\n</table>\n", +'dijit/form/_AutoCompleterMixin':function(){ +define([ + "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` + + + return declare("dijit.form._AutoCompleterMixin", null, { + // summary: + // A mixin that implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect` + // description: + // All widgets that mix in dijit.form._AutoCompleterMixin must extend `dijit.form._FormValueWidget`. + // tags: + // protected + + // 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, + + // pageSize: Integer + // Argument to data provider. + // Specifies number of search results per page (before hitting "next" button) + pageSize: Infinity, + + // store: [const] dojo.store.api.Store + // Reference to data provider object used by this ComboBox + store: null, + + // 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:{}, + + // 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". + // + // 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", + + // searchDelay: Integer + // Delay in milliseconds between when user types something and we start + // searching based on that value + searchDelay: 100, + + // searchAttr: String + // Search for items in the data store where this attribute (in the item) + // matches what the user typed + searchAttr: "name", + + // 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: "", + + // 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, + + _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 + + 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 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 ' ': + 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 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; + } + 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); + } + }, + + _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 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); + } + }, + + _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; + } + + // 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 nodes = this.dropDown.createOptions( + results, + options, + lang.hitch(this, "_getMenuLabelFromItem") + ); + + // show our list (only if we have content, else nothing) + this._showResultList(); + + // #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 + } + }, + + _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"); + }, + + 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(); + }, + + isLoaded: function(){ + // signal to _HasDropDown that it needs to call loadDropDown() to load the + // drop down asynchronously before displaying it + return false; + }, + + 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"); + } + }, + + _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(); + } + }, + + _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); + }, + + _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); + } + } + }); +}); + +}, +'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([ + "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: + // 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); + + // 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); + } + }); +}); + +}, +'dijit/form/ComboBoxMixin':function(){ +define([ + "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; +=====*/ + + // 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" + }, + + _setHasDownArrowAttr: function(/*Boolean*/ val){ + this._set("hasDownArrow", val); + this._buttonNode.style.display = val ? "" : "none"; + }, + + _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/form/_TextBoxMixin':function(){ +define([ + "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 + +var _TextBoxMixin = declare("dijit.form._TextBoxMixin", null, { + // summary: + // A mixin for textbox form input widgets + + // trim: Boolean + // Removes leading and trailing whitespace if true. Default is false. + trim: false, + + // 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: + // 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); + }, + + _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. + + 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]); + }, + + // 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: + // 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); + }, + + _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. + + 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); + } + }, + + format: function(value /*=====, constraints =====*/){ + // summary: + // Replaceable function to convert a value to a properly formatted string. + // value: String + // constraints: Object + // tags: + // protected extension + return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value)); + }, + + parse: function(value /*=====, constraints =====*/){ + // summary: + // Replaceable function to convert a formatted string to a value + // value: String + // constraints: Object + // tags: + // protected extension + + return value; // String + }, + + _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 + }, + + /*===== + onInput: function(event){ + // summary: + // 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: + // keydown | keypress | cut | paste | input + // tags: + // callback + }, + =====*/ + onInput: function(){}, + + __skipInputEvent: false, + _onInput: function(){ + // summary: + // 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")); + }, + + 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); + + // 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" ], function(event){ + this.connect(this.textbox, event, handleEvent); + }, this); + }, + + _blankValue: '', // if the textbox is blank, what value should be reported + filter: function(val){ + // summary: + // 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 + // + // tags: + // 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; + }, + + _setBlurValue: function(){ + this._setValueAttr(this.get('value'), true); + }, + + _onBlur: function(e){ + if(this.disabled){ return; } + this._setBlurValue(); + this.inherited(arguments); + + if(this._selectOnClickHandle){ + this.disconnect(this._selectOnClickHandle); + } + }, + + _isTextSelected: function(){ + return this.textbox.selectionStart == this.textbox.selectionEnd; + }, + + _onFocus: function(/*String*/ by){ + if(this.disabled || this.readOnly){ return; } + + // 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 + 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(); + }, + + reset: function(){ + // Overrides dijit._FormWidget.reset(). + // Additionally resets the displayed textbox value to '' + this.textbox.value = ''; + this.inherited(arguments); + }, + _setTextDirAttr: function(/*String*/ textDir){ + // summary: + // Setter for textDir. + // description: + // Users shouldn't call this function; they should be calling + // set('textDir', value) + // tags: + // private + + // 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); + } + } +}); + + +_TextBoxMixin._setSelectionRange = dijit._setSelectionRange = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){ + if(element.setSelectionRange){ + element.setSelectionRange(start, stop); + } +}; + +_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). + + // 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 _TextBoxMixin; +}); + +}, +'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){ + +/*===== + var TextBox = dijit.form.TextBox; +=====*/ + +// 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); + }, + + buildRendering: function(){ + this.inherited(arguments); + if(has("ie") && this.cols){ // attribute selectors is not supported in IE6 + domClass.add(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); + }, + + _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/PopupMenuItem':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/dom-style", // domStyle.set + "dojo/query", // query + "dojo/_base/window", // win.body + "./registry", // registry.byNode + "./MenuItem", + "./hccss" +], function(declare, domStyle, query, win, registry, MenuItem){ + +/*===== + var MenuItem = dijit.MenuItem; +=====*/ + + // module: + // dijit/PopupMenuItem + // summary: + // An item in a Menu that spawn a drop down (usually a drop down menu) + + return declare("dijit.PopupMenuItem", MenuItem, { + // summary: + // An item in a Menu that spawn a drop down (usually a drop down menu) + + _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 data-dojo-type="dijit.PopupMenuItem"> + // | <span>pick me</span> + // | <popup> ... </popup> + // | </div> + // tags: + // protected + + if(this.srcNodeRef){ + 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; } + 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 win.doc.body. + if(!this.popup){ + var node = query("[widgetId]", this.dropDownContainer)[0]; + this.popup = registry.byNode(node); + } + win.body().appendChild(this.popup.domNode); + this.popup.startup(); + + this.popup.domNode.style.display="none"; + if(this.arrowWrapper){ + domStyle.set(this.arrowWrapper, "visibility", ""); + } + this.focusNode.setAttribute("aria-haspopup", "true"); + }, + + destroyDescendants: function(/*Boolean*/ preserveDom){ + 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(preserveDom); + } + delete this.popup; + } + this.inherited(arguments); + } + }); +}); + +}, +'dijit/_TimePicker':function(){ +define([ + "dojo/_base/array", // array.forEach + "dojo/date", // date.compare + "dojo/date/locale", // locale.format + "dojo/date/stamp", // stamp.fromISOString stamp.toISOString + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.add domClass.contains domClass.toggle + "dojo/dom-construct", // domConstruct.create + "dojo/_base/event", // event.stop + "dojo/_base/kernel", // deprecated + "dojo/keys", // keys + "dojo/_base/lang", // lang.mixin + "dojo/_base/sniff", // has("ie") + "dojo/query", // query + "dijit/typematic", + "./_Widget", + "./_TemplatedMixin", + "./form/_FormValueWidget", + "dojo/text!./templates/TimePicker.html" +], function(array, ddate, locale, stamp, declare, domClass, domConstruct, event, kernel, keys, lang, has, query, + typematic, _Widget, _TemplatedMixin, _FormValueWidget, template){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _FormValueWidget = dijit.form._FormValueWidget; +=====*/ + + // module: + // dijit/_TimePicker + // summary: + // A graphical time picker. + + + /*===== + declare( + "dijit._TimePicker.__Constraints", + locale.__FormatOptions, + { + // clickableIncrement: String + // See `dijit._TimePicker.clickableIncrement` + clickableIncrement: "T00:15:00", + + // visibleIncrement: String + // See `dijit._TimePicker.visibleIncrement` + visibleIncrement: "T01:00:00", + + // visibleRange: String + // See `dijit._TimePicker.visibleRange` + visibleRange: "T05:00:00" + } + ); + =====*/ + + return declare("dijit._TimePicker", [_Widget, _TemplatedMixin], { + // summary: + // A graphical time picker. + // This widget is used internally by other widgets and is not available + // as a standalone widget due to lack of accessibility support. + + templateString: template, + + // baseClass: [protected] String + // The root className to use for the various states of this widget + baseClass: "dijitTimePicker", + + // clickableIncrement: String + // ISO-8601 string representing the amount by which + // every clickable element in the time picker increases. + // Set in local time, without a time zone. + // Example: `T00:15:00` creates 15 minute increments + // Must divide dijit._TimePicker.visibleIncrement evenly + clickableIncrement: "T00:15:00", + + // visibleIncrement: String + // ISO-8601 string representing the amount by which + // every element with a visible time in the time picker increases. + // Set in local time, without a time zone. + // Example: `T01:00:00` creates text in every 1 hour increment + visibleIncrement: "T01:00:00", + + // visibleRange: String + // ISO-8601 string representing the range of this TimePicker. + // The TimePicker will only display times in this range. + // Example: `T05:00:00` displays 5 hours of options + visibleRange: "T05:00:00", + + // value: String + // Date to display. + // Defaults to current time and date. + // Can be a Date object or an ISO-8601 string. + // If you specify the GMT time zone (`-01:00`), + // the time will be converted to the local time in the local time zone. + // Otherwise, the time is considered to be in the local time zone. + // If you specify the date and isDate is true, the date is used. + // Example: if your local time zone is `GMT -05:00`, + // `T10:00:00` becomes `T10:00:00-05:00` (considered to be local time), + // `T10:00:00-01:00` becomes `T06:00:00-05:00` (4 hour difference), + // `T10:00:00Z` becomes `T05:00:00-05:00` (5 hour difference between Zulu and local time) + // `yyyy-mm-ddThh:mm:ss` is the format to set the date and time + // Example: `2007-06-01T09:00:00` + value: new Date(), + + _visibleIncrement:2, + _clickableIncrement:1, + _totalIncrements:10, + + // constraints: dijit._TimePicker.__Constraints + // Specifies valid range of times (start time, end time) + constraints:{}, + +/*===== + serialize: function(val, options){ + // summary: + // User overridable function used to convert the attr('value') result to a String + // val: Date + // The current value + // options: Object? + // tags: + // protected + }, +=====*/ + serialize: stamp.toISOString, + +/*===== + // filterString: string + // The string to filter by + filterString: "", +=====*/ + + setValue: function(/*Date*/ value){ + // summary: + // Deprecated. Used set('value') instead. + // tags: + // deprecated + kernel.deprecated("dijit._TimePicker:setValue() is deprecated. Use set('value', ...) instead.", "", "2.0"); + this.set('value', value); + }, + + _setValueAttr: function(/*Date*/ date){ + // summary: + // Hook so set('value', ...) works. + // description: + // Set the value of the TimePicker. + // Redraws the TimePicker around the new date. + // tags: + // protected + this._set("value", date); + this._showText(); + }, + + _setFilterStringAttr: function(val){ + // summary: + // Called by TimeTextBox to filter the values shown in my list + this._set("filterString", val); + this._showText(); + }, + + isDisabledDate: function(/*===== dateObject, locale =====*/){ + // summary: + // May be overridden to disable certain dates in the TimePicker e.g. `isDisabledDate=locale.isWeekend` + // dateObject: Date + // locale: String? + // type: + // extension + return false; // Boolean + }, + + _getFilteredNodes: function(/*number*/ start, /*number*/ maxNum, /*Boolean*/ before, /*DOMnode*/ lastNode){ + // summary: + // Returns an array of nodes with the filter applied. At most maxNum nodes + // will be returned - but fewer may be returned as well. If the + // before parameter is set to true, then it will return the elements + // before the given index + // tags: + // private + var + nodes = [], + lastValue = lastNode ? lastNode.date : this._refDate, + n, + i = start, + max = this._maxIncrement + Math.abs(i), + chk = before ? -1 : 1, + dec = before ? 1 : 0, + inc = 1 - dec; + do{ + i -= dec; + n = this._createOption(i); + if(n){ + if((before && n.date > lastValue) || (!before && n.date < lastValue)){ + break; // don't wrap + } + nodes[before ? "unshift" : "push"](n); + lastValue = n.date; + } + i += inc; + }while(nodes.length < maxNum && (i*chk) < max); + return nodes; + }, + + _showText: function(){ + // summary: + // Displays the relevant choices in the drop down list + // tags: + // private + var fromIso = stamp.fromISOString; + this.timeMenu.innerHTML = ""; + this._clickableIncrementDate=fromIso(this.clickableIncrement); + this._visibleIncrementDate=fromIso(this.visibleIncrement); + this._visibleRangeDate=fromIso(this.visibleRange); + // get the value of the increments and the range in seconds (since 00:00:00) to find out how many divs to create + var + sinceMidnight = function(/*Date*/ date){ + return date.getHours() * 60 * 60 + date.getMinutes() * 60 + date.getSeconds(); + }, + clickableIncrementSeconds = sinceMidnight(this._clickableIncrementDate), + visibleIncrementSeconds = sinceMidnight(this._visibleIncrementDate), + visibleRangeSeconds = sinceMidnight(this._visibleRangeDate), + // round reference date to previous visible increment + time = (this.value || this.currentFocus).getTime(); + + this._refDate = new Date(time - time % (clickableIncrementSeconds*1000)); + this._refDate.setFullYear(1970,0,1); // match parse defaults + + // assume clickable increment is the smallest unit + this._clickableIncrement = 1; + // divide the visible range by the clickable increment to get the number of divs to create + // example: 10:00:00/00:15:00 -> display 40 divs + this._totalIncrements = visibleRangeSeconds / clickableIncrementSeconds; + // divide the visible increments by the clickable increments to get how often to display the time inline + // example: 01:00:00/00:15:00 -> display the time every 4 divs + this._visibleIncrement = visibleIncrementSeconds / clickableIncrementSeconds; + // divide the number of seconds in a day by the clickable increment in seconds to get the + // absolute max number of increments. + this._maxIncrement = (60 * 60 * 24) / clickableIncrementSeconds; + + var + // Find the nodes we should display based on our filter. + // Limit to 10 nodes displayed as a half-hearted attempt to stop drop down from overlapping <input>. + count = Math.min(this._totalIncrements, 10), + after = this._getFilteredNodes(0, (count >> 1) + 1, false), + moreAfter = [], + estBeforeLength = count - after.length, + before = this._getFilteredNodes(0, estBeforeLength, true, after[0]); + if(before.length < estBeforeLength && after.length > 0){ + moreAfter = this._getFilteredNodes(after.length, estBeforeLength - before.length, false, after[after.length-1]); + } + array.forEach(before.concat(after, moreAfter), function(n){ this.timeMenu.appendChild(n); }, this); + }, + + constructor: function(){ + this.constraints = {}; // create instance object + }, + + postMixInProperties: function(){ + this.inherited(arguments); + this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls + }, + + _setConstraintsAttr: function(/* Object */ constraints){ + // brings in visibleRange, increments, etc. + lang.mixin(this, constraints); + + // locale needs the lang in the constraints as locale + if(!constraints.locale){ + constraints.locale = this.lang; + } + }, + + postCreate: function(){ + // assign typematic mouse listeners to the arrow buttons + this.connect(this.timeMenu, has("ie") ? "onmousewheel" : 'DOMMouseScroll', "_mouseWheeled"); + this._connects.push(typematic.addMouseListener(this.upArrow, this, "_onArrowUp", 33, 250)); + this._connects.push(typematic.addMouseListener(this.downArrow, this, "_onArrowDown", 33, 250)); + + this.inherited(arguments); + }, + + _buttonMouse: function(/*Event*/ e){ + // summary: + // Handler for hover (and unhover) on up/down arrows + // tags: + // private + + // in non-IE browser the "mouseenter" event will become "mouseover", + // but in IE it's still "mouseenter" + domClass.toggle(e.currentTarget, e.currentTarget == this.upArrow ? "dijitUpArrowHover" : "dijitDownArrowHover", + e.type == "mouseenter" || e.type == "mouseover"); + }, + + _createOption: function(/*Number*/ index){ + // summary: + // Creates a clickable time option + // tags: + // private + var date = new Date(this._refDate); + var incrementDate = this._clickableIncrementDate; + date.setHours(date.getHours() + incrementDate.getHours() * index, + date.getMinutes() + incrementDate.getMinutes() * index, + date.getSeconds() + incrementDate.getSeconds() * index); + if(this.constraints.selector == "time"){ + date.setFullYear(1970,0,1); // make sure each time is for the same date + } + var dateString = locale.format(date, this.constraints); + if(this.filterString && dateString.toLowerCase().indexOf(this.filterString) !== 0){ + // Doesn't match the filter - return null + return null; + } + + var div = domConstruct.create("div", {"class": this.baseClass+"Item"}); + div.date = date; + div.index = index; + domConstruct.create('div',{ + "class": this.baseClass + "ItemInner", + innerHTML: dateString + }, div); + + if(index%this._visibleIncrement<1 && index%this._visibleIncrement>-1){ + domClass.add(div, this.baseClass+"Marker"); + }else if(!(index%this._clickableIncrement)){ + domClass.add(div, this.baseClass+"Tick"); + } + + if(this.isDisabledDate(date)){ + // set disabled + domClass.add(div, this.baseClass+"ItemDisabled"); + } + if(this.value && !ddate.compare(this.value, date, this.constraints.selector)){ + div.selected = true; + domClass.add(div, this.baseClass+"ItemSelected"); + if(domClass.contains(div, this.baseClass+"Marker")){ + domClass.add(div, this.baseClass+"MarkerSelected"); + }else{ + domClass.add(div, this.baseClass+"TickSelected"); + } + + // Initially highlight the current value. User can change highlight by up/down arrow keys + // or mouse movement. + this._highlightOption(div, true); + } + return div; + }, + + _onOptionSelected: function(/*Object*/ tgt){ + // summary: + // Called when user clicks an option in the drop down list + // tags: + // private + var tdate = tgt.target.date || tgt.target.parentNode.date; + if(!tdate || this.isDisabledDate(tdate)){ return; } + this._highlighted_option = null; + this.set('value', tdate); + this.onChange(tdate); + }, + + onChange: function(/*Date*/ /*===== time =====*/){ + // summary: + // Notification that a time was selected. It may be the same as the previous value. + // tags: + // public + }, + + _highlightOption: function(/*node*/ node, /*Boolean*/ highlight){ + // summary: + // Turns on/off highlight effect on a node based on mouse out/over event + // tags: + // private + if(!node){return;} + if(highlight){ + if(this._highlighted_option){ + this._highlightOption(this._highlighted_option, false); + } + this._highlighted_option = node; + }else if(this._highlighted_option !== node){ + return; + }else{ + this._highlighted_option = null; + } + domClass.toggle(node, this.baseClass+"ItemHover", highlight); + if(domClass.contains(node, this.baseClass+"Marker")){ + domClass.toggle(node, this.baseClass+"MarkerHover", highlight); + }else{ + domClass.toggle(node, this.baseClass+"TickHover", highlight); + } + }, + + onmouseover: function(/*Event*/ e){ + // summary: + // Handler for onmouseover event + // tags: + // private + this._keyboardSelected = null; + var tgr = (e.target.parentNode === this.timeMenu) ? e.target : e.target.parentNode; + // if we aren't targeting an item, then we return + if(!domClass.contains(tgr, this.baseClass+"Item")){return;} + this._highlightOption(tgr, true); + }, + + onmouseout: function(/*Event*/ e){ + // summary: + // Handler for onmouseout event + // tags: + // private + this._keyboardSelected = null; + var tgr = (e.target.parentNode === this.timeMenu) ? e.target : e.target.parentNode; + this._highlightOption(tgr, false); + }, + + _mouseWheeled: function(/*Event*/ e){ + // summary: + // Handle the mouse wheel events + // tags: + // private + this._keyboardSelected = null; + event.stop(e); + // we're not _measuring_ the scroll amount, just direction + var scrollAmount = (has("ie") ? e.wheelDelta : -e.detail); + this[(scrollAmount>0 ? "_onArrowUp" : "_onArrowDown")](); // yes, we're making a new dom node every time you mousewheel, or click + }, + + _onArrowUp: function(count){ + // summary: + // Handler for up arrow key. + // description: + // Removes the bottom time and add one to the top + // tags: + // private + if(typeof count == "number" && count == -1){ return; } // typematic end + if(!this.timeMenu.childNodes.length){ return; } + var index = this.timeMenu.childNodes[0].index; + var divs = this._getFilteredNodes(index, 1, true, this.timeMenu.childNodes[0]); + if(divs.length){ + this.timeMenu.removeChild(this.timeMenu.childNodes[this.timeMenu.childNodes.length - 1]); + this.timeMenu.insertBefore(divs[0], this.timeMenu.childNodes[0]); + } + }, + + _onArrowDown: function(count){ + // summary: + // Handler for up arrow key. + // description: + // Remove the top time and add one to the bottom + // tags: + // private + if(typeof count == "number" && count == -1){ return; } // typematic end + if(!this.timeMenu.childNodes.length){ return; } + var index = this.timeMenu.childNodes[this.timeMenu.childNodes.length - 1].index + 1; + var divs = this._getFilteredNodes(index, 1, false, this.timeMenu.childNodes[this.timeMenu.childNodes.length - 1]); + if(divs.length){ + this.timeMenu.removeChild(this.timeMenu.childNodes[0]); + this.timeMenu.appendChild(divs[0]); + } + }, + + handleKey: function(/*Event*/ e){ + // summary: + // Called from `dijit.form._DateTimeTextBox` to pass a keypress event + // from the `dijit.form.TimeTextBox` to be handled in this widget + // tags: + // protected + if(e.charOrCode == keys.DOWN_ARROW || e.charOrCode == keys.UP_ARROW){ + event.stop(e); + // Figure out which option to highlight now and then highlight it + if(this._highlighted_option && !this._highlighted_option.parentNode){ + this._highlighted_option = null; + } + var timeMenu = this.timeMenu, + tgt = this._highlighted_option || query("." + this.baseClass + "ItemSelected", timeMenu)[0]; + if(!tgt){ + tgt = timeMenu.childNodes[0]; + }else if(timeMenu.childNodes.length){ + if(e.charOrCode == keys.DOWN_ARROW && !tgt.nextSibling){ + this._onArrowDown(); + }else if(e.charOrCode == keys.UP_ARROW && !tgt.previousSibling){ + this._onArrowUp(); + } + if(e.charOrCode == keys.DOWN_ARROW){ + tgt = tgt.nextSibling; + }else{ + tgt = tgt.previousSibling; + } + } + this._highlightOption(tgt, true); + this._keyboardSelected = tgt; + return false; + }else if(e.charOrCode == keys.ENTER || e.charOrCode === keys.TAB){ + // mouse hover followed by TAB is NO selection + if(!this._keyboardSelected && e.charOrCode === keys.TAB){ + return true; // true means don't call stopEvent() + } + + // Accept the currently-highlighted option as the value + if(this._highlighted_option){ + this._onOptionSelected({target: this._highlighted_option}); + } + + // Call stopEvent() for ENTER key so that form doesn't submit, + // but not for TAB, so that TAB does switch focus + return e.charOrCode === keys.TAB; + } + return undefined; + } + }); +}); + +}, +'dijit/form/RadioButton':function(){ +define("dijit/form/RadioButton", [ + "dojo/_base/declare", // declare + "./CheckBox", + "./_RadioButtonMixin" +], function(declare, CheckBox, _RadioButtonMixin){ + +/*===== + var CheckBox = dijit.form.CheckBox; + var _RadioButtonMixin = dijit.form._RadioButtonMixin; +=====*/ + + // module: + // dijit/form/RadioButton + // summary: + // Radio button widget + + return declare("dijit.form.RadioButton", [CheckBox, _RadioButtonMixin], { + // summary: + // Same as an HTML radio, but with fancy styling. + + baseClass: "dijitRadio" + }); +}); + +}, +'url:dijit/form/templates/HorizontalSlider.html':"<table class=\"dijit dijitReset dijitSlider dijitSliderH\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"none\" data-dojo-attach-event=\"onkeypress:_onKeyPress,onkeyup:_onKeyUp\"\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t\t><td data-dojo-attach-point=\"topDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationT dijitSliderDecorationH\"></td\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerH\"\n\t\t\t><div class=\"dijitSliderDecrementIconH\" style=\"display:none\" data-dojo-attach-point=\"decrementButton\"><span class=\"dijitSliderButtonInner\">-</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperH dijitSliderLeftBumper\" data-dojo-attach-event=\"press:_onClkDecBumper\"></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><input data-dojo-attach-point=\"valueNode\" type=\"hidden\" ${!nameAttrSetting}\n\t\t\t/><div class=\"dijitReset dijitSliderBarContainerH\" role=\"presentation\" data-dojo-attach-point=\"sliderBarContainer\"\n\t\t\t\t><div role=\"presentation\" data-dojo-attach-point=\"progressBar\" class=\"dijitSliderBar dijitSliderBarH dijitSliderProgressBar dijitSliderProgressBarH\" data-dojo-attach-event=\"press:_onBarClick\"\n\t\t\t\t\t><div class=\"dijitSliderMoveable dijitSliderMoveableH\"\n\t\t\t\t\t\t><div data-dojo-attach-point=\"sliderHandle,focusNode\" class=\"dijitSliderImageHandle dijitSliderImageHandleH\" data-dojo-attach-event=\"press:_onHandleClick\" role=\"slider\" valuemin=\"${minimum}\" valuemax=\"${maximum}\"></div\n\t\t\t\t\t></div\n\t\t\t\t></div\n\t\t\t\t><div role=\"presentation\" data-dojo-attach-point=\"remainingBar\" class=\"dijitSliderBar dijitSliderBarH dijitSliderRemainingBar dijitSliderRemainingBarH\" data-dojo-attach-event=\"press:_onBarClick\"></div\n\t\t\t></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperH dijitSliderRightBumper\" data-dojo-attach-event=\"press:_onClkIncBumper\"></div\n\t\t></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerH\"\n\t\t\t><div class=\"dijitSliderIncrementIconH\" style=\"display:none\" data-dojo-attach-point=\"incrementButton\"><span class=\"dijitSliderButtonInner\">+</span></div\n\t\t></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t\t><td data-dojo-attach-point=\"containerNode,bottomDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationB dijitSliderDecorationH\"></td\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t></tr\n></table>\n", +'url:dijit/templates/TimePicker.html':"<div id=\"widget_${id}\" class=\"dijitMenu\"\n ><div data-dojo-attach-point=\"upArrow\" class=\"dijitButtonNode dijitUpArrowButton\" data-dojo-attach-event=\"onmouseenter:_buttonMouse,onmouseleave:_buttonMouse\"\n\t\t><div class=\"dijitReset dijitInline dijitArrowButtonInner\" role=\"presentation\"> </div\n\t\t><div class=\"dijitArrowButtonChar\">▲</div></div\n ><div data-dojo-attach-point=\"timeMenu,focusNode\" data-dojo-attach-event=\"onclick:_onOptionSelected,onmouseover,onmouseout\"></div\n ><div data-dojo-attach-point=\"downArrow\" class=\"dijitButtonNode dijitDownArrowButton\" data-dojo-attach-event=\"onmouseenter:_buttonMouse,onmouseleave:_buttonMouse\"\n\t\t><div class=\"dijitReset dijitInline dijitArrowButtonInner\" role=\"presentation\"> </div\n\t\t><div class=\"dijitArrowButtonChar\">▼</div></div\n></div>\n", +'dijit/InlineEditBox':function(){ +define([ + "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){ + +/*===== + 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; +=====*/ + +// module: +// dijit/InlineEditBox +// summary: +// An element with in-line edit capabilities + +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 + + templateString: template, + + 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); + }, + + 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{ + // inline-block mode + editStyle += "width:" + (width + (Number(width) == width ? "px" : "")) + ";"; + } + 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); + } + } + }, + + _onIntermediateChange: function(/*===== val =====*/){ + // 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); + }, + + getValue: function(){ + // summary: + // Return the [display] value of the edit widget + var ew = this.editWidget; + return String(ew.get("displayedValue" in ew ? "displayedValue" : "value")); + }, + + _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 + + 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 + } + + // _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. + } + }, + + _onBlur: function(){ + // summary: + // Called when focus moves outside the editor + // tags: + // private + + 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()){ + fm.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(lang.hitch(this, function(){ + if(this.editWidget.focusNode && this.editWidget.focusNode.tagName == "INPUT"){ + _TextBoxMixin.selectInputText(this.editWidget.focusNode); + } + }), 0); + } +}); + + +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 + + // 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: TextBox, + + // editorWrapper: String|Function + // Class name (or reference to the Class) for widget that wraps the editor widget, displaying save/cancel + // buttons. + editorWrapper: 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%", + + // value: String + // The display value of the widget in read-only mode + value: "", + + // 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>", // //   == + + constructor: function(){ + // summary: + // Sets up private arrays etc. + // tags: + // private + this.editorParams = {}; + }, + + postMixInProperties: function(){ + this.inherited(arguments); + + // save pointer to original source node, since Widget nulls-out srcNodeRef + this.displayNode = this.srcNodeRef; + + // 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]); + } + this.displayNode.setAttribute("role", "button"); + if(!this.displayNode.getAttribute("tabIndex")){ + this.displayNode.setAttribute("tabIndex", 0); + } + + 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'); + }, + + 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); + }, + + _onMouseOver: function(){ + // summary: + // Handler for onmouseover and onfocus event. + // tags: + // private + if(!this.disabled){ + domClass.add(this.displayNode, "dijitInlineEditBoxDisplayModeHover"); + } + }, + + _onMouseOut: function(){ + // summary: + // Handler for onmouseout and onblur event. + // tags: + // private + domClass.remove(this.displayNode, "dijitInlineEditBoxDisplayModeHover"); + }, + + _onClick: function(/*Event*/ e){ + // summary: + // Handler for onclick event. + // tags: + // private + if(this.disabled){ return; } + if(e){ event.stop(e); } + this._onMouseOut(); + + // Since FF gets upset if you move a node while in an event handler for that node... + setTimeout(lang.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._set('editing', true); + + // 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(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"); + + // 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; + + // 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 + + 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 + + // 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); + }, + + _onBlur: 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(lang.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; + } + this.inherited(arguments); + }, + + _showText: function(/*Boolean*/ focus){ + // summary: + // Revert to display mode, and optionally focus on display node + // tags: + // private + + 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(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 + + this._showText(focus); // set focus as needed + }, + + 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); + }, + + _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 = 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); + + 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); + } + }, + + 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"); + }, + + cancel: function(/*Boolean*/ focus){ + // summary: + // Revert to display mode, discarding any changes made in the editor + // tags: + // private + + if(this.disabled || !this.editing){ return; } + this._set('editing', false); + + // 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 + } + } +}); + +InlineEditBox._InlineEditor = InlineEditor; // for monkey patching + +return InlineEditBox; +}); +}, +'dojo/dnd/autoscroll':function(){ +define(["../main", "../window"], function(dojo) { + // module: + // dojo/dnd/autoscroll + // summary: + // TODOC + +dojo.getObject("dnd", true, dojo); + +dojo.dnd.getViewport = dojo.window.getBox; + +dojo.dnd.V_TRIGGER_AUTOSCROLL = 32; +dojo.dnd.H_TRIGGER_AUTOSCROLL = 32; + +dojo.dnd.V_AUTOSCROLL_VALUE = 16; +dojo.dnd.H_AUTOSCROLL_VALUE = 16; + +dojo.dnd.autoScroll = function(e){ + // summary: + // a handler for onmousemove event, which scrolls the window, if + // necesary + // e: Event + // onmousemove event + + // 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); +}; + +dojo.dnd._validNodes = {"div": 1, "p": 1, "td": 1}; +dojo.dnd._validOverflow = {"auto": 1, "scroll": 1}; + +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 + + // FIXME: needs more docs! + + var b, t, w, h, rx, ry, dx = 0, dy = 0, oldLeft, oldTop; + + 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; } + } + try{ + n = n.parentNode; + }catch(x){ + n = null; + } + } + dojo.dnd.autoScroll(e); +}; + + return dojo.dnd; +}); + +}, +'dijit/form/_RadioButtonMixin':function(){ +define([ + "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 + + return declare("dijit.form._RadioButtonMixin", null, { + // summary: + // Mixin to provide widget functionality for an HTML radio button + + // type: [private] String + // type attribute on <input> node. + // Users should not change this value. + type: "radio", + + _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; + }, + + _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); + } + })); + } + }, + + _onClick: function(/*Event*/ e){ + if(this.checked || this.disabled){ // nothing to do + event.stop(e); + return false; + } + 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.inherited(arguments); + } + }); +}); + +}, +'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(["../main", "./Moveable"], function(dojo) { + // module: + // dojo/dnd/TimedMoveable + // summary: + // TODOC + + /*===== + 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 + }); + =====*/ + + // precalculate long expressions + var oldOnMove = dojo.dnd.Moveable.prototype.onMove; + + dojo.declare("dojo.dnd.TimedMoveable", dojo.dnd.Moveable, { + // 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){ + // 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; + } + }, + + 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) + } + 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; + +}); + +}, +'dijit/layout/LinkPane':function(){ +define([ + "./ContentPane", + "../_TemplatedMixin", + "dojo/_base/declare" // declare +], function(ContentPane, _TemplatedMixin, declare){ + +/*===== + var _TemplatedMixin = dijit._TemplatedMixin; + var ContentPane = dijit.layout.ContentPane; +=====*/ + + // module: + // dijit/layout/LinkPane + // summary: + // A ContentPane with an href where (when declared in markup) + // the title is specified as innerHTML rather than as a title attribute. + + + return declare("dijit.layout.LinkPane", [ContentPane, _TemplatedMixin], { + // summary: + // A ContentPane with an href where (when declared in markup) + // the title is specified as innerHTML rather than as a title attribute. + // description: + // LinkPane is just a ContentPane that is declared in markup similarly + // to an anchor. The anchor's body (the words between `<a>` and `</a>`) + // become the title of the widget (used for TabContainer, AccordionContainer, etc.) + // example: + // | <a href="foo.html">my title</a> + + // I'm using a template because the user may specify the input as + // <a href="foo.html">title</a>, in which case we need to get rid of the + // <a> because we don't want a link. + templateString: '<div class="dijitLinkPane" data-dojo-attach-point="containerNode"></div>', + + postMixInProperties: function(){ + // If user has specified node contents, they become the title + // (the link must be plain text) + if(this.srcNodeRef){ + this.title += this.srcNodeRef.innerHTML; + } + this.inherited(arguments); + }, + + _fillContent: function(){ + // Overrides _Templated._fillContent(). + + // _Templated._fillContent() relocates srcNodeRef innerHTML to templated container node, + // but in our case the srcNodeRef innerHTML is the title, so shouldn't be + // copied + } + }); +}); + +}, +'dijit/form/_ListMouseMixin':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/touch", + "./_ListBase" +], function(declare, event, touch, _ListBase){ + +/*===== +var _ListBase = dijit.form._ListBase; +=====*/ + +// module: +// dijit/form/_ListMouseMixin +// summary: +// a mixin to handle mouse or touch events for a focus-less menu + +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 + + 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"); + }, + + _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)); + }, + + _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); + } + }, + + _onMouseOut: function(/*Event*/ /*===== evt ====*/){ + if(this._hoveredNode){ + this.onUnhover(this._hoveredNode); + if(this._getSelectedAttr() == this._hoveredNode){ + this.onSelect(this._hoveredNode); + } + this._hoveredNode = null; + } + if(this._isDragging){ + this._cancelDrag = (new Date()).getTime() + 1000; // cancel in 1 second if no _onMouseOver fires + } + }, + + _onMouseOver: function(/*Event*/ evt){ + if(this._cancelDrag){ + var time = (new Date()).getTime(); + if(time > this._cancelDrag){ + this._isDragging = false; + } + this._cancelDrag = null; + } + var node = this._getTarget(evt); + if(!node){ return; } + if(this._hoveredNode != node){ + if(this._hoveredNode){ + this._onMouseOut({ target: this._hoveredNode }); + } + if(node && node.parentNode == this.containerNode){ + if(this._isDragging){ + this._setSelectedAttr(node); + }else{ + this._hoveredNode = node; + this.onHover(node); + } + } + } + } +}); + +}); + +}, +'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/cldr/monetary':function(){ +define(["../main"], function(dojo) { + // module: + // dojo/cldr/monetary + // summary: + // TODOC + +dojo.getObject("cldr.monetary", true, dojo); + +dojo.cldr.monetary.getData = function(/*String*/code){ +// summary: A mapping of currency code to currency-specific formatting information. Returns a unique object with properties: places, round. +// code: an [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code + +// from http://www.unicode.org/cldr/data/common/supplemental/supplementalData.xml:supplementalData/currencyData/fractions + + var placesData = { + ADP:0,AFN:0,ALL:0,AMD:0,BHD:3,BIF:0,BYR:0,CLF:0,CLP:0, + COP:0,CRC:0,DJF:0,ESP:0,GNF:0,GYD:0,HUF:0,IDR:0,IQD:0, + IRR:3,ISK:0,ITL:0,JOD:3,JPY:0,KMF:0,KPW:0,KRW:0,KWD:3, + LAK:0,LBP:0,LUF:0,LYD:3,MGA:0,MGF:0,MMK:0,MNT:0,MRO:0, + MUR:0,OMR:3,PKR:0,PYG:0,RSD:0,RWF:0,SLL:0,SOS:0,STD:0, + SYP:0,TMM:0,TND:3,TRL:0,TZS:0,UGX:0,UZS:0,VND:0,VUV:0, + XAF:0,XOF:0,XPF:0,YER:0,ZMK:0,ZWD:0 + }; + + var roundingData = {CHF:5}; + + var places = placesData[code], round = roundingData[code]; + if(typeof places == "undefined"){ places = 2; } + if(typeof round == "undefined"){ round = 0; } + + return {places: places, round: round}; // Object +}; + +return dojo.cldr.monetary; +}); + +}, +'dojo/cookie':function(){ +define(["./_base/kernel", "./regexp"], function(dojo, regexp) { + // module: + // dojo/cookie + // summary: + // TODOC + + +/*===== +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; +} +=====*/ + + +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(); } + + 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.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. + + if(!("cookieEnabled" in navigator)){ + this("__djCookieTest__", "CookiesAllowed"); + navigator.cookieEnabled = this("__djCookieTest__") == "CookiesAllowed"; + if(navigator.cookieEnabled){ + this("__djCookieTest__", "", {expires: -1}); + } + } + return navigator.cookieEnabled; +}; + +return dojo.cookie; +}); + +}, +'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(){ +define([ + "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){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; +=====*/ + +// module: +// dijit/ProgressBar +// summary: +// A progress indication widget, showing the amount completed +// (often the percentage completed) of a task. + + +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> + + // 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: '', + + templateString: template, + + // _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"), + + postMixInProperties: function(){ + this.inherited(arguments); + if(!("value" in this.params)){ + this.value = this.indeterminate ? Infinity : this.progress; + } + }, + + buildRendering: function(){ + this.inherited(arguments); + this.indeterminateHighContrastImage.setAttribute("src", + this._indeterminateHighContrastImagePath.toString()); + this.update(); + }, + + update: function(/*Object?*/attributes){ + // 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 ..." }) + // tags: + // private + + // TODO: deprecate this method and use set() instead + + 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; + } + + 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); + + domClass.toggle(this.domNode, "dijitProgressBarIndeterminate", this.indeterminate); + tip.style.width = (percent * 100) + "%"; + this.onChange(); + }, + + _setValueAttr: function(v){ + this._set("value", v); + if(v == Infinity){ + this.update({indeterminate:true}); + }else{ + this.update({indeterminate:false, progress:v}); + } + }, + + _setLabelAttr: function(label){ + this._set("label", label); + this.update(); + }, + + _setIndeterminateAttr: function(indeterminate){ + // Deprecated, use set("value", ...) instead + this.indeterminate = indeterminate; + this.update(); + }, + + report: function(/*float*/percent){ + // summary: + // Generates message to show inside progress bar (normally indicating amount of task completed). + // May be overridden. + // tags: + // extension + + return this.label ? this.label : + (this.indeterminate ? " " : number.format(percent, { type: "percent", places: this.places, locale: this.lang })); + }, + + onChange: function(){ + // summary: + // Callback fired when progress updates. + // tags: + // extension + } +}); + +}); + +}, +'dijit/form/NumberTextBox':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/_base/lang", // lang.hitch lang.mixin + "dojo/number", // number._realNumberRegexp number.format number.parse number.regexp + "./RangeBoundTextBox" +], function(declare, lang, number, RangeBoundTextBox){ + +/*===== + var RangeBoundTextBox = dijit.form.RangeBoundTextBox; +=====*/ + + // module: + // dijit/form/NumberTextBox + // summary: + // A TextBox for entering numbers, with formatting and range checking + + + /*===== + declare( + "dijit.form.NumberTextBox.__Constraints", + [dijit.form.RangeBoundTextBox.__Constraints, number.__FormatOptions, number.__ParseOptions], { + // summary: + // Specifies both the rules on valid/invalid values (minimum, maximum, + // number of required decimal places), and also formatting options for + // displaying the value when the field is not focused. + // example: + // Minimum/maximum: + // To specify a field between 0 and 120: + // | {min:0,max:120} + // To specify a field that must be an integer: + // | {fractional:false} + // To specify a field where 0 to 3 decimal places are allowed on input: + // | {places:'0,3'} + }); + =====*/ + + var NumberTextBoxMixin = declare("dijit.form.NumberTextBoxMixin", null, { + // summary: + // A mixin for all number textboxes + // tags: + // protected + + // Override ValidationTextBox.regExpGen().... we use a reg-ex generating function rather + // than a straight regexp to deal with locale (plus formatting options too?) + regExpGen: number.regexp, + + /*===== + // constraints: dijit.form.NumberTextBox.__Constraints + // Despite the name, this parameter specifies both constraints on the input + // (including minimum/maximum allowed values) as well as + // formatting options like places (the number of digits to display after + // the decimal point). See `dijit.form.NumberTextBox.__Constraints` for details. + constraints: {}, + ======*/ + + // value: Number + // The value of this NumberTextBox as a Javascript Number (i.e., not a String). + // If the displayed value is blank, the value is NaN, and if the user types in + // an gibberish value (like "hello world"), the value is undefined + // (i.e. get('value') returns undefined). + // + // Symmetrically, set('value', NaN) will clear the displayed value, + // whereas set('value', undefined) will have no effect. + value: NaN, + + // editOptions: [protected] Object + // Properties to mix into constraints when the value is being edited. + // This is here because we edit the number in the format "12345", which is + // different than the display value (ex: "12,345") + editOptions: { pattern: '#.######' }, + + /*===== + _formatter: function(value, options){ + // summary: + // _formatter() is called by format(). It's the base routine for formatting a number, + // as a string, for example converting 12345 into "12,345". + // value: Number + // The number to be converted into a string. + // options: dojo.number.__FormatOptions? + // Formatting options + // tags: + // protected extension + + return "12345"; // String + }, + =====*/ + _formatter: number.format, + + postMixInProperties: function(){ + this.inherited(arguments); + this._set("type", "text"); // in case type="number" was specified which messes up parse/format + }, + + _setConstraintsAttr: function(/*Object*/ constraints){ + var places = typeof constraints.places == "number"? constraints.places : 0; + if(places){ places++; } // decimal rounding errors take away another digit of precision + if(typeof constraints.max != "number"){ + constraints.max = 9 * Math.pow(10, 15-places); + } + if(typeof constraints.min != "number"){ + constraints.min = -9 * Math.pow(10, 15-places); + } + this.inherited(arguments, [ constraints ]); + if(this.focusNode && this.focusNode.value && !isNaN(this.value)){ + this.set('value', this.value); + } + }, + + _onFocus: function(){ + if(this.disabled){ return; } + var val = this.get('value'); + if(typeof val == "number" && !isNaN(val)){ + var formattedValue = this.format(val, this.constraints); + if(formattedValue !== undefined){ + this.textbox.value = formattedValue; + } + } + this.inherited(arguments); + }, + + format: function(/*Number*/ value, /*dojo.number.__FormatOptions*/ constraints){ + // summary: + // Formats the value as a Number, according to constraints. + // tags: + // protected + + var formattedValue = String(value); + if(typeof value != "number"){ return formattedValue; } + if(isNaN(value)){ return ""; } + // check for exponential notation that dojo.number.format chokes on + if(!("rangeCheck" in this && this.rangeCheck(value, constraints)) && constraints.exponent !== false && /\de[-+]?\d/i.test(formattedValue)){ + return formattedValue; + } + if(this.editOptions && this.focused){ + constraints = lang.mixin({}, constraints, this.editOptions); + } + return this._formatter(value, constraints); + }, + + /*===== + _parser: function(value, constraints){ + // summary: + // Parses the string value as a Number, according to constraints. + // value: String + // String representing a number + // constraints: dojo.number.__ParseOptions + // Formatting options + // tags: + // protected + + return 123.45; // Number + }, + =====*/ + _parser: number.parse, + + parse: function(/*String*/ value, /*number.__FormatOptions*/ constraints){ + // summary: + // Replaceable function to convert a formatted string to a number value + // tags: + // protected extension + + var v = this._parser(value, lang.mixin({}, constraints, (this.editOptions && this.focused) ? this.editOptions : {})); + if(this.editOptions && this.focused && isNaN(v)){ + v = this._parser(value, constraints); // parse w/o editOptions: not technically needed but is nice for the user + } + return v; + }, + + _getDisplayedValueAttr: function(){ + var v = this.inherited(arguments); + return isNaN(v) ? this.textbox.value : v; + }, + + filter: function(/*Number*/ value){ + // summary: + // This is called with both the display value (string), and the actual value (a number). + // When called with the actual value it does corrections so that '' etc. are represented as NaN. + // Otherwise it dispatches to the superclass's filter() method. + // + // See `dijit.form.TextBox.filter` for more details. + return (value === null || value === '' || value === undefined) ? NaN : this.inherited(arguments); // set('value', null||''||undefined) should fire onChange(NaN) + }, + + serialize: function(/*Number*/ value, /*Object?*/ options){ + // summary: + // Convert value (a Number) into a canonical string (ie, how the number literal is written in javascript/java/C/etc.) + // tags: + // protected + return (typeof value != "number" || isNaN(value)) ? '' : this.inherited(arguments); + }, + + _setBlurValue: function(){ + var val = lang.hitch(lang.mixin({}, this, { focused: true }), "get")('value'); // parse with editOptions + this._setValueAttr(val, true); + }, + + _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){ + // summary: + // Hook so set('value', ...) works. + if(value !== undefined && formattedValue === undefined){ + formattedValue = String(value); + if(typeof value == "number"){ + if(isNaN(value)){ formattedValue = '' } + // check for exponential notation that number.format chokes on + else if(("rangeCheck" in this && this.rangeCheck(value, this.constraints)) || this.constraints.exponent === false || !/\de[-+]?\d/i.test(formattedValue)){ + formattedValue = undefined; // lets format compute a real string value + } + }else if(!value){ // 0 processed in if branch above, ''|null|undefined flows through here + formattedValue = ''; + value = NaN; + }else{ // non-numeric values + value = undefined; + } + } + this.inherited(arguments, [value, priorityChange, formattedValue]); + }, + + _getValueAttr: function(){ + // summary: + // Hook so get('value') works. + // Returns Number, NaN for '', or undefined for unparseable text + var v = this.inherited(arguments); // returns Number for all values accepted by parse() or NaN for all other displayed values + + // If the displayed value of the textbox is gibberish (ex: "hello world"), this.inherited() above + // returns NaN; this if() branch converts the return value to undefined. + // Returning undefined prevents user text from being overwritten when doing _setValueAttr(_getValueAttr()). + // A blank displayed value is still returned as NaN. + if(isNaN(v) && this.textbox.value !== ''){ + if(this.constraints.exponent !== false && /\de[-+]?\d/i.test(this.textbox.value) && (new RegExp("^"+number._realNumberRegexp(lang.mixin({}, this.constraints))+"$").test(this.textbox.value))){ // check for exponential notation that parse() rejected (erroneously?) + var n = Number(this.textbox.value); + return isNaN(n) ? undefined : n; // return exponential Number or undefined for random text (may not be possible to do with the above RegExp check) + }else{ + return undefined; // gibberish + } + }else{ + return v; // Number or NaN for '' + } + }, + + isValid: function(/*Boolean*/ isFocused){ + // Overrides dijit.form.RangeBoundTextBox.isValid to check that the editing-mode value is valid since + // it may not be formatted according to the regExp validation rules + if(!this.focused || this._isEmpty(this.textbox.value)){ + return this.inherited(arguments); + }else{ + var v = this.get('value'); + if(!isNaN(v) && this.rangeCheck(v, this.constraints)){ + if(this.constraints.exponent !== false && /\de[-+]?\d/i.test(this.textbox.value)){ // exponential, parse doesn't like it + return true; // valid exponential number in range + }else{ + return this.inherited(arguments); + } + }else{ + return false; + } + } + } + }); +/*===== + NumberTextBoxMixin = dijit.form.NumberTextBoxMixin; +=====*/ + + var NumberTextBox = declare("dijit.form.NumberTextBox", [RangeBoundTextBox,NumberTextBoxMixin], { + // summary: + // A TextBox for entering numbers, with formatting and range checking + // description: + // NumberTextBox is a textbox for entering and displaying numbers, supporting + // the following main features: + // + // 1. Enforce minimum/maximum allowed values (as well as enforcing that the user types + // a number rather than a random string) + // 2. NLS support (altering roles of comma and dot as "thousands-separator" and "decimal-point" + // depending on locale). + // 3. Separate modes for editing the value and displaying it, specifically that + // the thousands separator character (typically comma) disappears when editing + // but reappears after the field is blurred. + // 4. Formatting and constraints regarding the number of places (digits after the decimal point) + // allowed on input, and number of places displayed when blurred (see `constraints` parameter). + + baseClass: "dijitTextBox dijitNumberTextBox" + }); + + NumberTextBox.Mixin = NumberTextBoxMixin; // for monkey patching + + return NumberTextBox; +}); + +}, +'dijit/form/TimeTextBox':function(){ +define("dijit/form/TimeTextBox", [ + "dojo/_base/declare", // declare + "dojo/keys", // keys.DOWN_ARROW keys.ENTER keys.ESCAPE keys.TAB keys.UP_ARROW + "dojo/_base/lang", // lang.hitch + "../_TimePicker", + "./_DateTimeTextBox" +], function(declare, keys, lang, _TimePicker, _DateTimeTextBox){ + +/*===== + var _TimePicker = dijit._TimePicker; + var _DateTimeTextBox = dijit.form._DateTimeTextBox; +=====*/ + + // module: + // dijit/form/TimeTextBox + // summary: + // A validating, serializable, range-bound time text box with a drop down time picker + + + /*===== + declare( + "dijit.form.TimeTextBox.__Constraints", + [dijit.form._DateTimeTextBox.__Constraints, dijit._TimePicker.__Constraints] + ); + =====*/ + + return declare("dijit.form.TimeTextBox", _DateTimeTextBox, { + // summary: + // A validating, serializable, range-bound time text box with a drop down time picker + + baseClass: "dijitTextBox dijitComboBox dijitTimeTextBox", + popupClass: _TimePicker, + _selector: "time", + +/*===== + // constraints: dijit.form.TimeTextBox.__Constraints + constraints:{}, +=====*/ + + // value: Date + // The value of this widget as a JavaScript Date object. Note that the date portion implies time zone and daylight savings rules. + // + // Example: + // | new dijit.form.TimeTextBox({value: stamp.fromISOString("T12:59:59", new Date())}) + // + // When passed to the parser in markup, must be specified according to locale-independent + // `stamp.fromISOString` format. + // + // Example: + // | <input data-dojo-type='dijit.form.TimeTextBox' value='T12:34:00'> + value: new Date(""), // value.toString()="NaN" + //FIXME: in markup, you have no control over daylight savings + + _onKey: function(evt){ + if(this.disabled || this.readOnly){ return; } + this.inherited(arguments); + + // If the user has backspaced or typed some numbers, then filter the result list + // by what they typed. Maybe there's a better way to detect this, like _handleOnChange()? + switch(evt.keyCode){ + case keys.ENTER: + case keys.TAB: + case keys.ESCAPE: + case keys.DOWN_ARROW: + case keys.UP_ARROW: + // these keys have special meaning + break; + default: + // setTimeout() because the keystroke hasn't yet appeared in the <input>, + // so the get('displayedValue') call below won't give the result we want. + setTimeout(lang.hitch(this, function(){ + // set this.filterString to the filter to apply to the drop down list; + // it will be used in openDropDown() + var val = this.get('displayedValue'); + this.filterString = (val && !this.parse(val, this.constraints)) ? val.toLowerCase() : ""; + + // close the drop down and reopen it, in order to filter the items shown in the list + // and also since the drop down may need to be repositioned if the number of list items has changed + // and it's being displayed above the <input> + if(this._opened){ + this.closeDropDown(); + } + this.openDropDown(); + }), 0); + } + } + }); +}); + +}, +'dijit/ColorPalette':function(){ +define([ + "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 + +var ColorPalette = declare("dijit.ColorPalette", [_Widget, _TemplatedMixin, _PaletteMixin], { + // summary: + // A keyboard accessible color-picking widget + // description: + // Grid showing various colors, so the user can pick a certain color. + // Can be used standalone, or as a popup. + // + // example: + // | <div data-dojo-type="dijit.ColorPalette"></div> + // + // example: + // | var picker = new dijit.ColorPalette({ },srcNode); + // | picker.startup(); + + + // palette: [const] String + // Size of grid, either "7x10" or "3x4". + palette: "7x10", + + // _palettes: [protected] Map + // This represents the value of the colors. + // The first level is a hashmap of the different palettes available. + // The next two dimensions represent the columns and rows of colors. + _palettes: { + "7x10": [["white", "seashell", "cornsilk", "lemonchiffon","lightyellow", "palegreen", "paleturquoise", "lightcyan", "lavender", "plum"], + ["lightgray", "pink", "bisque", "moccasin", "khaki", "lightgreen", "lightseagreen", "lightskyblue", "cornflowerblue", "violet"], + ["silver", "lightcoral", "sandybrown", "orange", "palegoldenrod", "chartreuse", "mediumturquoise", "skyblue", "mediumslateblue","orchid"], + ["gray", "red", "orangered", "darkorange", "yellow", "limegreen", "darkseagreen", "royalblue", "slateblue", "mediumorchid"], + ["dimgray", "crimson", "chocolate", "coral", "gold", "forestgreen", "seagreen", "blue", "blueviolet", "darkorchid"], + ["darkslategray","firebrick","saddlebrown", "sienna", "olive", "green", "darkcyan", "mediumblue","darkslateblue", "darkmagenta" ], + ["black", "darkred", "maroon", "brown", "darkolivegreen", "darkgreen", "midnightblue", "navy", "indigo", "purple"]], + + "3x4": [["white", "lime", "green", "blue"], + ["silver", "yellow", "fuchsia", "navy"], + ["gray", "red", "purple", "black"]] + }, + + // templateString: String + // The template of this widget. + 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. + this._preparePalette( + this._palettes[this.palette], + i18n.getLocalization("dojo", "colors", this.lang)); + } +}); + +ColorPalette._Color = declare("dijit._Color", Color, { + // summary: + // Object associated with each cell in a ColorPalette palette. + // Implements dijit.Dye. + + // Template for each cell in normal (non-high-contrast mode). Each cell contains a wrapper + // node for showing the border (called dijitPaletteImg for back-compat), and dijitColorPaletteSwatch + // for showing the color. + template: + "<span class='dijitInline dijitPaletteImg'>" + + "<img src='${blankGif}' alt='${alt}' class='dijitColorPaletteSwatch' style='background-color: ${color}'/>" + + "</span>", + + // Template for each cell in high contrast mode. Each cell contains an image with the whole palette, + // but scrolled and clipped to show the correct color only + hcTemplate: + "<span class='dijitInline dijitPaletteImg' style='position: relative; overflow: hidden; height: 12px; width: 14px;'>" + + "<img src='${image}' alt='${alt}' style='position: absolute; left: ${left}px; top: ${top}px; ${size}'/>" + + "</span>", + + // _imagePaths: [protected] Map + // This is stores the path to the palette images used for high-contrast mode display + _imagePaths: { + "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(Color.named[alias]); + }, + + getValue: function(){ + // summary: + // Note that although dijit._Color is initialized with a value like "white" getValue() always + // returns a hex value + return this.toHex(); + }, + + fillCell: function(/*DOMNode*/ cell, /*String*/ blankGif){ + 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, + top: this._row * -20 - 5, + size: this.palette == "7x10" ? "height: 145px; width: 206px" : "height: 64px; width: 86px" + }); + + domConstruct.place(html, cell); + } +}); + + +return ColorPalette; +}); + +}, +'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", +'dijit/form/CurrencyTextBox':function(){ +define([ + "dojo/currency", // currency._mixInDefaults currency.format currency.parse currency.regexp + "dojo/_base/declare", // declare + "dojo/_base/lang", // lang.hitch + "./NumberTextBox" +], function(currency, declare, lang, NumberTextBox){ + +/*===== + var NumberTextBox = dijit.form.NumberTextBox; +=====*/ + + // module: + // dijit/form/CurrencyTextBox + // summary: + // A validating currency textbox + + + /*===== + declare( + "dijit.form.CurrencyTextBox.__Constraints", + [dijit.form.NumberTextBox.__Constraints, currency.__FormatOptions, currency.__ParseOptions], { + // summary: + // Specifies both the rules on valid/invalid values (minimum, maximum, + // number of required decimal places), and also formatting options for + // displaying the value when the field is not focused (currency symbol, + // etc.) + // description: + // Follows the pattern of `dijit.form.NumberTextBox.constraints`. + // In general developers won't need to set this parameter + // example: + // To ensure that the user types in the cents (for example, 1.00 instead of just 1): + // | {fractional:true} + }); + =====*/ + + return declare("dijit.form.CurrencyTextBox", NumberTextBox, { + // summary: + // A validating currency textbox + // description: + // CurrencyTextBox is similar to `dijit.form.NumberTextBox` but has a few + // extra features related to currency: + // + // 1. After specifying the currency type (american dollars, euros, etc.) it automatically + // sets parse/format options such as how many decimal places to show. + // 2. The currency mark (dollar sign, euro mark, etc.) is displayed when the field is blurred + // but erased during editing, so that the user can just enter a plain number. + + // currency: [const] String + // the [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code, a three letter sequence like "USD" + currency: "", + + /*===== + // constraints: dijit.form.CurrencyTextBox.__Constraints + // Despite the name, this parameter specifies both constraints on the input + // (including minimum/maximum allowed values) as well as + // formatting options. See `dijit.form.CurrencyTextBox.__Constraints` for details. + constraints: {}, + ======*/ + + baseClass: "dijitTextBox dijitCurrencyTextBox", + + // Override regExpGen ValidationTextBox.regExpGen().... we use a reg-ex generating function rather + // than a straight regexp to deal with locale (plus formatting options too?) + regExpGen: function(constraints){ + // if focused, accept either currency data or NumberTextBox format + return '(' + (this.focused ? this.inherited(arguments, [ lang.mixin({}, constraints, this.editOptions) ]) + '|' : '') + + currency.regexp(constraints) + ')'; + }, + + // Override NumberTextBox._formatter to deal with currencies, ex: converts "123.45" to "$123.45" + _formatter: currency.format, + + _parser: currency.parse, + + parse: function(/*String*/ value, /*Object*/ constraints){ + // summary: + // Parses string value as a Currency, according to the constraints object + // tags: + // protected extension + var v = this.inherited(arguments); + if(isNaN(v) && /\d+/.test(value)){ // currency parse failed, but it could be because they are using NumberTextBox format so try its parse + v = lang.hitch(lang.mixin({}, this, { _parser: NumberTextBox.prototype._parser }), "inherited")(arguments); + } + return v; + }, + + _setConstraintsAttr: function(/*Object*/ constraints){ + if(!constraints.currency && this.currency){ + constraints.currency = this.currency; + } + this.inherited(arguments, [ currency._mixInDefaults(lang.mixin(constraints, { exponent: false })) ]); // get places + } + }); +}); + +}, +'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/cldr/nls/gregorian':function(){ +define({ root: + +//begin v1.x content +{ + "months-format-narrow": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], + "quarters-standAlone-narrow": [ + "1", + "2", + "3", + "4" + ], + "field-weekday": "Day of the Week", + "dateFormatItem-yQQQ": "y QQQ", + "dateFormatItem-yMEd": "EEE, y-M-d", + "dateFormatItem-MMMEd": "E MMM d", + "eraNarrow": [ + "BCE", + "CE" + ], + "dateTimeFormats-appendItem-Day-Of-Week": "{0} {1}", + "dateFormat-long": "y MMMM d", + "months-format-wide": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], + "dateTimeFormat-medium": "{1} {0}", + "dateFormatItem-EEEd": "d EEE", + "dayPeriods-format-wide-pm": "PM", + "dateFormat-full": "EEEE, y MMMM dd", + "dateFormatItem-Md": "M-d", + "dayPeriods-format-abbr-am": "AM", + "dateTimeFormats-appendItem-Second": "{0} ({2}: {1})", + "field-era": "Era", + "dateFormatItem-yM": "y-M", + "months-standAlone-wide": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], + "timeFormat-short": "HH:mm", + "quarters-format-wide": [ + "Q1", + "Q2", + "Q3", + "Q4" + ], + "timeFormat-long": "HH:mm:ss z", + "field-year": "Year", + "dateFormatItem-yMMM": "y MMM", + "dateFormatItem-yQ": "y Q", + "dateTimeFormats-appendItem-Era": "{0} {1}", + "field-hour": "Hour", + "months-format-abbr": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], + "timeFormat-full": "HH:mm:ss zzzz", + "dateTimeFormats-appendItem-Week": "{0} ({2}: {1})", + "field-day-relative+0": "Today", + "field-day-relative+1": "Tomorrow", + "dateFormatItem-H": "HH", + "months-standAlone-abbr": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], + "quarters-format-abbr": [ + "Q1", + "Q2", + "Q3", + "Q4" + ], + "quarters-standAlone-wide": [ + "Q1", + "Q2", + "Q3", + "Q4" + ], + "dateFormatItem-M": "L", + "days-standAlone-wide": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ], + "timeFormat-medium": "HH:mm:ss", + "dateFormatItem-Hm": "HH:mm", + "quarters-standAlone-abbr": [ + "Q1", + "Q2", + "Q3", + "Q4" + ], + "eraAbbr": [ + "BCE", + "CE" + ], + "field-minute": "Minute", + "field-dayperiod": "Dayperiod", + "days-standAlone-abbr": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ], + "dateFormatItem-d": "d", + "dateFormatItem-ms": "mm:ss", + "quarters-format-narrow": [ + "1", + "2", + "3", + "4" + ], + "field-day-relative+-1": "Yesterday", + "dateFormatItem-h": "h a", + "dateTimeFormat-long": "{1} {0}", + "dayPeriods-format-narrow-am": "AM", + "dateFormatItem-MMMd": "MMM d", + "dateFormatItem-MEd": "E, M-d", + "dateTimeFormat-full": "{1} {0}", + "field-day": "Day", + "days-format-wide": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ], + "field-zone": "Zone", + "dateTimeFormats-appendItem-Day": "{0} ({2}: {1})", + "dateFormatItem-y": "y", + "months-standAlone-narrow": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], + "dateFormatItem-hm": "h:mm a", + "dateTimeFormats-appendItem-Year": "{0} {1}", + "dateTimeFormats-appendItem-Hour": "{0} ({2}: {1})", + "dayPeriods-format-abbr-pm": "PM", + "days-format-abbr": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ], + "eraNames": [ + "BCE", + "CE" + ], + "days-format-narrow": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ], + "days-standAlone-narrow": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ], + "dateFormatItem-MMM": "LLL", + "field-month": "Month", + "dateTimeFormats-appendItem-Quarter": "{0} ({2}: {1})", + "dayPeriods-format-wide-am": "AM", + "dateTimeFormats-appendItem-Month": "{0} ({2}: {1})", + "dateTimeFormats-appendItem-Minute": "{0} ({2}: {1})", + "dateFormat-short": "yyyy-MM-dd", + "field-second": "Second", + "dateFormatItem-yMMMEd": "EEE, y MMM d", + "dateTimeFormats-appendItem-Timezone": "{0} {1}", + "field-week": "Week", + "dateFormat-medium": "y MMM d", + "dayPeriods-format-narrow-pm": "PM", + "dateTimeFormat-short": "{1} {0}", + "dateFormatItem-Hms": "HH:mm:ss", + "dateFormatItem-hms": "h:mm:ss a" +} +//end v1.x content +, + "ar": true, + "ca": true, + "cs": true, + "da": true, + "de": true, + "el": true, + "en": true, + "en-au": true, + "en-ca": true, + "en-gb": true, + "es": true, + "fi": true, + "fr": true, + "fr-ch": true, + "he": true, + "hu": true, + "it": true, + "ja": true, + "ko": true, + "nb": true, + "nl": true, + "pl": true, + "pt": true, + "pt-pt": true, + "ro": true, + "ru": true, + "sk": true, + "sl": true, + "sv": true, + "th": true, + "tr": true, + "zh": true, + "zh-hant": true, + "zh-hk": true, + "zh-tw": true +}); +}, +'url:dijit/form/templates/VerticalSlider.html':"<table class=\"dijit dijitReset dijitSlider dijitSliderV\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"none\" data-dojo-attach-event=\"onkeypress:_onKeyPress,onkeyup:_onKeyUp\"\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerV\"\n\t\t\t><div class=\"dijitSliderIncrementIconV\" style=\"display:none\" data-dojo-attach-point=\"decrementButton\"><span class=\"dijitSliderButtonInner\">+</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><center><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperV dijitSliderTopBumper\" data-dojo-attach-event=\"press:_onClkIncBumper\"></div></center\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td data-dojo-attach-point=\"leftDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationL dijitSliderDecorationV\"></td\n\t\t><td class=\"dijitReset dijitSliderDecorationC\" style=\"height:100%;\"\n\t\t\t><input data-dojo-attach-point=\"valueNode\" type=\"hidden\" ${!nameAttrSetting}\n\t\t\t/><center class=\"dijitReset dijitSliderBarContainerV\" role=\"presentation\" data-dojo-attach-point=\"sliderBarContainer\"\n\t\t\t\t><div role=\"presentation\" data-dojo-attach-point=\"remainingBar\" class=\"dijitSliderBar dijitSliderBarV dijitSliderRemainingBar dijitSliderRemainingBarV\" data-dojo-attach-event=\"press:_onBarClick\"><!--#5629--></div\n\t\t\t\t><div role=\"presentation\" data-dojo-attach-point=\"progressBar\" class=\"dijitSliderBar dijitSliderBarV dijitSliderProgressBar dijitSliderProgressBarV\" data-dojo-attach-event=\"press:_onBarClick\"\n\t\t\t\t\t><div class=\"dijitSliderMoveable dijitSliderMoveableV\" style=\"vertical-align:top;\"\n\t\t\t\t\t\t><div data-dojo-attach-point=\"sliderHandle,focusNode\" class=\"dijitSliderImageHandle dijitSliderImageHandleV\" data-dojo-attach-event=\"press:_onHandleClick\" role=\"slider\" valuemin=\"${minimum}\" valuemax=\"${maximum}\"></div\n\t\t\t\t\t></div\n\t\t\t\t></div\n\t\t\t></center\n\t\t></td\n\t\t><td data-dojo-attach-point=\"containerNode,rightDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationR dijitSliderDecorationV\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><center><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperV dijitSliderBottomBumper\" data-dojo-attach-event=\"press:_onClkDecBumper\"></div></center\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerV\"\n\t\t\t><div class=\"dijitSliderDecrementIconV\" style=\"display:none\" data-dojo-attach-point=\"incrementButton\"><span class=\"dijitSliderButtonInner\">-</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n></table>\n", +'dijit/layout/LayoutContainer':function(){ +define([ + "dojo/_base/kernel", // kernel.deprecated + "dojo/_base/lang", + "dojo/_base/declare", // declare + "../_WidgetBase", + "./_LayoutWidget", + "./utils" // layoutUtils.layoutChildren +], function(kernel, lang, declare, _WidgetBase, _LayoutWidget, layoutUtils){ + +/*===== + var _WidgetBase = dijit._WidgetBase; + var _LayoutWidget = dijit.layout._LayoutWidget; +=====*/ + +// module: +// dijit/layout/LayoutContainer +// summary: +// Deprecated. Use `dijit.layout.BorderContainer` instead. + + +// This argument can be specified for the children of a LayoutContainer. +// 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, { + // layoutAlign: String + // "none", "left", "right", "bottom", "top", and "client". + // See the LayoutContainer description for details on this parameter. + layoutAlign: 'none' +}); + +return declare("dijit.layout.LayoutContainer", _LayoutWidget, { + // summary: + // Deprecated. Use `dijit.layout.BorderContainer` instead. + // + // description: + // Provides Delphi-style panel layout semantics. + // + // A LayoutContainer is a box with a specified size (like style="width: 500px; height: 500px;"), + // that contains children widgets marked with "layoutAlign" of "left", "right", "bottom", "top", and "client". + // It takes it's children marked as left/top/bottom/right, and lays them out along the edges of the box, + // and then it takes the child marked "client" and puts it into the remaining space in the middle. + // + // Left/right positioning is similar to CSS's "float: left" and "float: right", + // and top/bottom positioning would be similar to "float: top" and "float: bottom", if there were such + // CSS. + // + // Note that there can only be one client element, but there can be multiple left, right, top, + // or bottom elements. + // + // example: + // | <style> + // | html, body{ height: 100%; width: 100%; } + // | </style> + // | <div data-dojo-type="dijit.layout.LayoutContainer" style="width: 100%; height: 100%"> + // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="layoutAlign: 'top'">header text</div> + // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="layoutAlign: 'left'" style="width: 200px;">table of contents</div> + // | <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="layoutAlign: 'client'">client area</div> + // | </div> + // + // Lays out each child in the natural order the children occur in. + // Basically each child is laid out into the "remaining space", where "remaining space" is initially + // the content area of this widget, but is reduced to a smaller rectangle each time a child is added. + // tags: + // deprecated + + baseClass: "dijitLayoutContainer", + + constructor: function(){ + kernel.deprecated("dijit.layout.LayoutContainer is deprecated", "use BorderContainer instead", 2.0); + }, + + layout: function(){ + layoutUtils.layoutChildren(this.domNode, this._contentBox, this.getChildren()); + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + this.inherited(arguments); + if(this._started){ + layoutUtils.layoutChildren(this.domNode, this._contentBox, this.getChildren()); + } + }, + + removeChild: function(/*dijit._Widget*/ widget){ + this.inherited(arguments); + if(this._started){ + layoutUtils.layoutChildren(this.domNode, this._contentBox, this.getChildren()); + } + } +}); + +}); + +}, +'dijit/Tooltip':function(){ +define([ + "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; +=====*/ + + // module: + // dijit/Tooltip + // summary: + // Defines dijit.Tooltip widget (to display a tooltip), showTooltip()/hideTooltip(), and _MasterTooltip + + + 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 + + // duration: Integer + // Milliseconds to fade in/fade out + duration: manager.defaultDuration, + + 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. + 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; +}); + +}, +'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/form/VerticalSlider':function(){ +define([ + "dojo/_base/declare", // declare + "./HorizontalSlider", + "dojo/text!./templates/VerticalSlider.html" +], function(declare, HorizontalSlider, template){ + +/*===== + var HorizontalSlider = dijit.form.HorizontalSlider; +=====*/ + + // module: + // dijit/form/VerticalSlider + // summary: + // A form widget that allows one to select a value with a vertically draggable handle + + + return declare("dijit.form.VerticalSlider", HorizontalSlider, { + // summary: + // A form widget that allows one to select a value with a vertically draggable handle + + templateString: template, + _mousePixelCoord: "pageY", + _pixelCount: "h", + _startingPixelCoord: "y", + _handleOffsetCoord: "top", + _progressPixelSize: "height", + + // _descending: Boolean + // Specifies if the slider values go from high-on-top (true), or low-on-top (false) + // TODO: expose this in 1.2 - the css progress/remaining bar classes need to be reversed + _descending: true, + + _isReversed: function(){ + // summary: + // Overrides HorizontalSlider._isReversed. + // Indicates if values are high on top (with low numbers on the bottom). + return this._descending; + } + }); +}); + +}, +'dijit/form/DropDownButton':function(){ +define([ + "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; + } +}); + +}); + +}, +'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", +'dojo/date':function(){ +define(["./_base/kernel", "./_base/lang"], function(dojo, lang) { + // module: + // dojo/date + // summary: + // TODOC + +lang.getObject("date", true, dojo); + +/*===== +dojo.date = { + // summary: Date manipulation utilities +} +=====*/ + +dojo.date.getDaysInMonth = function(/*Date*/dateObject){ + // summary: + // Returns the number of days in the month used by dateObject + var month = dateObject.getMonth(); + var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + if(month == 1 && dojo.date.isLeapYear(dateObject)){ return 29; } // Number + return days[month]; // Number +}; + +dojo.date.isLeapYear = function(/*Date*/dateObject){ + // summary: + // Determines if the year of the dateObject is a leap year + // description: + // Leap years are years with an additional day YYYY-02-29, where the + // year number is a multiple of four with the following exception: If + // a year is a multiple of 100, then it is only a leap year if it is + // also a multiple of 400. For example, 1900 was not a leap year, but + // 2000 is one. + + var year = dateObject.getFullYear(); + return !(year%400) || (!(year%4) && !!(year%100)); // Boolean +}; + +// FIXME: This is not localized +dojo.date.getTimezoneName = function(/*Date*/dateObject){ + // summary: + // Get the user's time zone as provided by the browser + // dateObject: + // Needed because the timezone may vary with time (daylight savings) + // description: + // Try to get time zone info from toString or toLocaleString method of + // the Date object -- UTC offset is not a time zone. See + // http://www.twinsun.com/tz/tz-link.htm Note: results may be + // inconsistent across browsers. + + var str = dateObject.toString(); // Start looking in toString + var tz = ''; // The result -- return empty string if nothing found + var match; + + // First look for something in parentheses -- fast lookup, no regex + var pos = str.indexOf('('); + if(pos > -1){ + tz = str.substring(++pos, str.indexOf(')')); + }else{ + // If at first you don't succeed ... + // If IE knows about the TZ, it appears before the year + // Capital letters or slash before a 4-digit year + // at the end of string + var pat = /([A-Z\/]+) \d{4}$/; + if((match = str.match(pat))){ + tz = match[1]; + }else{ + // Some browsers (e.g. Safari) glue the TZ on the end + // of toLocaleString instead of putting it in toString + str = dateObject.toLocaleString(); + // Capital letters or slash -- end of string, + // after space + pat = / ([A-Z\/]+)$/; + if((match = str.match(pat))){ + tz = match[1]; + } + } + } + + // Make sure it doesn't somehow end up return AM or PM + return (tz == 'AM' || tz == 'PM') ? '' : tz; // String +}; + +// Utility methods to do arithmetic calculations with Dates + +dojo.date.compare = function(/*Date*/date1, /*Date?*/date2, /*String?*/portion){ + // summary: + // Compare two date objects by date, time, or both. + // description: + // Returns 0 if equal, positive if a > b, else negative. + // date1: + // Date object + // date2: + // Date object. If not specified, the current Date is used. + // portion: + // A string indicating the "date" or "time" portion of a Date object. + // Compares both "date" and "time" by default. One of the following: + // "date", "time", "datetime" + + // Extra step required in copy for IE - see #3112 + date1 = new Date(+date1); + date2 = new Date(+(date2 || new Date())); + + if(portion == "date"){ + // Ignore times and compare dates. + date1.setHours(0, 0, 0, 0); + date2.setHours(0, 0, 0, 0); + }else if(portion == "time"){ + // Ignore dates and compare times. + date1.setFullYear(0, 0, 0); + date2.setFullYear(0, 0, 0); + } + + if(date1 > date2){ return 1; } // int + if(date1 < date2){ return -1; } // int + return 0; // int +}; + +dojo.date.add = function(/*Date*/date, /*String*/interval, /*int*/amount){ + // summary: + // Add to a Date in intervals of different size, from milliseconds to years + // date: Date + // Date object to start with + // interval: + // A string representing the interval. One of the following: + // "year", "month", "day", "hour", "minute", "second", + // "millisecond", "quarter", "week", "weekday" + // amount: + // How much to add to the date. + + var sum = new Date(+date); // convert to Number before copying to accomodate IE (#3112) + var fixOvershoot = false; + var property = "Date"; + + switch(interval){ + case "day": + break; + case "weekday": + //i18n FIXME: assumes Saturday/Sunday weekend, but this is not always true. see dojo.cldr.supplemental + + // Divide the increment time span into weekspans plus leftover days + // e.g., 8 days is one 5-day weekspan / and two leftover days + // Can't have zero leftover days, so numbers divisible by 5 get + // a days value of 5, and the remaining days make up the number of weeks + var days, weeks; + var mod = amount % 5; + if(!mod){ + days = (amount > 0) ? 5 : -5; + weeks = (amount > 0) ? ((amount-5)/5) : ((amount+5)/5); + }else{ + days = mod; + weeks = parseInt(amount/5); + } + // Get weekday value for orig date param + var strt = date.getDay(); + // Orig date is Sat / positive incrementer + // Jump over Sun + var adj = 0; + if(strt == 6 && amount > 0){ + adj = 1; + }else if(strt == 0 && amount < 0){ + // Orig date is Sun / negative incrementer + // Jump back over Sat + adj = -1; + } + // Get weekday val for the new date + var trgt = strt + days; + // New date is on Sat or Sun + if(trgt == 0 || trgt == 6){ + adj = (amount > 0) ? 2 : -2; + } + // Increment by number of weeks plus leftover days plus + // weekend adjustments + amount = (7 * weeks) + days + adj; + break; + case "year": + property = "FullYear"; + // Keep increment/decrement from 2/29 out of March + fixOvershoot = true; + break; + case "week": + amount *= 7; + break; + case "quarter": + // Naive quarter is just three months + amount *= 3; + // fallthrough... + case "month": + // Reset to last day of month if you overshoot + fixOvershoot = true; + property = "Month"; + break; +// case "hour": +// case "minute": +// case "second": +// case "millisecond": + default: + property = "UTC"+interval.charAt(0).toUpperCase() + interval.substring(1) + "s"; + } + + if(property){ + sum["set"+property](sum["get"+property]()+amount); + } + + if(fixOvershoot && (sum.getDate() < date.getDate())){ + sum.setDate(0); + } + + return sum; // Date +}; + +dojo.date.difference = function(/*Date*/date1, /*Date?*/date2, /*String?*/interval){ + // summary: + // Get the difference in a specific unit of time (e.g., number of + // months, weeks, days, etc.) between two dates, rounded to the + // nearest integer. + // date1: + // Date object + // date2: + // Date object. If not specified, the current Date is used. + // interval: + // A string representing the interval. One of the following: + // "year", "month", "day", "hour", "minute", "second", + // "millisecond", "quarter", "week", "weekday" + // Defaults to "day". + + date2 = date2 || new Date(); + interval = interval || "day"; + var yearDiff = date2.getFullYear() - date1.getFullYear(); + var delta = 1; // Integer return value + + switch(interval){ + case "quarter": + var m1 = date1.getMonth(); + var m2 = date2.getMonth(); + // Figure out which quarter the months are in + var q1 = Math.floor(m1/3) + 1; + var q2 = Math.floor(m2/3) + 1; + // Add quarters for any year difference between the dates + q2 += (yearDiff * 4); + delta = q2 - q1; + break; + case "weekday": + var days = Math.round(dojo.date.difference(date1, date2, "day")); + var weeks = parseInt(dojo.date.difference(date1, date2, "week")); + var mod = days % 7; + + // Even number of weeks + if(mod == 0){ + days = weeks*5; + }else{ + // Weeks plus spare change (< 7 days) + var adj = 0; + var aDay = date1.getDay(); + var bDay = date2.getDay(); + + weeks = parseInt(days/7); + mod = days % 7; + // Mark the date advanced by the number of + // round weeks (may be zero) + var dtMark = new Date(date1); + dtMark.setDate(dtMark.getDate()+(weeks*7)); + var dayMark = dtMark.getDay(); + + // Spare change days -- 6 or less + if(days > 0){ + switch(true){ + // Range starts on Sat + case aDay == 6: + adj = -1; + break; + // Range starts on Sun + case aDay == 0: + adj = 0; + break; + // Range ends on Sat + case bDay == 6: + adj = -1; + break; + // Range ends on Sun + case bDay == 0: + adj = -2; + break; + // Range contains weekend + case (dayMark + mod) > 5: + adj = -2; + } + }else if(days < 0){ + switch(true){ + // Range starts on Sat + case aDay == 6: + adj = 0; + break; + // Range starts on Sun + case aDay == 0: + adj = 1; + break; + // Range ends on Sat + case bDay == 6: + adj = 2; + break; + // Range ends on Sun + case bDay == 0: + adj = 1; + break; + // Range contains weekend + case (dayMark + mod) < 0: + adj = 2; + } + } + days += adj; + days -= (weeks*2); + } + delta = days; + break; + case "year": + delta = yearDiff; + break; + case "month": + delta = (date2.getMonth() - date1.getMonth()) + (yearDiff * 12); + break; + case "week": + // Truncate instead of rounding + // Don't use Math.floor -- value may be negative + delta = parseInt(dojo.date.difference(date1, date2, "day")/7); + break; + case "day": + delta /= 24; + // fallthrough + case "hour": + delta /= 60; + // fallthrough + case "minute": + delta /= 60; + // fallthrough + case "second": + delta /= 1000; + // fallthrough + case "millisecond": + delta *= date2.getTime() - date1.getTime(); + } + + // Round for fractional values and DST leaps + return Math.round(delta); // Number (integer) +}; + +return dojo.date; +}); + +}, +'dijit/layout/_ContentPaneResizeMixin':function(){ +define([ + "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. + + +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: + // See `dijit.layout._LayoutWidget.startup` for description. + // Although ContentPane doesn't extend _LayoutWidget, it does implement + // the same API. + + if(this._started){ return; } + + var parent = this.getParent(); + this._childOfLayoutWidget = parent && parent.isLayoutContainer; + + // 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(); + }); + } + }, + + _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. + + 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); + }, + + 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(); + } + + this._resizeCalled = true; + + this._scheduleLayout(changeSize, resultSize); + }, + + _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; + } + }, + + _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); + } + + // 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; + }, + + _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: + // 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'); + } + }, + + _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/form/RangeBoundTextBox':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/i18n", // i18n.getLocalization + "./MappedTextBox" +], function(declare, i18n, MappedTextBox){ + +/*===== + var MappedTextBox = dijit.form.MappedTextBox; +=====*/ + + // module: + // dijit/form/RangeBoundTextBox + // summary: + // Base class for textbox form widgets which defines a range of valid values. + + /*===== + 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; + } + =====*/ + + return declare("dijit.form.RangeBoundTextBox", MappedTextBox, { + // summary: + // Base class for textbox form widgets which defines a range of valid values. + + // rangeMessage: String + // The message to display if value is out-of-range + rangeMessage: "", + + /*===== + // constraints: dijit.form.RangeBoundTextBox.__Constraints + constraints: {}, + ======*/ + + 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 + }, + + 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); + }, + + _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; + } + 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(); + }, + + 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 + }, + + 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); + }, + + postMixInProperties: function(){ + this.inherited(arguments); + if(!this.rangeMessage){ + this.messages = i18n.getLocalization("dijit.form", "validate", this.lang); + this.rangeMessage = this.messages.rangeMessage; + } + }, + + _setConstraintsAttr: function(/*Object*/ constraints){ + this.inherited(arguments); + if(this.focusNode){ // not set when called from postMixInProperties + if(this.constraints.min !== undefined){ + this.focusNode.setAttribute("aria-valuemin", this.constraints.min); + }else{ + this.focusNode.removeAttribute("aria-valuemin"); + } + if(this.constraints.max !== undefined){ + this.focusNode.setAttribute("aria-valuemax", this.constraints.max); + }else{ + this.focusNode.removeAttribute("aria-valuemax"); + } + } + }, + + _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){ + // summary: + // Hook so set('value', ...) works. + + this.focusNode.setAttribute("aria-valuenow", value); + this.inherited(arguments); + }, + + 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: Object + // text: String + // tags: + // protected. + } + }); +}); + +}, +'dijit/_editor/RichText':function(){ +define("dijit/_editor/RichText", [ + "dojo/_base/array", // array.forEach array.indexOf array.some + "dojo/_base/config", // config + "dojo/_base/declare", // declare + "dojo/_base/Deferred", // Deferred + "dojo/dom", // dom.byId + "dojo/dom-attr", // domAttr.set or get + "dojo/dom-class", // domClass.add domClass.remove + "dojo/dom-construct", // domConstruct.create domConstruct.destroy domConstruct.place + "dojo/dom-geometry", // domGeometry.getMarginBox domGeometry.position + "dojo/dom-style", // domStyle.getComputedStyle domStyle.set + "dojo/_base/event", // event.stop + "dojo/_base/kernel", // kernel.deprecated + "dojo/keys", // keys.BACKSPACE keys.TAB + "dojo/_base/lang", // lang.clone lang.hitch lang.isArray lang.isFunction lang.isString lang.trim + "dojo/on", // on() + "dojo/query", // query + "dojo/ready", // ready + "dojo/_base/sniff", // has("ie") has("mozilla") has("opera") has("safari") has("webkit") + "dojo/topic", // topic.publish() (publish) + "dojo/_base/unload", // unload + "dojo/_base/url", // url + "dojo/_base/window", // win.body win.doc.body.focus win.doc.createElement win.global.location win.withGlobal + "../_Widget", + "../_CssStateMixin", + "./selection", + "./range", + "./html", + "../focus", + ".." // dijit._scopeName +], function(array, config, declare, Deferred, dom, domAttr, domClass, domConstruct, domGeometry, domStyle, + event, kernel, keys, lang, on, query, ready, has, topic, unload, _Url, win, + _Widget, _CssStateMixin, selectionapi, rangeapi, htmlapi, focus, dijit){ + +/*===== + var _Widget = dijit._Widget; + var _CssStateMixin = dijit._CssStateMixin; +=====*/ + +// module: +// dijit/_editor/RichText +// summary: +// dijit._editor.RichText is the core of dijit.Editor, which provides basic +// WYSIWYG editing features. + +// if you want to allow for rich text saving with back/forward actions, you must add a text area to your page with +// the id==dijit._scopeName + "._editor.RichText.value" (typically "dijit._editor.RichText.value). For example, +// something like this will work: +// +// <textarea id="dijit._editor.RichText.value" style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea> +// + +var RichText = declare("dijit._editor.RichText", [_Widget, _CssStateMixin], { + // summary: + // dijit._editor.RichText is the core of dijit.Editor, which provides basic + // WYSIWYG editing features. + // + // description: + // dijit._editor.RichText is the core of dijit.Editor, which provides basic + // WYSIWYG editing features. It also encapsulates the differences + // of different js engines for various browsers. Do not use this widget + // with an HTML <TEXTAREA> tag, since the browser unescapes XML escape characters, + // like <. This can have unexpected behavior and lead to security issues + // such as scripting attacks. + // + // tags: + // private + + constructor: function(params){ + // contentPreFilters: Function(String)[] + // Pre content filter function register array. + // these filters will be executed before the actual + // editing area gets the html content. + this.contentPreFilters = []; + + // contentPostFilters: Function(String)[] + // post content filter function register array. + // These will be used on the resulting html + // from contentDomPostFilters. The resulting + // content is the final html (returned by getValue()). + this.contentPostFilters = []; + + // contentDomPreFilters: Function(DomNode)[] + // Pre content dom filter function register array. + // These filters are applied after the result from + // contentPreFilters are set to the editing area. + this.contentDomPreFilters = []; + + // contentDomPostFilters: Function(DomNode)[] + // Post content dom filter function register array. + // These filters are executed on the editing area dom. + // The result from these will be passed to contentPostFilters. + this.contentDomPostFilters = []; + + // editingAreaStyleSheets: dojo._URL[] + // array to store all the stylesheets applied to the editing area + this.editingAreaStyleSheets = []; + + // Make a copy of this.events before we start writing into it, otherwise we + // will modify the prototype which leads to bad things on pages w/multiple editors + this.events = [].concat(this.events); + + this._keyHandlers = {}; + + if(params && lang.isString(params.value)){ + this.value = params.value; + } + + this.onLoadDeferred = new Deferred(); + }, + + baseClass: "dijitEditor", + + // inheritWidth: Boolean + // whether to inherit the parent's width or simply use 100% + inheritWidth: false, + + // focusOnLoad: [deprecated] Boolean + // Focus into this widget when the page is loaded + focusOnLoad: false, + + // name: String? + // Specifies the name of a (hidden) <textarea> node on the page that's used to save + // the editor content on page leave. Used to restore editor contents after navigating + // to a new page and then hitting the back button. + name: "", + + // styleSheets: [const] String + // semicolon (";") separated list of css files for the editing area + styleSheets: "", + + // height: String + // Set height to fix the editor at a specific height, with scrolling. + // By default, this is 300px. If you want to have the editor always + // resizes to accommodate the content, use AlwaysShowToolbar plugin + // and set height="". If this editor is used within a layout widget, + // set height="100%". + height: "300px", + + // minHeight: String + // The minimum height that the editor should have. + minHeight: "1em", + + // isClosed: [private] Boolean + isClosed: true, + + // isLoaded: [private] Boolean + isLoaded: false, + + // _SEPARATOR: [private] String + // Used to concat contents from multiple editors into a single string, + // so they can be saved into a single <textarea> node. See "name" attribute. + _SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@", + + // _NAME_CONTENT_SEP: [private] String + // USed to separate name from content. Just a colon isn't safe. + _NAME_CONTENT_SEP: "@@**%%:%%**@@", + + // onLoadDeferred: [readonly] dojo.Deferred + // Deferred which is fired when the editor finishes loading. + // Call myEditor.onLoadDeferred.then(callback) it to be informed + // when the rich-text area initialization is finalized. + onLoadDeferred: null, + + // isTabIndent: Boolean + // Make tab key and shift-tab indent and outdent rather than navigating. + // Caution: sing this makes web pages inaccessible to users unable to use a mouse. + isTabIndent: false, + + // disableSpellCheck: [const] Boolean + // When true, disables the browser's native spell checking, if supported. + // Works only in Firefox. + disableSpellCheck: false, + + postCreate: function(){ + if("textarea" === this.domNode.tagName.toLowerCase()){ + console.warn("RichText should not be used with the TEXTAREA tag. See dijit._editor.RichText docs."); + } + + // Push in the builtin filters now, making them the first executed, but not over-riding anything + // users passed in. See: #6062 + this.contentPreFilters = [lang.hitch(this, "_preFixUrlAttributes")].concat(this.contentPreFilters); + if(has("mozilla")){ + this.contentPreFilters = [this._normalizeFontStyle].concat(this.contentPreFilters); + this.contentPostFilters = [this._removeMozBogus].concat(this.contentPostFilters); + } + if(has("webkit")){ + // Try to clean up WebKit bogus artifacts. The inserted classes + // made by WebKit sometimes messes things up. + this.contentPreFilters = [this._removeWebkitBogus].concat(this.contentPreFilters); + this.contentPostFilters = [this._removeWebkitBogus].concat(this.contentPostFilters); + } + if(has("ie")){ + // IE generates <strong> and <em> but we want to normalize to <b> and <i> + this.contentPostFilters = [this._normalizeFontStyle].concat(this.contentPostFilters); + this.contentDomPostFilters = [lang.hitch(this, this._stripBreakerNodes)].concat(this.contentDomPostFilters); + } + this.inherited(arguments); + + topic.publish(dijit._scopeName + "._editor.RichText::init", this); + this.open(); + this.setupDefaultShortcuts(); + }, + + setupDefaultShortcuts: function(){ + // summary: + // Add some default key handlers + // description: + // Overwrite this to setup your own handlers. The default + // implementation does not use Editor commands, but directly + // executes the builtin commands within the underlying browser + // support. + // tags: + // protected + var exec = lang.hitch(this, function(cmd, arg){ + return function(){ + return !this.execCommand(cmd,arg); + }; + }); + + var ctrlKeyHandlers = { + b: exec("bold"), + i: exec("italic"), + u: exec("underline"), + a: exec("selectall"), + s: function(){ this.save(true); }, + m: function(){ this.isTabIndent = !this.isTabIndent; }, + + "1": exec("formatblock", "h1"), + "2": exec("formatblock", "h2"), + "3": exec("formatblock", "h3"), + "4": exec("formatblock", "h4"), + + "\\": exec("insertunorderedlist") + }; + + if(!has("ie")){ + ctrlKeyHandlers.Z = exec("redo"); //FIXME: undo? + } + + var key; + for(key in ctrlKeyHandlers){ + this.addKeyHandler(key, true, false, ctrlKeyHandlers[key]); + } + }, + + // events: [private] String[] + // events which should be connected to the underlying editing area + events: ["onKeyPress", "onKeyDown", "onKeyUp"], // onClick handled specially + + // captureEvents: [deprecated] String[] + // Events which should be connected to the underlying editing + // area, events in this array will be addListener with + // capture=true. + // TODO: looking at the code I don't see any distinction between events and captureEvents, + // so get rid of this for 2.0 if not sooner + captureEvents: [], + + _editorCommandsLocalized: false, + _localizeEditorCommands: function(){ + // summary: + // When IE is running in a non-English locale, the API actually changes, + // so that we have to say (for example) danraku instead of p (for paragraph). + // Handle that here. + // tags: + // private + if(RichText._editorCommandsLocalized){ + // Use the already generate cache of mappings. + this._local2NativeFormatNames = RichText._local2NativeFormatNames; + this._native2LocalFormatNames = RichText._native2LocalFormatNames; + return; + } + RichText._editorCommandsLocalized = true; + RichText._local2NativeFormatNames = {}; + RichText._native2LocalFormatNames = {}; + this._local2NativeFormatNames = RichText._local2NativeFormatNames; + this._native2LocalFormatNames = RichText._native2LocalFormatNames; + //in IE, names for blockformat is locale dependent, so we cache the values here + + //put p after div, so if IE returns Normal, we show it as paragraph + //We can distinguish p and div if IE returns Normal, however, in order to detect that, + //we have to call this.document.selection.createRange().parentElement() or such, which + //could slow things down. Leave it as it is for now + var formats = ['div', 'p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'address']; + var localhtml = "", format, i=0; + while((format=formats[i++])){ + //append a <br> after each element to separate the elements more reliably + if(format.charAt(1) !== 'l'){ + localhtml += "<"+format+"><span>content</span></"+format+"><br/>"; + }else{ + localhtml += "<"+format+"><li>content</li></"+format+"><br/>"; + } + } + // queryCommandValue returns empty if we hide editNode, so move it out of screen temporary + // Also, IE9 does weird stuff unless we do it inside the editor iframe. + var style = { position: "absolute", top: "0px", zIndex: 10, opacity: 0.01 }; + var div = domConstruct.create('div', {style: style, innerHTML: localhtml}); + win.body().appendChild(div); + + // IE9 has a timing issue with doing this right after setting + // the inner HTML, so put a delay in. + var inject = lang.hitch(this, function(){ + var node = div.firstChild; + while(node){ + try{ + selectionapi.selectElement(node.firstChild); + var nativename = node.tagName.toLowerCase(); + this._local2NativeFormatNames[nativename] = document.queryCommandValue("formatblock"); + this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename; + node = node.nextSibling.nextSibling; + //console.log("Mapped: ", nativename, " to: ", this._local2NativeFormatNames[nativename]); + }catch(e){ /*Sqelch the occasional IE9 error */ } + } + div.parentNode.removeChild(div); + div.innerHTML = ""; + }); + setTimeout(inject, 0); + }, + + open: function(/*DomNode?*/ element){ + // summary: + // Transforms the node referenced in this.domNode into a rich text editing + // node. + // description: + // Sets up the editing area asynchronously. This will result in + // the creation and replacement with an iframe. + // tags: + // private + + if(!this.onLoadDeferred || this.onLoadDeferred.fired >= 0){ + this.onLoadDeferred = new Deferred(); + } + + if(!this.isClosed){ this.close(); } + topic.publish(dijit._scopeName + "._editor.RichText::open", this); + + if(arguments.length === 1 && element.nodeName){ // else unchanged + this.domNode = element; + } + + var dn = this.domNode; + + // "html" will hold the innerHTML of the srcNodeRef and will be used to + // initialize the editor. + var html; + + if(lang.isString(this.value)){ + // Allow setting the editor content programmatically instead of + // relying on the initial content being contained within the target + // domNode. + html = this.value; + delete this.value; + dn.innerHTML = ""; + }else if(dn.nodeName && dn.nodeName.toLowerCase() == "textarea"){ + // if we were created from a textarea, then we need to create a + // new editing harness node. + var ta = (this.textarea = dn); + this.name = ta.name; + html = ta.value; + dn = this.domNode = win.doc.createElement("div"); + dn.setAttribute('widgetId', this.id); + ta.removeAttribute('widgetId'); + dn.cssText = ta.cssText; + dn.className += " " + ta.className; + domConstruct.place(dn, ta, "before"); + var tmpFunc = lang.hitch(this, function(){ + //some browsers refuse to submit display=none textarea, so + //move the textarea off screen instead + domStyle.set(ta, { + display: "block", + position: "absolute", + top: "-1000px" + }); + + if(has("ie")){ //nasty IE bug: abnormal formatting if overflow is not hidden + var s = ta.style; + this.__overflow = s.overflow; + s.overflow = "hidden"; + } + }); + if(has("ie")){ + setTimeout(tmpFunc, 10); + }else{ + tmpFunc(); + } + + if(ta.form){ + var resetValue = ta.value; + this.reset = function(){ + var current = this.getValue(); + if(current !== resetValue){ + this.replaceValue(resetValue); + } + }; + on(ta.form, "submit", lang.hitch(this, function(){ + // Copy value to the <textarea> so it gets submitted along with form. + // FIXME: should we be calling close() here instead? + domAttr.set(ta, 'disabled', this.disabled); // don't submit the value if disabled + ta.value = this.getValue(); + })); + } + }else{ + html = htmlapi.getChildrenHtml(dn); + dn.innerHTML = ""; + } + + this.value = html; + + // If we're a list item we have to put in a blank line to force the + // bullet to nicely align at the top of text + if(dn.nodeName && dn.nodeName === "LI"){ + dn.innerHTML = " <br>"; + } + + // Construct the editor div structure. + this.header = dn.ownerDocument.createElement("div"); + dn.appendChild(this.header); + this.editingArea = dn.ownerDocument.createElement("div"); + dn.appendChild(this.editingArea); + this.footer = dn.ownerDocument.createElement("div"); + dn.appendChild(this.footer); + + if(!this.name){ + this.name = this.id + "_AUTOGEN"; + } + + // User has pressed back/forward button so we lost the text in the editor, but it's saved + // in a hidden <textarea> (which contains the data for all the editors on this page), + // so get editor value from there + if(this.name !== "" && (!config["useXDomain"] || config["allowXdRichTextSave"])){ + var saveTextarea = dom.byId(dijit._scopeName + "._editor.RichText.value"); + if(saveTextarea && saveTextarea.value !== ""){ + var datas = saveTextarea.value.split(this._SEPARATOR), i=0, dat; + while((dat=datas[i++])){ + var data = dat.split(this._NAME_CONTENT_SEP); + if(data[0] === this.name){ + html = data[1]; + datas = datas.splice(i, 1); + saveTextarea.value = datas.join(this._SEPARATOR); + break; + } + } + } + + if(!RichText._globalSaveHandler){ + RichText._globalSaveHandler = {}; + unload.addOnUnload(function(){ + var id; + for(id in RichText._globalSaveHandler){ + var f = RichText._globalSaveHandler[id]; + if(lang.isFunction(f)){ + f(); + } + } + }); + } + RichText._globalSaveHandler[this.id] = lang.hitch(this, "_saveContent"); + } + + this.isClosed = false; + + var ifr = (this.editorObject = this.iframe = win.doc.createElement('iframe')); + ifr.id = this.id+"_iframe"; + this._iframeSrc = this._getIframeDocTxt(); + ifr.style.border = "none"; + ifr.style.width = "100%"; + if(this._layoutMode){ + // iframe should be 100% height, thus getting it's height from surrounding + // <div> (which has the correct height set by Editor) + ifr.style.height = "100%"; + }else{ + if(has("ie") >= 7){ + if(this.height){ + ifr.style.height = this.height; + } + if(this.minHeight){ + ifr.style.minHeight = this.minHeight; + } + }else{ + ifr.style.height = this.height ? this.height : this.minHeight; + } + } + ifr.frameBorder = 0; + ifr._loadFunc = lang.hitch( this, function(w){ + this.window = w; + this.document = this.window.document; + + if(has("ie")){ + this._localizeEditorCommands(); + } + + // Do final setup and set initial contents of editor + this.onLoad(html); + }); + + // Set the iframe's initial (blank) content. + var iframeSrcRef = 'parent.' + dijit._scopeName + '.byId("'+this.id+'")._iframeSrc'; + var s = 'javascript:(function(){try{return ' + iframeSrcRef + '}catch(e){document.open();document.domain="' + + document.domain + '";document.write(' + iframeSrcRef + ');document.close();}})()'; + ifr.setAttribute('src', s); + this.editingArea.appendChild(ifr); + + if(has("safari") <= 4){ + var src = ifr.getAttribute("src"); + if(!src || src.indexOf("javascript") === -1){ + // Safari 4 and earlier sometimes act oddly + // So we have to set it again. + setTimeout(function(){ifr.setAttribute('src', s);},0); + } + } + + // TODO: this is a guess at the default line-height, kinda works + if(dn.nodeName === "LI"){ + dn.lastChild.style.marginTop = "-1.2em"; + } + + domClass.add(this.domNode, this.baseClass); + }, + + //static cache variables shared among all instance of this class + _local2NativeFormatNames: {}, + _native2LocalFormatNames: {}, + + _getIframeDocTxt: function(){ + // summary: + // Generates the boilerplate text of the document inside the iframe (ie, <html><head>...</head><body/></html>). + // Editor content (if not blank) should be added afterwards. + // tags: + // private + var _cs = domStyle.getComputedStyle(this.domNode); + + // The contents inside of <body>. The real contents are set later via a call to setValue(). + var html = ""; + var setBodyId = true; + if(has("ie") || has("webkit") || (!this.height && !has("mozilla"))){ + // In auto-expand mode, need a wrapper div for AlwaysShowToolbar plugin to correctly + // expand/contract the editor as the content changes. + html = "<div id='dijitEditorBody'></div>"; + setBodyId = false; + }else if(has("mozilla")){ + // workaround bug where can't select then delete text (until user types something + // into the editor)... and/or issue where typing doesn't erase selected text + this._cursorToStart = true; + html = " "; // + } + + var font = [ _cs.fontWeight, _cs.fontSize, _cs.fontFamily ].join(" "); + + // line height is tricky - applying a units value will mess things up. + // if we can't get a non-units value, bail out. + var lineHeight = _cs.lineHeight; + if(lineHeight.indexOf("px") >= 0){ + lineHeight = parseFloat(lineHeight)/parseFloat(_cs.fontSize); + // console.debug(lineHeight); + }else if(lineHeight.indexOf("em")>=0){ + lineHeight = parseFloat(lineHeight); + }else{ + // If we can't get a non-units value, just default + // it to the CSS spec default of 'normal'. Seems to + // work better, esp on IE, than '1.0' + lineHeight = "normal"; + } + var userStyle = ""; + var self = this; + this.style.replace(/(^|;)\s*(line-|font-?)[^;]+/ig, function(match){ + match = match.replace(/^;/ig,"") + ';'; + var s = match.split(":")[0]; + if(s){ + s = lang.trim(s); + s = s.toLowerCase(); + var i; + var sC = ""; + for(i = 0; i < s.length; i++){ + var c = s.charAt(i); + switch(c){ + case "-": + i++; + c = s.charAt(i).toUpperCase(); + default: + sC += c; + } + } + domStyle.set(self.domNode, sC, ""); + } + userStyle += match + ';'; + }); + + + // need to find any associated label element and update iframe document title + var label=query('label[for="'+this.id+'"]'); + + return [ + this.isLeftToRight() ? "<html>\n<head>\n" : "<html dir='rtl'>\n<head>\n", + (has("mozilla") && label.length ? "<title>" + label[0].innerHTML + "</title>\n" : ""), + "<meta http-equiv='Content-Type' content='text/html'>\n", + "<style>\n", + "\tbody,html {\n", + "\t\tbackground:transparent;\n", + "\t\tpadding: 1px 0 0 0;\n", + "\t\tmargin: -1px 0 0 0;\n", // remove extraneous vertical scrollbar on safari and firefox + + // Set the html/body sizing. Webkit always needs this, other browsers + // only set it when height is defined (not auto-expanding), otherwise + // scrollers do not appear. + ((has("webkit"))?"\t\twidth: 100%;\n":""), + ((has("webkit"))?"\t\theight: 100%;\n":""), + "\t}\n", + + // TODO: left positioning will cause contents to disappear out of view + // if it gets too wide for the visible area + "\tbody{\n", + "\t\ttop:0px;\n", + "\t\tleft:0px;\n", + "\t\tright:0px;\n", + "\t\tfont:", font, ";\n", + ((this.height||has("opera")) ? "" : "\t\tposition: fixed;\n"), + // FIXME: IE 6 won't understand min-height? + "\t\tmin-height:", this.minHeight, ";\n", + "\t\tline-height:", lineHeight,";\n", + "\t}\n", + "\tp{ margin: 1em 0; }\n", + + // Determine how scrollers should be applied. In autoexpand mode (height = "") no scrollers on y at all. + // But in fixed height mode we want both x/y scrollers. Also, if it's using wrapping div and in auto-expand + // (Mainly IE) we need to kill the y scroller on body and html. + (!setBodyId && !this.height ? "\tbody,html {overflow-y: hidden;}\n" : ""), + "\t#dijitEditorBody{overflow-x: auto; overflow-y:" + (this.height ? "auto;" : "hidden;") + " outline: 0px;}\n", + "\tli > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; }\n", + // Can't set min-height in IE9, it puts layout on li, which puts move/resize handles. + (!has("ie") ? "\tli{ min-height:1.2em; }\n" : ""), + "</style>\n", + this._applyEditingAreaStyleSheets(),"\n", + "</head>\n<body ", + (setBodyId?"id='dijitEditorBody' ":""), + "onload='frameElement._loadFunc(window,document)' style='"+userStyle+"'>", html, "</body>\n</html>" + ].join(""); // String + }, + + _applyEditingAreaStyleSheets: function(){ + // summary: + // apply the specified css files in styleSheets + // tags: + // private + var files = []; + if(this.styleSheets){ + files = this.styleSheets.split(';'); + this.styleSheets = ''; + } + + //empty this.editingAreaStyleSheets here, as it will be filled in addStyleSheet + files = files.concat(this.editingAreaStyleSheets); + this.editingAreaStyleSheets = []; + + var text='', i=0, url; + while((url=files[i++])){ + var abstring = (new _Url(win.global.location, url)).toString(); + this.editingAreaStyleSheets.push(abstring); + text += '<link rel="stylesheet" type="text/css" href="'+abstring+'"/>'; + } + return text; + }, + + addStyleSheet: function(/*dojo._Url*/ uri){ + // summary: + // add an external stylesheet for the editing area + // uri: + // A dojo.uri.Uri pointing to the url of the external css file + var url=uri.toString(); + + //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe + if(url.charAt(0) === '.' || (url.charAt(0) !== '/' && !uri.host)){ + url = (new _Url(win.global.location, url)).toString(); + } + + if(array.indexOf(this.editingAreaStyleSheets, url) > -1){ +// console.debug("dijit._editor.RichText.addStyleSheet: Style sheet "+url+" is already applied"); + return; + } + + this.editingAreaStyleSheets.push(url); + this.onLoadDeferred.addCallback(lang.hitch(this, function(){ + if(this.document.createStyleSheet){ //IE + this.document.createStyleSheet(url); + }else{ //other browser + var head = this.document.getElementsByTagName("head")[0]; + var stylesheet = this.document.createElement("link"); + stylesheet.rel="stylesheet"; + stylesheet.type="text/css"; + stylesheet.href=url; + head.appendChild(stylesheet); + } + })); + }, + + removeStyleSheet: function(/*dojo._Url*/ uri){ + // summary: + // remove an external stylesheet for the editing area + var url=uri.toString(); + //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe + if(url.charAt(0) === '.' || (url.charAt(0) !== '/' && !uri.host)){ + url = (new _Url(win.global.location, url)).toString(); + } + var index = array.indexOf(this.editingAreaStyleSheets, url); + if(index === -1){ +// console.debug("dijit._editor.RichText.removeStyleSheet: Style sheet "+url+" has not been applied"); + return; + } + delete this.editingAreaStyleSheets[index]; + win.withGlobal(this.window,'query', dojo, ['link:[href="'+url+'"]']).orphan(); + }, + + // disabled: Boolean + // The editor is disabled; the text cannot be changed. + disabled: false, + + _mozSettingProps: {'styleWithCSS':false}, + _setDisabledAttr: function(/*Boolean*/ value){ + value = !!value; + this._set("disabled", value); + if(!this.isLoaded){ return; } // this method requires init to be complete + if(has("ie") || has("webkit") || has("opera")){ + var preventIEfocus = has("ie") && (this.isLoaded || !this.focusOnLoad); + if(preventIEfocus){ this.editNode.unselectable = "on"; } + this.editNode.contentEditable = !value; + if(preventIEfocus){ + var _this = this; + setTimeout(function(){ + if(_this.editNode){ // guard in case widget destroyed before timeout + _this.editNode.unselectable = "off"; + } + }, 0); + } + }else{ //moz + try{ + this.document.designMode=(value?'off':'on'); + }catch(e){ return; } // ! _disabledOK + if(!value && this._mozSettingProps){ + var ps = this._mozSettingProps; + var n; + for(n in ps){ + if(ps.hasOwnProperty(n)){ + try{ + this.document.execCommand(n,false,ps[n]); + }catch(e2){} + } + } + } +// this.document.execCommand('contentReadOnly', false, value); +// if(value){ +// this.blur(); //to remove the blinking caret +// } + } + this._disabledOK = true; + }, + +/* Event handlers + *****************/ + + onLoad: function(/*String*/ html){ + // summary: + // Handler after the iframe finishes loading. + // html: String + // Editor contents should be set to this value + // tags: + // protected + + // TODO: rename this to _onLoad, make empty public onLoad() method, deprecate/make protected onLoadDeferred handler? + + if(!this.window.__registeredWindow){ + this.window.__registeredWindow = true; + this._iframeRegHandle = focus.registerIframe(this.iframe); + } + if(!has("ie") && !has("webkit") && (this.height || has("mozilla"))){ + this.editNode=this.document.body; + }else{ + // there's a wrapper div around the content, see _getIframeDocTxt(). + this.editNode=this.document.body.firstChild; + var _this = this; + if(has("ie")){ // #4996 IE wants to focus the BODY tag + this.tabStop = domConstruct.create('div', { tabIndex: -1 }, this.editingArea); + this.iframe.onfocus = function(){ _this.editNode.setActive(); }; + } + } + this.focusNode = this.editNode; // for InlineEditBox + + + var events = this.events.concat(this.captureEvents); + var ap = this.iframe ? this.document : this.editNode; + array.forEach(events, function(item){ + this.connect(ap, item.toLowerCase(), item); + }, this); + + this.connect(ap, "onmouseup", "onClick"); // mouseup in the margin does not generate an onclick event + + if(has("ie")){ // IE contentEditable + this.connect(this.document, "onmousedown", "_onIEMouseDown"); // #4996 fix focus + + // give the node Layout on IE + // TODO: this may no longer be needed, since we've reverted IE to using an iframe, + // not contentEditable. Removing it would also probably remove the need for creating + // the extra <div> in _getIframeDocTxt() + this.editNode.style.zoom = 1.0; + }else{ + this.connect(this.document, "onmousedown", function(){ + // Clear the moveToStart focus, as mouse + // down will set cursor point. Required to properly + // work with selection/position driven plugins and clicks in + // the window. refs: #10678 + delete this._cursorToStart; + }); + } + + if(has("webkit")){ + //WebKit sometimes doesn't fire right on selections, so the toolbar + //doesn't update right. Therefore, help it out a bit with an additional + //listener. A mouse up will typically indicate a display change, so fire this + //and get the toolbar to adapt. Reference: #9532 + this._webkitListener = this.connect(this.document, "onmouseup", "onDisplayChanged"); + this.connect(this.document, "onmousedown", function(e){ + var t = e.target; + if(t && (t === this.document.body || t === this.document)){ + // Since WebKit uses the inner DIV, we need to check and set position. + // See: #12024 as to why the change was made. + setTimeout(lang.hitch(this, "placeCursorAtEnd"), 0); + } + }); + } + + if(has("ie")){ + // Try to make sure 'hidden' elements aren't visible in edit mode (like browsers other than IE + // do). See #9103 + try{ + this.document.execCommand('RespectVisibilityInDesign', true, null); + }catch(e){/* squelch */} + } + + this.isLoaded = true; + + this.set('disabled', this.disabled); // initialize content to editable (or not) + + // Note that setValue() call will only work after isLoaded is set to true (above) + + // Set up a function to allow delaying the setValue until a callback is fired + // This ensures extensions like dijit.Editor have a way to hold the value set + // until plugins load (and do things like register filters). + var setContent = lang.hitch(this, function(){ + this.setValue(html); + if(this.onLoadDeferred){ + this.onLoadDeferred.callback(true); + } + this.onDisplayChanged(); + if(this.focusOnLoad){ + // after the document loads, then set focus after updateInterval expires so that + // onNormalizedDisplayChanged has run to avoid input caret issues + ready(lang.hitch(this, function(){ setTimeout(lang.hitch(this, "focus"), this.updateInterval); })); + } + // Save off the initial content now + this.value = this.getValue(true); + }); + if(this.setValueDeferred){ + this.setValueDeferred.addCallback(setContent); + }else{ + setContent(); + } + }, + + onKeyDown: function(/* Event */ e){ + // summary: + // Handler for onkeydown event + // tags: + // protected + + // we need this event at the moment to get the events from control keys + // such as the backspace. It might be possible to add this to Dojo, so that + // keyPress events can be emulated by the keyDown and keyUp detection. + + if(e.keyCode === keys.TAB && this.isTabIndent ){ + event.stop(e); //prevent tab from moving focus out of editor + + // FIXME: this is a poor-man's indent/outdent. It would be + // better if it added 4 " " chars in an undoable way. + // Unfortunately pasteHTML does not prove to be undoable + if(this.queryCommandEnabled((e.shiftKey ? "outdent" : "indent"))){ + this.execCommand((e.shiftKey ? "outdent" : "indent")); + } + } + if(has("ie")){ + if(e.keyCode == keys.TAB && !this.isTabIndent){ + if(e.shiftKey && !e.ctrlKey && !e.altKey){ + // focus the BODY so the browser will tab away from it instead + this.iframe.focus(); + }else if(!e.shiftKey && !e.ctrlKey && !e.altKey){ + // focus the BODY so the browser will tab away from it instead + this.tabStop.focus(); + } + }else if(e.keyCode === keys.BACKSPACE && this.document.selection.type === "Control"){ + // IE has a bug where if a non-text object is selected in the editor, + // hitting backspace would act as if the browser's back button was + // clicked instead of deleting the object. see #1069 + event.stop(e); + this.execCommand("delete"); + }else if((65 <= e.keyCode && e.keyCode <= 90) || + (e.keyCode>=37 && e.keyCode<=40) // FIXME: get this from connect() instead! + ){ //arrow keys + e.charCode = e.keyCode; + this.onKeyPress(e); + } + } + if(has("ff")){ + if(e.keyCode === keys.PAGE_UP || e.keyCode === keys.PAGE_DOWN ){ + if(this.editNode.clientHeight >= this.editNode.scrollHeight){ + // Stop the event to prevent firefox from trapping the cursor when there is no scroll bar. + e.preventDefault(); + } + } + } + return true; + }, + + onKeyUp: function(/*===== e =====*/){ + // summary: + // Handler for onkeyup event + // tags: + // callback + }, + + setDisabled: function(/*Boolean*/ disabled){ + // summary: + // Deprecated, use set('disabled', ...) instead. + // tags: + // deprecated + kernel.deprecated('dijit.Editor::setDisabled is deprecated','use dijit.Editor::attr("disabled",boolean) instead', 2.0); + this.set('disabled',disabled); + }, + _setValueAttr: function(/*String*/ value){ + // summary: + // Registers that attr("value", foo) should call setValue(foo) + this.setValue(value); + }, + _setDisableSpellCheckAttr: function(/*Boolean*/ disabled){ + if(this.document){ + domAttr.set(this.document.body, "spellcheck", !disabled); + }else{ + // try again after the editor is finished loading + this.onLoadDeferred.addCallback(lang.hitch(this, function(){ + domAttr.set(this.document.body, "spellcheck", !disabled); + })); + } + this._set("disableSpellCheck", disabled); + }, + + onKeyPress: function(e){ + // summary: + // Handle the various key events + // tags: + // protected + + var c = (e.keyChar && e.keyChar.toLowerCase()) || e.keyCode, + handlers = this._keyHandlers[c], + args = arguments; + + if(handlers && !e.altKey){ + array.some(handlers, function(h){ + // treat meta- same as ctrl-, for benefit of mac users + if(!(h.shift ^ e.shiftKey) && !(h.ctrl ^ (e.ctrlKey||e.metaKey))){ + if(!h.handler.apply(this, args)){ + e.preventDefault(); + } + return true; + } + }, this); + } + + // function call after the character has been inserted + if(!this._onKeyHitch){ + this._onKeyHitch = lang.hitch(this, "onKeyPressed"); + } + setTimeout(this._onKeyHitch, 1); + return true; + }, + + addKeyHandler: function(/*String*/ key, /*Boolean*/ ctrl, /*Boolean*/ shift, /*Function*/ handler){ + // summary: + // Add a handler for a keyboard shortcut + // description: + // The key argument should be in lowercase if it is a letter character + // tags: + // protected + if(!lang.isArray(this._keyHandlers[key])){ + this._keyHandlers[key] = []; + } + //TODO: would be nice to make this a hash instead of an array for quick lookups + this._keyHandlers[key].push({ + shift: shift || false, + ctrl: ctrl || false, + handler: handler + }); + }, + + onKeyPressed: function(){ + // summary: + // Handler for after the user has pressed a key, and the display has been updated. + // (Runs on a timer so that it runs after the display is updated) + // tags: + // private + this.onDisplayChanged(/*e*/); // can't pass in e + }, + + onClick: function(/*Event*/ e){ + // summary: + // Handler for when the user clicks. + // tags: + // private + + // console.info('onClick',this._tryDesignModeOn); + this.onDisplayChanged(e); + }, + + _onIEMouseDown: function(){ + // summary: + // IE only to prevent 2 clicks to focus + // tags: + // protected + + if(!this.focused && !this.disabled){ + this.focus(); + } + }, + + _onBlur: function(e){ + // summary: + // Called from focus manager when focus has moved away from this editor + // tags: + // protected + + // console.info('_onBlur') + + this.inherited(arguments); + + var newValue = this.getValue(true); + if(newValue !== this.value){ + this.onChange(newValue); + } + this._set("value", newValue); + }, + + _onFocus: function(/*Event*/ e){ + // summary: + // Called from focus manager when focus has moved into this editor + // tags: + // protected + + // console.info('_onFocus') + if(!this.disabled){ + if(!this._disabledOK){ + this.set('disabled', false); + } + this.inherited(arguments); + } + }, + + // TODO: remove in 2.0 + blur: function(){ + // summary: + // Remove focus from this instance. + // tags: + // deprecated + if(!has("ie") && this.window.document.documentElement && this.window.document.documentElement.focus){ + this.window.document.documentElement.focus(); + }else if(win.doc.body.focus){ + win.doc.body.focus(); + } + }, + + focus: function(){ + // summary: + // Move focus to this editor + if(!this.isLoaded){ + this.focusOnLoad = true; + return; + } + if(this._cursorToStart){ + delete this._cursorToStart; + if(this.editNode.childNodes){ + this.placeCursorAtStart(); // this calls focus() so return + return; + } + } + if(!has("ie")){ + focus.focus(this.iframe); + }else if(this.editNode && this.editNode.focus){ + // editNode may be hidden in display:none div, lets just punt in this case + //this.editNode.focus(); -> causes IE to scroll always (strict and quirks mode) to the top the Iframe + // if we fire the event manually and let the browser handle the focusing, the latest + // cursor position is focused like in FF + this.iframe.fireEvent('onfocus', document.createEventObject()); // createEventObject only in IE + // }else{ + // TODO: should we throw here? + // console.debug("Have no idea how to focus into the editor!"); + } + }, + + // _lastUpdate: 0, + updateInterval: 200, + _updateTimer: null, + onDisplayChanged: function(/*Event*/ /*===== e =====*/){ + // summary: + // This event will be fired every time the display context + // changes and the result needs to be reflected in the UI. + // description: + // If you don't want to have update too often, + // onNormalizedDisplayChanged should be used instead + // tags: + // private + + // var _t=new Date(); + if(this._updateTimer){ + clearTimeout(this._updateTimer); + } + if(!this._updateHandler){ + this._updateHandler = lang.hitch(this,"onNormalizedDisplayChanged"); + } + this._updateTimer = setTimeout(this._updateHandler, this.updateInterval); + + // Technically this should trigger a call to watch("value", ...) registered handlers, + // but getValue() is too slow to call on every keystroke so we don't. + }, + onNormalizedDisplayChanged: function(){ + // summary: + // This event is fired every updateInterval ms or more + // description: + // If something needs to happen immediately after a + // user change, please use onDisplayChanged instead. + // tags: + // private + delete this._updateTimer; + }, + onChange: function(/*===== newContent =====*/){ + // summary: + // This is fired if and only if the editor loses focus and + // the content is changed. + }, + _normalizeCommand: function(/*String*/ cmd, /*Anything?*/argument){ + // summary: + // Used as the advice function to map our + // normalized set of commands to those supported by the target + // browser. + // tags: + // private + + var command = cmd.toLowerCase(); + if(command === "formatblock"){ + if(has("safari") && argument === undefined){ command = "heading"; } + }else if(command === "hilitecolor" && !has("mozilla")){ + command = "backcolor"; + } + + return command; + }, + + _qcaCache: {}, + queryCommandAvailable: function(/*String*/ command){ + // summary: + // Tests whether a command is supported by the host. Clients + // SHOULD check whether a command is supported before attempting + // to use it, behaviour for unsupported commands is undefined. + // command: + // The command to test for + // tags: + // private + + // memoizing version. See _queryCommandAvailable for computing version + var ca = this._qcaCache[command]; + if(ca !== undefined){ return ca; } + return (this._qcaCache[command] = this._queryCommandAvailable(command)); + }, + + _queryCommandAvailable: function(/*String*/ command){ + // summary: + // See queryCommandAvailable(). + // tags: + // private + + var ie = 1; + var mozilla = 1 << 1; + var webkit = 1 << 2; + var opera = 1 << 3; + + function isSupportedBy(browsers){ + return { + ie: Boolean(browsers & ie), + mozilla: Boolean(browsers & mozilla), + webkit: Boolean(browsers & webkit), + opera: Boolean(browsers & opera) + }; + } + + var supportedBy = null; + + switch(command.toLowerCase()){ + case "bold": case "italic": case "underline": + case "subscript": case "superscript": + case "fontname": case "fontsize": + case "forecolor": case "hilitecolor": + case "justifycenter": case "justifyfull": case "justifyleft": + case "justifyright": case "delete": case "selectall": case "toggledir": + supportedBy = isSupportedBy(mozilla | ie | webkit | opera); + break; + + case "createlink": case "unlink": case "removeformat": + case "inserthorizontalrule": case "insertimage": + case "insertorderedlist": case "insertunorderedlist": + case "indent": case "outdent": case "formatblock": + case "inserthtml": case "undo": case "redo": case "strikethrough": case "tabindent": + supportedBy = isSupportedBy(mozilla | ie | opera | webkit); + break; + + case "blockdirltr": case "blockdirrtl": + case "dirltr": case "dirrtl": + case "inlinedirltr": case "inlinedirrtl": + supportedBy = isSupportedBy(ie); + break; + case "cut": case "copy": case "paste": + supportedBy = isSupportedBy( ie | mozilla | webkit); + break; + + case "inserttable": + supportedBy = isSupportedBy(mozilla | ie); + break; + + case "insertcell": case "insertcol": case "insertrow": + case "deletecells": case "deletecols": case "deleterows": + case "mergecells": case "splitcell": + supportedBy = isSupportedBy(ie | mozilla); + break; + + default: return false; + } + + return (has("ie") && supportedBy.ie) || + (has("mozilla") && supportedBy.mozilla) || + (has("webkit") && supportedBy.webkit) || + (has("opera") && supportedBy.opera); // Boolean return true if the command is supported, false otherwise + }, + + execCommand: function(/*String*/ command, argument){ + // summary: + // Executes a command in the Rich Text area + // command: + // The command to execute + // argument: + // An optional argument to the command + // tags: + // protected + var returnValue; + + //focus() is required for IE to work + //In addition, focus() makes sure after the execution of + //the command, the editor receives the focus as expected + this.focus(); + + command = this._normalizeCommand(command, argument); + + if(argument !== undefined){ + if(command === "heading"){ + throw new Error("unimplemented"); + }else if((command === "formatblock") && has("ie")){ + argument = '<'+argument+'>'; + } + } + + //Check to see if we have any over-rides for commands, they will be functions on this + //widget of the form _commandImpl. If we don't, fall through to the basic native + //exec command of the browser. + var implFunc = "_" + command + "Impl"; + if(this[implFunc]){ + returnValue = this[implFunc](argument); + }else{ + argument = arguments.length > 1 ? argument : null; + if(argument || command !== "createlink"){ + returnValue = this.document.execCommand(command, false, argument); + } + } + + this.onDisplayChanged(); + return returnValue; + }, + + queryCommandEnabled: function(/*String*/ command){ + // summary: + // Check whether a command is enabled or not. + // command: + // The command to execute + // tags: + // protected + if(this.disabled || !this._disabledOK){ return false; } + + command = this._normalizeCommand(command); + + //Check to see if we have any over-rides for commands, they will be functions on this + //widget of the form _commandEnabledImpl. If we don't, fall through to the basic native + //command of the browser. + var implFunc = "_" + command + "EnabledImpl"; + + if(this[implFunc]){ + return this[implFunc](command); + }else{ + return this._browserQueryCommandEnabled(command); + } + }, + + queryCommandState: function(command){ + // summary: + // Check the state of a given command and returns true or false. + // tags: + // protected + + if(this.disabled || !this._disabledOK){ return false; } + command = this._normalizeCommand(command); + try{ + return this.document.queryCommandState(command); + }catch(e){ + //Squelch, occurs if editor is hidden on FF 3 (and maybe others.) + return false; + } + }, + + queryCommandValue: function(command){ + // summary: + // Check the value of a given command. This matters most for + // custom selections and complex values like font value setting. + // tags: + // protected + + if(this.disabled || !this._disabledOK){ return false; } + var r; + command = this._normalizeCommand(command); + if(has("ie") && command === "formatblock"){ + r = this._native2LocalFormatNames[this.document.queryCommandValue(command)]; + }else if(has("mozilla") && command === "hilitecolor"){ + var oldValue; + try{ + oldValue = this.document.queryCommandValue("styleWithCSS"); + }catch(e){ + oldValue = false; + } + this.document.execCommand("styleWithCSS", false, true); + r = this.document.queryCommandValue(command); + this.document.execCommand("styleWithCSS", false, oldValue); + }else{ + r = this.document.queryCommandValue(command); + } + return r; + }, + + // Misc. + + _sCall: function(name, args){ + // summary: + // Run the named method of dijit._editor.selection over the + // current editor instance's window, with the passed args. + // tags: + // private + return win.withGlobal(this.window, name, selectionapi, args); + }, + + // FIXME: this is a TON of code duplication. Why? + + placeCursorAtStart: function(){ + // summary: + // Place the cursor at the start of the editing area. + // tags: + // private + + this.focus(); + + //see comments in placeCursorAtEnd + var isvalid=false; + if(has("mozilla")){ + // TODO: Is this branch even necessary? + var first=this.editNode.firstChild; + while(first){ + if(first.nodeType === 3){ + if(first.nodeValue.replace(/^\s+|\s+$/g, "").length>0){ + isvalid=true; + this._sCall("selectElement", [ first ]); + break; + } + }else if(first.nodeType === 1){ + isvalid=true; + var tg = first.tagName ? first.tagName.toLowerCase() : ""; + // Collapse before childless tags. + if(/br|input|img|base|meta|area|basefont|hr|link/.test(tg)){ + this._sCall("selectElement", [ first ]); + }else{ + // Collapse inside tags with children. + this._sCall("selectElementChildren", [ first ]); + } + break; + } + first = first.nextSibling; + } + }else{ + isvalid=true; + this._sCall("selectElementChildren", [ this.editNode ]); + } + if(isvalid){ + this._sCall("collapse", [ true ]); + } + }, + + placeCursorAtEnd: function(){ + // summary: + // Place the cursor at the end of the editing area. + // tags: + // private + + this.focus(); + + //In mozilla, if last child is not a text node, we have to use + // selectElementChildren on this.editNode.lastChild otherwise the + // cursor would be placed at the end of the closing tag of + //this.editNode.lastChild + var isvalid=false; + if(has("mozilla")){ + var last=this.editNode.lastChild; + while(last){ + if(last.nodeType === 3){ + if(last.nodeValue.replace(/^\s+|\s+$/g, "").length>0){ + isvalid=true; + this._sCall("selectElement", [ last ]); + break; + } + }else if(last.nodeType === 1){ + isvalid=true; + if(last.lastChild){ + this._sCall("selectElement", [ last.lastChild ]); + }else{ + this._sCall("selectElement", [ last ]); + } + break; + } + last = last.previousSibling; + } + }else{ + isvalid=true; + this._sCall("selectElementChildren", [ this.editNode ]); + } + if(isvalid){ + this._sCall("collapse", [ false ]); + } + }, + + getValue: function(/*Boolean?*/ nonDestructive){ + // summary: + // Return the current content of the editing area (post filters + // are applied). Users should call get('value') instead. + // nonDestructive: + // defaults to false. Should the post-filtering be run over a copy + // of the live DOM? Most users should pass "true" here unless they + // *really* know that none of the installed filters are going to + // mess up the editing session. + // tags: + // private + if(this.textarea){ + if(this.isClosed || !this.isLoaded){ + return this.textarea.value; + } + } + + return this._postFilterContent(null, nonDestructive); + }, + _getValueAttr: function(){ + // summary: + // Hook to make attr("value") work + return this.getValue(true); + }, + + setValue: function(/*String*/ html){ + // summary: + // This function sets the content. No undo history is preserved. + // Users should use set('value', ...) instead. + // tags: + // deprecated + + // TODO: remove this and getValue() for 2.0, and move code to _setValueAttr() + + if(!this.isLoaded){ + // try again after the editor is finished loading + this.onLoadDeferred.addCallback(lang.hitch(this, function(){ + this.setValue(html); + })); + return; + } + this._cursorToStart = true; + if(this.textarea && (this.isClosed || !this.isLoaded)){ + this.textarea.value=html; + }else{ + html = this._preFilterContent(html); + var node = this.isClosed ? this.domNode : this.editNode; + if(html && has("mozilla") && html.toLowerCase() === "<p></p>"){ + html = "<p> </p>"; // + } + + // Use to avoid webkit problems where editor is disabled until the user clicks it + if(!html && has("webkit")){ + html = " "; // + } + node.innerHTML = html; + this._preDomFilterContent(node); + } + + this.onDisplayChanged(); + this._set("value", this.getValue(true)); + }, + + replaceValue: function(/*String*/ html){ + // summary: + // This function set the content while trying to maintain the undo stack + // (now only works fine with Moz, this is identical to setValue in all + // other browsers) + // tags: + // protected + + if(this.isClosed){ + this.setValue(html); + }else if(this.window && this.window.getSelection && !has("mozilla")){ // Safari + // look ma! it's a totally f'd browser! + this.setValue(html); + }else if(this.window && this.window.getSelection){ // Moz + html = this._preFilterContent(html); + this.execCommand("selectall"); + if(!html){ + this._cursorToStart = true; + html = " "; // + } + this.execCommand("inserthtml", html); + this._preDomFilterContent(this.editNode); + }else if(this.document && this.document.selection){//IE + //In IE, when the first element is not a text node, say + //an <a> tag, when replacing the content of the editing + //area, the <a> tag will be around all the content + //so for now, use setValue for IE too + this.setValue(html); + } + + this._set("value", this.getValue(true)); + }, + + _preFilterContent: function(/*String*/ html){ + // summary: + // Filter the input before setting the content of the editing + // area. DOM pre-filtering may happen after this + // string-based filtering takes place but as of 1.2, this is not + // guaranteed for operations such as the inserthtml command. + // tags: + // private + + var ec = html; + array.forEach(this.contentPreFilters, function(ef){ if(ef){ ec = ef(ec); } }); + return ec; + }, + _preDomFilterContent: function(/*DomNode*/ dom){ + // summary: + // filter the input's live DOM. All filter operations should be + // considered to be "live" and operating on the DOM that the user + // will be interacting with in their editing session. + // tags: + // private + dom = dom || this.editNode; + array.forEach(this.contentDomPreFilters, function(ef){ + if(ef && lang.isFunction(ef)){ + ef(dom); + } + }, this); + }, + + _postFilterContent: function( + /*DomNode|DomNode[]|String?*/ dom, + /*Boolean?*/ nonDestructive){ + // summary: + // filter the output after getting the content of the editing area + // + // description: + // post-filtering allows plug-ins and users to specify any number + // of transforms over the editor's content, enabling many common + // use-cases such as transforming absolute to relative URLs (and + // vice-versa), ensuring conformance with a particular DTD, etc. + // The filters are registered in the contentDomPostFilters and + // contentPostFilters arrays. Each item in the + // contentDomPostFilters array is a function which takes a DOM + // Node or array of nodes as its only argument and returns the + // same. It is then passed down the chain for further filtering. + // The contentPostFilters array behaves the same way, except each + // member operates on strings. Together, the DOM and string-based + // filtering allow the full range of post-processing that should + // be necessaray to enable even the most agressive of post-editing + // conversions to take place. + // + // If nonDestructive is set to "true", the nodes are cloned before + // filtering proceeds to avoid potentially destructive transforms + // to the content which may still needed to be edited further. + // Once DOM filtering has taken place, the serialized version of + // the DOM which is passed is run through each of the + // contentPostFilters functions. + // + // dom: + // a node, set of nodes, which to filter using each of the current + // members of the contentDomPostFilters and contentPostFilters arrays. + // + // nonDestructive: + // defaults to "false". If true, ensures that filtering happens on + // a clone of the passed-in content and not the actual node + // itself. + // + // tags: + // private + + var ec; + if(!lang.isString(dom)){ + dom = dom || this.editNode; + if(this.contentDomPostFilters.length){ + if(nonDestructive){ + dom = lang.clone(dom); + } + array.forEach(this.contentDomPostFilters, function(ef){ + dom = ef(dom); + }); + } + ec = htmlapi.getChildrenHtml(dom); + }else{ + ec = dom; + } + + if(!lang.trim(ec.replace(/^\xA0\xA0*/, '').replace(/\xA0\xA0*$/, '')).length){ + ec = ""; + } + + // if(has("ie")){ + // //removing appended <P> </P> for IE + // ec = ec.replace(/(?:<p> </p>[\n\r]*)+$/i,""); + // } + array.forEach(this.contentPostFilters, function(ef){ + ec = ef(ec); + }); + + return ec; + }, + + _saveContent: function(){ + // summary: + // Saves the content in an onunload event if the editor has not been closed + // tags: + // private + + var saveTextarea = dom.byId(dijit._scopeName + "._editor.RichText.value"); + if(saveTextarea){ + if(saveTextarea.value){ + saveTextarea.value += this._SEPARATOR; + } + saveTextarea.value += this.name + this._NAME_CONTENT_SEP + this.getValue(true); + } + }, + + + escapeXml: function(/*String*/ str, /*Boolean*/ noSingleQuotes){ + // summary: + // Adds escape sequences for special characters in XML. + // Optionally skips escapes for single quotes + // tags: + // private + + str = str.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """); + if(!noSingleQuotes){ + str = str.replace(/'/gm, "'"); + } + return str; // string + }, + + getNodeHtml: function(/* DomNode */ node){ + // summary: + // Deprecated. Use dijit/_editor/html::_getNodeHtml() instead. + // tags: + // deprecated + kernel.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit/_editor/html::getNodeHtml instead', 2); + return htmlapi.getNodeHtml(node); // String + }, + + getNodeChildrenHtml: function(/* DomNode */ dom){ + // summary: + // Deprecated. Use dijit/_editor/html::getChildrenHtml() instead. + // tags: + // deprecated + kernel.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit/_editor/html::getChildrenHtml instead', 2); + return htmlapi.getChildrenHtml(dom); + }, + + close: function(/*Boolean?*/ save){ + // summary: + // Kills the editor and optionally writes back the modified contents to the + // element from which it originated. + // save: + // Whether or not to save the changes. If false, the changes are discarded. + // tags: + // private + + if(this.isClosed){ return; } + + if(!arguments.length){ save = true; } + if(save){ + this._set("value", this.getValue(true)); + } + + // line height is squashed for iframes + // FIXME: why was this here? if(this.iframe){ this.domNode.style.lineHeight = null; } + + if(this.interval){ clearInterval(this.interval); } + + if(this._webkitListener){ + //Cleaup of WebKit fix: #9532 + this.disconnect(this._webkitListener); + delete this._webkitListener; + } + + // Guard against memory leaks on IE (see #9268) + if(has("ie")){ + this.iframe.onfocus = null; + } + this.iframe._loadFunc = null; + + if(this._iframeRegHandle){ + this._iframeRegHandle.remove(); + delete this._iframeRegHandle; + } + + if(this.textarea){ + var s = this.textarea.style; + s.position = ""; + s.left = s.top = ""; + if(has("ie")){ + s.overflow = this.__overflow; + this.__overflow = null; + } + this.textarea.value = this.value; + domConstruct.destroy(this.domNode); + this.domNode = this.textarea; + }else{ + // Note that this destroys the iframe + this.domNode.innerHTML = this.value; + } + delete this.iframe; + + domClass.remove(this.domNode, this.baseClass); + this.isClosed = true; + this.isLoaded = false; + + delete this.editNode; + delete this.focusNode; + + if(this.window && this.window._frameElement){ + this.window._frameElement = null; + } + + this.window = null; + this.document = null; + this.editingArea = null; + this.editorObject = null; + }, + + destroy: function(){ + if(!this.isClosed){ this.close(false); } + if(this._updateTimer){ + clearTimeout(this._updateTimer); + } + this.inherited(arguments); + if(RichText._globalSaveHandler){ + delete RichText._globalSaveHandler[this.id]; + } + }, + + _removeMozBogus: function(/* String */ html){ + // summary: + // Post filter to remove unwanted HTML attributes generated by mozilla + // tags: + // private + return html.replace(/\stype="_moz"/gi, '').replace(/\s_moz_dirty=""/gi, '').replace(/_moz_resizing="(true|false)"/gi,''); // String + }, + _removeWebkitBogus: function(/* String */ html){ + // summary: + // Post filter to remove unwanted HTML attributes generated by webkit + // tags: + // private + html = html.replace(/\sclass="webkit-block-placeholder"/gi, ''); + html = html.replace(/\sclass="apple-style-span"/gi, ''); + // For some reason copy/paste sometime adds extra meta tags for charset on + // webkit (chrome) on mac.They need to be removed. See: #12007" + html = html.replace(/<meta charset=\"utf-8\" \/>/gi, ''); + return html; // String + }, + _normalizeFontStyle: function(/* String */ html){ + // summary: + // Convert 'strong' and 'em' to 'b' and 'i'. + // description: + // Moz can not handle strong/em tags correctly, so to help + // mozilla and also to normalize output, convert them to 'b' and 'i'. + // + // Note the IE generates 'strong' and 'em' rather than 'b' and 'i' + // tags: + // private + return html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2') + .replace(/<(\/)?em([ \>])/gi, '<$1i$2' ); // String + }, + + _preFixUrlAttributes: function(/* String */ html){ + // summary: + // Pre-filter to do fixing to href attributes on <a> and <img> tags + // tags: + // private + return html.replace(/(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi, + '$1$4$2$3$5$2 _djrealurl=$2$3$5$2') + .replace(/(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi, + '$1$4$2$3$5$2 _djrealurl=$2$3$5$2'); // String + }, + + /***************************************************************************** + The following functions implement HTML manipulation commands for various + browser/contentEditable implementations. The goal of them is to enforce + standard behaviors of them. + ******************************************************************************/ + + /*** queryCommandEnabled implementations ***/ + + _browserQueryCommandEnabled: function(command){ + // summary: + // Implementation to call to the native queryCommandEnabled of the browser. + // command: + // The command to check. + // tags: + // protected + if(!command) { return false; } + var elem = has("ie") ? this.document.selection.createRange() : this.document; + try{ + return elem.queryCommandEnabled(command); + }catch(e){ + return false; + } + }, + + _createlinkEnabledImpl: function(/*===== argument =====*/){ + // summary: + // This function implements the test for if the create link + // command should be enabled or not. + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var enabled = true; + if(has("opera")){ + var sel = this.window.getSelection(); + if(sel.isCollapsed){ + enabled = true; + }else{ + enabled = this.document.queryCommandEnabled("createlink"); + } + }else{ + enabled = this._browserQueryCommandEnabled("createlink"); + } + return enabled; + }, + + _unlinkEnabledImpl: function(/*===== argument =====*/){ + // summary: + // This function implements the test for if the unlink + // command should be enabled or not. + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var enabled = true; + if(has("mozilla") || has("webkit")){ + enabled = this._sCall("hasAncestorElement", ["a"]); + }else{ + enabled = this._browserQueryCommandEnabled("unlink"); + } + return enabled; + }, + + _inserttableEnabledImpl: function(/*===== argument =====*/){ + // summary: + // This function implements the test for if the inserttable + // command should be enabled or not. + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var enabled = true; + if(has("mozilla") || has("webkit")){ + enabled = true; + }else{ + enabled = this._browserQueryCommandEnabled("inserttable"); + } + return enabled; + }, + + _cutEnabledImpl: function(/*===== argument =====*/){ + // summary: + // This function implements the test for if the cut + // command should be enabled or not. + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var enabled = true; + if(has("webkit")){ + // WebKit deems clipboard activity as a security threat and natively would return false + var sel = this.window.getSelection(); + if(sel){ sel = sel.toString(); } + enabled = !!sel; + }else{ + enabled = this._browserQueryCommandEnabled("cut"); + } + return enabled; + }, + + _copyEnabledImpl: function(/*===== argument =====*/){ + // summary: + // This function implements the test for if the copy + // command should be enabled or not. + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var enabled = true; + if(has("webkit")){ + // WebKit deems clipboard activity as a security threat and natively would return false + var sel = this.window.getSelection(); + if(sel){ sel = sel.toString(); } + enabled = !!sel; + }else{ + enabled = this._browserQueryCommandEnabled("copy"); + } + return enabled; + }, + + _pasteEnabledImpl: function(/*===== argument =====*/){ + // summary:c + // This function implements the test for if the paste + // command should be enabled or not. + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var enabled = true; + if(has("webkit")){ + return true; + }else{ + enabled = this._browserQueryCommandEnabled("paste"); + } + return enabled; + }, + + /*** execCommand implementations ***/ + + _inserthorizontalruleImpl: function(argument){ + // summary: + // This function implements the insertion of HTML 'HR' tags. + // into a point on the page. IE doesn't to it right, so + // we have to use an alternate form + // argument: + // arguments to the exec command, if any. + // tags: + // protected + if(has("ie")){ + return this._inserthtmlImpl("<hr>"); + } + return this.document.execCommand("inserthorizontalrule", false, argument); + }, + + _unlinkImpl: function(argument){ + // summary: + // This function implements the unlink of an 'a' tag. + // argument: + // arguments to the exec command, if any. + // tags: + // protected + if((this.queryCommandEnabled("unlink")) && (has("mozilla") || has("webkit"))){ + var a = this._sCall("getAncestorElement", [ "a" ]); + this._sCall("selectElement", [ a ]); + return this.document.execCommand("unlink", false, null); + } + return this.document.execCommand("unlink", false, argument); + }, + + _hilitecolorImpl: function(argument){ + // summary: + // This function implements the hilitecolor command + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var returnValue; + var isApplied = this._handleTextColorOrProperties("hilitecolor", argument); + if(!isApplied){ + if(has("mozilla")){ + // mozilla doesn't support hilitecolor properly when useCSS is + // set to false (bugzilla #279330) + this.document.execCommand("styleWithCSS", false, true); + console.log("Executing color command."); + returnValue = this.document.execCommand("hilitecolor", false, argument); + this.document.execCommand("styleWithCSS", false, false); + }else{ + returnValue = this.document.execCommand("hilitecolor", false, argument); + } + } + return returnValue; + }, + + _backcolorImpl: function(argument){ + // summary: + // This function implements the backcolor command + // argument: + // arguments to the exec command, if any. + // tags: + // protected + if(has("ie")){ + // Tested under IE 6 XP2, no problem here, comment out + // IE weirdly collapses ranges when we exec these commands, so prevent it + // var tr = this.document.selection.createRange(); + argument = argument ? argument : null; + } + var isApplied = this._handleTextColorOrProperties("backcolor", argument); + if(!isApplied){ + isApplied = this.document.execCommand("backcolor", false, argument); + } + return isApplied; + }, + + _forecolorImpl: function(argument){ + // summary: + // This function implements the forecolor command + // argument: + // arguments to the exec command, if any. + // tags: + // protected + if(has("ie")){ + // Tested under IE 6 XP2, no problem here, comment out + // IE weirdly collapses ranges when we exec these commands, so prevent it + // var tr = this.document.selection.createRange(); + argument = argument? argument : null; + } + var isApplied = false; + isApplied = this._handleTextColorOrProperties("forecolor", argument); + if(!isApplied){ + isApplied = this.document.execCommand("forecolor", false, argument); + } + return isApplied; + }, + + _inserthtmlImpl: function(argument){ + // summary: + // This function implements the insertion of HTML content into + // a point on the page. + // argument: + // The content to insert, if any. + // tags: + // protected + argument = this._preFilterContent(argument); + var rv = true; + if(has("ie")){ + var insertRange = this.document.selection.createRange(); + if(this.document.selection.type.toUpperCase() === 'CONTROL'){ + var n=insertRange.item(0); + while(insertRange.length){ + insertRange.remove(insertRange.item(0)); + } + n.outerHTML=argument; + }else{ + insertRange.pasteHTML(argument); + } + insertRange.select(); + //insertRange.collapse(true); + }else if(has("mozilla") && !argument.length){ + //mozilla can not inserthtml an empty html to delete current selection + //so we delete the selection instead in this case + this._sCall("remove"); // FIXME + }else{ + rv = this.document.execCommand("inserthtml", false, argument); + } + return rv; + }, + + _boldImpl: function(argument){ + // summary: + // This function implements an over-ride of the bold command. + // argument: + // Not used, operates by selection. + // tags: + // protected + var applied = false; + if(has("ie")){ + this._adaptIESelection(); + applied = this._adaptIEFormatAreaAndExec("bold"); + } + if(!applied){ + applied = this.document.execCommand("bold", false, argument); + } + return applied; + }, + + _italicImpl: function(argument){ + // summary: + // This function implements an over-ride of the italic command. + // argument: + // Not used, operates by selection. + // tags: + // protected + var applied = false; + if(has("ie")){ + this._adaptIESelection(); + applied = this._adaptIEFormatAreaAndExec("italic"); + } + if(!applied){ + applied = this.document.execCommand("italic", false, argument); + } + return applied; + }, + + _underlineImpl: function(argument){ + // summary: + // This function implements an over-ride of the underline command. + // argument: + // Not used, operates by selection. + // tags: + // protected + var applied = false; + if(has("ie")){ + this._adaptIESelection(); + applied = this._adaptIEFormatAreaAndExec("underline"); + } + if(!applied){ + applied = this.document.execCommand("underline", false, argument); + } + return applied; + }, + + _strikethroughImpl: function(argument){ + // summary: + // This function implements an over-ride of the strikethrough command. + // argument: + // Not used, operates by selection. + // tags: + // protected + var applied = false; + if(has("ie")){ + this._adaptIESelection(); + applied = this._adaptIEFormatAreaAndExec("strikethrough"); + } + if(!applied){ + applied = this.document.execCommand("strikethrough", false, argument); + } + return applied; + }, + + _superscriptImpl: function(argument){ + // summary: + // This function implements an over-ride of the superscript command. + // argument: + // Not used, operates by selection. + // tags: + // protected + var applied = false; + if(has("ie")){ + this._adaptIESelection(); + applied = this._adaptIEFormatAreaAndExec("superscript"); + } + if(!applied){ + applied = this.document.execCommand("superscript", false, argument); + } + return applied; + }, + + _subscriptImpl: function(argument){ + // summary: + // This function implements an over-ride of the superscript command. + // argument: + // Not used, operates by selection. + // tags: + // protected + var applied = false; + if(has("ie")){ + this._adaptIESelection(); + applied = this._adaptIEFormatAreaAndExec("subscript"); + + } + if(!applied){ + applied = this.document.execCommand("subscript", false, argument); + } + return applied; + }, + + _fontnameImpl: function(argument){ + // summary: + // This function implements the fontname command + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var isApplied; + if(has("ie")){ + isApplied = this._handleTextColorOrProperties("fontname", argument); + } + if(!isApplied){ + isApplied = this.document.execCommand("fontname", false, argument); + } + return isApplied; + }, + + _fontsizeImpl: function(argument){ + // summary: + // This function implements the fontsize command + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var isApplied; + if(has("ie")){ + isApplied = this._handleTextColorOrProperties("fontsize", argument); + } + if(!isApplied){ + isApplied = this.document.execCommand("fontsize", false, argument); + } + return isApplied; + }, + + _insertorderedlistImpl: function(argument){ + // summary: + // This function implements the insertorderedlist command + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var applied = false; + if(has("ie")){ + applied = this._adaptIEList("insertorderedlist", argument); + } + if(!applied){ + applied = this.document.execCommand("insertorderedlist", false, argument); + } + return applied; + }, + + _insertunorderedlistImpl: function(argument){ + // summary: + // This function implements the insertunorderedlist command + // argument: + // arguments to the exec command, if any. + // tags: + // protected + var applied = false; + if(has("ie")){ + applied = this._adaptIEList("insertunorderedlist", argument); + } + if(!applied){ + applied = this.document.execCommand("insertunorderedlist", false, argument); + } + return applied; + }, + + getHeaderHeight: function(){ + // summary: + // A function for obtaining the height of the header node + return this._getNodeChildrenHeight(this.header); // Number + }, + + getFooterHeight: function(){ + // summary: + // A function for obtaining the height of the footer node + return this._getNodeChildrenHeight(this.footer); // Number + }, + + _getNodeChildrenHeight: function(node){ + // summary: + // An internal function for computing the cumulative height of all child nodes of 'node' + // node: + // The node to process the children of; + var h = 0; + if(node && node.childNodes){ + // IE didn't compute it right when position was obtained on the node directly is some cases, + // so we have to walk over all the children manually. + var i; + for(i = 0; i < node.childNodes.length; i++){ + var size = domGeometry.position(node.childNodes[i]); + h += size.h; + } + } + return h; // Number + }, + + _isNodeEmpty: function(node, startOffset){ + // summary: + // Function to test if a node is devoid of real content. + // node: + // The node to check. + // tags: + // private. + if(node.nodeType === 1/*element*/){ + if(node.childNodes.length > 0){ + return this._isNodeEmpty(node.childNodes[0], startOffset); + } + return true; + }else if(node.nodeType === 3/*text*/){ + return (node.nodeValue.substring(startOffset) === ""); + } + return false; + }, + + _removeStartingRangeFromRange: function(node, range){ + // summary: + // Function to adjust selection range by removing the current + // start node. + // node: + // The node to remove from the starting range. + // range: + // The range to adapt. + // tags: + // private + if(node.nextSibling){ + range.setStart(node.nextSibling,0); + }else{ + var parent = node.parentNode; + while(parent && parent.nextSibling == null){ + //move up the tree until we find a parent that has another node, that node will be the next node + parent = parent.parentNode; + } + if(parent){ + range.setStart(parent.nextSibling,0); + } + } + return range; + }, + + _adaptIESelection: function(){ + // summary: + // Function to adapt the IE range by removing leading 'newlines' + // Needed to fix issue with bold/italics/underline not working if + // range included leading 'newlines'. + // In IE, if a user starts a selection at the very end of a line, + // then the native browser commands will fail to execute correctly. + // To work around the issue, we can remove all empty nodes from + // the start of the range selection. + var selection = rangeapi.getSelection(this.window); + if(selection && selection.rangeCount && !selection.isCollapsed){ + var range = selection.getRangeAt(0); + var firstNode = range.startContainer; + var startOffset = range.startOffset; + + while(firstNode.nodeType === 3/*text*/ && startOffset >= firstNode.length && firstNode.nextSibling){ + //traverse the text nodes until we get to the one that is actually highlighted + startOffset = startOffset - firstNode.length; + firstNode = firstNode.nextSibling; + } + + //Remove the starting ranges until the range does not start with an empty node. + var lastNode=null; + while(this._isNodeEmpty(firstNode, startOffset) && firstNode !== lastNode){ + lastNode =firstNode; //this will break the loop in case we can't find the next sibling + range = this._removeStartingRangeFromRange(firstNode, range); //move the start container to the next node in the range + firstNode = range.startContainer; + startOffset = 0; //start at the beginning of the new starting range + } + selection.removeAllRanges();// this will work as long as users cannot select multiple ranges. I have not been able to do that in the editor. + selection.addRange(range); + } + }, + + _adaptIEFormatAreaAndExec: function(command){ + // summary: + // Function to handle IE's quirkiness regarding how it handles + // format commands on a word. This involves a lit of node splitting + // and format cloning. + // command: + // The format command, needed to check if the desired + // command is true or not. + var selection = rangeapi.getSelection(this.window); + var doc = this.document; + var rs, ret, range, txt, startNode, endNode, breaker, sNode; + if(command && selection && selection.isCollapsed){ + var isApplied = this.queryCommandValue(command); + if(isApplied){ + + // We have to split backwards until we hit the format + var nNames = this._tagNamesForCommand(command); + range = selection.getRangeAt(0); + var fs = range.startContainer; + if(fs.nodeType === 3){ + var offset = range.endOffset; + if(fs.length < offset){ + //We are not looking from the right node, try to locate the correct one + ret = this._adjustNodeAndOffset(rs, offset); + fs = ret.node; + offset = ret.offset; + } + } + var topNode; + while(fs && fs !== this.editNode){ + // We have to walk back and see if this is still a format or not. + // Hm, how do I do this? + var tName = fs.tagName? fs.tagName.toLowerCase() : ""; + if(array.indexOf(nNames, tName) > -1){ + topNode = fs; + break; + } + fs = fs.parentNode; + } + + // Okay, we have a stopping place, time to split things apart. + if(topNode){ + // Okay, we know how far we have to split backwards, so we have to split now. + rs = range.startContainer; + var newblock = doc.createElement(topNode.tagName); + domConstruct.place(newblock, topNode, "after"); + if(rs && rs.nodeType === 3){ + // Text node, we have to split it. + var nodeToMove, tNode; + var endOffset = range.endOffset; + if(rs.length < endOffset){ + //We are not splitting the right node, try to locate the correct one + ret = this._adjustNodeAndOffset(rs, endOffset); + rs = ret.node; + endOffset = ret.offset; + } + + txt = rs.nodeValue; + startNode = doc.createTextNode(txt.substring(0, endOffset)); + var endText = txt.substring(endOffset, txt.length); + if(endText){ + endNode = doc.createTextNode(endText); + } + // Place the split, then remove original nodes. + domConstruct.place(startNode, rs, "before"); + if(endNode){ + breaker = doc.createElement("span"); + breaker.className = "ieFormatBreakerSpan"; + domConstruct.place(breaker, rs, "after"); + domConstruct.place(endNode, breaker, "after"); + endNode = breaker; + } + domConstruct.destroy(rs); + + // Okay, we split the text. Now we need to see if we're + // parented to the block element we're splitting and if + // not, we have to split all the way up. Ugh. + var parentC = startNode.parentNode; + var tagList = []; + var tagData; + while(parentC !== topNode){ + var tg = parentC.tagName; + tagData = {tagName: tg}; + tagList.push(tagData); + + var newTg = doc.createElement(tg); + // Clone over any 'style' data. + if(parentC.style){ + if(newTg.style){ + if(parentC.style.cssText){ + newTg.style.cssText = parentC.style.cssText; + tagData.cssText = parentC.style.cssText; + } + } + } + // If font also need to clone over any font data. + if(parentC.tagName === "FONT"){ + if(parentC.color){ + newTg.color = parentC.color; + tagData.color = parentC.color; + } + if(parentC.face){ + newTg.face = parentC.face; + tagData.face = parentC.face; + } + if(parentC.size){ // this check was necessary on IE + newTg.size = parentC.size; + tagData.size = parentC.size; + } + } + if(parentC.className){ + newTg.className = parentC.className; + tagData.className = parentC.className; + } + + // Now move end node and every sibling + // after it over into the new tag. + if(endNode){ + nodeToMove = endNode; + while(nodeToMove){ + tNode = nodeToMove.nextSibling; + newTg.appendChild(nodeToMove); + nodeToMove = tNode; + } + } + if(newTg.tagName == parentC.tagName){ + breaker = doc.createElement("span"); + breaker.className = "ieFormatBreakerSpan"; + domConstruct.place(breaker, parentC, "after"); + domConstruct.place(newTg, breaker, "after"); + }else{ + domConstruct.place(newTg, parentC, "after"); + } + startNode = parentC; + endNode = newTg; + parentC = parentC.parentNode; + } + + // Lastly, move the split out all the split tags + // to the new block as they should now be split properly. + if(endNode){ + nodeToMove = endNode; + if(nodeToMove.nodeType === 1 || (nodeToMove.nodeType === 3 && nodeToMove.nodeValue)){ + // Non-blank text and non-text nodes need to clear out that blank space + // before moving the contents. + newblock.innerHTML = ""; + } + while(nodeToMove){ + tNode = nodeToMove.nextSibling; + newblock.appendChild(nodeToMove); + nodeToMove = tNode; + } + } + + // We had intermediate tags, we have to now recreate them inbetween the split + // and restore what styles, classnames, etc, we can. + if(tagList.length){ + tagData = tagList.pop(); + var newContTag = doc.createElement(tagData.tagName); + if(tagData.cssText && newContTag.style){ + newContTag.style.cssText = tagData.cssText; + } + if(tagData.className){ + newContTag.className = tagData.className; + } + if(tagData.tagName === "FONT"){ + if(tagData.color){ + newContTag.color = tagData.color; + } + if(tagData.face){ + newContTag.face = tagData.face; + } + if(tagData.size){ + newContTag.size = tagData.size; + } + } + domConstruct.place(newContTag, newblock, "before"); + while(tagList.length){ + tagData = tagList.pop(); + var newTgNode = doc.createElement(tagData.tagName); + if(tagData.cssText && newTgNode.style){ + newTgNode.style.cssText = tagData.cssText; + } + if(tagData.className){ + newTgNode.className = tagData.className; + } + if(tagData.tagName === "FONT"){ + if(tagData.color){ + newTgNode.color = tagData.color; + } + if(tagData.face){ + newTgNode.face = tagData.face; + } + if(tagData.size){ + newTgNode.size = tagData.size; + } + } + newContTag.appendChild(newTgNode); + newContTag = newTgNode; + } + + // Okay, everything is theoretically split apart and removed from the content + // so insert the dummy text to select, select it, then + // clear to position cursor. + sNode = doc.createTextNode("."); + breaker.appendChild(sNode); + newContTag.appendChild(sNode); + win.withGlobal(this.window, lang.hitch(this, function(){ + var newrange = rangeapi.create(); + newrange.setStart(sNode, 0); + newrange.setEnd(sNode, sNode.length); + selection.removeAllRanges(); + selection.addRange(newrange); + selectionapi.collapse(false); + sNode.parentNode.innerHTML = ""; + })); + }else{ + // No extra tags, so we have to insert a breaker point and rely + // on filters to remove it later. + breaker = doc.createElement("span"); + breaker.className="ieFormatBreakerSpan"; + sNode = doc.createTextNode("."); + breaker.appendChild(sNode); + domConstruct.place(breaker, newblock, "before"); + win.withGlobal(this.window, lang.hitch(this, function(){ + var newrange = rangeapi.create(); + newrange.setStart(sNode, 0); + newrange.setEnd(sNode, sNode.length); + selection.removeAllRanges(); + selection.addRange(newrange); + selectionapi.collapse(false); + sNode.parentNode.innerHTML = ""; + })); + } + if(!newblock.firstChild){ + // Empty, we don't need it. Split was at end or similar + // So, remove it. + domConstruct.destroy(newblock); + } + return true; + } + } + return false; + }else{ + range = selection.getRangeAt(0); + rs = range.startContainer; + if(rs && rs.nodeType === 3){ + // Text node, we have to split it. + win.withGlobal(this.window, lang.hitch(this, function(){ + var offset = range.startOffset; + if(rs.length < offset){ + //We are not splitting the right node, try to locate the correct one + ret = this._adjustNodeAndOffset(rs, offset); + rs = ret.node; + offset = ret.offset; + } + txt = rs.nodeValue; + startNode = doc.createTextNode(txt.substring(0, offset)); + var endText = txt.substring(offset); + if(endText !== ""){ + endNode = doc.createTextNode(txt.substring(offset)); + } + // Create a space, we'll select and bold it, so + // the whole word doesn't get bolded + breaker = doc.createElement("span"); + sNode = doc.createTextNode("."); + breaker.appendChild(sNode); + if(startNode.length){ + domConstruct.place(startNode, rs, "after"); + }else{ + startNode = rs; + } + domConstruct.place(breaker, startNode, "after"); + if(endNode){ + domConstruct.place(endNode, breaker, "after"); + } + domConstruct.destroy(rs); + var newrange = rangeapi.create(); + newrange.setStart(sNode, 0); + newrange.setEnd(sNode, sNode.length); + selection.removeAllRanges(); + selection.addRange(newrange); + doc.execCommand(command); + domConstruct.place(breaker.firstChild, breaker, "before"); + domConstruct.destroy(breaker); + newrange.setStart(sNode, 0); + newrange.setEnd(sNode, sNode.length); + selection.removeAllRanges(); + selection.addRange(newrange); + selectionapi.collapse(false); + sNode.parentNode.innerHTML = ""; + })); + return true; + } + } + }else{ + return false; + } + }, + + _adaptIEList: function(command /*===== , argument =====*/){ + // summary: + // This function handles normalizing the IE list behavior as + // much as possible. + // command: + // The list command to execute. + // argument: + // Any additional argument. + // tags: + // private + var selection = rangeapi.getSelection(this.window); + if(selection.isCollapsed){ + // In the case of no selection, lets commonize the behavior and + // make sure that it indents if needed. + if(selection.rangeCount && !this.queryCommandValue(command)){ + var range = selection.getRangeAt(0); + var sc = range.startContainer; + if(sc && sc.nodeType == 3){ + // text node. Lets see if there is a node before it that isn't + // some sort of breaker. + if(!range.startOffset){ + // We're at the beginning of a text area. It may have been br split + // Who knows? In any event, we must create the list manually + // or IE may shove too much into the list element. It seems to + // grab content before the text node too if it's br split. + // Why can't IE work like everyone else? + win.withGlobal(this.window, lang.hitch(this, function(){ + // Create a space, we'll select and bold it, so + // the whole word doesn't get bolded + var lType = "ul"; + if(command === "insertorderedlist"){ + lType = "ol"; + } + var list = domConstruct.create(lType); + var li = domConstruct.create("li", null, list); + domConstruct.place(list, sc, "before"); + // Move in the text node as part of the li. + li.appendChild(sc); + // We need a br after it or the enter key handler + // sometimes throws errors. + domConstruct.create("br", null, list, "after"); + // Okay, now lets move our cursor to the beginning. + var newrange = rangeapi.create(); + newrange.setStart(sc, 0); + newrange.setEnd(sc, sc.length); + selection.removeAllRanges(); + selection.addRange(newrange); + selectionapi.collapse(true); + })); + return true; + } + } + } + } + return false; + }, + + _handleTextColorOrProperties: function(command, argument){ + // summary: + // This function handles appplying text color as best it is + // able to do so when the selection is collapsed, making the + // behavior cross-browser consistent. It also handles the name + // and size for IE. + // command: + // The command. + // argument: + // Any additional arguments. + // tags: + // private + var selection = rangeapi.getSelection(this.window); + var doc = this.document; + var rs, ret, range, txt, startNode, endNode, breaker, sNode; + argument = argument || null; + if(command && selection && selection.isCollapsed){ + if(selection.rangeCount){ + range = selection.getRangeAt(0); + rs = range.startContainer; + if(rs && rs.nodeType === 3){ + // Text node, we have to split it. + win.withGlobal(this.window, lang.hitch(this, function(){ + var offset = range.startOffset; + if(rs.length < offset){ + //We are not splitting the right node, try to locate the correct one + ret = this._adjustNodeAndOffset(rs, offset); + rs = ret.node; + offset = ret.offset; + } + txt = rs.nodeValue; + startNode = doc.createTextNode(txt.substring(0, offset)); + var endText = txt.substring(offset); + if(endText !== ""){ + endNode = doc.createTextNode(txt.substring(offset)); + } + // Create a space, we'll select and bold it, so + // the whole word doesn't get bolded + breaker = domConstruct.create("span"); + sNode = doc.createTextNode("."); + breaker.appendChild(sNode); + // Create a junk node to avoid it trying to stlye the breaker. + // This will get destroyed later. + var extraSpan = domConstruct.create("span"); + breaker.appendChild(extraSpan); + if(startNode.length){ + domConstruct.place(startNode, rs, "after"); + }else{ + startNode = rs; + } + domConstruct.place(breaker, startNode, "after"); + if(endNode){ + domConstruct.place(endNode, breaker, "after"); + } + domConstruct.destroy(rs); + var newrange = rangeapi.create(); + newrange.setStart(sNode, 0); + newrange.setEnd(sNode, sNode.length); + selection.removeAllRanges(); + selection.addRange(newrange); + if(has("webkit")){ + // WebKit is frustrating with positioning the cursor. + // It stinks to have a selected space, but there really + // isn't much choice here. + var style = "color"; + if(command === "hilitecolor" || command === "backcolor"){ + style = "backgroundColor"; + } + domStyle.set(breaker, style, argument); + selectionapi.remove(); + domConstruct.destroy(extraSpan); + breaker.innerHTML = " "; // + selectionapi.selectElement(breaker); + this.focus(); + }else{ + this.execCommand(command, argument); + domConstruct.place(breaker.firstChild, breaker, "before"); + domConstruct.destroy(breaker); + newrange.setStart(sNode, 0); + newrange.setEnd(sNode, sNode.length); + selection.removeAllRanges(); + selection.addRange(newrange); + selectionapi.collapse(false); + sNode.parentNode.removeChild(sNode); + } + })); + return true; + } + } + } + return false; + }, + + _adjustNodeAndOffset: function(/*DomNode*/node, /*Int*/offset){ + // summary: + // In the case there are multiple text nodes in a row the offset may not be within the node. + // If the offset is larger than the node length, it will attempt to find + // the next text sibling until it locates the text node in which the offset refers to + // node: + // The node to check. + // offset: + // The position to find within the text node + // tags: + // private. + while(node.length < offset && node.nextSibling && node.nextSibling.nodeType === 3){ + //Adjust the offset and node in the case of multiple text nodes in a row + offset = offset - node.length; + node = node.nextSibling; + } + return {"node": node, "offset": offset}; + }, + + _tagNamesForCommand: function(command){ + // summary: + // Function to return the tab names that are associated + // with a particular style. + // command: String + // The command to return tags for. + // tags: + // private + if(command === "bold"){ + return ["b", "strong"]; + }else if(command === "italic"){ + return ["i","em"]; + }else if(command === "strikethrough"){ + return ["s", "strike"]; + }else if(command === "superscript"){ + return ["sup"]; + }else if(command === "subscript"){ + return ["sub"]; + }else if(command === "underline"){ + return ["u"]; + } + return []; + }, + + _stripBreakerNodes: function(node){ + // summary: + // Function for stripping out the breaker spans inserted by the formatting command. + // Registered as a filter for IE, handles the breaker spans needed to fix up + // How bold/italic/etc, work when selection is collapsed (single cursor). + win.withGlobal(this.window, lang.hitch(this, function(){ + var breakers = query(".ieFormatBreakerSpan", node); + var i; + for(i = 0; i < breakers.length; i++){ + var b = breakers[i]; + while(b.firstChild){ + domConstruct.place(b.firstChild, b, "before"); + } + domConstruct.destroy(b); + } + })); + return node; + } +}); + +return RichText; + +}); + +}, +'dijit/nls/loading':function(){ +define({ root: +//begin v1.x content +({ + loadingState: "Loading...", + errorState: "Sorry, an error occurred" +}) +//end v1.x content +, +"zh": true, +"zh-tw": true, +"tr": true, +"th": true, +"sv": true, +"sl": true, +"sk": true, +"ru": true, +"ro": true, +"pt": true, +"pt-pt": true, +"pl": true, +"nl": true, +"nb": true, +"ko": true, +"kk": true, +"ja": true, +"it": true, +"hu": true, +"hr": true, +"he": true, +"fr": true, +"fi": true, +"es": true, +"el": true, +"de": true, +"da": true, +"cs": true, +"ca": true, +"az": true, +"ar": true +}); + +}, +'dojo/dnd/Moveable':function(){ +define(["../main", "../Evented", "../touch", "./Mover"], function(dojo, Evented, touch) { + // module: + // dojo/dnd/Moveable + // summary: + // TODOC + + +/*===== +dojo.declare("dojo.dnd.__MoveableArgs", [], { + // handle: Node||String + // A node (or node's id), which is used as a mouse handle. + // If omitted, the node itself is used as a handle. + handle: null, + + // delay: Number + // delay move by this number of pixels + delay: 0, + + // skip: Boolean + // skip move of form elements + skip: false, + + // mover: Object + // a constructor of custom Mover + mover: dojo.dnd.Mover +}); +=====*/ + +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 + // node: Node + // a node (or node's id) to be moved + // params: dojo.dnd.__MoveableArgs? + // optional parameters + this.node = dojo.byId(node); + if(!params){ params = {}; } + this.handle = params.handle ? dojo.byId(params.handle) : null; + if(!this.handle){ this.handle = this.node; } + this.delay = params.delay > 0 ? params.delay : 0; + this.skip = params.skip; + this.mover = params.mover ? params.mover : dojo.dnd.Mover; + this.events = [ + 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") + ]; + }, + + // markup methods + markupFactory: function(params, node, ctor){ + return new ctor(node, params); + }, + + // methods + destroy: function(){ + // summary: + // stops watching for possible move, deletes all references, so the object can be garbage-collected + dojo.forEach(this.events, dojo.disconnect); + this.events = this.node = this.handle = null; + }, + + // mouse event processors + onMouseDown: function(e){ + // summary: + // event processor for onmousedown/ontouchstart, creates a Mover for the node + // e: Event + // mouse/touch event + if(this.skip && dojo.dnd.isFormElement(e)){ return; } + if(this.delay){ + this.events.push( + dojo.connect(this.handle, touch.move, this, "onMouseMove"), + dojo.connect(this.handle, touch.release, this, "onMouseUp") + ); + this._lastX = e.pageX; + this._lastY = e.pageY; + }else{ + this.onDragDetected(e); + } + dojo.stopEvent(e); + }, + onMouseMove: function(e){ + // summary: + // event processor for onmousemove/ontouchmove, used only for delayed drags + // e: Event + // mouse/touch event + if(Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay){ + this.onMouseUp(e); + this.onDragDetected(e); + } + dojo.stopEvent(e); + }, + onMouseUp: function(e){ + // summary: + // event processor for onmouseup, used only for delayed drags + // e: Event + // mouse event + for(var i = 0; i < 2; ++i){ + dojo.disconnect(this.events.pop()); + } + dojo.stopEvent(e); + }, + onSelectStart: function(e){ + // summary: + // event processor for onselectevent and ondragevent + // e: Event + // mouse event + if(!this.skip || !dojo.dnd.isFormElement(e)){ + dojo.stopEvent(e); + } + }, + + // local events + onDragDetected: function(/* Event */ e){ + // summary: + // called when the drag is detected; + // responsible for creation of the mover + new this.mover(this.node, e, this); + }, + onMoveStart: function(/* dojo.dnd.Mover */ mover){ + // summary: + // called before every move operation + dojo.publish("/dnd/move/start", [mover]); + dojo.addClass(dojo.body(), "dojoMove"); + dojo.addClass(this.node, "dojoMoveItem"); + }, + onMoveStop: function(/* dojo.dnd.Mover */ mover){ + // summary: + // called after every move operation + dojo.publish("/dnd/move/stop", [mover]); + dojo.removeClass(dojo.body(), "dojoMove"); + dojo.removeClass(this.node, "dojoMoveItem"); + }, + onFirstMove: function(/* dojo.dnd.Mover */ mover, /* Event */ e){ + // 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){ + // summary: + // called during every move notification; + // should actually move the node; can be overwritten. + this.onMoving(mover, leftTop); + var s = mover.node.style; + s.left = leftTop.l + "px"; + s.top = leftTop.t + "px"; + this.onMoved(mover, leftTop); + }, + 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; +}); + +}, +'dijit/TooltipDialog':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.replace + "dojo/_base/event", // event.stop + "dojo/keys", // keys + "dojo/_base/lang", // lang.hitch + "./focus", + "./layout/ContentPane", + "./_DialogMixin", + "./form/_FormMixin", + "./_TemplatedMixin", + "dojo/text!./templates/TooltipDialog.html", + "." // exports methods to dijit global +], function(declare, domClass, event, keys, lang, + focus, ContentPane, _DialogMixin, _FormMixin, _TemplatedMixin, template, dijit){ + +/*===== + var ContentPane = dijit.layout.ContentPane; + var _DialogMixin = dijit._DialogMixin; + var _FormMixin = dijit.form._FormMixin; + var _TemplatedMixin = dijit._TemplatedMixin; +=====*/ + + // module: + // dijit/TooltipDialog + // summary: + // Pops up a dialog that appears like a Tooltip + + + return declare("dijit.TooltipDialog", + [ContentPane, _TemplatedMixin, _FormMixin, _DialogMixin], { + // summary: + // Pops up a dialog that appears like a Tooltip + + // title: String + // Description of tooltip dialog (required for a11y) + title: "", + + // 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, + + // 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, + + // baseClass: [protected] String + // The root className to use for the various states of this widget + baseClass: "dijitTooltipDialog", + + // _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, + + templateString: template, + + _setTitleAttr: function(/*String*/ title){ + this.containerNode.title = title; + this._set("title", title) + }, + + postCreate: function(){ + this.inherited(arguments); + this.connect(this.containerNode, "onkeypress", "_onKey"); + }, + + 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"); + + domClass.replace(this.domNode, newC, this._currentOrientClass || ""); + this._currentOrientClass = newC; + }, + + focus: function(){ + // summary: + // Focus on first field + this._getFocusItems(this.containerNode); + focus.focus(this._firstFocusItem); + }, + + 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 + + this.orient(this.domNode,pos.aroundCorner, pos.corner); + this._onShow(); // lazy load trigger + }, + + 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(); + }, + + _onKey: function(/*Event*/ evt){ + // summary: + // Handler for keyboard events + // description: + // Keep keyboard focus in dialog; close dialog on escape key + // tags: + // private + + var node = evt.target; + if(evt.charOrCode === keys.TAB){ + this._getFocusItems(this.containerNode); + } + var singleFocusItem = (this._firstFocusItem == this._lastFocusItem); + if(evt.charOrCode == keys.ESCAPE){ + // Use setTimeout to avoid crash on IE, see #10396. + setTimeout(lang.hitch(this, "onCancel"), 0); + event.stop(evt); + }else 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 if(evt.charOrCode === keys.TAB){ + // we want the browser's default tab handling to move focus + // but we don't want the tab to propagate upwards + evt.stopPropagation(); + } + } + }); +}); + +}, +'dojo/nls/colors':function(){ +define({ root: +//begin v1.x content +({ +// local representation of all CSS3 named colors, companion to dojo.colors. To be used where descriptive information +// is required for each color, such as a palette widget, and not for specifying color programatically. + +//Note: due to the SVG 1.0 spec additions, some of these are alternate spellings for the same color e.g. gray vs. gray. +//TODO: should we be using unique rgb values as keys instead and avoid these duplicates, or rely on the caller to do the reverse mapping? +aliceblue: "alice blue", +antiquewhite: "antique white", +aqua: "aqua", +aquamarine: "aquamarine", +azure: "azure", +beige: "beige", +bisque: "bisque", +black: "black", +blanchedalmond: "blanched almond", +blue: "blue", +blueviolet: "blue-violet", +brown: "brown", +burlywood: "burlywood", +cadetblue: "cadet blue", +chartreuse: "chartreuse", +chocolate: "chocolate", +coral: "coral", +cornflowerblue: "cornflower blue", +cornsilk: "cornsilk", +crimson: "crimson", +cyan: "cyan", +darkblue: "dark blue", +darkcyan: "dark cyan", +darkgoldenrod: "dark goldenrod", +darkgray: "dark gray", +darkgreen: "dark green", +darkgrey: "dark gray", // same as darkgray +darkkhaki: "dark khaki", +darkmagenta: "dark magenta", +darkolivegreen: "dark olive green", +darkorange: "dark orange", +darkorchid: "dark orchid", +darkred: "dark red", +darksalmon: "dark salmon", +darkseagreen: "dark sea green", +darkslateblue: "dark slate blue", +darkslategray: "dark slate gray", +darkslategrey: "dark slate gray", // same as darkslategray +darkturquoise: "dark turquoise", +darkviolet: "dark violet", +deeppink: "deep pink", +deepskyblue: "deep sky blue", +dimgray: "dim gray", +dimgrey: "dim gray", // same as dimgray +dodgerblue: "dodger blue", +firebrick: "fire brick", +floralwhite: "floral white", +forestgreen: "forest green", +fuchsia: "fuchsia", +gainsboro: "gainsboro", +ghostwhite: "ghost white", +gold: "gold", +goldenrod: "goldenrod", +gray: "gray", +green: "green", +greenyellow: "green-yellow", +grey: "gray", // same as gray +honeydew: "honeydew", +hotpink: "hot pink", +indianred: "indian red", +indigo: "indigo", +ivory: "ivory", +khaki: "khaki", +lavender: "lavender", +lavenderblush: "lavender blush", +lawngreen: "lawn green", +lemonchiffon: "lemon chiffon", +lightblue: "light blue", +lightcoral: "light coral", +lightcyan: "light cyan", +lightgoldenrodyellow: "light goldenrod yellow", +lightgray: "light gray", +lightgreen: "light green", +lightgrey: "light gray", // same as lightgray +lightpink: "light pink", +lightsalmon: "light salmon", +lightseagreen: "light sea green", +lightskyblue: "light sky blue", +lightslategray: "light slate gray", +lightslategrey: "light slate gray", // same as lightslategray +lightsteelblue: "light steel blue", +lightyellow: "light yellow", +lime: "lime", +limegreen: "lime green", +linen: "linen", +magenta: "magenta", +maroon: "maroon", +mediumaquamarine: "medium aquamarine", +mediumblue: "medium blue", +mediumorchid: "medium orchid", +mediumpurple: "medium purple", +mediumseagreen: "medium sea green", +mediumslateblue: "medium slate blue", +mediumspringgreen: "medium spring green", +mediumturquoise: "medium turquoise", +mediumvioletred: "medium violet-red", +midnightblue: "midnight blue", +mintcream: "mint cream", +mistyrose: "misty rose", +moccasin: "moccasin", +navajowhite: "navajo white", +navy: "navy", +oldlace: "old lace", +olive: "olive", +olivedrab: "olive drab", +orange: "orange", +orangered: "orange red", +orchid: "orchid", +palegoldenrod: "pale goldenrod", +palegreen: "pale green", +paleturquoise: "pale turquoise", +palevioletred: "pale violet-red", +papayawhip: "papaya whip", +peachpuff: "peach puff", +peru: "peru", +pink: "pink", +plum: "plum", +powderblue: "powder blue", +purple: "purple", +red: "red", +rosybrown: "rosy brown", +royalblue: "royal blue", +saddlebrown: "saddle brown", +salmon: "salmon", +sandybrown: "sandy brown", +seagreen: "sea green", +seashell: "seashell", +sienna: "sienna", +silver: "silver", +skyblue: "sky blue", +slateblue: "slate blue", +slategray: "slate gray", +slategrey: "slate gray", // same as slategray +snow: "snow", +springgreen: "spring green", +steelblue: "steel blue", +tan: "tan", +teal: "teal", +thistle: "thistle", +tomato: "tomato", +transparent: "transparent", +turquoise: "turquoise", +violet: "violet", +wheat: "wheat", +white: "white", +whitesmoke: "white smoke", +yellow: "yellow", +yellowgreen: "yellow green" +}) +//end v1.x content +, +"zh": true, +"zh-tw": true, +"tr": true, +"th": true, +"sv": true, +"sl": true, +"sk": true, +"ru": true, +"ro": true, +"pt": true, +"pt-pt": true, +"pl": true, +"nl": true, +"nb": true, +"ko": true, +"kk": true, +"ja": true, +"it": true, +"hu": true, +"hr": true, +"he": true, +"fr": true, +"fi": true, +"es": true, +"el": true, +"de": true, +"da": true, +"cs": true, +"ca": true, +"az": true, +"ar": true +}); + +}, +'dojo/store/util/SimpleQueryEngine':function(){ +define(["../../_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; +}; +}); + +}, +'dojo/cldr/nls/number':function(){ +define({ root: + +//begin v1.x content +{ + "scientificFormat": "#E0", + "currencySpacing-afterCurrency-currencyMatch": "[:letter:]", + "infinity": "∞", + "list": ";", + "percentSign": "%", + "minusSign": "-", + "currencySpacing-beforeCurrency-surroundingMatch": "[:digit:]", + "decimalFormat-short": "000T", + "currencySpacing-afterCurrency-insertBetween": " ", + "nan": "NaN", + "nativeZeroDigit": "0", + "plusSign": "+", + "currencySpacing-afterCurrency-surroundingMatch": "[:digit:]", + "currencySpacing-beforeCurrency-currencyMatch": "[:letter:]", + "currencyFormat": "¤ #,##0.00", + "perMille": "‰", + "group": ",", + "percentFormat": "#,##0%", + "decimalFormat": "#,##0.###", + "decimal": ".", + "patternDigit": "#", + "currencySpacing-beforeCurrency-insertBetween": " ", + "exponential": "E" +} +//end v1.x content +, + "ar": true, + "ca": true, + "cs": true, + "da": true, + "de": true, + "el": true, + "en": true, + "en-au": true, + "en-gb": true, + "es": true, + "fi": true, + "fr": true, + "fr-ch": true, + "he": true, + "hu": true, + "it": true, + "ja": true, + "ko": true, + "nb": true, + "nl": true, + "pl": true, + "pt": true, + "pt-pt": true, + "ro": true, + "ru": true, + "sk": true, + "sl": true, + "sv": true, + "th": true, + "tr": true, + "zh": true, + "zh-hant": true, + "zh-hk": true +}); +}, +'dijit/form/_ExpandingTextAreaMixin':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/dom-construct", // domConstruct.create + "dojo/_base/lang", // lang.hitch + "dojo/_base/window" // win.body +], function(declare, domConstruct, lang, win){ + + // module: + // dijit/form/_ExpandingTextAreaMixin + // summary: + // Mixin for textarea widgets to add auto-expanding capability + + // feature detection + var needsHelpShrinking; + + return declare("dijit.form._ExpandingTextAreaMixin", null, { + // summary: + // Mixin for textarea widgets to add auto-expanding capability + + _setValueAttr: function(){ + this.inherited(arguments); + this.resize(); + }, + + postCreate: function(){ + this.inherited(arguments); + var textarea = this.textbox; + + if(needsHelpShrinking == undefined){ + var te = domConstruct.create('textarea', {rows:"5", cols:"20", value: ' ', style: {zoom:1, overflow:'hidden', visibility:'hidden', position:'absolute', border:"0px solid black", padding:"0px"}}, win.body(), "last"); + needsHelpShrinking = te.scrollHeight >= te.clientHeight; + win.body().removeChild(te); + } + this.connect(textarea, "onscroll", "_resizeLater"); + this.connect(textarea, "onresize", "_resizeLater"); + this.connect(textarea, "onfocus", "_resizeLater"); + textarea.style.overflowY = "hidden"; + this._estimateHeight(); + this._resizeLater(); + }, + + _onInput: function(e){ + this.inherited(arguments); + this.resize(); + }, + + _estimateHeight: function(){ + // summary: + // Approximate the height when the textarea is invisible with the number of lines in the text. + // Fails when someone calls setValue with a long wrapping line, but the layout fixes itself when the user clicks inside so . . . + // In IE, the resize event is supposed to fire when the textarea becomes visible again and that will correct the size automatically. + // + var textarea = this.textbox; + textarea.style.height = "auto"; + // #rows = #newlines+1 + // Note: on Moz, the following #rows appears to be 1 too many. + // Actually, Moz is reserving room for the scrollbar. + // If you increase the font size, this behavior becomes readily apparent as the last line gets cut off without the +1. + textarea.rows = (textarea.value.match(/\n/g) || []).length + 2; + }, + + _resizeLater: function(){ + setTimeout(lang.hitch(this, "resize"), 0); + }, + + resize: function(){ + // summary: + // Resizes the textarea vertically (should be called after a style/value change) + function textareaScrollHeight(){ + var empty = false; + if(textarea.value === ''){ + textarea.value = ' '; + empty = true; + } + var sh = textarea.scrollHeight; + if(empty){ textarea.value = ''; } + return sh; + } + + var textarea = this.textbox; + if(textarea.style.overflowY == "hidden"){ textarea.scrollTop = 0; } + if(this.resizeTimer){ clearTimeout(this.resizeTimer); } + this.resizeTimer = null; + if(this.busyResizing){ return; } + this.busyResizing = true; + if(textareaScrollHeight() || textarea.offsetHeight){ + var currentHeight = textarea.style.height; + if(!(/px/.test(currentHeight))){ + currentHeight = textareaScrollHeight(); + textarea.rows = 1; + textarea.style.height = currentHeight + "px"; + } + var newH = Math.max(parseInt(currentHeight) - textarea.clientHeight, 0) + textareaScrollHeight(); + var newHpx = newH + "px"; + if(newHpx != textarea.style.height){ + textarea.rows = 1; + textarea.style.height = newHpx; + } + if(needsHelpShrinking){ + var scrollHeight = textareaScrollHeight(); + textarea.style.height = "auto"; + if(textareaScrollHeight() < scrollHeight){ // scrollHeight can shrink so now try a larger value + newHpx = newH - scrollHeight + textareaScrollHeight() + "px"; + } + textarea.style.height = newHpx; + } + textarea.style.overflowY = textareaScrollHeight() > textarea.clientHeight ? "auto" : "hidden"; + }else{ + // hidden content of unknown size + this._estimateHeight(); + } + this.busyResizing = false; + }, + + destroy: function(){ + if(this.resizeTimer){ clearTimeout(this.resizeTimer); } + if(this.shrinkTimer){ clearTimeout(this.shrinkTimer); } + this.inherited(arguments); + } + }); +}); + +}, +'dijit/MenuItem':function(){ +define([ + "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/MenuBarItem':function(){ +define([ + "dojo/_base/declare", // declare + "./MenuItem", + "dojo/text!./templates/MenuBarItem.html" +], function(declare, MenuItem, template){ + +/*===== + var MenuItem = dijit.MenuItem; +=====*/ + + // module: + // dijit/MenuBarItem + // summary: + // Item in a MenuBar that's clickable, and doesn't spawn a submenu when pressed (or hovered) + + + var _MenuBarItemMixin = declare("dijit._MenuBarItemMixin", null, { + templateString: template, + + // Map widget attributes to DOMNode attributes. + _setIconClassAttr: null // cancel MenuItem setter because we don't have a place for an icon + }); + + var MenuBarItem = declare("dijit.MenuBarItem", [MenuItem, _MenuBarItemMixin], { + // summary: + // Item in a MenuBar that's clickable, and doesn't spawn a submenu when pressed (or hovered) + + }); + MenuBarItem._MenuBarItemMixin = _MenuBarItemMixin; // dojox.mobile is accessing this + + + return MenuBarItem; +}); + +}, +'dijit/layout/TabController':function(){ +define([ + "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; +}); + +}, +'dojo/cldr/supplemental':function(){ +define(["../_base/kernel", "../_base/lang", "../i18n"], function(dojo, lang) { + // module: + // dojo/cldr/supplemental + // summary: + // TODOC + +lang.getObject("cldr.supplemental", true, dojo); + +dojo.cldr.supplemental.getFirstDayOfWeek = function(/*String?*/locale){ +// summary: Returns a zero-based index for first day of the week +// description: +// Returns a zero-based index for first day of the week, as used by the local (Gregorian) calendar. +// e.g. Sunday (returns 0), or Monday (returns 1) + + // from http://www.unicode.org/cldr/data/common/supplemental/supplementalData.xml:supplementalData/weekData/firstDay + var firstDay = {/*default is 1=Monday*/ + mv:5, + ae:6,af:6,bh:6,dj:6,dz:6,eg:6,er:6,et:6,iq:6,ir:6,jo:6,ke:6,kw:6, + ly:6,ma:6,om:6,qa:6,sa:6,sd:6,so:6,sy:6,tn:6,ye:6, + ar:0,as:0,az:0,bw:0,ca:0,cn:0,fo:0,ge:0,gl:0,gu:0,hk:0, + il:0,'in':0,jm:0,jp:0,kg:0,kr:0,la:0,mh:0,mn:0,mo:0,mp:0, + mt:0,nz:0,ph:0,pk:0,sg:0,th:0,tt:0,tw:0,um:0,us:0,uz:0, + vi:0,zw:0 +// variant. do not use? gb:0, + }; + + var country = dojo.cldr.supplemental._region(locale); + var dow = firstDay[country]; + return (dow === undefined) ? 1 : dow; /*Number*/ +}; + +dojo.cldr.supplemental._region = function(/*String?*/locale){ + locale = dojo.i18n.normalizeLocale(locale); + var tags = locale.split('-'); + var region = tags[1]; + if(!region){ + // IE often gives language only (#2269) + // Arbitrary mappings of language-only locales to a country: + region = {de:"de", en:"us", es:"es", fi:"fi", fr:"fr", he:"il", hu:"hu", it:"it", + ja:"jp", ko:"kr", nl:"nl", pt:"br", sv:"se", zh:"cn"}[tags[0]]; + }else if(region.length == 4){ + // The ISO 3166 country code is usually in the second position, unless a + // 4-letter script is given. See http://www.ietf.org/rfc/rfc4646.txt + region = tags[2]; + } + return region; +}; + +dojo.cldr.supplemental.getWeekend = function(/*String?*/locale){ +// summary: Returns a hash containing the start and end days of the weekend +// description: +// Returns a hash containing the start and end days of the weekend according to local custom using locale, +// or by default in the user's locale. +// e.g. {start:6, end:0} + + // from http://www.unicode.org/cldr/data/common/supplemental/supplementalData.xml:supplementalData/weekData/weekend{Start,End} + var weekendStart = {/*default is 6=Saturday*/ + 'in':0, + af:4,dz:4,ir:4,om:4,sa:4,ye:4, + ae:5,bh:5,eg:5,il:5,iq:5,jo:5,kw:5,ly:5,ma:5,qa:5,sd:5,sy:5,tn:5 + }; + + var weekendEnd = {/*default is 0=Sunday*/ + af:5,dz:5,ir:5,om:5,sa:5,ye:5, + ae:6,bh:5,eg:6,il:6,iq:6,jo:6,kw:6,ly:6,ma:6,qa:6,sd:6,sy:6,tn:6 + }; + + var country = dojo.cldr.supplemental._region(locale); + var start = weekendStart[country]; + var end = weekendEnd[country]; + if(start === undefined){start=6;} + if(end === undefined){end=0;} + return {start:start, end:end}; /*Object {start,end}*/ +}; + +return dojo.cldr.supplemental; +}); + +}, +'dijit/MenuBar':function(){ +require({cache:{ +'url:dijit/templates/MenuBar.html':"<div class=\"dijitMenuBar dijitMenuPassive\" data-dojo-attach-point=\"containerNode\" role=\"menubar\" tabIndex=\"${tabIndex}\" data-dojo-attach-event=\"onkeypress: _onKeyPress\"></div>\n"}}); +define("dijit/MenuBar", [ + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/keys", // keys.DOWN_ARROW + "./_MenuBase", + "dojo/text!./templates/MenuBar.html" +], function(declare, event, keys, _MenuBase, template){ + +/*===== + var _MenuBase = dijit._MenuBase; +=====*/ + +// module: +// dijit/MenuBar +// summary: +// A menu bar, listing menu choices horizontally, like the "File" menu in most desktop applications + +return declare("dijit.MenuBar", _MenuBase, { + // summary: + // A menu bar, listing menu choices horizontally, like the "File" menu in most desktop applications + + templateString: template, + + baseClass: "dijitMenuBar", + + // _isMenuBar: [protected] Boolean + // This is a MenuBar widget, not a (vertical) Menu widget. + _isMenuBar: true, + + postCreate: function(){ + var l = this.isLeftToRight(); + this.connectKeyNavHandlers( + l ? [keys.LEFT_ARROW] : [keys.RIGHT_ARROW], + l ? [keys.RIGHT_ARROW] : [keys.LEFT_ARROW] + ); + + // parameter to dijit.popup.open() about where to put popup (relative to this.domNode) + this._orient = ["below"]; + }, + + focusChild: function(item){ + // overload focusChild so that whenever the focus is moved to a new item, + // check the previous focused whether it has its popup open, if so, after + // focusing the new item, open its submenu immediately + var prev_item = this.focusedChild, + showpopup = prev_item && prev_item.popup && prev_item.popup.isShowingNow; + this.inherited(arguments); + if(showpopup && item.popup && !item.disabled){ + this._openPopup(); // TODO: on down arrow, _openPopup() is called here and in onItemClick() + } + }, + + _onKeyPress: function(/*Event*/ evt){ + // summary: + // Handle keyboard based menu navigation. + // tags: + // protected + + if(evt.ctrlKey || evt.altKey){ return; } + + switch(evt.charOrCode){ + case keys.DOWN_ARROW: + this._moveToPopup(evt); + event.stop(evt); + } + }, + + onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){ + // summary: + // Handle clicks on an item. Cancels a dropdown if already open. + // tags: + // private + if(item.popup && item.popup.isShowingNow){ + item.popup.onCancel(); + }else{ + this.inherited(arguments); + } + } +}); + +}); + +}, +'dijit/ToolbarSeparator':function(){ +define("dijit/ToolbarSeparator", [ + "dojo/_base/declare", // declare + "dojo/dom", // dom.setSelectable + "./_Widget", + "./_TemplatedMixin" +], function(declare, dom, _Widget, _TemplatedMixin){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; +=====*/ + + // module: + // dijit/ToolbarSeparator + // summary: + // A spacer between two `dijit.Toolbar` items + + + return declare("dijit.ToolbarSeparator", [_Widget, _TemplatedMixin], { + // summary: + // A spacer between two `dijit.Toolbar` items + + templateString: '<div class="dijitToolbarSeparator dijitInline" role="presentation"></div>', + + buildRendering: function(){ + this.inherited(arguments); + dom.setSelectable(this.domNode, false); + }, + + isFocusable: function(){ + // summary: + // This widget isn't focusable, so pass along that fact. + // tags: + // protected + return false; + } + }); +}); + +}, +'dijit/layout/StackController':function(){ +define([ + "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; +}); + +}, +'url:dijit/templates/TooltipDialog.html':"<div role=\"presentation\" tabIndex=\"-1\">\n\t<div class=\"dijitTooltipContainer\" role=\"presentation\">\n\t\t<div class =\"dijitTooltipContents dijitTooltipFocusNode\" data-dojo-attach-point=\"containerNode\" role=\"dialog\"></div>\n\t</div>\n\t<div class=\"dijitTooltipConnector\" role=\"presentation\"></div>\n</div>\n", +'dojo/dnd/Mover':function(){ +define(["../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/form/HorizontalRule':function(){ +define([ + "dojo/_base/declare", // declare + "../_Widget", + "../_TemplatedMixin" +], function(declare, _Widget, _TemplatedMixin){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; +=====*/ + +// module: +// dijit/form/HorizontalRule +// summary: +// Hash marks for `dijit.form.HorizontalSlider` + + +return declare("dijit.form.HorizontalRule", [_Widget, _TemplatedMixin], { + // summary: + // Hash marks for `dijit.form.HorizontalSlider` + + templateString: '<div class="dijitRuleContainer dijitRuleContainerH"></div>', + + // count: Integer + // Number of hash marks to generate + count: 3, + + // container: String + // For HorizontalSlider, this is either "topDecoration" or "bottomDecoration", + // and indicates whether this rule goes above or below the slider. + container: "containerNode", + + // ruleStyle: String + // CSS style to apply to individual hash marks + ruleStyle: "", + + _positionPrefix: '<div class="dijitRuleMark dijitRuleMarkH" style="left:', + _positionSuffix: '%;', + _suffix: '"></div>', + + _genHTML: function(pos){ + return this._positionPrefix + pos + this._positionSuffix + this.ruleStyle + this._suffix; + }, + + // _isHorizontal: [protected extension] Boolean + // VerticalRule will override this... + _isHorizontal: true, + + buildRendering: function(){ + this.inherited(arguments); + + var innerHTML; + if(this.count == 1){ + innerHTML = this._genHTML(50, 0); + }else{ + var i; + var interval = 100 / (this.count-1); + if(!this._isHorizontal || this.isLeftToRight()){ + innerHTML = this._genHTML(0, 0); + for(i=1; i < this.count-1; i++){ + innerHTML += this._genHTML(interval*i, i); + } + innerHTML += this._genHTML(100, this.count-1); + }else{ + innerHTML = this._genHTML(100, 0); + for(i=1; i < this.count-1; i++){ + innerHTML += this._genHTML(100-interval*i, i); + } + innerHTML += this._genHTML(0, this.count-1); + } + } + this.domNode.innerHTML = innerHTML; + } +}); + +}); + +}, +'dijit/layout/TabContainer':function(){ +define([ + "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"; + } + } + }); +}); + +}, +'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", +'dijit/_editor/nls/FontChoice':function(){ +define("dijit/_editor/nls/FontChoice", { root: +//begin v1.x content +({ + fontSize: "Size", + fontName: "Font", + formatBlock: "Format", + + serif: "serif", + "sans-serif": "sans-serif", + monospace: "monospace", + cursive: "cursive", + fantasy: "fantasy", + + noFormat: "None", + p: "Paragraph", + h1: "Heading", + h2: "Subheading", + h3: "Sub-subheading", + pre: "Pre-formatted", + + 1: "xx-small", + 2: "x-small", + 3: "small", + 4: "medium", + 5: "large", + 6: "x-large", + 7: "xx-large" +}) +//end v1.x content +, +"zh": true, +"zh-tw": true, +"tr": true, +"th": true, +"sv": true, +"sl": true, +"sk": true, +"ru": true, +"ro": true, +"pt": true, +"pt-pt": true, +"pl": true, +"nl": true, +"nb": true, +"ko": true, +"kk": true, +"ja": true, +"it": true, +"hu": true, +"hr": true, +"he": true, +"fr": true, +"fi": true, +"es": true, +"el": true, +"de": true, +"da": true, +"cs": true, +"ca": true, +"az": true, +"ar": true +}); + +}, +'dijit/form/_Spinner':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/_base/event", // event.stop + "dojo/keys", // keys keys.DOWN_ARROW keys.PAGE_DOWN keys.PAGE_UP keys.UP_ARROW + "dojo/_base/lang", // lang.hitch + "dojo/_base/sniff", // has("mozilla") + "dijit/typematic", + "./RangeBoundTextBox", + "dojo/text!./templates/Spinner.html", + "./_TextBoxMixin" // selectInputText +], function(declare, event, keys, lang, has, typematic, RangeBoundTextBox, template, _TextBoxMixin){ + +/*===== + var RangeBoundTextBox = dijit.form.RangeBoundTextBox; +=====*/ + + // module: + // dijit/form/_Spinner + // summary: + // Mixin for validation widgets with a spinner. + + + return declare("dijit.form._Spinner", RangeBoundTextBox, { + // summary: + // Mixin for validation widgets with a spinner. + // description: + // This class basically (conceptually) extends `dijit.form.ValidationTextBox`. + // It modifies the template to have up/down arrows, and provides related handling code. + + // defaultTimeout: Number + // Number of milliseconds before a held arrow key or up/down button becomes typematic + defaultTimeout: 500, + + // minimumTimeout: Number + // minimum number of milliseconds that typematic event fires when held key or button is held + minimumTimeout: 10, + + // 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, + + // smallDelta: Number + // Adjust the value by this much when spinning using the arrow keys/buttons + smallDelta: 1, + + // largeDelta: Number + // Adjust the value by this much when spinning using the PgUp/Dn keys + largeDelta: 10, + + templateString: template, + + baseClass: "dijitTextBox dijitSpinner", + + // Set classes like dijitUpArrowButtonHover or dijitDownArrowButtonActive depending on + // mouse action over specified node + cssStateNodes: { + "upArrowNode": "dijitUpArrowButton", + "downArrowNode": "dijitDownArrowButton" + }, + + adjust: function(val /*=====, delta =====*/){ + // summary: + // Overridable function used to adjust a primitive value(Number/Date/...) by the delta amount specified. + // The val is adjusted in a way that makes sense to the object type. + // val: Object + // delta: Number + // tags: + // protected extension + return val; + }, + + _arrowPressed: function(/*Node*/ nodePressed, /*Number*/ direction, /*Number*/ increment){ + // summary: + // Handler for arrow button or arrow key being pressed + if(this.disabled || this.readOnly){ return; } + this._setValueAttr(this.adjust(this.get('value'), direction*increment), false); + _TextBoxMixin.selectInputText(this.textbox, this.textbox.value.length); + }, + + _arrowReleased: function(/*Node*/ /*===== node =====*/){ + // summary: + // Handler for arrow button or arrow key being released + this._wheelTimer = null; + }, + + _typematicCallback: function(/*Number*/ count, /*DOMNode*/ node, /*Event*/ evt){ + var inc=this.smallDelta; + if(node == this.textbox){ + var key = evt.charOrCode; + inc = (key == keys.PAGE_UP || key == keys.PAGE_DOWN) ? this.largeDelta : this.smallDelta; + node = (key == keys.UP_ARROW || key == keys.PAGE_UP) ? this.upArrowNode : this.downArrowNode; + } + if(count == -1){ this._arrowReleased(node); } + else{ this._arrowPressed(node, (node == this.upArrowNode) ? 1 : -1, inc); } + }, + + _wheelTimer: null, + _mouseWheeled: function(/*Event*/ evt){ + // summary: + // Mouse wheel listener where supported + + event.stop(evt); + // FIXME: Safari bubbles + + // be nice to DOH and scroll as much as the event says to + var wheelDelta = evt.wheelDelta / 120; + if(Math.floor(wheelDelta) != wheelDelta){ + // If not an int multiple of 120, then its touchpad scrolling. + // This can change very fast so just assume 1 wheel click to make it more manageable. + wheelDelta = evt.wheelDelta > 0 ? 1 : -1; + } + var scrollAmount = evt.detail ? (evt.detail * -1) : wheelDelta; + if(scrollAmount !== 0){ + var node = this[(scrollAmount > 0 ? "upArrowNode" : "downArrowNode" )]; + + this._arrowPressed(node, scrollAmount, this.smallDelta); + + if(!this._wheelTimer){ + clearTimeout(this._wheelTimer); + } + this._wheelTimer = setTimeout(lang.hitch(this,"_arrowReleased",node), 50); + } + + }, + + postCreate: function(){ + this.inherited(arguments); + + // extra listeners + this.connect(this.domNode, !has("mozilla") ? "onmousewheel" : 'DOMMouseScroll', "_mouseWheeled"); + this._connects.push(typematic.addListener(this.upArrowNode, this.textbox, {charOrCode:keys.UP_ARROW,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout, this.minimumTimeout)); + this._connects.push(typematic.addListener(this.downArrowNode, this.textbox, {charOrCode:keys.DOWN_ARROW,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout, this.minimumTimeout)); + this._connects.push(typematic.addListener(this.upArrowNode, this.textbox, {charOrCode:keys.PAGE_UP,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout, this.minimumTimeout)); + this._connects.push(typematic.addListener(this.downArrowNode, this.textbox, {charOrCode:keys.PAGE_DOWN,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout, this.minimumTimeout)); + } + }); +}); + +}, +'dijit/form/Button':function(){ +define([ + "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 + }); +} + +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(["../main", "./Mover", "./Moveable"], function(dojo) { + // module: + // dojo/dnd/move + // summary: + // TODOC + + +/*===== +dojo.declare("dojo.dnd.move.__constrainedMoveableArgs", [dojo.dnd.__MoveableArgs], { + // constraints: Function + // Calculates a constraint box. + // It is called in a context of the moveable object. + constraints: function(){}, + + // within: Boolean + // restrict move within boundaries. + within: false +}); +=====*/ + +dojo.declare("dojo.dnd.move.constrainedMoveable", dojo.dnd.Moveable, { + // object attributes (for markup) + constraints: function(){}, + within: false, + + constructor: function(node, params){ + // summary: + // an object that makes a node moveable + // node: Node + // a node (or node's id) to be moved + // params: dojo.dnd.move.__constrainedMoveableArgs? + // an optional object with additional parameters; + // the rest is passed to the base class + if(!params){ params = {}; } + this.constraints = params.constraints; + this.within = params.within; + }, + onFirstMove: function(/* dojo.dnd.Mover */ mover){ + // summary: + // called during the very first move notification; + // can be used to initialize coordinates, can be overwritten. + var c = this.constraintBox = this.constraints.call(this, mover); + c.r = c.l + c.w; + c.b = c.t + c.h; + if(this.within){ + var mb = dojo._getMarginSize(mover.node); + c.r -= mb.w; + c.b -= mb.h; + } + }, + onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ + // summary: + // called during every move notification; + // should actually move the node; can be overwritten. + var c = this.constraintBox, s = mover.node.style; + this.onMoving(mover, leftTop); + leftTop.l = leftTop.l < c.l ? c.l : c.r < leftTop.l ? c.r : leftTop.l; + leftTop.t = leftTop.t < c.t ? c.t : c.b < leftTop.t ? c.b : leftTop.t; + s.left = leftTop.l + "px"; + s.top = leftTop.t + "px"; + this.onMoved(mover, leftTop); + } +}); + +/*===== +dojo.declare("dojo.dnd.move.__boxConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], { + // box: Object + // a constraint box + box: {} +}); +=====*/ + +dojo.declare("dojo.dnd.move.boxConstrainedMoveable", dojo.dnd.move.constrainedMoveable, { + // box: + // object attributes (for markup) + box: {}, + + constructor: function(node, params){ + // summary: + // an object, which makes a node moveable + // node: Node + // a node (or node's id) to be moved + // params: dojo.dnd.move.__boxConstrainedMoveableArgs? + // an optional object with parameters + var box = params && params.box; + this.constraints = function(){ return box; }; + } +}); + +/*===== +dojo.declare("dojo.dnd.move.__parentConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], { + // area: String + // A parent's area to restrict the move. + // Can be "margin", "border", "padding", or "content". + area: "" +}); +=====*/ + +dojo.declare("dojo.dnd.move.parentConstrainedMoveable", dojo.dnd.move.constrainedMoveable, { + // area: + // object attributes (for markup) + area: "content", + + constructor: function(node, params){ + // summary: + // an object, which makes a node moveable + // node: Node + // a node (or node's id) to be moved + // params: dojo.dnd.move.__parentConstrainedMoveableArgs? + // an optional object with parameters + var area = params && params.area; + this.constraints = function(){ + var n = this.node.parentNode, + s = dojo.getComputedStyle(n), + mb = dojo._getMarginBox(n, s); + if(area == "margin"){ + return mb; // Object + } + var t = dojo._getMarginExtents(n, s); + mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; + if(area == "border"){ + return mb; // Object + } + t = dojo._getBorderExtents(n, s); + mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; + if(area == "padding"){ + return mb; // Object + } + t = dojo._getPadExtents(n, s); + mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; + return mb; // Object + }; + } +}); + +// patching functions one level up for compatibility + +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/form/Form':function(){ +define([ + "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; +=====*/ + + // 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: + // 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: + // 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, []); + } + }, + + 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 + }, + + _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); + } + }, + + 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(){ +define([ + "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){ + + +/*===== + 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 + + +return declare("dijit.layout._TabContainerBase", [StackContainer, _TemplatedMixin], { + // 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: 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(["../_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/Editor':function(){ +define([ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/_base/Deferred", // Deferred + "dojo/i18n", // i18n.getLocalization + "dojo/dom-attr", // domAttr.set + "dojo/dom-class", // domClass.add + "dojo/dom-geometry", + "dojo/dom-style", // domStyle.set, get + "dojo/_base/event", // event.stop + "dojo/keys", // keys.F1 keys.F15 keys.TAB + "dojo/_base/lang", // lang.getObject lang.hitch + "dojo/_base/sniff", // has("ie") has("mac") has("webkit") + "dojo/string", // string.substitute + "dojo/topic", // topic.publish() + "dojo/_base/window", // win.withGlobal + "./_base/focus", // dijit.getBookmark() + "./_Container", + "./Toolbar", + "./ToolbarSeparator", + "./layout/_LayoutWidget", + "./form/ToggleButton", + "./_editor/_Plugin", + "./_editor/plugins/EnterKeyHandling", + "./_editor/html", + "./_editor/range", + "./_editor/RichText", + ".", // dijit._scopeName + "dojo/i18n!./_editor/nls/commands" +], function(array, declare, Deferred, i18n, domAttr, domClass, domGeometry, domStyle, + event, keys, lang, has, string, topic, win, + focusBase, _Container, Toolbar, ToolbarSeparator, _LayoutWidget, ToggleButton, + _Plugin, EnterKeyHandling, html, rangeapi, RichText, dijit){ + + // module: + // dijit/Editor + // summary: + // A rich text Editing widget + + var Editor = declare("dijit.Editor", RichText, { + // summary: + // A rich text Editing widget + // + // description: + // This widget provides basic WYSIWYG editing features, based on the browser's + // underlying rich text editing capability, accompanied by a toolbar (`dijit.Toolbar`). + // A plugin model is available to extend the editor's capabilities as well as the + // the options available in the toolbar. Content generation may vary across + // browsers, and clipboard operations may have different results, to name + // a few limitations. Note: this widget should not be used with the HTML + // <TEXTAREA> tag -- see dijit._editor.RichText for details. + + // plugins: [const] Object[] + // A list of plugin names (as strings) or instances (as objects) + // for this widget. + // + // When declared in markup, it might look like: + // | plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]" + plugins: null, + + // extraPlugins: [const] Object[] + // A list of extra plugin names which will be appended to plugins array + extraPlugins: null, + + constructor: function(){ + // summary: + // Runs on widget initialization to setup arrays etc. + // tags: + // private + + if(!lang.isArray(this.plugins)){ + this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|", + "insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull", + EnterKeyHandling /*, "createLink"*/]; + } + + this._plugins=[]; + this._editInterval = this.editActionInterval * 1000; + + //IE will always lose focus when other element gets focus, while for FF and safari, + //when no iframe is used, focus will be lost whenever another element gets focus. + //For IE, we can connect to onBeforeDeactivate, which will be called right before + //the focus is lost, so we can obtain the selected range. For other browsers, + //no equivalent of onBeforeDeactivate, so we need to do two things to make sure + //selection is properly saved before focus is lost: 1) when user clicks another + //element in the page, in which case we listen to mousedown on the entire page and + //see whether user clicks out of a focus editor, if so, save selection (focus will + //only lost after onmousedown event is fired, so we can obtain correct caret pos.) + //2) when user tabs away from the editor, which is handled in onKeyDown below. + if(has("ie")){ + this.events.push("onBeforeDeactivate"); + this.events.push("onBeforeActivate"); + } + }, + + postMixInProperties: function(){ + // summary: + // Extension to make sure a deferred is in place before certain functions + // execute, like making sure all the plugins are properly inserted. + + // Set up a deferred so that the value isn't applied to the editor + // until all the plugins load, needed to avoid timing condition + // reported in #10537. + this.setValueDeferred = new Deferred(); + this.inherited(arguments); + }, + + postCreate: function(){ + //for custom undo/redo, if enabled. + this._steps=this._steps.slice(0); + this._undoedSteps=this._undoedSteps.slice(0); + + if(lang.isArray(this.extraPlugins)){ + this.plugins=this.plugins.concat(this.extraPlugins); + } + + this.inherited(arguments); + + this.commands = i18n.getLocalization("dijit._editor", "commands", this.lang); + + if(!this.toolbar){ + // if we haven't been assigned a toolbar, create one + this.toolbar = new Toolbar({ + dir: this.dir, + lang: this.lang + }); + this.header.appendChild(this.toolbar.domNode); + } + + array.forEach(this.plugins, this.addPlugin, this); + + // Okay, denote the value can now be set. + this.setValueDeferred.callback(true); + + domClass.add(this.iframe.parentNode, "dijitEditorIFrameContainer"); + domClass.add(this.iframe, "dijitEditorIFrame"); + domAttr.set(this.iframe, "allowTransparency", true); + + if(has("webkit")){ + // Disable selecting the entire editor by inadvertent double-clicks. + // on buttons, title bar, etc. Otherwise clicking too fast on + // a button such as undo/redo selects the entire editor. + domStyle.set(this.domNode, "KhtmlUserSelect", "none"); + } + this.toolbar.startup(); + this.onNormalizedDisplayChanged(); //update toolbar button status + }, + destroy: function(){ + array.forEach(this._plugins, function(p){ + if(p && p.destroy){ + p.destroy(); + } + }); + this._plugins=[]; + this.toolbar.destroyRecursive(); + delete this.toolbar; + this.inherited(arguments); + }, + addPlugin: function(/*String||Object||Function*/plugin, /*Integer?*/index){ + // summary: + // takes a plugin name as a string or a plugin instance and + // adds it to the toolbar and associates it with this editor + // instance. The resulting plugin is added to the Editor's + // plugins array. If index is passed, it's placed in the plugins + // array at that index. No big magic, but a nice helper for + // passing in plugin names via markup. + // + // plugin: String, args object, plugin instance, or plugin constructor + // + // args: + // This object will be passed to the plugin constructor + // + // index: Integer + // Used when creating an instance from + // something already in this.plugins. Ensures that the new + // instance is assigned to this.plugins at that index. + var args=lang.isString(plugin)?{name:plugin}:lang.isFunction(plugin)?{ctor:plugin}:plugin; + if(!args.setEditor){ + var o={"args":args,"plugin":null,"editor":this}; + if(args.name){ + // search registry for a plugin factory matching args.name, if it's not there then + // fallback to 1.0 API: + // ask all loaded plugin modules to fill in o.plugin if they can (ie, if they implement args.name) + // remove fallback for 2.0. + if(_Plugin.registry[args.name]){ + o.plugin = _Plugin.registry[args.name](args); + }else{ + topic.publish(dijit._scopeName + ".Editor.getPlugin", o); // publish + } + } + if(!o.plugin){ + var pc = args.ctor || lang.getObject(args.name); + if(pc){ + o.plugin=new pc(args); + } + } + if(!o.plugin){ + console.warn('Cannot find plugin',plugin); + return; + } + plugin=o.plugin; + } + if(arguments.length > 1){ + this._plugins[index] = plugin; + }else{ + this._plugins.push(plugin); + } + plugin.setEditor(this); + if(lang.isFunction(plugin.setToolbar)){ + plugin.setToolbar(this.toolbar); + } + }, + + //the following 2 functions are required to make the editor play nice under a layout widget, see #4070 + + resize: function(size){ + // summary: + // Resize the editor to the specified size, see `dijit.layout._LayoutWidget.resize` + if(size){ + // we've been given a height/width for the entire editor (toolbar + contents), calls layout() + // to split the allocated size between the toolbar and the contents + _LayoutWidget.prototype.resize.apply(this, arguments); + } + /* + else{ + // do nothing, the editor is already laid out correctly. The user has probably specified + // the height parameter, which was used to set a size on the iframe + } + */ + }, + layout: function(){ + // summary: + // Called from `dijit.layout._LayoutWidget.resize`. This shouldn't be called directly + // tags: + // protected + + // Converts the iframe (or rather the <div> surrounding it) to take all the available space + // except what's needed for the header (toolbars) and footer (breadcrumbs, etc). + // A class was added to the iframe container and some themes style it, so we have to + // calc off the added margins and padding too. See tracker: #10662 + var areaHeight = (this._contentBox.h - + (this.getHeaderHeight() + this.getFooterHeight() + + domGeometry.getPadBorderExtents(this.iframe.parentNode).h + + domGeometry.getMarginExtents(this.iframe.parentNode).h)); + this.editingArea.style.height = areaHeight + "px"; + if(this.iframe){ + this.iframe.style.height="100%"; + } + this._layoutMode = true; + }, + + _onIEMouseDown: function(/*Event*/ e){ + // summary: + // IE only to prevent 2 clicks to focus + // tags: + // private + var outsideClientArea; + // IE 8's componentFromPoint is broken, which is a shame since it + // was smaller code, but oh well. We have to do this brute force + // to detect if the click was scroller or not. + var b = this.document.body; + var clientWidth = b.clientWidth; + var clientHeight = b.clientHeight; + var clientLeft = b.clientLeft; + var offsetWidth = b.offsetWidth; + var offsetHeight = b.offsetHeight; + var offsetLeft = b.offsetLeft; + + //Check for vertical scroller click. + if(/^rtl$/i.test(b.dir || "")){ + if(clientWidth < offsetWidth && e.x > clientWidth && e.x < offsetWidth){ + // Check the click was between width and offset width, if so, scroller + outsideClientArea = true; + } + }else{ + // RTL mode, we have to go by the left offsets. + if(e.x < clientLeft && e.x > offsetLeft){ + // Check the click was between width and offset width, if so, scroller + outsideClientArea = true; + } + } + if(!outsideClientArea){ + // Okay, might be horiz scroller, check that. + if(clientHeight < offsetHeight && e.y > clientHeight && e.y < offsetHeight){ + // Horizontal scroller. + outsideClientArea = true; + } + } + if(!outsideClientArea){ + delete this._cursorToStart; // Remove the force to cursor to start position. + delete this._savedSelection; // new mouse position overrides old selection + if(e.target.tagName == "BODY"){ + setTimeout(lang.hitch(this, "placeCursorAtEnd"), 0); + } + this.inherited(arguments); + } + }, + onBeforeActivate: function(){ + this._restoreSelection(); + }, + onBeforeDeactivate: function(e){ + // summary: + // Called on IE right before focus is lost. Saves the selected range. + // tags: + // private + if(this.customUndo){ + this.endEditing(true); + } + //in IE, the selection will be lost when other elements get focus, + //let's save focus before the editor is deactivated + if(e.target.tagName != "BODY"){ + this._saveSelection(); + } + //console.log('onBeforeDeactivate',this); + }, + + /* beginning of custom undo/redo support */ + + // customUndo: Boolean + // Whether we shall use custom undo/redo support instead of the native + // browser support. By default, we now use custom undo. It works better + // than native browser support and provides a consistent behavior across + // browsers with a minimal performance hit. We already had the hit on + // the slowest browser, IE, anyway. + customUndo: true, + + // editActionInterval: Integer + // When using customUndo, not every keystroke will be saved as a step. + // Instead typing (including delete) will be grouped together: after + // a user stops typing for editActionInterval seconds, a step will be + // saved; if a user resume typing within editActionInterval seconds, + // the timeout will be restarted. By default, editActionInterval is 3 + // seconds. + editActionInterval: 3, + + beginEditing: function(cmd){ + // summary: + // Called to note that the user has started typing alphanumeric characters, if it's not already noted. + // Deals with saving undo; see editActionInterval parameter. + // tags: + // private + if(!this._inEditing){ + this._inEditing=true; + this._beginEditing(cmd); + } + if(this.editActionInterval>0){ + if(this._editTimer){ + clearTimeout(this._editTimer); + } + this._editTimer = setTimeout(lang.hitch(this, this.endEditing), this._editInterval); + } + }, + + // TODO: declaring these in the prototype is meaningless, just create in the constructor/postCreate + _steps:[], + _undoedSteps:[], + + execCommand: function(cmd){ + // summary: + // Main handler for executing any commands to the editor, like paste, bold, etc. + // Called by plugins, but not meant to be called by end users. + // tags: + // protected + if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){ + return this[cmd](); + }else{ + if(this.customUndo){ + this.endEditing(); + this._beginEditing(); + } + var r = this.inherited(arguments); + if(this.customUndo){ + this._endEditing(); + } + return r; + } + }, + + _pasteImpl: function(){ + // summary: + // Over-ride of paste command control to make execCommand cleaner + // tags: + // Protected + return this._clipboardCommand("paste"); + }, + + _cutImpl: function(){ + // summary: + // Over-ride of cut command control to make execCommand cleaner + // tags: + // Protected + return this._clipboardCommand("cut"); + }, + + _copyImpl: function(){ + // summary: + // Over-ride of copy command control to make execCommand cleaner + // tags: + // Protected + return this._clipboardCommand("copy"); + }, + + _clipboardCommand: function(cmd){ + // summary: + // Function to handle processing clipboard commands (or at least try to). + // tags: + // Private + var r; + try{ + // Try to exec the superclass exec-command and see if it works. + r = this.document.execCommand(cmd, false, null); + if(has("webkit") && !r){ //see #4598: webkit does not guarantee clipboard support from js + throw { code: 1011 }; // throw an object like Mozilla's error + } + }catch(e){ + //TODO: when else might we get an exception? Do we need the Mozilla test below? + if(e.code == 1011 /* Mozilla: service denied */){ + // Warn user of platform limitation. Cannot programmatically access clipboard. See ticket #4136 + var sub = string.substitute, + accel = {cut:'X', copy:'C', paste:'V'}; + alert(sub(this.commands.systemShortcut, + [this.commands[cmd], sub(this.commands[has("mac") ? 'appleKey' : 'ctrlKey'], [accel[cmd]])])); + } + r = false; + } + return r; + }, + + queryCommandEnabled: function(cmd){ + // summary: + // Returns true if specified editor command is enabled. + // Used by the plugins to know when to highlight/not highlight buttons. + // tags: + // protected + if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){ + return cmd == 'undo' ? (this._steps.length > 1) : (this._undoedSteps.length > 0); + }else{ + return this.inherited(arguments); + } + }, + _moveToBookmark: function(b){ + // summary: + // Selects the text specified in bookmark b + // tags: + // private + var bookmark = b.mark; + var mark = b.mark; + var col = b.isCollapsed; + var r, sNode, eNode, sel; + if(mark){ + if(has("ie") < 9){ + if(lang.isArray(mark)){ + //IE CONTROL, have to use the native bookmark. + bookmark = []; + array.forEach(mark,function(n){ + bookmark.push(rangeapi.getNode(n,this.editNode)); + },this); + win.withGlobal(this.window,'moveToBookmark',dijit,[{mark: bookmark, isCollapsed: col}]); + }else{ + if(mark.startContainer && mark.endContainer){ + // Use the pseudo WC3 range API. This works better for positions + // than the IE native bookmark code. + sel = rangeapi.getSelection(this.window); + if(sel && sel.removeAllRanges){ + sel.removeAllRanges(); + r = rangeapi.create(this.window); + sNode = rangeapi.getNode(mark.startContainer,this.editNode); + eNode = rangeapi.getNode(mark.endContainer,this.editNode); + if(sNode && eNode){ + // Okay, we believe we found the position, so add it into the selection + // There are cases where it may not be found, particularly in undo/redo, when + // IE changes the underlying DOM on us (wraps text in a <p> tag or similar. + // So, in those cases, don't bother restoring selection. + r.setStart(sNode,mark.startOffset); + r.setEnd(eNode,mark.endOffset); + sel.addRange(r); + } + } + } + } + }else{//w3c range + sel = rangeapi.getSelection(this.window); + if(sel && sel.removeAllRanges){ + sel.removeAllRanges(); + r = rangeapi.create(this.window); + sNode = rangeapi.getNode(mark.startContainer,this.editNode); + eNode = rangeapi.getNode(mark.endContainer,this.editNode); + if(sNode && eNode){ + // Okay, we believe we found the position, so add it into the selection + // There are cases where it may not be found, particularly in undo/redo, when + // formatting as been done and so on, so don't restore selection then. + r.setStart(sNode,mark.startOffset); + r.setEnd(eNode,mark.endOffset); + sel.addRange(r); + } + } + } + } + }, + _changeToStep: function(from, to){ + // summary: + // Reverts editor to "to" setting, from the undo stack. + // tags: + // private + this.setValue(to.text); + var b=to.bookmark; + if(!b){ return; } + this._moveToBookmark(b); + }, + undo: function(){ + // summary: + // Handler for editor undo (ex: ctrl-z) operation + // tags: + // private + //console.log('undo'); + var ret = false; + if(!this._undoRedoActive){ + this._undoRedoActive = true; + this.endEditing(true); + var s=this._steps.pop(); + if(s && this._steps.length>0){ + this.focus(); + this._changeToStep(s,this._steps[this._steps.length-1]); + this._undoedSteps.push(s); + this.onDisplayChanged(); + delete this._undoRedoActive; + ret = true; + } + delete this._undoRedoActive; + } + return ret; + }, + redo: function(){ + // summary: + // Handler for editor redo (ex: ctrl-y) operation + // tags: + // private + //console.log('redo'); + var ret = false; + if(!this._undoRedoActive){ + this._undoRedoActive = true; + this.endEditing(true); + var s=this._undoedSteps.pop(); + if(s && this._steps.length>0){ + this.focus(); + this._changeToStep(this._steps[this._steps.length-1],s); + this._steps.push(s); + this.onDisplayChanged(); + ret = true; + } + delete this._undoRedoActive; + } + return ret; + }, + endEditing: function(ignore_caret){ + // summary: + // Called to note that the user has stopped typing alphanumeric characters, if it's not already noted. + // Deals with saving undo; see editActionInterval parameter. + // tags: + // private + if(this._editTimer){ + clearTimeout(this._editTimer); + } + if(this._inEditing){ + this._endEditing(ignore_caret); + this._inEditing=false; + } + }, + + _getBookmark: function(){ + // summary: + // Get the currently selected text + // tags: + // protected + var b=win.withGlobal(this.window,focusBase.getBookmark); + var tmp=[]; + if(b && b.mark){ + var mark = b.mark; + if(has("ie") < 9){ + // Try to use the pseudo range API on IE for better accuracy. + var sel = rangeapi.getSelection(this.window); + if(!lang.isArray(mark)){ + if(sel){ + var range; + if(sel.rangeCount){ + range = sel.getRangeAt(0); + } + if(range){ + b.mark = range.cloneRange(); + }else{ + b.mark = win.withGlobal(this.window,focusBase.getBookmark); + } + } + }else{ + // Control ranges (img, table, etc), handle differently. + array.forEach(b.mark,function(n){ + tmp.push(rangeapi.getIndex(n,this.editNode).o); + },this); + b.mark = tmp; + } + } + try{ + if(b.mark && b.mark.startContainer){ + tmp=rangeapi.getIndex(b.mark.startContainer,this.editNode).o; + b.mark={startContainer:tmp, + startOffset:b.mark.startOffset, + endContainer:b.mark.endContainer===b.mark.startContainer?tmp:rangeapi.getIndex(b.mark.endContainer,this.editNode).o, + endOffset:b.mark.endOffset}; + } + }catch(e){ + b.mark = null; + } + } + return b; + }, + _beginEditing: function(){ + // summary: + // Called when the user starts typing alphanumeric characters. + // Deals with saving undo; see editActionInterval parameter. + // tags: + // private + if(this._steps.length === 0){ + // You want to use the editor content without post filtering + // to make sure selection restores right for the 'initial' state. + // and undo is called. So not using this.value, as it was 'processed' + // and the line-up for selections may have been altered. + this._steps.push({'text':html.getChildrenHtml(this.editNode),'bookmark':this._getBookmark()}); + } + }, + _endEditing: function(){ + // summary: + // Called when the user stops typing alphanumeric characters. + // Deals with saving undo; see editActionInterval parameter. + // tags: + // private + // Avoid filtering to make sure selections restore. + var v = html.getChildrenHtml(this.editNode); + + this._undoedSteps=[];//clear undoed steps + this._steps.push({text: v, bookmark: this._getBookmark()}); + }, + onKeyDown: function(e){ + // summary: + // Handler for onkeydown event. + // tags: + // private + + //We need to save selection if the user TAB away from this editor + //no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate + if(!has("ie") && !this.iframe && e.keyCode == keys.TAB && !this.tabIndent){ + this._saveSelection(); + } + if(!this.customUndo){ + this.inherited(arguments); + return; + } + var k = e.keyCode; + if(e.ctrlKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892 + if(k == 90 || k == 122){ //z + event.stop(e); + this.undo(); + return; + }else if(k == 89 || k == 121){ //y + event.stop(e); + this.redo(); + return; + } + } + this.inherited(arguments); + + switch(k){ + case keys.ENTER: + case keys.BACKSPACE: + case keys.DELETE: + this.beginEditing(); + break; + case 88: //x + case 86: //v + if(e.ctrlKey && !e.altKey && !e.metaKey){ + this.endEditing();//end current typing step if any + if(e.keyCode == 88){ + this.beginEditing('cut'); + //use timeout to trigger after the cut is complete + setTimeout(lang.hitch(this, this.endEditing), 1); + }else{ + this.beginEditing('paste'); + //use timeout to trigger after the paste is complete + setTimeout(lang.hitch(this, this.endEditing), 1); + } + break; + } + //pass through + default: + if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<keys.F1 || e.keyCode>keys.F15)){ + this.beginEditing(); + break; + } + //pass through + case keys.ALT: + this.endEditing(); + break; + case keys.UP_ARROW: + case keys.DOWN_ARROW: + case keys.LEFT_ARROW: + case keys.RIGHT_ARROW: + case keys.HOME: + case keys.END: + case keys.PAGE_UP: + case keys.PAGE_DOWN: + this.endEditing(true); + break; + //maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed + case keys.CTRL: + case keys.SHIFT: + case keys.TAB: + break; + } + }, + _onBlur: function(){ + // summary: + // Called from focus manager when focus has moved away from this editor + // tags: + // protected + + //this._saveSelection(); + this.inherited(arguments); + this.endEditing(true); + }, + _saveSelection: function(){ + // summary: + // Save the currently selected text in _savedSelection attribute + // tags: + // private + try{ + this._savedSelection=this._getBookmark(); + }catch(e){ /* Squelch any errors that occur if selection save occurs due to being hidden simultaneously. */} + }, + _restoreSelection: function(){ + // summary: + // Re-select the text specified in _savedSelection attribute; + // see _saveSelection(). + // tags: + // private + if(this._savedSelection){ + // Clear off cursor to start, we're deliberately going to a selection. + delete this._cursorToStart; + // only restore the selection if the current range is collapsed + // if not collapsed, then it means the editor does not lose + // selection and there is no need to restore it + if(win.withGlobal(this.window,'isCollapsed',dijit)){ + this._moveToBookmark(this._savedSelection); + } + delete this._savedSelection; + } + }, + + onClick: function(){ + // summary: + // Handler for when editor is clicked + // tags: + // protected + this.endEditing(true); + this.inherited(arguments); + }, + + replaceValue: function(/*String*/ html){ + // summary: + // over-ride of replaceValue to support custom undo and stack maintenance. + // tags: + // protected + if(!this.customUndo){ + this.inherited(arguments); + }else{ + if(this.isClosed){ + this.setValue(html); + }else{ + this.beginEditing(); + if(!html){ + html = " "; // + } + this.setValue(html); + this.endEditing(); + } + } + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + var disableFunc = lang.hitch(this, function(){ + if((!this.disabled && value) || (!this._buttonEnabledPlugins && value)){ + // Disable editor: disable all enabled buttons and remember that list + array.forEach(this._plugins, function(p){ + p.set("disabled", true); + }); + }else if(this.disabled && !value){ + // Restore plugins to being active. + array.forEach(this._plugins, function(p){ + p.set("disabled", false); + }); + } + }); + this.setValueDeferred.addCallback(disableFunc); + this.inherited(arguments); + }, + + _setStateClass: function(){ + try{ + this.inherited(arguments); + + // Let theme set the editor's text color based on editor enabled/disabled state. + // We need to jump through hoops because the main document (where the theme CSS is) + // is separate from the iframe's document. + if(this.document && this.document.body){ + domStyle.set(this.document.body, "color", domStyle.get(this.iframe, "color")); + } + }catch(e){ /* Squelch any errors caused by focus change if hidden during a state change */} + } + }); + + // Register the "default plugins", ie, the built-in editor commands + function simplePluginFactory(args){ + return new _Plugin({ command: args.name }); + } + function togglePluginFactory(args){ + return new _Plugin({ buttonClass: ToggleButton, command: args.name }); + } + lang.mixin(_Plugin.registry, { + "undo": simplePluginFactory, + "redo": simplePluginFactory, + "cut": simplePluginFactory, + "copy": simplePluginFactory, + "paste": simplePluginFactory, + "insertOrderedList": simplePluginFactory, + "insertUnorderedList": simplePluginFactory, + "indent": simplePluginFactory, + "outdent": simplePluginFactory, + "justifyCenter": simplePluginFactory, + "justifyFull": simplePluginFactory, + "justifyLeft": simplePluginFactory, + "justifyRight": simplePluginFactory, + "delete": simplePluginFactory, + "selectAll": simplePluginFactory, + "removeFormat": simplePluginFactory, + "unlink": simplePluginFactory, + "insertHorizontalRule": simplePluginFactory, + + "bold": togglePluginFactory, + "italic": togglePluginFactory, + "underline": togglePluginFactory, + "strikethrough": togglePluginFactory, + "subscript": togglePluginFactory, + "superscript": togglePluginFactory, + + "|": function(){ + return new _Plugin({ button: new ToolbarSeparator(), setEditor: function(editor){this.editor = editor;}}); + } + }); + + return Editor; +}); + +}, +'dojo/cldr/nls/currency':function(){ +define({ root: + +//begin v1.x content +{ + "USD_symbol": "US$", + "CAD_symbol": "CA$", + "GBP_symbol": "£", + "HKD_symbol": "HK$", + "JPY_symbol": "JPÂ¥", + "AUD_symbol": "AU$", + "CNY_symbol": "CNÂ¥", + "EUR_symbol": "€" +} +//end v1.x content +, + "ar": true, + "ca": true, + "cs": true, + "da": true, + "de": true, + "el": true, + "en": true, + "en-au": true, + "en-ca": true, + "es": true, + "fi": true, + "fr": true, + "he": true, + "hu": true, + "it": true, + "ja": true, + "ko": true, + "nb": true, + "nl": true, + "pl": true, + "pt": true, + "ro": true, + "ru": true, + "sk": true, + "sl": true, + "sv": true, + "th": true, + "tr": true, + "zh": true, + "zh-hant": true, + "zh-hk": true, + "zh-tw": true +}); +}, +'dijit/Toolbar':function(){ +define([ + "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([ + "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){ + +/*===== +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 + // + // 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", + +/*===== + // selectedChildWidget: [readonly] dijit._Widget + // References the currently selected child widget, if any. + // Adjust selected child with selectChild() method. + selectedChildWidget: null, +=====*/ + + buildRendering: function(){ + this.inherited(arguments); + domClass.add(this.domNode, "dijitLayoutContainer"); + this.containerNode.setAttribute("role", "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 + 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(["./_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/ComboBox':function(){ +define([ + "dojo/_base/declare", // declare + "./ValidationTextBox", + "./ComboBoxMixin" +], function(declare, ValidationTextBox, ComboBoxMixin){ + +/*===== + var ValidationTextBox = dijit.form.ValidationTextBox; + var ComboBoxMixin = dijit.form.ComboBoxMixin; +=====*/ + + // module: + // dijit/form/ComboBox + // summary: + // Auto-completing text box + + return declare("dijit.form.ComboBox", [ValidationTextBox, ComboBoxMixin], { + // summary: + // Auto-completing text box + // + // 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. + }); +}); + +}, +'dijit/form/_FormMixin':function(){ +define([ + "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(){ + array.forEach(this._getDescendantFormWidgets(), function(widget){ + if(widget.reset){ + widget.reset(); + } + }); + }, + + validate: function(){ + // summary: + // returns if the form is valid - same as isValid - but + // provides a few additional (ui-specific) features. + // 1 - it will highlight any sub-widgets that are not + // valid + // 2 - it will call focus() on the first invalid + // sub-widget + var didFocus = false; + 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 + winUtils.scrollIntoView(widget.containerNode || widget.domNode); + widget.focus(); + didFocus = true; + } + return valid; + }), function(item){ return item; }); + }, + + setValues: function(val){ + kernel.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.", "", "2.0"); + return this.set('value', val); + }, + _setValueAttr: function(/*Object*/ obj){ + // summary: + // Fill in form values from according to an Object (in the format returned by get('value')) + + // generate map from name --> [list of widgets with that name] + var map = { }; + array.forEach(this._getDescendantFormWidgets(), function(widget){ + if(!widget.name){ return; } + var entry = map[widget.name] || (map[widget.name] = [] ); + entry.push(widget); + }); + + for(var name in map){ + if(!map.hasOwnProperty(name)){ + continue; + } + var widgets = map[name], // array of widgets w/this name + values = lang.getObject(name, false, obj); // list of values for those widgets + + if(values === undefined){ + continue; + } + 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 + 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 + array.forEach(widgets, function(w, i){ + w.set('value', values[i]); + }); + } + } + + /*** + * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets) + + array.forEach(this.containerNode.elements, function(element){ + if(element.name == ''){return}; // like "continue" + var namePath = element.name.split("."); + var myObj=obj; + var name=namePath[namePath.length-1]; + for(var j=1,len2=namePath.length;j<len2;++j){ + var p=namePath[j - 1]; + // repeater support block + var nameA=p.split("["); + if(nameA.length > 1){ + if(typeof(myObj[nameA[0]]) == "undefined"){ + myObj[nameA[0]]=[ ]; + } // if + + nameIndex=parseInt(nameA[1]); + if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){ + myObj[nameA[0]][nameIndex] = { }; + } + myObj=myObj[nameA[0]][nameIndex]; + continue; + } // repeater support ends + + if(typeof(myObj[p]) == "undefined"){ + myObj=undefined; + break; + }; + myObj=myObj[p]; + } + + if(typeof(myObj) == "undefined"){ + return; // like "continue" + } + if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){ + return; // like "continue" + } + + // TODO: widget values (just call set('value', ...) on the widget) + + // TODO: maybe should call dojo.getNodeProp() instead + switch(element.type){ + case "checkbox": + element.checked = (name in myObj) && + 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; + 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"; + array.forEach(element.options, function(option){ + option.selected = option.value == myObj[name]; + }); + break; + case "hidden": + case "text": + case "textarea": + case "password": + element.value = myObj[name] || ""; + break; + } + }); + */ + + // Note: no need to call this._set("value", ...) as the child updates will trigger onChange events + // which I am monitoring. + }, + + getValues: function(){ + kernel.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0"); + return this.get('value'); + }, + _getValueAttr: function(){ + // summary: + // Returns Object representing form values. See description of `value` for details. + // description: + + // The value is updated into this.value every time a child has an onChange event, + // so in the common case this function could just return this.value. However, + // 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 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 = { }; + array.forEach(this._getDescendantFormWidgets(), function(widget){ + var name = widget.name; + if(!name || widget.disabled){ return; } + + // Single value widget (checkbox, radio, or plain <input> type widget) + var value = widget.get('value'); + + // Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays + if(typeof widget.checked == 'boolean'){ + if(/Radio/.test(widget.declaredClass)){ + // radio button + if(value !== false){ + lang.setObject(name, value, obj); + }else{ + // give radio widgets a default of null + value = lang.getObject(name, false, obj); + if(value === undefined){ + lang.setObject(name, null, obj); + } + } + }else{ + // checkbox/toggle button + var ary=lang.getObject(name, false, obj); + if(!ary){ + ary=[]; + lang.setObject(name, ary, obj); + } + if(value !== false){ + ary.push(value); + } + } + }else{ + var prev=lang.getObject(name, false, obj); + if(typeof prev != "undefined"){ + if(lang.isArray(prev)){ + prev.push(value); + }else{ + lang.setObject(name, [prev, value], obj); + } + }else{ + // unique name + lang.setObject(name, value, obj); + } + } + }); + + /*** + * 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 = { }; + array.forEach(this.containerNode.elements, function(elm){ + if(!elm.name) { + return; // like "continue" + } + var namePath = elm.name.split("."); + var myObj=obj; + var name=namePath[namePath.length-1]; + for(var j=1,len2=namePath.length;j<len2;++j){ + var nameIndex = null; + var p=namePath[j - 1]; + var nameA=p.split("["); + if(nameA.length > 1){ + if(typeof(myObj[nameA[0]]) == "undefined"){ + myObj[nameA[0]]=[ ]; + } // if + nameIndex=parseInt(nameA[1]); + if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){ + myObj[nameA[0]][nameIndex] = { }; + } + }else if(typeof(myObj[nameA[0]]) == "undefined"){ + myObj[nameA[0]] = { } + } // if + + if(nameA.length == 1){ + myObj=myObj[nameA[0]]; + }else{ + myObj=myObj[nameA[0]][nameIndex]; + } // if + } // for + + 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{ + // can not set value when there is no name + } + }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"){ + if(typeof(myObj[name]) == 'undefined'){ + myObj[name]=[ ]; + } + for(var jdx=0,len3=elm.options.length; jdx<len3; ++jdx){ + if(elm.options[jdx].selected){ + myObj[name].push(elm.options[jdx].value); + } + } + } // if + name=undefined; + }); // forEach + ***/ + return obj; + }, + + isValid: function(){ + // summary: + // Returns true if all of the widgets are valid. + // Deprecated, will be removed in 2.0. Use get("state") instead. + + return this.state == ""; + }, + + 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 + // state changes on the form as a whole. + // + // Deprecated. Will be removed in 2.0. Use watch("state", ...) instead. + }, + + _getState: function(){ + // summary: + // Compute what this.state should be based on state of children + var states = array.map(this._descendants, function(w){ + return w.get("state") || ""; + }); + + 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. + array.forEach(this._childConnections || [], lang.hitch(this, "disconnect")); + array.forEach(this._childWatches || [], function(w){ w.unwatch(); }); + }, + + connectChildren: function(/*Boolean*/ inStartup){ + // summary: + // Setup connections to monitor changes to children's value, error state, and disabled state, + // in order to update Form.value and Form.state. + // + // You can call this function directly, ex. in the event that you + // programmatically add a widget to the form *after* the form has been + // initialized. + + var _this = this; + + // Remove old connections, if any + this.disconnectChildren(); + + 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; } : lang.hitch(this, "_set"); + set("value", this.get("value")); + set("state", this._getState()); + + // Monitor changes to error state and disabled state in order to update + // Form.state + var conns = (this._childConnections = []), + watches = (this._childWatches = []); + 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. + array.forEach(["state", "disabled"], function(attr){ + watches.push(widget.watch(attr, function(){ + _this.set("state", _this._getState()); + })); + }); + }); + + // And monitor calls to child.onChange so we can update this.value + 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() + // 2. Form.reset() + // 3. user selecting a radio button (which will de-select another radio button, + // causing two onChange events) + if(_this._onChangeDelayTimer){ + clearTimeout(_this._onChangeDelayTimer); + } + _this._onChangeDelayTimer = setTimeout(function(){ + delete _this._onChangeDelayTimer; + _this._set("value", _this.get("value")); + }, 10); + }; + 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, + // but that gets a little complicated when a checkbox is checked/unchecked + // since this.value["checkboxName"] contains an array of all the checkboxes w/the same name. + // Doing simple thing for now. + conns.push(_this.connect(widget, "onChange", onChange)); + + // Disabling/enabling a child widget should remove it's value from this.value. + // Again, this code could be more efficient, doing simple thing for now. + watches.push(widget.watch("disabled", onChange)); + } + ); + }, + + startup: function(){ + this.inherited(arguments); + + // Initialize value and valid/invalid state tracking. Needs to be done in startup() + // so that children are initialized. + this.connectChildren(true); + + // Make state change call onValidStateChange(), will be removed in 2.0 + this.watch("state", function(attr, oldVal, newVal){ this.onValidStateChange(newVal == ""); }); + }, + + destroy: function(){ + this.disconnectChildren(); + this.inherited(arguments); + } + + }); +}); + +}, +'dijit/_editor/plugins/LinkDialog':function(){ +define([ + "require", + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.get + "dojo/keys", // keys.ENTER + "dojo/_base/lang", // lang.delegate lang.hitch lang.trim + "dojo/_base/sniff", // has("ie") + "dojo/string", // string.substitute + "dojo/_base/window", // win.withGlobal + "../../_Widget", + "../_Plugin", + "../../form/DropDownButton", + "../range", + "../selection" +], function(require, declare, domAttr, keys, lang, has, string, win, + _Widget, _Plugin, DropDownButton, rangeapi, selectionapi){ + +/*===== + var _Plugin = dijit._editor._Plugin; +=====*/ + +// module: +// dijit/_editor/plugins/LinkDialog +// summary: +// Editor plugins: LinkDialog (for inserting links) and ImgLinkDialog (for inserting images) + + +var LinkDialog = declare("dijit._editor.plugins.LinkDialog", _Plugin, { + // summary: + // This plugin provides the basis for an 'anchor' (link) dialog and an extension of it + // provides the image link dialog. + // + // description: + // The command provided by this plugin is: + // * createLink + + // Override _Plugin.buttonClass. This plugin is controlled by a DropDownButton + // (which triggers a TooltipDialog). + buttonClass: DropDownButton, + + // Override _Plugin.useDefaultCommand... processing is handled by this plugin, not by dijit.Editor. + useDefaultCommand: false, + + // urlRegExp: [protected] String + // Used for validating input as correct URL. While file:// urls are not terribly + // useful, they are technically valid. + urlRegExp: "((https?|ftps?|file)\\://|\./|/|)(/[a-zA-Z]{1,1}:/|)(((?:(?:[\\da-zA-Z](?:[-\\da-zA-Z]{0,61}[\\da-zA-Z])?)\\.)*(?:[a-zA-Z](?:[-\\da-zA-Z]{0,80}[\\da-zA-Z])?)\\.?)|(((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])|(0[xX]0*[\\da-fA-F]?[\\da-fA-F]\\.){3}0[xX]0*[\\da-fA-F]?[\\da-fA-F]|(0+[0-3][0-7][0-7]\\.){3}0+[0-3][0-7][0-7]|(0|[1-9]\\d{0,8}|[1-3]\\d{9}|4[01]\\d{8}|42[0-8]\\d{7}|429[0-3]\\d{6}|4294[0-8]\\d{5}|42949[0-5]\\d{4}|429496[0-6]\\d{3}|4294967[01]\\d{2}|42949672[0-8]\\d|429496729[0-5])|0[xX]0*[\\da-fA-F]{1,8}|([\\da-fA-F]{1,4}\\:){7}[\\da-fA-F]{1,4}|([\\da-fA-F]{1,4}\\:){6}((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])))(\\:\\d+)?(/(?:[^?#\\s/]+/)*(?:[^?#\\s/]{0,}(?:\\?[^?#\\s/]*)?(?:#.*)?)?)?", + + // emailRegExp: [protected] String + // Used for validating input as correct email address. Taken from dojox.validate + emailRegExp: "<?(mailto\\:)([!#-'*+\\-\\/-9=?A-Z^-~]+[.])*[!#-'*+\\-\\/-9=?A-Z^-~]+" /*username*/ + "@" + + "((?:(?:[\\da-zA-Z](?:[-\\da-zA-Z]{0,61}[\\da-zA-Z])?)\\.)+(?:[a-zA-Z](?:[-\\da-zA-Z]{0,6}[\\da-zA-Z])?)\\.?)|localhost|^[^-][a-zA-Z0-9_-]*>?", // host. + + // htmlTemplate: [protected] String + // String used for templating the HTML to insert at the desired point. + htmlTemplate: "<a href=\"${urlInput}\" _djrealurl=\"${urlInput}\"" + + " target=\"${targetSelect}\"" + + ">${textInput}</a>", + + // tag: [protected] String + // Tag used for the link type. + tag: "a", + + // _hostRxp [private] RegExp + // Regular expression used to validate url fragments (ip address, hostname, etc) + _hostRxp: /^((([^\[:]+):)?([^@]+)@)?(\[([^\]]+)\]|([^\[:]*))(:([0-9]+))?$/, + + // _userAtRxp [private] RegExp + // Regular expression used to validate e-mail address fragment. + _userAtRxp: /^([!#-'*+\-\/-9=?A-Z^-~]+[.])*[!#-'*+\-\/-9=?A-Z^-~]+@/i, + + // linkDialogTemplate: [protected] String + // Template for contents of TooltipDialog to pick URL + linkDialogTemplate: [ + "<table><tr><td>", + "<label for='${id}_urlInput'>${url}</label>", + "</td><td>", + "<input data-dojo-type='dijit.form.ValidationTextBox' required='true' " + + "id='${id}_urlInput' name='urlInput' data-dojo-props='intermediateChanges:true'/>", + "</td></tr><tr><td>", + "<label for='${id}_textInput'>${text}</label>", + "</td><td>", + "<input data-dojo-type='dijit.form.ValidationTextBox' required='true' id='${id}_textInput' " + + "name='textInput' data-dojo-props='intermediateChanges:true'/>", + "</td></tr><tr><td>", + "<label for='${id}_targetSelect'>${target}</label>", + "</td><td>", + "<select id='${id}_targetSelect' name='targetSelect' data-dojo-type='dijit.form.Select'>", + "<option selected='selected' value='_self'>${currentWindow}</option>", + "<option value='_blank'>${newWindow}</option>", + "<option value='_top'>${topWindow}</option>", + "<option value='_parent'>${parentWindow}</option>", + "</select>", + "</td></tr><tr><td colspan='2'>", + "<button data-dojo-type='dijit.form.Button' type='submit' id='${id}_setButton'>${set}</button>", + "<button data-dojo-type='dijit.form.Button' type='button' id='${id}_cancelButton'>${buttonCancel}</button>", + "</td></tr></table>" + ].join(""), + + _initButton: function(){ + this.inherited(arguments); + + // Setup to lazy create TooltipDialog first time the button is clicked + this.button.loadDropDown = lang.hitch(this, "_loadDropDown"); + + this._connectTagEvents(); + }, + _loadDropDown: function(callback){ + // Called the first time the button is pressed. Initialize TooltipDialog. + require([ + "dojo/i18n", // i18n.getLocalization + "../../TooltipDialog", + "../../registry", // registry.byId, registry.getUniqueId + "../../form/Button", // used by template + "../../form/Select", // used by template + "../../form/ValidationTextBox", // used by template + "dojo/i18n!../../nls/common", + "dojo/i18n!../nls/LinkDialog" + ], lang.hitch(this, function(i18n, TooltipDialog, registry){ + var _this = this; + this.tag = this.command == 'insertImage' ? 'img' : 'a'; + var messages = lang.delegate(i18n.getLocalization("dijit", "common", this.lang), + i18n.getLocalization("dijit._editor", "LinkDialog", this.lang)); + var dropDown = (this.dropDown = this.button.dropDown = new TooltipDialog({ + title: messages[this.command + "Title"], + execute: lang.hitch(this, "setValue"), + onOpen: function(){ + _this._onOpenDialog(); + TooltipDialog.prototype.onOpen.apply(this, arguments); + }, + onCancel: function(){ + setTimeout(lang.hitch(_this, "_onCloseDialog"),0); + } + })); + messages.urlRegExp = this.urlRegExp; + messages.id = registry.getUniqueId(this.editor.id); + this._uniqueId = messages.id; + this._setContent(dropDown.title + + "<div style='border-bottom: 1px black solid;padding-bottom:2pt;margin-bottom:4pt'></div>" + + string.substitute(this.linkDialogTemplate, messages)); + dropDown.startup(); + this._urlInput = registry.byId(this._uniqueId + "_urlInput"); + this._textInput = registry.byId(this._uniqueId + "_textInput"); + this._setButton = registry.byId(this._uniqueId + "_setButton"); + this.connect(registry.byId(this._uniqueId + "_cancelButton"), "onClick", function(){ + this.dropDown.onCancel(); + }); + if(this._urlInput){ + this.connect(this._urlInput, "onChange", "_checkAndFixInput"); + } + if(this._textInput){ + this.connect(this._textInput, "onChange", "_checkAndFixInput"); + } + + // Build up the dual check for http/https/file:, and mailto formats. + this._urlRegExp = new RegExp("^" + this.urlRegExp + "$", "i"); + this._emailRegExp = new RegExp("^" + this.emailRegExp + "$", "i"); + this._urlInput.isValid = lang.hitch(this, function(){ + // Function over-ride of isValid to test if the input matches a url or a mailto style link. + var value = this._urlInput.get("value"); + return this._urlRegExp.test(value) || this._emailRegExp.test(value); + }); + + // Listen for enter and execute if valid. + this.connect(dropDown.domNode, "onkeypress", function(e){ + if(e && e.charOrCode == keys.ENTER && + !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey){ + if(!this._setButton.get("disabled")){ + dropDown.onExecute(); + dropDown.execute(dropDown.get('value')); + } + } + }); + + callback(); + })); + }, + + _checkAndFixInput: function(){ + // summary: + // A function to listen for onChange events and test the input contents + // for valid information, such as valid urls with http/https/ftp and if + // not present, try and guess if the input url is relative or not, and if + // not, append http:// to it. Also validates other fields as determined by + // the internal _isValid function. + var self = this; + var url = this._urlInput.get("value"); + var fixupUrl = function(url){ + var appendHttp = false; + var appendMailto = false; + if(url && url.length > 1){ + url = lang.trim(url); + if(url.indexOf("mailto:") !== 0){ + if(url.indexOf("/") > 0){ + if(url.indexOf("://") === -1){ + // Check that it doesn't start with / or ./, which would + // imply 'target server relativeness' + if(url.charAt(0) !== '/' && url.indexOf("./") !== 0){ + if(self._hostRxp.test(url)){ + appendHttp = true; + } + } + } + }else if(self._userAtRxp.test(url)){ + // If it looks like a foo@, append a mailto. + appendMailto = true; + } + } + } + if(appendHttp){ + self._urlInput.set("value", "http://" + url); + } + if(appendMailto){ + self._urlInput.set("value", "mailto:" + url); + } + self._setButton.set("disabled", !self._isValid()); + }; + if(this._delayedCheck){ + clearTimeout(this._delayedCheck); + this._delayedCheck = null; + } + this._delayedCheck = setTimeout(function(){ + fixupUrl(url); + }, 250); + }, + + _connectTagEvents: function(){ + // summary: + // Over-ridable function that connects tag specific events. + this.editor.onLoadDeferred.addCallback(lang.hitch(this, function(){ + this.connect(this.editor.editNode, "ondblclick", this._onDblClick); + })); + }, + + _isValid: function(){ + // summary: + // Internal function to allow validating of the inputs + // for a link to determine if set should be disabled or not + // tags: + // protected + return this._urlInput.isValid() && this._textInput.isValid(); + }, + + _setContent: function(staticPanel){ + // summary: + // Helper for _initButton above. Not sure why it's a separate method. + this.dropDown.set({ + parserScope: "dojo", // make parser search for dojoType/data-dojo-type even if page is multi-version + content: staticPanel + }); + }, + + _checkValues: function(args){ + // summary: + // Function to check the values in args and 'fix' them up as needed. + // args: Object + // Content being set. + // tags: + // protected + if(args && args.urlInput){ + args.urlInput = args.urlInput.replace(/"/g, """); + } + return args; + }, + + setValue: function(args){ + // summary: + // Callback from the dialog when user presses "set" button. + // tags: + // private + //TODO: prevent closing popup if the text is empty + this._onCloseDialog(); + if(has("ie") < 9){ //see #4151 + var sel = rangeapi.getSelection(this.editor.window); + var range = sel.getRangeAt(0); + var a = range.endContainer; + if(a.nodeType === 3){ + // Text node, may be the link contents, so check parent. + // This plugin doesn't really support nested HTML elements + // in the link, it assumes all link content is text. + a = a.parentNode; + } + if(a && (a.nodeName && a.nodeName.toLowerCase() !== this.tag)){ + // Still nothing, one last thing to try on IE, as it might be 'img' + // and thus considered a control. + a = win.withGlobal(this.editor.window, + "getSelectedElement", selectionapi, [this.tag]); + } + if(a && (a.nodeName && a.nodeName.toLowerCase() === this.tag)){ + // Okay, we do have a match. IE, for some reason, sometimes pastes before + // instead of removing the targeted paste-over element, so we unlink the + // old one first. If we do not the <a> tag remains, but it has no content, + // so isn't readily visible (but is wrong for the action). + if(this.editor.queryCommandEnabled("unlink")){ + // Select all the link children, then unlink. The following insert will + // then replace the selected text. + win.withGlobal(this.editor.window, + "selectElementChildren", selectionapi, [a]); + this.editor.execCommand("unlink"); + } + } + } + // make sure values are properly escaped, etc. + args = this._checkValues(args); + this.editor.execCommand('inserthtml', + string.substitute(this.htmlTemplate, args)); + }, + + _onCloseDialog: function(){ + // summary: + // Handler for close event on the dialog + this.editor.focus(); + }, + + _getCurrentValues: function(a){ + // summary: + // Over-ride for getting the values to set in the dropdown. + // a: + // The anchor/link to process for data for the dropdown. + // tags: + // protected + var url, text, target; + if(a && a.tagName.toLowerCase() === this.tag){ + url = a.getAttribute('_djrealurl') || a.getAttribute('href'); + target = a.getAttribute('target') || "_self"; + text = a.textContent || a.innerText; + win.withGlobal(this.editor.window, "selectElement", selectionapi, [a, true]); + }else{ + text = win.withGlobal(this.editor.window, selectionapi.getSelectedText); + } + return {urlInput: url || '', textInput: text || '', targetSelect: target || ''}; //Object; + }, + + _onOpenDialog: function(){ + // summary: + // Handler for when the dialog is opened. + // If the caret is currently in a URL then populate the URL's info into the dialog. + var a; + if(has("ie") < 9){ + // IE is difficult to select the element in, using the range unified + // API seems to work reasonably well. + var sel = rangeapi.getSelection(this.editor.window); + var range = sel.getRangeAt(0); + a = range.endContainer; + if(a.nodeType === 3){ + // Text node, may be the link contents, so check parent. + // This plugin doesn't really support nested HTML elements + // in the link, it assumes all link content is text. + a = a.parentNode; + } + if(a && (a.nodeName && a.nodeName.toLowerCase() !== this.tag)){ + // Still nothing, one last thing to try on IE, as it might be 'img' + // and thus considered a control. + a = win.withGlobal(this.editor.window, + "getSelectedElement", selectionapi, [this.tag]); + } + }else{ + a = win.withGlobal(this.editor.window, + "getAncestorElement", selectionapi, [this.tag]); + } + this.dropDown.reset(); + this._setButton.set("disabled", true); + this.dropDown.set("value", this._getCurrentValues(a)); + }, + + _onDblClick: function(e){ + // summary: + // Function to define a behavior on double clicks on the element + // type this dialog edits to select it and pop up the editor + // dialog. + // e: Object + // The double-click event. + // tags: + // protected. + if(e && e.target){ + var t = e.target; + var tg = t.tagName? t.tagName.toLowerCase() : ""; + if(tg === this.tag && domAttr.get(t,"href")){ + var editor = this.editor; + + win.withGlobal(editor.window, + "selectElement", + selectionapi, [t]); + + editor.onDisplayChanged(); + + // Call onNormalizedDisplayChange() now, rather than on timer. + // On IE, when focus goes to the first <input> in the TooltipDialog, the editor loses it's selection. + // Later if onNormalizedDisplayChange() gets called via the timer it will disable the LinkDialog button + // (actually, all the toolbar buttons), at which point clicking the <input> will close the dialog, + // since (for unknown reasons) focus.js ignores disabled controls. + if(editor._updateTimer){ + clearTimeout(editor._updateTimer); + delete editor._updateTimer; + } + editor.onNormalizedDisplayChanged(); + + var button = this.button; + setTimeout(function(){ + // Focus shift outside the event handler. + // IE doesn't like focus changes in event handles. + button.set("disabled", false); + button.loadAndOpenDropDown().then(function(){ + if(button.dropDown.focus){ + button.dropDown.focus(); + } + }); + }, 10); + } + } + } +}); + +var ImgLinkDialog = declare("dijit._editor.plugins.ImgLinkDialog", [LinkDialog], { + // summary: + // This plugin extends LinkDialog and adds in a plugin for handling image links. + // provides the image link dialog. + // + // description: + // The command provided by this plugin is: + // * insertImage + + // linkDialogTemplate: [protected] String + // Over-ride for template since img dialog doesn't need target that anchor tags may. + linkDialogTemplate: [ + "<table><tr><td>", + "<label for='${id}_urlInput'>${url}</label>", + "</td><td>", + "<input dojoType='dijit.form.ValidationTextBox' regExp='${urlRegExp}' " + + "required='true' id='${id}_urlInput' name='urlInput' data-dojo-props='intermediateChanges:true'/>", + "</td></tr><tr><td>", + "<label for='${id}_textInput'>${text}</label>", + "</td><td>", + "<input data-dojo-type='dijit.form.ValidationTextBox' required='false' id='${id}_textInput' " + + "name='textInput' data-dojo-props='intermediateChanges:true'/>", + "</td></tr><tr><td>", + "</td><td>", + "</td></tr><tr><td colspan='2'>", + "<button data-dojo-type='dijit.form.Button' type='submit' id='${id}_setButton'>${set}</button>", + "<button data-dojo-type='dijit.form.Button' type='button' id='${id}_cancelButton'>${buttonCancel}</button>", + "</td></tr></table>" + ].join(""), + + // htmlTemplate: [protected] String + // String used for templating the <img> HTML to insert at the desired point. + htmlTemplate: "<img src=\"${urlInput}\" _djrealurl=\"${urlInput}\" alt=\"${textInput}\" />", + + // tag: [protected] String + // Tag used for the link type (img). + tag: "img", + + _getCurrentValues: function(img){ + // summary: + // Over-ride for getting the values to set in the dropdown. + // a: + // The anchor/link to process for data for the dropdown. + // tags: + // protected + var url, text; + if(img && img.tagName.toLowerCase() === this.tag){ + url = img.getAttribute('_djrealurl') || img.getAttribute('src'); + text = img.getAttribute('alt'); + win.withGlobal(this.editor.window, + "selectElement", selectionapi, [img, true]); + }else{ + text = win.withGlobal(this.editor.window, selectionapi.getSelectedText); + } + return {urlInput: url || '', textInput: text || ''}; //Object; + }, + + _isValid: function(){ + // summary: + // Over-ride for images. You can have alt text of blank, it is valid. + // tags: + // protected + return this._urlInput.isValid(); + }, + + _connectTagEvents: function(){ + // summary: + // Over-ridable function that connects tag specific events. + this.inherited(arguments); + this.editor.onLoadDeferred.addCallback(lang.hitch(this, function(){ + // Use onmousedown instead of onclick. Seems that IE eats the first onclick + // to wrap it in a selector box, then the second one acts as onclick. See #10420 + this.connect(this.editor.editNode, "onmousedown", this._selectTag); + })); + }, + + _selectTag: function(e){ + // summary: + // A simple event handler that lets me select an image if it is clicked on. + // makes it easier to select images in a standard way across browsers. Otherwise + // selecting an image for edit becomes difficult. + // e: Event + // The mousedown event. + // tags: + // private + if(e && e.target){ + var t = e.target; + var tg = t.tagName? t.tagName.toLowerCase() : ""; + if(tg === this.tag){ + win.withGlobal(this.editor.window, + "selectElement", + selectionapi, [t]); + } + } + }, + + _checkValues: function(args){ + // summary: + // Function to check the values in args and 'fix' them up as needed + // (special characters in the url or alt text) + // args: Object + // Content being set. + // tags: + // protected + if(args && args.urlInput){ + args.urlInput = args.urlInput.replace(/"/g, """); + } + if(args && args.textInput){ + args.textInput = args.textInput.replace(/"/g, """); + } + return args; + }, + + _onDblClick: function(e){ + // summary: + // Function to define a behavior on double clicks on the element + // type this dialog edits to select it and pop up the editor + // dialog. + // e: Object + // The double-click event. + // tags: + // protected. + if(e && e.target){ + var t = e.target; + var tg = t.tagName ? t.tagName.toLowerCase() : ""; + if(tg === this.tag && domAttr.get(t,"src")){ + var editor = this.editor; + + win.withGlobal(editor.window, + "selectElement", + selectionapi, [t]); + editor.onDisplayChanged(); + + // Call onNormalizedDisplayChange() now, rather than on timer. + // On IE, when focus goes to the first <input> in the TooltipDialog, the editor loses it's selection. + // Later if onNormalizedDisplayChange() gets called via the timer it will disable the LinkDialog button + // (actually, all the toolbar buttons), at which point clicking the <input> will close the dialog, + // since (for unknown reasons) focus.js ignores disabled controls. + if(editor._updateTimer){ + clearTimeout(editor._updateTimer); + delete editor._updateTimer; + } + editor.onNormalizedDisplayChanged(); + + var button = this.button; + setTimeout(function(){ + // Focus shift outside the event handler. + // IE doesn't like focus changes in event handles. + button.set("disabled", false); + button.loadAndOpenDropDown().then(function(){ + if(button.dropDown.focus){ + button.dropDown.focus(); + } + }); + }, 10); + } + } + } +}); + +// Register these plugins +_Plugin.registry["createLink"] = function(){ + return new LinkDialog({command: "createLink"}); +}; +_Plugin.registry["insertImage"] = function(){ + return new ImgLinkDialog({command: "insertImage"}); +}; + + +// Export both LinkDialog and ImgLinkDialog +LinkDialog.ImgLinkDialog = ImgLinkDialog; +return LinkDialog; +}); + +}, +'dijit/DropDownMenu':function(){ +define([ + "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 + + return declare("dijit.DropDownMenu", [_MenuBase, _OnDijitClickMixin], { + // summary: + // A menu, without features for context menu (Meaning, drop down menu) + + templateString: template, + + baseClass: "dijitMenu", + + 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]); + }, + + _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); + 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; + } + } + }); +}); + +}, +'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){ + +/*===== + var DropDownMenu = dijit.DropDownMenu; +=====*/ + +// 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 + }); +} + +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: [], + + // 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, similar 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(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); + }, + + // thanks burstlib! + _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){ + // summary: + // 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 + }, + + _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){ + // summary: + // 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 + }, + + bindDomNode: function(/*String|DomNode*/ node){ + // summary: + // Attach menu to given node + node = dom.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, + window = this._iframeContentWindow(iframe); + cn = win.withGlobal(window, win.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 == win.body() ? win.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. + domAttr.set(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 = 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) : []; + + 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. + + 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 + + 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); + } + } + }, + + unBindDomNode: function(/*String|DomNode*/ nodeName){ + // summary: + // Detach menu from given node + + 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; + } + + // 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(); + } + + // 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); + } + } + + domAttr.remove(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 event.stop(e). (Shift-F10 on windows doesn't generate the + // oncontextmenu event.) + + if(!this._openTimer){ + this._openTimer = setTimeout(lang.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 ifc = domGeometry.position(iframe, true), + window = this._iframeContentWindow(iframe), + scroll = win.withGlobal(window, "_docScroll", dojo); + + 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; + } + }else{ + coords = domGeometry.position(target, true); + coords.x += 10; + coords.y += 10; + } + + 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; + + 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._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 + }; + }, + + uninitialize: function(){ + array.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this); + this.inherited(arguments); + } +}); + +}); + +}, +'dijit/form/_CheckBoxMixin':function(){ +define([ + "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 + + return declare("dijit.form._CheckBoxMixin", null, { + // summary: + // Mixin to provide widget functionality corresponding to an HTML checkbox + // + // 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. + // + + // 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). + 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, + + // aria-pressed for toggle buttons, and aria-checked for checkboxes + _aria_attr: "aria-checked", + + _setReadOnlyAttr: function(/*Boolean*/ value){ + this._set("readOnly", value); + domAttr.set(this.focusNode, 'readOnly', value); + this.focusNode.setAttribute("aria-readonly", value); + }, + + // 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, + + postMixInProperties: function(){ + if(this.value == ""){ + this.value = "on"; + } + this.inherited(arguments); + }, + + 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: + // 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); + } + }); +}); + +}, +'dijit/layout/ContentPane':function(){ +define([ + "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. + + +return declare("dijit.layout.ContentPane", [_Widget, _ContentPaneResizeMixin], { + // summary: + // A widget containing an HTML fragment, specified inline + // or by uri. Fragment may include widgets. + // + // description: + // This widget embeds a document fragment in the page, specified + // either by uri, javascript generated markup or DOM reference. + // Any widgets within this content are instantiated and managed, + // but laid out according to the HTML structure. Unlike IFRAME, + // ContentPane embeds a document fragment as would be found + // inside the BODY tag of a full HTML document. It should not + // contain the HTML, HEAD, or BODY tags. + // For more advanced functionality with scripts and + // stylesheets, see dojox.layout.ContentPane. This widget may be + // used stand alone or as a base class for other widgets. + // ContentPane is useful as a child of other layout containers + // such as BorderContainer or TabContainer, but note that those + // widgets can contain any widget as a child. + // + // example: + // Some quick samples: + // To change the innerHTML: cp.set('content', '<b>new content</b>') + // + // Or you can send it a NodeList: cp.set('content', dojo.query('div [class=selected]', userSelection)) + // + // To do an ajax update: cp.set('href', url) + + // href: String + // The href of the content that displays now. + // Set this at construction if you want to load data externally when the + // pane is shown. (Set preload=true to load it immediately.) + // 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>. + // I.e., strip <html> and <head> (and it's contents) from the href + extractContent: false, + + // parseOnLoad: Boolean + // Parse content and create the widgets, if any. + parseOnLoad: true, + + // 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: kernel._scopeName, + + // preventCache: Boolean + // Prevent caching of data from href's by appending a timestamp to the href. + preventCache: false, + + // preload: Boolean + // Force load of data on initialization even if pane is hidden. + preload: false, + + // refreshOnShow: Boolean + // Refresh (re-download) content when pane goes from hidden to shown + refreshOnShow: false, + + // loadingMessage: String + // Message that shows while downloading + loadingMessage: "<span class='dijitContentPaneLoading'><span class='dijitInline dijitIconLoading'></span>${loadingState}</span>", + + // errorMessage: String + // Message that shows if an error occurs + errorMessage: "<span class='dijitContentPaneError'><span class='dijitInline dijitIconError'></span>${errorState}</span>", + + // isLoaded: [readonly] Boolean + // True if the ContentPane has data in it, either specified + // during initialization (via href or inline content), or set + // via set('content', ...) / set('href', ...) + // + // False if it doesn't have any content, or if ContentPane is + // still in the process of downloading href. + isLoaded: false, + + 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 data-dojo-type="dijit.layout.ContentPane" data-dojo-props="href: './bar', ioArgs: {timeout: 500}"> + ioArgs: {}, + + // onLoadDeferred: [readonly] dojo.Deferred + // This is the `dojo.Deferred` returned by set('href', ...) and refresh(). + // Calling onLoadDeferred.addCallback() or addErrback() registers your + // callback to be called only once, when the prior set('href', ...) call or + // the initial href parameter to the constructor finishes loading. + // + // This is different than an onLoad() handler which gets called any time any href + // or content is loaded. + onLoadDeferred: null, + + // 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. + _setTitleAttr: null, + + // Flag to parser that I'll parse my contents, so it shouldn't. + stopParser: true, + + // template: [private] Boolean + // Flag from the parser that this ContentPane is inside a template + // so the contents are pre-parsed. + // (TODO: this declaration can be commented out in 2.0) + template: false, + + create: function(params, srcNodeRef){ + // Convert a srcNodeRef argument into a content parameter, so that the original contents are + // 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 = win.doc.createDocumentFragment(); + srcNodeRef = dom.byId(srcNodeRef); + while(srcNodeRef.firstChild){ + df.appendChild(srcNodeRef.firstChild); + } + params = lang.delegate(params, {content: df}); + } + this.inherited(arguments, [params, srcNodeRef]); + }, + + postMixInProperties: function(){ + this.inherited(arguments); + var messages = i18n.getLocalization("dijit", "loading", this.lang); + this.loadingMessage = string.substitute(this.loadingMessage, messages); + this.errorMessage = string.substitute(this.errorMessage, messages); + }, + + buildRendering: function(){ + this.inherited(arguments); + + // Since we have no template we need to set this.containerNode ourselves, to make getChildren() work. + // For subclasses of ContentPane that do have a template, does nothing. + if(!this.containerNode){ + this.containerNode = this.domNode; + } + + // remove the title attribute so it doesn't show up when hovering + // over a node (TODO: remove in 2.0, no longer needed after #11490) + this.domNode.title = ""; + + if(!domAttr.get(this.domNode,"role")){ + this.domNode.setAttribute("role", "group"); + } + }, + + startup: function(){ + // summary: + // Call startup() on all children including non _Widget ones like dojo.dnd.Source objects + + // This starts all the widgets + this.inherited(arguments); + + // And this catches stuff like dojo.dnd.Source + if(this._contentSetter){ + array.forEach(this._contentSetter.parseResults, function(obj){ + if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){ + obj.startup(); + obj._started = true; + } + }, this); + } + }, + + setHref: function(/*String|Uri*/ href){ + // summary: + // Deprecated. Use set('href', ...) instead. + kernel.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.", "", "2.0"); + return this.set("href", href); + }, + _setHrefAttr: function(/*String|Uri*/ href){ + // summary: + // Hook so set("href", ...) works. + // description: + // Reset the (external defined) content of this pane and replace with new url + // Note: It delays the download until widget is shown if preload is false. + // href: + // url to the page you want to get, must be within the same domain as your mainpage + + // Cancel any in-flight requests (a set('href', ...) will cancel any in-flight set('href', ...)) + this.cancel(); + + this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel")); + this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad")); + + this._set("href", href); + + // _setHrefAttr() is called during creation and by the user, after creation. + // Assuming preload == false, only in the second case do we actually load the URL; + // otherwise it's done in startup(), and only if this widget is shown. + if(this.preload || (this._created && this._isShown())){ + this._load(); + }else{ + // Set flag to indicate that href needs to be loaded the next time the + // ContentPane is made visible + this._hrefChanged = true; + } + + return this.onLoadDeferred; // Deferred + }, + + setContent: function(/*String|DomNode|Nodelist*/data){ + // summary: + // Deprecated. Use set('content', ...) instead. + kernel.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use set('content', ...) instead.", "", "2.0"); + this.set("content", data); + }, + _setContentAttr: function(/*String|DomNode|Nodelist*/data){ + // summary: + // Hook to make set("content", ...) work. + // Replaces old content with data content, include style classes from old content + // data: + // the new Content may be String, DomNode or NodeList + // + // if data is a NodeList (or an array of nodes) nodes are copied + // so you can import nodes from another document implicitly + + // clear href so we can't run refresh and clear content + // refresh should only work if we downloaded the content + this._set("href", ""); + + // Cancel any in-flight requests (a set('content', ...) will cancel any in-flight set('href', ...)) + this.cancel(); + + // 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 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 data-dojo-type=ContentPane>...</div>) + // or as initialization parameter (ie: new ContentPane({content: ...}) + 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; // Deferred + }, + _getContentAttr: function(){ + // summary: + // Hook to make get("content") work + return this.containerNode.innerHTML; + }, + + cancel: function(){ + // summary: + // Cancels an in-flight download of content + if(this._xhrDfd && (this._xhrDfd.fired == -1)){ + this._xhrDfd.cancel(); + } + delete this._xhrDfd; // garbage collect + + this.onLoadDeferred = null; + }, + + uninitialize: function(){ + if(this._beingDestroyed){ + this.cancel(); + } + this.inherited(arguments); + }, + + destroyRecursive: function(/*Boolean*/ preserveDom){ + // summary: + // Destroy the ContentPane and its contents + + // if we have multiple controllers destroying us, bail after the first + if(this._beingDestroyed){ + return; + } + this.inherited(arguments); + }, + + _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 necessary processing, including href download and layout/resize of + // child widget(s) + + this.inherited(arguments); + + if(this.href){ + if(!this._xhrDfd && // if there's an href that isn't already being loaded + (!this.isLoaded || this._hrefChanged || this.refreshOnShow) + ){ + return this.refresh(); // If child has an href, promise that fires when the load is complete + } + } + }, + + refresh: function(){ + // summary: + // [Re]download contents of href and display + // description: + // 1. cancels any currently in-flight requests + // 2. posts "loading..." message + // 3. sends XHR to download new data + + // Cancel possible prior in-flight request + this.cancel(); + + 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 + }, + + _load: function(){ + // summary: + // Load/reload the href specified in this.href + + // display loading message + this._setContent(this.onDownloadStart(), true); + + var self = this; + var getArgs = { + preventCache: (this.preventCache || this.refreshOnShow), + url: this.href, + handleAs: "text" + }; + if(lang.isObject(this.ioArgs)){ + lang.mixin(getArgs, this.ioArgs); + } + + var hand = (this._xhrDfd = (this.ioMethod || xhr.get)(getArgs)); + + hand.addCallback(function(html){ + try{ + self._isDownloaded = true; + self._setContent(html, false); + self.onDownloadEnd(); + }catch(err){ + self._onError('Content', err); // onContentError + } + delete self._xhrDfd; + return html; + }); + + hand.addErrback(function(err){ + if(!hand.canceled){ + // show error message in the pane + self._onError('Download', err); // onDownloadError + } + delete self._xhrDfd; + return err; + }); + + // Remove flag saying that a load is needed + delete this._hrefChanged; + }, + + _onLoadHandler: function(data){ + // summary: + // This is called whenever new content is being loaded + this._set("isLoaded", true); + try{ + this.onLoadDeferred.callback(data); + }catch(e){ + console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message); + } + }, + + _onUnloadHandler: function(){ + // summary: + // This is called whenever the content is being unloaded + this._set("isLoaded", false); + try{ + this.onUnload(); + }catch(e){ + console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message); + } + }, + + destroyDescendants: function(/*Boolean*/ preserveDom){ + // summary: + // Destroy all the widgets inside the ContentPane and empty containerNode + + // Make sure we call onUnload (but only when the ContentPane has real content) + if(this.isLoaded){ + this._onUnloadHandler(); + } + + // Even if this.isLoaded == false there might still be a "Loading..." message + // to erase, so continue... + + // For historical reasons we need to delete all widgets under this.containerNode, + // even ones that the user has created manually. + var setter = this._contentSetter; + array.forEach(this.getChildren(), function(widget){ + if(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 + 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 + if(!preserveDom){ + html._emptyNode(this.containerNode); + } + + // Delete any state information we have about current contents + delete this._singleChild; + }, + + _setContent: function(/*String|DocumentFragment*/ cont, /*Boolean*/ isFakeContent){ + // summary: + // Insert the content into the container node + + // first get rid of child widgets + this.destroyDescendants(); + + // 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 html._ContentSetter)){ + setter = this._contentSetter = new html._ContentSetter({ + node: this.containerNode, + _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); + try{ + this.containerNode.innerHTML = errMess; + }catch(e){ + console.error('Fatal '+this.id+' could not change content due to '+e.message, e); + } + })/*, + _onError */ + }); + } + + var setterParams = lang.mixin({ + cleanContent: this.cleanContent, + extractContent: this.extractContent, + parseContent: !cont.domNode && this.parseOnLoad, + parserScope: this.parserScope, + startup: false, + dir: this.dir, + lang: this.lang, + textDir: this.textDir + }, this._contentSetterParams || {}); + + 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; + + if(this.doLayout){ + this._checkIfSingleChild(); + } + + if(!isFakeContent){ + if(this._started){ + // Startup each top level child widget (and they will start their children, recursively) + 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 + this._scheduleLayout(); + } + + this._onLoadHandler(cont); + } + }, + + _onError: function(type, err, consoleText){ + this.onLoadDeferred.errback(err); + + // shows user the string that is returned by on[type]Error + // override 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 + this._setContent(errText, true); + } + }, + + // EVENT's, should be overide-able + onLoad: function(/*===== data =====*/){ + // summary: + // Event hook, is called after everything is loaded and widgetified + // tags: + // callback + }, + + onUnload: function(){ + // summary: + // Event hook, is called before old content is cleared + // tags: + // callback + }, + + onDownloadStart: function(){ + // summary: + // Called before download starts. + // description: + // The string returned by this function will be the html + // that tells the user we are loading something. + // Override with your own function if you want to change text. + // tags: + // extension + return this.loadingMessage; + }, + + onContentError: function(/*Error*/ /*===== error =====*/){ + // summary: + // Called on DOM faults, require faults etc. in content. + // + // In order to display an error message in the pane, return + // the error message from this method, as an HTML string. + // + // By default (if this method is not overriden), it returns + // nothing, so the error message is just printed to the console. + // tags: + // extension + }, + + onDownloadError: function(/*Error*/ /*===== error =====*/){ + // summary: + // Called when download error occurs. + // + // In order to display an error message in the pane, return + // the error message from this method, as an HTML string. + // + // Default behavior (if this method is not overriden) is to display + // the error message inside the pane. + // tags: + // extension + return this.errorMessage; + }, + + onDownloadEnd: function(){ + // summary: + // Called when download is finished. + // tags: + // callback + } +}); + +}); + +}, +'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/_KeyNavContainer':function(){ +define([ + "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){ + +/*===== + var _FocusMixin = dijit._FocusMixin; + var _Container = dijit._Container; +=====*/ + + // module: + // dijit/_KeyNavContainer + // summary: + // A _Container with keyboard navigation of its children. + + return declare("dijit._KeyNavContainer", [_FocusMixin, _Container], { + + // 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. + +/*===== + // focusedChild: [protected] Widget + // The currently focused child widget, or null if there isn't one + focusedChild: null, +=====*/ + + // 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", + + 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 + + // TODO: call this automatically from my own postCreate() + + 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"); + }, + + startupKeyNavChildren: function(){ + kernel.deprecated("startupKeyNavChildren() call no longer needed", "", "2.0"); + }, + + startup: function(){ + this.inherited(arguments); + array.forEach(this.getChildren(), lang.hitch(this, "_startupChild")); + }, + + addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){ + this.inherited(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 + this.focusChild(this._getFirstFocusableChild()); + }, + + focusLastChild: function(){ + // summary: + // Focus the last focusable child in the container. + // tags: + // protected + this.focusChild(this._getLastFocusableChild()); + }, + + focusNext: function(){ + // summary: + // Focus the next widget + // tags: + // protected + this.focusChild(this._getNextFocusableChild(this.focusedChild, 1)); + }, + + 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); + }, + + focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){ + // summary: + // 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: + // 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); + }, + + _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) + // Also, _onFocus() would fire when focus went directly to a child widget due to mouse click. + + // 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; } + + 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 + domAttr.set(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){ + domAttr.set(this.domNode, "tabIndex", this.tabIndex); + } + this.focusedChild = null; + 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(); + event.stop(evt); + } + }, + + _onChildBlur: function(/*dijit._Widget*/ /*===== widget =====*/){ + // summary: + // Called when focus leaves a child widget to go + // to a sibling widget. + // Used by MenuBase.js (TODO: move code there) + // 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 + } + }); +}); + +}, +'dijit/layout/utils':function(){ +define([ + "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() + + var layout = lang.getObject("layout", true, dijit); + /*===== layout = dijit.layout =====*/ + + 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) + }; + }; + + function capitalize(word){ + return word.substring(0,1).toUpperCase() + word.substring(1); + } + + function size(widget, dim){ + // size the child + var newSize = widget.resize ? widget.resize(dim) : domGeometry.setMarginBox(widget.domNode, dim); + + // 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); + } + } + + 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 = lang.mixin({}, dim); + + domClass.add(container, "dijitLayoutContainer"); + + // 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"; })); + + // 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) + } + + // 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"; + + domClass.add(elm, "dijitAlign" + capitalize(pos)); + + // Size adjustments to make to this child widget + var sizeSetting = {}; + + // 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; + } + + // 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"; + } + }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); + } + }); + }; + + + return { + marginBox2contentBox: layout.marginBox2contentBox, + layoutChildren: layout.layoutChildren + }; +}); + +}, +'dijit/form/DataList':function(){ +define([ + "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 + + function toItem(/*DOMNode*/ option){ + // summary: + // Convert <option> node to hash + return { + id: option.value, + value: option.value, + name: lang.trim(option.innerText || option.textContent || '') + }; + } + + 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> + // | ... + + constructor: function(/*Object?*/ params, /*DomNode|String*/ srcNodeRef){ + // store pointer to original DOM tree + this.domNode = dom.byId(srcNodeRef); + + lang.mixin(this, params); + if(this.id){ + registry.add(this); // add to registry so it can be easily found by id + } + this.domNode.style.display = "none"; + + this.inherited(arguments, [{ + data: query("option", this.domNode).map(toItem) + }]); + }, + + destroy: function(){ + registry.remove(this.id); + }, + + 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); + } + }); +}); + +}, +'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/_editor/_Plugin':function(){ +define([ + "dojo/_base/connect", // connect.connect + "dojo/_base/declare", // declare + "dojo/_base/lang", // lang.mixin, lang.hitch + "../form/Button" +], function(connect, declare, lang, Button){ + +// module: +// dijit/_editor/_Plugin +// summary: +// Base class for a "plugin" to the editor, which is usually +// a single button on the Toolbar and some associated code + + +var _Plugin = declare("dijit._editor._Plugin", null, { + // summary: + // Base class for a "plugin" to the editor, which is usually + // a single button on the Toolbar and some associated code + + constructor: function(/*Object?*/args){ + this.params = args || {}; + lang.mixin(this, this.params); + this._connects=[]; + this._attrPairNames = {}; + }, + + // editor: [const] dijit.Editor + // Points to the parent editor + editor: null, + + // iconClassPrefix: [const] String + // The CSS class name for the button node is formed from `iconClassPrefix` and `command` + iconClassPrefix: "dijitEditorIcon", + + // button: dijit._Widget? + // Pointer to `dijit.form.Button` or other widget (ex: `dijit.form.FilteringSelect`) + // that is added to the toolbar to control this plugin. + // If not specified, will be created on initialization according to `buttonClass` + button: null, + + // command: String + // String like "insertUnorderedList", "outdent", "justifyCenter", etc. that represents an editor command. + // Passed to editor.execCommand() if `useDefaultCommand` is true. + command: "", + + // useDefaultCommand: Boolean + // If true, this plugin executes by calling Editor.execCommand() with the argument specified in `command`. + useDefaultCommand: true, + + // buttonClass: Widget Class + // Class of widget (ex: dijit.form.Button or dijit.form.FilteringSelect) + // that is added to the toolbar to control this plugin. + // This is used to instantiate the button, unless `button` itself is specified directly. + buttonClass: Button, + + // disabled: Boolean + // Flag to indicate if this plugin has been disabled and should do nothing + // helps control button state, among other things. Set via the setter api. + disabled: false, + + getLabel: function(/*String*/key){ + // summary: + // Returns the label to use for the button + // tags: + // private + return this.editor.commands[key]; // String + }, + + _initButton: function(){ + // summary: + // Initialize the button or other widget that will control this plugin. + // This code only works for plugins controlling built-in commands in the editor. + // tags: + // protected extension + if(this.command.length){ + var label = this.getLabel(this.command), + editor = this.editor, + className = this.iconClassPrefix+" "+this.iconClassPrefix + this.command.charAt(0).toUpperCase() + this.command.substr(1); + if(!this.button){ + var props = lang.mixin({ + label: label, + dir: editor.dir, + lang: editor.lang, + showLabel: false, + iconClass: className, + dropDown: this.dropDown, + tabIndex: "-1" + }, this.params || {}); + this.button = new this.buttonClass(props); + } + } + if(this.get("disabled") && this.button){ + this.button.set("disabled", this.get("disabled")); + } + }, + + destroy: function(){ + // summary: + // Destroy this plugin + + var h; + while(h = this._connects.pop()){ h.remove(); } + if(this.dropDown){ + this.dropDown.destroyRecursive(); + } + }, + + connect: function(o, f, tf){ + // summary: + // Make a connect.connect() that is automatically disconnected when this plugin is destroyed. + // Similar to `dijit._Widget.connect`. + // tags: + // protected + this._connects.push(connect.connect(o, f, this, tf)); + }, + + updateState: function(){ + // summary: + // Change state of the plugin to respond to events in the editor. + // description: + // This is called on meaningful events in the editor, such as change of selection + // or caret position (but not simple typing of alphanumeric keys). It gives the + // plugin a chance to update the CSS of its button. + // + // For example, the "bold" plugin will highlight/unhighlight the bold button depending on whether the + // characters next to the caret are bold or not. + // + // Only makes sense when `useDefaultCommand` is true, as it calls Editor.queryCommandEnabled(`command`). + var e = this.editor, + c = this.command, + checked, enabled; + if(!e || !e.isLoaded || !c.length){ return; } + var disabled = this.get("disabled"); + if(this.button){ + try{ + enabled = !disabled && e.queryCommandEnabled(c); + if(this.enabled !== enabled){ + this.enabled = enabled; + this.button.set('disabled', !enabled); + } + if(typeof this.button.checked == 'boolean'){ + checked = e.queryCommandState(c); + if(this.checked !== checked){ + this.checked = checked; + this.button.set('checked', e.queryCommandState(c)); + } + } + }catch(e){ + console.log(e); // FIXME: we shouldn't have debug statements in our code. Log as an error? + } + } + }, + + setEditor: function(/*dijit.Editor*/ editor){ + // summary: + // Tell the plugin which Editor it is associated with. + + // TODO: refactor code to just pass editor to constructor. + + // FIXME: detach from previous editor!! + this.editor = editor; + + // FIXME: prevent creating this if we don't need to (i.e., editor can't handle our command) + this._initButton(); + + // Processing for buttons that execute by calling editor.execCommand() + if(this.button && this.useDefaultCommand){ + if(this.editor.queryCommandAvailable(this.command)){ + this.connect(this.button, "onClick", + lang.hitch(this.editor, "execCommand", this.command, this.commandArg) + ); + }else{ + // hide button because editor doesn't support command (due to browser limitations) + this.button.domNode.style.display = "none"; + } + } + + this.connect(this.editor, "onNormalizedDisplayChanged", "updateState"); + }, + + setToolbar: function(/*dijit.Toolbar*/ toolbar){ + // summary: + // Tell the plugin to add it's controller widget (often a button) + // to the toolbar. Does nothing if there is no controller widget. + + // TODO: refactor code to just pass toolbar to constructor. + + if(this.button){ + toolbar.addChild(this.button); + } + // console.debug("adding", this.button, "to:", toolbar); + }, + + set: function(/* attribute */ name, /* anything */ value){ + // summary: + // Set a property on a plugin + // name: + // The property to set. + // value: + // The value to set in the property. + // description: + // Sets named properties on a plugin which may potentially be handled by a + // setter in the plugin. + // For example, if the plugin has a properties "foo" + // and "bar" and a method named "_setFooAttr", calling: + // | plugin.set("foo", "Howdy!"); + // would be equivalent to writing: + // | plugin._setFooAttr("Howdy!"); + // and: + // | plugin.set("bar", 3); + // would be equivalent to writing: + // | plugin.bar = 3; + // + // set() may also be called with a hash of name/value pairs, ex: + // | plugin.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); + if(this[names.s]){ + // use the explicit setter + var result = this[names.s].apply(this, Array.prototype.slice.call(arguments, 1)); + }else{ + this._set(name, value); + } + return result || this; + }, + + get: function(name){ + // summary: + // Get a property from a plugin. + // name: + // The property to get. + // description: + // Get a named property from a plugin. 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 plugin has a properties "foo" + // and "bar" and a method named "_getFooAttr", calling: + // | plugin.get("foo"); + // would be equivalent to writing: + // | plugin._getFooAttr(); + // and: + // | plugin.get("bar"); + // would be equivalent to writing: + // | plugin.bar; + var names = this._getAttrNames(name); + return this[names.g] ? this[names.g]() : this[name]; + }, + + _setDisabledAttr: function(disabled){ + // summary: + // Function to set the plugin state and call updateState to make sure the + // button is updated appropriately. + this.disabled = disabled; + this.updateState(); + }, + + _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] = { + s: "_set"+uc+"Attr", + g: "_get"+uc+"Attr" + }); + }, + + _set: function(/*String*/ name, /*anything*/ value){ + // summary: + // Helper function to set new value for specified attribute + this[name] = value; + } +}); + +// Hash mapping plugin name to factory, used for registering plugins +_Plugin.registry = {}; + +return _Plugin; + +}); + +}, +'dijit/form/CheckBox':function(){ +define([ + "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; +=====*/ + + // module: + // dijit/form/CheckBox + // summary: + // Checkbox widget + + // 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: + // 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: template, + + baseClass: "dijitCheckBox", + + _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; + } + if(this._created){ + this.set('checked', newValue, priorityChange); + } + }, + _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); + }, + + // Override behavior from Button, since we don't have an iconNode + _setIconClassAttr: null, + + postMixInProperties: function(){ + this.inherited(arguments); + + // 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" : ""; + }, + + _fillContent: function(){ + // Override Button::_fillContent() since it doesn't make sense for CheckBox, + // since CheckBox doesn't even have a container + }, + + _onFocus: function(){ + if(this.id){ + query("label[for='"+this.id+"']").addClass("dijitFocusedLabel"); + } + this.inherited(arguments); + }, + + _onBlur: function(){ + if(this.id){ + query("label[for='"+this.id+"']").removeClass("dijitFocusedLabel"); + } + this.inherited(arguments); + } + }); +}); + +}, +'url:dijit/templates/MenuBar.html':"<div class=\"dijitMenuBar dijitMenuPassive\" data-dojo-attach-point=\"containerNode\" role=\"menubar\" tabIndex=\"${tabIndex}\" data-dojo-attach-event=\"onkeypress: _onKeyPress\"></div>\n", +'dijit/tree/_dndSelector':function(){ +define([ + "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`. + + + 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 + + /*===== + // selection: Hash<String, DomNode> + // (id, DomNode) map for every TreeNode that's currently selected. + // The DOMNode is the TreeNode.rowNode. + selection: {}, + =====*/ + + constructor: function(){ + // summary: + // Initialization + // tags: + // private + + this.selection={}; + this.anchor = null; + + this.tree.domNode.setAttribute("aria-multiselect", !this.singular); + + 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")) + ); + }, + + // 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, + + // 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; + }, + + selectNone: function(){ + // summary: + // Unselects all items + // tags: + // private + + this.setSelection([]); + return this; // self + }, + + 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. + + 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 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.selection[node.id]; + })); + array.forEach(this._setDifference(newSelection, oldSelection), lang.hitch(this, function(node){ + node.setSelected(true); + this.selection[node.id] = node; + })); + this._updateSelectionProperties(); + }, + _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. + + array.forEach(ys, function(y){ y.__exclude__ = true; }); + var ret = array.filter(xs, function(x){ return !x.__exclude__; }); + + // 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] + + 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); + }, + // mouse events + onMouseDown: function(e){ + // summary: + // Event processor for onmousedown/ontouchstart + // e: Event + // onmousedown/ontouchstart event + // tags: + // protected + + // ignore click on expando node + if(!this.current || this.tree.isExpandoNode(e.target, this.current)){ return; } + + if(!mouse.isLeft(e)){ return; } // ignore right-click + + 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); + }, + + onMouseUp: function(e){ + // summary: + // Event processor for onmouseup/ontouchend + // e: Event + // onmouseup/ontouchend event + // tags: + // 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, connect.isCopyKey(e), e.shiftKey); + }, + onMouseMove: function(/*===== e =====*/){ + // summary: + // event processor for onmousemove/ontouchmove + // e: Event + // onmousemove/ontouchmove event + this._doDeselect = false; + }, + + _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"); + } + }, + + userSelect: function(node, multi, range){ + // 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: + // 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 = 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; + } + } + } + }, + + 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]; + return { + data: widget, + type: ["treeNode"] + }; // dojo.dnd.Item + }, + + 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); + } + } + }); +}); + +}, +'dojo/html':function(){ +define(["./_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.. + + // 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: + // 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 + }; + +/*==== + dojo.html._emptyNode = function(node){ + // summary: + // removes all child nodes from the given node + // node: DOMNode + // the parent element + }; +=====*/ + dojo.html._emptyNode = domConstruct.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 + domConstruct.empty(node); + + 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"); + } + } + + // return DomNode + return node; + }; + + // 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: "", + + // 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" + lang.mixin(this, params || {}); + + // 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 ); + + 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(); + + return this.node; + }, + setContent: function(){ + // summary: + // sets the content on the node + + 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 + + // 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); + }, + + 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(lang.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]; } + } + } + + // 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 + + 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); + } + }, + + _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() + + dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){ + // 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(lang.mixin( + params, + { content: cont, node: node } + )); + return op.set(); + } + }; + + return dojo.html; +}); + +}, +'dijit/_PaletteMixin':function(){ +define([ + "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){ + +/*===== + var _CssStateMixin = dijit._CssStateMixin; +=====*/ + +// module: +// dijit/_PaletteMixin +// summary: +// A keyboard accessible palette, for picking a color/emoticon/etc. + +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. + + // 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: "", + + // _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, +=====*/ + +/*===== + // _xDim: [protected] Integer + // This is the number of cells horizontally across. + _xDim: null, +=====*/ + +/*===== + // _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", + + _dyeFactory: function(value /*===== , row, col =====*/){ + // summary: + // Return instance of dijit.Dye for specified cell of palette + // tags: + // extension + var dyeClassObj = lang.getObject(this.dyeClass); + return new dyeClassObj(value); + }, + + _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 + + this._cells = []; + var url = this._blankGif; + + this.connect(this.gridNode, "ondijitclick", "_onCellClick"); + + 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); + + var cellNode = domConstruct.create("td", { + "class": this.cellClass, + tabIndex: "-1", + title: titles[value], + role: "gridcell" + }); + + // prepare cell inner structure + cellObject.fillCell(cellNode, url); + + domConstruct.place(cellNode, rowNode); + + cellNode.index = this._cells.length; + + // save cell info into _cells + this._cells.push({node:cellNode, dye:cellObject}); + } + } + } + this._xDim = choices[0].length; + this._yDim = choices.length; + + // 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. + + 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 + ) + ); + } + }, + + postCreate: 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 + focus.focus(this._currentFocus); + }, + + _onCellClick: function(/*Event*/ evt){ + // summary: + // Handler for click, enter key & space key. Selects the cell. + // evt: + // The event. + // tags: + // private + + var target = evt.target; + + // 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; + } + + var 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. + this._setCurrent(target); + focus.focus(target); + this._setValueAttr(value, true); + + event.stop(evt); + }, + + _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"); + } + + // Set tabIndex of new cell + this._currentFocus = node; + if(node){ + domAttr.set(node, "tabIndex", this.tabIndex); + } + }, + + _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){ + domClass.remove(this._cells[this._selectedCell].node, this.cellClass + "Selected"); + } + 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; + domClass.add(this._cells[i].node, this.cellClass + "Selected"); + break; + } + } + } + + // record new value, or null if no matching cell + this._set("value", this._selectedCell >= 0 ? value : null); + + if(priorityChange || priorityChange === undefined){ + this.onChange(value); + } + }, + + onChange: function(/*===== value =====*/){ + // summary: + // Callback when a cell is selected. + // value: String + // Value corresponding to cell. + }, + + _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 + + // 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); + + // 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); + } + }, + + _getDye: function(/*DomNode*/ cell){ + // summary: + // Get JS object for given cell DOMNode + + return this._cells[cell.index].dye; + } +}); + +/*===== +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. + }, + + fillCell: function(cell, blankGif){ + // summary: + // Add cell DOMNode inner structure + // cell: DomNode + // The surrounding cell + // blankGif: String + // URL for blank cell image + } + } +); +=====*/ + +}); + +}, +'url:dijit/templates/TitlePane.html':"<div>\n\t<div data-dojo-attach-event=\"onclick:_onTitleClick, onkeypress:_onTitleKey\"\n\t\t\tclass=\"dijitTitlePaneTitle\" data-dojo-attach-point=\"titleBarNode\">\n\t\t<div class=\"dijitTitlePaneTitleFocus\" data-dojo-attach-point=\"focusNode\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" data-dojo-attach-point=\"arrowNode\" class=\"dijitArrowNode\" role=\"presentation\"\n\t\t\t/><span data-dojo-attach-point=\"arrowNodeInner\" class=\"dijitArrowNodeInner\"></span\n\t\t\t><span data-dojo-attach-point=\"titleNode\" class=\"dijitTitlePaneTextNode\"></span>\n\t\t</div>\n\t</div>\n\t<div class=\"dijitTitlePaneContentOuter\" data-dojo-attach-point=\"hideNode\" role=\"presentation\">\n\t\t<div class=\"dijitReset\" data-dojo-attach-point=\"wipeNode\" role=\"presentation\">\n\t\t\t<div class=\"dijitTitlePaneContentInner\" data-dojo-attach-point=\"containerNode\" role=\"region\" id=\"${id}_pane\">\n\t\t\t\t<!-- nested divs because wipeIn()/wipeOut() doesn't work right on node w/padding etc. Put padding on inner div. -->\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</div>\n", +'dijit/form/ValidationTextBox':function(){ +define([ + "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_ = ""; + } + =====*/ + + 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: template, + baseClass: "dijitTextBox dijitValidationTextBox", + + // required: Boolean + // User is required to enter data into this field. + required: false, + + // promptMessage: String + // If defined, display this hint string immediately on focus to the textbox, if empty. + // Also displays if the textbox value is Incomplete (not yet valid but will be with additional input). + // Think of this like a tooltip that tells the user what to do, not an error message + // that tells the user what they've done wrong. + // + // Message disappears when user starts typing. + promptMessage: "", + + // invalidMessage: String + // The message to display if value is invalid. + // The translated string value is read from the message file by default. + // Set to "" to use the promptMessage instead. + invalidMessage: "$_unset_$", + + // missingMessage: String + // The message to display if value is empty and the field is required. + // The translated string value is read from the message file by default. + // Set to "" to use the invalidMessage instead. + missingMessage: "$_unset_$", + + // message: String + // Currently error/prompt message. + // When using the default tooltip implementation, this will only be + // displayed when the field is focused. + message: "", + + // constraints: dijit.form.ValidationTextBox.__Constraints + // user-defined object needed to pass parameters to the validator functions + constraints: {}, + + // regExp: [extension protected] String + // regular expression string used to validate the input + // Do not specify both regExp and regExpGen + regExp: ".*", + + 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. + // tags: + // extension protected + return this.regExp; // String + }, + + // state: [readonly] String + // Shows current state (ie, validation result) of input (""=Normal, Incomplete, or Error) + state: "", + + // tooltipPosition: String[] + // See description of `dijit.Tooltip.defaultPosition` for details on this parameter. + tooltipPosition: [], + + _setValueAttr: function(){ + // summary: + // Hook so set('value', ...) works. + this.inherited(arguments); + this.validate(this.focused); + }, + + validator: function(/*anything*/ value, /*dijit.form.ValidationTextBox.__Constraints*/ constraints){ + // summary: + // Overridable function used to validate the text input against the regular expression. + // tags: + // protected + return (new RegExp("^(?:" + this.regExpGen(constraints) + ")"+(this.required?"":"?")+"$")).test(value) && + (!this.required || !this._isEmpty(value)) && + (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean + }, + + _isValidSubset: function(){ + // summary: + // Returns true if the value is either already valid or could be made valid by appending characters. + // This is used for validation while the user [may be] still typing. + return this.textbox.value.search(this._partialre) == 0; + }, + + isValid: function(/*Boolean*/ /*===== isFocused =====*/){ + // summary: + // Tests if value is valid. + // Can override with your own routine in a subclass. + // tags: + // protected + return this.validator(this.textbox.value, this.constraints); + }, + + _isEmpty: function(value){ + // summary: + // Checks for whitespace + return (this.trim ? /^\s*$/ : /^$/).test(value); // Boolean + }, + + getErrorMessage: function(/*Boolean*/ /*===== isFocused =====*/){ + // summary: + // Return an error message to show if appropriate + // tags: + // protected + return (this.required && this._isEmpty(this.textbox.value)) ? this.missingMessage : this.invalidMessage; // String + }, + + getPromptMessage: function(/*Boolean*/ /*===== isFocused =====*/){ + // summary: + // Return a hint message to show when widget is first focused + // tags: + // protected + return this.promptMessage; // String + }, + + _maskValidSubsetError: true, + validate: function(/*Boolean*/ isFocused){ + // summary: + // Called by oninit, onblur, and onkeypress. + // description: + // Show missing or invalid messages if appropriate, and highlight textbox field. + // tags: + // protected + var message = ""; + var isValid = this.disabled || this.isValid(isFocused); + if(isValid){ this._maskValidSubsetError = true; } + 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")); + 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 + message = this.getErrorMessage(isFocused); + }else if(this.state == "Incomplete"){ + message = this.getPromptMessage(isFocused); // show the prompt whenever the value is not yet complete + this._maskValidSubsetError = !this._hasBeenBlurred || isFocused; // no Incomplete warnings while focused + }else if(isEmpty){ + message = this.getPromptMessage(isFocused); // show the prompt whenever there's no error and no text + } + this.set("message", message); + + return isValid; + }, + + displayMessage: function(/*String*/ message){ + // summary: + // Overridable method to display validation errors/hints. + // By default uses a tooltip. + // tags: + // extension + 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.inherited(arguments); + }, + + //////////// INITIALIZATION METHODS /////////////////////////////////////// + + constructor: function(){ + this.constraints = {}; + }, + + _setConstraintsAttr: function(/*Object*/ constraints){ + if(!constraints.locale && this.lang){ + constraints.locale = this.lang; + } + this._set("constraints", constraints); + this._computePartialRE(); + }, + + _computePartialRE: function(){ + var p = this.regExpGen(this.constraints); + this.regExp = p; + var partialre = ""; + // 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){ + switch(re.charAt(0)){ + case '{': + case '+': + case '?': + case '*': + case '^': + case '$': + case '|': + case '(': + partialre += re; + break; + case ")": + partialre += "|$)"; + break; + default: + partialre += "(?:"+re+"|$)"; + break; + } + } + );} + try{ // this is needed for now since the above regexp parsing needs more test verification + "".search(partialre); + }catch(e){ // should never be here unless the original RE is bad or the parsing is bad + partialre = this.regExp; + console.warn('RegExp error in ' + this.declaredClass + ': ' + this.regExp); + } // should never be here unless the original RE is bad or the parsing is bad + this._partialre = "^(?:" + partialre + ")$"; + }, + + postMixInProperties: function(){ + this.inherited(arguments); + 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; } + if(!this.missingMessage){ this.missingMessage = this.invalidMessage; } + this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls attachPoints + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + this.inherited(arguments); // call FormValueWidget._setDisabledAttr() + this._refreshState(); + }, + + _setRequiredAttr: function(/*Boolean*/ value){ + this._set("required", value); + this.focusNode.setAttribute("aria-required", value); + this._refreshState(); + }, + + _setMessageAttr: function(/*String*/ message){ + this._set("message", message); + this.displayMessage(message); + }, + + reset:function(){ + // Overrides dijit.form.TextBox.reset() by also + // hiding errors about partial matches + this._maskValidSubsetError = true; + this.inherited(arguments); + }, + + _onBlur: function(){ + // the message still exists but for back-compat, and to erase the tooltip + // (if the message is being displayed as a tooltip), call displayMessage('') + this.displayMessage(''); + + this.inherited(arguments); + } + }); +}); + +}, +'dijit/layout/BorderContainer':function(){ +define([ + "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){ + +/*===== + var _WidgetBase = dijit._WidgetBase; + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _LayoutWidget = dijit.layout._LayoutWidget; +=====*/ + +// module: +// dijit/layout/BorderContainer +// summary: +// Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides. + +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 + +/*===== + // 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, +=====*/ + + // 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, + + 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>', + + constructor: function(){ + this._handlers = []; + }, + + postMixInProperties: function(){ + this.inherited(arguments); + + this.horizontal = /top|bottom/.test(this.region); + this._factor = /top|left/.test(this.region) ? 1 : -1; + this._cookieName = this.container.id + "_" + this.region; + }, + + 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; + } + } + }, + + _computeMaxSize: function(){ + // summary: + // Return the maximum size that my corresponding pane can be set to + + 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 + + return Math.min(this.child.maxSize, childSize + spaceAvailable); + }, + + _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"); + + // 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; + } + + if(this.container.persist){ + cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365}); + } + }, + + _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 + + 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); + }, + + startup: function(){ + if(this._started){ return; } + array.forEach(this.getChildren(), this._setupChild, this); + this.inherited(arguments); + }, + + _setupChild: function(/*dijit._Widget*/ child){ + // Override _LayoutWidget._setupChild(). + + var region = child.region; + if(region){ + this.inherited(arguments); + + domClass.add(child.domNode, this.baseClass+"Pane"); + + var ltr = this.isLeftToRight(); + if(region == "leading"){ region = ltr ? "left" : "right"; } + if(region == "trailing"){ region = ltr ? "right" : "left"; } + + // 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; + + domConstruct.place(splitter.domNode, child.domNode, "after"); + + // 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. + } + }, + + layout: function(){ + // Implement _LayoutWidget.layout() virtual method. + this._layoutChildren(); + }, + + addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ + // Override _LayoutWidget.addChild(). + this.inherited(arguments); + if(this._started){ + this.layout(); //OPT + } + }, + + removeChild: function(/*dijit._Widget*/ child){ + // Override _LayoutWidget.removeChild(). + + 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. + 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"); + }, + + getChildren: function(){ + // Override _LayoutWidget.getChildren() to only return real children, not the splitters. + return array.filter(this.inherited(arguments), function(widget){ + return !widget.isSplitter; + }); + }, + + // 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; + }, + + resize: function(newSize, currentSize){ + // Overrides _LayoutWidget.resize(). + + // 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); + + domStyle.set(node, "padding", "0px"); + } + + this.inherited(arguments); + }, + + _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 + + 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; + } + + // 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 0; + }); + + // 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); + } + }); + + // 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 + layoutUtils.layoutChildren(this.domNode, dim, childrenAndSplitters, + changedChildId, changedChildSize); + }, + + destroyRecursive: function(){ + // Destroy splitters first, while getChildren() still works + array.forEach(this.getChildren(), function(child){ + var splitter = child._splitterWidget; + if(splitter){ + splitter.destroy(); + } + 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.) +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: '', + + // 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, + + // 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, + + // 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, + + // 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 +}); + +// For monkey patching +BorderContainer._Splitter = _Splitter; +BorderContainer._Gutter = _Gutter; + +return BorderContainer; +}); + +}, +'dojo/number':function(){ +define(["./_base/kernel", "./_base/lang", "./i18n", "./i18n!./cldr/nls/number", "./string", "./regexp"], + function(dojo, lang, i18n, nlsNumber, dstring, dregexp) { + + // module: + // dojo/number + // summary: + // TODOC + +lang.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; +} +=====*/ + +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 + + 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 +}; + +//dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough +dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough + +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. + + //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 + 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; +} +=====*/ + +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 + + 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); + } + + 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); + } + + // Truncate fractional + if(maxPlaces < fractional.length){ + valueParts[1] = fractional.substr(0, maxPlaces); + } + }else{ + if(valueParts[1]){ valueParts.pop(); } + } + + // 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); + } + + // Truncate whole + if(patternDigits.indexOf("#") == -1){ + valueParts[0] = valueParts[0].substr(valueParts[0].length - pad); + } + } + + // 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 || "."); +}; + +/*===== +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 +}; + +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); + + 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; + }); + } + +//TODO: substitute localized sign/percent/permille/etc.? + + // 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){ + // 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; +} +=====*/ + +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 = 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 + ); + + var exponentRE = dregexp.buildGroupRE(flags.exponent, + function(q){ + if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; } + return ""; + } + ); + + 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; +} +=====*/ + +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 = dregexp.buildGroupRE(flags.signed, + function(q){ return q ? "[-+]" : ""; }, + true + ); + + var numberRE = dregexp.buildGroupRE(flags.separator, + function(sep){ + if(!sep){ + return "(?:\\d+)"; + } + + sep = dregexp.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 +}; + +return dojo.number; +}); + +}, +'dojo/data/util/filter':function(){ +define(["dojo/_base/lang"], function(lang) { + // module: + // dojo/data/util/filter + // summary: + // TODOC + +var filter = lang.getObject("dojo.data.util.filter", true); + +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. + + 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 + } + +}; + +return filter; +}); + +}, +'dijit/_WidgetsInTemplateMixin':function(){ +define([ + "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 + + return declare("dijit._WidgetsInTemplateMixin", null, { + // summary: + // Mixin to supplement _TemplatedMixin when template contains widgets + + // _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, + + // 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, + + _beforeFillContent: function(){ + if(this.widgetsInTemplate){ + // Before copying over content, instantiate widgets in template + var node = this.domNode; + + 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 + })); + + this._supportingWidgets = registry.findWidgets(node); + + this._attachTemplateNodes(cw, function(n,p){ + return n[p]; + }); + } + }, + + startup: function(){ + array.forEach(this._startupWidgets, function(w){ + if(w && !w._started && w.startup){ + w.startup(); + } + }); + this.inherited(arguments); + } + }); +}); + +}, +'dijit/form/HorizontalRuleLabels':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/number", // number.format + "dojo/query", // query + "./HorizontalRule" +], function(declare, number, query, HorizontalRule){ + +/*===== + var HorizontalRule = dijit.form.HorizontalRule; +=====*/ + +// module: +// dijit/form/HorizontalRuleLabels +// summary: +// Labels for `dijit.form.HorizontalSlider` + +return declare("dijit.form.HorizontalRuleLabels", HorizontalRule, { + // summary: + // Labels for `dijit.form.HorizontalSlider` + + templateString: '<div class="dijitRuleContainer dijitRuleContainerH dijitRuleLabelsContainer dijitRuleLabelsContainerH"></div>', + + // labelStyle: String + // CSS style to apply to individual text labels + labelStyle: "", + + // labels: String[]? + // Array of text labels to render - evenly spaced from left-to-right or bottom-to-top. + // Alternately, minimum and maximum can be specified, to get numeric labels. + labels: [], + + // numericMargin: Integer + // Number of generated numeric labels that should be rendered as '' on the ends when labels[] are not specified + numericMargin: 0, + + // numericMinimum: Integer + // Leftmost label value for generated numeric labels when labels[] are not specified + minimum: 0, + + // numericMaximum: Integer + // Rightmost label value for generated numeric labels when labels[] are not specified + maximum: 1, + + // constraints: Object + // pattern, places, lang, et al (see dojo.number) for generated numeric labels when labels[] are not specified + constraints: {pattern:"#%"}, + + _positionPrefix: '<div class="dijitRuleLabelContainer dijitRuleLabelContainerH" style="left:', + _labelPrefix: '"><div class="dijitRuleLabel dijitRuleLabelH">', + _suffix: '</div></div>', + + _calcPosition: function(pos){ + // summary: + // Returns the value to be used in HTML for the label as part of the left: attribute + // tags: + // protected extension + return pos; + }, + + _genHTML: function(pos, ndx){ + return this._positionPrefix + this._calcPosition(pos) + this._positionSuffix + this.labelStyle + this._labelPrefix + this.labels[ndx] + this._suffix; + }, + + getLabels: function(){ + // summary: + // Overridable function to return array of labels to use for this slider. + // Can specify a getLabels() method instead of a labels[] array, or min/max attributes. + // tags: + // protected extension + + // if the labels array was not specified directly, then see if <li> children were + var labels = this.labels; + if(!labels.length){ + // for markup creation, labels are specified as child elements + labels = query("> li", this.srcNodeRef).map(function(node){ + return String(node.innerHTML); + }); + } + this.srcNodeRef.innerHTML = ''; + // if the labels were not specified directly and not as <li> children, then calculate numeric labels + if(!labels.length && this.count > 1){ + var start = this.minimum; + var inc = (this.maximum - start) / (this.count-1); + for(var i=0; i < this.count; i++){ + labels.push((i < this.numericMargin || i >= (this.count-this.numericMargin)) ? '' : number.format(start, this.constraints)); + start += inc; + } + } + return labels; + }, + + postMixInProperties: function(){ + this.inherited(arguments); + this.labels = this.getLabels(); + this.count = this.labels.length; + } +}); + +}); + +}, +'url:dijit/templates/MenuBarItem.html':"<div class=\"dijitReset dijitInline dijitMenuItem dijitMenuItemLabel\" data-dojo-attach-point=\"focusNode\" role=\"menuitem\" tabIndex=\"-1\"\n\t\tdata-dojo-attach-event=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<span data-dojo-attach-point=\"containerNode\"></span>\n</div>\n", +'dijit/form/FilteringSelect':function(){ +define([ + "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 + + + return declare("dijit.form.FilteringSelect", [MappedTextBox, ComboBoxMixin], { + // summary: + // An enhanced version of the HTML SELECT tag, populated dynamically + // + // description: + // An enhanced version of the HTML SELECT tag, populated dynamically. It works + // very nicely with very large data sets because it can load and page data as needed. + // It also resembles ComboBox, but does not allow values outside of the provided ones. + // If OPTION tags are used as the data provider via markup, then the + // OPTION tag's child text node is used as the displayed value when selected + // while the OPTION tag's value attribute is used as the widget value on form submit. + // To set the default value when using OPTION tags, specify the selected + // attribute on 1 of the child OPTION tags. + // + // Similar features: + // - There is a drop down list of possible values. + // - You can only enter a value from the drop down list. (You can't + // enter an arbitrary value.) + // - The value submitted with the form is the hidden value (ex: CA), + // not the displayed value a.k.a. label (ex: California) + // + // Enhancements over plain HTML version: + // - If you type in some text then it will filter down the list of + // possible values in the drop down list. + // - List can be specified either as a static list or via a javascript + // function (that can get the list from a server) + + // required: Boolean + // True (default) if user is required to enter a value into this field. + required: true, + + _lastDisplayedValue: "", + + _isValidSubset: function(){ + return this._opened; + }, + + isValid: function(){ + // Overrides ValidationTextBox.isValid() + return this.item || (!this.required && this.get('displayedValue') == ""); // #5974 + }, + + _refreshState: function(){ + if(!this.searchTimer){ // state will be refreshed after results are returned + this.inherited(arguments); + } + }, + + _callbackSetLabel: function( + /*Array*/ result, + /*Object*/ query, + /*Object*/ options, + /*Boolean?*/ priorityChange){ + // summary: + // 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((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.set("value", '', priorityChange || (priorityChange === undefined && !this.focused), this.textbox.value, null); + }else{ + this.set('item', result[0], priorityChange); + } + }, + + _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(query[this.searchAttr] !== this._lastQuery){ + return; + } + this.inherited(arguments); + + if(this.item === undefined){ // item == undefined for keyboard search + // If the search returned no items that means that the user typed + // in something invalid (and they can't make it valid by typing more characters), + // so flag the FilteringSelect as being in an invalid state + this.validate(true); + } + }, + + _getValueAttr: function(){ + // summary: + // Hook for get('value') to work. + + // don't get the textbox value but rather the previously set hidden value. + // Use this.valueNode.value which isn't always set for other MappedTextBox widgets until blur + return this.valueNode.value; + }, + + _getValueField: function(){ + // Overrides ComboBox._getValueField() + return "value"; + }, + + _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; } + + 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){ + // 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 + this.inherited(arguments); + this._lastDisplayedValue = this.textbox.value; + }, + + _getDisplayQueryString: function(/*String*/ text){ + return text.replace(/([\\\*\?])/g, "\\$1"); + }, + + _setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){ + // summary: + // Hook so set('displayedValue', label) works. + // description: + // Sets textbox to display label. Also performs reverse lookup + // to set the hidden value. label should corresponding to item.searchAttr. + + if(label == null){ label = ''; } + + // This is called at initialization along with every custom setter. + // Usually (or always?) the call can be ignored. If it needs to be + // processed then at least make sure that the XHR request doesn't trigger an onChange() + // event, even if it returns after creation has finished + if(!this._created){ + if(!("displayedValue" in this.params)){ + return; + } + priorityChange = false; + } + + // Do a reverse lookup to map the specified displayedValue to the hidden value. + // Note that if there's a custom labelFunc() this code + if(this.store){ + this.closeDropDown(); + 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 + // sense to the user + this.textbox.value = label; + this._lastDisplayedValue = label; + this._set("displayedValue", label); // for watch("displayedValue") notification + var _this = this; + var options = { + ignoreCase: this.ignoreCase, + deep: true + }; + 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/_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=[]; + + 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([ + "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", + + _onClick: function(/*Event*/ e){ + // summary: + // 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; + }, + + postCreate: function(){ + this.inherited(arguments); + dom.setSelectable(this.focusNode, false); + }, + + 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 + }, + + _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; + } +}); + +}); + +}, +'dojo/colors':function(){ +define(["./_base/kernel", "./_base/lang", "./_base/Color", "./_base/array"], function(dojo, lang, Color, ArrayUtil) { + // module: + // dojo/colors + // summary: + // TODOC + + var ColorExt = lang.getObject("dojo.colors", true); + +//TODO: this module appears to break naming conventions + +/*===== + lang.mixin(dojo, { + colors: { + // summary: Color utilities, extending Base dojo.Color + } + }); +=====*/ + + // 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 + }; + + 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 + }; + + 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; +}); + +}, +'url:dijit/form/templates/Spinner.html':"<div class=\"dijit dijitReset dijitInline dijitLeft\"\n\tid=\"widget_${id}\" role=\"presentation\"\n\t><div class=\"dijitReset dijitButtonNode dijitSpinnerButtonContainer\"\n\t\t><input class=\"dijitReset dijitInputField dijitSpinnerButtonInner\" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t/><div class=\"dijitReset dijitLeft dijitButtonNode dijitArrowButton dijitUpArrowButton\"\n\t\t\tdata-dojo-attach-point=\"upArrowNode\"\n\t\t\t><div class=\"dijitArrowButtonInner\"\n\t\t\t\t><input class=\"dijitReset dijitInputField\" value=\"▲\" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t\t\t${_buttonInputDisabled}\n\t\t\t/></div\n\t\t></div\n\t\t><div class=\"dijitReset dijitLeft dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\tdata-dojo-attach-point=\"downArrowNode\"\n\t\t\t><div class=\"dijitArrowButtonInner\"\n\t\t\t\t><input class=\"dijitReset dijitInputField\" value=\"▼\" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t\t\t${_buttonInputDisabled}\n\t\t\t/></div\n\t\t></div\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' data-dojo-attach-point=\"textbox,focusNode\" type=\"${type}\" data-dojo-attach-event=\"onkeypress:_onKeyPress\"\n\t\t\trole=\"spinbutton\" autocomplete=\"off\" ${!nameAttrSetting}\n\t/></div\n></div>\n", +'dijit/tree/_dndContainer':function(){ +define([ + "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: + // 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 + 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")) + ]; + }, + + 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; + }, + + // mouse events + onMouseOver: function(widget /*===== , evt =====*/){ + // summary: + // Called when mouse is moved over a TreeNode + // widget: TreeNode + // evt: Event + // tags: + // protected + this.current = widget; + }, + + onMouseOut: function(/*===== widget, evt =====*/){ + // summary: + // Called when mouse is moved away from a TreeNode + // widget: TreeNode + // evt: Event + // tags: + // protected + this.current = null; + }, + + _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; + }, + + _addItemClass: function(node, type){ + // summary: + // 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", ""); + } + }); +}); + +}, +'dojo/date/locale':function(){ +define([ + "../_base/kernel", + "../_base/lang", + "../_base/array", + "../date", + "../cldr/supplemental", + "../regexp", + "../string", + "../i18n!../cldr/nls/gregorian" +], function(dojo, lang, array, date, cldr, regexp, string, gregorian) { + // module: + // dojo/date/locale + // summary: + // This modules defines dojo.date.locale, localization methods for Date. + +lang.getObject("date.locale", true, dojo); + +// Localization methods for Date. Honor local customs using locale-dependent dojo.cldr data. + +// Load the bundles containing localization information for +// names and formats + +//NOTE: Everything in this module assumes Gregorian calendars. +// Other calendars will be implemented in separate modules. + + // Format a pattern without literals + function formatPattern(dateObject, bundle, options, pattern){ + return pattern.replace(/([a-z])\1*/ig, function(match){ + var s, pad, + c = match.charAt(0), + l = match.length, + widthList = ["abbr", "wide", "narrow"]; + switch(c){ + case 'G': + s = bundle[(l < 4) ? "eraAbbr" : "eraNames"][dateObject.getFullYear() < 0 ? 0 : 1]; + break; + case 'y': + s = dateObject.getFullYear(); + switch(l){ + case 1: + break; + case 2: + if(!options.fullYear){ + s = String(s); s = s.substr(s.length - 2); + break; + } + // fallthrough + default: + pad = true; + } + break; + case 'Q': + case 'q': + s = Math.ceil((dateObject.getMonth()+1)/3); +// switch(l){ +// case 1: case 2: + pad = true; +// break; +// case 3: case 4: // unimplemented +// } + break; + case 'M': + var m = dateObject.getMonth(); + if(l<3){ + s = m+1; pad = true; + }else{ + var propM = ["months", "format", widthList[l-3]].join("-"); + s = bundle[propM][m]; + } + break; + case 'w': + var firstDay = 0; + s = dojo.date.locale._getWeekOfYear(dateObject, firstDay); pad = true; + break; + case 'd': + s = dateObject.getDate(); pad = true; + break; + case 'D': + s = dojo.date.locale._getDayOfYear(dateObject); pad = true; + break; + case 'E': + var d = dateObject.getDay(); + if(l<3){ + s = d+1; pad = true; + }else{ + var propD = ["days", "format", widthList[l-3]].join("-"); + s = bundle[propD][d]; + } + break; + case 'a': + var timePeriod = (dateObject.getHours() < 12) ? 'am' : 'pm'; + s = options[timePeriod] || bundle['dayPeriods-format-wide-' + timePeriod]; + break; + case 'h': + case 'H': + case 'K': + case 'k': + var h = dateObject.getHours(); + // strange choices in the date format make it impossible to write this succinctly + switch (c){ + case 'h': // 1-12 + s = (h % 12) || 12; + break; + case 'H': // 0-23 + s = h; + break; + case 'K': // 0-11 + s = (h % 12); + break; + case 'k': // 1-24 + s = h || 24; + break; + } + pad = true; + break; + case 'm': + s = dateObject.getMinutes(); pad = true; + break; + case 's': + s = dateObject.getSeconds(); pad = true; + break; + case 'S': + s = Math.round(dateObject.getMilliseconds() * Math.pow(10, l-3)); pad = true; + break; + case 'v': // FIXME: don't know what this is. seems to be same as z? + case 'z': + // We only have one timezone to offer; the one from the browser + s = dojo.date.locale._getZone(dateObject, true, options); + if(s){break;} + l=4; + // fallthrough... use GMT if tz not available + case 'Z': + var offset = dojo.date.locale._getZone(dateObject, false, options); + var tz = [ + (offset<=0 ? "+" : "-"), + string.pad(Math.floor(Math.abs(offset)/60), 2), + string.pad(Math.abs(offset)% 60, 2) + ]; + if(l==4){ + tz.splice(0, 0, "GMT"); + tz.splice(3, 0, ":"); + } + s = tz.join(""); + break; +// case 'Y': case 'u': case 'W': case 'F': case 'g': case 'A': case 'e': +// console.log(match+" modifier unimplemented"); + default: + throw new Error("dojo.date.locale.format: invalid pattern char: "+pattern); + } + if(pad){ s = string.pad(s, l); } + return s; + }); + } + +/*===== + dojo.date.locale.__FormatOptions = function(){ + // selector: String + // choice of 'time','date' (default: date and time) + // formatLength: String + // choice of long, short, medium or full (plus any custom additions). Defaults to 'short' + // datePattern:String + // override pattern with this string + // timePattern:String + // override pattern with this string + // am: String + // override strings for am in times + // pm: String + // override strings for pm in times + // locale: String + // override the locale used to determine formatting rules + // fullYear: Boolean + // (format only) use 4 digit years whenever 2 digit years are called for + // strict: Boolean + // (parse only) strict parsing, off by default + this.selector = selector; + this.formatLength = formatLength; + this.datePattern = datePattern; + this.timePattern = timePattern; + this.am = am; + this.pm = pm; + this.locale = locale; + this.fullYear = fullYear; + this.strict = strict; + } +=====*/ + +dojo.date.locale._getZone = function(/*Date*/dateObject, /*boolean*/getName, /*dojo.date.locale.__FormatOptions?*/options){ + // summary: + // Returns the zone (or offset) for the given date and options. This + // is broken out into a separate function so that it can be overridden + // by timezone-aware code. + // + // dateObject: + // the date and/or time being formatted. + // + // getName: + // Whether to return the timezone string (if true), or the offset (if false) + // + // options: + // The options being used for formatting + if(getName){ + return date.getTimezoneName(dateObject); + }else{ + return dateObject.getTimezoneOffset(); + } +}; + + +dojo.date.locale.format = function(/*Date*/dateObject, /*dojo.date.locale.__FormatOptions?*/options){ + // summary: + // Format a Date object as a String, using locale-specific settings. + // + // description: + // Create a string from a Date object using a known localized pattern. + // By default, this method formats both date and time from dateObject. + // Formatting patterns are chosen appropriate to the locale. Different + // formatting lengths may be chosen, with "full" used by default. + // Custom patterns may be used or registered with translations using + // the dojo.date.locale.addCustomFormats method. + // Formatting patterns are implemented using [the syntax described at + // unicode.org](http://www.unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns) + // + // dateObject: + // the date and/or time to be formatted. If a time only is formatted, + // the values in the year, month, and day fields are irrelevant. The + // opposite is true when formatting only dates. + + options = options || {}; + + var locale = dojo.i18n.normalizeLocale(options.locale), + formatLength = options.formatLength || 'short', + bundle = dojo.date.locale._getGregorianBundle(locale), + str = [], + sauce = lang.hitch(this, formatPattern, dateObject, bundle, options); + if(options.selector == "year"){ + return _processPattern(bundle["dateFormatItem-yyyy"] || "yyyy", sauce); + } + var pattern; + if(options.selector != "date"){ + pattern = options.timePattern || bundle["timeFormat-"+formatLength]; + if(pattern){str.push(_processPattern(pattern, sauce));} + } + if(options.selector != "time"){ + pattern = options.datePattern || bundle["dateFormat-"+formatLength]; + if(pattern){str.push(_processPattern(pattern, sauce));} + } + + return str.length == 1 ? str[0] : bundle["dateTimeFormat-"+formatLength].replace(/\{(\d+)\}/g, + function(match, key){ return str[key]; }); // String +}; + +dojo.date.locale.regexp = function(/*dojo.date.locale.__FormatOptions?*/options){ + // summary: + // Builds the regular needed to parse a localized date + + return dojo.date.locale._parseInfo(options).regexp; // String +}; + +dojo.date.locale._parseInfo = function(/*dojo.date.locale.__FormatOptions?*/options){ + options = options || {}; + var locale = dojo.i18n.normalizeLocale(options.locale), + bundle = dojo.date.locale._getGregorianBundle(locale), + formatLength = options.formatLength || 'short', + datePattern = options.datePattern || bundle["dateFormat-" + formatLength], + timePattern = options.timePattern || bundle["timeFormat-" + formatLength], + pattern; + if(options.selector == 'date'){ + pattern = datePattern; + }else if(options.selector == 'time'){ + pattern = timePattern; + }else{ + pattern = bundle["dateTimeFormat-"+formatLength].replace(/\{(\d+)\}/g, + function(match, key){ return [timePattern, datePattern][key]; }); + } + + var tokens = [], + re = _processPattern(pattern, lang.hitch(this, _buildDateTimeRE, tokens, bundle, options)); + return {regexp: re, tokens: tokens, bundle: bundle}; +}; + +dojo.date.locale.parse = function(/*String*/value, /*dojo.date.locale.__FormatOptions?*/options){ + // summary: + // Convert a properly formatted string to a primitive Date object, + // using locale-specific settings. + // + // description: + // Create a Date object from a string using a known localized pattern. + // By default, this method parses looking for both date and time in the string. + // Formatting patterns are chosen appropriate to the locale. Different + // formatting lengths may be chosen, with "full" used by default. + // Custom patterns may be used or registered with translations using + // the dojo.date.locale.addCustomFormats method. + // + // Formatting patterns are implemented using [the syntax described at + // unicode.org](http://www.unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns) + // When two digit years are used, a century is chosen according to a sliding + // window of 80 years before and 20 years after present year, for both `yy` and `yyyy` patterns. + // year < 100CE requires strict mode. + // + // value: + // A string representation of a date + + // remove non-printing bidi control chars from input and pattern + var controlChars = /[\u200E\u200F\u202A\u202E]/g, + info = dojo.date.locale._parseInfo(options), + tokens = info.tokens, bundle = info.bundle, + re = new RegExp("^" + info.regexp.replace(controlChars, "") + "$", + info.strict ? "" : "i"), + match = re.exec(value && value.replace(controlChars, "")); + + if(!match){ return null; } // null + + var widthList = ['abbr', 'wide', 'narrow'], + result = [1970,0,1,0,0,0,0], // will get converted to a Date at the end + amPm = "", + valid = dojo.every(match, function(v, i){ + if(!i){return true;} + var token=tokens[i-1]; + var l=token.length; + switch(token.charAt(0)){ + case 'y': + if(l != 2 && options.strict){ + //interpret year literally, so '5' would be 5 A.D. + result[0] = v; + }else{ + if(v<100){ + v = Number(v); + //choose century to apply, according to a sliding window + //of 80 years before and 20 years after present year + var year = '' + new Date().getFullYear(), + century = year.substring(0, 2) * 100, + cutoff = Math.min(Number(year.substring(2, 4)) + 20, 99); + result[0] = (v < cutoff) ? century + v : century - 100 + v; + }else{ + //we expected 2 digits and got more... + if(options.strict){ + return false; + } + //interpret literally, so '150' would be 150 A.D. + //also tolerate '1950', if 'yyyy' input passed to 'yy' format + result[0] = v; + } + } + break; + case 'M': + if(l>2){ + var months = bundle['months-format-' + widthList[l-3]].concat(); + if(!options.strict){ + //Tolerate abbreviating period in month part + //Case-insensitive comparison + v = v.replace(".","").toLowerCase(); + months = dojo.map(months, function(s){ return s.replace(".","").toLowerCase(); } ); + } + v = dojo.indexOf(months, v); + if(v == -1){ +// console.log("dojo.date.locale.parse: Could not parse month name: '" + v + "'."); + return false; + } + }else{ + v--; + } + result[1] = v; + break; + case 'E': + case 'e': + var days = bundle['days-format-' + widthList[l-3]].concat(); + if(!options.strict){ + //Case-insensitive comparison + v = v.toLowerCase(); + days = dojo.map(days, function(d){return d.toLowerCase();}); + } + v = dojo.indexOf(days, v); + if(v == -1){ +// console.log("dojo.date.locale.parse: Could not parse weekday name: '" + v + "'."); + return false; + } + + //TODO: not sure what to actually do with this input, + //in terms of setting something on the Date obj...? + //without more context, can't affect the actual date + //TODO: just validate? + break; + case 'D': + result[1] = 0; + // fallthrough... + case 'd': + result[2] = v; + break; + case 'a': //am/pm + var am = options.am || bundle['dayPeriods-format-wide-am'], + pm = options.pm || bundle['dayPeriods-format-wide-pm']; + if(!options.strict){ + var period = /\./g; + v = v.replace(period,'').toLowerCase(); + am = am.replace(period,'').toLowerCase(); + pm = pm.replace(period,'').toLowerCase(); + } + if(options.strict && v != am && v != pm){ +// console.log("dojo.date.locale.parse: Could not parse am/pm part."); + return false; + } + + // we might not have seen the hours field yet, so store the state and apply hour change later + amPm = (v == pm) ? 'p' : (v == am) ? 'a' : ''; + break; + case 'K': //hour (1-24) + if(v == 24){ v = 0; } + // fallthrough... + case 'h': //hour (1-12) + case 'H': //hour (0-23) + case 'k': //hour (0-11) + //TODO: strict bounds checking, padding + if(v > 23){ +// console.log("dojo.date.locale.parse: Illegal hours value"); + return false; + } + + //in the 12-hour case, adjusting for am/pm requires the 'a' part + //which could come before or after the hour, so we will adjust later + result[3] = v; + break; + case 'm': //minutes + result[4] = v; + break; + case 's': //seconds + result[5] = v; + break; + case 'S': //milliseconds + result[6] = v; +// break; +// case 'w': +//TODO var firstDay = 0; +// default: +//TODO: throw? +// console.log("dojo.date.locale.parse: unsupported pattern char=" + token.charAt(0)); + } + return true; + }); + + var hours = +result[3]; + if(amPm === 'p' && hours < 12){ + result[3] = hours + 12; //e.g., 3pm -> 15 + }else if(amPm === 'a' && hours == 12){ + result[3] = 0; //12am -> 0 + } + + //TODO: implement a getWeekday() method in order to test + //validity of input strings containing 'EEE' or 'EEEE'... + + var dateObject = new Date(result[0], result[1], result[2], result[3], result[4], result[5], result[6]); // Date + if(options.strict){ + dateObject.setFullYear(result[0]); + } + + // Check for overflow. The Date() constructor normalizes things like April 32nd... + //TODO: why isn't this done for times as well? + var allTokens = tokens.join(""), + dateToken = allTokens.indexOf('d') != -1, + monthToken = allTokens.indexOf('M') != -1; + + if(!valid || + (monthToken && dateObject.getMonth() > result[1]) || + (dateToken && dateObject.getDate() > result[2])){ + return null; + } + + // Check for underflow, due to DST shifts. See #9366 + // This assumes a 1 hour dst shift correction at midnight + // We could compare the timezone offset after the shift and add the difference instead. + if((monthToken && dateObject.getMonth() < result[1]) || + (dateToken && dateObject.getDate() < result[2])){ + dateObject = date.add(dateObject, "hour", 1); + } + + return dateObject; // Date +}; + +function _processPattern(pattern, applyPattern, applyLiteral, applyAll){ + //summary: Process a pattern with literals in it + + // Break up on single quotes, treat every other one as a literal, except '' which becomes ' + var identity = function(x){return x;}; + applyPattern = applyPattern || identity; + applyLiteral = applyLiteral || identity; + applyAll = applyAll || identity; + + //split on single quotes (which escape literals in date format strings) + //but preserve escaped single quotes (e.g., o''clock) + var chunks = pattern.match(/(''|[^'])+/g), + literal = pattern.charAt(0) == "'"; + + dojo.forEach(chunks, function(chunk, i){ + if(!chunk){ + chunks[i]=''; + }else{ + chunks[i]=(literal ? applyLiteral : applyPattern)(chunk.replace(/''/g, "'")); + literal = !literal; + } + }); + return applyAll(chunks.join('')); +} + +function _buildDateTimeRE(tokens, bundle, options, pattern){ + pattern = regexp.escapeString(pattern); + if(!options.strict){ pattern = pattern.replace(" a", " ?a"); } // kludge to tolerate no space before am/pm + return pattern.replace(/([a-z])\1*/ig, function(match){ + // Build a simple regexp. Avoid captures, which would ruin the tokens list + var s, + c = match.charAt(0), + l = match.length, + p2 = '', p3 = ''; + if(options.strict){ + if(l > 1){ p2 = '0' + '{'+(l-1)+'}'; } + if(l > 2){ p3 = '0' + '{'+(l-2)+'}'; } + }else{ + p2 = '0?'; p3 = '0{0,2}'; + } + switch(c){ + case 'y': + s = '\\d{2,4}'; + break; + case 'M': + s = (l>2) ? '\\S+?' : '1[0-2]|'+p2+'[1-9]'; + break; + case 'D': + s = '[12][0-9][0-9]|3[0-5][0-9]|36[0-6]|'+p2+'[1-9][0-9]|'+p3+'[1-9]'; + break; + case 'd': + s = '3[01]|[12]\\d|'+p2+'[1-9]'; + break; + case 'w': + s = '[1-4][0-9]|5[0-3]|'+p2+'[1-9]'; + break; + case 'E': + s = '\\S+'; + break; + case 'h': //hour (1-12) + s = '1[0-2]|'+p2+'[1-9]'; + break; + case 'k': //hour (0-11) + s = '1[01]|'+p2+'\\d'; + break; + case 'H': //hour (0-23) + s = '1\\d|2[0-3]|'+p2+'\\d'; + break; + case 'K': //hour (1-24) + s = '1\\d|2[0-4]|'+p2+'[1-9]'; + break; + case 'm': + case 's': + s = '[0-5]\\d'; + break; + case 'S': + s = '\\d{'+l+'}'; + break; + case 'a': + var am = options.am || bundle['dayPeriods-format-wide-am'], + pm = options.pm || bundle['dayPeriods-format-wide-pm']; + s = am + '|' + pm; + if(!options.strict){ + if(am != am.toLowerCase()){ s += '|' + am.toLowerCase(); } + if(pm != pm.toLowerCase()){ s += '|' + pm.toLowerCase(); } + if(s.indexOf('.') != -1){ s += '|' + s.replace(/\./g, ""); } + } + s = s.replace(/\./g, "\\."); + break; + default: + // case 'v': + // case 'z': + // case 'Z': + s = ".*"; +// console.log("parse of date format, pattern=" + pattern); + } + + if(tokens){ tokens.push(match); } + + return "(" + s + ")"; // add capture + }).replace(/[\xa0 ]/g, "[\\s\\xa0]"); // normalize whitespace. Need explicit handling of \xa0 for IE. +} + +var _customFormats = []; +dojo.date.locale.addCustomFormats = function(/*String*/packageName, /*String*/bundleName){ + // summary: + // Add a reference to a bundle containing localized custom formats to be + // used by date/time formatting and parsing routines. + // + // description: + // The user may add custom localized formats where the bundle has properties following the + // same naming convention used by dojo.cldr: `dateFormat-xxxx` / `timeFormat-xxxx` + // The pattern string should match the format used by the CLDR. + // See dojo.date.locale.format() for details. + // The resources must be loaded by dojo.requireLocalization() prior to use + + _customFormats.push({pkg:packageName,name:bundleName}); +}; + +dojo.date.locale._getGregorianBundle = function(/*String*/locale){ + var gregorian = {}; + dojo.forEach(_customFormats, function(desc){ + var bundle = dojo.i18n.getLocalization(desc.pkg, desc.name, locale); + gregorian = lang.mixin(gregorian, bundle); + }, this); + return gregorian; /*Object*/ +}; + +dojo.date.locale.addCustomFormats("dojo.cldr","gregorian"); + +dojo.date.locale.getNames = function(/*String*/item, /*String*/type, /*String?*/context, /*String?*/locale){ + // summary: + // Used to get localized strings from dojo.cldr for day or month names. + // + // item: + // 'months' || 'days' + // type: + // 'wide' || 'abbr' || 'narrow' (e.g. "Monday", "Mon", or "M" respectively, in English) + // context: + // 'standAlone' || 'format' (default) + // locale: + // override locale used to find the names + + var label, + lookup = dojo.date.locale._getGregorianBundle(locale), + props = [item, context, type]; + if(context == 'standAlone'){ + var key = props.join('-'); + label = lookup[key]; + // Fall back to 'format' flavor of name + if(label[0] == 1){ label = undefined; } // kludge, in the absence of real aliasing support in dojo.cldr + } + props[1] = 'format'; + + // return by copy so changes won't be made accidentally to the in-memory model + return (label || lookup[props.join('-')]).concat(); /*Array*/ +}; + +dojo.date.locale.isWeekend = function(/*Date?*/dateObject, /*String?*/locale){ + // summary: + // Determines if the date falls on a weekend, according to local custom. + + var weekend = cldr.getWeekend(locale), + day = (dateObject || new Date()).getDay(); + if(weekend.end < weekend.start){ + weekend.end += 7; + if(day < weekend.start){ day += 7; } + } + return day >= weekend.start && day <= weekend.end; // Boolean +}; + +// These are used only by format and strftime. Do they need to be public? Which module should they go in? + +dojo.date.locale._getDayOfYear = function(/*Date*/dateObject){ + // summary: gets the day of the year as represented by dateObject + return date.difference(new Date(dateObject.getFullYear(), 0, 1, dateObject.getHours()), dateObject) + 1; // Number +}; + +dojo.date.locale._getWeekOfYear = function(/*Date*/dateObject, /*Number*/firstDayOfWeek){ + if(arguments.length == 1){ firstDayOfWeek = 0; } // Sunday + + var firstDayOfYear = new Date(dateObject.getFullYear(), 0, 1).getDay(), + adj = (firstDayOfYear - firstDayOfWeek + 7) % 7, + week = Math.floor((dojo.date.locale._getDayOfYear(dateObject) + adj - 1) / 7); + + // if year starts on the specified day, start counting weeks at 1 + if(firstDayOfYear == firstDayOfWeek){ week++; } + + return week; // Number +}; + +return dojo.date.locale; +}); + +}, +'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/form/VerticalRule':function(){ +define([ + "dojo/_base/declare", // declare + "./HorizontalRule" +], function(declare, HorizontalRule){ + +/*===== + var HorizontalRule = dijit.form.HorizontalRule; +=====*/ + + // module: + // dijit/form/VerticalRule + // summary: + // Hash marks for the `dijit.form.VerticalSlider` + + return declare("dijit.form.VerticalRule", HorizontalRule, { + // summary: + // Hash marks for the `dijit.form.VerticalSlider` + + templateString: '<div class="dijitRuleContainer dijitRuleContainerV"></div>', + _positionPrefix: '<div class="dijitRuleMark dijitRuleMarkV" style="top:', + + /*===== + // container: String + // This is either "leftDecoration" or "rightDecoration", + // to indicate whether this rule goes to the left or to the right of the slider. + // Note that on RTL system, "leftDecoration" would actually go to the right, and vice-versa. + container: "", + =====*/ + + // Overrides HorizontalRule._isHorizontal + _isHorizontal: false + + }); +}); + +}, +'dijit/form/_FormSelectWidget':function(){ +define([ + "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. + + +/*===== +dijit.form.__SelectOption = function(){ + // value: String + // The value of the option. Setting to empty (or missing) will + // place a separator at that location + // label: String + // The label for our option. It can contain html tags. + // selected: Boolean + // Whether or not we are a selected option + // disabled: Boolean + // Whether or not this specific option is disabled + this.value = value; + this.label = label; + this.selected = selected; + this.disabled = disabled; +} +=====*/ + +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. + // This also provides the mechanism for reading the elements from + // a store, if desired. + + // multiple: [const] Boolean + // Whether or not we are multi-valued + multiple: false, + + // options: dijit.form.__SelectOption[] + // The set of options for our select item. Roughly corresponds to + // the html <option> tag. + options: null, + + // store: 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, + + // query: object + // A query to use when fetching items from our store + query: null, + + // queryOptions: object + // Query options to use when fetching from the store + queryOptions: null, + + // onFetch: Function + // A callback to do with an onFetch - but before any items are actually + // iterated over (i.e. to filter even further what you want to add) + onFetch: null, + + // sortByLabel: Boolean + // Flag to sort the options returned from a store by the label of + // the store. + sortByLabel: true, + + + // loadChildrenOnOpen: Boolean + // By default loadChildren is called when the items are fetched from the + // store. This property allows delaying loadChildren (and the creation + // of the options/menuitems) until the user clicks the button to open the + // dropdown. + loadChildrenOnOpen: false, + + getOptions: function(/*anything*/ valueOrIdx){ + // summary: + // Returns a given option (or options). + // valueOrIdx: + // If passed in as a string, that string is used to look up the option + // in the array of options - based on the value property. + // (See dijit.form.__SelectOption). + // + // If passed in a number, then the option with the given index (0-based) + // within this select will be returned. + // + // If passed in a dijit.form.__SelectOption, the same option will be + // returned if and only if it exists within this select. + // + // If passed an array, then an array will be returned with each element + // in the array being looked up. + // + // If not passed a value, then all options will be returned + // + // returns: + // The option corresponding with the given value or index. null + // is returned if any of the following are true: + // - A string value is passed in which doesn't exist + // - An index is passed in which is outside the bounds of the array of options + // - A dijit.form.__SelectOption is passed in which is not a part of the select + + // NOTE: the compare for passing in a dijit.form.__SelectOption checks + // if the value property matches - NOT if the exact option exists + // NOTE: if passing in an array, null elements will be placed in the returned + // array when a value is not found. + var lookupValue = valueOrIdx, opts = this.options || [], l = opts.length; + + if(lookupValue === undefined){ + return opts; // dijit.form.__SelectOption[] + } + if(lang.isArray(lookupValue)){ + return array.map(lookupValue, "return this.getOptions(item);", this); // dijit.form.__SelectOption[] + } + 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(!array.some(this.options, function(o, idx){ + if(o === lookupValue || + (o.value && o.value === lookupValue.value)){ + lookupValue = idx; + return true; + } + return false; + })){ + lookupValue = -1; + } + } + if(typeof lookupValue == "string"){ + for(var i=0; i<l; i++){ + if(opts[i].value === lookupValue){ + lookupValue = i; + break; + } + } + } + if(typeof lookupValue == "number" && lookupValue >= 0 && lookupValue < l){ + return this.options[lookupValue]; // dijit.form.__SelectOption + } + return null; // null + }, + + addOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ option){ + // summary: + // Adds an option or options to the end of the select. If value + // 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(!lang.isArray(option)){ option = [option]; } + array.forEach(option, function(i){ + if(i && lang.isObject(i)){ + this.options.push(i); + } + }, this); + this._loadChildren(); + }, + + removeOption: function(/*String|dijit.form.__SelectOption|Number|Array*/ valueOrIdx){ + // summary: + // Removes the given option or options. You can remove by string + // (in which case the value is removed), number (in which case the + // index in the options array is removed), or select option (in + // 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(!lang.isArray(valueOrIdx)){ valueOrIdx = [valueOrIdx]; } + var oldOpts = this.getOptions(valueOrIdx); + 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 = array.filter(this.options, function(node){ + return (node.value !== i.value || node.label !== i.label); + }); + this._removeOptionItem(i); + } + }, this); + this._loadChildren(); + }, + + updateOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ newOption){ + // 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 yield better performance since + // the children will only be loaded once. + 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]; } + } + }, this); + this._loadChildren(); + }, + + setStore: function(/*dojo.data.api.Identity*/ store, + /*anything?*/ selectedValue, + /*Object?*/ fetchArgs){ + // summary: + // Sets the store you would like to use with this select widget. + // The selected value is the value of the new store to set. This + // 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 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 + // fetchArgs: Object? + // The arguments that will be passed to the store's fetch() function + var oStore = this.store; + fetchArgs = fetchArgs || {}; + if(oStore !== store){ + // Our store has changed, so update our notifications + var h; + while(h = this._notifyConnections.pop()){ h.remove(); } + + if(store && store.getFeatures()["dojo.data.api.Notification"]){ + this._notifyConnections = [ + 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); + } + + // Turn off change notifications while we make all these changes + this._onChangeActive = false; + + // Remove existing options (if there are any) + if(this.options && this.options.length){ + this.removeOption(this.options); + } + + // Add our new options + if(store){ + this._loadingStore = true; + store.fetch(lang.delegate(fetchArgs, { + onComplete: function(items, opts){ + if(this.sortByLabel && !fetchArgs.sort && items.length){ + 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 + 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{ + this._pseudoLoadChildren(items); + } + this._fetchedWith = opts; + this._lastValueReported = this.multiple ? [] : null; + this._onChangeActive = true; + this.onSetStore(); + this._handleOnChange(this.value); + }, + scope: this + })); + }else{ + delete this._fetchedWith; + } + return oStore; // dojo.data.api.Identity + }, + + // TODO: implement set() and watch() for store and query, although not sure how to handle + // setting them individually rather than together (as in setStore() above) + + _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ + // summary: + // set the value of the widget. + // If a string is passed, then we set our value from looking it up. + if(this._loadingStore){ + // Our store is loading - so save our value, and we'll set it when + // we're done + this._pendingValue = newValue; + return; + } + var opts = this.getOptions() || []; + if(!lang.isArray(newValue)){ + newValue = [newValue]; + } + array.forEach(newValue, function(i, idx){ + if(!lang.isObject(i)){ + i = i + ""; + } + if(typeof i === "string"){ + newValue[idx] = array.filter(opts, function(node){ + return node.value === i; + })[0] || {value: "", label: ""}; + } + }, this); + + // Make sure some sane default is set + newValue = array.filter(newValue, function(i){ return i && i.value; }); + if(!this.multiple && (!newValue[0] || !newValue[0].value) && opts.length){ + newValue[0] = opts[0]; + } + array.forEach(opts, function(i){ + i.selected = array.some(newValue, function(v){ return v.value === i.value; }); + }); + 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]); + this._updateSelection(); + this._handleOnChange(this.value, priorityChange); + }, + + _getDisplayedValueAttr: function(){ + // summary: + // returns the displayed value of the widget + var val = this.get("value"); + if(!lang.isArray(val)){ + val = [val]; + } + var ret = array.map(this.getOptions(val), function(v){ + if(v && "label" in v){ + return v.label; + }else if(v){ + return v.value; + } + return null; + }, this); + return this.multiple ? ret : ret[0]; + }, + + _loadChildren: function(){ + // summary: + // Loads the children represented by this widget's options. + // reset the menu to make it populatable on the next click + if(this._loadingStore){ return; } + array.forEach(this._getChildren(), function(child){ + child.destroyRecursive(); + }); + // Add each menu item + array.forEach(this.options, this._addOptionItem, this); + + // Update states + this._updateSelection(); + }, + + _updateSelection: function(){ + // summary: + // Sets the "selected" class on the item for styling purposes + this._set("value", this._getValueFromOpts()); + var val = this.value; + if(!lang.isArray(val)){ + val = [val]; + } + if(val && val[0]){ + array.forEach(this._getChildren(), function(child){ + var isSelected = array.some(val, function(v){ + return child.option && (v === child.option.value); + }); + domClass.toggle(child.domNode, this.baseClass + "SelectedOption", isSelected); + child.domNode.setAttribute("aria-selected", isSelected); + }, this); + } + }, + + _getValueFromOpts: function(){ + // summary: + // Returns the value of the widget by reading the options for + // the selected flag + var opts = this.getOptions() || []; + if(!this.multiple && opts.length){ + // Mirror what a select does - choose the first one + var opt = array.filter(opts, function(i){ + return i.selected; + })[0]; + if(opt && opt.value){ + return opt.value + }else{ + opts[0].selected = true; + return opts[0].value; + } + }else if(this.multiple){ + // Set value to be the sum of all selected + return array.map(array.filter(opts, function(i){ + return i.selected; + }), function(i){ + return i.value; + }) || []; + } + return ""; + }, + + // Internal functions to call when we have store notifications come in + _onNewItem: function(/*item*/ item, /*Object?*/ parentInfo){ + if(!parentInfo || !parentInfo.parent){ + // Only add it if we are top-level + this._addOptionForItem(item); + } + }, + _onDeleteItem: function(/*item*/ item){ + var store = this.store; + this.removeOption(store.getIdentity(item)); + }, + _onSetItem: function(/*item*/ item){ + this.updateOption(this._getOptionObjForItem(item)); + }, + + _getOptionObjForItem: function(item){ + // summary: + // Returns an option object based off the given item. The "value" + // of the option item will be the identity of the item, the "label" + // of the option will be the label of the item. If the item contains + // children, the children value of the item will be set + var store = this.store, label = store.getLabel(item), + value = (label ? store.getIdentity(item) : null); + return {value: value, label: label, item:item}; // dijit.form.__SelectOption + }, + + _addOptionForItem: function(/*item*/ item){ + // summary: + // Creates (and adds) the option for the given item + var store = this.store; + if(!store.isItemLoaded(item)){ + // We are not loaded - so let's load it and add later + store.loadItem({item: item, onItem: function(i){ + this._addOptionForItem(i); + }, + scope: this}); + return; + } + var newOpt = this._getOptionObjForItem(item); + this.addOption(newOpt); + }, + + constructor: function(/*Object*/ keywordArgs){ + // summary: + // 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); + dom.setSelectable(this.focusNode, false); + }, + + _fillContent: function(){ + // summary: + // Loads our options and sets up our dropdown correctly. We + // don't want any content, so we don't call any inherit chain + // function. + var opts = this.options; + if(!opts){ + 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-" + 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?) + // decide before 1.6 + selected: node.getAttribute("selected") || false, + disabled: node.getAttribute("disabled") || false + }; + }, this) : []; + } + if(!this.value){ + this._set("value", this._getValueFromOpts()); + }else if(this.multiple && typeof this.value == "string"){ + this._set("value", this.value.split(",")); + } + }, + + postCreate: function(){ + // summary: + // sets up our event handling that we need for functioning + // as a select + this.inherited(arguments); + + // Make our event connections for updating state + this.connect(this, "onChange", "_updateSelection"); + this.connect(this, "startup", "_loadChildren"); + + this._setValueAttr(this.value, null); + }, + + startup: function(){ + // summary: + // Connects in our store, if we have one defined + this.inherited(arguments); + var store = this.store, fetchArgs = {}; + array.forEach(["query", "queryOptions", "onFetch"], function(i){ + if(this[i]){ + fetchArgs[i] = this[i]; + } + delete this[i]; + }, this); + if(store && store.getFeatures()["dojo.data.api.Identity"]){ + // Temporarily set our store to null so that it will get set + // and connected appropriately + this.store = null; + this.setStore(store, this._oValue, fetchArgs); + } + }, + + destroy: function(){ + // summary: + // Clean up our connections + var h; + while(h = this._notifyConnections.pop()){ h.remove(); } + this.inherited(arguments); + }, + + _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 + // separator is added in that place. Make sure to store the option + // in the created option widget. + }, + + _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 =====*/){ + // summary: + // Overridable function which will set the display for the + // widget. newDisplay is either a string (in the case of + // single selects) or array of strings (in the case of multi-selects) + }, + + _getChildren: function(){ + // summary: + // Overridable function to return the children that this widget contains. + return []; + }, + + _getSelectedOptionsAttr: function(){ + // summary: + // hooks into this.attr to provide a mechanism for getting the + // option items for the current value of the widget. + return this.getOptions(this.get("value")); + }, + + _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. + // items: + // An array of items that will be loaded, when needed + }, + + onSetStore: function(){ + // summary: + // a function that can be connected to in order to receive a + // notification that the store has finished loading and all options + // from that store are available + } +}); + +}); + +}, +'dijit/form/Select':function(){ +define([ + "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){ + +/*===== + 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; +=====*/ + +// module: +// dijit/form/Select +// summary: +// This is a "styleable" select box - it is basically a DropDownButton which +// can take a <select> as its input. + + +var _SelectMenu = declare("dijit.form._SelectMenu", Menu, { + // summary: + // An internally-used menu for dropdown that allows us a vertical scrollbar + buildRendering: function(){ + // summary: + // Stub in our own changes, so that our domNode is not a table + // otherwise, we won't respond correctly to heights/overflows + this.inherited(arguments); + var o = (this.menuTableNode = this.domNode); + var n = (this.domNode = domConstruct.create("div", {style: {overflowX: "hidden", overflowY: "scroll"}})); + if(o.parentNode){ + o.parentNode.replaceChild(n, o); + } + domClass.remove(o, "dijitMenuTable"); + n.className = o.className + " dijitSelectMenu"; + o.className = "dijitReset dijitMenuTable"; + 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 + + this.inherited(arguments); + + this.connect(this.domNode, "onmousemove", event.stop); + }, + + resize: function(/*Object*/ mb){ + // summary: + // Overridden so that we are able to handle resizing our + // internal widget. Note that this is not a "full" resize + // implementation - it only works correctly if you pass it a + // marginBox. + // + // mb: Object + // The margin box to set this dropdown to. + if(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 + // browser/OS specific width. + this.menuTableNode.style.width = "100%"; + } + } + } +}); + +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: template, + + // required: Boolean + // Can be true or false, default is false. + required: false, + + // state: [readonly] String + // "Incomplete" if this select is required but unset (i.e. blank value), "" otherwise + state: "", + + // message: String + // Currently displayed error/prompt message + message: "", + + // tooltipPosition: String[] + // See description of dijit.Tooltip.defaultPosition for details on this parameter. + tooltipPosition: [], + + // emptyLabel: string + // What to display in an "empty" dropdown + emptyLabel: " ", // + + // _isLoaded: Boolean + // Whether or not we have been loaded + _isLoaded: false, + + // _childrenLoaded: Boolean + // Whether or not our children have been loaded + _childrenLoaded: false, + + _fillContent: function(){ + // summary: + // Set the value to be the first, or the selected index + this.inherited(arguments); + // set value from selected option + if(this.options.length && !this.value && this.srcNodeRef){ + var si = this.srcNodeRef.selectedIndex || 0; // || 0 needed for when srcNodeRef is not a SELECT + this.value = this.options[si >= 0 ? si : 0].value; + } + // Create the dropDown widget + this.dropDown = new _SelectMenu({id: this.id + "_menu"}); + domClass.add(this.dropDown.domNode, this.baseClass + "Menu"); + }, + + _getMenuItemForOption: function(/*dijit.form.__SelectOption*/ option){ + // summary: + // For the given option, return the menu item that should be + // 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 MenuSeparator(); + }else{ + // Just a regular menu option + var click = lang.hitch(this, "_setValueAttr", option); + var item = new MenuItem({ + option: option, + label: option.label || this.emptyLabel, + onClick: click, + disabled: option.disabled || false + }); + item.focusNode.setAttribute("role", "listitem"); + return item; + } + }, + + _addOptionItem: function(/*dijit.form.__SelectOption*/ option){ + // summary: + // For the given option, add an option to our dropdown. + // If the option doesn't have a value, then a separator is added + // in that place. + if(this.dropDown){ + this.dropDown.addChild(this._getMenuItemForOption(option)); + } + }, + + _getChildren: function(){ + if(!this.dropDown){ + return []; + } + return this.dropDown.getChildren(); + }, + + _loadChildren: function(/*Boolean*/ loadMenuItems){ + // summary: + // Resets the menu and the length attribute of the button - and + // ensures that the label is appropriately set. + // loadMenuItems: Boolean + // actually loads the child menu items - we only do this when we are + // populating for showing the dropdown. + + if(loadMenuItems === true){ + // this.inherited destroys this.dropDown's child widgets (MenuItems). + // Avoid this.dropDown (Menu widget) having a pointer to a destroyed widget (which will cause + // issues later in _setSelected). (see #10296) + if(this.dropDown){ + delete this.dropDown.focusedChild; + } + if(this.options.length){ + this.inherited(arguments); + }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) + array.forEach(this._getChildren(), function(child){ child.destroyRecursive(); }); + var item = new MenuItem({label: " "}); + this.dropDown.addChild(item); + } + }else{ + this._updateSelection(); + } + + this._isLoaded = false; + this._childrenLoaded = true; + + if(!this._loadingStore){ + // Don't call this if we are loading - since we will handle it later + this._setValueAttr(this.value); + } + }, + + _setValueAttr: function(value){ + this.inherited(arguments); + 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){ + // summary: + // 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>'; + this.focusNode.setAttribute("aria-valuetext", lbl); + }, + + validate: function(/*Boolean*/ isFocused){ + // summary: + // 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.disabled || this.isValid(isFocused); + this._set("state", isValid ? "" : "Incomplete"); + this.focusNode.setAttribute("aria-invalid", isValid ? "false" : "true"); + var message = isValid ? "" : this._missingMsg; + 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 =====*/){ + // 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. + return (!this.required || this.value === 0 || !(/^\s*$/.test(this.value || ""))); // handle value is null or undefined + }, + + reset: function(){ + // summary: + // Overridden so that the state will be cleared. + this.inherited(arguments); + Tooltip.hide(this.domNode); + this.validate(this.focused); // to update this.state + }, + + postMixInProperties: function(){ + // summary: + // set the missing message + this.inherited(arguments); + this._missingMsg = i18n.getLocalization("dijit.form", "validate", + this.lang).missingMessage; + }, + + postCreate: function(){ + // summary: + // stop mousemove from selecting text on IE to be consistent with other browsers + + this.inherited(arguments); + + this.connect(this.domNode, "onmousemove", event.stop); + }, + + _setStyleAttr: function(/*String||Object*/ value){ + this.inherited(arguments); + domClass.toggle(this.domNode, this.baseClass + "FixedWidth", !!this.domNode.style.width); + }, + + isLoaded: function(){ + return this._isLoaded; + }, + + loadDropDown: function(/*Function*/ loadCallback){ + // summary: + // populates the menu + this._loadChildren(true); + this._isLoaded = true; + loadCallback(); + }, + + closeDropDown: function(){ + // overriding _HasDropDown.closeDropDown() + this.inherited(arguments); + + if(this.dropDown && this.dropDown.menuTableNode){ + // Erase possible width: 100% setting from _SelectMenu.resize(). + // Leaving it would interfere with the next openDropDown() call, which + // queries the natural size of the drop down. + this.dropDown.menuTableNode.style.width = ""; + } + }, + + uninitialize: function(preserveDom){ + if(this.dropDown && !this.dropDown._destroyed){ + this.dropDown.destroyRecursive(preserveDom); + delete this.dropDown; + } + this.inherited(arguments); + }, + + _onFocus: function(){ + this.validate(true); // show tooltip if second focus of required tooltip, but no selection + this.inherited(arguments); + }, + + _onBlur: function(){ + Tooltip.hide(this.domNode); + this.inherited(arguments); + } +}); + +Select._Menu = _SelectMenu; // for monkey patching + +return Select; +}); + +}, +'dijit/_editor/range':function(){ +define("dijit/_editor/range", [ + "dojo/_base/array", // array.every + "dojo/_base/declare", // declare + "dojo/_base/lang", // lang.isArray + "dojo/_base/window", // win.global + ".." // for exporting symbols to dijit, TODO: remove in 2.0 +], function(array, declare, lang, win, dijit){ + +// module: +// dijit/_editor/range +// summary: +// W3C range API + + +dijit.range={}; + +dijit.range.getIndex = function(/*DomNode*/node, /*DomNode*/parent){ +// dojo.profile.start("dijit.range.getIndex"); + var ret = [], retR = []; + var onode = node; + + var pnode, n; + while(node != parent){ + var i = 0; + pnode = node.parentNode; + while((n = pnode.childNodes[i++])){ + if(n === node){ + --i; + break; + } + } + //if(i>=pnode.childNodes.length){ + //dojo.debug("Error finding index of a node in dijit.range.getIndex"); + //} + ret.unshift(i); + retR.unshift(i - pnode.childNodes.length); + node = pnode; + } + + //normalized() can not be called so often to prevent + //invalidating selection/range, so we have to detect + //here that any text nodes in a row + if(ret.length > 0 && onode.nodeType == 3){ + n = onode.previousSibling; + while(n && n.nodeType == 3){ + ret[ret.length - 1]--; + n = n.previousSibling; + } + n = onode.nextSibling; + while(n && n.nodeType == 3){ + retR[retR.length - 1]++; + n = n.nextSibling; + } + } +// dojo.profile.end("dijit.range.getIndex"); + return {o: ret, r:retR}; +}; + +dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){ + if(!lang.isArray(index) || index.length == 0){ + return parent; + } + var node = parent; +// if(!node)debugger + array.every(index, function(i){ + if(i >= 0 && i < node.childNodes.length){ + node = node.childNodes[i]; + }else{ + node = null; + //console.debug('Error: can not find node with index',index,'under parent node',parent ); + return false; //terminate array.every + } + return true; //carry on the every loop + }); + + return node; +}; + +dijit.range.getCommonAncestor = function(n1, n2, root){ + root = root || n1.ownerDocument.body; + var getAncestors = function(n){ + var as = []; + while(n){ + as.unshift(n); + if(n !== root){ + n = n.parentNode; + }else{ + break; + } + } + return as; + }; + var n1as = getAncestors(n1); + var n2as = getAncestors(n2); + + var m = Math.min(n1as.length, n2as.length); + var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default) + for(var i = 1; i < m; i++){ + if(n1as[i] === n2as[i]){ + com = n1as[i] + }else{ + break; + } + } + return com; +}; + +dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){ + root = root || node.ownerDocument.body; + while(node && node !== root){ + var name = node.nodeName.toUpperCase(); + if(regex.test(name)){ + return node; + } + + node = node.parentNode; + } + return null; +}; + +dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/; +dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){ + root = root || node.ownerDocument.body; + regex = regex || dijit.range.BlockTagNames; + var block = null, blockContainer; + while(node && node !== root){ + var name = node.nodeName.toUpperCase(); + if(!block && regex.test(name)){ + block = node; + } + if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){ + blockContainer = node; + } + + node = node.parentNode; + } + return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body}; +}; + +dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){ + var atBeginning = false; + var offsetAtBeginning = (offset == 0); + if(!offsetAtBeginning && node.nodeType == 3){ //if this is a text node, check whether the left part is all space + if(/^[\s\xA0]+$/.test(node.nodeValue.substr(0, offset))){ + offsetAtBeginning = true; + } + } + if(offsetAtBeginning){ + var cnode = node; + atBeginning = true; + while(cnode && cnode !== container){ + if(cnode.previousSibling){ + atBeginning = false; + break; + } + cnode = cnode.parentNode; + } + } + return atBeginning; +}; + +dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){ + var atEnd = false; + var offsetAtEnd = (offset == (node.length || node.childNodes.length)); + if(!offsetAtEnd && node.nodeType == 3){ //if this is a text node, check whether the right part is all space + if(/^[\s\xA0]+$/.test(node.nodeValue.substr(offset))){ + offsetAtEnd = true; + } + } + if(offsetAtEnd){ + var cnode = node; + atEnd = true; + while(cnode && cnode !== container){ + if(cnode.nextSibling){ + atEnd = false; + break; + } + cnode = cnode.parentNode; + } + } + return atEnd; +}; + +dijit.range.adjacentNoneTextNode = function(startnode, next){ + var node = startnode; + var len = (0 - startnode.length) || 0; + var prop = next ? 'nextSibling' : 'previousSibling'; + while(node){ + if(node.nodeType != 3){ + break; + } + len += node.length; + node = node[prop]; + } + return [node,len]; +}; + +dijit.range._w3c = Boolean(window['getSelection']); +dijit.range.create = function(/*Window?*/window){ + if(dijit.range._w3c){ + return (window || win.global).document.createRange(); + }else{//IE + return new dijit.range.W3CRange; + } +}; + +dijit.range.getSelection = function(/*Window*/win, /*Boolean?*/ignoreUpdate){ + if(dijit.range._w3c){ + return win.getSelection(); + }else{//IE + var s = new dijit.range.ie.selection(win); + if(!ignoreUpdate){ + s._getCurrentSelection(); + } + return s; + } +}; + +if(!dijit.range._w3c){ + dijit.range.ie = { + cachedSelection: {}, + selection: function(win){ + this._ranges = []; + this.addRange = function(r, /*boolean*/internal){ + this._ranges.push(r); + if(!internal){ + r._select(); + } + this.rangeCount = this._ranges.length; + }; + this.removeAllRanges = function(){ + //don't detach, the range may be used later +// for(var i=0;i<this._ranges.length;i++){ +// this._ranges[i].detach(); +// } + this._ranges = []; + this.rangeCount = 0; + }; + var _initCurrentRange = function(){ + var r = win.document.selection.createRange(); + var type = win.document.selection.type.toUpperCase(); + if(type == "CONTROL"){ + //TODO: multiple range selection(?) + return new dijit.range.W3CRange(dijit.range.ie.decomposeControlRange(r)); + }else{ + return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r)); + } + }; + this.getRangeAt = function(i){ + return this._ranges[i]; + }; + this._getCurrentSelection = function(){ + this.removeAllRanges(); + var r = _initCurrentRange(); + if(r){ + this.addRange(r, true); + this.isCollapsed = r.collapsed; + }else{ + this.isCollapsed = true; + } + }; + }, + decomposeControlRange: function(range){ + var firstnode = range.item(0), lastnode = range.item(range.length - 1); + var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode; + var startOffset = dijit.range.getIndex(firstnode, startContainer).o[0]; + var endOffset = dijit.range.getIndex(lastnode, endContainer).o[0] + 1; + return [startContainer, startOffset,endContainer, endOffset]; + }, + getEndPoint: function(range, end){ + var atmrange = range.duplicate(); + atmrange.collapse(!end); + var cmpstr = 'EndTo' + (end ? 'End' : 'Start'); + var parentNode = atmrange.parentElement(); + + var startnode, startOffset, lastNode; + if(parentNode.childNodes.length > 0){ + array.every(parentNode.childNodes, function(node, i){ + var calOffset; + if(node.nodeType != 3){ + atmrange.moveToElementText(node); + + if(atmrange.compareEndPoints(cmpstr, range) > 0){ + //startnode = node.previousSibling; + if(lastNode && lastNode.nodeType == 3){ + //where shall we put the start? in the text node or after? + startnode = lastNode; + calOffset = true; + }else{ + startnode = parentNode; + startOffset = i; + return false; + } + }else{ + if(i == parentNode.childNodes.length - 1){ + startnode = parentNode; + startOffset = parentNode.childNodes.length; + return false; + } + } + }else{ + if(i == parentNode.childNodes.length - 1){//at the end of this node + startnode = node; + calOffset = true; + } + } + // try{ + if(calOffset && startnode){ + var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0]; + if(prevnode){ + startnode = prevnode.nextSibling; + }else{ + startnode = parentNode.firstChild; //firstChild must be a text node + } + var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode); + prevnode = prevnodeobj[0]; + var lenoffset = prevnodeobj[1]; + if(prevnode){ + atmrange.moveToElementText(prevnode); + atmrange.collapse(false); + }else{ + atmrange.moveToElementText(parentNode); + } + atmrange.setEndPoint(cmpstr, range); + startOffset = atmrange.text.length - lenoffset; + + return false; + } + // }catch(e){ debugger } + lastNode = node; + return true; + }); + }else{ + startnode = parentNode; + startOffset = 0; + } + + //if at the end of startnode and we are dealing with start container, then + //move the startnode to nextSibling if it is a text node + //TODO: do this for end container? + if(!end && startnode.nodeType == 1 && startOffset == startnode.childNodes.length){ + var nextnode = startnode.nextSibling; + if(nextnode && nextnode.nodeType == 3){ + startnode = nextnode; + startOffset = 0; + } + } + return [startnode, startOffset]; + }, + setEndPoint: function(range, container, offset){ + //text node + var atmrange = range.duplicate(), node, len; + if(container.nodeType != 3){ //normal node + if(offset > 0){ + node = container.childNodes[offset - 1]; + if(node){ + if(node.nodeType == 3){ + container = node; + offset = node.length; + //pass through + }else{ + if(node.nextSibling && node.nextSibling.nodeType == 3){ + container = node.nextSibling; + offset = 0; + //pass through + }else{ + atmrange.moveToElementText(node.nextSibling ? node : container); + var parent = node.parentNode; + var tempNode = parent.insertBefore(node.ownerDocument.createTextNode(' '), node.nextSibling); + atmrange.collapse(false); + parent.removeChild(tempNode); + } + } + } + }else{ + atmrange.moveToElementText(container); + atmrange.collapse(true); + } + } + if(container.nodeType == 3){ + var prevnodeobj = dijit.range.adjacentNoneTextNode(container); + var prevnode = prevnodeobj[0]; + len = prevnodeobj[1]; + if(prevnode){ + atmrange.moveToElementText(prevnode); + atmrange.collapse(false); + //if contentEditable is not inherit, the above collapse won't make the end point + //in the correctly position: it always has a -1 offset, so compensate it + if(prevnode.contentEditable != 'inherit'){ + len++; + } + }else{ + atmrange.moveToElementText(container.parentNode); + atmrange.collapse(true); + } + + offset += len; + if(offset > 0){ + if(atmrange.move('character', offset) != offset){ + console.error('Error when moving!'); + } + } + } + + return atmrange; + }, + decomposeTextRange: function(range){ + var tmpary = dijit.range.ie.getEndPoint(range); + var startContainer = tmpary[0], startOffset = tmpary[1]; + var endContainer = tmpary[0], endOffset = tmpary[1]; + + if(range.htmlText.length){ + if(range.htmlText == range.text){ //in the same text node + endOffset = startOffset + range.text.length; + }else{ + tmpary = dijit.range.ie.getEndPoint(range, true); + endContainer = tmpary[0],endOffset = tmpary[1]; +// if(startContainer.tagName == "BODY"){ +// startContainer = startContainer.firstChild; +// } + } + } + return [startContainer, startOffset, endContainer, endOffset]; + }, + setRange: function(range, startContainer, startOffset, endContainer, endOffset, collapsed){ + var start = dijit.range.ie.setEndPoint(range, startContainer, startOffset); + + range.setEndPoint('StartToStart', start); + if(!collapsed){ + var end = dijit.range.ie.setEndPoint(range, endContainer, endOffset); + } + range.setEndPoint('EndToEnd', end || start); + + return range; + } + }; + +declare("dijit.range.W3CRange",null, { + constructor: function(){ + if(arguments.length>0){ + this.setStart(arguments[0][0],arguments[0][1]); + this.setEnd(arguments[0][2],arguments[0][3]); + }else{ + this.commonAncestorContainer = null; + this.startContainer = null; + this.startOffset = 0; + this.endContainer = null; + this.endOffset = 0; + this.collapsed = true; + } + }, + _updateInternal: function(){ + if(this.startContainer !== this.endContainer){ + this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer); + }else{ + this.commonAncestorContainer = this.startContainer; + } + this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset); + }, + setStart: function(node, offset){ + offset=parseInt(offset); + if(this.startContainer === node && this.startOffset == offset){ + return; + } + delete this._cachedBookmark; + + this.startContainer = node; + this.startOffset = offset; + if(!this.endContainer){ + this.setEnd(node, offset); + }else{ + this._updateInternal(); + } + }, + setEnd: function(node, offset){ + offset=parseInt(offset); + if(this.endContainer === node && this.endOffset == offset){ + return; + } + delete this._cachedBookmark; + + this.endContainer = node; + this.endOffset = offset; + if(!this.startContainer){ + this.setStart(node, offset); + }else{ + this._updateInternal(); + } + }, + setStartAfter: function(node, offset){ + this._setPoint('setStart', node, offset, 1); + }, + setStartBefore: function(node, offset){ + this._setPoint('setStart', node, offset, 0); + }, + setEndAfter: function(node, offset){ + this._setPoint('setEnd', node, offset, 1); + }, + setEndBefore: function(node, offset){ + this._setPoint('setEnd', node, offset, 0); + }, + _setPoint: function(what, node, offset, ext){ + var index = dijit.range.getIndex(node, node.parentNode).o; + this[what](node.parentNode, index.pop()+ext); + }, + _getIERange: function(){ + var r = (this._body || this.endContainer.ownerDocument.body).createTextRange(); + dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset, this.collapsed); + return r; + }, + getBookmark: function(){ + this._getIERange(); + return this._cachedBookmark; + }, + _select: function(){ + var r = this._getIERange(); + r.select(); + }, + deleteContents: function(){ + var s = this.startContainer, r = this._getIERange(); + if(s.nodeType === 3 && !this.startOffset){ + //if the range starts at the beginning of a + //text node, move it to before the textnode + //to make sure the range is still valid + //after deleteContents() finishes + this.setStartBefore(s); + } + r.pasteHTML(''); + this.endContainer = this.startContainer; + this.endOffset = this.startOffset; + this.collapsed = true; + }, + cloneRange: function(){ + var r = new dijit.range.W3CRange([this.startContainer,this.startOffset, + this.endContainer,this.endOffset]); + r._body = this._body; + return r; + }, + detach: function(){ + this._body = null; + this.commonAncestorContainer = null; + this.startContainer = null; + this.startOffset = 0; + this.endContainer = null; + this.endOffset = 0; + this.collapsed = true; +} +}); +} //if(!dijit.range._w3c) + + +return dijit.range; +}); + +}, +'dojo/store/util/QueryResults':function(){ +define(["../../_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); + +util.QueryResults = function(results){ + // summary: + // A function that wraps the results of a store query with additional + // methods. + // + // description: + // 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 + // | }); + + 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; +}; + +return util.QueryResults; +}); + +}, +'dijit/form/_ListBase':function(){ +define([ + "dojo/_base/declare", // declare + "dojo/window" // winUtils.scrollIntoView +], function(declare, winUtils){ + +// module: +// dijit/form/_ListBase +// summary: +// Focus-less menu to handle UI events consistently + +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 + + // selected: DOMnode + // currently selected node + selected: null, + + _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; + } + return tgt; + }, + + selectFirstNode: function(){ + // summary: + // Select the first displayed item in the list. + var first = this.containerNode.firstChild; + while(first && first.style.display == "none"){ + first = first.nextSibling; + } + this._setSelectedAttr(first); + }, + + selectLastNode: function(){ + // summary: + // 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); + }, + + selectNextNode: function(){ + // summary: + // Select the item just below the current selection. + // If nothing selected, select first node. + var selectedNode = this._getSelectedAttr(); + if(!selectedNode){ + this.selectFirstNode(); + }else{ + var next = selectedNode.nextSibling; + while(next && next.style.display == "none"){ + next = next.nextSibling; + } + if(!next){ + this.selectFirstNode(); + }else{ + this._setSelectedAttr(next); + } + } + }, + + selectPreviousNode: function(){ + // summary: + // 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); + } + } + }, + + _setSelectedAttr: function(/*DomNode*/ node){ + // summary: + // 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); + } + }, + + _getSelectedAttr: function(){ + // summary: + // Returns the selected node. + var v = this.selected; + return (v && v.parentNode == this.containerNode) ? v : (this.selected = null); + } +}); + +}); + +}, +'dojo/DeferredList':function(){ +define(["./_base/kernel", "./_base/Deferred", "./_base/array"], function(dojo, Deferred, darray) { + // module: + // dojo/DeferredList + // summary: + // TODOC + + +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 = []; + Deferred.call(this); + var self = this; + if(list.length === 0 && !fireOnOneCallback){ + this.resolve([0, []]); + } + 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); + } + + } + }); +}; +dojo.DeferredList.prototype = new Deferred(); + +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. + + 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; +}; + +return dojo.DeferredList; +}); + +}, +'dojo/dnd/common':function(){ +define(["../main"], function(dojo) { + // module: + // dojo/dnd/common + // summary: + // TODOC + +dojo.getObject("dnd", true, dojo); + +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; +}; + +dojo.dnd._empty = {}; + +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 +}; + +return dojo.dnd; +}); + +}, +'dijit/CalendarLite':function(){ +define([ + "dojo/_base/array", // array.forEach array.map + "dojo/_base/declare", // declare + "dojo/cldr/supplemental", // cldrSupplemental.getFirstDayOfWeek + "dojo/date", // date + "dojo/date/locale", + "dojo/dom", // dom.setSelectable + "dojo/dom-class", // domClass.contains + "dojo/_base/event", // event.stop + "dojo/_base/lang", // lang.getObject, lang.hitch + "dojo/_base/sniff", // has("ie") has("webkit") + "dojo/string", // string.substitute + "dojo/_base/window", // win.doc.createTextNode + "./_WidgetBase", + "./_TemplatedMixin", + "dojo/text!./templates/Calendar.html" +], function(array, declare, cldrSupplemental, date, local, dom, domClass, event, lang, has, string, win, + _WidgetBase, _TemplatedMixin, template){ + +/*===== + var _WidgetBase = dijit._WidgetBase; + var _TemplatedMixin = dijit._TemplatedMixin; +=====*/ + + // module: + // dijit/CalendarLite + // summary: + // Lightweight version of Calendar widget aimed towards mobile use + + var CalendarLite = declare("dijit.CalendarLite", [_WidgetBase, _TemplatedMixin], { + // summary: + // Lightweight version of Calendar widget aimed towards mobile use + // + // description: + // A simple GUI for choosing a date in the context of a monthly calendar. + // This widget can't be used in a form because it doesn't serialize the date to an + // `<input>` field. For a form element, use dijit.form.DateTextBox instead. + // + // Note that the parser takes all dates attributes passed in the + // [RFC 3339 format](http://www.faqs.org/rfcs/rfc3339.html), e.g. `2005-06-30T08:05:00-07:00` + // so that they are serializable and locale-independent. + // + // Also note that this widget isn't keyboard accessible; use dijit.Calendar for that + // example: + // | var calendar = new dijit.CalendarLite({}, dojo.byId("calendarNode")); + // + // example: + // | <div data-dojo-type="dijit.CalendarLite"></div> + + // Template for main calendar + templateString: template, + + // Template for cell for a day of the week (ex: M) + dowTemplateString: '<th class="dijitReset dijitCalendarDayLabelTemplate" role="columnheader"><span class="dijitCalendarDayLabel">${d}</span></th>', + + // Templates for a single date (ex: 13), and for a row for a week (ex: 20 21 22 23 24 25 26) + dateTemplateString: '<td class="dijitReset" role="gridcell" data-dojo-attach-point="dateCells"><span class="dijitCalendarDateLabel" data-dojo-attach-point="dateLabels"></span></td>', + weekTemplateString: '<tr class="dijitReset dijitCalendarWeekTemplate" role="row">${d}${d}${d}${d}${d}${d}${d}</tr>', + + // value: Date + // The currently selected Date, initially set to invalid date to indicate no selection. + value: new Date(""), + // TODO: for 2.0 make this a string (ISO format) rather than a Date + + // datePackage: String + // JavaScript object containing Calendar functions. Uses Gregorian Calendar routines + // from dojo.date by default. + datePackage: date, + + // dayWidth: String + // How to represent the days of the week in the calendar header. See locale + dayWidth: "narrow", + + // tabIndex: Integer + // Order fields are traversed when user hits the tab key + tabIndex: "0", + + // currentFocus: Date + // Date object containing the currently focused date, or the date which would be focused + // if the calendar itself was focused. Also indicates which year and month to display, + // i.e. the current "page" the calendar is on. + currentFocus: new Date(), + + baseClass:"dijitCalendar", + + _isValidDate: function(/*Date*/ value){ + // summary: + // Runs various tests on the value, checking that it's a valid date, rather + // than blank or NaN. + // tags: + // private + return value && !isNaN(value) && typeof value == "object" && + value.toString() != this.constructor.prototype.value.toString(); + }, + + _getValueAttr: function(){ + // summary: + // Support get('value') + + // this.value is set to 1AM, but return midnight, local time for back-compat + if(this.value && !isNaN(this.value)){ + var value = new this.dateClassObj(this.value); + value.setHours(0, 0, 0, 0); + + // If daylight savings pushes midnight to the previous date, fix the Date + // object to point at 1am so it will represent the correct day. See #9366 + if(value.getDate() < this.value.getDate()){ + value = this.dateFuncObj.add(value, "hour", 1); + } + return value; + }else{ + return null; + } + }, + + _setValueAttr: function(/*Date|Number*/ value, /*Boolean*/ priorityChange){ + // summary: + // Support set("value", ...) + // description: + // Set the current date and update the UI. If the date is disabled, the value will + // not change, but the display will change to the corresponding month. + // value: + // Either a Date or the number of seconds since 1970. + // tags: + // protected + if(value){ + // convert from Number to Date, or make copy of Date object so that setHours() call below + // doesn't affect original value + value = new this.dateClassObj(value); + } + if(this._isValidDate(value)){ + if(!this._isValidDate(this.value) || this.dateFuncObj.compare(value, this.value)){ + value.setHours(1, 0, 0, 0); // round to nearest day (1am to avoid issues when DST shift occurs at midnight, see #8521, #9366) + + if(!this.isDisabledDate(value, this.lang)){ + this._set("value", value); + + // Set focus cell to the new value. Arguably this should only happen when there isn't a current + // focus point. This will also repopulate the grid, showing the new selected value (and possibly + // new month/year). + this.set("currentFocus", value); + + if(priorityChange || typeof priorityChange == "undefined"){ + this.onChange(this.get('value')); + } + } + } + }else{ + // clear value, and repopulate grid (to deselect the previously selected day) without changing currentFocus + this._set("value", null); + this.set("currentFocus", this.currentFocus); + } + }, + + _setText: function(node, text){ + // summary: + // This just sets the content of node to the specified text. + // Can't do "node.innerHTML=text" because of an IE bug w/tables, see #3434. + // tags: + // private + while(node.firstChild){ + node.removeChild(node.firstChild); + } + node.appendChild(win.doc.createTextNode(text)); + }, + + _populateGrid: function(){ + // summary: + // Fills in the calendar grid with each day (1-31) + // tags: + // private + + var month = new this.dateClassObj(this.currentFocus); + month.setDate(1); + + var firstDay = month.getDay(), + daysInMonth = this.dateFuncObj.getDaysInMonth(month), + daysInPreviousMonth = this.dateFuncObj.getDaysInMonth(this.dateFuncObj.add(month, "month", -1)), + today = new this.dateClassObj(), + dayOffset = cldrSupplemental.getFirstDayOfWeek(this.lang); + if(dayOffset > firstDay){ dayOffset -= 7; } + + // Mapping from date (as specified by number returned from Date.valueOf()) to corresponding <td> + this._date2cell = {}; + + // Iterate through dates in the calendar and fill in date numbers and style info + array.forEach(this.dateCells, function(template, idx){ + var i = idx + dayOffset; + var date = new this.dateClassObj(month), + number, clazz = "dijitCalendar", adj = 0; + + if(i < firstDay){ + number = daysInPreviousMonth - firstDay + i + 1; + adj = -1; + clazz += "Previous"; + }else if(i >= (firstDay + daysInMonth)){ + number = i - firstDay - daysInMonth + 1; + adj = 1; + clazz += "Next"; + }else{ + number = i - firstDay + 1; + clazz += "Current"; + } + + if(adj){ + date = this.dateFuncObj.add(date, "month", adj); + } + date.setDate(number); + + if(!this.dateFuncObj.compare(date, today, "date")){ + clazz = "dijitCalendarCurrentDate " + clazz; + } + + if(this._isSelectedDate(date, this.lang)){ + clazz = "dijitCalendarSelectedDate " + clazz; + template.setAttribute("aria-selected", true); + }else{ + template.setAttribute("aria-selected", false); + } + + if(this.isDisabledDate(date, this.lang)){ + clazz = "dijitCalendarDisabledDate " + clazz; + template.setAttribute("aria-disabled", true); + }else{ + clazz = "dijitCalendarEnabledDate " + clazz; + template.removeAttribute("aria-disabled"); + } + + var clazz2 = this.getClassForDate(date, this.lang); + if(clazz2){ + clazz = clazz2 + " " + clazz; + } + + template.className = clazz + "Month dijitCalendarDateTemplate"; + + // Each cell has an associated integer value representing it's date + var dateVal = date.valueOf(); + this._date2cell[dateVal] = template; + template.dijitDateValue = dateVal; + + // Set Date string (ex: "13"). + this._setText(this.dateLabels[idx], date.getDateLocalized ? date.getDateLocalized(this.lang) : date.getDate()); + }, this); + + // set name of this month + this.monthWidget.set("month", month); + + // Fill in localized prev/current/next years + var y = month.getFullYear() - 1; + var d = new this.dateClassObj(); + array.forEach(["previous", "current", "next"], function(name){ + d.setFullYear(y++); + this._setText(this[name+"YearLabelNode"], + this.dateLocaleModule.format(d, {selector:'year', locale:this.lang})); + }, this); + }, + + goToToday: function(){ + // summary: + // Sets calendar's value to today's date + this.set('value', new this.dateClassObj()); + }, + + constructor: function(/*Object*/args){ + this.datePackage = args.datePackage || this.datePackage; + this.dateFuncObj = typeof this.datePackage == "string" ? + lang.getObject(this.datePackage, false) :// "string" part for back-compat, remove for 2.0 + this.datePackage; + this.dateClassObj = this.dateFuncObj.Date || Date; + this.dateLocaleModule = lang.getObject("locale", false, this.dateFuncObj); + }, + + _createMonthWidget: function(){ + // summary: + // Creates the drop down button that displays the current month and lets user pick a new one + + return CalendarLite._MonthWidget({ + id: this.id + "_mw", + lang: this.lang, + dateLocaleModule: this.dateLocaleModule + }, this.monthNode); + }, + + buildRendering: function(){ + // Markup for days of the week (referenced from template) + var d = this.dowTemplateString, + dayNames = this.dateLocaleModule.getNames('days', this.dayWidth, 'standAlone', this.lang), + dayOffset = cldrSupplemental.getFirstDayOfWeek(this.lang); + this.dayCellsHtml = string.substitute([d,d,d,d,d,d,d].join(""), {d: ""}, function(){ + return dayNames[dayOffset++ % 7] + }); + + // Markup for dates of the month (referenced from template), but without numbers filled in + var r = string.substitute(this.weekTemplateString, {d: this.dateTemplateString}); + this.dateRowsHtml = [r,r,r,r,r,r].join(""); + + // Instantiate from template. + // dateCells and dateLabels arrays filled when _Templated parses my template. + this.dateCells = []; + this.dateLabels = []; + this.inherited(arguments); + + dom.setSelectable(this.domNode, false); + + var dateObj = new this.dateClassObj(this.currentFocus); + + this._supportingWidgets.push(this.monthWidget = this._createMonthWidget()); + + this.set('currentFocus', dateObj, false); // draw the grid to the month specified by currentFocus + + // Set up connects for increment/decrement of months/years + var connect = lang.hitch(this, function(nodeProp, part, amount){ + this.connect(this[nodeProp], "onclick", function(){ + this._setCurrentFocusAttr(this.dateFuncObj.add(this.currentFocus, part, amount)); + }); + }); + connect("incrementMonth", "month", 1); + connect("decrementMonth", "month", -1); + connect("nextYearLabelNode", "year", 1); + connect("previousYearLabelNode", "year", -1); + }, + + _setCurrentFocusAttr: function(/*Date*/ date, /*Boolean*/ forceFocus){ + // summary: + // If the calendar currently has focus, then focuses specified date, + // changing the currently displayed month/year if necessary. + // If the calendar doesn't have focus, updates currently + // displayed month/year, and sets the cell that will get focus. + // forceFocus: + // If true, will focus() the cell even if calendar itself doesn't have focus + + var oldFocus = this.currentFocus, + oldCell = oldFocus && this._date2cell ? this._date2cell[oldFocus.valueOf()] : null; + + // round specified value to nearest day (1am to avoid issues when DST shift occurs at midnight, see #8521, #9366) + date = new this.dateClassObj(date); + date.setHours(1, 0, 0, 0); + + this._set("currentFocus", date); + + // TODO: only re-populate grid when month/year has changed + this._populateGrid(); + + // set tabIndex=0 on new cell, and focus it (but only if Calendar itself is focused) + var newCell = this._date2cell[date.valueOf()]; + newCell.setAttribute("tabIndex", this.tabIndex); + if(this.focused || forceFocus){ + newCell.focus(); + } + + // set tabIndex=-1 on old focusable cell + if(oldCell && oldCell != newCell){ + if(has("webkit")){ // see #11064 about webkit bug + oldCell.setAttribute("tabIndex", "-1"); + }else{ + oldCell.removeAttribute("tabIndex"); + } + } + }, + + focus: function(){ + // summary: + // Focus the calendar by focusing one of the calendar cells + this._setCurrentFocusAttr(this.currentFocus, true); + }, + + _onDayClick: function(/*Event*/ evt){ + // summary: + // Handler for day clicks, selects the date if appropriate + // tags: + // protected + event.stop(evt); + for(var node = evt.target; node && !node.dijitDateValue; node = node.parentNode); + if(node && !domClass.contains(node, "dijitCalendarDisabledDate")){ + this.set('value', node.dijitDateValue); + } + }, + + onChange: function(/*Date*/ /*===== date =====*/){ + // summary: + // Called only when the selected date has changed + }, + + _isSelectedDate: function(dateObject /*===== , locale =====*/){ + // summary: + // Extension point so developers can subclass Calendar to + // support multiple (concurrently) selected dates + // dateObject: Date + // locale: String? + // tags: + // protected extension + return this._isValidDate(this.value) && !this.dateFuncObj.compare(dateObject, this.value, "date") + }, + + isDisabledDate: function(/*===== dateObject, locale =====*/){ + // summary: + // May be overridden to disable certain dates in the calendar e.g. `isDisabledDate=dojo.date.locale.isWeekend` + // dateObject: Date + // locale: String? + // tags: + // extension +/*===== + return false; // Boolean +=====*/ + }, + + getClassForDate: function(/*===== dateObject, locale =====*/){ + // summary: + // May be overridden to return CSS classes to associate with the date entry for the given dateObject, + // for example to indicate a holiday in specified locale. + // dateObject: Date + // locale: String? + // tags: + // extension + +/*===== + return ""; // String +=====*/ + } + }); + + CalendarLite._MonthWidget = declare("dijit.CalendarLite._MonthWidget", _WidgetBase, { + // summary: + // Displays name of current month padded to the width of the month + // w/the longest name, so that changing months doesn't change width. + // + // Create as new dijit.Calendar._MonthWidget({ + // lang: ..., + // dateLocaleModule: ... + // }) + + _setMonthAttr: function(month){ + // summary: + // Set the current month to display as a label + var monthNames = this.dateLocaleModule.getNames('months', 'wide', 'standAlone', this.lang, month), + spacer = + (has("ie") == 6 ? "" : "<div class='dijitSpacer'>" + + array.map(monthNames, function(s){ return "<div>" + s + "</div>"; }).join("") + "</div>"); + + // Set name of current month and also fill in spacer element with all the month names + // (invisible) so that the maximum width will affect layout. But not on IE6 because then + // the center <TH> overlaps the right <TH> (due to a browser bug). + this.domNode.innerHTML = + spacer + + "<div class='dijitCalendarMonthLabel dijitCalendarCurrentMonthLabel'>" + + monthNames[month.getMonth()] + "</div>"; + } + }); + + return CalendarLite; +}); + +}, +'dijit/CheckedMenuItem':function(){ +require({cache:{ +'url:dijit/templates/CheckedMenuItem.html':"<tr class=\"dijitReset dijitMenuItem\" data-dojo-attach-point=\"focusNode\" role=\"menuitemcheckbox\" 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=\"dijitMenuItemIcon dijitCheckedMenuItemIcon\" data-dojo-attach-point=\"iconNode\"/>\n\t\t<span class=\"dijitCheckedMenuItemIconChar\">✓</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" data-dojo-attach-point=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" data-dojo-attach-point=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" role=\"presentation\"> </td>\n</tr>\n"}}); +define("dijit/CheckedMenuItem", [ + "dojo/_base/declare", // declare + "dojo/dom-class", // domClass.toggle + "./MenuItem", + "dojo/text!./templates/CheckedMenuItem.html", + "./hccss" +], function(declare, domClass, MenuItem, template){ + +/*===== + var MenuItem = dijit.MenuItem; +=====*/ + + // module: + // dijit/CheckedMenuItem + // summary: + // A checkbox-like menu item for toggling on and off + + return declare("dijit.CheckedMenuItem", MenuItem, { + // summary: + // A checkbox-like menu item for toggling on and off + + templateString: template, + + // 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. + domClass.toggle(this.domNode, "dijitCheckedMenuItemChecked", checked); + this.domNode.setAttribute("aria-checked", checked); + this._set("checked", checked); + }, + + iconClass: "", // override dijitNoIcon + + 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); + } + }); +}); + +}, +'dijit/form/VerticalRuleLabels':function(){ +define([ + "dojo/_base/declare", // declare + "./HorizontalRuleLabels" +], function(declare, HorizontalRuleLabels){ + +/*===== + var HorizontalRuleLabels = dijit.form.HorizontalRuleLabels; +=====*/ + + // module: + // dijit/form/VerticalRuleLabels + // summary: + // Labels for the `dijit.form.VerticalSlider` + + return declare("dijit.form.VerticalRuleLabels", HorizontalRuleLabels, { + // summary: + // Labels for the `dijit.form.VerticalSlider` + + templateString: '<div class="dijitRuleContainer dijitRuleContainerV dijitRuleLabelsContainer dijitRuleLabelsContainerV"></div>', + + _positionPrefix: '<div class="dijitRuleLabelContainer dijitRuleLabelContainerV" style="top:', + _labelPrefix: '"><span class="dijitRuleLabel dijitRuleLabelV">', + + _calcPosition: function(pos){ + // Overrides HorizontalRuleLabel._calcPosition() + return 100-pos; + }, + + // needed to prevent labels from being reversed in RTL mode + _isHorizontal: false + }); +}); + +}, +'dijit/Declaration':function(){ +define([ + "dojo/_base/array", // array.forEach array.map + "dojo/_base/connect", // connect.connect + "dojo/_base/declare", // declare + "dojo/_base/lang", // lang.getObject + "dojo/parser", // parser._functionFromScript + "dojo/query", // query + "./_Widget", + "./_TemplatedMixin", + "./_WidgetsInTemplateMixin", + "dojo/NodeList-dom" +], function(array, connect, declare, lang, parser, query, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin){ + +/*===== + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var _WidgetsInTemplateMixin = dijit._WidgetsInTemplateMixin; +=====*/ + + // module: + // dijit/Declaration + // summary: + // The Declaration widget allows a developer to declare new widget + // classes directly from a snippet of markup. + + return declare("dijit.Declaration", _Widget, { + // summary: + // The Declaration widget allows a developer to declare new widget + // classes directly from a snippet of markup. + + // _noScript: [private] Boolean + // Flag to parser to leave alone the script tags contained inside of me + _noScript: true, + + // stopParser: [private] Boolean + // Flag to parser to not try and parse widgets declared inside of me + stopParser: true, + + // widgetClass: [const] String + // Name of class being declared, ex: "acme.myWidget" + widgetClass: "", + + // propList: [const] Object + // Set of attributes for this widget along with default values, ex: + // {delay: 100, title: "hello world"} + defaults: null, + + // mixins: [const] String[] + // List containing the prototype for this widget, and also any mixins, + // ex: ["dijit._Widget", "dijit._Container"] + mixins: [], + + buildRendering: function(){ + var src = this.srcNodeRef.parentNode.removeChild(this.srcNodeRef), + methods = query("> script[type^='dojo/method']", src).orphan(), + connects = query("> script[type^='dojo/connect']", src).orphan(), + srcType = src.nodeName; + + var propList = this.defaults || {}; + + // For all methods defined like <script type="dojo/method" data-dojo-event="foo">, + // add that method to prototype. + // If there's no "event" specified then it's code to run on instantiation, + // so it becomes a connection to "postscript" (handled below). + array.forEach(methods, function(s){ + var evt = s.getAttribute("event") || s.getAttribute("data-dojo-event"), + func = parser._functionFromScript(s); + if(evt){ + propList[evt] = func; + }else{ + connects.push(s); + } + }); + + // map array of strings like [ "dijit.form.Button" ] to array of mixin objects + // (note that array.map(this.mixins, lang.getObject) doesn't work because it passes + // a bogus third argument to getObject(), confusing it) + this.mixins = this.mixins.length ? + array.map(this.mixins, function(name){ return lang.getObject(name); } ) : + [ _Widget, _TemplatedMixin, _WidgetsInTemplateMixin ]; + + propList._skipNodeCache = true; + propList.templateString = + "<"+srcType+" class='"+src.className+"'" + + " data-dojo-attach-point='"+ + (src.getAttribute("data-dojo-attach-point") || src.getAttribute("dojoAttachPoint") || '')+ + "' data-dojo-attach-event='"+ + (src.getAttribute("data-dojo-attach-event") || src.getAttribute("dojoAttachEvent") || '')+ + "' >"+src.innerHTML.replace(/\%7B/g,"{").replace(/\%7D/g,"}")+"</"+srcType+">"; + + // create the new widget class + var wc = declare( + this.widgetClass, + this.mixins, + propList + ); + + // Handle <script> blocks of form: + // <script type="dojo/connect" data-dojo-event="foo"> + // and + // <script type="dojo/method"> + // (Note that the second one is just shorthand for a dojo/connect to postscript) + // Since this is a connect in the declaration, we are actually connection to the method + // in the _prototype_. + array.forEach(connects, function(s){ + var evt = s.getAttribute("event") || s.getAttribute("data-dojo-event") || "postscript", + func = parser._functionFromScript(s); + connect.connect(wc.prototype, evt, func); + }); + } + }); +}); + +}, +'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){ + +/*===== + var _WidgetBase = dijit._WidgetBase; + var _TemplatedMixin = dijit._TemplatedMixin; + var _Contained = dijit._Contained; +=====*/ + + // module: + // dijit/MenuSeparator + // summary: + // A line between two menu items + + return declare("dijit.MenuSeparator", [_WidgetBase, _TemplatedMixin, _Contained], { + // summary: + // A line between two menu items + + templateString: template, + + buildRendering: function(){ + this.inherited(arguments); + dom.setSelectable(this.domNode, false); + }, + + isFocusable: function(){ + // summary: + // Override to always return false + // tags: + // protected + + return false; // Boolean + } + }); +}); + +}, +'dijit/form/_ComboBoxMenu':function(){ +define([ + "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){ + +/*===== + var _WidgetBase = dijit._WidgetBase; + var _TemplatedMixin = dijit._TemplatedMixin; + var _ComboBoxMenuMixin = dijit.form._ComboBoxMenuMixin; + var _ListMouseMixin = dijit.form._ListMouseMixin; +=====*/ + + // module: + // dijit/form/_ComboBoxMenu + // summary: + // Focus-less menu for internal use in `dijit.form.ComboBox` + + 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 + + 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>", + + baseClass: "dijitComboBoxMenu", + + postCreate: function(){ + this.inherited(arguments); + if(!this.isLeftToRight()){ + domClass.add(this.previousButton, "dijitMenuItemRtl"); + domClass.add(this.nextButton, "dijitMenuItemRtl"); + } + }, + + _createMenuItem: function(){ + return domConstruct.create("div", { + "class": "dijitReset dijitMenuItem" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl"), + role: "option" + }); + }, + + onHover: function(/*DomNode*/ node){ + // summary: + // Add hover CSS + domClass.add(node, "dijitMenuItemHover"); + }, + + onUnhover: function(/*DomNode*/ node){ + // summary: + // Remove hover CSS + domClass.remove(node, "dijitMenuItemHover"); + }, + + onSelect: function(/*DomNode*/ node){ + // summary: + // Add selected CSS + domClass.add(node, "dijitMenuItemSelected"); + }, + + onDeselect: function(/*DomNode*/ node){ + // summary: + // Remove selected CSS + domClass.remove(node, "dijitMenuItemSelected"); + }, + + _page: function(/*Boolean*/ up){ + // summary: + // Handles page-up and page-down keypresses + + 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(); + } + 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; + } + }, + + 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; + } + } + }); +}); + +}, +'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; +=====*/ + + + // module: + // dijit/Dialog + // summary: + // A modal dialog Widget + + + /*===== + 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 _DialogBase = declare("dijit._DialogBase", [_TemplatedMixin, _FormMixin, _DialogMixin, _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 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(); + + templateString: template, + + baseClass: "dijitDialog", + + cssStateNodes: { + closeButtonNode: "dijitDialogCloseIcon" + }, + + // Map widget attributes to DOMNode attributes. + _setTitleAttr: [ + { node: "titleNode", type: "innerHTML" }, + { node: "titleBar", type: "attribute" } + ], + + // 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, + + //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":"", + + postMixInProperties: function(){ + var _nlsResources = i18n.getLocalization("dijit", "common"); + lang.mixin(this, _nlsResources); + this.inherited(arguments); + }, + + postCreate: function(){ + domStyle.set(this.domNode, { + display: "none", + position:"absolute" + }); + win.body().appendChild(this.domNode); + + this.inherited(arguments); + + this.connect(this, "onExecute", "hide"); + this.connect(this, "onCancel", "hide"); + this._modalconnects = []; + }, + + 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 + + // 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); + }, + + _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(); + }, + + _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 + + var node = this.domNode; + + 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"); + } + + this.underlayAttrs = { + dialogId: this.id, + "class": array.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" ") + }; + }, + + _size: function(){ + // summary: + // If necessary, shrink dialog contents so dialog fits in viewport + // tags: + // private + + this._checkIfSingleChild(); + + // 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" + }); + } + + 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 + + var w = Math.min(bb.w, Math.floor(viewport.w * 0.75)), + h = Math.min(bb.h, Math.floor(viewport.h * 0.75)); + + 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{ + if(this._singleChild && this._singleChild.resize){ + this._singleChild.resize(); + } + } + }, + + _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" + }); + } + }, + + _onKey: function(/*Event*/ evt){ + // summary: + // Handles the keyboard events for accessibility reasons + // tags: + // private + + 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{ + // 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*/ } + } + } + } + }, + + show: 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(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"))); + + domStyle.set(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 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._fadeInDeferred.callback(true); + delete this._fadeInDeferred; + }) + }).play(); + + return this._fadeInDeferred; + }, + + hide: function(){ + // summary: + // 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; + } + if(this._fadeInDeferred){ + this._fadeInDeferred.cancel(); + } + + // fade-in Animation object, setup below + var fadeOut; + + 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')); + + 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(); + + if(this._scrollConnected){ + this._scrollConnected = false; + } + var h; + while(h = this._modalconnects.pop()){ + h.remove(); + } + + if(this._relativePosition){ + delete this._relativePosition; + } + this._set("open", false); + + 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(); + } + }, + + destroy: function(){ + 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(); + } + + DialogLevelManager.hide(this); + + this.inherited(arguments); + } + }); + + var Dialog = declare("dijit.Dialog", [ContentPane, _DialogBase], {}); + Dialog._DialogBase = _DialogBase; // for monkey patching + + var DialogLevelManager = Dialog._DialogLevelManager = { + // 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. + + _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. + + // 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}); + }, + + 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. + + if(ds[ds.length-1].dialog == dialog){ + // Removing the top (or only) dialog in the stack, return focus + // to previous dialog + + ds.pop(); + + var pd = ds[ds.length-1]; // the new active dialog (or the base page itself) + + // 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); + } + + // 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; + } + + if(focus){ + focus.focus(); + } + } + }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); + } + } + }, + + isTop: function(/*dijit._Widget*/ dialog){ + // summary: + // Returns true if specified Dialog is the top in the task + 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) + // } + 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 + }); + } + + return Dialog; +}); + +}, +'dijit/form/MultiSelect':function(){ +define("dijit/form/MultiSelect", [ + "dojo/_base/array", // array.indexOf, array.map + "dojo/_base/declare", // declare + "dojo/dom-geometry", // domGeometry.setMarginBox + "dojo/query", // query + "./_FormValueWidget" +], function(array, declare, domGeometry, query, _FormValueWidget){ + +/*===== + var _FormValueWidget = dijit.form._FormValueWidget; +=====*/ + +// module: +// dijit/form/MultiSelect +// summary: +// Widget version of a <select multiple=true> element, +// for selecting multiple options. + +return declare("dijit.form.MultiSelect", _FormValueWidget, { + // summary: + // Widget version of a <select multiple=true> element, + // for selecting multiple options. + + // size: Number + // Number of elements to display on a page + // NOTE: may be removed in version 2.0, since elements may have variable height; + // set the size via style="..." or CSS class names instead. + size: 7, + + templateString: "<select multiple='true' ${!nameAttrSetting} data-dojo-attach-point='containerNode,focusNode' data-dojo-attach-event='onchange: _onChange'></select>", + + addSelected: function(/*dijit.form.MultiSelect*/ select){ + // summary: + // Move the selected nodes of a passed Select widget + // instance to this Select widget. + // + // example: + // | // move all the selected values from "bar" to "foo" + // | dijit.byId("foo").addSelected(dijit.byId("bar")); + + select.getSelected().forEach(function(n){ + this.containerNode.appendChild(n); + // scroll to bottom to see item + // cannot use scrollIntoView since <option> tags don't support all attributes + // does not work on IE due to a bug where <select> always shows scrollTop = 0 + this.domNode.scrollTop = this.domNode.offsetHeight; // overshoot will be ignored + // scrolling the source select is trickier esp. on safari who forgets to change the scrollbar size + var oldscroll = select.domNode.scrollTop; + select.domNode.scrollTop = 0; + select.domNode.scrollTop = oldscroll; + },this); + this._set('value', this.get('value')); + }, + + getSelected: function(){ + // summary: + // Access the NodeList of the selected options directly + return query("option",this.containerNode).filter(function(n){ + return n.selected; // Boolean + }); // dojo.NodeList + }, + + _getValueAttr: function(){ + // summary: + // Hook so get('value') works. + // description: + // Returns an array of the selected options' values. + + // Don't call getSelect.map() because it doesn't return a real array, + // and that messes up dojo.toJson() calls like in the Form.html test + return array.map(this.getSelected(), function(n){ + return n.value; + }); + }, + + multiple: true, // for Form + + _setValueAttr: function(/*Array*/ values, /*Boolean?*/ priorityChange){ + // summary: + // Hook so set('value', values) works. + // description: + // Set the value(s) of this Select based on passed values + query("option",this.containerNode).forEach(function(n){ + n.selected = (array.indexOf(values,n.value) != -1); + }); + this.inherited(arguments); + }, + + invertSelection: function(/*Boolean?*/ onChange){ + // summary: + // Invert the selection + // onChange: Boolean + // If false, onChange is not fired. + var val = []; + query("option",this.containerNode).forEach(function(n){ + if(!n.selected){ val.push(n.value); } + }); + this._setValueAttr(val, !(onChange === false || onChange == null)); + }, + + _onChange: function(/*Event*/){ + this._handleOnChange(this.get('value'), true); + }, + + // for layout widgets: + resize: function(/*Object*/ size){ + if(size){ + domGeometry.setMarginBox(this.domNode, size); + } + }, + + postCreate: function(){ + this._set('value', this.get('value')); + this.inherited(arguments); + } +}); + +}); + +}, +'dijit/form/_DateTimeTextBox':function(){ +define([ + "dojo/date", // date date.compare + "dojo/date/locale", // locale.regexp + "dojo/date/stamp", // stamp.fromISOString stamp.toISOString + "dojo/_base/declare", // declare + "dojo/_base/lang", // lang.getObject + "./RangeBoundTextBox", + "../_HasDropDown", + "dojo/text!./templates/DropDownBox.html" +], function(date, locale, stamp, declare, lang, RangeBoundTextBox, _HasDropDown, template){ + +/*===== + var _HasDropDown = dijit._HasDropDown; + var RangeBoundTextBox = dijit.form.RangeBoundTextBox; +=====*/ + + // module: + // dijit/form/_DateTimeTextBox + // summary: + // Base class for validating, serializable, range-bound date or time text box. + + + new Date("X"); // workaround for #11279, new Date("") == NaN + + /*===== + declare( + "dijit.form._DateTimeTextBox.__Constraints", + [RangeBoundTextBox.__Constraints, locale.__FormatOptions], { + // summary: + // Specifies both the rules on valid/invalid values (first/last date/time allowed), + // and also formatting options for how the date/time is displayed. + // example: + // To restrict to dates within 2004, displayed in a long format like "December 25, 2005": + // | {min:'2004-01-01',max:'2004-12-31', formatLength:'long'} + }); + =====*/ + + var _DateTimeTextBox = declare("dijit.form._DateTimeTextBox", [RangeBoundTextBox, _HasDropDown], { + // summary: + // Base class for validating, serializable, range-bound date or time text box. + + templateString: template, + + // hasDownArrow: [const] Boolean + // Set this textbox to display a down arrow button, to open the drop down list. + hasDownArrow: true, + + // openOnClick: [const] Boolean + // Set to true to open drop down upon clicking anywhere on the textbox. + openOnClick: true, + + /*===== + // constraints: dijit.form._DateTimeTextBox.__Constraints + // Despite the name, this parameter specifies both constraints on the input + // (including starting/ending dates/times allowed) as well as + // formatting options like whether the date is displayed in long (ex: December 25, 2005) + // or short (ex: 12/25/2005) format. See `dijit.form._DateTimeTextBox.__Constraints` for details. + constraints: {}, + ======*/ + + // Override ValidationTextBox.regExpGen().... we use a reg-ex generating function rather + // than a straight regexp to deal with locale (plus formatting options too?) + regExpGen: locale.regexp, + + // datePackage: String + // JavaScript namespace to find calendar routines. Uses Gregorian calendar routines + // at dojo.date, by default. + datePackage: date, + + postMixInProperties: function(){ + this.inherited(arguments); + this._set("type", "text"); // in case type="date"|"time" was specified which messes up parse/format + }, + + // Override _FormWidget.compare() to work for dates/times + compare: function(/*Date*/ val1, /*Date*/ val2){ + var isInvalid1 = this._isInvalidDate(val1); + var isInvalid2 = this._isInvalidDate(val2); + return isInvalid1 ? (isInvalid2 ? 0 : -1) : (isInvalid2 ? 1 : date.compare(val1, val2, this._selector)); + }, + + // flag to _HasDropDown to make drop down Calendar width == <input> width + forceWidth: true, + + format: function(/*Date*/ value, /*dojo.date.locale.__FormatOptions*/ constraints){ + // summary: + // Formats the value as a Date, according to specified locale (second argument) + // tags: + // protected + if(!value){ return ''; } + return this.dateLocaleModule.format(value, constraints); + }, + + "parse": function(/*String*/ value, /*dojo.date.locale.__FormatOptions*/ constraints){ + // summary: + // Parses as string as a Date, according to constraints + // tags: + // protected + + return this.dateLocaleModule.parse(value, constraints) || (this._isEmpty(value) ? null : undefined); // Date + }, + + // Overrides ValidationTextBox.serialize() to serialize a date in canonical ISO format. + serialize: function(/*anything*/ val, /*Object?*/ options){ + if(val.toGregorian){ + val = val.toGregorian(); + } + return stamp.toISOString(val, options); + }, + + // dropDownDefaultValue: Date + // The default value to focus in the popupClass widget when the textbox value is empty. + dropDownDefaultValue : new Date(), + + // value: Date + // The value of this widget as a JavaScript Date object. Use get("value") / set("value", val) to manipulate. + // When passed to the parser in markup, must be specified according to `dojo.date.stamp.fromISOString` + value: new Date(""), // value.toString()="NaN" + + _blankValue: null, // used by filter() when the textbox is blank + + // popupClass: [protected extension] String + // Name of the popup widget class used to select a date/time. + // Subclasses should specify this. + popupClass: "", // default is no popup = text only + + + // _selector: [protected extension] String + // Specifies constraints.selector passed to dojo.date functions, should be either + // "date" or "time". + // Subclass must specify this. + _selector: "", + + constructor: function(/*Object*/ args){ + this.datePackage = args.datePackage || this.datePackage; + this.dateFuncObj = typeof this.datePackage == "string" ? + lang.getObject(this.datePackage, false) :// "string" part for back-compat, remove for 2.0 + this.datePackage; + this.dateClassObj = this.dateFuncObj.Date || Date; + this.dateLocaleModule = lang.getObject("locale", false, this.dateFuncObj); + this.regExpGen = this.dateLocaleModule.regexp; + this._invalidDate = this.constructor.prototype.value.toString(); + }, + + buildRendering: function(){ + this.inherited(arguments); + + if(!this.hasDownArrow){ + this._buttonNode.style.display = "none"; + } + + // If openOnClick is true, we basically just want to treat the whole widget as the + // button. We need to do that also if the actual drop down button will be hidden, + // so that there's a mouse method for opening the drop down. + if(this.openOnClick || !this.hasDownArrow){ + this._buttonNode = this.domNode; + this.baseClass += " dijitComboBoxOpenOnClick"; + } + }, + + _setConstraintsAttr: function(/*Object*/ constraints){ + constraints.selector = this._selector; + constraints.fullYear = true; // see #5465 - always format with 4-digit years + var fromISO = stamp.fromISOString; + if(typeof constraints.min == "string"){ constraints.min = fromISO(constraints.min); } + if(typeof constraints.max == "string"){ constraints.max = fromISO(constraints.max); } + this.inherited(arguments); + }, + + _isInvalidDate: function(/*Date*/ value){ + // summary: + // Runs various tests on the value, checking for invalid conditions + // tags: + // private + return !value || isNaN(value) || typeof value != "object" || value.toString() == this._invalidDate; + }, + + _setValueAttr: function(/*Date|String*/ value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){ + // summary: + // Sets the date on this textbox. Note: value can be a JavaScript Date literal or a string to be parsed. + if(value !== undefined){ + if(typeof value == "string"){ + value = stamp.fromISOString(value); + } + if(this._isInvalidDate(value)){ + value = null; + } + if(value instanceof Date && !(this.dateClassObj instanceof Date)){ + value = new this.dateClassObj(value); + } + } + this.inherited(arguments); + if(this.value instanceof Date){ + this.filterString = ""; + } + if(this.dropDown){ + this.dropDown.set('value', value, false); + } + }, + + _set: function(attr, value){ + // Avoid spurious watch() notifications when value is changed to new Date object w/the same value + if(attr == "value" && this.value instanceof Date && this.compare(value, this.value) == 0){ + return; + } + this.inherited(arguments); + }, + + _setDropDownDefaultValueAttr: function(/*Date*/ val){ + if(this._isInvalidDate(val)){ + // convert null setting into today's date, since there needs to be *some* default at all times. + val = new this.dateClassObj(); + } + this.dropDownDefaultValue = val; + }, + + openDropDown: function(/*Function*/ callback){ + // rebuild drop down every time, so that constraints get copied (#6002) + if(this.dropDown){ + this.dropDown.destroy(); + } + var PopupProto = lang.isString(this.popupClass) ? lang.getObject(this.popupClass, false) : this.popupClass, + textBox = this, + value = this.get("value"); + this.dropDown = new PopupProto({ + onChange: function(value){ + // this will cause InlineEditBox and other handlers to do stuff so make sure it's last + textBox.set('value', value, true); + }, + id: this.id + "_popup", + dir: textBox.dir, + lang: textBox.lang, + value: value, + currentFocus: !this._isInvalidDate(value) ? value : this.dropDownDefaultValue, + constraints: textBox.constraints, + filterString: textBox.filterString, // for TimeTextBox, to filter times shown + + datePackage: textBox.datePackage, + + isDisabledDate: function(/*Date*/ date){ + // summary: + // disables dates outside of the min/max of the _DateTimeTextBox + return !textBox.rangeCheck(date, textBox.constraints); + } + }); + + this.inherited(arguments); + }, + + _getDisplayedValueAttr: function(){ + return this.textbox.value; + }, + + _setDisplayedValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){ + this._setValueAttr(this.parse(value, this.constraints), priorityChange, value); + } + }); + + return _DateTimeTextBox; +}); + +}, +'dijit/form/_ToggleButtonMixin':function(){ +define([ + "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). + +return declare("dijit.form._ToggleButtonMixin", null, { + // summary: + // A mixin to provide functionality to allow a button that can be in two states (checked or not). + + // 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, + + // aria-pressed for toggle buttons, and aria-checked for checkboxes + _aria_attr: "aria-pressed", + + _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; + }, + + _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); + }, + + 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); + } +}); + +}); + +}, +'dijit/Calendar':function(){ +define([ + "dojo/_base/array", // array.map + "dojo/date", + "dojo/date/locale", + "dojo/_base/declare", // declare + "dojo/dom-attr", // domAttr.get + "dojo/dom-class", // domClass.add domClass.contains domClass.remove domClass.toggle + "dojo/_base/event", // event.stop + "dojo/_base/kernel", // kernel.deprecated + "dojo/keys", // keys + "dojo/_base/lang", // lang.hitch + "dojo/_base/sniff", // has("ie") + "./CalendarLite", + "./_Widget", + "./_CssStateMixin", + "./_TemplatedMixin", + "./form/DropDownButton", + "./hccss" // not used directly, but sets CSS class on <body> +], function(array, date, local, declare, domAttr, domClass, event, kernel, keys, lang, has, + CalendarLite, _Widget, _CssStateMixin, _TemplatedMixin, DropDownButton){ + +/*===== + var CalendarLite = dijit.CalendarLite; + var _CssStateMixin = dijit._CssStateMixin; + var _Widget = dijit._Widget; + var _TemplatedMixin = dijit._TemplatedMixin; + var DropDownButton = dijit.form.DropDownButton; +=====*/ + + // module: + // dijit/Calendar + // summary: + // A simple GUI for choosing a date in the context of a monthly calendar. + + var Calendar = declare("dijit.Calendar", + [CalendarLite, _Widget, _CssStateMixin], // _Widget for deprecated methods like setAttribute() + { + // summary: + // A simple GUI for choosing a date in the context of a monthly calendar. + // + // description: + // See CalendarLite for general description. Calendar extends CalendarLite, adding: + // - month drop down list + // - keyboard navigation + // - CSS classes for hover/mousepress on date, month, and year nodes + // - support of deprecated methods (will be removed in 2.0) + + // Set node classes for various mouse events, see dijit._CssStateMixin for more details + cssStateNodes: { + "decrementMonth": "dijitCalendarArrow", + "incrementMonth": "dijitCalendarArrow", + "previousYearLabelNode": "dijitCalendarPreviousYear", + "nextYearLabelNode": "dijitCalendarNextYear" + }, + + setValue: function(/*Date*/ value){ + // summary: + // Deprecated. Use set('value', ...) instead. + // tags: + // deprecated + kernel.deprecated("dijit.Calendar:setValue() is deprecated. Use set('value', ...) instead.", "", "2.0"); + this.set('value', value); + }, + + _createMonthWidget: function(){ + // summary: + // Creates the drop down button that displays the current month and lets user pick a new one + + return new Calendar._MonthDropDownButton({ + id: this.id + "_mddb", + tabIndex: -1, + onMonthSelect: lang.hitch(this, "_onMonthSelect"), + lang: this.lang, + dateLocaleModule: this.dateLocaleModule + }, this.monthNode); + }, + + buildRendering: function(){ + this.inherited(arguments); + + // Events specific to Calendar, not used in CalendarLite + this.connect(this.domNode, "onkeypress", "_onKeyPress"); + this.connect(this.dateRowsNode, "onmouseover", "_onDayMouseOver"); + this.connect(this.dateRowsNode, "onmouseout", "_onDayMouseOut"); + this.connect(this.dateRowsNode, "onmousedown", "_onDayMouseDown"); + this.connect(this.dateRowsNode, "onmouseup", "_onDayMouseUp"); + }, + + _onMonthSelect: function(/*Number*/ newMonth){ + // summary: + // Handler for when user selects a month from the drop down list + // tags: + // protected + + // move to selected month, bounding by the number of days in the month + // (ex: dec 31 --> jan 28, not jan 31) + this._setCurrentFocusAttr(this.dateFuncObj.add(this.currentFocus, "month", + newMonth - this.currentFocus.getMonth())); + }, + + _onDayMouseOver: function(/*Event*/ evt){ + // summary: + // Handler for mouse over events on days, sets hovered style + // tags: + // protected + + // event can occur on <td> or the <span> inside the td, + // set node to the <td>. + var node = + domClass.contains(evt.target, "dijitCalendarDateLabel") ? + evt.target.parentNode : + evt.target; + + if(node && ( + (node.dijitDateValue && !domClass.contains(node, "dijitCalendarDisabledDate")) + || node == this.previousYearLabelNode || node == this.nextYearLabelNode + )){ + domClass.add(node, "dijitCalendarHoveredDate"); + this._currentNode = node; + } + }, + + _onDayMouseOut: function(/*Event*/ evt){ + // summary: + // Handler for mouse out events on days, clears hovered style + // tags: + // protected + + if(!this._currentNode){ return; } + + // if mouse out occurs moving from <td> to <span> inside <td>, ignore it + if(evt.relatedTarget && evt.relatedTarget.parentNode == this._currentNode){ return; } + var cls = "dijitCalendarHoveredDate"; + if(domClass.contains(this._currentNode, "dijitCalendarActiveDate")){ + cls += " dijitCalendarActiveDate"; + } + domClass.remove(this._currentNode, cls); + this._currentNode = null; + }, + + _onDayMouseDown: function(/*Event*/ evt){ + var node = evt.target.parentNode; + if(node && node.dijitDateValue && !domClass.contains(node, "dijitCalendarDisabledDate")){ + domClass.add(node, "dijitCalendarActiveDate"); + this._currentNode = node; + } + }, + + _onDayMouseUp: function(/*Event*/ evt){ + var node = evt.target.parentNode; + if(node && node.dijitDateValue){ + domClass.remove(node, "dijitCalendarActiveDate"); + } + }, + + handleKey: function(/*Event*/ evt){ + // summary: + // Provides keyboard navigation of calendar. + // description: + // Called from _onKeyPress() to handle keypress on a stand alone Calendar, + // and also from `dijit.form._DateTimeTextBox` to pass a keypress event + // from the `dijit.form.DateTextBox` to be handled in this widget + // returns: + // False if the key was recognized as a navigation key, + // to indicate that the event was handled by Calendar and shouldn't be propogated + // tags: + // protected + var increment = -1, + interval, + newValue = this.currentFocus; + switch(evt.charOrCode){ + case keys.RIGHT_ARROW: + increment = 1; + //fallthrough... + case keys.LEFT_ARROW: + interval = "day"; + if(!this.isLeftToRight()){ increment *= -1; } + break; + case keys.DOWN_ARROW: + increment = 1; + //fallthrough... + case keys.UP_ARROW: + interval = "week"; + break; + case keys.PAGE_DOWN: + increment = 1; + //fallthrough... + case keys.PAGE_UP: + interval = evt.ctrlKey || evt.altKey ? "year" : "month"; + break; + case keys.END: + // go to the next month + newValue = this.dateFuncObj.add(newValue, "month", 1); + // subtract a day from the result when we're done + interval = "day"; + //fallthrough... + case keys.HOME: + newValue = new this.dateClassObj(newValue); + newValue.setDate(1); + break; + case keys.ENTER: + case " ": + this.set("value", this.currentFocus); + break; + default: + return true; + } + + if(interval){ + newValue = this.dateFuncObj.add(newValue, interval, increment); + } + + this._setCurrentFocusAttr(newValue); + + return false; + }, + + _onKeyPress: function(/*Event*/ evt){ + // summary: + // For handling keypress events on a stand alone calendar + if(!this.handleKey(evt)){ + event.stop(evt); + } + }, + + onValueSelected: function(/*Date*/ /*===== date =====*/){ + // summary: + // Deprecated. Notification that a date cell was selected. It may be the same as the previous value. + // description: + // Formerly used by `dijit.form._DateTimeTextBox` (and thus `dijit.form.DateTextBox`) + // to get notification when the user has clicked a date. Now onExecute() (above) is used. + // tags: + // protected + }, + + onChange: function(value){ + this.onValueSelected(value); // remove in 2.0 + }, + + getClassForDate: function(/*===== dateObject, locale =====*/){ + // summary: + // May be overridden to return CSS classes to associate with the date entry for the given dateObject, + // for example to indicate a holiday in specified locale. + // dateObject: Date + // locale: String? + // tags: + // extension + +/*===== + return ""; // String +=====*/ + } + }); + + Calendar._MonthDropDownButton = declare("dijit.Calendar._MonthDropDownButton", DropDownButton, { + // summary: + // DropDownButton for the current month. Displays name of current month + // and a list of month names in the drop down + + onMonthSelect: function(){ }, + + postCreate: function(){ + this.inherited(arguments); + this.dropDown = new Calendar._MonthDropDown({ + id: this.id + "_mdd", //do not change this id because it is referenced in the template + onChange: this.onMonthSelect + }); + }, + _setMonthAttr: function(month){ + // summary: + // Set the current month to display as a label + var monthNames = this.dateLocaleModule.getNames('months', 'wide', 'standAlone', this.lang, month); + this.dropDown.set("months", monthNames); + + // Set name of current month and also fill in spacer element with all the month names + // (invisible) so that the maximum width will affect layout. But not on IE6 because then + // the center <TH> overlaps the right <TH> (due to a browser bug). + this.containerNode.innerHTML = + (has("ie") == 6 ? "" : "<div class='dijitSpacer'>" + this.dropDown.domNode.innerHTML + "</div>") + + "<div class='dijitCalendarMonthLabel dijitCalendarCurrentMonthLabel'>" + monthNames[month.getMonth()] + "</div>"; + } + }); + + Calendar._MonthDropDown = declare("dijit.Calendar._MonthDropDown", [_Widget, _TemplatedMixin], { + // summary: + // The list-of-months drop down from the MonthDropDownButton + + // months: String[] + // List of names of months, possibly w/some undefined entries for Hebrew leap months + // (ex: ["January", "February", undefined, "April", ...]) + months: [], + + templateString: "<div class='dijitCalendarMonthMenu dijitMenu' " + + "data-dojo-attach-event='onclick:_onClick,onmouseover:_onMenuHover,onmouseout:_onMenuHover'></div>", + + _setMonthsAttr: function(/*String[]*/ months){ + this.domNode.innerHTML = array.map(months, function(month, idx){ + return month ? "<div class='dijitCalendarMonthLabel' month='" + idx +"'>" + month + "</div>" : ""; + }).join(""); + }, + + _onClick: function(/*Event*/ evt){ + this.onChange(domAttr.get(evt.target, "month")); + }, + + onChange: function(/*Number*/ /*===== month =====*/){ + // summary: + // Callback when month is selected from drop down + }, + + _onMenuHover: function(evt){ + domClass.toggle(evt.target, "dijitCalendarMonthLabelHover", evt.type == "mouseover"); + } + }); + + return Calendar; +}); + +}, +'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", +'dijit/_editor/selection':function(){ +define([ + "dojo/dom", // dom.byId + "dojo/_base/lang", + "dojo/_base/sniff", // has("ie") has("opera") + "dojo/_base/window", // win.body win.doc win.doc.createElement win.doc.selection win.doc.selection.createRange win.doc.selection.type.toLowerCase win.global win.global.getSelection + ".." // for exporting symbols to dijit._editor.selection (TODO: remove in 2.0) +], function(dom, lang, has, win, dijit){ + +// module: +// dijit/_editor/selection +// summary: +// Text selection API + + +lang.getObject("_editor.selection", true, dijit); + +// FIXME: +// all of these methods branch internally for IE. This is probably +// sub-optimal in terms of runtime performance. We should investigate the +// size difference for differentiating at definition time. + +lang.mixin(dijit._editor.selection, { + getType: function(){ + // summary: + // Get the selection type (like win.doc.select.type in IE). + if(has("ie") < 9){ + return win.doc.selection.type.toLowerCase(); + }else{ + var stype = "text"; + + // Check if the actual selection is a CONTROL (IMG, TABLE, HR, etc...). + var oSel; + try{ + oSel = win.global.getSelection(); + }catch(e){ /*squelch*/ } + + if(oSel && oSel.rangeCount == 1){ + var oRange = oSel.getRangeAt(0); + if( (oRange.startContainer == oRange.endContainer) && + ((oRange.endOffset - oRange.startOffset) == 1) && + (oRange.startContainer.nodeType != 3 /* text node*/) + ){ + stype = "control"; + } + } + return stype; //String + } + }, + + getSelectedText: function(){ + // summary: + // Return the text (no html tags) included in the current selection or null if no text is selected + if(has("ie") < 9){ + if(dijit._editor.selection.getType() == 'control'){ + return null; + } + return win.doc.selection.createRange().text; + }else{ + var selection = win.global.getSelection(); + if(selection){ + return selection.toString(); //String + } + } + return ''; + }, + + getSelectedHtml: function(){ + // summary: + // Return the html text of the current selection or null if unavailable + if(has("ie") < 9){ + if(dijit._editor.selection.getType() == 'control'){ + return null; + } + return win.doc.selection.createRange().htmlText; + }else{ + var selection = win.global.getSelection(); + if(selection && selection.rangeCount){ + var i; + var html = ""; + for(i = 0; i < selection.rangeCount; i++){ + //Handle selections spanning ranges, such as Opera + var frag = selection.getRangeAt(i).cloneContents(); + var div = win.doc.createElement("div"); + div.appendChild(frag); + html += div.innerHTML; + } + return html; //String + } + return null; + } + }, + + getSelectedElement: function(){ + // summary: + // Retrieves the selected element (if any), just in the case that + // a single element (object like and image or a table) is + // selected. + if(dijit._editor.selection.getType() == "control"){ + if(has("ie") < 9){ + var range = win.doc.selection.createRange(); + if(range && range.item){ + return win.doc.selection.createRange().item(0); + } + }else{ + var selection = win.global.getSelection(); + return selection.anchorNode.childNodes[ selection.anchorOffset ]; + } + } + return null; + }, + + getParentElement: function(){ + // summary: + // Get the parent element of the current selection + if(dijit._editor.selection.getType() == "control"){ + var p = this.getSelectedElement(); + if(p){ return p.parentNode; } + }else{ + if(has("ie") < 9){ + var r = win.doc.selection.createRange(); + r.collapse(true); + return r.parentElement(); + }else{ + var selection = win.global.getSelection(); + if(selection){ + var node = selection.anchorNode; + while(node && (node.nodeType != 1)){ // not an element + node = node.parentNode; + } + return node; + } + } + } + return null; + }, + + hasAncestorElement: function(/*String*/tagName /* ... */){ + // summary: + // Check whether current selection has a parent element which is + // of type tagName (or one of the other specified tagName) + // tagName: String + // The tag name to determine if it has an ancestor of. + return this.getAncestorElement.apply(this, arguments) != null; //Boolean + }, + + getAncestorElement: function(/*String*/tagName /* ... */){ + // summary: + // Return the parent element of the current selection which is of + // type tagName (or one of the other specified tagName) + // tagName: String + // The tag name to determine if it has an ancestor of. + var node = this.getSelectedElement() || this.getParentElement(); + return this.getParentOfType(node, arguments); //DOMNode + }, + + isTag: function(/*DomNode*/ node, /*String[]*/ tags){ + // summary: + // Function to determine if a node is one of an array of tags. + // node: + // The node to inspect. + // tags: + // An array of tag name strings to check to see if the node matches. + if(node && node.tagName){ + var _nlc = node.tagName.toLowerCase(); + for(var i=0; i<tags.length; i++){ + var _tlc = String(tags[i]).toLowerCase(); + if(_nlc == _tlc){ + return _tlc; // String + } + } + } + return ""; + }, + + getParentOfType: function(/*DomNode*/ node, /*String[]*/ tags){ + // summary: + // Function to locate a parent node that matches one of a set of tags + // node: + // The node to inspect. + // tags: + // An array of tag name strings to check to see if the node matches. + while(node){ + if(this.isTag(node, tags).length){ + return node; // DOMNode + } + node = node.parentNode; + } + return null; + }, + + collapse: function(/*Boolean*/beginning){ + // summary: + // Function to collapse (clear), the current selection + // beginning: Boolean + // Boolean to indicate whether to collapse the cursor to the beginning of the selection or end. + if(window.getSelection){ + var selection = win.global.getSelection(); + if(selection.removeAllRanges){ // Mozilla + if(beginning){ + selection.collapseToStart(); + }else{ + selection.collapseToEnd(); + } + }else{ // Safari + // pulled from WebCore/ecma/kjs_window.cpp, line 2536 + selection.collapse(beginning); + } + }else if(has("ie")){ // IE + var range = win.doc.selection.createRange(); + range.collapse(beginning); + range.select(); + } + }, + + remove: function(){ + // summary: + // Function to delete the currently selected content from the document. + var sel = win.doc.selection; + if(has("ie") < 9){ + if(sel.type.toLowerCase() != "none"){ + sel.clear(); + } + return sel; //Selection + }else{ + sel = win.global.getSelection(); + sel.deleteFromDocument(); + return sel; //Selection + } + }, + + selectElementChildren: function(/*DomNode*/element,/*Boolean?*/nochangefocus){ + // summary: + // clear previous selection and select the content of the node + // (excluding the node itself) + // element: DOMNode + // The element you wish to select the children content of. + // nochangefocus: Boolean + // Boolean to indicate if the foxus should change or not. + var global = win.global; + var doc = win.doc; + var range; + element = dom.byId(element); + if(doc.selection && has("ie") < 9 && win.body().createTextRange){ // IE + range = element.ownerDocument.body.createTextRange(); + range.moveToElementText(element); + if(!nochangefocus){ + try{ + range.select(); // IE throws an exception here if the widget is hidden. See #5439 + }catch(e){ /* squelch */} + } + }else if(global.getSelection){ + var selection = win.global.getSelection(); + if(has("opera")){ + //Opera's selectAllChildren doesn't seem to work right + //against <body> nodes and possibly others ... so + //we use the W3C range API + if(selection.rangeCount){ + range = selection.getRangeAt(0); + }else{ + range = doc.createRange(); + } + range.setStart(element, 0); + range.setEnd(element,(element.nodeType == 3)?element.length:element.childNodes.length); + selection.addRange(range); + }else{ + selection.selectAllChildren(element); + } + } + }, + + selectElement: function(/*DomNode*/element,/*Boolean?*/nochangefocus){ + // summary: + // clear previous selection and select element (including all its children) + // element: DOMNode + // The element to select. + // nochangefocus: Boolean + // Boolean indicating if the focus should be changed. IE only. + var range; + var doc = win.doc; + var global = win.global; + element = dom.byId(element); + if(has("ie") < 9 && win.body().createTextRange){ + try{ + var tg = element.tagName ? element.tagName.toLowerCase() : ""; + if(tg === "img" || tg === "table"){ + range = win.body().createControlRange(); + }else{ + range = win.body().createRange(); + } + range.addElement(element); + if(!nochangefocus){ + range.select(); + } + }catch(e){ + this.selectElementChildren(element,nochangefocus); + } + }else if(global.getSelection){ + var selection = global.getSelection(); + range = doc.createRange(); + if(selection.removeAllRanges){ // Mozilla + // FIXME: does this work on Safari? + if(has("opera")){ + //Opera works if you use the current range on + //the selection if present. + if(selection.getRangeAt(0)){ + range = selection.getRangeAt(0); + } + } + range.selectNode(element); + selection.removeAllRanges(); + selection.addRange(range); + } + } + }, + + inSelection: function(node){ + // summary: + // This function determines if 'node' is + // in the current selection. + // tags: + // public + if(node){ + var newRange; + var doc = win.doc; + var range; + + if(win.global.getSelection){ + //WC3 + var sel = win.global.getSelection(); + if(sel && sel.rangeCount > 0){ + range = sel.getRangeAt(0); + } + if(range && range.compareBoundaryPoints && doc.createRange){ + try{ + newRange = doc.createRange(); + newRange.setStart(node, 0); + if(range.compareBoundaryPoints(range.START_TO_END, newRange) === 1){ + return true; + } + }catch(e){ /* squelch */} + } + }else if(doc.selection){ + // Probably IE, so we can't use the range object as the pseudo + // range doesn't implement the boundry checking, we have to + // use IE specific crud. + range = doc.selection.createRange(); + try{ + newRange = node.ownerDocument.body.createControlRange(); + if(newRange){ + newRange.addElement(node); + } + }catch(e1){ + try{ + newRange = node.ownerDocument.body.createTextRange(); + newRange.moveToElementText(node); + }catch(e2){/* squelch */} + } + if(range && newRange){ + // We can finally compare similar to W3C + if(range.compareEndPoints("EndToStart", newRange) === 1){ + return true; + } + } + } + } + return false; // boolean + } + +}); + +return dijit._editor.selection; +}); + +}, +'dijit/form/nls/ComboBox':function(){ +define({ root: +//begin v1.x content +({ + previousMessage: "Previous choices", + nextMessage: "More choices" +}) +//end v1.x content +, +"zh": true, +"zh-tw": true, +"tr": true, +"th": true, +"sv": true, +"sl": true, +"sk": true, +"ru": true, +"ro": true, +"pt": true, +"pt-pt": true, +"pl": true, +"nl": true, +"nb": true, +"ko": true, +"kk": true, +"ja": true, +"it": true, +"hu": true, +"hr": true, +"he": true, +"fr": true, +"fi": true, +"es": true, +"el": true, +"de": true, +"da": true, +"cs": true, +"ca": true, +"az": true, +"ar": true +}); + +}, +'dojo/fx':function(){ +define([ + "./_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: + // TODOC + + + /*===== + dojo.fx = { + // summary: Effects library on top of Base animations + }; + var coreFx = dojo.fx; + =====*/ + +// 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 + }); +} + + var coreFx = dojo.fx = {}; + + var _baseObj = { + _fire: function(evt, args){ + if(this[evt]){ + this[evt].apply(this, args||[]); + } + return this; + } + }; + + 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); + }; + _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); + } + }, + 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); + } + this._onAnimateCtx = connect.connect(this._current, "onAnimate", this, "_onAnimate"); + if(this._onEndCtx){ + connect.disconnect(this._onEndCtx); + } + 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(); + } + 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); + } + 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); } + } + }); + lang.extend(_chain, _baseObj); + + 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 + }; + + var _combine = function(animations){ + this._animations = animations||[]; + this._connects = []; + this._finished = 0; + + 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); + + 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); } + )); + } + ); + }; + lang.extend(_combine, { + _doAction: function(action, args){ + arrayUtil.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; + arrayUtil.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(){ + 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 + }; + + 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 + }; + + coreFx.wipeOut = /*===== 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 = dom.byId(args.node), s = node.style, o; + + var anim = baseFx.animateProperty(lang.mixin({ + properties: { + height: { + end: 1 // 0 causes IE to display the whole panel + } + } + }, args)); + + 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); + + return anim; // dojo.Animation + }; + + 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() + + var node = args.node = dom.byId(args.node), + top = null, left = null; + + 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 + } + }, args)); + connect.connect(anim, "beforeBegin", anim, init); + + return anim; // dojo.Animation + }; + + return coreFx; +}); + +}, +'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 + + return declare("dijit._DialogMixin", null, { + // summary: + // This provides functions useful to Dialog and TooltipDialog + + 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 + }, + + 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 + }, + + 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')); + }, + + _getFocusItems: function(){ + // summary: + // 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; + } + }); +}); + +}, +'dijit/nls/common':function(){ +define({ root: +//begin v1.x content +({ + buttonOk: "OK", + buttonCancel: "Cancel", + buttonSave: "Save", + itemClose: "Close" +}) +//end v1.x content +, +"zh": true, +"zh-tw": true, +"tr": true, +"th": true, +"sv": true, +"sl": true, +"sk": true, +"ru": true, +"ro": true, +"pt": true, +"pt-pt": true, +"pl": true, +"nl": true, +"nb": true, +"ko": true, +"kk": true, +"ja": true, +"it": true, +"hu": true, +"hr": true, +"he": true, +"fr": true, +"fi": true, +"es": true, +"el": true, +"de": true, +"da": true, +"cs": true, +"ca": true, +"az": true, +"ar": true +}); + +}, +'dijit/Tree':function(){ +define([ + "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 + + +var TreeNode = declare( + "dijit._TreeNode", + [_Widget, _TemplatedMixin, _Container, _Contained, _CssStateMixin], +{ + // summary: + // Single node within a tree. This class is used internally + // by Tree and should not be accessed directly. + // tags: + // private + + // item: [const] Item + // the dojo.data entry this tree represents + item: null, + + // isTreeNode: [protected] Boolean + // Indicates that this is a TreeNode. Used by `dijit.Tree` only, + // should not be accessed directly. + isTreeNode: true, + + // 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) + isExpandable: null, + + // isExpanded: [readonly] Boolean + // This node is currently expanded (ie, opened) + isExpanded: false, + + // state: [private] String + // Dynamic loading-related stuff. + // When an empty folder node appears, it is "UNCHECKED" first, + // then after dojo.data query it becomes "LOADING" and, finally "LOADED" + state: "UNCHECKED", + + templateString: treeNodeTemplate, + + baseClass: "dijitTreeNode", + + // For hover effect for tree node, and focus effect for label + cssStateNodes: { + rowNode: "dijitTreeRow", + labelNode: "dijitTreeLabel" + }, + + // 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); + + // set expand icon for leaf + this._setExpando(); + + // set icon and label class based on item + this._updateItemClasses(this.item); + + if(this.isExpandable){ + this.labelNode.setAttribute("aria-expanded", this.isExpanded); + } + + //aria-selected should be false on all selectable elements. + this.setSelected(false); + }, + + _setIndentAttr: function(indent){ + // summary: + // Tell this node how many levels it should be indented + // description: + // 0 for top level nodes, 1 for their children, 2 for their + // grandchildren, etc. + + // Math.max() is to prevent negative padding on hidden root node (when indent == -1) + var pixels = (Math.max(indent, 0) * this.tree._nodePixelIndent) + "px"; + + domStyle.set(this.domNode, "backgroundPosition", pixels + " 0px"); + domStyle.set(this.rowNode, this.isLeftToRight() ? "paddingLeft" : "paddingRight", pixels); + + array.forEach(this.getChildren(), function(child){ + child.set("indent", indent+1); + }); + + this._set("indent", indent); + }, + + markProcessing: function(){ + // summary: + // Visually denote that tree is loading data, etc. + // tags: + // private + this.state = "LOADING"; + this._setExpando(true); + }, + + unmarkProcessing: function(){ + // summary: + // Clear markup from markProcessing() call + // tags: + // private + this._setExpando(false); + }, + + _updateItemClasses: function(item){ + // summary: + // Set appropriate CSS classes for icon and label dom node + // (used to allow for item updates to change respective CSS) + // tags: + // private + var tree = this.tree, model = tree.model; + if(tree._v10Compat && item === model.root){ + // For back-compat with 1.0, need to use null to specify root item (TODO: remove in 2.0) + item = null; + } + this._applyClassAndStyle(item, "icon", "Icon"); + this._applyClassAndStyle(item, "label", "Label"); + this._applyClassAndStyle(item, "row", "Row"); + }, + + _applyClassAndStyle: function(item, lower, upper){ + // summary: + // Set the appropriate CSS classes and styles for labels, icons and rows. + // + // item: + // The data item. + // + // lower: + // The lower case attribute to use, e.g. 'icon', 'label' or 'row'. + // + // upper: + // The upper case attribute to use, e.g. 'Icon', 'Label' or 'Row'. + // + // tags: + // private + + var clsName = "_" + lower + "Class"; + var nodeName = lower + "Node"; + var oldCls = this[clsName]; + + this[clsName] = this.tree["get" + upper + "Class"](item, this.isExpanded); + domClass.replace(this[nodeName], this[clsName] || "", oldCls || ""); + + domStyle.set(this[nodeName], this.tree["get" + upper + "Style"](item, this.isExpanded) || {}); + }, + + _updateLayout: function(){ + // summary: + // Set appropriate CSS classes for this.domNode + // tags: + // private + var parent = this.getParent(); + 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 */ + domClass.add(this.domNode, "dijitTreeIsRoot"); + }else{ + domClass.toggle(this.domNode, "dijitTreeIsLast", !this.getNextSibling()); + } + }, + + _setExpando: function(/*Boolean*/ processing){ + // summary: + // Set the right image for the expando node + // tags: + // private + + var styles = ["dijitTreeExpandoLoading", "dijitTreeExpandoOpened", + "dijitTreeExpandoClosed", "dijitTreeExpandoLeaf"], + _a11yStates = ["*","-","+","*"], + idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3); + + // apply the appropriate class to the expando node + domClass.replace(this.expandoNode, styles[idx], styles); + + // provide a non-image based indicator for images-off mode + this.expandoNodeText.innerHTML = _a11yStates[idx]; + + }, + + expand: function(){ + // summary: + // Show my children + // returns: + // Deferred that fires when expansion is complete + + // If there's already an expand in progress or we are already expanded, just return + if(this._expandDeferred){ + return this._expandDeferred; // dojo.Deferred + } + + // cancel in progress collapse operation + this._wipeOut && this._wipeOut.stop(); + + // All the state information for when a node is expanded, maybe this should be + // set when the animation completes instead + this.isExpanded = true; + this.labelNode.setAttribute("aria-expanded", "true"); + if(this.tree.showRoot || this !== this.tree.rootNode){ + this.containerNode.setAttribute("role", "group"); + } + domClass.add(this.contentNode,'dijitTreeContentExpanded'); + this._setExpando(); + this._updateItemClasses(this.item); + if(this == this.tree.rootNode){ + this.tree.domNode.setAttribute("aria-expanded", "true"); + } + + var def, + 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 Deferred(function(){ + // Canceller + wipeIn.stop(); + })); + + wipeIn.play(); + + return def; // dojo.Deferred + }, + + collapse: function(){ + // summary: + // Collapse this node (if it's expanded) + + if(!this.isExpanded){ return; } + + // cancel in progress expand operation + if(this._expandDeferred){ + this._expandDeferred.cancel(); + delete this._expandDeferred; + } + + this.isExpanded = false; + this.labelNode.setAttribute("aria-expanded", "false"); + if(this == this.tree.rootNode){ + this.tree.domNode.setAttribute("aria-expanded", "false"); + } + domClass.remove(this.contentNode,'dijitTreeContentExpanded'); + this._setExpando(); + this._updateItemClasses(this.item); + + if(!this._wipeOut){ + this._wipeOut = fxUtils.wipeOut({ + node: this.containerNode, duration: manager.defaultDuration + }); + } + this._wipeOut.play(); + }, + + // indent: Integer + // Levels from this node to the root node + indent: 0, + + setChildItems: function(/* Object[] */ items){ + // summary: + // Sets the child items of this node, removing/adding nodes + // from current children to match specified items[] array. + // Also, if this.persist == true, expands any children that were previously + // opened. + // returns: + // Deferred object that fires after all previously opened children + // have been expanded again (or fires instantly if there are no such children). + + var tree = this.tree, + model = tree.model, + defs = []; // list of deferreds that need to fire before I am complete + + + // 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. + array.forEach(this.getChildren(), function(child){ + _Container.prototype.removeChild.call(this, child); + }, this); + + this.state = "LOADED"; + + if(items && items.length > 0){ + this.isExpandable = true; + + // 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 + array.forEach(items, function(item){ + var id = model.getIdentity(item), + existingNodes = tree._itemNodesMap[id], + node; + if(existingNodes){ + for(var i=0;i<existingNodes.length;i++){ + if(existingNodes[i] && !existingNodes[i].getParent()){ + node = existingNodes[i]; + node.set('indent', this.indent+1); + break; + } + } + } + if(!node){ + node = this.tree._createTreeNode({ + item: item, + tree: tree, + isExpandable: model.mayHaveChildren(item), + label: tree.getLabel(item), + tooltip: tree.getTooltip(item), + dir: tree.dir, + lang: tree.lang, + textDir: tree.textDir, + indent: this.indent + 1 + }); + if(existingNodes){ + existingNodes.push(node); + }else{ + tree._itemNodesMap[id] = [node]; + } + } + this.addChild(node); + + // 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(node)){ + defs.push(tree._expandNode(node)); + } + }, this); + + // note that updateLayout() needs to be called on each child after + // _all_ the children exist + array.forEach(this.getChildren(), function(child){ + child._updateLayout(); + }); + }else{ + this.isExpandable=false; + } + + if(this._setExpando){ + // change expando to/from dot or + icon, as appropriate + this._setExpando(false); + } + + // Set leaf icon or folder icon, as appropriate + this._updateItemClasses(this.item); + + // On initial tree show, make the selected TreeNode as either the root node of the tree, + // or the first child, if the root node is hidden + if(this == tree.rootNode){ + var fc = this.tree.showRoot ? this : this.getChildren()[0]; + if(fc){ + fc.setFocusable(true); + tree.lastFocused = fc; + }else{ + // fallback: no nodes in tree so focus on Tree <div> itself + tree.domNode.setAttribute("tabIndex", "0"); + } + } + + return new DeferredList(defs); // dojo.Deferred + }, + + getTreePath: function(){ + var node = this; + var path = []; + while(node && node !== this.tree.rootNode){ + path.unshift(node.item); + node = node.getParent(); + } + path.unshift(this.tree.rootNode.item); + + return path; + }, + + getIdentity: function(){ + return this.tree.model.getIdentity(this.item); + }, + + removeChild: function(/* treeNode */ node){ + this.inherited(arguments); + + var children = this.getChildren(); + if(children.length == 0){ + this.isExpandable = false; + this.collapse(); + } + + array.forEach(children, function(child){ + child._updateLayout(); + }); + }, + + makeExpandable: function(){ + // summary: + // if this node wasn't already showing the expando node, + // turn it into one and call _setExpando() + + // TODO: hmm this isn't called from anywhere, maybe should remove it for 2.0 + + this.isExpandable = true; + this._setExpando(false); + }, + + _onLabelFocus: function(){ + // summary: + // Called when this row is focused (possibly programatically) + // Note that we aren't using _onFocus() builtin to dijit + // because it's called when focus is moved to a descendant TreeNode. + // tags: + // private + this.tree._onNodeFocus(this); + }, + + setSelected: function(/*Boolean*/ selected){ + // summary: + // A Tree has a (single) currently selected node. + // Mark that this node is/isn't that currently selected node. + // 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). + this.labelNode.setAttribute("aria-selected", selected); + domClass.toggle(this.rowNode, "dijitTreeRowSelected", selected); + }, + + setFocusable: function(/*Boolean*/ selected){ + // summary: + // A Tree has a (single) node that's focusable. + // Mark that this node is/isn't that currently focsuable node. + // 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). + + this.labelNode.setAttribute("tabIndex", selected ? "0" : "-1"); + }, + + _onClick: function(evt){ + // summary: + // Handler for onclick event on a node + // tags: + // private + this.tree._onClick(this, evt); + }, + _onDblClick: function(evt){ + // summary: + // Handler for ondblclick event on a node + // tags: + // private + this.tree._onDblClick(this, evt); + }, + + _onMouseEnter: function(evt){ + // summary: + // Handler for onmouseenter event on a node + // tags: + // private + this.tree._onNodeMouseEnter(this, evt); + }, + + _onMouseLeave: function(evt){ + // summary: + // Handler for onmouseenter event on a node + // 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); + } + } +}); + +var Tree = declare("dijit.Tree", [_Widget, _TemplatedMixin], { + // summary: + // This widget displays hierarchical data from a store. + + // store: [deprecated] String||dojo.data.Store + // Deprecated. Use "model" parameter instead. + // The store to get data to display in the tree. + store: null, + + // model: dijit.Tree.model + // Interface to read tree data, get notifications of changes to tree data, + // and for handling drop operations (i.e drag and drop onto the tree) + model: null, + + // query: [deprecated] anything + // Deprecated. User should specify query to the model directly instead. + // Specifies datastore query to return the root item or top items for the tree. + query: null, + + // label: [deprecated] String + // Deprecated. Use dijit.tree.ForestStoreModel directly instead. + // Used in conjunction with query parameter. + // If a query is specified (rather than a root node id), and a label is also specified, + // then a fake root node is created and displayed, with this label. + label: "", + + // showRoot: [const] Boolean + // Should the root node be displayed, or hidden? + showRoot: true, + + // childrenAttr: [deprecated] String[] + // Deprecated. This information should be specified in the model. + // One ore more attributes that holds children of a tree node + childrenAttr: ["children"], + + // paths: String[][] or Item[][] + // Full paths from rootNode to selected nodes expressed as array of items or array of ids. + // 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: [], + + // selectedItems: [readonly] Item[] + // The currently selected items in this tree. + // This property can only be set (via set('selectedItems', ...)) when that item is already + // visible in the tree. (I.e. the tree has already been expanded to show that node.) + // Should generally use `paths` attribute to set the selected items instead. + selectedItems: null, + + // selectedItem: [readonly] Item + // Backward compatible singular variant of selectedItems. + selectedItem: null, + + // openOnClick: Boolean + // If true, clicking a folder node's label will open it, rather than calling onClick() + openOnClick: false, + + // openOnDblClick: Boolean + // If true, double-clicking a folder node's label will open it, rather than calling onDblClick() + openOnDblClick: false, + + templateString: treeTemplate, + + // persist: Boolean + // Enables/disables use of cookies for state saving. + persist: true, + + // autoExpand: Boolean + // Fully expand the tree on load. Overrides `persist`. + autoExpand: false, + + // 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"], + + //declare the above items so they can be pulled from the tree's markup + + // onDndDrop: [protected] Function + // Parameter to dndController, see `dijit.tree.dndSource.onDndDrop`. + // Generally this doesn't need to be set. + onDndDrop: null, + + /*===== + 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[] + // The DOMNodes dragged from the source container + // target: DomNode + // The target TreeNode.rowNode + // source: dojo.dnd.Source + // The source container the nodes were dragged from, perhaps another Tree or a plain 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 + return [{}]; + }, + =====*/ + itemCreator: null, + + // onDndCancel: [protected] Function + // Parameter to dndController, see `dijit.tree.dndSource.onDndCancel`. + // Generally this doesn't need to be set. + onDndCancel: null, + +/*===== + checkAcceptance: function(source, nodes){ + // summary: + // Checks if the Tree itself 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 + }, +=====*/ + checkAcceptance: null, + +/*===== + 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; // Boolean + }, +=====*/ + checkItemAcceptance: null, + + // dragThreshold: Integer + // Number of pixels mouse moves before it's considered the start of a drag operation + dragThreshold: 5, + + // betweenThreshold: Integer + // Set to a positive value to allow drag and drop "between" nodes. + // + // If during DnD mouse is over a (target) node but less than betweenThreshold + // pixels from the bottom edge, dropping the the dragged node will make it + // the next sibling of the target node, rather than the child. + // + // Similarly, if mouse is over a target node but less that betweenThreshold + // pixels from the top edge, dropping the dragged node will make it + // the target node's previous sibling rather than the target node's child. + betweenThreshold: 0, + + // _nodePixelIndent: Integer + // Number of pixels to indent tree nodes (relative to parent node). + // Default is 19 but can be overridden by setting CSS class dijitTreeIndent + // and calling resize() or startup() on tree after it's in the DOM. + _nodePixelIndent: 19, + + _publish: function(/*String*/ topicName, /*Object*/ message){ + // summary: + // Publish a message for this widget/topic + topic.publish(this.id, lang.mixin({tree: this, event: topicName}, message || {})); // publish + }, + + postMixInProperties: function(){ + this.tree = this; + + if(this.autoExpand){ + // There's little point in saving opened/closed state of nodes for a Tree + // that initially opens all it's nodes. + this.persist = false; + } + + this._itemNodesMap={}; + + if(!this.cookieName && this.id){ + this.cookieName = this.id + "SaveStateCookie"; + } + + this._loadDeferred = new Deferred(); + + this.inherited(arguments); + }, + + postCreate: function(){ + this._initState(); + + // Create glue between store and Tree, if not specified directly by user + if(!this.model){ + this._store2model(); + } + + // monitor changes to items + this.connect(this.model, "onChange", "_onItemChange"); + this.connect(this.model, "onChildrenChange", "_onItemChildrenChange"); + this.connect(this.model, "onDelete", "_onItemDelete"); + + this._load(); + + this.inherited(arguments); + + if(this.dndController){ + if(lang.isString(this.dndController)){ + this.dndController = lang.getObject(this.dndController); + } + var params={}; + for(var i=0; i<this.dndParams.length;i++){ + if(this[this.dndParams[i]]){ + params[this.dndParams[i]] = this[this.dndParams[i]]; + } + } + this.dndController = new this.dndController(this, params); + } + }, + + _store2model: function(){ + // summary: + // User specified a store&query rather than model, so create model from store/query + this._v10Compat = true; + kernel.deprecated("Tree: from version 2.0, should specify a model object rather than a store/query"); + + var modelParams = { + id: this.id + "_ForestStoreModel", + store: this.store, + query: this.query, + childrenAttrs: this.childrenAttr + }; + + // Only override the model's mayHaveChildren() method if the user has specified an override + if(this.params.mayHaveChildren){ + modelParams.mayHaveChildren = lang.hitch(this, "mayHaveChildren"); + } + + if(this.params.getItemChildren){ + modelParams.getChildren = lang.hitch(this, function(item, onComplete, onError){ + this.getItemChildren((this._v10Compat && item === this.model.root) ? null : item, onComplete, onError); + }); + } + 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 + this.showRoot = Boolean(this.label); + }, + + onLoad: function(){ + // summary: + // Called when tree finishes loading and expanding. + // description: + // If persist == true the loading may encompass many levels of fetches + // from the data store, each asynchronous. Waits for all to finish. + // tags: + // callback + }, + + _load: function(){ + // summary: + // Initial load of the tree. + // Load root node (possibly hidden) and it's children. + this.model.getRoot( + 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 + 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); + if(this._itemNodesMap[identity]){ + this._itemNodesMap[identity].push(rn); + }else{ + this._itemNodesMap[identity] = [rn]; + } + + rn._updateLayout(); // sets "dijitTreeIsRoot" CSS classname + + // load top level children and then fire onLoad() event + this._expandNode(rn).addCallback(lang.hitch(this, function(){ + this._loadDeferred.callback(true); + this.onLoad(); + })); + }), + function(err){ + console.error(this, ": error loading root: ", err); + } + ); + }, + + 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 = 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(/*Item or id*/ item){ + this.set('selectedItems', [item]); + }, + + _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( lang.hitch(this, function(){ + var identities = array.map(items, function(item){ + return (!item || lang.isString(item)) ? item : tree.model.getIdentity(item); + }); + var nodes = []; + array.forEach(identities, function(id){ + nodes = nodes.concat(tree._itemNodesMap[id] || []); + }); + this.set('selectedNodes', nodes); + })); + }, + + _setPathAttr: function(/*Item[] || String[]*/ path){ + // summary: + // Singular variant of _setPathsAttr + if(path.length){ + return this.set("paths", [path]); + }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. + // paths: + // Array of arrays of items or item id's + // returns: + // Deferred to indicate when the set is complete + var tree = this; + + // 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 DeferredList(array.map(paths, function(path){ + var d = new Deferred(); + + // normalize path to use identity + path = array.map(path, function(item){ + return lang.isString(item) ? item : tree.model.getIdentity(item); + }); + + if(path.length){ + // Wait for the tree to load, if it hasn't already. + tree._loadDeferred.addCallback(function(){ selectPath(path, [tree.rootNode], d); }); + }else{ + d.errback("Empty path"); + } + return d; + })).addCallback(setNodes); + + function selectPath(path, nodes, def){ + // Traverse path; the next path component should be among "nodes". + var nextPath = path.shift(); + var nextNode = array.filter(nodes, function(node){ + return node.getIdentity() == nextPath; + })[0]; + if(!!nextNode){ + if(path.length){ + tree._expandNode(nextNode).addCallback(function(){ selectPath(path, nextNode.getChildren(), def); }); + }else{ + //Successfully reached the end of this path + def.callback(nextNode); + } + }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", array.map( + array.filter(newNodes,function(x){return x[0];}), + function(x){return x[1];})); + } + }, + + _setSelectedNodeAttr: function(node){ + this.set('selectedNodes', [node]); + }, + _setSelectedNodesAttr: function(nodes){ + this._loadDeferred.addCallback( lang.hitch(this, function(){ + this.dndController.setSelection(nodes); + })); + }, + + + ////////////// Data store related functions ////////////////////// + // These just get passed to the model; they are here for back-compat + + mayHaveChildren: function(/*dojo.data.Item*/ /*===== item =====*/){ + // summary: + // Deprecated. This should be specified on the model itself. + // + // Overridable function to tell if an item has or may have children. + // Controls whether or not +/- expando icon is shown. + // (For efficiency reasons we may not want to check if an element actually + // has children until user clicks the expando node) + // tags: + // deprecated + }, + + getItemChildren: function(/*===== parentItem, onComplete =====*/){ + // summary: + // Deprecated. This should be specified on the model itself. + // + // Overridable function that return array of child items of given parent item, + // or if parentItem==null then return top items in tree + // tags: + // deprecated + }, + + /////////////////////////////////////////////////////// + // Functions for converting an item to a TreeNode + getLabel: function(/*dojo.data.Item*/ item){ + // summary: + // Overridable function to get the label for a tree node (given the item) + // tags: + // extension + return this.model.getLabel(item); // String + }, + + getIconClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ + // summary: + // Overridable function to return CSS class name to display icon + // tags: + // extension + return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf" + }, + + 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(/*===== 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(/*===== item, opened =====*/){ + // summary: + // Overridable function to return CSS styles to display icon + // item: dojo.data.Item + // opened: Boolean + // returns: Object + // Object suitable for input to dojo.style() like {backgroundImage: "url(...)"} + // tags: + // extension + }, + + 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(/*===== 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 =====*/){ + // summary: + // Overridable function to get the tooltip for a tree node (given the item) + // tags: + // extension + return ""; // String + }, + + /////////// Keyboard and Mouse handlers //////////////////// + + _onKeyPress: function(/*Event*/ e){ + // summary: + // Translates keypress events into commands for the controller + if(e.altKey){ return; } + var treeNode = registry.getEnclosingWidget(e.target); + if(!treeNode){ return; } + + var key = e.charOrCode; + if(typeof key == "string" && key != " "){ // handle printables (letter navigation) + // Check for key navigation. + if(!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey){ + this._onLetterKeyNav( { node: treeNode, key: key.toLowerCase() } ); + event.stop(e); + } + }else{ // handle non-printables (arrow keys) + // clear record of recent printables (being saved for multi-char letter navigation), + // because "a", down-arrow, "b" shouldn't search for "ab" + if(this._curSearch){ + clearTimeout(this._curSearch.timer); + delete this._curSearch; + } + + var map = this._keyHandlerMap; + if(!map){ + // setup table mapping keys to events + map = {}; + 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[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 } ); + event.stop(e); + } + } + }, + + _onEnterKey: function(/*Object*/ message){ + this._publish("execute", { item: message.item, node: message.node } ); + this.dndController.userSelect(message.node, connect.isCopyKey( message.evt ), message.evt.shiftKey); + this.onClick(message.item, message.node, message.evt); + }, + + _onDownArrow: function(/*Object*/ message){ + // summary: + // down arrow pressed; get next visible node, set focus there + var node = this._getNextNode(message.node); + if(node && node.isTreeNode){ + this.focusNode(node); + } + }, + + _onUpArrow: function(/*Object*/ message){ + // summary: + // Up arrow pressed; move to previous visible node + + var node = message.node; + + // if younger siblings + var previousSibling = node.getPreviousSibling(); + if(previousSibling){ + node = previousSibling; + // if the previous node is expanded, dive in deep + while(node.isExpandable && node.isExpanded && node.hasChildren()){ + // move to the last child + var children = node.getChildren(); + node = children[children.length-1]; + } + }else{ + // if this is the first child, return the parent + // unless the parent is the root of a tree with a hidden root + var parent = node.getParent(); + if(!(!this.showRoot && parent === this.rootNode)){ + node = parent; + } + } + + if(node && node.isTreeNode){ + this.focusNode(node); + } + }, + + _onRightArrow: function(/*Object*/ message){ + // summary: + // Right arrow pressed; go to child node + var node = message.node; + + // if not expanded, expand, else move to 1st child + if(node.isExpandable && !node.isExpanded){ + this._expandNode(node); + }else if(node.hasChildren()){ + node = node.getChildren()[0]; + if(node && node.isTreeNode){ + this.focusNode(node); + } + } + }, + + _onLeftArrow: function(/*Object*/ message){ + // summary: + // Left arrow pressed. + // If not collapsed, collapse, else move to parent. + + var node = message.node; + + if(node.isExpandable && node.isExpanded){ + this._collapseNode(node); + }else{ + var parent = node.getParent(); + if(parent && parent.isTreeNode && !(!this.showRoot && parent === this.rootNode)){ + this.focusNode(parent); + } + } + }, + + _onHomeKey: function(){ + // summary: + // Home key pressed; get first visible node, and set focus there + var node = this._getRootOrFirstNode(); + if(node){ + this.focusNode(node); + } + }, + + _onEndKey: function(){ + // summary: + // End key pressed; go to last visible node. + + var node = this.rootNode; + while(node.isExpanded){ + var c = node.getChildren(); + node = c[c.length - 1]; + } + + if(node && node.isTreeNode){ + this.focusNode(node); + } + }, + + // multiCharSearchDuration: Number + // If multiple characters are typed where each keystroke happens within + // multiCharSearchDuration of the previous keystroke, + // search for nodes matching all the keystrokes. + // + // For example, typing "ab" will search for entries starting with + // "ab" unless the delay between "a" and "b" is greater than multiCharSearchDuration. + multiCharSearchDuration: 250, + + _onLetterKeyNav: function(message){ + // summary: + // Called when user presses a prinatable key; search for node starting with recently typed letters. + // message: Object + // Like { node: TreeNode, key: 'a' } where key is the key the user pressed. + + // Branch depending on whether this key starts a new search, or modifies an existing search + var cs = this._curSearch; + if(cs){ + // We are continuing a search. Ex: user has pressed 'a', and now has pressed + // 'b', so we want to search for nodes starting w/"ab". + cs.pattern = cs.pattern + message.key; + clearTimeout(cs.timer); + }else{ + // We are starting a new search + cs = this._curSearch = { + pattern: message.key, + startNode: message.node + }; + } + + // set/reset timer to forget recent keystrokes + var self = this; + cs.timer = setTimeout(function(){ + delete self._curSearch; + }, this.multiCharSearchDuration); + + // Navigate to TreeNode matching keystrokes [entered so far]. + var node = cs.startNode; + do{ + node = this._getNextNode(node); + //check for last node, jump to first node if necessary + if(!node){ + node = this._getRootOrFirstNode(); + } + }while(node !== cs.startNode && (node.label.toLowerCase().substr(0, cs.pattern.length) != cs.pattern)); + if(node && node.isTreeNode){ + // no need to set focus if back where we started + if(node !== cs.startNode){ + this.focusNode(node); + } + } + }, + + isExpandoNode: function(node, widget){ + // summary: + // check whether a dom node is the expandoNode for a particular TreeNode widget + return dom.isDescendant(node, widget.expandoNode); + }, + _onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){ + // summary: + // Translates click events into commands for the controller to process + + var domElement = e.target, + isExpandoClick = this.isExpandoNode(domElement, nodeWidget); + + if( (this.openOnClick && nodeWidget.isExpandable) || isExpandoClick ){ + // expando node was clicked, or label of a folder node was clicked; open it + if(nodeWidget.isExpandable){ + this._onExpandoClick({node:nodeWidget}); + } + }else{ + this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } ); + this.onClick(nodeWidget.item, nodeWidget, e); + this.focusNode(nodeWidget); + } + event.stop(e); + }, + _onDblClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){ + // summary: + // Translates double-click events into commands for the controller to process + + var domElement = e.target, + isExpandoClick = (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText); + + if( (this.openOnDblClick && nodeWidget.isExpandable) ||isExpandoClick ){ + // expando node was clicked, or label of a folder node was clicked; open it + if(nodeWidget.isExpandable){ + this._onExpandoClick({node:nodeWidget}); + } + }else{ + this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } ); + this.onDblClick(nodeWidget.item, nodeWidget, e); + this.focusNode(nodeWidget); + } + event.stop(e); + }, + + _onExpandoClick: function(/*Object*/ message){ + // summary: + // User clicked the +/- icon; expand or collapse my children. + var node = message.node; + + // If we are collapsing, we might be hiding the currently focused node. + // Also, clicking the expando node might have erased focus from the current node. + // For simplicity's sake just focus on the node with the expando. + this.focusNode(node); + + if(node.isExpanded){ + this._collapseNode(node); + }else{ + this._expandNode(node); + } + }, + + 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(/*===== item, node, evt =====*/){ + // summary: + // Callback when a tree node is double-clicked + // item: dojo.data.Item + // node: TreeNode + // evt: Event + // tags: + // callback + }, + onOpen: function(/*===== item, node =====*/){ + // summary: + // Callback when a node is opened + // item: dojo.data.Item + // node: TreeNode + // tags: + // callback + }, + onClose: function(/*===== item, node =====*/){ + // summary: + // Callback when a node is closed + // item: dojo.data.Item + // node: TreeNode + // tags: + // callback + }, + + _getNextNode: function(node){ + // summary: + // Get next visible node + + if(node.isExpandable && node.isExpanded && node.hasChildren()){ + // if this is an expanded node, get the first child + return node.getChildren()[0]; // _TreeNode + }else{ + // find a parent node with a sibling + while(node && node.isTreeNode){ + var returnNode = node.getNextSibling(); + if(returnNode){ + return returnNode; // _TreeNode + } + node = node.getParent(); + } + return null; + } + }, + + _getRootOrFirstNode: function(){ + // summary: + // Get first visible node + return this.showRoot ? this.rootNode : this.rootNode.getChildren()[0]; + }, + + _collapseNode: function(/*_TreeNode*/ node){ + // summary: + // Called when the user has requested to collapse the node + + if(node._expandNodeDeferred){ + delete node._expandNodeDeferred; + } + + if(node.isExpandable){ + if(node.state == "LOADING"){ + // ignore clicks while we are in the process of loading data + return; + } + + node.collapse(); + this.onClose(node.item, node); + + this._state(node, false); + } + }, + + _expandNode: function(/*_TreeNode*/ node, /*Boolean?*/ recursive){ + // summary: + // Called when the user has requested to expand the node + // recursive: + // Internal flag used when _expandNode() calls itself, don't set. + // returns: + // Deferred that fires when the node is loaded and opened and (if persist=true) all it's descendants + // that were previously opened too + + if(node._expandNodeDeferred && !recursive){ + // there's already an expand in progress (or completed), so just return + return node._expandNodeDeferred; // dojo.Deferred + } + + var model = this.model, + item = node.item, + _this = this; + + switch(node.state){ + case "UNCHECKED": + // need to load all the children, and then expand + node.markProcessing(); + + // 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 Deferred()); + + // Get the children + model.getChildren( + item, + function(items){ + node.unmarkProcessing(); + + // Display the children and also start expanding any children that were previously expanded + // (if this.persist == true). The returned Deferred will fire when those expansions finish. + var scid = node.setChildItems(items); + + // Call _expandNode() again but this time it will just to do the animation (default branch). + // The returned Deferred will fire when the animation completes. + // TODO: seems like I can avoid recursion and just use a deferred to sequence the events? + var ed = _this._expandNode(node, true); + + // After the above two tasks (setChildItems() and recursive _expandNode()) finish, + // signal that I am done. + scid.addCallback(function(){ + ed.addCallback(function(){ + def.callback(); + }) + }); + }, + function(err){ + console.error(_this, ": error loading root children: ", err); + } + ); + break; + + default: // "LOADED" + // data is already loaded; just expand node + def = (node._expandNodeDeferred = node.expand()); + + this.onOpen(node.item, node); + + this._state(node, true); + } + + return def; // dojo.Deferred + }, + + ////////////////// Miscellaneous functions //////////////// + + focusNode: function(/* _tree.Node */ node){ + // summary: + // Focus on the specified node (which must be visible) + // tags: + // protected + + // set focus so that the label will be voiced using screen readers + focus.focus(node.labelNode); + }, + + _onNodeFocus: function(/*dijit._Widget*/ node){ + // summary: + // Called when a TreeNode gets focus, either by user clicking + // it, or programatically by arrow key handling code. + // description: + // It marks that the current node is the selected one, and the previously + // selected node no longer is. + + if(node && node != this.lastFocused){ + if(this.lastFocused && !this.lastFocused._destroyed){ + // mark that the previously focsable node is no longer focusable + this.lastFocused.setFocusable(false); + } + + // mark that the new node is the currently selected one + node.setFocusable(true); + this.lastFocused = 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 =====*/){ + // summary: + // Called when mouse leaves a node (onmouseleave event), + // this is monitored by the DND code + }, + + //////////////// Events from the model ////////////////////////// + + _onItemChange: function(/*Item*/ item){ + // summary: + // Processes notification of a change to an item's scalar values like label + var model = this.model, + identity = model.getIdentity(item), + nodes = this._itemNodesMap[identity]; + + if(nodes){ + var label = this.getLabel(item), + tooltip = this.getTooltip(item); + array.forEach(nodes, function(node){ + node.set({ + item: item, // theoretically could be new JS Object representing same item + label: label, + tooltip: tooltip + }); + node._updateItemClasses(item); + }); + } + }, + + _onItemChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ + // summary: + // Processes notification of a change to an item's children + var model = this.model, + identity = model.getIdentity(parent), + parentNodes = this._itemNodesMap[identity]; + + if(parentNodes){ + array.forEach(parentNodes,function(parentNode){ + parentNode.setChildItems(newChildrenList); + }); + } + }, + + _onItemDelete: function(/*Item*/ item){ + // summary: + // Processes notification of a deletion of an item + var model = this.model, + identity = model.getIdentity(item), + nodes = this._itemNodesMap[identity]; + + if(nodes){ + array.forEach(nodes,function(node){ + // Remove node from set of selected nodes (if it's selected) + this.dndController.removeTreeNode(node); + + var parent = node.getParent(); + if(parent){ + // if node has not already been orphaned from a _onSetItem(parent, "children", ..) call... + parent.removeChild(node); + } + node.destroyRecursive(); + }, this); + delete this._itemNodesMap[identity]; + } + }, + + /////////////// Miscellaneous funcs + + _initState: function(){ + // summary: + // Load in which nodes should be opened automatically + 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(node, expanded){ + // summary: + // Query or set expanded state for an node + if(!this.persist){ + return false; + } + var path = array.map(node.getTreePath(), function(item){ + return this.model.getIdentity(item); + }, this).join("/"); + if(arguments.length === 1){ + return this._openedNodes[path]; + }else{ + 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}); + } + }, + + destroy: function(){ + if(this._curSearch){ + clearTimeout(this._curSearch.timer); + delete this._curSearch; + } + if(this.rootNode){ + this.rootNode.destroyRecursive(); + } + if(this.dndController && !lang.isString(this.dndController)){ + this.dndController.destroy(); + } + this.rootNode = null; + this.inherited(arguments); + }, + + destroyRecursive: function(){ + // A tree is treated as a leaf, not as a node with children (like a grid), + // but defining destroyRecursive for back-compat. + this.destroy(); + }, + + resize: function(changeSize){ + if(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 = domGeometry.position(this.tree.indentDetector).w; + + if(this.tree.rootNode){ + // If tree has already loaded, then reset indent for all the nodes + this.tree.rootNode.set('indent', this.showRoot ? 0 : -1); + } + }, + + _createTreeNode: function(/*Object*/ args){ + // summary: + // creates a TreeNode + // description: + // Developers can override this method to define their own TreeNode class; + // 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 TreeNode(args); + }, + + _setTextDirAttr: function(textDir){ + if(textDir && this.textDir!= textDir){ + this._set("textDir",textDir); + this.rootNode.set("textDir", textDir); + } + } +}); + +Tree._TreeNode = TreeNode; // for monkey patching + +return Tree; +}); + +}, +'dijit/form/HorizontalSlider':function(){ +define([ + "dojo/_base/array", // array.forEach + "dojo/_base/declare", // declare + "dojo/dnd/move", + "dojo/_base/event", // event.stop + "dojo/_base/fx", // fx.animateProperty + "dojo/dom-geometry", // domGeometry.position + "dojo/dom-style", // domStyle.getComputedStyle + "dojo/keys", // keys.DOWN_ARROW keys.END keys.HOME keys.LEFT_ARROW keys.PAGE_DOWN keys.PAGE_UP keys.RIGHT_ARROW keys.UP_ARROW + "dojo/_base/lang", // lang.hitch + "dojo/_base/sniff", // has("ie") has("mozilla") + "dojo/dnd/Moveable", // Moveable + "dojo/dnd/Mover", // Mover Mover.prototype.destroy.apply + "dojo/query", // query + "../registry", // registry.findWidgets + "../focus", // focus.focus() + "../typematic", + "./Button", + "./_FormValueWidget", + "../_Container", + "dojo/text!./templates/HorizontalSlider.html" +], function(array, declare, move, event, fx, domGeometry, domStyle, keys, lang, has, Moveable, Mover, query, + registry, focus, typematic, Button, _FormValueWidget, _Container, template){ + +/*===== + var Button = dijit.form.Button; + var _FormValueWidget = dijit.form._FormValueWidget; + var _Container = dijit._Container; +=====*/ + +// module: +// dijit/form/HorizontalSlider +// summary: +// A form widget that allows one to select a value with a horizontally draggable handle + + +var _SliderMover = declare("dijit.form._SliderMover", Mover, { + onMouseMove: function(e){ + var widget = this.widget; + var abspos = widget._abspos; + if(!abspos){ + abspos = widget._abspos = domGeometry.position(widget.sliderBarContainer, true); + widget._setPixelValue_ = lang.hitch(widget, "_setPixelValue"); + widget._isReversed_ = widget._isReversed(); + } + var pixelValue = e[widget._mousePixelCoord] - abspos[widget._startingPixelCoord]; + widget._setPixelValue_(widget._isReversed_ ? (abspos[widget._pixelCount]-pixelValue) : pixelValue, abspos[widget._pixelCount], false); + }, + + destroy: function(e){ + Mover.prototype.destroy.apply(this, arguments); + var widget = this.widget; + widget._abspos = null; + widget._setValueAttr(widget.value, true); + } +}); + +var HorizontalSlider = declare("dijit.form.HorizontalSlider", [_FormValueWidget, _Container], { + // summary: + // A form widget that allows one to select a value with a horizontally draggable handle + + templateString: template, + + // Overrides FormValueWidget.value to indicate numeric value + value: 0, + + // showButtons: [const] Boolean + // Show increment/decrement buttons at the ends of the slider? + showButtons: true, + + // minimum:: [const] Integer + // The minimum value the slider can be set to. + minimum: 0, + + // maximum: [const] Integer + // The maximum value the slider can be set to. + maximum: 100, + + // discreteValues: Integer + // If specified, indicates that the slider handle has only 'discreteValues' possible positions, + // and that after dragging the handle, it will snap to the nearest possible position. + // Thus, the slider has only 'discreteValues' possible values. + // + // For example, if minimum=10, maxiumum=30, and discreteValues=3, then the slider handle has + // three possible positions, representing values 10, 20, or 30. + // + // If discreteValues is not specified or if it's value is higher than the number of pixels + // in the slider bar, then the slider handle can be moved freely, and the slider's value will be + // computed/reported based on pixel position (in this case it will likely be fractional, + // such as 123.456789). + discreteValues: Infinity, + + // pageIncrement: Integer + // If discreteValues is also specified, this indicates the amount of clicks (ie, snap positions) + // that the slider handle is moved via pageup/pagedown keys. + // If discreteValues is not specified, it indicates the number of pixels. + pageIncrement: 2, + + // clickSelect: Boolean + // If clicking the slider bar changes the value or not + clickSelect: true, + + // slideDuration: Number + // The time in ms to take to animate the slider handle from 0% to 100%, + // when clicking the slider bar to make the handle move. + slideDuration: registry.defaultDuration, + + // Map widget attributes to DOMNode attributes. + _setIdAttr: "", // Override _FormWidget which sends id to focusNode + + baseClass: "dijitSlider", + + // Apply CSS classes to up/down arrows and handle per mouse state + cssStateNodes: { + incrementButton: "dijitSliderIncrementButton", + decrementButton: "dijitSliderDecrementButton", + focusNode: "dijitSliderThumb" + }, + + _mousePixelCoord: "pageX", + _pixelCount: "w", + _startingPixelCoord: "x", + _handleOffsetCoord: "left", + _progressPixelSize: "width", + + _onKeyUp: function(/*Event*/ e){ + if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){ return; } + this._setValueAttr(this.value, true); + }, + + _onKeyPress: function(/*Event*/ e){ + if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){ return; } + switch(e.charOrCode){ + case keys.HOME: + this._setValueAttr(this.minimum, false); + break; + case keys.END: + this._setValueAttr(this.maximum, false); + break; + // this._descending === false: if ascending vertical (min on top) + // (this._descending || this.isLeftToRight()): if left-to-right horizontal or descending vertical + case ((this._descending || this.isLeftToRight()) ? keys.RIGHT_ARROW : keys.LEFT_ARROW): + case (this._descending === false ? keys.DOWN_ARROW : keys.UP_ARROW): + case (this._descending === false ? keys.PAGE_DOWN : keys.PAGE_UP): + this.increment(e); + break; + case ((this._descending || this.isLeftToRight()) ? keys.LEFT_ARROW : keys.RIGHT_ARROW): + case (this._descending === false ? keys.UP_ARROW : keys.DOWN_ARROW): + case (this._descending === false ? keys.PAGE_UP : keys.PAGE_DOWN): + this.decrement(e); + break; + default: + return; + } + event.stop(e); + }, + + _onHandleClick: function(e){ + if(this.disabled || this.readOnly){ return; } + if(!has("ie")){ + // make sure you get focus when dragging the handle + // (but don't do on IE because it causes a flicker on mouse up (due to blur then focus) + focus.focus(this.sliderHandle); + } + event.stop(e); + }, + + _isReversed: function(){ + // summary: + // Returns true if direction is from right to left + // tags: + // protected extension + return !this.isLeftToRight(); + }, + + _onBarClick: function(e){ + if(this.disabled || this.readOnly || !this.clickSelect){ return; } + focus.focus(this.sliderHandle); + event.stop(e); + var abspos = domGeometry.position(this.sliderBarContainer, true); + var pixelValue = e[this._mousePixelCoord] - abspos[this._startingPixelCoord]; + this._setPixelValue(this._isReversed() ? (abspos[this._pixelCount] - pixelValue) : pixelValue, abspos[this._pixelCount], true); + this._movable.onMouseDown(e); + }, + + _setPixelValue: function(/*Number*/ pixelValue, /*Number*/ maxPixels, /*Boolean?*/ priorityChange){ + if(this.disabled || this.readOnly){ return; } + var count = this.discreteValues; + if(count <= 1 || count == Infinity){ count = maxPixels; } + count--; + var pixelsPerValue = maxPixels / count; + var wholeIncrements = Math.round(pixelValue / pixelsPerValue); + this._setValueAttr(Math.max(Math.min((this.maximum-this.minimum)*wholeIncrements/count + this.minimum, this.maximum), this.minimum), priorityChange); + }, + + _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){ + // summary: + // Hook so set('value', value) works. + this._set("value", value); + this.valueNode.value = value; + this.focusNode.setAttribute("aria-valuenow", value); + this.inherited(arguments); + var percent = (value - this.minimum) / (this.maximum - this.minimum); + var progressBar = (this._descending === false) ? this.remainingBar : this.progressBar; + var remainingBar = (this._descending === false) ? this.progressBar : this.remainingBar; + if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){ + this._inProgressAnim.stop(true); + } + if(priorityChange && this.slideDuration > 0 && progressBar.style[this._progressPixelSize]){ + // animate the slider + var _this = this; + var props = {}; + var start = parseFloat(progressBar.style[this._progressPixelSize]); + var duration = this.slideDuration * (percent-start/100); + if(duration == 0){ return; } + if(duration < 0){ duration = 0 - duration; } + props[this._progressPixelSize] = { start: start, end: percent*100, units:"%" }; + this._inProgressAnim = fx.animateProperty({ node: progressBar, duration: duration, + onAnimate: function(v){ + remainingBar.style[_this._progressPixelSize] = (100 - parseFloat(v[_this._progressPixelSize])) + "%"; + }, + onEnd: function(){ + delete _this._inProgressAnim; + }, + properties: props + }); + this._inProgressAnim.play(); + }else{ + progressBar.style[this._progressPixelSize] = (percent*100) + "%"; + remainingBar.style[this._progressPixelSize] = ((1-percent)*100) + "%"; + } + }, + + _bumpValue: function(signedChange, /*Boolean?*/ priorityChange){ + if(this.disabled || this.readOnly){ return; } + var s = domStyle.getComputedStyle(this.sliderBarContainer); + var c = domGeometry.getContentBox(this.sliderBarContainer, s); + var count = this.discreteValues; + if(count <= 1 || count == Infinity){ count = c[this._pixelCount]; } + count--; + var value = (this.value - this.minimum) * count / (this.maximum - this.minimum) + signedChange; + if(value < 0){ value = 0; } + if(value > count){ value = count; } + value = value * (this.maximum - this.minimum) / count + this.minimum; + this._setValueAttr(value, priorityChange); + }, + + _onClkBumper: function(val){ + if(this.disabled || this.readOnly || !this.clickSelect){ return; } + this._setValueAttr(val, true); + }, + + _onClkIncBumper: function(){ + this._onClkBumper(this._descending === false ? this.minimum : this.maximum); + }, + + _onClkDecBumper: function(){ + this._onClkBumper(this._descending === false ? this.maximum : this.minimum); + }, + + decrement: function(/*Event*/ e){ + // summary: + // Decrement slider + // tags: + // private + this._bumpValue(e.charOrCode == keys.PAGE_DOWN ? -this.pageIncrement : -1); + }, + + increment: function(/*Event*/ e){ + // summary: + // Increment slider + // tags: + // private + this._bumpValue(e.charOrCode == keys.PAGE_UP ? this.pageIncrement : 1); + }, + + _mouseWheeled: function(/*Event*/ evt){ + // summary: + // Event handler for mousewheel where supported + event.stop(evt); + var janky = !has("mozilla"); + var scroll = evt[(janky ? "wheelDelta" : "detail")] * (janky ? 1 : -1); + this._bumpValue(scroll < 0 ? -1 : 1, true); // negative scroll acts like a decrement + }, + + startup: function(){ + if(this._started){ return; } + + array.forEach(this.getChildren(), function(child){ + if(this[child.container] != this.containerNode){ + this[child.container].appendChild(child.domNode); + } + }, this); + + this.inherited(arguments); + }, + + _typematicCallback: function(/*Number*/ count, /*Object*/ button, /*Event*/ e){ + if(count == -1){ + this._setValueAttr(this.value, true); + }else{ + this[(button == (this._descending? this.incrementButton : this.decrementButton)) ? "decrement" : "increment"](e); + } + }, + + buildRendering: function(){ + this.inherited(arguments); + if(this.showButtons){ + this.incrementButton.style.display=""; + this.decrementButton.style.display=""; + } + + // find any associated label element and add to slider focusnode. + var label = query('label[for="'+this.id+'"]'); + if(label.length){ + label[0].id = (this.id+"_label"); + this.focusNode.setAttribute("aria-labelledby", label[0].id); + } + + this.focusNode.setAttribute("aria-valuemin", this.minimum); + this.focusNode.setAttribute("aria-valuemax", this.maximum); + }, + + postCreate: function(){ + this.inherited(arguments); + + if(this.showButtons){ + this._connects.push(typematic.addMouseListener( + this.decrementButton, this, "_typematicCallback", 25, 500)); + this._connects.push(typematic.addMouseListener( + this.incrementButton, this, "_typematicCallback", 25, 500)); + } + this.connect(this.domNode, !has("mozilla") ? "onmousewheel" : "DOMMouseScroll", "_mouseWheeled"); + + // define a custom constructor for a SliderMover that points back to me + var mover = declare(_SliderMover, { + widget: this + }); + this._movable = new Moveable(this.sliderHandle, {mover: mover}); + + this._layoutHackIE7(); + }, + + destroy: function(){ + this._movable.destroy(); + if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){ + this._inProgressAnim.stop(true); + } + this._supportingWidgets = registry.findWidgets(this.domNode); // tells destroy about pseudo-child widgets (ruler/labels) + this.inherited(arguments); + } +}); + +HorizontalSlider._Mover = _SliderMover; // for monkey patching + +return HorizontalSlider; +}); + +}}}); + +require(["dojo/i18n"], function(i18n){ +i18n._preloadLocalizations("dijit/nls/dijit-all", ["nl-nl","en-us","da","fi-fi","pt-pt","hu","sk","sl","pl","ca","sv","zh-tw","ar","en-gb","he-il","de-de","ko-kr","ja-jp","nb","ru","es-es","th","cs","it-it","pt-br","fr-fr","el","tr","zh-cn"]); +}); +define("dijit/dijit-all", [ + ".", + "./dijit", + "./ColorPalette", + "./Declaration", + "./Dialog", + "./DialogUnderlay", + "./TooltipDialog", + "./Editor", + "./_editor/plugins/FontChoice", + "./_editor/plugins/LinkDialog", + "./Menu", + "./MenuItem", + "./PopupMenuItem", + "./CheckedMenuItem", + "./MenuBar", + "./MenuBarItem", + "./PopupMenuBarItem", + "./MenuSeparator", + "./ProgressBar", + "./TitlePane", + "./Toolbar", + "./Tooltip", + "./Tree", + "./InlineEditBox", + "./form/Form", + "./form/Button", + "./form/DropDownButton", + "./form/ComboButton", + "./form/ToggleButton", + "./form/CheckBox", + "./form/RadioButton", + "./form/TextBox", + "./form/ValidationTextBox", + "./form/CurrencyTextBox", + "./form/DateTextBox", + "./form/TimeTextBox", + "./form/NumberSpinner", + "./form/NumberTextBox", + "./form/ComboBox", + "./form/FilteringSelect", + "./form/MultiSelect", + "./form/Select", + "./form/HorizontalSlider", + "./form/VerticalSlider", + "./form/HorizontalRule", + "./form/VerticalRule", + "./form/HorizontalRuleLabels", + "./form/VerticalRuleLabels", + "./form/SimpleTextarea", + "./form/Textarea", + "./layout/AccordionContainer", + "./layout/ContentPane", + "./layout/BorderContainer", + "./layout/LayoutContainer", + "./layout/LinkPane", + "./layout/SplitContainer", + "./layout/StackContainer", + "./layout/TabContainer" +], function(dijit){ + + // module: + // dijit/dijit-all + // summary: + // A rollup that includes every dijit. You probably don't need this. + + console.warn("dijit-all may include much more code than your application actually requires. We strongly recommend that you investigate a custom build or the web build tool"); + + return dijit; +}); |
